diff --git a/substrate/srml/contract/src/double_map.rs b/substrate/srml/contract/src/double_map.rs index 6867d2a5c6..c79fc7fe2c 100644 --- a/substrate/srml/contract/src/double_map.rs +++ b/substrate/srml/contract/src/double_map.rs @@ -83,6 +83,11 @@ pub trait StorageDoubleMap { unhashed::get(&full_key::(k1, k2)[..]) } + /// Returns `true` if value under the specified keys exists. + fn exists(k1: Self::Key1, k2: Self::Key2) -> bool { + unhashed::exists(&full_key::(k1, k2)[..]) + } + /// Removes all entries that shares the `k1` as the first key. fn remove_prefix(k1: Self::Key1) { unhashed::kill_prefix(&first_part_of_key::(k1)) diff --git a/substrate/srml/contract/src/exec.rs b/substrate/srml/contract/src/exec.rs index 79b6e0eee3..7830a95a79 100644 --- a/substrate/srml/contract/src/exec.rs +++ b/substrate/srml/contract/src/exec.rs @@ -185,9 +185,13 @@ impl<'a, T: Trait> ExecutionContext<'a, T> { /// (transfering endowment), specified by `contract_create` flag, /// or because of a transfer via `call`. /// -/// Note, that the fee is denominated in `T::Balance` units, but +/// NOTE: that the fee is denominated in `T::Balance` units, but /// charged in `T::Gas` from the provided `gas_meter`. This means /// that the actual amount charged might differ. +/// +/// NOTE: that we allow for draining all funds of the contract so it +/// can go below existential deposit, essentially giving a contract +/// the chance to give up it's life. fn transfer<'a, T: Trait>( gas_meter: &mut GasMeter, contract_create: bool, @@ -212,6 +216,7 @@ fn transfer<'a, T: Trait>( return Err("not enough gas to pay transfer fee"); } + // We allow balance to go below the existential deposit here: let from_balance = ctx.overlay.get_balance(transactor); let new_from_balance = match from_balance.checked_sub(&value) { Some(b) => b, diff --git a/substrate/srml/contract/src/tests.rs b/substrate/srml/contract/src/tests.rs index c60d3dd9bf..6bbb6cd11e 100644 --- a/substrate/srml/contract/src/tests.rs +++ b/substrate/srml/contract/src/tests.rs @@ -241,6 +241,40 @@ fn contract_transfer() { }); } +#[test] +fn contract_transfer_to_death() { + const CONTRACT_SHOULD_TRANSFER_VALUE: u64 = 6; + + let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap(); + + with_externalities(&mut ExtBuilder::default().existential_deposit(5).build(), || { + >::insert(1, code_transfer.to_vec()); + + Balances::set_free_balance(&0, 100_000_000); + Balances::increase_total_stake_by(100_000_000); + + Balances::set_free_balance(&1, 6); + Balances::increase_total_stake_by(6); + >::insert(1, b"foo".to_vec(), b"1".to_vec()); + + assert_ok!(Contract::call(Origin::signed(0), 1, 0, 100_000, Vec::new())); + + assert_eq!( + Balances::free_balance(&0), + // 2 * 10 - gas used by the contract (10) multiplied by gas price (2) + // 2 * 135 - base gas fee for call (by transaction) + // 2 * 135 - base gas fee for call (by the contract) + 100_000_000 - (2 * 10) - (2 * 135) - (2 * 135), + ); + + assert!(!>::exists(1)); + assert!(!>::exists(1, b"foo".to_vec())); + assert_eq!(Balances::free_balance(&1), 0); + + assert_eq!(Balances::free_balance(&9), CONTRACT_SHOULD_TRANSFER_VALUE); + }); +} + #[test] fn contract_transfer_takes_creation_fee() { const CONTRACT_SHOULD_TRANSFER_VALUE: u64 = 6;