Add ext_terminate (#5234)

With this patch forward this will be the only way for
a contract to destroy itself. This patch therefore changes
the semantics of all other contract initiated balance
transfers to fail if they would bring the caller below the
existential deposit.
This commit is contained in:
Alexander Theißen
2020-03-17 11:51:34 +01:00
committed by GitHub
parent e91d4be998
commit 601f2538c6
5 changed files with 377 additions and 97 deletions
+100 -21
View File
@@ -128,6 +128,13 @@ pub trait Ext {
gas_meter: &mut GasMeter<Self::T>,
) -> Result<(), DispatchError>;
/// Transfer all funds to `beneficiary` and delete the contract.
fn terminate(
&mut self,
beneficiary: &AccountIdOf<Self::T>,
gas_meter: &mut GasMeter<Self::T>,
) -> Result<(), DispatchError>;
/// Call (possibly transferring some amount of funds) into the specified account.
fn call(
&mut self,
@@ -428,20 +435,6 @@ where
gas_meter,
)?;
// Destroy contract if insufficient remaining balance.
if nested.overlay.get_balance(&dest) < nested.config.existential_deposit {
let parent = nested.parent
.expect("a nested execution context must have a parent; qed");
if parent.is_live(&dest) {
return Err(ExecError {
reason: "contract cannot be destroyed during recursive execution".into(),
buffer: output.data,
});
}
nested.overlay.destroy_contract(&dest);
}
Ok(output)
}
None => Ok(ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() }),
@@ -535,9 +528,37 @@ where
Ok((dest, output))
}
fn new_call_context<'b>(&'b mut self, caller: T::AccountId, value: BalanceOf<T>)
-> CallContext<'b, 'a, T, V, L>
{
pub fn terminate(
&mut self,
beneficiary: &T::AccountId,
gas_meter: &mut GasMeter<T>,
) -> Result<(), DispatchError> {
let self_id = self.self_account.clone();
let value = self.overlay.get_balance(&self_id);
if let Some(parent) = self.parent {
if parent.is_live(&self_id) {
return Err(DispatchError::Other(
"Cannot terminate a contract that is present on the call stack",
));
}
}
transfer(
gas_meter,
TransferCause::Terminate,
&self_id,
beneficiary,
value,
self,
)?;
self.overlay.destroy_contract(&self_id);
Ok(())
}
fn new_call_context<'b>(
&'b mut self,
caller: T::AccountId,
value: BalanceOf<T>,
) -> CallContext<'b, 'a, T, V, L> {
let timestamp = self.timestamp.clone();
let block_number = self.block_number.clone();
CallContext {
@@ -606,6 +627,7 @@ impl<T: Trait> Token<T> for TransferFeeToken<BalanceOf<T>> {
enum TransferCause {
Call,
Instantiate,
Terminate,
}
/// Transfer some funds from `transactor` to `dest`.
@@ -642,7 +664,7 @@ fn transfer<'a, T: Trait, V: Vm<T>, L: Loader<T>>(
Instantiate => ContractInstantiate,
// Otherwise the fee is to transfer to an account.
Call => TransferFeeKind::Transfer,
Call | Terminate => TransferFeeKind::Transfer,
};
TransferFeeToken {
kind,
@@ -664,11 +686,19 @@ fn transfer<'a, T: Trait, V: Vm<T>, L: Loader<T>>(
if to_balance.is_zero() && value < ctx.config.existential_deposit {
Err("value too low to create account")?
}
// Only ext_terminate is allowed to bring the sender below the existential deposit
let required_balance = match cause {
Terminate => 0.into(),
_ => ctx.config.existential_deposit
};
T::Currency::ensure_can_withdraw(
transactor,
value,
WithdrawReason::Transfer.into(),
new_from_balance,
new_from_balance.checked_sub(&required_balance)
.ok_or("brings sender below existential deposit")?,
)?;
let new_to_balance = match to_balance.checked_add(&value) {
@@ -740,6 +770,14 @@ where
self.ctx.transfer(to.clone(), value, gas_meter)
}
fn terminate(
&mut self,
beneficiary: &AccountIdOf<Self::T>,
gas_meter: &mut GasMeter<Self::T>,
) -> Result<(), DispatchError> {
self.ctx.terminate(beneficiary, gas_meter)
}
fn call(
&mut self,
to: &T::AccountId,
@@ -1321,7 +1359,7 @@ mod tests {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
ctx.overlay.set_balance(&ALICE, 1);
ctx.overlay.set_balance(&ALICE, 100);
let result = ctx.instantiate(
1,
@@ -1591,6 +1629,7 @@ mod tests {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
ctx.overlay.set_balance(&ALICE, 1000);
ctx.overlay.set_balance(&BOB, 100);
ctx.overlay.instantiate_contract(&BOB, instantiator_ch).unwrap();
assert_matches!(
@@ -1650,6 +1689,7 @@ mod tests {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
ctx.overlay.set_balance(&ALICE, 1000);
ctx.overlay.set_balance(&BOB, 100);
ctx.overlay.instantiate_contract(&BOB, instantiator_ch).unwrap();
assert_matches!(
@@ -1668,6 +1708,45 @@ mod tests {
});
}
#[test]
fn termination_from_instantiate_fails() {
let vm = MockVm::new();
let mut loader = MockLoader::empty();
let terminate_ch = loader.insert(|mut ctx| {
ctx.ext.terminate(&ALICE, &mut ctx.gas_meter).unwrap();
exec_success()
});
ExtBuilder::default()
.existential_deposit(15)
.build()
.execute_with(|| {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
ctx.overlay.set_balance(&ALICE, 1000);
assert_matches!(
ctx.instantiate(
100,
&mut GasMeter::<Test>::with_limit(10000, 1),
&terminate_ch,
vec![],
),
Err(ExecError {
reason: DispatchError::Other("insufficient remaining balance"),
buffer
}) if buffer == Vec::<u8>::new()
);
assert_eq!(
&ctx.events(),
&[]
);
});
}
#[test]
fn rent_allowance() {
let vm = MockVm::new();
@@ -1683,7 +1762,7 @@ mod tests {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
ctx.overlay.set_balance(&ALICE, 1);
ctx.overlay.set_balance(&ALICE, 100);
let result = ctx.instantiate(
1,