Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
21 changes: 4 additions & 17 deletions frame/contracts/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use crate::{
storage::{self, WriteOutcome},
BalanceOf, CodeHash, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf,
DebugBufferVec, Determinism, Error, Event, Nonce, Origin, Pallet as Contracts, Schedule,
System, WasmBlob, LOG_TARGET,
WasmBlob, LOG_TARGET,
};
use frame_support::{
crypto::ecdsa::ECDSAExt,
Expand All @@ -33,7 +33,7 @@ use frame_support::{
storage::{with_transaction, TransactionOutcome},
traits::{
fungible::{Inspect, Mutate},
tokens::{Fortitude::Polite, Preservation},
tokens::Preservation,
Contains, OriginTrait, Randomness, Time,
},
weights::Weight,
Expand Down Expand Up @@ -1283,21 +1283,8 @@ where
}
let frame = self.top_frame_mut();
let info = frame.terminate();
frame.nested_storage.terminate(&info);

// During the termination process we first transfer all the free balance of the contract to
// the beneficiary. And then we release the held balance under the `StorageDepositReserve`
// reason to be transferred back.
// We need a provider so that the `reducible_balance` calculation includes the `ed` and the
// `transfer` succeeds while keeping the account alive. The provider is removed once the
// storage deposit process is finished.
System::<T>::inc_providers(&frame.account_id);
Self::transfer(
Preservation::Expendable,
&frame.account_id,
beneficiary,
T::Currency::reducible_balance(&frame.account_id, Preservation::Expendable, Polite),
)?;
frame.nested_storage.terminate(&info, beneficiary);

info.queue_trie_for_deletion();
ContractInfoOf::<T>::remove(&frame.account_id);
E::decrement_refcount(info.code_hash);
Expand Down
88 changes: 60 additions & 28 deletions frame/contracts/src/storage/meter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
//! This module contains functions to meter the storage deposit.

use crate::{
storage::ContractInfo, BalanceOf, CodeInfo, Config, Error, Event, HoldReason, Inspect, Origin,
Pallet, StorageDeposit as Deposit, System, LOG_TARGET,
storage::ContractInfo, AccountIdOf, BalanceOf, CodeInfo, Config, Error, Event, HoldReason,
Inspect, Origin, Pallet, StorageDeposit as Deposit, System, LOG_TARGET,
};

use frame_support::{
Expand Down Expand Up @@ -88,7 +88,7 @@ pub trait Ext<T: Config> {
origin: &T::AccountId,
contract: &T::AccountId,
amount: &DepositOf<T>,
terminated: bool,
status: &ContractStatus<T>,
) -> Result<(), DispatchError>;
}

Expand Down Expand Up @@ -224,6 +224,18 @@ impl Diff {
}
}

/// The beneficiary of a contract termination.
type BeneficiaryOf<T> = AccountIdOf<T>;

/// The status of a contract.
///
/// In case of termination the beneficiary is indicated.
#[derive(RuntimeDebugNoBound, Clone, PartialEq, Eq)]
pub enum ContractStatus<T: Config> {
Alive,
Terminated(BeneficiaryOf<T>),
}

/// Records information to charge or refund a plain account.
///
/// All the charges are deferred to the end of a whole call stack. Reason is that by doing
Expand All @@ -237,7 +249,7 @@ impl Diff {
struct Charge<T: Config> {
contract: T::AccountId,
amount: DepositOf<T>,
terminated: bool,
status: ContractStatus<T>,
}

/// Records the storage changes of a storage meter.
Expand All @@ -250,15 +262,15 @@ enum Contribution<T: Config> {
Checked(DepositOf<T>),
/// The contract was terminated. In this process the [`Diff`] was converted into a [`Deposit`]
/// in order to calculate the refund.
Terminated(DepositOf<T>),
Terminated(DepositOf<T>, BeneficiaryOf<T>),
}

impl<T: Config> Contribution<T> {
/// See [`Diff::update_contract`].
fn update_contract(&self, info: Option<&mut ContractInfo<T>>) -> DepositOf<T> {
match self {
Self::Alive(diff) => diff.update_contract::<T>(info),
Self::Terminated(deposit) | Self::Checked(deposit) => deposit.clone(),
Self::Terminated(deposit, _) | Self::Checked(deposit) => deposit.clone(),
}
}
}
Expand Down Expand Up @@ -325,7 +337,7 @@ where
self.charges.push(Charge {
contract: contract.clone(),
amount: own_deposit,
terminated: absorbed.is_terminated(),
status: absorbed.is_terminated(),
});
}
}
Expand All @@ -341,8 +353,12 @@ where
}

/// True if the contract is terminated.
fn is_terminated(&self) -> bool {
matches!(self.own_contribution, Contribution::Terminated(_))
fn is_terminated(&self) -> ContractStatus<T> {
match &self.own_contribution {
Contribution::Terminated(_, beneficiary) =>
ContractStatus::Terminated(beneficiary.clone()),
_ => ContractStatus::Alive,
}
}
}

Expand Down Expand Up @@ -386,10 +402,10 @@ where
Origin::Signed(o) => o,
};
for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Refund(_))) {
E::charge(origin, &charge.contract, &charge.amount, charge.terminated)?;
E::charge(origin, &charge.contract, &charge.amount, &charge.status)?;
}
for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Charge(_))) {
E::charge(origin, &charge.contract, &charge.amount, charge.terminated)?;
E::charge(origin, &charge.contract, &charge.amount, &charge.status)?;
}
Ok(self.total_deposit)
}
Expand Down Expand Up @@ -417,7 +433,7 @@ where
/// deposit charge separately from the storage charge.
pub fn charge_deposit(&mut self, contract: T::AccountId, amount: DepositOf<T>) {
self.total_deposit = self.total_deposit.saturating_add(&amount);
self.charges.push(Charge { contract, amount, terminated: false });
self.charges.push(Charge { contract, amount, status: ContractStatus::Alive });
}

/// Charge from `origin` a storage deposit plus the ed for contract instantiation.
Expand Down Expand Up @@ -447,21 +463,30 @@ where
// We need to make sure that the contract's account exists.
T::Currency::transfer(origin, contract, ed, Preservation::Preserve)?;

System::<T>::inc_consumers(&contract)?;

// Normally, deposit charges are deferred to be able to coalesce them with refunds.
// However, we need to charge immediately so that the account is created before
// charges possibly below the ed are collected and fail.
E::charge(origin, contract, &deposit.saturating_sub(&Deposit::Charge(ed)), false)?;
E::charge(
origin,
contract,
&deposit.saturating_sub(&Deposit::Charge(ed)),
&ContractStatus::Alive,
)?;

Ok(deposit)
}

/// Call to tell the meter that the currently executing contract was executed.
///
/// This will manipulate the meter so that all storage deposit accumulated in
/// `contract_info` will be refunded to the `origin` of the meter.
pub fn terminate(&mut self, info: &ContractInfo<T>) {
/// `contract_info` will be refunded to the `origin` of the meter. And the free
/// (`reducible_balance`) will be sent to the `beneficiary`.
pub fn terminate(&mut self, info: &ContractInfo<T>, beneficiary: &T::AccountId) {
debug_assert!(self.is_alive());
self.own_contribution = Contribution::Terminated(Deposit::Refund(info.total_deposit()));
self.own_contribution =
Contribution::Terminated(Deposit::Refund(info.total_deposit()), beneficiary.clone());
}

/// [`Self::charge`] does not enforce the storage limit since we want to do this check as late
Expand Down Expand Up @@ -530,7 +555,7 @@ impl<T: Config> Ext<T> for ReservingExt {
origin: &T::AccountId,
contract: &T::AccountId,
amount: &DepositOf<T>,
terminated: bool,
status: &ContractStatus<T>,
) -> Result<(), DispatchError> {
match amount {
Deposit::Charge(amount) | Deposit::Refund(amount) if amount.is_zero() => return Ok(()),
Expand Down Expand Up @@ -588,8 +613,15 @@ impl<T: Config> Ext<T> for ReservingExt {
}
},
}
if terminated {
System::<T>::dec_providers(&contract)?;
if let ContractStatus::<T>::Terminated(beneficiary) = status {
System::<T>::dec_consumers(&contract);
// Whatever is left in the contract is sent to the termination beneficiary.
T::Currency::transfer(
&contract,
beneficiary,
T::Currency::reducible_balance(&contract, Preservation::Expendable, Polite),
Preservation::Expendable,
)?;
}
Ok(())
}
Expand Down Expand Up @@ -629,7 +661,7 @@ mod tests {
origin: AccountIdOf<Test>,
contract: AccountIdOf<Test>,
amount: DepositOf<Test>,
terminated: bool,
status: ContractStatus<Test>,
}

#[derive(Default, Debug, PartialEq, Eq, Clone)]
Expand Down Expand Up @@ -663,14 +695,14 @@ mod tests {
origin: &AccountIdOf<Test>,
contract: &AccountIdOf<Test>,
amount: &DepositOf<Test>,
terminated: bool,
status: &ContractStatus<Test>,
) -> Result<(), DispatchError> {
TestExtTestValue::mutate(|ext| {
ext.charges.push(Charge {
origin: origin.clone(),
contract: contract.clone(),
amount: amount.clone(),
terminated,
status: status.clone(),
})
});
Ok(())
Expand Down Expand Up @@ -757,19 +789,19 @@ mod tests {
origin: ALICE,
contract: CHARLIE,
amount: Deposit::Refund(10),
terminated: false,
status: ContractStatus::Alive,
},
Charge {
origin: ALICE,
contract: CHARLIE,
amount: Deposit::Refund(20),
terminated: false,
status: ContractStatus::Alive,
},
Charge {
origin: ALICE,
contract: BOB,
amount: Deposit::Charge(2),
terminated: false,
status: ContractStatus::Alive,
},
],
},
Expand Down Expand Up @@ -848,13 +880,13 @@ mod tests {
origin: ALICE,
contract: CHARLIE,
amount: Deposit::Refund(119),
terminated: true,
status: ContractStatus::Terminated(CHARLIE),
},
Charge {
origin: ALICE,
contract: BOB,
amount: Deposit::Charge(12),
terminated: false,
status: ContractStatus::Alive,
},
],
},
Expand Down Expand Up @@ -890,7 +922,7 @@ mod tests {
let mut nested1 = nested0.nested(BalanceOf::<Test>::zero());
nested1.charge(&Diff { items_removed: 5, ..Default::default() });
nested1.charge(&Diff { bytes_added: 20, ..Default::default() });
nested1.terminate(&nested1_info);
nested1.terminate(&nested1_info, &CHARLIE);
nested0.enforce_limit(Some(&mut nested1_info)).unwrap();
nested0.absorb(nested1, &CHARLIE, None);

Expand Down
18 changes: 9 additions & 9 deletions frame/contracts/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1598,15 +1598,6 @@ fn self_destruct_works() {
pretty_assertions::assert_eq!(
System::events(),
vec![
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
from: addr.clone(),
to: DJANGO,
amount: 100_000 + min_balance,
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Contracts(crate::Event::Terminated {
Expand Down Expand Up @@ -1641,6 +1632,15 @@ fn self_destruct_works() {
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
from: addr.clone(),
to: DJANGO,
amount: 100_000 + min_balance,
}),
topics: vec![],
},
],
);
});
Expand Down