#[cfg(test)]
mod tests;
use super::{Nested, Root, State};
use crate::{
storage::ContractInfo, BalanceOf, Config, ExecConfig, ExecOrigin as Origin, HoldReason, Pallet,
StorageDeposit as Deposit,
};
use alloc::vec::Vec;
use core::{marker::PhantomData, mem};
use frame_support::{traits::Get, DefaultNoBound, RuntimeDebugNoBound};
use sp_runtime::{
traits::{Saturating, Zero},
DispatchError, FixedPointNumber, FixedU128,
};
#[cfg(test)]
use num_traits::Bounded;
pub type DepositOf<T> = Deposit<BalanceOf<T>>;
pub type Meter<T> = RawMeter<T, ReservingExt, Root>;
pub type GenericMeter<T, S> = RawMeter<T, ReservingExt, S>;
pub trait Ext<T: Config> {
fn charge(
origin: &T::AccountId,
contract: &T::AccountId,
amount: &DepositOf<T>,
exec_config: &ExecConfig<T>,
) -> Result<(), DispatchError>;
}
pub enum ReservingExt {}
#[derive(DefaultNoBound, RuntimeDebugNoBound)]
pub struct RawMeter<T: Config, E, S: State> {
pub(crate) limit: Option<BalanceOf<T>>,
total_deposit: DepositOf<T>,
own_contribution: Contribution<T>,
charges: Vec<Charge<T>>,
max_charged: BalanceOf<T>,
pub(crate) is_root: bool,
_phantom: PhantomData<(E, S)>,
}
#[derive(Default, RuntimeDebugNoBound)]
pub struct Diff {
pub bytes_added: u32,
pub bytes_removed: u32,
pub items_added: u32,
pub items_removed: u32,
}
impl Diff {
pub fn update_contract<T: Config>(&self, info: Option<&mut ContractInfo<T>>) -> DepositOf<T> {
let per_byte = T::DepositPerByte::get();
let per_item = T::DepositPerChildTrieItem::get();
let bytes_added = self.bytes_added.saturating_sub(self.bytes_removed);
let items_added = self.items_added.saturating_sub(self.items_removed);
let mut bytes_deposit = Deposit::Charge(per_byte.saturating_mul((bytes_added).into()));
let mut items_deposit = Deposit::Charge(per_item.saturating_mul((items_added).into()));
let info = if let Some(info) = info {
info
} else {
return bytes_deposit.saturating_add(&items_deposit)
};
let bytes_removed = self.bytes_removed.saturating_sub(self.bytes_added);
let items_removed = self.items_removed.saturating_sub(self.items_added);
let ratio = FixedU128::checked_from_rational(bytes_removed, info.storage_bytes)
.unwrap_or_default()
.min(FixedU128::from_u32(1));
bytes_deposit = bytes_deposit
.saturating_add(&Deposit::Refund(ratio.saturating_mul_int(info.storage_byte_deposit)));
let ratio = FixedU128::checked_from_rational(items_removed, info.storage_items)
.unwrap_or_default()
.min(FixedU128::from_u32(1));
items_deposit = items_deposit
.saturating_add(&Deposit::Refund(ratio.saturating_mul_int(info.storage_item_deposit)));
info.storage_bytes =
info.storage_bytes.saturating_add(bytes_added).saturating_sub(bytes_removed);
info.storage_items =
info.storage_items.saturating_add(items_added).saturating_sub(items_removed);
match &bytes_deposit {
Deposit::Charge(amount) =>
info.storage_byte_deposit = info.storage_byte_deposit.saturating_add(*amount),
Deposit::Refund(amount) =>
info.storage_byte_deposit = info.storage_byte_deposit.saturating_sub(*amount),
}
match &items_deposit {
Deposit::Charge(amount) =>
info.storage_item_deposit = info.storage_item_deposit.saturating_add(*amount),
Deposit::Refund(amount) =>
info.storage_item_deposit = info.storage_item_deposit.saturating_sub(*amount),
}
bytes_deposit.saturating_add(&items_deposit)
}
}
impl Diff {
fn saturating_add(&self, rhs: &Self) -> Self {
Self {
bytes_added: self.bytes_added.saturating_add(rhs.bytes_added),
bytes_removed: self.bytes_removed.saturating_add(rhs.bytes_removed),
items_added: self.items_added.saturating_add(rhs.items_added),
items_removed: self.items_removed.saturating_add(rhs.items_removed),
}
}
}
#[derive(RuntimeDebugNoBound, Clone, PartialEq, Eq)]
pub enum ContractState<T: Config> {
Alive { amount: DepositOf<T> },
Terminated,
}
#[derive(RuntimeDebugNoBound, Clone)]
struct Charge<T: Config> {
contract: T::AccountId,
state: ContractState<T>,
}
#[derive(RuntimeDebugNoBound)]
enum Contribution<T: Config> {
Alive(Diff),
Checked(DepositOf<T>),
}
impl<T: Config> Contribution<T> {
fn update_contract(&self, info: Option<&mut ContractInfo<T>>) -> DepositOf<T> {
match self {
Self::Alive(diff) => diff.update_contract::<T>(info),
Self::Checked(deposit) => deposit.clone(),
}
}
}
impl<T: Config> Default for Contribution<T> {
fn default() -> Self {
Self::Alive(Default::default())
}
}
impl<T, E, S> RawMeter<T, E, S>
where
T: Config,
E: Ext<T>,
S: State,
{
pub fn nested(&self, mut limit: Option<BalanceOf<T>>) -> RawMeter<T, E, Nested> {
if let (Some(new_limit), Some(old_limit)) = (limit, self.limit) {
limit = Some(new_limit.min(old_limit));
}
RawMeter { limit, ..Default::default() }
}
pub fn reset(&mut self) {
self.own_contribution = Default::default();
self.total_deposit = Default::default();
self.charges = Default::default();
self.max_charged = Default::default();
}
pub fn absorb(
&mut self,
absorbed: RawMeter<T, E, Nested>,
contract: &T::AccountId,
info: Option<&mut ContractInfo<T>>,
) {
self.max_charged = self
.max_charged
.max(self.consumed().saturating_add(&absorbed.max_charged()).charge_or_zero());
let own_deposit = absorbed.own_contribution.update_contract(info);
self.total_deposit = self
.total_deposit
.saturating_add(&absorbed.total_deposit)
.saturating_add(&own_deposit);
self.charges.extend_from_slice(&absorbed.charges);
self.recalulculate_max_charged();
if !own_deposit.is_zero() {
self.charges.push(Charge {
contract: contract.clone(),
state: ContractState::Alive { amount: own_deposit },
});
}
}
pub fn absorb_only_max_charged(&mut self, absorbed: RawMeter<T, E, Nested>) {
self.max_charged = self
.max_charged
.max(self.consumed().saturating_add(&absorbed.max_charged()).charge_or_zero());
}
pub fn record_charge(&mut self, amount: &DepositOf<T>) {
self.total_deposit = self.total_deposit.saturating_add(amount);
self.recalulculate_max_charged();
}
pub fn consumed(&self) -> DepositOf<T> {
self.total_deposit.saturating_add(&self.own_contribution.update_contract(None))
}
pub fn max_charged(&self) -> DepositOf<T> {
Deposit::Charge(self.max_charged)
}
fn recalulculate_max_charged(&mut self) {
self.max_charged = self.max_charged.max(self.consumed().charge_or_zero());
}
#[cfg(test)]
pub fn available(&self) -> BalanceOf<T> {
self.consumed()
.available(&self.limit.unwrap_or(BalanceOf::<T>::max_value()))
.unwrap_or_default()
}
}
impl<T, E> RawMeter<T, E, Root>
where
T: Config,
E: Ext<T>,
{
pub fn new(limit: Option<BalanceOf<T>>) -> Self {
Self {
limit,
is_root: true,
own_contribution: Contribution::Checked(Default::default()),
..Default::default()
}
}
pub fn execute_postponed_deposits(
&mut self,
origin: &Origin<T>,
exec_config: &ExecConfig<T>,
) -> Result<DepositOf<T>, DispatchError> {
let origin = match origin {
Origin::Root => return Ok(Deposit::Charge(Zero::zero())),
Origin::Signed(o) => o,
};
self.charges.sort_by(|a, b| a.contract.cmp(&b.contract));
self.charges = {
let mut coalesced: Vec<Charge<T>> = Vec::with_capacity(self.charges.len());
for mut ch in mem::take(&mut self.charges) {
if let Some(last) = coalesced.last_mut() {
if last.contract == ch.contract {
match (&mut last.state, &mut ch.state) {
(
ContractState::Alive { amount: last_amount },
ContractState::Alive { amount: ch_amount },
) => {
*last_amount = last_amount.saturating_add(&ch_amount);
},
(ContractState::Alive { amount }, ContractState::Terminated) |
(ContractState::Terminated, ContractState::Alive { amount }) => {
self.total_deposit = self.total_deposit.saturating_sub(&amount);
last.state = ContractState::Terminated;
},
(ContractState::Terminated, ContractState::Terminated) =>
debug_assert!(
false,
"We never emit two terminates for the same contract."
),
}
continue;
}
}
coalesced.push(ch);
}
coalesced
};
for charge in self.charges.iter() {
if let ContractState::Alive { amount: amount @ Deposit::Refund(_) } = &charge.state {
E::charge(origin, &charge.contract, amount, exec_config)?;
}
}
for charge in self.charges.iter() {
if let ContractState::Alive { amount: amount @ Deposit::Charge(_) } = &charge.state {
E::charge(origin, &charge.contract, amount, exec_config)?;
}
}
Ok(self.total_deposit.clone())
}
pub fn terminate(&mut self, contract: T::AccountId, refunded: BalanceOf<T>) {
self.total_deposit = self.total_deposit.saturating_add(&Deposit::Refund(refunded));
self.charges.push(Charge { contract, state: ContractState::Terminated });
}
}
impl<T: Config, E: Ext<T>> RawMeter<T, E, Nested> {
pub fn charge(&mut self, diff: &Diff) {
match &mut self.own_contribution {
Contribution::Alive(own) => {
*own = own.saturating_add(diff);
self.recalulculate_max_charged();
},
_ => panic!("Charge is never called after termination; qed"),
};
}
pub fn charge_deposit(&mut self, contract: T::AccountId, amount: DepositOf<T>) {
self.record_charge(&amount);
self.charges.push(Charge { contract, state: ContractState::Alive { amount } });
}
pub fn finalize_own_contributions(&mut self, info: Option<&mut ContractInfo<T>>) {
let deposit = self.own_contribution.update_contract(info);
self.own_contribution = Contribution::Checked(deposit);
}
}
impl<T: Config> Ext<T> for ReservingExt {
fn charge(
origin: &T::AccountId,
contract: &T::AccountId,
amount: &DepositOf<T>,
exec_config: &ExecConfig<T>,
) -> Result<(), DispatchError> {
match amount {
Deposit::Charge(amount) | Deposit::Refund(amount) if amount.is_zero() => (),
Deposit::Charge(amount) => {
<Pallet<T>>::charge_deposit(
Some(HoldReason::StorageDepositReserve),
origin,
contract,
*amount,
exec_config,
)?;
},
Deposit::Refund(amount) => {
<Pallet<T>>::refund_deposit(
HoldReason::StorageDepositReserve,
contract,
origin,
*amount,
Some(exec_config),
)?;
},
}
Ok(())
}
}