diff --git a/substrate/node/runtime/src/lib.rs b/substrate/node/runtime/src/lib.rs index e17e0085f0365647af1789b83248ee9b8bd92be5..e80d5fa83ab381278c83756b2e4a45b7781694d4 100644 --- a/substrate/node/runtime/src/lib.rs +++ b/substrate/node/runtime/src/lib.rs @@ -80,8 +80,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to equal spec_version. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 148, - impl_version: 148, + spec_version: 149, + impl_version: 149, apis: RUNTIME_API_VERSIONS, }; diff --git a/substrate/srml/contracts/src/account_db.rs b/substrate/srml/contracts/src/account_db.rs index 5cfd0d3a65fa8dc77c5584cefdcbd730cc401092..9f8b29e0bd897594007b004878da5cdc36ba7007 100644 --- a/substrate/srml/contracts/src/account_db.rs +++ b/substrate/srml/contracts/src/account_db.rs @@ -34,11 +34,57 @@ use system; // the trie_id in the overlay, thus we provide an overlay on the fields // specifically. pub struct ChangeEntry<T: Trait> { + /// If Some(_), then the account balance is modified to the value. If None and `reset` is false, + /// the balance unmodified. If None and `reset` is true, the balance is reset to 0. balance: Option<BalanceOf<T>>, - /// If None, the code_hash remains untouched. + /// If Some(_), then a contract is created with the code hash. If None and `reset` is false, + /// then the contract code is unmodified. If None and `reset` is true, the contract is deleted. code_hash: Option<CodeHash<T>>, + /// If Some(_), then the rent allowance is set to the value. If None and `reset` is false, then + /// the rent allowance is unmodified. If None and `reset` is true, the contract is deleted. rent_allowance: Option<BalanceOf<T>>, storage: BTreeMap<StorageKey, Option<Vec<u8>>>, + /// If true, indicates that the existing contract and all its storage entries should be removed + /// and replaced with the fields on this change entry. Otherwise, the fields on this change + /// entry are updates merged into the existing contract info and storage. + reset: bool, +} + +impl<T: Trait> ChangeEntry<T> { + fn balance(&self) -> Option<BalanceOf<T>> { + self.balance.or_else(|| { + if self.reset { + Some(<BalanceOf<T>>::zero()) + } else { + None + } + }) + } + + fn code_hash(&self) -> Option<Option<CodeHash<T>>> { + if self.reset { + Some(self.code_hash) + } else { + self.code_hash.map(Some) + } + } + + fn rent_allowance(&self) -> Option<Option<BalanceOf<T>>> { + if self.reset { + Some(self.rent_allowance) + } else { + self.rent_allowance.map(Some) + } + } + + fn storage(&self, location: &StorageKey) -> Option<Option<Vec<u8>>> { + let value = self.storage.get(location).cloned(); + if self.reset { + Some(value.unwrap_or(None)) + } else { + value + } + } } // Cannot derive(Default) since it erroneously bounds T by Default. @@ -49,6 +95,7 @@ impl<T: Trait> Default for ChangeEntry<T> { balance: Default::default(), code_hash: Default::default(), storage: Default::default(), + reset: false, } } } @@ -98,7 +145,7 @@ impl<T: Trait> AccountDb<T> for DirectAccountDb { fn commit(&mut self, s: ChangeSet<T>) { let mut total_imbalance = SignedImbalance::zero(); for (address, changed) in s.into_iter() { - if let Some(balance) = changed.balance { + if let Some(balance) = changed.balance() { let (imbalance, outcome) = T::Currency::make_free_balance_be(&address, balance); total_imbalance = total_imbalance.merge(imbalance); if let UpdateBalanceOutcome::AccountKilled = outcome { @@ -109,9 +156,10 @@ impl<T: Trait> AccountDb<T> for DirectAccountDb { } } - if changed.code_hash.is_some() - || changed.rent_allowance.is_some() + if changed.code_hash().is_some() + || changed.rent_allowance().is_some() || !changed.storage.is_empty() + || changed.reset { let old_info = match <ContractInfoOf<T>>::get(&address) { Some(ContractInfo::Alive(alive)) => Some(alive), @@ -120,20 +168,40 @@ impl<T: Trait> AccountDb<T> for DirectAccountDb { Some(ContractInfo::Tombstone(_)) => continue, }; - let mut new_info = if let Some(info) = old_info.clone() { - info - } else if let Some(code_hash) = changed.code_hash { - AliveContractInfo::<T> { - code_hash, - storage_size: T::StorageSizeOffset::get(), - trie_id: <T as Trait>::TrieIdGenerator::trie_id(&address), - deduct_block: <system::Module<T>>::block_number(), - rent_allowance: <BalanceOf<T>>::max_value(), - last_write: None, + let mut new_info = match (changed.reset, old_info.clone(), changed.code_hash) { + // Existing contract is being modified. + (false, Some(info), _) => info, + // Existing contract is being removed. + (true, Some(info), None) => { + child::kill_storage(&info.trie_id); + <ContractInfoOf<T>>::remove(&address); + continue; } - } else { - // No contract exist and no code_hash provided - continue; + // Existing contract is being replaced by a new one. + (true, Some(info), Some(code_hash)) => { + child::kill_storage(&info.trie_id); + AliveContractInfo::<T> { + code_hash, + storage_size: T::StorageSizeOffset::get(), + trie_id: <T as Trait>::TrieIdGenerator::trie_id(&address), + deduct_block: <system::Module<T>>::block_number(), + rent_allowance: <BalanceOf<T>>::max_value(), + last_write: None, + } + } + // New contract is being created. + (_, None, Some(code_hash)) => { + AliveContractInfo::<T> { + code_hash, + storage_size: T::StorageSizeOffset::get(), + trie_id: <T as Trait>::TrieIdGenerator::trie_id(&address), + deduct_block: <system::Module<T>>::block_number(), + rent_allowance: <BalanceOf<T>>::max_value(), + last_write: None, + } + } + // There is no existing at the address nor a new one to be created. + (_, None, None) => continue, }; if let Some(rent_allowance) = changed.rent_allowance { @@ -227,6 +295,19 @@ impl<'a, T: Trait> OverlayAccountDb<'a, T> { Ok(()) } + + /// Mark a contract as deleted. + pub fn destroy_contract(&mut self, account: &T::AccountId) { + let mut local = self.local.borrow_mut(); + local.insert( + account.clone(), + ChangeEntry { + reset: true, + ..Default::default() + } + ); + } + /// Assume contract exists pub fn set_rent_allowance(&mut self, account: &T::AccountId, rent_allowance: BalanceOf<T>) { self.local @@ -254,36 +335,35 @@ impl<'a, T: Trait> AccountDb<T> for OverlayAccountDb<'a, T> { self.local .borrow() .get(account) - .and_then(|a| a.storage.get(location)) - .cloned() + .and_then(|changes| changes.storage(location)) .unwrap_or_else(|| self.underlying.get_storage(account, trie_id, location)) } fn get_code_hash(&self, account: &T::AccountId) -> Option<CodeHash<T>> { self.local .borrow() .get(account) - .and_then(|changes| changes.code_hash) - .or_else(|| self.underlying.get_code_hash(account)) + .and_then(|changes| changes.code_hash()) + .unwrap_or_else(|| self.underlying.get_code_hash(account)) } fn get_rent_allowance(&self, account: &T::AccountId) -> Option<BalanceOf<T>> { self.local .borrow() .get(account) - .and_then(|changes| changes.rent_allowance) - .or_else(|| self.underlying.get_rent_allowance(account)) + .and_then(|changes| changes.rent_allowance()) + .unwrap_or_else(|| self.underlying.get_rent_allowance(account)) } fn contract_exists(&self, account: &T::AccountId) -> bool { self.local .borrow() .get(account) - .map(|a| a.code_hash.is_some()) + .and_then(|changes| changes.code_hash().map(|code_hash| code_hash.is_some())) .unwrap_or_else(|| self.underlying.contract_exists(account)) } fn get_balance(&self, account: &T::AccountId) -> BalanceOf<T> { self.local .borrow() .get(account) - .and_then(|a| a.balance) + .and_then(|changes| changes.balance()) .unwrap_or_else(|| self.underlying.get_balance(account)) } fn commit(&mut self, s: ChangeSet<T>) { @@ -293,10 +373,14 @@ impl<'a, T: Trait> AccountDb<T> for OverlayAccountDb<'a, T> { match local.entry(address) { Entry::Occupied(e) => { let mut value = e.into_mut(); - value.balance = changed.balance.or(value.balance); - value.code_hash = changed.code_hash.or(value.code_hash); - value.rent_allowance = changed.rent_allowance.or(value.rent_allowance); - value.storage.extend(changed.storage.into_iter()); + if changed.reset { + *value = changed; + } else { + value.balance = changed.balance.or(value.balance); + value.code_hash = changed.code_hash.or(value.code_hash); + value.rent_allowance = changed.rent_allowance.or(value.rent_allowance); + value.storage.extend(changed.storage.into_iter()); + } } Entry::Vacant(e) => { e.insert(changed); diff --git a/substrate/srml/contracts/src/exec.rs b/substrate/srml/contracts/src/exec.rs index 5ba02d43a0ffe68d1d99bdf4e260a4b35a043b47..818a689f99d9080ced3fc298c8e975bb98dfd049 100644 --- a/substrate/srml/contracts/src/exec.rs +++ b/substrate/srml/contracts/src/exec.rs @@ -267,6 +267,7 @@ pub enum DeferredAction<T: Trait> { } pub struct ExecutionContext<'a, T: Trait + 'a, V, L> { + pub parent: Option<&'a ExecutionContext<'a, T, V, L>>, pub self_account: T::AccountId, pub self_trie_id: Option<TrieId>, pub overlay: OverlayAccountDb<'a, T>, @@ -291,6 +292,7 @@ where /// account (not a contract). pub fn top_level(origin: T::AccountId, cfg: &'a Config<T>, vm: &'a V, loader: &'a L) -> Self { ExecutionContext { + parent: None, self_trie_id: None, self_account: origin, overlay: OverlayAccountDb::<T>::new(&DirectAccountDb), @@ -308,6 +310,7 @@ where -> ExecutionContext<'b, T, V, L> { ExecutionContext { + parent: Some(self), self_trie_id: trie_id, self_account: dest, overlay: OverlayAccountDb::new(&self.overlay), @@ -385,13 +388,29 @@ where nested.loader.load_main(&dest_code_hash), input_data ); - nested.vm + let output = nested.vm .execute( &executable, nested.new_call_context(caller, value), input_data, 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", + buffer: output.data, + }); + } + + nested.overlay.destroy_contract(&dest); + } + + Ok(output) } None => Ok(ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() }), } @@ -464,6 +483,14 @@ where gas_meter, )?; + // Error out if insufficient remaining balance. + if nested.overlay.get_balance(&dest) < nested.config.existential_deposit { + return Err(ExecError { + reason: "insufficient remaining balance", + buffer: output.data, + }); + } + // Deposit an instantiation event. nested.deferred.push(DeferredAction::DepositEvent { event: RawEvent::Instantiated(caller.clone(), dest.clone()), @@ -507,6 +534,13 @@ where Ok(output) } + + /// Returns whether a contract, identified by address, is currently live in the execution + /// stack, meaning it is in the middle of an execution. + fn is_live(&self, account: &T::AccountId) -> bool { + &self.self_account == account || + self.parent.map_or(false, |parent| parent.is_live(account)) + } } #[cfg_attr(test, derive(Debug, PartialEq, Eq))] diff --git a/substrate/srml/contracts/src/tests.rs b/substrate/srml/contracts/src/tests.rs index 5c5910c1571e798b69fc07e53054cd48458049d3..6ac1a50764f3f44a0f3181f2ece031deb0e0c85a 100644 --- a/substrate/srml/contracts/src/tests.rs +++ b/substrate/srml/contracts/src/tests.rs @@ -1602,7 +1602,7 @@ const CODE_RETURN_WITH_DATA: &str = r#" ;; Copy all but the first 4 bytes of the input data as the output data. (call $ext_scratch_write - (i32.const 4) ;; Offset from the start of the scratch buffer. + (i32.const 4) ;; Pointer to the data to return. (i32.sub ;; Count of bytes to copy. (get_local $buf_size) (i32.const 4) @@ -1926,3 +1926,467 @@ fn deploy_and_call_other_contract() { } ); } + +const CODE_SELF_DESTRUCT: &str = r#" +(module + (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) + (import "env" "ext_address" (func $ext_address)) + (import "env" "ext_balance" (func $ext_balance)) + (import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32))) + (import "env" "memory" (memory 1 1)) + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "deploy")) + + (func (export "call") + ;; If the input data is not empty, then recursively call self with empty input data. + ;; This should trap instead of self-destructing since a contract cannot be removed live in + ;; the execution stack cannot be removed. If the recursive call traps, then trap here as + ;; well. + (if (call $ext_scratch_size) + (then + (call $ext_address) + + ;; Expect address to be 8 bytes. + (call $assert + (i32.eq + (call $ext_scratch_size) + (i32.const 8) + ) + ) + + ;; Read own address into memory. + (call $ext_scratch_read + (i32.const 16) ;; Pointer to write address to + (i32.const 0) ;; Offset into scrach buffer + (i32.const 8) ;; Length of encoded address + ) + + ;; Recursively call self with empty imput data. + (call $assert + (i32.eq + (call $ext_call + (i32.const 16) ;; Pointer to own address + (i32.const 8) ;; Length of own address + (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 8) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer + (i32.const 0) ;; Pointer to input data buffer address + (i32.const 0) ;; Length of input data buffer + ) + (i32.const 0) + ) + ) + ) + ) + + ;; Send entire remaining balance to the 0 address. + (call $ext_balance) + + ;; Balance should be encoded as a u64. + (call $assert + (i32.eq + (call $ext_scratch_size) + (i32.const 8) + ) + ) + + ;; Read balance into memory. + (call $ext_scratch_read + (i32.const 8) ;; Pointer to write balance to + (i32.const 0) ;; Offset into scrach buffer + (i32.const 8) ;; Length of encoded balance + ) + + ;; Self-destruct by sending full balance to the 0 address. + (call $assert + (i32.eq + (call $ext_call + (i32.const 0) ;; Pointer to destination address + (i32.const 8) ;; Length of destination address + (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 8) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer + (i32.const 0) ;; Pointer to input data buffer address + (i32.const 0) ;; Length of input data buffer + ) + (i32.const 0) + ) + ) + ) +) +"#; + +#[test] +fn self_destruct_by_draining_balance() { + let (wasm, code_hash) = compile_module::<Test>(CODE_SELF_DESTRUCT).unwrap(); + with_externalities( + &mut ExtBuilder::default().existential_deposit(50).build(), + || { + Balances::deposit_creating(&ALICE, 1_000_000); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); + + // Instantiate the BOB contract. + assert_ok!(Contract::create( + Origin::signed(ALICE), + 100_000, + 100_000, + code_hash.into(), + vec![], + )); + + // Check that the BOB contract has been instantiated. + assert_matches!( + ContractInfoOf::<Test>::get(BOB), + Some(ContractInfo::Alive(_)) + ); + + // Call BOB with no input data, forcing it to self-destruct. + assert_ok!(Contract::call( + Origin::signed(ALICE), + BOB, + 0, + 100_000, + vec![], + )); + + // Check that BOB is now dead. + assert!(ContractInfoOf::<Test>::get(BOB).is_none()); + } + ); +} + +#[test] +fn cannot_self_destruct_while_live() { + let (wasm, code_hash) = compile_module::<Test>(CODE_SELF_DESTRUCT).unwrap(); + with_externalities( + &mut ExtBuilder::default().existential_deposit(50).build(), + || { + Balances::deposit_creating(&ALICE, 1_000_000); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); + + // Instantiate the BOB contract. + assert_ok!(Contract::create( + Origin::signed(ALICE), + 100_000, + 100_000, + code_hash.into(), + vec![], + )); + + // Check that the BOB contract has been instantiated. + assert_matches!( + ContractInfoOf::<Test>::get(BOB), + Some(ContractInfo::Alive(_)) + ); + + // Call BOB with input data, forcing it make a recursive call to itself to + // self-destruct, resulting in a trap. + assert_err!( + Contract::call( + Origin::signed(ALICE), + BOB, + 0, + 100_000, + vec![0], + ), + "during execution" + ); + + // Check that BOB is still alive. + assert_matches!( + ContractInfoOf::<Test>::get(BOB), + Some(ContractInfo::Alive(_)) + ); + } + ); +} + +const CODE_DESTROY_AND_TRANSFER: &str = r#" +(module + (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) + (import "env" "ext_get_storage" (func $ext_get_storage (param i32) (result i32))) + (import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32 i32))) + (import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32))) + (import "env" "ext_create" (func $ext_create (param i32 i32 i64 i32 i32 i32 i32) (result i32))) + (import "env" "memory" (memory 1 1)) + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "deploy") + ;; Input data is the code hash of the contract to be deployed. + (call $assert + (i32.eq + (call $ext_scratch_size) + (i32.const 32) + ) + ) + + ;; Copy code hash from scratch buffer into this contract's memory. + (call $ext_scratch_read + (i32.const 48) ;; The pointer where to store the scratch buffer contents, + (i32.const 0) ;; Offset from the start of the scratch buffer. + (i32.const 32) ;; Count of bytes to copy. + ) + + ;; Deploy the contract with the provided code hash. + (call $assert + (i32.eq + (call $ext_create + (i32.const 48) ;; Pointer to the code hash. + (i32.const 32) ;; Length of the code hash. + (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 0) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer. + (i32.const 0) ;; Pointer to input data buffer address + (i32.const 0) ;; Length of input data buffer + ) + (i32.const 0) + ) + ) + + ;; Read the address of the instantiated contract into memory. + (call $assert + (i32.eq + (call $ext_scratch_size) + (i32.const 8) + ) + ) + (call $ext_scratch_read + (i32.const 80) ;; The pointer where to store the scratch buffer contents, + (i32.const 0) ;; Offset from the start of the scratch buffer. + (i32.const 8) ;; Count of bytes to copy. + ) + + ;; Store the return address. + (call $ext_set_storage + (i32.const 16) ;; Pointer to the key + (i32.const 1) ;; Value is not null + (i32.const 80) ;; Pointer to the value + (i32.const 8) ;; Length of the value + ) + ) + + (func (export "call") + ;; Read address of destination contract from storage. + (call $assert + (i32.eq + (call $ext_get_storage + (i32.const 16) ;; Pointer to the key + ) + (i32.const 0) + ) + ) + (call $assert + (i32.eq + (call $ext_scratch_size) + (i32.const 8) + ) + ) + (call $ext_scratch_read + (i32.const 80) ;; The pointer where to store the contract address. + (i32.const 0) ;; Offset from the start of the scratch buffer. + (i32.const 8) ;; Count of bytes to copy. + ) + + ;; Calling the destination contract with non-empty input data should fail. + (call $assert + (i32.eq + (call $ext_call + (i32.const 80) ;; Pointer to destination address + (i32.const 8) ;; Length of destination address + (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 0) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer + (i32.const 0) ;; Pointer to input data buffer address + (i32.const 1) ;; Length of input data buffer + ) + (i32.const 0x0100) + ) + ) + + ;; Call the destination contract regularly, forcing it to self-destruct. + (call $assert + (i32.eq + (call $ext_call + (i32.const 80) ;; Pointer to destination address + (i32.const 8) ;; Length of destination address + (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 8) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer + (i32.const 0) ;; Pointer to input data buffer address + (i32.const 0) ;; Length of input data buffer + ) + (i32.const 0) + ) + ) + + ;; Calling the destination address with non-empty input data should now work since the + ;; contract has been removed. Also transfer a balance to the address so we can ensure this + ;; does not keep the contract alive. + (call $assert + (i32.eq + (call $ext_call + (i32.const 80) ;; Pointer to destination address + (i32.const 8) ;; Length of destination address + (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 0) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer + (i32.const 0) ;; Pointer to input data buffer address + (i32.const 1) ;; Length of input data buffer + ) + (i32.const 0) + ) + ) + ) + + (data (i32.const 0) "\00\00\01") ;; Endowment to send when creating contract. + (data (i32.const 8) "") ;; Value to send when calling contract. + (data (i32.const 16) "") ;; The key to store the contract address under. +) +"#; + +// This tests that one contract cannot prevent another from self-destructing by sending it +// additional funds after it has been drained. +#[test] +fn destroy_contract_and_transfer_funds() { + let (callee_wasm, callee_code_hash) = compile_module::<Test>(CODE_SELF_DESTRUCT).unwrap(); + let (caller_wasm, caller_code_hash) = compile_module::<Test>(CODE_DESTROY_AND_TRANSFER).unwrap(); + + with_externalities( + &mut ExtBuilder::default().existential_deposit(50).build(), + || { + // Create + Balances::deposit_creating(&ALICE, 1_000_000); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, callee_wasm)); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, caller_wasm)); + + // This deploys the BOB contract, which in turn deploys the CHARLIE contract during + // construction. + assert_ok!(Contract::create( + Origin::signed(ALICE), + 200_000, + 100_000, + caller_code_hash.into(), + callee_code_hash.as_ref().to_vec(), + )); + + // Check that the CHARLIE contract has been instantiated. + assert_matches!( + ContractInfoOf::<Test>::get(CHARLIE), + Some(ContractInfo::Alive(_)) + ); + + // Call BOB, which calls CHARLIE, forcing CHARLIE to self-destruct. + assert_ok!(Contract::call( + Origin::signed(ALICE), + BOB, + 0, + 100_000, + CHARLIE.encode(), + )); + + // Check that CHARLIE has moved on to the great beyond (ie. died). + assert!(ContractInfoOf::<Test>::get(CHARLIE).is_none()); + } + ); +} + +const CODE_SELF_DESTRUCTING_CONSTRUCTOR: &str = r#" +(module + (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) + (import "env" "ext_balance" (func $ext_balance)) + (import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32))) + (import "env" "memory" (memory 1 1)) + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "deploy") + ;; Send entire remaining balance to the 0 address. + (call $ext_balance) + + ;; Balance should be encoded as a u64. + (call $assert + (i32.eq + (call $ext_scratch_size) + (i32.const 8) + ) + ) + + ;; Read balance into memory. + (call $ext_scratch_read + (i32.const 8) ;; Pointer to write balance to + (i32.const 0) ;; Offset into scrach buffer + (i32.const 8) ;; Length of encoded balance + ) + + ;; Self-destruct by sending full balance to the 0 address. + (call $assert + (i32.eq + (call $ext_call + (i32.const 0) ;; Pointer to destination address + (i32.const 8) ;; Length of destination address + (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 8) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer + (i32.const 0) ;; Pointer to input data buffer address + (i32.const 0) ;; Length of input data buffer + ) + (i32.const 0) + ) + ) + ) + + (func (export "call")) +) +"#; + +#[test] +fn cannot_self_destruct_in_constructor() { + let (wasm, code_hash) = compile_module::<Test>(CODE_SELF_DESTRUCTING_CONSTRUCTOR).unwrap(); + with_externalities( + &mut ExtBuilder::default().existential_deposit(50).build(), + || { + Balances::deposit_creating(&ALICE, 1_000_000); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); + + // Fail to instantiate the BOB contract since its final balance is below existential + // deposit. + assert_err!( + Contract::create( + Origin::signed(ALICE), + 100_000, + 100_000, + code_hash.into(), + vec![], + ), + "insufficient remaining balance" + ); + } + ); +} diff --git a/substrate/srml/contracts/src/wasm/runtime.rs b/substrate/srml/contracts/src/wasm/runtime.rs index ecc4dfc7fb592f40ae5504d654405af65cc9865e..c6ef1bb3f561aea37d5527da9021b485a4ee4ed5 100644 --- a/substrate/srml/contracts/src/wasm/runtime.rs +++ b/substrate/srml/contracts/src/wasm/runtime.rs @@ -70,10 +70,6 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { special_trap: None, } } - - fn memory(&self) -> &sandbox::Memory { - &self.memory - } } pub(crate) fn to_execution_result<E: Ext>(