From 13839ce2fa18f544184b60fd1989d1f468d71d5d Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Mon, 16 Dec 2019 17:47:49 +0100 Subject: [PATCH 01/33] Add a simple direct storage access module --- frame/contracts/src/storage.rs | 45 ++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 frame/contracts/src/storage.rs diff --git a/frame/contracts/src/storage.rs b/frame/contracts/src/storage.rs new file mode 100644 index 0000000000000..33e70c802ba0b --- /dev/null +++ b/frame/contracts/src/storage.rs @@ -0,0 +1,45 @@ +// Copyright 2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +use crate::{Trait, CodeHash, TrieId, ContractInfoOf, BalanceOf, exec::StorageKey}; +use crate::exec::{AccountIdOf}; +use sp_io::hashing::blake2_256; +use support::{storage::child, StorageMap}; + +pub fn read_contract_storage(trie_id: &TrieId, key: &StorageKey) -> Option> { + child::get_raw(trie_id, &blake2_256(key)) +} + +pub fn write_contract_storage(trie_id: &TrieId, key: &StorageKey, value: Option>) { + // TODO: How do we track the size of the contract's + let hashed_key = blake2_256(key); + match value { + Some(v) => child::put_raw(trie_id, &hashed_key, &v), + None => child::kill(trie_id, &hashed_key), + } +} + +pub fn rent_allowance(account: &AccountIdOf) -> Option> { + >::get(account).and_then(|i| i.as_alive().map(|i| i.rent_allowance)) +} + +pub fn set_rent_allowance(account: &AccountIdOf, rent_allowance: BalanceOf) { + unimplemented!() +} + +pub fn code_hash(account: &AccountIdOf) -> Option> { + >::get(account).and_then(|i| i.as_alive().map(|i| i.code_hash)) +} From 34e363f55df5cb41477c825732e89f08c58a15a0 Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Tue, 17 Dec 2019 19:54:49 +0100 Subject: [PATCH 02/33] WIP --- frame/contracts/src/exec.rs | 250 ++++++++++++++++++--------------- frame/contracts/src/lib.rs | 15 +- frame/contracts/src/storage.rs | 43 +++++- frame/contracts/src/util.rs | 18 +++ 4 files changed, 211 insertions(+), 115 deletions(-) create mode 100644 frame/contracts/src/util.rs diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 9cc1c50260db9..d7efbb2a985d4 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -15,16 +15,17 @@ // along with Substrate. If not, see . use super::{CodeHash, Config, ContractAddressFor, Event, RawEvent, Trait, - TrieId, BalanceOf, ContractInfo}; + TrieId, BalanceOf, ContractInfo, TrieIdGenerator}; use crate::account_db::{AccountDb, DirectAccountDb, OverlayAccountDb}; use crate::gas::{Gas, GasMeter, Token}; use crate::rent; +use crate::storage; use sp_std::prelude::*; use sp_runtime::traits::{Bounded, CheckedAdd, CheckedSub, Zero}; use frame_support::{ storage::unhashed, dispatch::DispatchError, - traits::{WithdrawReason, Currency, Time, Randomness}, + traits::{ExistenceRequirement, WithdrawReason, Currency, Time, Randomness}, }; pub type AccountIdOf = ::AccountId; @@ -295,7 +296,6 @@ pub struct ExecutionContext<'a, T: Trait + 'a, V, L> { pub caller: Option<&'a ExecutionContext<'a, T, V, L>>, pub self_account: T::AccountId, pub self_trie_id: Option, - pub overlay: OverlayAccountDb<'a, T>, pub depth: usize, pub deferred: Vec>, pub config: &'a Config, @@ -320,7 +320,6 @@ where caller: None, self_trie_id: None, self_account: origin, - overlay: OverlayAccountDb::::new(&DirectAccountDb), depth: 0, deferred: Vec::new(), config: &cfg, @@ -331,14 +330,13 @@ where } } - fn nested<'b, 'c: 'b>(&'c self, dest: T::AccountId, trie_id: Option) + fn nested<'b, 'c: 'b>(&'c self, dest: T::AccountId, trie_id: TrieId) -> ExecutionContext<'b, T, V, L> { ExecutionContext { caller: Some(self), - self_trie_id: trie_id, + self_trie_id: Some(trie_id), self_account: dest, - overlay: OverlayAccountDb::new(&self.overlay), depth: self.depth + 1, deferred: Vec::new(), config: self.config, @@ -401,11 +399,10 @@ where return Err(ExecError { reason: "contract has been evicted".into(), buffer: input_data, - }); + }), + Some(ContractInfo::Alive(alive_info)) => alive_info.trie_id, }; - let caller = self.self_account.clone(); - let dest_trie_id = contract_info.and_then(|i| i.as_alive().map(|i| i.trie_id.clone())); self.with_nested_context(dest.clone(), dest_trie_id, |nested| { if value > BalanceOf::::zero() { @@ -424,7 +421,7 @@ where // If code_hash is not none, then the destination account is a live contract, otherwise // it is a regular account since tombstone accounts have already been rejected. - match nested.overlay.get_code_hash(&dest) { + match storage::code_hash::(&dest) { Some(dest_code_hash) => { let executable = try_or_exec_error!( nested.loader.load_main(&dest_code_hash), @@ -438,6 +435,27 @@ where gas_meter, )?; + // TODO: Handle the case when after returning from the nested call the balance + // is below existential deposit. + + // Destroy contract if insufficient remaining balance. + if T::Currency::free_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, + }); + } + storage::destroy_contract::( + &dest, + nested.self_trie_id.as_ref().expect( + "a nested exexcution context must have trie id assgined; qed" + ) + ); + } + Ok(output) } None => Ok(ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() }), @@ -477,11 +495,20 @@ where ); // TrieId has not been generated yet and storage is empty since contract is new. - let dest_trie_id = None; + // + // Generate it now. + let dest_trie_id = ::TrieIdGenerator::trie_id(&dest); let output = self.with_nested_context(dest.clone(), dest_trie_id, |nested| { try_or_exec_error!( - nested.overlay.instantiate_contract(&dest, code_hash.clone()), + storage::place_contract::( + &dest, + nested + .self_trie_id + .clone() + .expect("the nested context always has to have self_trie_id"), + code_hash.clone() + ), input_data ); @@ -511,8 +538,10 @@ where gas_meter, )?; + // TODO: Handle the case when after returning from the nested call the balance + // is below existential deposit. // Error out if insufficient remaining balance. - if nested.overlay.get_balance(&dest) < nested.config.existential_deposit { + if T::Currency::free_balance(&dest) < nested.config.existential_deposit { return Err(ExecError { reason: "insufficient remaining balance".into(), buffer: output.data, @@ -573,21 +602,25 @@ where } } - fn with_nested_context(&mut self, dest: T::AccountId, trie_id: Option, func: F) + fn with_nested_context(&mut self, dest: T::AccountId, trie_id: TrieId, func: F) -> ExecResult where F: FnOnce(&mut ExecutionContext) -> ExecResult { - let (output, change_set, deferred) = { + let (output, deferred) = { let mut nested = self.nested(dest, trie_id); - let output = func(&mut nested)?; - (output, nested.overlay.into_change_set(), nested.deferred) + let output = crate::util::with_transaction(|| { + let output = func(&mut nested); + match output { + Ok(ref rv) if rv.is_success() => (output, crate::util::TxOutcome::Commit), + _ => (output, crate::util::TxOutcome::Discard), + } + })?; + (output, nested.deferred) }; - if output.is_success() { - self.overlay.commit(change_set); + // self.overlay.commit(change_set); self.deferred.extend(deferred); } - Ok(output) } @@ -658,6 +691,15 @@ fn transfer<'a, T: Trait, V: Vm, L: Loader>( use self::TransferCause::*; use self::TransferFeeKind::*; + let to_balance = T::Currency::free_balance(dest); + + // `would_create` indicates whether the account will be created if this transfer gets executed. + // This flag is orthogonal to `cause. + // For example, we can instantiate a contract at the address which already has some funds. In this + // `would_create` will be `false`. Another example would be when this function is called from `call`, + // and account with the address `dest` doesn't exist yet `would_create` will be `true`. + let would_create = to_balance.is_zero(); + let token = { let kind: TransferFeeKind = match cause { // If this function is called from `Instantiate` routine, then we always @@ -676,44 +718,12 @@ fn transfer<'a, T: Trait, V: Vm, L: Loader>( 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, - None => Err("balance too low to send value")?, - }; - let to_balance = ctx.overlay.get_balance(dest); - 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.checked_sub(&required_balance) - .ok_or("brings sender below existential deposit")?, - )?; - - let new_to_balance = match to_balance.checked_add(&value) { - Some(b) => b, - None => Err("destination balance too high to receive value")?, - }; - - if transactor != dest { - ctx.overlay.set_balance(transactor, new_from_balance); - ctx.overlay.set_balance(dest, new_to_balance); - ctx.deferred.push(DeferredAction::DepositEvent { - event: RawEvent::Transfer(transactor.clone(), dest.clone(), value), - topics: Vec::new(), - }); - } + // Make the transfer. + T::Currency::transfer(transactor, dest, value, ExistenceRequirement::AllowDeath)?; + ctx.deferred.push(DeferredAction::DepositEvent { + event: RawEvent::Transfer(transactor.clone(), dest.clone(), value), + topics: Vec::new(), + }); Ok(()) } @@ -735,19 +745,30 @@ where type T = T; fn get_storage(&self, key: &StorageKey) -> Option> { - self.ctx.overlay.get_storage(&self.ctx.self_account, self.ctx.self_trie_id.as_ref(), key) + match self.ctx.self_trie_id { + Some(ref trie_id) => storage::read_contract_storage(trie_id, key), + // TODO: My current understanding is that `self.ctx.self_trie_id` can be `None` only + // for the top-level account, i.e. EOA. As such, it cannot issue any reads to the + // storage, because it has no. Furthermore, EOA cannot have contract storage. + None => unimplemented!(), + } } fn set_storage(&mut self, key: StorageKey, value: Option>) -> Result<(), &'static str> { if let Some(ref value) = value { + // TODO: Move this earlier to runtime.rs? This way the buffer won't get loaded. if self.max_value_size() < value.len() as u32 { return Err("value size exceeds maximum"); } } - self.ctx - .overlay - .set_storage(&self.ctx.self_account, key, value); + match self.ctx.self_trie_id { + Some(ref trie_id) => storage::write_contract_storage(trie_id, &key, value), + // TODO: My current understanding is that `self.ctx.self_trie_id` can be `None` only + // for the top-level account, i.e. EOA. As such, it cannot issue any reads to the + // storage, because it has no. Furthermore, EOA cannot have contract storage. + None => unimplemented!(), + } Ok(()) } @@ -820,7 +841,7 @@ where } fn balance(&self) -> BalanceOf { - self.ctx.overlay.get_balance(&self.ctx.self_account) + T::Currency::free_balance(&self.ctx.self_account) } fn value_transferred(&self) -> BalanceOf { @@ -851,11 +872,11 @@ where } fn set_rent_allowance(&mut self, rent_allowance: BalanceOf) { - self.ctx.overlay.set_rent_allowance(&self.ctx.self_account, rent_allowance) + storage::set_rent_allowance::(&self.ctx.self_account, rent_allowance); } fn rent_allowance(&self) -> BalanceOf { - self.ctx.overlay.get_rent_allowance(&self.ctx.self_account) + storage::rent_allowance::(&self.ctx.self_account) .unwrap_or(>::max_value()) // Must never be triggered actually } @@ -894,9 +915,10 @@ mod tests { Vm, ExecResult, RawEvent, DeferredAction, }; use crate::{ - account_db::AccountDb, gas::GasMeter, tests::{ExtBuilder, Test}, + account_db::AccountDb, gas::GasMeter, tests::{ExtBuilder, Test, set_balance, get_balance}, exec::{ExecReturnValue, ExecError, STATUS_SUCCESS}, CodeHash, Config, gas::Gas, + storage, }; use std::{cell::RefCell, rc::Rc, collections::HashMap, marker::PhantomData}; use assert_matches::assert_matches; @@ -1010,6 +1032,12 @@ mod tests { Ok(ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() }) } + fn place_contract(address: &u64, code_hash: CodeHash) { + use crate::TrieIdGenerator; + let trie_id = ::TrieIdGenerator::trie_id(address); + storage::place_contract::(&BOB, trie_id, code_hash).unwrap() + } + #[test] fn it_works() { let value = Default::default(); @@ -1029,7 +1057,7 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let cfg = Config::preload(); let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); - ctx.overlay.instantiate_contract(&BOB, exec_ch).unwrap(); + place_contract(&BOB, exec_ch); assert_matches!( ctx.call(BOB, value, &mut gas_meter, data), @@ -1051,8 +1079,8 @@ mod tests { let loader = MockLoader::empty(); let cfg = Config::preload(); let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); - ctx.overlay.set_balance(&origin, 100); - ctx.overlay.set_balance(&dest, 0); + set_balance(&origin, 100); + set_balance(&dest, 0); let mut gas_meter = GasMeter::::new(GAS_LIMIT); @@ -1072,7 +1100,7 @@ mod tests { let cfg = Config::preload(); let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); - ctx.overlay.set_balance(&origin, 100); + set_balance(&origin, 100); let mut gas_meter = GasMeter::::new(GAS_LIMIT); @@ -1097,8 +1125,8 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let cfg = Config::preload(); let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); - ctx.overlay.set_balance(&origin, 100); - ctx.overlay.set_balance(&dest, 0); + set_balance(&origin, 100); + set_balance(&dest, 0); let output = ctx.call( dest, @@ -1108,15 +1136,15 @@ mod tests { ).unwrap(); assert!(output.is_success()); - assert_eq!(ctx.overlay.get_balance(&origin), 45); - assert_eq!(ctx.overlay.get_balance(&dest), 55); + assert_eq!(get_balance(&origin), 45); + assert_eq!(get_balance(&dest), 55); }); } #[test] fn changes_are_reverted_on_failing_call() { - // This test verifies that a contract is able to transfer - // some funds to another account. + // This test verifies that changes are reverted on a call which fails (or equally, returns + // a non-zero status code). let origin = ALICE; let dest = BOB; @@ -1129,9 +1157,9 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let cfg = Config::preload(); let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); - ctx.overlay.instantiate_contract(&BOB, return_ch).unwrap(); - ctx.overlay.set_balance(&origin, 100); - ctx.overlay.set_balance(&dest, 0); + place_contract(&BOB, return_ch); + set_balance(&origin, 100); + set_balance(&dest, 0); let output = ctx.call( dest, @@ -1141,8 +1169,8 @@ mod tests { ).unwrap(); assert!(!output.is_success()); - assert_eq!(ctx.overlay.get_balance(&origin), 100); - assert_eq!(ctx.overlay.get_balance(&dest), 0); + assert_eq!(get_balance(&origin), 100); + assert_eq!(get_balance(&dest), 0); }); } @@ -1159,8 +1187,8 @@ mod tests { let loader = MockLoader::empty(); let cfg = Config::preload(); let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); - ctx.overlay.set_balance(&origin, 100); - ctx.overlay.set_balance(&dest, 0); + set_balance(&origin, 100); + set_balance(&dest, 0); let mut gas_meter = GasMeter::::new(GAS_LIMIT); @@ -1184,8 +1212,8 @@ mod tests { let loader = MockLoader::empty(); let cfg = Config::preload(); let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); - ctx.overlay.set_balance(&origin, 100); - ctx.overlay.set_balance(&dest, 15); + set_balance(&origin, 100); + set_balance(&dest, 15); let mut gas_meter = GasMeter::::new(GAS_LIMIT); @@ -1212,8 +1240,8 @@ mod tests { let cfg = Config::preload(); let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); - ctx.overlay.set_balance(&origin, 100); - ctx.overlay.set_balance(&dest, 15); + set_balance(&origin, 100); + set_balance(&dest, 15); let mut gas_meter = GasMeter::::new(GAS_LIMIT); @@ -1244,7 +1272,7 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let cfg = Config::preload(); let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); - ctx.overlay.set_balance(&origin, 0); + set_balance(&origin, 0); let result = ctx.call( dest, @@ -1260,8 +1288,8 @@ mod tests { buffer: _, }) ); - assert_eq!(ctx.overlay.get_balance(&origin), 0); - assert_eq!(ctx.overlay.get_balance(&dest), 0); + assert_eq!(get_balance(&origin), 0); + assert_eq!(get_balance(&dest), 0); }); } @@ -1281,7 +1309,7 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let cfg = Config::preload(); let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); - ctx.overlay.instantiate_contract(&BOB, return_ch).unwrap(); + place_contract(&BOB, return_ch); let result = ctx.call( dest, @@ -1312,7 +1340,7 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let cfg = Config::preload(); let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); - ctx.overlay.instantiate_contract(&BOB, return_ch).unwrap(); + place_contract(&BOB, return_ch); let result = ctx.call( dest, @@ -1340,7 +1368,7 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let cfg = Config::preload(); let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); - ctx.overlay.instantiate_contract(&BOB, input_data_ch).unwrap(); + place_contract(&BOB, input_data_ch); let result = ctx.call( BOB, @@ -1414,8 +1442,8 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let cfg = Config::preload(); let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); - ctx.overlay.set_balance(&BOB, 1); - ctx.overlay.instantiate_contract(&BOB, recurse_ch).unwrap(); + set_balance(&BOB, 1); + place_contract(&BOB, recurse_ch); let result = ctx.call( BOB, @@ -1460,8 +1488,8 @@ mod tests { let cfg = Config::preload(); let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); - ctx.overlay.instantiate_contract(&dest, bob_ch).unwrap(); - ctx.overlay.instantiate_contract(&CHARLIE, charlie_ch).unwrap(); + place_contract(&dest, bob_ch); + place_contract(&CHARLIE, charlie_ch); let result = ctx.call( dest, @@ -1501,8 +1529,8 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let cfg = Config::preload(); let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); - ctx.overlay.instantiate_contract(&BOB, bob_ch).unwrap(); - ctx.overlay.instantiate_contract(&CHARLIE, charlie_ch).unwrap(); + place_contract(&BOB, bob_ch); + place_contract(&CHARLIE, charlie_ch); let result = ctx.call( BOB, @@ -1550,7 +1578,7 @@ mod tests { 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); + set_balance(&ALICE, 1000); let instantiated_contract_address = assert_matches!( ctx.instantiate( @@ -1564,7 +1592,7 @@ mod tests { // Check that the newly created account has the expected code hash and // there are instantiation event. - assert_eq!(ctx.overlay.get_code_hash(&instantiated_contract_address).unwrap(), dummy_ch); + assert_eq!(storage::code_hash::(&instantiated_contract_address).unwrap(), dummy_ch); assert_eq!(&ctx.events(), &[ DeferredAction::DepositEvent { event: RawEvent::Transfer(ALICE, instantiated_contract_address, 100), @@ -1590,7 +1618,7 @@ mod tests { 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); + set_balance(&ALICE, 1000); let instantiated_contract_address = assert_matches!( ctx.instantiate( @@ -1603,7 +1631,7 @@ mod tests { ); // Check that the account has not been created. - assert!(ctx.overlay.get_code_hash(&instantiated_contract_address).is_none()); + assert!(storage::code_hash::(&instantiated_contract_address).is_none()); assert!(ctx.events().is_empty()); }); } @@ -1635,9 +1663,9 @@ mod tests { 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); - ctx.overlay.set_balance(&BOB, 100); - ctx.overlay.instantiate_contract(&BOB, instantiator_ch).unwrap(); + set_balance(&ALICE, 1000); + set_balance(&BOB, 100); + place_contract(&BOB, instantiator_ch); assert_matches!( ctx.call(BOB, 20, &mut GasMeter::::new(GAS_LIMIT), vec![]), @@ -1648,7 +1676,7 @@ mod tests { // Check that the newly created account has the expected code hash and // there are instantiation event. - assert_eq!(ctx.overlay.get_code_hash(&instantiated_contract_address).unwrap(), dummy_ch); + assert_eq!(storage::code_hash::(&instantiated_contract_address).unwrap(), dummy_ch); assert_eq!(&ctx.events(), &[ DeferredAction::DepositEvent { event: RawEvent::Transfer(ALICE, BOB, 20), @@ -1695,9 +1723,9 @@ mod tests { 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); - ctx.overlay.set_balance(&BOB, 100); - ctx.overlay.instantiate_contract(&BOB, instantiator_ch).unwrap(); + set_balance(&ALICE, 1000); + set_balance(&BOB, 100); + place_contract(&BOB, instantiator_ch); assert_matches!( ctx.call(BOB, 20, &mut GasMeter::::new(GAS_LIMIT), vec![]), diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 245c95a4fa4e9..33d3d5ac030a8 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -83,9 +83,11 @@ mod gas; mod account_db; +mod storage; mod exec; mod wasm; mod rent; +mod util; #[cfg(test)] mod tests; @@ -255,6 +257,12 @@ where } } +impl From> for ContractInfo { + fn from(alive_info: AliveContractInfo) -> Self { + Self::Alive(alive_info) + } +} + /// Get a trie id (trie id must be unique and collision resistant depending upon its context). /// Note that it is different than encode because trie id should be collision resistant /// (being a proper unique identifier). @@ -646,8 +654,13 @@ impl Module { let result = func(&mut ctx, gas_meter); if result.as_ref().map(|output| output.is_success()).unwrap_or(false) { + // TODO: Audit this. + // + // We no longer need to commit now, since all changes are committed eagearly, but + // rollbacked on error. + // Commit all changes that made it thus far into the persistent storage. - DirectAccountDb.commit(ctx.overlay.into_change_set()); + // DirectAccountDb.commit(ctx.overlay.into_change_set()); } // Execute deferred actions. diff --git a/frame/contracts/src/storage.rs b/frame/contracts/src/storage.rs index 33e70c802ba0b..5c0598634d13f 100644 --- a/frame/contracts/src/storage.rs +++ b/frame/contracts/src/storage.rs @@ -14,10 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -use crate::{Trait, CodeHash, TrieId, ContractInfoOf, BalanceOf, exec::StorageKey}; -use crate::exec::{AccountIdOf}; +use crate::{ + exec::{AccountIdOf, StorageKey}, + AliveContractInfo, BalanceOf, CodeHash, ContractInfoOf, Trait, TrieId, +}; use sp_io::hashing::blake2_256; -use support::{storage::child, StorageMap}; +use sp_runtime::traits::Bounded; +use support::{storage::child, traits::Get, StorageMap}; pub fn read_contract_storage(trie_id: &TrieId, key: &StorageKey) -> Option> { child::get_raw(trie_id, &blake2_256(key)) @@ -43,3 +46,37 @@ pub fn set_rent_allowance(account: &AccountIdOf, rent_allowance: Ba pub fn code_hash(account: &AccountIdOf) -> Option> { >::get(account).and_then(|i| i.as_alive().map(|i| i.code_hash)) } + +/// Creates a new contract descriptor in the storage with the given code hash at the given address. +/// +/// Returns `Err` if there is already a contract (or a tombstone) exists at the given address. +pub fn place_contract( + account: &AccountIdOf, + trie_id: TrieId, + ch: CodeHash, +) -> Result<(), &'static str> { + >::mutate(account, |maybe_contract_info| { + if maybe_contract_info.is_some() { + return Err("Alive contract or tombstone already exists"); + } + + *maybe_contract_info = Some( + AliveContractInfo:: { + code_hash: ch, + storage_size: T::StorageSizeOffset::get(), + trie_id, + deduct_block: >::block_number(), + rent_allowance: >::max_value(), + last_write: None, + } + .into(), + ); + + Ok(()) + }) +} + +pub fn destroy_contract(address: &AccountIdOf, trie_id: &TrieId) { + >::remove(address); + child::kill_storage(trie_id); +} diff --git a/frame/contracts/src/util.rs b/frame/contracts/src/util.rs new file mode 100644 index 0000000000000..2734cbbffc578 --- /dev/null +++ b/frame/contracts/src/util.rs @@ -0,0 +1,18 @@ +pub enum TxOutcome { + Commit, + Discard, +} + +pub fn with_transaction(f: impl FnOnce() -> (R, TxOutcome)) -> R { + use sp_io::storage::{ + start_transaction, commit_transaction, discard_transaction, + }; + + start_transaction(); + let (result, outcome) = f(); + match outcome { + TxOutcome::Commit => commit_transaction(), + TxOutcome::Discard => discard_transaction(), + } + result +} From f528e8d83b90eeb024e6daa0eee356b69b0920c4 Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Wed, 18 Dec 2019 13:11:15 +0100 Subject: [PATCH 03/33] Completely migrate to the transactional system. --- frame/contracts/src/exec.rs | 41 +++++++++------------------------ frame/contracts/src/lib.rs | 8 ++----- frame/contracts/src/storage.rs | 42 +++++++++++++++++++++++++++------- 3 files changed, 47 insertions(+), 44 deletions(-) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index d7efbb2a985d4..7f8a54743eda0 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -330,7 +330,7 @@ where } } - fn nested<'b, 'c: 'b>(&'c self, dest: T::AccountId, trie_id: TrieId) + fn nested<'b, 'c: 'b>(&'c self, dest: T::AccountId, trie_id: Option) -> ExecutionContext<'b, T, V, L> { ExecutionContext { @@ -399,10 +399,11 @@ where return Err(ExecError { reason: "contract has been evicted".into(), buffer: input_data, - }), - Some(ContractInfo::Alive(alive_info)) => alive_info.trie_id, + }); }; + let caller = self.self_account.clone(); + let dest_trie_id = contract_info.and_then(|i| i.as_alive().map(|i| i.trie_id.clone())); self.with_nested_context(dest.clone(), dest_trie_id, |nested| { if value > BalanceOf::::zero() { @@ -499,7 +500,7 @@ where // Generate it now. let dest_trie_id = ::TrieIdGenerator::trie_id(&dest); - let output = self.with_nested_context(dest.clone(), dest_trie_id, |nested| { + let output = self.with_nested_context(dest.clone(), Some(dest_trie_id), |nested| { try_or_exec_error!( storage::place_contract::( &dest, @@ -602,7 +603,8 @@ where } } - fn with_nested_context(&mut self, dest: T::AccountId, trie_id: TrieId, func: F) + /// Execute the given closure within a nested execution context. + fn with_nested_context(&mut self, dest: T::AccountId, trie_id: Option, func: F) -> ExecResult where F: FnOnce(&mut ExecutionContext) -> ExecResult { @@ -720,10 +722,6 @@ fn transfer<'a, T: Trait, V: Vm, L: Loader>( // Make the transfer. T::Currency::transfer(transactor, dest, value, ExistenceRequirement::AllowDeath)?; - ctx.deferred.push(DeferredAction::DepositEvent { - event: RawEvent::Transfer(transactor.clone(), dest.clone(), value), - topics: Vec::new(), - }); Ok(()) } @@ -763,7 +761,7 @@ where } match self.ctx.self_trie_id { - Some(ref trie_id) => storage::write_contract_storage(trie_id, &key, value), + Some(ref trie_id) => storage::write_contract_storage::(&self.ctx.self_account, trie_id, &key, value), // TODO: My current understanding is that `self.ctx.self_trie_id` can be `None` only // for the top-level account, i.e. EOA. As such, it cannot issue any reads to the // storage, because it has no. Furthermore, EOA cannot have contract storage. @@ -915,7 +913,7 @@ mod tests { Vm, ExecResult, RawEvent, DeferredAction, }; use crate::{ - account_db::AccountDb, gas::GasMeter, tests::{ExtBuilder, Test, set_balance, get_balance}, + gas::GasMeter, tests::{ExtBuilder, Test, set_balance, get_balance}, exec::{ExecReturnValue, ExecError, STATUS_SUCCESS}, CodeHash, Config, gas::Gas, storage, @@ -1035,7 +1033,7 @@ mod tests { fn place_contract(address: &u64, code_hash: CodeHash) { use crate::TrieIdGenerator; let trie_id = ::TrieIdGenerator::trie_id(address); - storage::place_contract::(&BOB, trie_id, code_hash).unwrap() + storage::place_contract::(&address, trie_id, code_hash).unwrap() } #[test] @@ -1594,10 +1592,6 @@ mod tests { // there are instantiation event. assert_eq!(storage::code_hash::(&instantiated_contract_address).unwrap(), dummy_ch); assert_eq!(&ctx.events(), &[ - DeferredAction::DepositEvent { - event: RawEvent::Transfer(ALICE, instantiated_contract_address, 100), - topics: Vec::new(), - }, DeferredAction::DepositEvent { event: RawEvent::Instantiated(ALICE, instantiated_contract_address), topics: Vec::new(), @@ -1678,14 +1672,6 @@ mod tests { // there are instantiation event. assert_eq!(storage::code_hash::(&instantiated_contract_address).unwrap(), dummy_ch); assert_eq!(&ctx.events(), &[ - DeferredAction::DepositEvent { - event: RawEvent::Transfer(ALICE, BOB, 20), - topics: Vec::new(), - }, - DeferredAction::DepositEvent { - event: RawEvent::Transfer(BOB, instantiated_contract_address, 15), - topics: Vec::new(), - }, DeferredAction::DepositEvent { event: RawEvent::Instantiated(BOB, instantiated_contract_address), topics: Vec::new(), @@ -1734,12 +1720,7 @@ mod tests { // The contract wasn't instantiated so we don't expect to see an instantiation // event here. - assert_eq!(&ctx.events(), &[ - DeferredAction::DepositEvent { - event: RawEvent::Transfer(ALICE, BOB, 20), - topics: Vec::new(), - }, - ]); + assert_eq!(&ctx.events(), &[]); }); } diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 33d3d5ac030a8..139a5ad148ebd 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -798,13 +798,9 @@ impl Module { decl_event! { pub enum Event where - Balance = BalanceOf, - ::AccountId, - ::Hash + ::AccountId, + ::Hash { - /// Transfer happened `from` to `to` with given `value` as part of a `call` or `instantiate`. - Transfer(AccountId, AccountId, Balance), - /// Contract deployed by address at the specified address. Instantiated(AccountId, AccountId), diff --git a/frame/contracts/src/storage.rs b/frame/contracts/src/storage.rs index 5c0598634d13f..e979f60961863 100644 --- a/frame/contracts/src/storage.rs +++ b/frame/contracts/src/storage.rs @@ -16,7 +16,7 @@ use crate::{ exec::{AccountIdOf, StorageKey}, - AliveContractInfo, BalanceOf, CodeHash, ContractInfoOf, Trait, TrieId, + ContractInfo, AliveContractInfo, BalanceOf, CodeHash, ContractInfoOf, Trait, TrieId, }; use sp_io::hashing::blake2_256; use sp_runtime::traits::Bounded; @@ -26,13 +26,34 @@ pub fn read_contract_storage(trie_id: &TrieId, key: &StorageKey) -> Option>) { - // TODO: How do we track the size of the contract's +pub fn write_contract_storage(account: &AccountIdOf, trie_id: &TrieId, key: &StorageKey, value: Option>) { let hashed_key = blake2_256(key); - match value { - Some(v) => child::put_raw(trie_id, &hashed_key, &v), - None => child::kill(trie_id, &hashed_key), - } + + // We need to accurately track the size of the storage of the contract. + // + // For that we query the previous value and get its size. + let existing_size = child::get_raw(trie_id, &hashed_key).map(|v| v.len()).unwrap_or(0); + let new_size = match value { + Some(v) => { + child::put_raw(trie_id, &hashed_key, &v); + v.len() + } + None => { + child::kill(trie_id, &hashed_key); + 0 + } + }; + + >::mutate(account, |maybe_contract_info| { + match maybe_contract_info { + Some(ContractInfo::Alive(ref mut alive_info)) => { + alive_info.storage_size -= existing_size as u32; + alive_info.storage_size += new_size as u32; + alive_info.last_write = Some(>::block_number()); + }, + _ => panic!(), // TODO: Justify this. + } + }); } pub fn rent_allowance(account: &AccountIdOf) -> Option> { @@ -40,7 +61,12 @@ pub fn rent_allowance(account: &AccountIdOf) -> Option } pub fn set_rent_allowance(account: &AccountIdOf, rent_allowance: BalanceOf) { - unimplemented!() + >::mutate(account, |maybe_contract_info| { + match maybe_contract_info { + Some(ContractInfo::Alive(ref mut alive_info)) => alive_info.rent_allowance = rent_allowance, + _ => panic!(), // TODO: Justify this. + } + }) } pub fn code_hash(account: &AccountIdOf) -> Option> { From 3b2d3117bbfcaef8d6f9d24d4a58d67ab46cc87b Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Wed, 18 Dec 2019 15:26:21 +0100 Subject: [PATCH 04/33] Format --- frame/contracts/src/storage.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/frame/contracts/src/storage.rs b/frame/contracts/src/storage.rs index e979f60961863..c3cacfe9278f4 100644 --- a/frame/contracts/src/storage.rs +++ b/frame/contracts/src/storage.rs @@ -16,7 +16,7 @@ use crate::{ exec::{AccountIdOf, StorageKey}, - ContractInfo, AliveContractInfo, BalanceOf, CodeHash, ContractInfoOf, Trait, TrieId, + AliveContractInfo, BalanceOf, CodeHash, ContractInfo, ContractInfoOf, Trait, TrieId, }; use sp_io::hashing::blake2_256; use sp_runtime::traits::Bounded; @@ -26,13 +26,20 @@ pub fn read_contract_storage(trie_id: &TrieId, key: &StorageKey) -> Option(account: &AccountIdOf, trie_id: &TrieId, key: &StorageKey, value: Option>) { +pub fn write_contract_storage( + account: &AccountIdOf, + trie_id: &TrieId, + key: &StorageKey, + value: Option>, +) { let hashed_key = blake2_256(key); // We need to accurately track the size of the storage of the contract. // // For that we query the previous value and get its size. - let existing_size = child::get_raw(trie_id, &hashed_key).map(|v| v.len()).unwrap_or(0); + let existing_size = child::get_raw(trie_id, &hashed_key) + .map(|v| v.len()) + .unwrap_or(0); let new_size = match value { Some(v) => { child::put_raw(trie_id, &hashed_key, &v); @@ -50,7 +57,7 @@ pub fn write_contract_storage(account: &AccountIdOf, trie_id: &Trie alive_info.storage_size -= existing_size as u32; alive_info.storage_size += new_size as u32; alive_info.last_write = Some(>::block_number()); - }, + } _ => panic!(), // TODO: Justify this. } }); @@ -63,7 +70,9 @@ pub fn rent_allowance(account: &AccountIdOf) -> Option pub fn set_rent_allowance(account: &AccountIdOf, rent_allowance: BalanceOf) { >::mutate(account, |maybe_contract_info| { match maybe_contract_info { - Some(ContractInfo::Alive(ref mut alive_info)) => alive_info.rent_allowance = rent_allowance, + Some(ContractInfo::Alive(ref mut alive_info)) => { + alive_info.rent_allowance = rent_allowance + } _ => panic!(), // TODO: Justify this. } }) From f83e65617cd2e29ceae601db714bbecd39c55b6c Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Wed, 18 Dec 2019 16:19:31 +0100 Subject: [PATCH 05/33] Fix wasm compilation --- frame/contracts/src/storage.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/frame/contracts/src/storage.rs b/frame/contracts/src/storage.rs index c3cacfe9278f4..42effd0375f9a 100644 --- a/frame/contracts/src/storage.rs +++ b/frame/contracts/src/storage.rs @@ -18,6 +18,7 @@ use crate::{ exec::{AccountIdOf, StorageKey}, AliveContractInfo, BalanceOf, CodeHash, ContractInfo, ContractInfoOf, Trait, TrieId, }; +use sp_std::prelude::*; use sp_io::hashing::blake2_256; use sp_runtime::traits::Bounded; use support::{storage::child, traits::Get, StorageMap}; From 4d7b8fb2dd3b99cf66c6755cb2611d8ebc0cdd29 Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Wed, 18 Dec 2019 16:59:21 +0100 Subject: [PATCH 06/33] Get rid of account_db module --- frame/contracts/src/exec.rs | 8 +------- frame/contracts/src/lib.rs | 10 +--------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 7f8a54743eda0..36b29dd115eab 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -913,7 +913,7 @@ mod tests { Vm, ExecResult, RawEvent, DeferredAction, }; use crate::{ - gas::GasMeter, tests::{ExtBuilder, Test, set_balance, get_balance}, + gas::GasMeter, tests::{ExtBuilder, Test, set_balance, get_balance, place_contract}, exec::{ExecReturnValue, ExecError, STATUS_SUCCESS}, CodeHash, Config, gas::Gas, storage, @@ -1030,12 +1030,6 @@ mod tests { Ok(ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() }) } - fn place_contract(address: &u64, code_hash: CodeHash) { - use crate::TrieIdGenerator; - let trie_id = ::TrieIdGenerator::trie_id(address); - storage::place_contract::(&address, trie_id, code_hash).unwrap() - } - #[test] fn it_works() { let value = Default::default(); diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 139a5ad148ebd..664a36ea94452 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -81,8 +81,6 @@ #[macro_use] mod gas; - -mod account_db; mod storage; mod exec; mod wasm; @@ -93,7 +91,6 @@ mod util; mod tests; use crate::exec::ExecutionContext; -use crate::account_db::{AccountDb, DirectAccountDb}; use crate::wasm::{WasmLoader, WasmVm}; pub use crate::gas::{Gas, GasMeter}; @@ -620,12 +617,7 @@ impl Module { .get_alive() .ok_or(ContractAccessError::IsTombstone)?; - let maybe_value = AccountDb::::get_storage( - &DirectAccountDb, - &address, - Some(&contract_info.trie_id), - &key, - ); + let maybe_value = storage::read_contract_storage(&contract_info.trie_id, &key); Ok(maybe_value) } From 4d597043a9b152191cbbaf918cf443a3dd300fb9 Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Thu, 19 Dec 2019 14:59:06 +0100 Subject: [PATCH 07/33] Make deposit event eager --- frame/contracts/src/exec.rs | 72 ++++++++++++++++--------------------- frame/contracts/src/lib.rs | 7 ---- 2 files changed, 31 insertions(+), 48 deletions(-) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 36b29dd115eab..4d909554a34f9 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -265,12 +265,6 @@ impl Token for ExecFeeToken { #[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq, Clone))] #[derive(sp_runtime::RuntimeDebug)] pub enum DeferredAction { - DepositEvent { - /// A list of topics this event will be deposited with. - topics: Vec, - /// The event to deposit. - event: Event, - }, DispatchRuntimeCall { /// The account id of the contract who dispatched this call. origin: T::AccountId, @@ -550,10 +544,7 @@ where } // Deposit an instantiation event. - nested.deferred.push(DeferredAction::DepositEvent { - event: RawEvent::Instantiated(caller.clone(), dest.clone()), - topics: Vec::new(), - }); + deposit_event::(vec![], RawEvent::Instantiated(caller.clone(), dest.clone())); Ok(output) })?; @@ -896,6 +887,16 @@ where } } +fn deposit_event( + topics: Vec, + event: Event, +) { + >::deposit_event_indexed( + &*topics, + ::Event::from(event).into(), + ) +} + /// These tests exercise the executive layer. /// /// In these tests the VM/loader are mocked. Instead of dealing with wasm bytecode they use simple closures. @@ -909,38 +910,33 @@ where #[cfg(test)] mod tests { use super::{ - BalanceOf, ExecFeeToken, ExecutionContext, Ext, Loader, TransferFeeKind, TransferFeeToken, - Vm, ExecResult, RawEvent, DeferredAction, + BalanceOf, DeferredAction, Event, ExecFeeToken, ExecResult, ExecutionContext, Ext, Loader, + RawEvent, TransferFeeKind, TransferFeeToken, Vm, }; use crate::{ gas::GasMeter, tests::{ExtBuilder, Test, set_balance, get_balance, place_contract}, exec::{ExecReturnValue, ExecError, STATUS_SUCCESS}, CodeHash, Config, gas::Gas, storage, + tests::{get_balance, place_contract, set_balance, ExtBuilder, MetaEvent, Test}, + CodeHash, Config, }; - use std::{cell::RefCell, rc::Rc, collections::HashMap, marker::PhantomData}; - use assert_matches::assert_matches; use sp_runtime::DispatchError; + use assert_matches::assert_matches; + use std::{cell::RefCell, collections::HashMap, marker::PhantomData, rc::Rc}; const ALICE: u64 = 1; const BOB: u64 = 2; const CHARLIE: u64 = 3; - const GAS_LIMIT: Gas = 10_000_000_000; - - impl<'a, T, V, L> ExecutionContext<'a, T, V, L> - where T: crate::Trait - { - fn events(&self) -> Vec> { - self.deferred - .iter() - .filter(|action| match *action { - DeferredAction::DepositEvent { .. } => true, - _ => false, - }) - .cloned() - .collect() - } + fn events() -> Vec> { + >::events() + .into_iter() + .filter_map(|meta| match meta.event { + MetaEvent::contract(contract_event) => Some(contract_event), + _ => None, + }) + .collect() } struct MockCtx<'a> { @@ -1585,11 +1581,8 @@ mod tests { // Check that the newly created account has the expected code hash and // there are instantiation event. assert_eq!(storage::code_hash::(&instantiated_contract_address).unwrap(), dummy_ch); - assert_eq!(&ctx.events(), &[ - DeferredAction::DepositEvent { - event: RawEvent::Instantiated(ALICE, instantiated_contract_address), - topics: Vec::new(), - } + assert_eq!(&events(), &[ + RawEvent::Instantiated(ALICE, instantiated_contract_address) ]); }); } @@ -1620,7 +1613,7 @@ mod tests { // Check that the account has not been created. assert!(storage::code_hash::(&instantiated_contract_address).is_none()); - assert!(ctx.events().is_empty()); + assert!(events().is_empty()); }); } @@ -1665,11 +1658,8 @@ mod tests { // Check that the newly created account has the expected code hash and // there are instantiation event. assert_eq!(storage::code_hash::(&instantiated_contract_address).unwrap(), dummy_ch); - assert_eq!(&ctx.events(), &[ - DeferredAction::DepositEvent { - event: RawEvent::Instantiated(BOB, instantiated_contract_address), - topics: Vec::new(), - }, + assert_eq!(&events(), &[ + RawEvent::Instantiated(BOB, instantiated_contract_address) ]); }); } @@ -1714,7 +1704,7 @@ mod tests { // The contract wasn't instantiated so we don't expect to see an instantiation // event here. - assert_eq!(&ctx.events(), &[]); + assert_eq!(&events(), &[]); }); } diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 664a36ea94452..c433a3b1eddbc 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -659,13 +659,6 @@ impl Module { ctx.deferred.into_iter().for_each(|deferred| { use self::exec::DeferredAction::*; match deferred { - DepositEvent { - topics, - event, - } => >::deposit_event_indexed( - &*topics, - ::Event::from(event).into(), - ), DispatchRuntimeCall { origin: who, call, From 4c9f607f8bfff6c1f6ff47a1bfa209850a1b99df Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Thu, 19 Dec 2019 17:02:38 +0100 Subject: [PATCH 08/33] Make restore_to eager --- frame/contracts/src/exec.rs | 31 +- frame/contracts/src/lib.rs | 102 -- frame/contracts/src/rent.rs | 83 + frame/contracts/src/tests/mod.rs | 2475 +++++++++++++++++++++++++++ frame/contracts/src/wasm/mod.rs | 11 +- frame/contracts/src/wasm/runtime.rs | 30 +- 6 files changed, 2589 insertions(+), 143 deletions(-) create mode 100644 frame/contracts/src/tests/mod.rs diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 4d909554a34f9..35bc9762dd42e 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -148,14 +148,14 @@ pub trait Ext { /// Notes a call dispatch. fn note_dispatch_call(&mut self, call: CallOf); - /// Notes a restoration request. - fn note_restore_to( + /// Restores the given destination contract sacrificing the current one. + fn restore_to( &mut self, dest: AccountIdOf, code_hash: CodeHash, rent_allowance: BalanceOf, delta: Vec, - ); + ) -> Result<(), &'static str>; /// Returns a reference to the account id of the caller. fn caller(&self) -> &AccountIdOf; @@ -271,19 +271,6 @@ pub enum DeferredAction { /// The call to dispatch. call: ::Call, }, - RestoreTo { - /// The account id of the contract which is removed during the restoration and transfers - /// its storage to the restored contract. - donor: T::AccountId, - /// The account id of the restored contract. - dest: T::AccountId, - /// The code hash of the restored contract. - code_hash: CodeHash, - /// The initial rent allowance to set. - rent_allowance: BalanceOf, - /// The keys to delete upon restoration. - delta: Vec, - }, } pub struct ExecutionContext<'a, T: Trait + 'a, V, L> { @@ -805,20 +792,20 @@ where }); } - fn note_restore_to( + fn restore_to( &mut self, dest: AccountIdOf, code_hash: CodeHash, rent_allowance: BalanceOf, delta: Vec, - ) { - self.ctx.deferred.push(DeferredAction::RestoreTo { - donor: self.ctx.self_account.clone(), + ) -> Result<(), &'static str> { + crate::rent::restore_to::( + self.ctx.self_account.clone(), dest, code_hash, rent_allowance, delta, - }); + ) } fn address(&self) -> &T::AccountId { @@ -910,7 +897,7 @@ fn deposit_event( #[cfg(test)] mod tests { use super::{ - BalanceOf, DeferredAction, Event, ExecFeeToken, ExecResult, ExecutionContext, Ext, Loader, + BalanceOf, Event, ExecFeeToken, ExecResult, ExecutionContext, Ext, Loader, RawEvent, TransferFeeKind, TransferFeeToken, Vm, }; use crate::{ diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index c433a3b1eddbc..bc71c2f55e180 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -101,7 +101,6 @@ use serde::{Serialize, Deserialize}; use sp_core::crypto::UncheckedFrom; use sp_std::{prelude::*, marker::PhantomData, fmt::Debug}; use codec::{Codec, Encode, Decode}; -use sp_io::hashing::blake2_256; use sp_runtime::{ traits::{ Hash, StaticLookup, Zero, MaybeSerializeDeserialize, Member, @@ -672,112 +671,11 @@ impl Module { gas_meter.refund(post_info.calc_unspent(&info)); Self::deposit_event(RawEvent::Dispatched(who, result.is_ok())); } - RestoreTo { - donor, - dest, - code_hash, - rent_allowance, - delta, - } => { - let result = Self::restore_to( - donor.clone(), dest.clone(), code_hash.clone(), rent_allowance.clone(), delta - ); - Self::deposit_event( - RawEvent::Restored(donor, dest, code_hash, rent_allowance, result.is_ok()) - ); - } } }); result } - - fn restore_to( - origin: T::AccountId, - dest: T::AccountId, - code_hash: CodeHash, - rent_allowance: BalanceOf, - delta: Vec, - ) -> DispatchResult { - let mut origin_contract = >::get(&origin) - .and_then(|c| c.get_alive()) - .ok_or(Error::::InvalidSourceContract)?; - - let current_block = >::block_number(); - - if origin_contract.last_write == Some(current_block) { - Err(Error::::InvalidContractOrigin)? - } - - let dest_tombstone = >::get(&dest) - .and_then(|c| c.get_tombstone()) - .ok_or(Error::::InvalidDestinationContract)?; - - let last_write = if !delta.is_empty() { - Some(current_block) - } else { - origin_contract.last_write - }; - - let key_values_taken = delta.iter() - .filter_map(|key| { - child::get_raw( - &origin_contract.child_trie_info(), - &blake2_256(key), - ).map(|value| { - child::kill( - &origin_contract.child_trie_info(), - &blake2_256(key), - ); - - (key, value) - }) - }) - .collect::>(); - - let tombstone = >::new( - // This operation is cheap enough because last_write (delta not included) - // is not this block as it has been checked earlier. - &child::root( - &origin_contract.child_trie_info(), - )[..], - code_hash, - ); - - if tombstone != dest_tombstone { - for (key, value) in key_values_taken { - child::put_raw( - &origin_contract.child_trie_info(), - &blake2_256(key), - &value, - ); - } - - return Err(Error::::InvalidTombstone.into()); - } - - origin_contract.storage_size -= key_values_taken.iter() - .map(|(_, value)| value.len() as u32) - .sum::(); - - >::remove(&origin); - >::insert(&dest, ContractInfo::Alive(RawAliveContractInfo { - trie_id: origin_contract.trie_id, - storage_size: origin_contract.storage_size, - empty_pair_count: origin_contract.empty_pair_count, - total_pair_count: origin_contract.total_pair_count, - code_hash, - rent_allowance, - deduct_block: current_block, - last_write, - })); - - let origin_free_balance = T::Currency::free_balance(&origin); - T::Currency::make_free_balance_be(&origin, >::zero()); - T::Currency::deposit_creating(&dest, origin_free_balance); - - Ok(()) - } } decl_event! { diff --git a/frame/contracts/src/rent.rs b/frame/contracts/src/rent.rs index 1d8f47462731b..fc9d53062f83e 100644 --- a/frame/contracts/src/rent.rs +++ b/frame/contracts/src/rent.rs @@ -396,3 +396,86 @@ pub fn compute_rent_projection( current_block_number + blocks_left, )) } + +/// Restores the destination account using the origin as prototype. +/// +/// The restoration will be performed iff: +/// - origin exists and is alive, +/// - the origin's storage is not written in the current block +/// - the restored account has tombstone +/// - the tombstone matches the hash of the origin storage root, and code hash. +/// +/// Upon succesful restoration, `origin` will be destroyed, all its funds are transferred to +/// the restored account. The restored account will inherit the last write block and its last +/// deduct block will be set to the current block. +pub fn restore_to( + origin: T::AccountId, + dest: T::AccountId, + code_hash: CodeHash, + rent_allowance: BalanceOf, + delta: Vec, +) -> Result<(), &'static str> { + let mut origin_contract = >::get(&origin) + .and_then(|c| c.get_alive()) + .ok_or("Cannot restore from inexisting or tombstone contract")?; + + let current_block = >::block_number(); + + if origin_contract.last_write == Some(current_block) { + return Err("Origin TrieId written in the current block"); + } + + let dest_tombstone = >::get(&dest) + .and_then(|c| c.get_tombstone()) + .ok_or("Cannot restore to inexisting or alive contract")?; + + let last_write = if !delta.is_empty() { + Some(current_block) + } else { + origin_contract.last_write + }; + + let key_values_taken = delta.iter() + .filter_map(|key| { + child::get_raw(&origin_contract.trie_id, &blake2_256(key)).map(|value| { + child::kill(&origin_contract.trie_id, &blake2_256(key)); + (key, value) + }) + }) + .collect::>(); + + let tombstone = >::new( + // This operation is cheap enough because last_write (delta not included) + // is not this block as it has been checked earlier. + &sp_io::storage::child_root(&origin_contract.trie_id)[..], + code_hash, + ); + + if tombstone != dest_tombstone { + for (key, value) in key_values_taken { + child::put_raw(&origin_contract.trie_id, &blake2_256(key), &value); + } + + return Err("Tombstones don't match"); + } + + origin_contract.storage_size -= key_values_taken.iter() + .map(|(_, value)| value.len() as u32) + .sum::(); + + >::remove(&origin); + >::insert(&dest, ContractInfo::Alive(RawAliveContractInfo { + trie_id: origin_contract.trie_id, + storage_size: origin_contract.storage_size, + code_hash, + rent_allowance, + deduct_block: current_block, + last_write, + })); + + let origin_free_balance = T::Currency::free_balance(&origin); + T::Currency::make_free_balance_be(&origin, >::zero()); + T::Currency::deposit_creating(&dest, origin_free_balance); + + Ok(()) +} diff --git a/frame/contracts/src/tests/mod.rs b/frame/contracts/src/tests/mod.rs new file mode 100644 index 0000000000000..a776db7853b2a --- /dev/null +++ b/frame/contracts/src/tests/mod.rs @@ -0,0 +1,2475 @@ +// Copyright 2018-2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +// TODO: #1417 Add more integration tests +// also remove the #![allow(unused)] below. + +#![allow(unused)] + +use crate::{ + BalanceOf, ComputeDispatchFee, ContractAddressFor, ContractInfo, ContractInfoOf, GenesisConfig, + Module, RawAliveContractInfo, RawEvent, Trait, TrieId, TrieIdFromParentCounter, Schedule, + TrieIdGenerator, CheckBlockGasLimit, CodeHash, storage, +}; +use assert_matches::assert_matches; +use hex_literal::*; +use codec::{Decode, Encode, KeyedVec}; +use sp_runtime::{ + Perbill, BuildStorage, transaction_validity::{InvalidTransaction, ValidTransaction}, + traits::{BlakeTwo256, Hash, IdentityLookup, SignedExtension}, + testing::{Digest, DigestItem, Header, UintAuthorityId, H256}, +}; +use support::{ + assert_ok, assert_err, impl_outer_dispatch, impl_outer_event, impl_outer_origin, parameter_types, + storage::child, StorageMap, StorageValue, traits::{Currency, Get}, + weights::{DispatchInfo, DispatchClass, Weight}, +}; +use std::{cell::RefCell, sync::atomic::{AtomicUsize, Ordering}}; +use primitives::storage::well_known_keys; +use system::{self, EventRecord, Phase}; + +mod contract { + // Re-export contents of the root. This basically + // needs to give a name for the current crate. + // This hack is required for `impl_outer_event!`. + pub use super::super::*; + use support::impl_outer_event; +} +impl_outer_event! { + pub enum MetaEvent for Test { + balances, contract, + } +} +impl_outer_origin! { + pub enum Origin for Test { } +} +impl_outer_dispatch! { + pub enum Call for Test where origin: Origin { + balances::Balances, + contract::Contract, + } +} + +thread_local! { + static EXISTENTIAL_DEPOSIT: RefCell = RefCell::new(0); + static TRANSFER_FEE: RefCell = RefCell::new(0); + static INSTANTIATION_FEE: RefCell = RefCell::new(0); + static BLOCK_GAS_LIMIT: RefCell = RefCell::new(0); +} + +pub struct ExistentialDeposit; +impl Get for ExistentialDeposit { + fn get() -> u64 { EXISTENTIAL_DEPOSIT.with(|v| *v.borrow()) } +} + +pub struct TransferFee; +impl Get for TransferFee { + fn get() -> u64 { TRANSFER_FEE.with(|v| *v.borrow()) } +} + +pub struct CreationFee; +impl Get for CreationFee { + fn get() -> u64 { INSTANTIATION_FEE.with(|v| *v.borrow()) } +} + +pub struct BlockGasLimit; +impl Get for BlockGasLimit { + fn get() -> u64 { BLOCK_GAS_LIMIT.with(|v| *v.borrow()) } +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct Test; +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: Weight = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); +} +impl system::Trait for Test { + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Call = (); + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = MetaEvent; + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type AvailableBlockRatio = AvailableBlockRatio; + type MaximumBlockLength = MaximumBlockLength; + type Version = (); +} +impl balances::Trait for Test { + type Balance = u64; + type OnFreeBalanceZero = Contract; + type OnNewAccount = (); + type Event = MetaEvent; + type DustRemoval = (); + type TransferPayment = (); + type ExistentialDeposit = ExistentialDeposit; + type TransferFee = TransferFee; + type CreationFee = CreationFee; +} +parameter_types! { + pub const MinimumPeriod: u64 = 1; +} +impl timestamp::Trait for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; +} +parameter_types! { + pub const SignedClaimHandicap: u64 = 2; + pub const TombstoneDeposit: u64 = 16; + pub const StorageSizeOffset: u32 = 8; + pub const RentByteFee: u64 = 4; + pub const RentDepositOffset: u64 = 10_000; + pub const SurchargeReward: u64 = 150; + pub const TransactionBaseFee: u64 = 2; + pub const TransactionByteFee: u64 = 6; + pub const ContractFee: u64 = 21; + pub const CallBaseFee: u64 = 135; + pub const InstantiateBaseFee: u64 = 175; + pub const MaxDepth: u32 = 100; + pub const MaxValueSize: u32 = 16_384; +} +impl Trait for Test { + type Currency = Balances; + type Time = Timestamp; + type Randomness = Randomness; + type Call = Call; + type DetermineContractAddress = DummyContractAddressFor; + type Event = MetaEvent; + type ComputeDispatchFee = DummyComputeDispatchFee; + type TrieIdGenerator = DummyTrieIdGenerator; + type GasPayment = (); + type RentPayment = (); + type SignedClaimHandicap = SignedClaimHandicap; + type TombstoneDeposit = TombstoneDeposit; + type StorageSizeOffset = StorageSizeOffset; + type RentByteFee = RentByteFee; + type RentDepositOffset = RentDepositOffset; + type SurchargeReward = SurchargeReward; + type TransferFee = TransferFee; + type CreationFee = CreationFee; + type TransactionBaseFee = TransactionBaseFee; + type TransactionByteFee = TransactionByteFee; + type ContractFee = ContractFee; + type CallBaseFee = CallBaseFee; + type InstantiateBaseFee = InstantiateBaseFee; + type MaxDepth = MaxDepth; + type MaxValueSize = MaxValueSize; + type BlockGasLimit = BlockGasLimit; +} + +type Balances = balances::Module; +type Timestamp = timestamp::Module; +type Contract = Module; +type System = system::Module; +type Randomness = randomness_collective_flip::Module; + +pub struct DummyContractAddressFor; +impl ContractAddressFor for DummyContractAddressFor { + fn contract_address_for(_code_hash: &H256, _data: &[u8], origin: &u64) -> u64 { + *origin + 1 + } +} + +pub struct DummyTrieIdGenerator; +impl TrieIdGenerator for DummyTrieIdGenerator { + fn trie_id(account_id: &u64) -> TrieId { + use primitives::storage::well_known_keys; + + let new_seed = super::AccountCounter::mutate(|v| { + *v = v.wrapping_add(1); + *v + }); + + // TODO: see https://github.com/paritytech/substrate/issues/2325 + let mut res = vec![]; + res.extend_from_slice(well_known_keys::CHILD_STORAGE_KEY_PREFIX); + res.extend_from_slice(b"default:"); + res.extend_from_slice(&new_seed.to_le_bytes()); + res.extend_from_slice(&account_id.to_le_bytes()); + res + } +} + +pub struct DummyComputeDispatchFee; +impl ComputeDispatchFee for DummyComputeDispatchFee { + fn compute_dispatch_fee(call: &Call) -> u64 { + 69 + } +} + +const ALICE: u64 = 1; +const BOB: u64 = 2; +const CHARLIE: u64 = 3; +const DJANGO: u64 = 4; + +pub struct ExtBuilder { + existential_deposit: u64, + gas_price: u64, + block_gas_limit: u64, + transfer_fee: u64, + instantiation_fee: u64, +} +impl Default for ExtBuilder { + fn default() -> Self { + Self { + existential_deposit: 0, + gas_price: 2, + block_gas_limit: 100_000_000, + transfer_fee: 0, + instantiation_fee: 0, + } + } +} +impl ExtBuilder { + pub fn existential_deposit(mut self, existential_deposit: u64) -> Self { + self.existential_deposit = existential_deposit; + self + } + pub fn gas_price(mut self, gas_price: u64) -> Self { + self.gas_price = gas_price; + self + } + pub fn block_gas_limit(mut self, block_gas_limit: u64) -> Self { + self.block_gas_limit = block_gas_limit; + self + } + pub fn transfer_fee(mut self, transfer_fee: u64) -> Self { + self.transfer_fee = transfer_fee; + self + } + pub fn instantiation_fee(mut self, instantiation_fee: u64) -> Self { + self.instantiation_fee = instantiation_fee; + self + } + pub fn set_associated_consts(&self) { + EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); + TRANSFER_FEE.with(|v| *v.borrow_mut() = self.transfer_fee); + INSTANTIATION_FEE.with(|v| *v.borrow_mut() = self.instantiation_fee); + BLOCK_GAS_LIMIT.with(|v| *v.borrow_mut() = self.block_gas_limit); + } + pub fn build(self) -> sp_io::TestExternalities { + self.set_associated_consts(); + let mut t = system::GenesisConfig::default().build_storage::().unwrap(); + balances::GenesisConfig:: { + balances: vec![], + vesting: vec![], + }.assimilate_storage(&mut t).unwrap(); + GenesisConfig:: { + current_schedule: Schedule { + enable_println: true, + ..Default::default() + }, + gas_price: self.gas_price, + }.assimilate_storage(&mut t).unwrap(); + sp_io::TestExternalities::new(t) + } +} + +/// Generate Wasm binary and code hash from wabt source. +fn compile_module(wabt_module: &str) + -> Result<(Vec, ::Output), wabt::Error> + where T: system::Trait +{ + let wasm = wabt::wat2wasm(wabt_module)?; + let code_hash = T::Hashing::hash(&wasm); + Ok((wasm, code_hash)) +} + +pub fn set_balance(who: &u64, amount: u64) { + Balances::deposit_creating(who, amount); +} + +pub fn get_balance(who: &u64) -> u64 { + Balances::free_balance(who) +} + +pub fn place_contract(address: &u64, code_hash: CodeHash) { + use crate::TrieIdGenerator; + let trie_id = ::TrieIdGenerator::trie_id(address); + storage::place_contract::(&address, trie_id, code_hash).unwrap() +} + +pub fn get_storage(who: &u64, key: [u8; 32]) -> Option> { + use crate::ContractInfoOf; + let trie_id = >::get(who) + .expect("no contract info under the given address") + .get_alive() + .expect("the contract exists but not alive") + .trie_id; + storage::read_contract_storage(&trie_id, &key) +} + +pub fn set_storage(who: &u64, key: [u8; 32], value: Option>) { + use crate::ContractInfoOf; + let trie_id = >::get(who) + .expect("no contract info under the given address") + .get_alive() + .expect("the contract exists but not alive") + .trie_id; + storage::write_contract_storage::(who, &trie_id, &key, value); +} + +// Perform a simple transfer to a non-existent account supplying way more gas than needed. +// Then we check that the all unused gas is refunded. +#[test] +fn refunds_unused_gas() { + ExtBuilder::default().gas_price(2).build().execute_with(|| { + Balances::deposit_creating(&ALICE, 100_000_000); + + assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, Vec::new())); + + // 2 * 135 - gas price multiplied by the call base fee. + assert_eq!(Balances::free_balance(&ALICE), 100_000_000 - (2 * 135)); + }); +} + +#[test] +fn account_removal_removes_storage() { + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let key1 = [1; 32]; + let key2 = [2; 32]; + + // Set up two accounts with free balance above the existential threshold. + { + Balances::deposit_creating(&1, 110); + place_contract(&1, H256::repeat_byte(1)); + set_storage(&1, key1.clone(), Some(b"1".to_vec())); + set_storage(&1, key2.clone(), Some(b"2".to_vec())); + + Balances::deposit_creating(&2, 110); + place_contract(&2, H256::repeat_byte(2)); + set_storage(&2, key1.clone(), Some(b"3".to_vec())); + set_storage(&2, key2.clone(), Some(b"4".to_vec())); + } + + // Transfer funds from account 1 of such amount that after this transfer + // the balance of account 1 will be below the existential threshold. + // + // This should trigger OnFreeBalanceZero callback and therefore kill the child storage and + // remove the contract info. + assert_ok!(Balances::transfer(Origin::signed(1), 2, 20)); + + // Verify that the account 1 is removed, while the account 2 is in place. + { + assert!(>::get(&1).is_none()); + + assert!(>::get(&2).is_some()); + assert_eq!( + get_storage(&2, key1), + Some(b"3".to_vec()) + ); + assert_eq!( + get_storage(&2, key2), + Some(b"4".to_vec()) + ); + } + }); +} + +const CODE_RETURN_FROM_START_FN: &str = r#" +(module + (import "env" "ext_return" (func $ext_return (param i32 i32))) + (import "env" "ext_deposit_event" (func $ext_deposit_event (param i32 i32 i32 i32))) + (import "env" "memory" (memory 1 1)) + + (start $start) + (func $start + (call $ext_deposit_event + (i32.const 0) ;; The topics buffer + (i32.const 0) ;; The topics buffer's length + (i32.const 8) ;; The data buffer + (i32.const 4) ;; The data buffer's length + ) + (call $ext_return + (i32.const 8) + (i32.const 4) + ) + (unreachable) + ) + + (func (export "call") + (unreachable) + ) + (func (export "deploy")) + + (data (i32.const 8) "\01\02\03\04") +) +"#; + +#[test] +fn instantiate_and_call_and_deposit_event() { + let (wasm, code_hash) = compile_module::(CODE_RETURN_FROM_START_FN).unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + Balances::deposit_creating(&ALICE, 1_000_000); + + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); + + // Check at the end to get hash on error easily + let creation = Contract::instantiate( + Origin::signed(ALICE), + 100, + 100_000, + code_hash.into(), + vec![], + ); + + assert_eq!(System::events(), vec![ + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::balances(balances::RawEvent::NewAccount(1, 1_000_000)), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::CodeStored(code_hash.into())), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::balances( + balances::RawEvent::NewAccount(BOB, 100) + ), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::balances( + balances::RawEvent::Transfer(ALICE, BOB, 100, 0) + ), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::Contract(BOB, vec![1, 2, 3, 4])), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::Instantiated(ALICE, BOB)), + topics: vec![], + }, + ]); + + assert_ok!(creation); + assert!(ContractInfoOf::::exists(BOB)); + }); +} + +const CODE_DISPATCH_CALL: &str = r#" +(module + (import "env" "ext_dispatch_call" (func $ext_dispatch_call (param i32 i32))) + (import "env" "memory" (memory 1 1)) + + (func (export "call") + (call $ext_dispatch_call + (i32.const 8) ;; Pointer to the start of encoded call buffer + (i32.const 11) ;; Length of the buffer + ) + ) + (func (export "deploy")) + + (data (i32.const 8) "\00\00\03\00\00\00\00\00\00\00\C8") +) +"#; + +#[test] +fn dispatch_call() { + // This test can fail due to the encoding changes. In case it becomes too annoying + // let's rewrite so as we use this module controlled call or we serialize it in runtime. + let encoded = Encode::encode(&Call::Balances(balances::Call::transfer(CHARLIE, 50))); + assert_eq!(&encoded[..], &hex!("00000300000000000000C8")[..]); + + let (wasm, code_hash) = compile_module::(CODE_DISPATCH_CALL).unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + Balances::deposit_creating(&ALICE, 1_000_000); + + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); + + // Let's keep this assert even though it's redundant. If you ever need to update the + // wasm source this test will fail and will show you the actual hash. + assert_eq!(System::events(), vec![ + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::balances(balances::RawEvent::NewAccount(1, 1_000_000)), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::CodeStored(code_hash.into())), + topics: vec![], + }, + ]); + + assert_ok!(Contract::instantiate( + Origin::signed(ALICE), + 100, + 100_000, + code_hash.into(), + vec![], + )); + + assert_ok!(Contract::call( + Origin::signed(ALICE), + BOB, // newly created account + 0, + 100_000, + vec![], + )); + + assert_eq!(System::events(), vec![ + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::balances(balances::RawEvent::NewAccount(1, 1_000_000)), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::CodeStored(code_hash.into())), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::balances( + balances::RawEvent::NewAccount(BOB, 100) + ), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::balances( + balances::RawEvent::Transfer(ALICE, BOB, 100, 0) + ), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::Instantiated(ALICE, BOB)), + topics: vec![], + }, + + // Dispatching the call. + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::balances( + balances::RawEvent::NewAccount(CHARLIE, 50) + ), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::balances( + balances::RawEvent::Transfer(BOB, CHARLIE, 50, 0) + ), + topics: vec![], + }, + + // Event emited as a result of dispatch. + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::Dispatched(BOB, true)), + topics: vec![], + } + ]); + }); +} + +const CODE_DISPATCH_CALL_THEN_TRAP: &str = r#" +(module + (import "env" "ext_dispatch_call" (func $ext_dispatch_call (param i32 i32))) + (import "env" "memory" (memory 1 1)) + + (func (export "call") + (call $ext_dispatch_call + (i32.const 8) ;; Pointer to the start of encoded call buffer + (i32.const 11) ;; Length of the buffer + ) + (unreachable) ;; trap so that the top level transaction fails + ) + (func (export "deploy")) + + (data (i32.const 8) "\00\00\03\00\00\00\00\00\00\00\C8") +) +"#; + +#[test] +fn dispatch_call_not_dispatched_after_top_level_transaction_failure() { + // This test can fail due to the encoding changes. In case it becomes too annoying + // let's rewrite so as we use this module controlled call or we serialize it in runtime. + let encoded = Encode::encode(&Call::Balances(balances::Call::transfer(CHARLIE, 50))); + assert_eq!(&encoded[..], &hex!("00000300000000000000C8")[..]); + + let (wasm, code_hash) = compile_module::(CODE_DISPATCH_CALL_THEN_TRAP).unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + Balances::deposit_creating(&ALICE, 1_000_000); + + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); + + // Let's keep this assert even though it's redundant. If you ever need to update the + // wasm source this test will fail and will show you the actual hash. + assert_eq!(System::events(), vec![ + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::balances(balances::RawEvent::NewAccount(1, 1_000_000)), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::CodeStored(code_hash.into())), + topics: vec![], + }, + ]); + + assert_ok!(Contract::instantiate( + Origin::signed(ALICE), + 100, + 100_000, + code_hash.into(), + vec![], + )); + + // Call the newly instantiated contract. The contract is expected to dispatch a call + // and then trap. + assert_err!( + Contract::call( + Origin::signed(ALICE), + BOB, // newly created account + 0, + 100_000, + vec![], + ), + "during execution" + ); + assert_eq!(System::events(), vec![ + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::balances(balances::RawEvent::NewAccount(1, 1_000_000)), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::CodeStored(code_hash.into())), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::balances( + balances::RawEvent::NewAccount(BOB, 100) + ), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::balances( + balances::RawEvent::Transfer(ALICE, BOB, 100, 0) + ), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::Instantiated(ALICE, BOB)), + topics: vec![], + }, + // ABSENCE of events which would be caused by dispatched Balances::transfer call + ]); + }); +} + +const CODE_SET_RENT: &str = r#" +(module + (import "env" "ext_dispatch_call" (func $ext_dispatch_call (param i32 i32))) + (import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32 i32))) + (import "env" "ext_set_rent_allowance" (func $ext_set_rent_allowance (param i32 i32))) + (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" "memory" (memory 1 1)) + + ;; insert a value of 4 bytes into storage + (func $call_0 + (call $ext_set_storage + (i32.const 1) + (i32.const 1) + (i32.const 0) + (i32.const 4) + ) + ) + + ;; remove the value inserted by call_1 + (func $call_1 + (call $ext_set_storage + (i32.const 1) + (i32.const 0) + (i32.const 0) + (i32.const 0) + ) + ) + + ;; transfer 50 to ALICE + (func $call_2 + (call $ext_dispatch_call + (i32.const 68) + (i32.const 11) + ) + ) + + ;; do nothing + (func $call_else) + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + ;; Dispatch the call according to input size + (func (export "call") + (local $input_size i32) + (set_local $input_size + (call $ext_scratch_size) + ) + (block $IF_ELSE + (block $IF_2 + (block $IF_1 + (block $IF_0 + (br_table $IF_0 $IF_1 $IF_2 $IF_ELSE + (get_local $input_size) + ) + (unreachable) + ) + (call $call_0) + return + ) + (call $call_1) + return + ) + (call $call_2) + return + ) + (call $call_else) + ) + + ;; Set into storage a 4 bytes value + ;; Set call set_rent_allowance with input + (func (export "deploy") + (local $input_size i32) + (set_local $input_size + (call $ext_scratch_size) + ) + (call $ext_set_storage + (i32.const 0) + (i32.const 1) + (i32.const 0) + (i32.const 4) + ) + (call $ext_scratch_read + (i32.const 0) + (i32.const 0) + (get_local $input_size) + ) + (call $ext_set_rent_allowance + (i32.const 0) + (get_local $input_size) + ) + ) + + ;; Encoding of 10 in balance + (data (i32.const 0) "\28") + + ;; Encoding of call transfer 50 to CHARLIE + (data (i32.const 68) "\00\00\03\00\00\00\00\00\00\00\C8") +) +"#; + +/// Input data for each call in set_rent code +mod call { + pub fn set_storage_4_byte() -> Vec { vec![] } + pub fn remove_storage_4_byte() -> Vec { vec![0] } + pub fn transfer() -> Vec { vec![0, 0] } + pub fn null() -> Vec { vec![0, 0, 0] } +} + +/// Test correspondence of set_rent code and its hash. +/// Also test that encoded extrinsic in code correspond to the correct transfer +#[test] +fn test_set_rent_code_and_hash() { + // This test can fail due to the encoding changes. In case it becomes too annoying + // let's rewrite so as we use this module controlled call or we serialize it in runtime. + let encoded = Encode::encode(&Call::Balances(balances::Call::transfer(CHARLIE, 50))); + assert_eq!(&encoded[..], &hex!("00000300000000000000C8")[..]); + + let (wasm, code_hash) = compile_module::(CODE_SET_RENT).unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + Balances::deposit_creating(&ALICE, 1_000_000); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); + + // If you ever need to update the wasm source this test will fail + // and will show you the actual hash. + assert_eq!(System::events(), vec![ + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::balances(balances::RawEvent::NewAccount(1, 1_000_000)), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::CodeStored(code_hash.into())), + topics: vec![], + }, + ]); + }); +} + +#[test] +fn storage_size() { + let (wasm, code_hash) = compile_module::(CODE_SET_RENT).unwrap(); + + // Storage size + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + // Create + Balances::deposit_creating(&ALICE, 1_000_000); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); + assert_ok!(Contract::instantiate( + Origin::signed(ALICE), + 30_000, + 100_000, code_hash.into(), + ::Balance::from(1_000u32).encode() // rent allowance + )); + let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap(); + assert_eq!(bob_contract.storage_size, ::StorageSizeOffset::get() + 4); + + assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::set_storage_4_byte())); + let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap(); + assert_eq!(bob_contract.storage_size, ::StorageSizeOffset::get() + 4 + 4); + + assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::remove_storage_4_byte())); + let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap(); + assert_eq!(bob_contract.storage_size, ::StorageSizeOffset::get() + 4); + }); +} + +#[test] +fn deduct_blocks() { + let (wasm, code_hash) = compile_module::(CODE_SET_RENT).unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + // Create + Balances::deposit_creating(&ALICE, 1_000_000); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); + assert_ok!(Contract::instantiate( + Origin::signed(ALICE), + 30_000, + 100_000, code_hash.into(), + ::Balance::from(1_000u32).encode() // rent allowance + )); + + // Check creation + let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap(); + assert_eq!(bob_contract.rent_allowance, 1_000); + + // Advance 4 blocks + System::initialize(&5, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + + // Trigger rent through call + assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null())); + + // Check result + let rent = (8 + 4 - 3) // storage size = size_offset + deploy_set_storage - deposit_offset + * 4 // rent byte price + * 4; // blocks to rent + let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap(); + assert_eq!(bob_contract.rent_allowance, 1_000 - rent); + assert_eq!(bob_contract.deduct_block, 5); + assert_eq!(Balances::free_balance(BOB), 30_000 - rent); + + // Advance 7 blocks more + System::initialize(&12, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + + // Trigger rent through call + assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null())); + + // Check result + let rent_2 = (8 + 4 - 2) // storage size = size_offset + deploy_set_storage - deposit_offset + * 4 // rent byte price + * 7; // blocks to rent + let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap(); + assert_eq!(bob_contract.rent_allowance, 1_000 - rent - rent_2); + assert_eq!(bob_contract.deduct_block, 12); + assert_eq!(Balances::free_balance(BOB), 30_000 - rent - rent_2); + + // Second call on same block should have no effect on rent + assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null())); + + let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap(); + assert_eq!(bob_contract.rent_allowance, 1_000 - rent - rent_2); + assert_eq!(bob_contract.deduct_block, 12); + assert_eq!(Balances::free_balance(BOB), 30_000 - rent - rent_2); + }); +} + +#[test] +fn call_contract_removals() { + removals(|| { + // Call on already-removed account might fail, and this is fine. + Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()); + true + }); +} + +#[test] +fn inherent_claim_surcharge_contract_removals() { + removals(|| Contract::claim_surcharge(Origin::NONE, BOB, Some(ALICE)).is_ok()); +} + +#[test] +fn signed_claim_surcharge_contract_removals() { + removals(|| Contract::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok()); +} + +#[test] +fn claim_surcharge_malus() { + // Test surcharge malus for inherent + claim_surcharge(4, || Contract::claim_surcharge(Origin::NONE, BOB, Some(ALICE)).is_ok(), true); + claim_surcharge(3, || Contract::claim_surcharge(Origin::NONE, BOB, Some(ALICE)).is_ok(), true); + claim_surcharge(2, || Contract::claim_surcharge(Origin::NONE, BOB, Some(ALICE)).is_ok(), true); + claim_surcharge(1, || Contract::claim_surcharge(Origin::NONE, BOB, Some(ALICE)).is_ok(), false); + + // Test surcharge malus for signed + claim_surcharge(4, || Contract::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok(), true); + claim_surcharge(3, || Contract::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok(), false); + claim_surcharge(2, || Contract::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok(), false); + claim_surcharge(1, || Contract::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok(), false); +} + +/// Claim surcharge with the given trigger_call at the given blocks. +/// if removes is true then assert that the contract is a tombstonedead +fn claim_surcharge(blocks: u64, trigger_call: impl Fn() -> bool, removes: bool) { + let (wasm, code_hash) = compile_module::(CODE_SET_RENT).unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + // Create + Balances::deposit_creating(&ALICE, 1_000_000); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); + assert_ok!(Contract::instantiate( + Origin::signed(ALICE), + 100, + 100_000, code_hash.into(), + ::Balance::from(1_000u32).encode() // rent allowance + )); + + // Advance blocks + System::initialize(&blocks, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + + // Trigger rent through call + assert!(trigger_call()); + + if removes { + assert!(ContractInfoOf::::get(BOB).unwrap().get_tombstone().is_some()); + } else { + assert!(ContractInfoOf::::get(BOB).unwrap().get_alive().is_some()); + } + }); +} + +/// Test for all kind of removals for the given trigger: +/// * if balance is reached and balance > subsistence threshold +/// * if allowance is exceeded +/// * if balance is reached and balance < subsistence threshold +fn removals(trigger_call: impl Fn() -> bool) { + let (wasm, code_hash) = compile_module::(CODE_SET_RENT).unwrap(); + + // Balance reached and superior to subsistence threshold + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + // Create + Balances::deposit_creating(&ALICE, 1_000_000); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm.clone())); + assert_ok!(Contract::instantiate( + Origin::signed(ALICE), + 100, + 100_000, code_hash.into(), + ::Balance::from(1_000u32).encode() // rent allowance + )); + + let subsistence_threshold = 50 /*existential_deposit*/ + 16 /*tombstone_deposit*/; + + // Trigger rent must have no effect + assert!(trigger_call()); + assert_eq!(ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap().rent_allowance, 1_000); + assert_eq!(Balances::free_balance(&BOB), 100); + + // Advance blocks + System::initialize(&10, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + + // Trigger rent through call + assert!(trigger_call()); + assert!(ContractInfoOf::::get(BOB).unwrap().get_tombstone().is_some()); + assert_eq!(Balances::free_balance(&BOB), subsistence_threshold); + + // Advance blocks + System::initialize(&20, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + + // Trigger rent must have no effect + assert!(trigger_call()); + assert!(ContractInfoOf::::get(BOB).unwrap().get_tombstone().is_some()); + assert_eq!(Balances::free_balance(&BOB), subsistence_threshold); + }); + + // Allowance exceeded + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + // Create + Balances::deposit_creating(&ALICE, 1_000_000); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm.clone())); + assert_ok!(Contract::instantiate( + Origin::signed(ALICE), + 1_000, + 100_000, code_hash.into(), + ::Balance::from(100u32).encode() // rent allowance + )); + + // Trigger rent must have no effect + assert!(trigger_call()); + assert_eq!(ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap().rent_allowance, 100); + assert_eq!(Balances::free_balance(&BOB), 1_000); + + // Advance blocks + System::initialize(&10, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + + // Trigger rent through call + assert!(trigger_call()); + assert!(ContractInfoOf::::get(BOB).unwrap().get_tombstone().is_some()); + // Balance should be initial balance - initial rent_allowance + assert_eq!(Balances::free_balance(&BOB), 900); + + // Advance blocks + System::initialize(&20, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + + // Trigger rent must have no effect + assert!(trigger_call()); + assert!(ContractInfoOf::::get(BOB).unwrap().get_tombstone().is_some()); + assert_eq!(Balances::free_balance(&BOB), 900); + }); + + // Balance reached and inferior to subsistence threshold + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + // Create + Balances::deposit_creating(&ALICE, 1_000_000); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm.clone())); + assert_ok!(Contract::instantiate( + Origin::signed(ALICE), + 50+Balances::minimum_balance(), + 100_000, code_hash.into(), + ::Balance::from(1_000u32).encode() // rent allowance + )); + + // Trigger rent must have no effect + assert!(trigger_call()); + assert_eq!(ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap().rent_allowance, 1_000); + assert_eq!(Balances::free_balance(&BOB), 50 + Balances::minimum_balance()); + + // Transfer funds + assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::transfer())); + assert_eq!(ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap().rent_allowance, 1_000); + assert_eq!(Balances::free_balance(&BOB), Balances::minimum_balance()); + + // Advance blocks + System::initialize(&10, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + + // Trigger rent through call + assert!(trigger_call()); + assert!(ContractInfoOf::::get(BOB).is_none()); + assert_eq!(Balances::free_balance(&BOB), Balances::minimum_balance()); + + // Advance blocks + System::initialize(&20, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + + // Trigger rent must have no effect + assert!(trigger_call()); + assert!(ContractInfoOf::::get(BOB).is_none()); + assert_eq!(Balances::free_balance(&BOB), Balances::minimum_balance()); + }); +} + +#[test] +fn call_removed_contract() { + let (wasm, code_hash) = compile_module::(CODE_SET_RENT).unwrap(); + + // Balance reached and superior to subsistence threshold + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + // Create + Balances::deposit_creating(&ALICE, 1_000_000); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm.clone())); + assert_ok!(Contract::instantiate( + Origin::signed(ALICE), + 100, + 100_000, code_hash.into(), + ::Balance::from(1_000u32).encode() // rent allowance + )); + + // Calling contract should succeed. + assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null())); + + // Advance blocks + System::initialize(&10, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + + // Calling contract should remove contract and fail. + assert_err!( + Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()), + "contract has been evicted" + ); + + // Subsequent contract calls should also fail. + assert_err!( + Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()), + "contract has been evicted" + ); + }) +} + +const CODE_CHECK_DEFAULT_RENT_ALLOWANCE: &str = r#" +(module + (import "env" "ext_rent_allowance" (func $ext_rent_allowance)) + (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" "memory" (memory 1 1)) + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "call")) + + (func (export "deploy") + ;; fill the scratch buffer with the rent allowance. + (call $ext_rent_allowance) + + ;; assert $ext_scratch_size == 8 + (call $assert + (i32.eq + (call $ext_scratch_size) + (i32.const 8) + ) + ) + + ;; copy contents of the scratch buffer into the contract's memory. + (call $ext_scratch_read + (i32.const 8) ;; Pointer in memory to the place where to copy. + (i32.const 0) ;; Offset from the start of the scratch buffer. + (i32.const 8) ;; Count of bytes to copy. + ) + + ;; assert that contents of the buffer is equal to >::max_value(). + (call $assert + (i64.eq + (i64.load + (i32.const 8) + ) + (i64.const 0xFFFFFFFFFFFFFFFF) + ) + ) + ) +) +"#; + +#[test] +fn default_rent_allowance_on_instantiate() { + let (wasm, code_hash) = compile_module::(CODE_CHECK_DEFAULT_RENT_ALLOWANCE).unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + // Create + Balances::deposit_creating(&ALICE, 1_000_000); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); + assert_ok!(Contract::instantiate( + Origin::signed(ALICE), + 30_000, + 100_000, + code_hash.into(), + vec![], + )); + + // Check creation + let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap(); + assert_eq!(bob_contract.rent_allowance, >::max_value()); + + // Advance blocks + System::initialize(&5, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + + // Trigger rent through call + assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null())); + + // Check contract is still alive + let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive(); + assert!(bob_contract.is_some()) + }); +} + +const CODE_RESTORATION: &str = r#" +(module + (import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32 i32))) + (import "env" "ext_restore_to" + (func $ext_restore_to + (param i32 i32 i32 i32 i32 i32 i32 i32) + (result i32) + ) + ) + (import "env" "memory" (memory 1 1)) + + (func (export "call") + ;; Drop the status code. + (drop + (call $ext_restore_to + ;; Pointer and length of the encoded dest buffer. + (i32.const 256) + (i32.const 8) + ;; Pointer and length of the encoded code hash buffer + (i32.const 264) + (i32.const 32) + ;; Pointer and length of the encoded rent_allowance buffer + (i32.const 296) + (i32.const 8) + ;; Pointer and number of items in the delta buffer. + ;; This buffer specifies multiple keys for removal before restoration. + (i32.const 100) + (i32.const 1) + ) + ) + ) + (func (export "deploy") + ;; Data to restore + (call $ext_set_storage + (i32.const 0) + (i32.const 1) + (i32.const 0) + (i32.const 4) + ) + + ;; ACL + (call $ext_set_storage + (i32.const 100) + (i32.const 1) + (i32.const 0) + (i32.const 4) + ) + ) + + ;; Data to restore + (data (i32.const 0) "\28") + + ;; Buffer that has ACL storage keys. + (data (i32.const 100) "\01") + + ;; Address of bob + (data (i32.const 256) "\02\00\00\00\00\00\00\00") + + ;; Code hash of SET_RENT + (data (i32.const 264) + "\14\eb\65\3c\86\98\d6\b2\3d\8d\3c\4a\54\c6\c4\71" + "\b9\fc\19\36\df\ca\a0\a1\f2\dc\ad\9d\e5\36\0b\25" + ) + + ;; Rent allowance + (data (i32.const 296) "\32\00\00\00\00\00\00\00") +) +"#; + +#[test] +fn restorations_dirty_storage_and_different_storage() { + restoration(true, true); +} + +#[test] +fn restorations_dirty_storage() { + restoration(false, true); +} + +#[test] +fn restoration_different_storage() { + restoration(true, false); +} + +#[test] +fn restoration_success() { + restoration(false, false); +} + +fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: bool) { + let (set_rent_wasm, set_rent_code_hash) = compile_module::(CODE_SET_RENT).unwrap(); + let (restoration_wasm, restoration_code_hash) = + compile_module::(CODE_RESTORATION).unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + Balances::deposit_creating(&ALICE, 1_000_000); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, restoration_wasm)); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, set_rent_wasm)); + + // If you ever need to update the wasm source this test will fail + // and will show you the actual hash. + assert_eq!(System::events(), vec![ + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::balances(balances::RawEvent::NewAccount(1, 1_000_000)), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::CodeStored(restoration_code_hash.into())), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::CodeStored(set_rent_code_hash.into())), + topics: vec![], + }, + ]); + + // Create an account with address `BOB` with code `CODE_SET_RENT`. + // The input parameter sets the rent allowance to 0. + assert_ok!(Contract::instantiate( + Origin::signed(ALICE), + 30_000, + 100_000, + set_rent_code_hash.into(), + ::Balance::from(0u32).encode() + )); + + // Check if `BOB` was created successfully and that the rent allowance is + // set to 0. + let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap(); + assert_eq!(bob_contract.rent_allowance, 0); + + if test_different_storage { + assert_ok!(Contract::call( + Origin::signed(ALICE), + BOB, 0, 100_000, + call::set_storage_4_byte()) + ); + } + + // Advance 4 blocks, to the 5th. + System::initialize(&5, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + + // Call `BOB`, which makes it pay rent. Since the rent allowance is set to 0 + // we expect that it will get removed leaving tombstone. + assert_err!( + Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()), + "contract has been evicted" + ); + assert!(ContractInfoOf::::get(BOB).unwrap().get_tombstone().is_some()); + + // Create another account with the address `DJANGO` with `CODE_RESTORATION`. + // + // Note that we can't use `ALICE` for creating `DJANGO` so we create yet another + // account `CHARLIE` and create `DJANGO` with it. + Balances::deposit_creating(&CHARLIE, 1_000_000); + assert_ok!(Contract::instantiate( + Origin::signed(CHARLIE), + 30_000, + 100_000, + restoration_code_hash.into(), + ::Balance::from(0u32).encode() + )); + + // Before performing a call to `DJANGO` save its original trie id. + let django_trie_id = ContractInfoOf::::get(DJANGO).unwrap() + .get_alive().unwrap().trie_id; + + if !test_restore_to_with_dirty_storage { + // Advance 1 block, to the 6th. + System::initialize(&6, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + } + + // Perform a call to `DJANGO`. This should either perform restoration successfully or + // fail depending on the test parameters. + assert_ok!(Contract::call( + Origin::signed(ALICE), + DJANGO, + 0, + 100_000, + vec![], + )); + + if test_different_storage || test_restore_to_with_dirty_storage { + // Parametrization of the test imply restoration failure. Check that `DJANGO` aka + // restoration contract is still in place and also that `BOB` doesn't exist. + assert!(ContractInfoOf::::get(BOB).unwrap().get_tombstone().is_some()); + let django_contract = ContractInfoOf::::get(DJANGO).unwrap() + .get_alive().unwrap(); + assert_eq!(django_contract.storage_size, 16); + assert_eq!(django_contract.trie_id, django_trie_id); + assert_eq!(django_contract.deduct_block, System::block_number()); + } else { + // Here we expect that the restoration is succeeded. Check that the restoration + // contract `DJANGO` ceased to exist and that `BOB` returned back. + println!("{:?}", ContractInfoOf::::get(BOB)); + let bob_contract = ContractInfoOf::::get(BOB).unwrap() + .get_alive().unwrap(); + assert_eq!(bob_contract.rent_allowance, 50); + assert_eq!(bob_contract.storage_size, 12); + assert_eq!(bob_contract.trie_id, django_trie_id); + assert_eq!(bob_contract.deduct_block, System::block_number()); + assert!(ContractInfoOf::::get(DJANGO).is_none()); + } + }); +} + +const CODE_STORAGE_SIZE: &str = r#" +(module + (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_scratch_size" (func $ext_scratch_size (result i32))) + (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) + (import "env" "memory" (memory 16 16)) + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "call") + ;; assert $ext_scratch_size == 8 + (call $assert + (i32.eq + (call $ext_scratch_size) + (i32.const 4) + ) + ) + + ;; copy contents of the scratch buffer into the contract's memory. + (call $ext_scratch_read + (i32.const 32) ;; Pointer in memory to the place where to copy. + (i32.const 0) ;; Offset from the start of the scratch buffer. + (i32.const 4) ;; Count of bytes to copy. + ) + + ;; place a garbage value in storage, the size of which is specified by the call input. + (call $ext_set_storage + (i32.const 0) ;; Pointer to storage key + (i32.const 1) ;; Value is not null + (i32.const 0) ;; Pointer to value + (i32.load (i32.const 32)) ;; Size of value + ) + + (call $assert + (i32.eq + (call $ext_get_storage + (i32.const 0) ;; Pointer to storage key + ) + (i32.const 0) + ) + ) + + (call $assert + (i32.eq + (call $ext_scratch_size) + (i32.load (i32.const 32)) + ) + ) + ) + + (func (export "deploy")) + + (data (i32.const 0) "\01") ;; Storage key (32 B) +) +"#; + +#[test] +fn storage_max_value_limit() { + let (wasm, code_hash) = compile_module::(CODE_STORAGE_SIZE).unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + // Create + Balances::deposit_creating(&ALICE, 1_000_000); + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); + assert_ok!(Contract::instantiate( + Origin::signed(ALICE), + 30_000, + 100_000, + code_hash.into(), + vec![], + )); + + // Check creation + let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap(); + assert_eq!(bob_contract.rent_allowance, >::max_value()); + + // Call contract with allowed storage value. + assert_ok!(Contract::call( + Origin::signed(ALICE), + BOB, + 0, + 100_000, + Encode::encode(&self::MaxValueSize::get()), + )); + + // Call contract with too large a storage value. + assert_err!( + Contract::call( + Origin::signed(ALICE), + BOB, + 0, + 100_000, + Encode::encode(&(self::MaxValueSize::get() + 1)), + ), + "during execution" + ); + }); +} + +const CODE_RETURN_WITH_DATA: &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_scratch_write" (func $ext_scratch_write (param i32 i32))) + (import "env" "memory" (memory 1 1)) + + ;; Deploy routine is the same as call. + (func (export "deploy") (result i32) + (call $call) + ) + + ;; Call reads the first 4 bytes (LE) as the exit status and returns the rest as output data. + (func $call (export "call") (result i32) + (local $buf_size i32) + (local $exit_status i32) + + ;; Find out the size of the scratch buffer + (set_local $buf_size (call $ext_scratch_size)) + + ;; Copy scratch buffer into this contract memory. + (call $ext_scratch_read + (i32.const 0) ;; The pointer where to store the scratch buffer contents, + (i32.const 0) ;; Offset from the start of the scratch buffer. + (get_local $buf_size) ;; Count of bytes to copy. + ) + + ;; Copy all but the first 4 bytes of the input data as the output data. + (call $ext_scratch_write + (i32.const 4) ;; Pointer to the data to return. + (i32.sub ;; Count of bytes to copy. + (get_local $buf_size) + (i32.const 4) + ) + ) + + ;; Return the first 4 bytes of the input data as the exit status. + (i32.load (i32.const 0)) + ) +) +"#; + +const CODE_CALLER_CONTRACT: &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" "ext_instantiate" (func $ext_instantiate (param i32 i32 i64 i32 i32 i32 i32) (result i32))) + (import "env" "ext_println" (func $ext_println (param i32 i32))) + (import "env" "memory" (memory 1 1)) + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func $current_balance (param $sp i32) (result i64) + (call $ext_balance) + (call $assert + (i32.eq (call $ext_scratch_size) (i32.const 8)) + ) + (call $ext_scratch_read + (i32.sub (get_local $sp) (i32.const 8)) + (i32.const 0) + (i32.const 8) + ) + (i64.load (i32.sub (get_local $sp) (i32.const 8))) + ) + + (func (export "deploy")) + + (func (export "call") + (local $sp i32) + (local $exit_code i32) + (local $balance i64) + + ;; 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 24) ;; 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. + ) + + ;; Read current balance into local variable. + (set_local $sp (i32.const 1024)) + (set_local $balance + (call $current_balance (get_local $sp)) + ) + + ;; Fail to deploy the contract since it returns a non-zero exit status. + (set_local $exit_code + (call $ext_instantiate + (i32.const 24) ;; 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 9) ;; Pointer to input data buffer address + (i32.const 7) ;; Length of input data buffer + ) + ) + + ;; Check non-zero exit status. + (call $assert + (i32.eq (get_local $exit_code) (i32.const 0x11)) + ) + + ;; Check that scratch buffer is empty since contract instantiation failed. + (call $assert + (i32.eq (call $ext_scratch_size) (i32.const 0)) + ) + + ;; Check that balance has not changed. + (call $assert + (i64.eq (get_local $balance) (call $current_balance (get_local $sp))) + ) + + ;; Fail to deploy the contract due to insufficient gas. + (set_local $exit_code + (call $ext_instantiate + (i32.const 24) ;; Pointer to the code hash. + (i32.const 32) ;; Length of the code hash. + (i64.const 200) ;; How much gas to devote for the execution. + (i32.const 0) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer. + (i32.const 8) ;; Pointer to input data buffer address + (i32.const 8) ;; Length of input data buffer + ) + ) + + ;; Check for special trap exit status. + (call $assert + (i32.eq (get_local $exit_code) (i32.const 0x0100)) + ) + + ;; Check that scratch buffer is empty since contract instantiation failed. + (call $assert + (i32.eq (call $ext_scratch_size) (i32.const 0)) + ) + + ;; Check that balance has not changed. + (call $assert + (i64.eq (get_local $balance) (call $current_balance (get_local $sp))) + ) + + ;; Deploy the contract successfully. + (set_local $exit_code + (call $ext_instantiate + (i32.const 24) ;; 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 8) ;; Pointer to input data buffer address + (i32.const 8) ;; Length of input data buffer + ) + ) + + ;; Check for success exit status. + (call $assert + (i32.eq (get_local $exit_code) (i32.const 0x00)) + ) + + ;; Check that scratch buffer contains the address of the new contract. + (call $assert + (i32.eq (call $ext_scratch_size) (i32.const 8)) + ) + + ;; Copy contract address from scratch buffer into this contract's memory. + (call $ext_scratch_read + (i32.const 16) ;; 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. + ) + + ;; Check that balance has been deducted. + (set_local $balance + (i64.sub (get_local $balance) (i64.load (i32.const 0))) + ) + (call $assert + (i64.eq (get_local $balance) (call $current_balance (get_local $sp))) + ) + + ;; Call the new contract and expect it to return failing exit code. + (set_local $exit_code + (call $ext_call + (i32.const 16) ;; Pointer to "callee" address. + (i32.const 8) ;; Length of "callee" 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 9) ;; Pointer to input data buffer address + (i32.const 7) ;; Length of input data buffer + ) + ) + + ;; Check non-zero exit status. + (call $assert + (i32.eq (get_local $exit_code) (i32.const 0x11)) + ) + + ;; Check that scratch buffer contains the expected return data. + (call $assert + (i32.eq (call $ext_scratch_size) (i32.const 3)) + ) + (i32.store + (i32.sub (get_local $sp) (i32.const 4)) + (i32.const 0) + ) + (call $ext_scratch_read + (i32.sub (get_local $sp) (i32.const 4)) + (i32.const 0) + (i32.const 3) + ) + (call $assert + (i32.eq + (i32.load (i32.sub (get_local $sp) (i32.const 4))) + (i32.const 0x00776655) + ) + ) + + ;; Check that balance has not changed. + (call $assert + (i64.eq (get_local $balance) (call $current_balance (get_local $sp))) + ) + + ;; Fail to call the contract due to insufficient gas. + (set_local $exit_code + (call $ext_call + (i32.const 16) ;; Pointer to "callee" address. + (i32.const 8) ;; Length of "callee" address. + (i64.const 100) ;; How much gas to devote for the execution. + (i32.const 0) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer. + (i32.const 8) ;; Pointer to input data buffer address + (i32.const 8) ;; Length of input data buffer + ) + ) + + ;; Check for special trap exit status. + (call $assert + (i32.eq (get_local $exit_code) (i32.const 0x0100)) + ) + + ;; Check that scratch buffer is empty since call trapped. + (call $assert + (i32.eq (call $ext_scratch_size) (i32.const 0)) + ) + + ;; Check that balance has not changed. + (call $assert + (i64.eq (get_local $balance) (call $current_balance (get_local $sp))) + ) + + ;; Call the contract successfully. + (set_local $exit_code + (call $ext_call + (i32.const 16) ;; Pointer to "callee" address. + (i32.const 8) ;; Length of "callee" 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 8) ;; Pointer to input data buffer address + (i32.const 8) ;; Length of input data buffer + ) + ) + + ;; Check for success exit status. + (call $assert + (i32.eq (get_local $exit_code) (i32.const 0x00)) + ) + + ;; Check that scratch buffer contains the expected return data. + (call $assert + (i32.eq (call $ext_scratch_size) (i32.const 4)) + ) + (i32.store + (i32.sub (get_local $sp) (i32.const 4)) + (i32.const 0) + ) + (call $ext_scratch_read + (i32.sub (get_local $sp) (i32.const 4)) + (i32.const 0) + (i32.const 4) + ) + (call $assert + (i32.eq + (i32.load (i32.sub (get_local $sp) (i32.const 4))) + (i32.const 0x77665544) + ) + ) + + ;; Check that balance has been deducted. + (set_local $balance + (i64.sub (get_local $balance) (i64.load (i32.const 0))) + ) + (call $assert + (i64.eq (get_local $balance) (call $current_balance (get_local $sp))) + ) + ) + + (data (i32.const 0) "\00\80") ;; The value to transfer on instantiation and calls. + ;; Chosen to be greater than existential deposit. + (data (i32.const 8) "\00\11\22\33\44\55\66\77") ;; The input data to instantiations and calls. +) +"#; + +#[test] +fn deploy_and_call_other_contract() { + let (callee_wasm, callee_code_hash) = compile_module::(CODE_RETURN_WITH_DATA).unwrap(); + let (caller_wasm, caller_code_hash) = compile_module::(CODE_CALLER_CONTRACT).unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + // 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)); + + assert_ok!(Contract::instantiate( + Origin::signed(ALICE), + 100_000, + 100_000, + caller_code_hash.into(), + vec![], + )); + + // Call BOB contract, which attempts to instantiate and call the callee contract and + // makes various assertions on the results from those calls. + assert_ok!(Contract::call( + Origin::signed(ALICE), + BOB, + 0, + 200_000, + callee_code_hash.as_ref().to_vec(), + )); + }); +} + +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::(CODE_SELF_DESTRUCT).unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + 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::instantiate( + Origin::signed(ALICE), + 100_000, + 100_000, + code_hash.into(), + vec![], + )); + + // Check that the BOB contract has been instantiated. + assert_matches!( + ContractInfoOf::::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::::get(BOB).is_none()); + }); +} + +#[test] +fn cannot_self_destruct_while_live() { + let (wasm, code_hash) = compile_module::(CODE_SELF_DESTRUCT).unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + 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::instantiate( + Origin::signed(ALICE), + 100_000, + 100_000, + code_hash.into(), + vec![], + )); + + // Check that the BOB contract has been instantiated. + assert_matches!( + ContractInfoOf::::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::::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_instantiate" (func $ext_instantiate (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_instantiate + (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::(CODE_SELF_DESTRUCT).unwrap(); + let (caller_wasm, caller_code_hash) = compile_module::(CODE_DESTROY_AND_TRANSFER).unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + // 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::instantiate( + 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::::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::::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::(CODE_SELF_DESTRUCTING_CONSTRUCTOR).unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + 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::instantiate( + Origin::signed(ALICE), + 100_000, + 100_000, + code_hash.into(), + vec![], + ), + "insufficient remaining balance" + ); + }); +} + +#[test] +fn check_block_gas_limit_works() { + ExtBuilder::default().block_gas_limit(50).build().execute_with(|| { + let info = DispatchInfo { weight: 100, class: DispatchClass::Normal, pays_fee: true }; + let check = CheckBlockGasLimit::(Default::default()); + let call: Call = crate::Call::put_code(1000, vec![]).into(); + + assert_eq!( + check.validate(&0, &call, info, 0), InvalidTransaction::ExhaustsResources.into(), + ); + + let call: Call = crate::Call::update_schedule(Default::default()).into(); + assert_eq!(check.validate(&0, &call, info, 0), Ok(Default::default())); + }); +} + +const CODE_GET_RUNTIME_STORAGE: &str = r#" +(module + (import "env" "ext_get_runtime_storage" + (func $ext_get_runtime_storage (param i32 i32) (result i32)) + ) + (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_scratch_write" (func $ext_scratch_write (param i32 i32))) + (import "env" "memory" (memory 1 1)) + + (func (export "deploy")) + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func $call (export "call") + ;; Load runtime storage for the first key and assert that it exists. + (call $assert + (i32.eq + (call $ext_get_runtime_storage + (i32.const 16) + (i32.const 4) + ) + (i32.const 0) + ) + ) + + ;; assert $ext_scratch_size == 4 + (call $assert + (i32.eq + (call $ext_scratch_size) + (i32.const 4) + ) + ) + + ;; copy contents of the scratch buffer into the contract's memory. + (call $ext_scratch_read + (i32.const 4) ;; Pointer in memory to the place where to copy. + (i32.const 0) ;; Offset from the start of the scratch buffer. + (i32.const 4) ;; Count of bytes to copy. + ) + + ;; assert that contents of the buffer is equal to the i32 value of 0x14144020. + (call $assert + (i32.eq + (i32.load + (i32.const 4) + ) + (i32.const 0x14144020) + ) + ) + + ;; Load the second key and assert that it doesn't exist. + (call $assert + (i32.eq + (call $ext_get_runtime_storage + (i32.const 20) + (i32.const 4) + ) + (i32.const 1) + ) + ) + ) + + ;; The first key, 4 bytes long. + (data (i32.const 16) "\01\02\03\04") + ;; The second key, 4 bytes long. + (data (i32.const 20) "\02\03\04\05") +) +"#; + +#[test] +fn get_runtime_storage() { + let (wasm, code_hash) = compile_module::(CODE_GET_RUNTIME_STORAGE).unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + Balances::deposit_creating(&ALICE, 1_000_000); + + support::storage::unhashed::put_raw( + &[1, 2, 3, 4], + 0x14144020u32.to_le_bytes().to_vec().as_ref() + ); + + assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); + assert_ok!(Contract::instantiate( + Origin::signed(ALICE), + 100, + 100_000, + code_hash.into(), + vec![], + )); + assert_ok!(Contract::call( + Origin::signed(ALICE), + BOB, + 0, + 100_000, + vec![], + )); + }); +} diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index cb69cd689b265..20a84324c437b 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -304,19 +304,20 @@ mod tests { fn note_dispatch_call(&mut self, call: Call) { self.dispatches.push(DispatchEntry(call)); } - fn note_restore_to( + fn restore_to( &mut self, dest: u64, code_hash: H256, rent_allowance: u64, delta: Vec, - ) { + ) -> Result<(), &'static str> { self.restores.push(RestoreEntry { dest, code_hash, rent_allowance, delta, }); + Ok(()) } fn caller(&self) -> &u64 { &42 @@ -427,14 +428,14 @@ mod tests { fn note_dispatch_call(&mut self, call: Call) { (**self).note_dispatch_call(call) } - fn note_restore_to( + fn restore_to( &mut self, dest: u64, code_hash: H256, rent_allowance: u64, delta: Vec, - ) { - (**self).note_restore_to( + ) -> Result<(), &'static str> { + (**self).restore_to( dest, code_hash, rent_allowance, diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index f87f5d1ef53cc..147f524fde4b6 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -799,17 +799,18 @@ define_env!(Env, , Ok(()) }, - // Record a request to restore the caller contract to the specified contract. + // Try to restore the given destination contract sacrificing the caller. // - // At the finalization stage, i.e. when all changes from the extrinsic that invoked this - // contract are committed, this function will compute a tombstone hash from the caller's - // storage and the given code hash and if the hash matches the hash found in the tombstone at - // the specified address - kill the caller contract and restore the destination contract and set - // the specified `rent_allowance`. All caller's funds are transferred to the destination. + // This function will compute a tombstone hash from the caller's storage and the given code hash + // and if the hash matches the hash found in the tombstone at the specified address - kill + // the caller contract and restore the destination contract and set the specified `rent_allowance`. + // All caller's funds are transfered to the destination. // - // This function doesn't perform restoration right away but defers it to the end of the - // transaction. If there is no tombstone in the destination address or if the hashes don't match - // then restoration is cancelled and no changes are made. + // If there is no tombstone at the destination address or if the hashes don't match + // then restoration is cancelled and no changes are made. In this case this function + // will return non-zero code. + // + // Otherwise, If the restoration was performed correctly, 0 is returned. // // `dest_ptr`, `dest_len` - the pointer and the length of a buffer that encodes `T::AccountId` // with the address of the to be restored contract. @@ -829,7 +830,7 @@ define_env!(Env, , rent_allowance_len: u32, delta_ptr: u32, delta_count: u32 - ) => { + ) -> u32 => { let dest: <::T as frame_system::Trait>::AccountId = read_sandbox_memory_as(ctx, dest_ptr, dest_len)?; let code_hash: CodeHash<::T> = @@ -857,14 +858,15 @@ define_env!(Env, , delta }; - ctx.ext.note_restore_to( + match ctx.ext.restore_to( dest, code_hash, rent_allowance, delta, - ); - - Ok(()) + ) { + Ok(_) => Ok(0), + Err(_) => Ok(1), + } }, // Returns the size of the scratch buffer. From 86c8b32232b7e83171e3cf632b358f92e9f400e9 Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Thu, 11 Jun 2020 12:22:52 +0200 Subject: [PATCH 09/33] It almost compiles. --- frame/contracts/src/exec.rs | 30 +- frame/contracts/src/lib.rs | 5 +- frame/contracts/src/rent.rs | 22 +- frame/contracts/src/storage.rs | 22 +- frame/contracts/src/tests.rs | 4 +- frame/contracts/src/tests/mod.rs | 2475 ------------------------------ 6 files changed, 45 insertions(+), 2513 deletions(-) delete mode 100644 frame/contracts/src/tests/mod.rs diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 35bc9762dd42e..ba650175f00d7 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -16,7 +16,6 @@ use super::{CodeHash, Config, ContractAddressFor, Event, RawEvent, Trait, TrieId, BalanceOf, ContractInfo, TrieIdGenerator}; -use crate::account_db::{AccountDb, DirectAccountDb, OverlayAccountDb}; use crate::gas::{Gas, GasMeter, Token}; use crate::rent; use crate::storage; @@ -316,7 +315,7 @@ where { ExecutionContext { caller: Some(self), - self_trie_id: Some(trie_id), + self_trie_id: trie_id, self_account: dest, depth: self.depth + 1, deferred: Vec::new(), @@ -422,11 +421,11 @@ where // Destroy contract if insufficient remaining balance. if T::Currency::free_balance(&dest) < nested.config.existential_deposit { - let parent = nested.parent + let caller = nested.caller .expect("a nested execution context must have a parent; qed"); - if parent.is_live(&dest) { + if caller.is_live(&dest) { return Err(ExecError { - reason: "contract cannot be destroyed during recursive execution", + reason: "contract cannot be destroyed during recursive execution".into(), buffer: output.data, }); } @@ -545,7 +544,7 @@ where gas_meter: &mut GasMeter, ) -> Result<(), DispatchError> { let self_id = self.self_account.clone(); - let value = self.overlay.get_balance(&self_id); + let value = T::Currency::free_balance(&self_id); if let Some(caller) = self.caller { if caller.is_live(&self_id) { return Err(DispatchError::Other( @@ -561,7 +560,7 @@ where value, self, )?; - self.overlay.destroy_contract(&self_id); + storage::destroy_contract::(&self_id, self.self_trie_id.as_ref().expect("")); // TODO: Proof Ok(()) } @@ -841,10 +840,10 @@ where } fn deposit_event(&mut self, topics: Vec, data: Vec) { - self.ctx.deferred.push(DeferredAction::DepositEvent { + deposit_event::( topics, - event: RawEvent::ContractExecution(self.ctx.self_account.clone(), data), - }); + RawEvent::ContractExecution(self.ctx.self_account.clone(), data) + ); } fn set_rent_allowance(&mut self, rent_allowance: BalanceOf) { @@ -878,7 +877,7 @@ fn deposit_event( topics: Vec, event: Event, ) { - >::deposit_event_indexed( + >::deposit_event_indexed( &*topics, ::Event::from(event).into(), ) @@ -917,7 +916,7 @@ mod tests { const CHARLIE: u64 = 3; fn events() -> Vec> { - >::events() + >::events() .into_iter() .filter_map(|meta| match meta.event { MetaEvent::contract(contract_event) => Some(contract_event), @@ -1369,7 +1368,7 @@ mod tests { let cfg = Config::preload(); let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); - ctx.overlay.set_balance(&ALICE, 100); + set_balance(&ALICE, 100); let result = ctx.instantiate( 1, @@ -1712,7 +1711,7 @@ mod tests { .execute_with(|| { let cfg = Config::preload(); let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); - ctx.overlay.set_balance(&ALICE, 1000); + set_balance(&ALICE, 1000); assert_matches!( ctx.instantiate( @@ -1748,8 +1747,7 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let cfg = Config::preload(); let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); - - ctx.overlay.set_balance(&ALICE, 100); + set_balance(&ALICE, 100); let result = ctx.instantiate( 1, diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index bc71c2f55e180..8230e978446e0 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -681,8 +681,9 @@ impl Module { decl_event! { pub enum Event where - ::AccountId, - ::Hash + Balance = BalanceOf, + ::AccountId, + ::Hash { /// Contract deployed by address at the specified address. Instantiated(AccountId, AccountId), diff --git a/frame/contracts/src/rent.rs b/frame/contracts/src/rent.rs index fc9d53062f83e..329e59e860dd8 100644 --- a/frame/contracts/src/rent.rs +++ b/frame/contracts/src/rent.rs @@ -18,7 +18,7 @@ use crate::{ AliveContractInfo, BalanceOf, ContractInfo, ContractInfoOf, Module, RawEvent, - TombstoneContractInfo, Trait, + TombstoneContractInfo, Trait, CodeHash, }; use frame_support::storage::child; use frame_support::traits::{Currency, ExistenceRequirement, Get, OnUnbalanced, WithdrawReason}; @@ -413,13 +413,17 @@ pub fn restore_to( dest: T::AccountId, code_hash: CodeHash, rent_allowance: BalanceOf, - delta: Vec, + delta: Vec, ) -> Result<(), &'static str> { + use sp_core::blake2_256; + let mut origin_contract = >::get(&origin) .and_then(|c| c.get_alive()) .ok_or("Cannot restore from inexisting or tombstone contract")?; - let current_block = >::block_number(); + let child_trie_info = origin_contract.child_trie_info(); + + let current_block = >::block_number(); if origin_contract.last_write == Some(current_block) { return Err("Origin TrieId written in the current block"); @@ -437,8 +441,8 @@ pub fn restore_to( let key_values_taken = delta.iter() .filter_map(|key| { - child::get_raw(&origin_contract.trie_id, &blake2_256(key)).map(|value| { - child::kill(&origin_contract.trie_id, &blake2_256(key)); + child::get_raw(&child_trie_info, &blake2_256(key)).map(|value| { + child::kill(&child_trie_info, &blake2_256(key)); (key, value) }) }) @@ -447,13 +451,13 @@ pub fn restore_to( let tombstone = >::new( // This operation is cheap enough because last_write (delta not included) // is not this block as it has been checked earlier. - &sp_io::storage::child_root(&origin_contract.trie_id)[..], + &child::root(&child_trie_info)[..], code_hash, ); if tombstone != dest_tombstone { for (key, value) in key_values_taken { - child::put_raw(&origin_contract.trie_id, &blake2_256(key), &value); + child::put_raw(&child_trie_info, &blake2_256(key), &value); } return Err("Tombstones don't match"); @@ -464,9 +468,11 @@ pub fn restore_to( .sum::(); >::remove(&origin); - >::insert(&dest, ContractInfo::Alive(RawAliveContractInfo { + >::insert(&dest, ContractInfo::Alive(AliveContractInfo:: { trie_id: origin_contract.trie_id, storage_size: origin_contract.storage_size, + empty_pair_count: origin_contract.empty_pair_count, + total_pair_count: origin_contract.total_pair_count, code_hash, rent_allowance, deduct_block: current_block, diff --git a/frame/contracts/src/storage.rs b/frame/contracts/src/storage.rs index 42effd0375f9a..493146cba49b0 100644 --- a/frame/contracts/src/storage.rs +++ b/frame/contracts/src/storage.rs @@ -21,10 +21,10 @@ use crate::{ use sp_std::prelude::*; use sp_io::hashing::blake2_256; use sp_runtime::traits::Bounded; -use support::{storage::child, traits::Get, StorageMap}; +use frame_support::{storage::child, traits::Get, StorageMap}; pub fn read_contract_storage(trie_id: &TrieId, key: &StorageKey) -> Option> { - child::get_raw(trie_id, &blake2_256(key)) + child::get_raw(&crate::child_trie_info(&trie_id), &blake2_256(key)) } pub fn write_contract_storage( @@ -35,19 +35,21 @@ pub fn write_contract_storage( ) { let hashed_key = blake2_256(key); + let child_trie_info = &crate::child_trie_info(&trie_id); + // We need to accurately track the size of the storage of the contract. // // For that we query the previous value and get its size. - let existing_size = child::get_raw(trie_id, &hashed_key) + let existing_size = child::get_raw(&child_trie_info, &hashed_key) .map(|v| v.len()) .unwrap_or(0); let new_size = match value { Some(v) => { - child::put_raw(trie_id, &hashed_key, &v); + child::put_raw(&child_trie_info, &hashed_key, &v); v.len() } None => { - child::kill(trie_id, &hashed_key); + child::kill(&child_trie_info, &hashed_key); 0 } }; @@ -57,7 +59,7 @@ pub fn write_contract_storage( Some(ContractInfo::Alive(ref mut alive_info)) => { alive_info.storage_size -= existing_size as u32; alive_info.storage_size += new_size as u32; - alive_info.last_write = Some(>::block_number()); + alive_info.last_write = Some(>::block_number()); } _ => panic!(), // TODO: Justify this. } @@ -99,10 +101,12 @@ pub fn place_contract( *maybe_contract_info = Some( AliveContractInfo:: { code_hash: ch, - storage_size: T::StorageSizeOffset::get(), + storage_size: 0, trie_id, - deduct_block: >::block_number(), + deduct_block: >::block_number(), rent_allowance: >::max_value(), + empty_pair_count: 0, + total_pair_count: 0, last_write: None, } .into(), @@ -114,5 +118,5 @@ pub fn place_contract( pub fn destroy_contract(address: &AccountIdOf, trie_id: &TrieId) { >::remove(address); - child::kill_storage(trie_id); + child::kill_storage(&crate::child_trie_info(&trie_id)); } diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index a98fdf2d25804..e16b681849d01 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -16,9 +16,7 @@ use crate::{ BalanceOf, ContractAddressFor, ContractInfo, ContractInfoOf, GenesisConfig, Module, - RawAliveContractInfo, RawEvent, Trait, TrieId, Schedule, TrieIdGenerator, - account_db::{AccountDb, DirectAccountDb, OverlayAccountDb}, - gas::Gas, + RawAliveContractInfo, RawEvent, Trait, TrieId, Schedule, TrieIdGenerator, gas::Gas, }; use assert_matches::assert_matches; use hex_literal::*; diff --git a/frame/contracts/src/tests/mod.rs b/frame/contracts/src/tests/mod.rs deleted file mode 100644 index a776db7853b2a..0000000000000 --- a/frame/contracts/src/tests/mod.rs +++ /dev/null @@ -1,2475 +0,0 @@ -// Copyright 2018-2019 Parity Technologies (UK) Ltd. -// This file is part of Substrate. - -// Substrate is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Substrate is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Substrate. If not, see . - -// TODO: #1417 Add more integration tests -// also remove the #![allow(unused)] below. - -#![allow(unused)] - -use crate::{ - BalanceOf, ComputeDispatchFee, ContractAddressFor, ContractInfo, ContractInfoOf, GenesisConfig, - Module, RawAliveContractInfo, RawEvent, Trait, TrieId, TrieIdFromParentCounter, Schedule, - TrieIdGenerator, CheckBlockGasLimit, CodeHash, storage, -}; -use assert_matches::assert_matches; -use hex_literal::*; -use codec::{Decode, Encode, KeyedVec}; -use sp_runtime::{ - Perbill, BuildStorage, transaction_validity::{InvalidTransaction, ValidTransaction}, - traits::{BlakeTwo256, Hash, IdentityLookup, SignedExtension}, - testing::{Digest, DigestItem, Header, UintAuthorityId, H256}, -}; -use support::{ - assert_ok, assert_err, impl_outer_dispatch, impl_outer_event, impl_outer_origin, parameter_types, - storage::child, StorageMap, StorageValue, traits::{Currency, Get}, - weights::{DispatchInfo, DispatchClass, Weight}, -}; -use std::{cell::RefCell, sync::atomic::{AtomicUsize, Ordering}}; -use primitives::storage::well_known_keys; -use system::{self, EventRecord, Phase}; - -mod contract { - // Re-export contents of the root. This basically - // needs to give a name for the current crate. - // This hack is required for `impl_outer_event!`. - pub use super::super::*; - use support::impl_outer_event; -} -impl_outer_event! { - pub enum MetaEvent for Test { - balances, contract, - } -} -impl_outer_origin! { - pub enum Origin for Test { } -} -impl_outer_dispatch! { - pub enum Call for Test where origin: Origin { - balances::Balances, - contract::Contract, - } -} - -thread_local! { - static EXISTENTIAL_DEPOSIT: RefCell = RefCell::new(0); - static TRANSFER_FEE: RefCell = RefCell::new(0); - static INSTANTIATION_FEE: RefCell = RefCell::new(0); - static BLOCK_GAS_LIMIT: RefCell = RefCell::new(0); -} - -pub struct ExistentialDeposit; -impl Get for ExistentialDeposit { - fn get() -> u64 { EXISTENTIAL_DEPOSIT.with(|v| *v.borrow()) } -} - -pub struct TransferFee; -impl Get for TransferFee { - fn get() -> u64 { TRANSFER_FEE.with(|v| *v.borrow()) } -} - -pub struct CreationFee; -impl Get for CreationFee { - fn get() -> u64 { INSTANTIATION_FEE.with(|v| *v.borrow()) } -} - -pub struct BlockGasLimit; -impl Get for BlockGasLimit { - fn get() -> u64 { BLOCK_GAS_LIMIT.with(|v| *v.borrow()) } -} - -#[derive(Clone, Eq, PartialEq, Debug)] -pub struct Test; -parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const MaximumBlockWeight: Weight = 1024; - pub const MaximumBlockLength: u32 = 2 * 1024; - pub const AvailableBlockRatio: Perbill = Perbill::one(); -} -impl system::Trait for Test { - type Origin = Origin; - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Call = (); - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; - type Event = MetaEvent; - type BlockHashCount = BlockHashCount; - type MaximumBlockWeight = MaximumBlockWeight; - type AvailableBlockRatio = AvailableBlockRatio; - type MaximumBlockLength = MaximumBlockLength; - type Version = (); -} -impl balances::Trait for Test { - type Balance = u64; - type OnFreeBalanceZero = Contract; - type OnNewAccount = (); - type Event = MetaEvent; - type DustRemoval = (); - type TransferPayment = (); - type ExistentialDeposit = ExistentialDeposit; - type TransferFee = TransferFee; - type CreationFee = CreationFee; -} -parameter_types! { - pub const MinimumPeriod: u64 = 1; -} -impl timestamp::Trait for Test { - type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; -} -parameter_types! { - pub const SignedClaimHandicap: u64 = 2; - pub const TombstoneDeposit: u64 = 16; - pub const StorageSizeOffset: u32 = 8; - pub const RentByteFee: u64 = 4; - pub const RentDepositOffset: u64 = 10_000; - pub const SurchargeReward: u64 = 150; - pub const TransactionBaseFee: u64 = 2; - pub const TransactionByteFee: u64 = 6; - pub const ContractFee: u64 = 21; - pub const CallBaseFee: u64 = 135; - pub const InstantiateBaseFee: u64 = 175; - pub const MaxDepth: u32 = 100; - pub const MaxValueSize: u32 = 16_384; -} -impl Trait for Test { - type Currency = Balances; - type Time = Timestamp; - type Randomness = Randomness; - type Call = Call; - type DetermineContractAddress = DummyContractAddressFor; - type Event = MetaEvent; - type ComputeDispatchFee = DummyComputeDispatchFee; - type TrieIdGenerator = DummyTrieIdGenerator; - type GasPayment = (); - type RentPayment = (); - type SignedClaimHandicap = SignedClaimHandicap; - type TombstoneDeposit = TombstoneDeposit; - type StorageSizeOffset = StorageSizeOffset; - type RentByteFee = RentByteFee; - type RentDepositOffset = RentDepositOffset; - type SurchargeReward = SurchargeReward; - type TransferFee = TransferFee; - type CreationFee = CreationFee; - type TransactionBaseFee = TransactionBaseFee; - type TransactionByteFee = TransactionByteFee; - type ContractFee = ContractFee; - type CallBaseFee = CallBaseFee; - type InstantiateBaseFee = InstantiateBaseFee; - type MaxDepth = MaxDepth; - type MaxValueSize = MaxValueSize; - type BlockGasLimit = BlockGasLimit; -} - -type Balances = balances::Module; -type Timestamp = timestamp::Module; -type Contract = Module; -type System = system::Module; -type Randomness = randomness_collective_flip::Module; - -pub struct DummyContractAddressFor; -impl ContractAddressFor for DummyContractAddressFor { - fn contract_address_for(_code_hash: &H256, _data: &[u8], origin: &u64) -> u64 { - *origin + 1 - } -} - -pub struct DummyTrieIdGenerator; -impl TrieIdGenerator for DummyTrieIdGenerator { - fn trie_id(account_id: &u64) -> TrieId { - use primitives::storage::well_known_keys; - - let new_seed = super::AccountCounter::mutate(|v| { - *v = v.wrapping_add(1); - *v - }); - - // TODO: see https://github.com/paritytech/substrate/issues/2325 - let mut res = vec![]; - res.extend_from_slice(well_known_keys::CHILD_STORAGE_KEY_PREFIX); - res.extend_from_slice(b"default:"); - res.extend_from_slice(&new_seed.to_le_bytes()); - res.extend_from_slice(&account_id.to_le_bytes()); - res - } -} - -pub struct DummyComputeDispatchFee; -impl ComputeDispatchFee for DummyComputeDispatchFee { - fn compute_dispatch_fee(call: &Call) -> u64 { - 69 - } -} - -const ALICE: u64 = 1; -const BOB: u64 = 2; -const CHARLIE: u64 = 3; -const DJANGO: u64 = 4; - -pub struct ExtBuilder { - existential_deposit: u64, - gas_price: u64, - block_gas_limit: u64, - transfer_fee: u64, - instantiation_fee: u64, -} -impl Default for ExtBuilder { - fn default() -> Self { - Self { - existential_deposit: 0, - gas_price: 2, - block_gas_limit: 100_000_000, - transfer_fee: 0, - instantiation_fee: 0, - } - } -} -impl ExtBuilder { - pub fn existential_deposit(mut self, existential_deposit: u64) -> Self { - self.existential_deposit = existential_deposit; - self - } - pub fn gas_price(mut self, gas_price: u64) -> Self { - self.gas_price = gas_price; - self - } - pub fn block_gas_limit(mut self, block_gas_limit: u64) -> Self { - self.block_gas_limit = block_gas_limit; - self - } - pub fn transfer_fee(mut self, transfer_fee: u64) -> Self { - self.transfer_fee = transfer_fee; - self - } - pub fn instantiation_fee(mut self, instantiation_fee: u64) -> Self { - self.instantiation_fee = instantiation_fee; - self - } - pub fn set_associated_consts(&self) { - EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); - TRANSFER_FEE.with(|v| *v.borrow_mut() = self.transfer_fee); - INSTANTIATION_FEE.with(|v| *v.borrow_mut() = self.instantiation_fee); - BLOCK_GAS_LIMIT.with(|v| *v.borrow_mut() = self.block_gas_limit); - } - pub fn build(self) -> sp_io::TestExternalities { - self.set_associated_consts(); - let mut t = system::GenesisConfig::default().build_storage::().unwrap(); - balances::GenesisConfig:: { - balances: vec![], - vesting: vec![], - }.assimilate_storage(&mut t).unwrap(); - GenesisConfig:: { - current_schedule: Schedule { - enable_println: true, - ..Default::default() - }, - gas_price: self.gas_price, - }.assimilate_storage(&mut t).unwrap(); - sp_io::TestExternalities::new(t) - } -} - -/// Generate Wasm binary and code hash from wabt source. -fn compile_module(wabt_module: &str) - -> Result<(Vec, ::Output), wabt::Error> - where T: system::Trait -{ - let wasm = wabt::wat2wasm(wabt_module)?; - let code_hash = T::Hashing::hash(&wasm); - Ok((wasm, code_hash)) -} - -pub fn set_balance(who: &u64, amount: u64) { - Balances::deposit_creating(who, amount); -} - -pub fn get_balance(who: &u64) -> u64 { - Balances::free_balance(who) -} - -pub fn place_contract(address: &u64, code_hash: CodeHash) { - use crate::TrieIdGenerator; - let trie_id = ::TrieIdGenerator::trie_id(address); - storage::place_contract::(&address, trie_id, code_hash).unwrap() -} - -pub fn get_storage(who: &u64, key: [u8; 32]) -> Option> { - use crate::ContractInfoOf; - let trie_id = >::get(who) - .expect("no contract info under the given address") - .get_alive() - .expect("the contract exists but not alive") - .trie_id; - storage::read_contract_storage(&trie_id, &key) -} - -pub fn set_storage(who: &u64, key: [u8; 32], value: Option>) { - use crate::ContractInfoOf; - let trie_id = >::get(who) - .expect("no contract info under the given address") - .get_alive() - .expect("the contract exists but not alive") - .trie_id; - storage::write_contract_storage::(who, &trie_id, &key, value); -} - -// Perform a simple transfer to a non-existent account supplying way more gas than needed. -// Then we check that the all unused gas is refunded. -#[test] -fn refunds_unused_gas() { - ExtBuilder::default().gas_price(2).build().execute_with(|| { - Balances::deposit_creating(&ALICE, 100_000_000); - - assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, Vec::new())); - - // 2 * 135 - gas price multiplied by the call base fee. - assert_eq!(Balances::free_balance(&ALICE), 100_000_000 - (2 * 135)); - }); -} - -#[test] -fn account_removal_removes_storage() { - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let key1 = [1; 32]; - let key2 = [2; 32]; - - // Set up two accounts with free balance above the existential threshold. - { - Balances::deposit_creating(&1, 110); - place_contract(&1, H256::repeat_byte(1)); - set_storage(&1, key1.clone(), Some(b"1".to_vec())); - set_storage(&1, key2.clone(), Some(b"2".to_vec())); - - Balances::deposit_creating(&2, 110); - place_contract(&2, H256::repeat_byte(2)); - set_storage(&2, key1.clone(), Some(b"3".to_vec())); - set_storage(&2, key2.clone(), Some(b"4".to_vec())); - } - - // Transfer funds from account 1 of such amount that after this transfer - // the balance of account 1 will be below the existential threshold. - // - // This should trigger OnFreeBalanceZero callback and therefore kill the child storage and - // remove the contract info. - assert_ok!(Balances::transfer(Origin::signed(1), 2, 20)); - - // Verify that the account 1 is removed, while the account 2 is in place. - { - assert!(>::get(&1).is_none()); - - assert!(>::get(&2).is_some()); - assert_eq!( - get_storage(&2, key1), - Some(b"3".to_vec()) - ); - assert_eq!( - get_storage(&2, key2), - Some(b"4".to_vec()) - ); - } - }); -} - -const CODE_RETURN_FROM_START_FN: &str = r#" -(module - (import "env" "ext_return" (func $ext_return (param i32 i32))) - (import "env" "ext_deposit_event" (func $ext_deposit_event (param i32 i32 i32 i32))) - (import "env" "memory" (memory 1 1)) - - (start $start) - (func $start - (call $ext_deposit_event - (i32.const 0) ;; The topics buffer - (i32.const 0) ;; The topics buffer's length - (i32.const 8) ;; The data buffer - (i32.const 4) ;; The data buffer's length - ) - (call $ext_return - (i32.const 8) - (i32.const 4) - ) - (unreachable) - ) - - (func (export "call") - (unreachable) - ) - (func (export "deploy")) - - (data (i32.const 8) "\01\02\03\04") -) -"#; - -#[test] -fn instantiate_and_call_and_deposit_event() { - let (wasm, code_hash) = compile_module::(CODE_RETURN_FROM_START_FN).unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - Balances::deposit_creating(&ALICE, 1_000_000); - - assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); - - // Check at the end to get hash on error easily - let creation = Contract::instantiate( - Origin::signed(ALICE), - 100, - 100_000, - code_hash.into(), - vec![], - ); - - assert_eq!(System::events(), vec![ - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::balances(balances::RawEvent::NewAccount(1, 1_000_000)), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::contract(RawEvent::CodeStored(code_hash.into())), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::balances( - balances::RawEvent::NewAccount(BOB, 100) - ), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::balances( - balances::RawEvent::Transfer(ALICE, BOB, 100, 0) - ), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::contract(RawEvent::Contract(BOB, vec![1, 2, 3, 4])), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::contract(RawEvent::Instantiated(ALICE, BOB)), - topics: vec![], - }, - ]); - - assert_ok!(creation); - assert!(ContractInfoOf::::exists(BOB)); - }); -} - -const CODE_DISPATCH_CALL: &str = r#" -(module - (import "env" "ext_dispatch_call" (func $ext_dispatch_call (param i32 i32))) - (import "env" "memory" (memory 1 1)) - - (func (export "call") - (call $ext_dispatch_call - (i32.const 8) ;; Pointer to the start of encoded call buffer - (i32.const 11) ;; Length of the buffer - ) - ) - (func (export "deploy")) - - (data (i32.const 8) "\00\00\03\00\00\00\00\00\00\00\C8") -) -"#; - -#[test] -fn dispatch_call() { - // This test can fail due to the encoding changes. In case it becomes too annoying - // let's rewrite so as we use this module controlled call or we serialize it in runtime. - let encoded = Encode::encode(&Call::Balances(balances::Call::transfer(CHARLIE, 50))); - assert_eq!(&encoded[..], &hex!("00000300000000000000C8")[..]); - - let (wasm, code_hash) = compile_module::(CODE_DISPATCH_CALL).unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - Balances::deposit_creating(&ALICE, 1_000_000); - - assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); - - // Let's keep this assert even though it's redundant. If you ever need to update the - // wasm source this test will fail and will show you the actual hash. - assert_eq!(System::events(), vec![ - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::balances(balances::RawEvent::NewAccount(1, 1_000_000)), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::contract(RawEvent::CodeStored(code_hash.into())), - topics: vec![], - }, - ]); - - assert_ok!(Contract::instantiate( - Origin::signed(ALICE), - 100, - 100_000, - code_hash.into(), - vec![], - )); - - assert_ok!(Contract::call( - Origin::signed(ALICE), - BOB, // newly created account - 0, - 100_000, - vec![], - )); - - assert_eq!(System::events(), vec![ - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::balances(balances::RawEvent::NewAccount(1, 1_000_000)), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::contract(RawEvent::CodeStored(code_hash.into())), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::balances( - balances::RawEvent::NewAccount(BOB, 100) - ), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::balances( - balances::RawEvent::Transfer(ALICE, BOB, 100, 0) - ), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::contract(RawEvent::Instantiated(ALICE, BOB)), - topics: vec![], - }, - - // Dispatching the call. - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::balances( - balances::RawEvent::NewAccount(CHARLIE, 50) - ), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::balances( - balances::RawEvent::Transfer(BOB, CHARLIE, 50, 0) - ), - topics: vec![], - }, - - // Event emited as a result of dispatch. - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::contract(RawEvent::Dispatched(BOB, true)), - topics: vec![], - } - ]); - }); -} - -const CODE_DISPATCH_CALL_THEN_TRAP: &str = r#" -(module - (import "env" "ext_dispatch_call" (func $ext_dispatch_call (param i32 i32))) - (import "env" "memory" (memory 1 1)) - - (func (export "call") - (call $ext_dispatch_call - (i32.const 8) ;; Pointer to the start of encoded call buffer - (i32.const 11) ;; Length of the buffer - ) - (unreachable) ;; trap so that the top level transaction fails - ) - (func (export "deploy")) - - (data (i32.const 8) "\00\00\03\00\00\00\00\00\00\00\C8") -) -"#; - -#[test] -fn dispatch_call_not_dispatched_after_top_level_transaction_failure() { - // This test can fail due to the encoding changes. In case it becomes too annoying - // let's rewrite so as we use this module controlled call or we serialize it in runtime. - let encoded = Encode::encode(&Call::Balances(balances::Call::transfer(CHARLIE, 50))); - assert_eq!(&encoded[..], &hex!("00000300000000000000C8")[..]); - - let (wasm, code_hash) = compile_module::(CODE_DISPATCH_CALL_THEN_TRAP).unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - Balances::deposit_creating(&ALICE, 1_000_000); - - assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); - - // Let's keep this assert even though it's redundant. If you ever need to update the - // wasm source this test will fail and will show you the actual hash. - assert_eq!(System::events(), vec![ - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::balances(balances::RawEvent::NewAccount(1, 1_000_000)), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::contract(RawEvent::CodeStored(code_hash.into())), - topics: vec![], - }, - ]); - - assert_ok!(Contract::instantiate( - Origin::signed(ALICE), - 100, - 100_000, - code_hash.into(), - vec![], - )); - - // Call the newly instantiated contract. The contract is expected to dispatch a call - // and then trap. - assert_err!( - Contract::call( - Origin::signed(ALICE), - BOB, // newly created account - 0, - 100_000, - vec![], - ), - "during execution" - ); - assert_eq!(System::events(), vec![ - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::balances(balances::RawEvent::NewAccount(1, 1_000_000)), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::contract(RawEvent::CodeStored(code_hash.into())), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::balances( - balances::RawEvent::NewAccount(BOB, 100) - ), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::balances( - balances::RawEvent::Transfer(ALICE, BOB, 100, 0) - ), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::contract(RawEvent::Instantiated(ALICE, BOB)), - topics: vec![], - }, - // ABSENCE of events which would be caused by dispatched Balances::transfer call - ]); - }); -} - -const CODE_SET_RENT: &str = r#" -(module - (import "env" "ext_dispatch_call" (func $ext_dispatch_call (param i32 i32))) - (import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32 i32))) - (import "env" "ext_set_rent_allowance" (func $ext_set_rent_allowance (param i32 i32))) - (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" "memory" (memory 1 1)) - - ;; insert a value of 4 bytes into storage - (func $call_0 - (call $ext_set_storage - (i32.const 1) - (i32.const 1) - (i32.const 0) - (i32.const 4) - ) - ) - - ;; remove the value inserted by call_1 - (func $call_1 - (call $ext_set_storage - (i32.const 1) - (i32.const 0) - (i32.const 0) - (i32.const 0) - ) - ) - - ;; transfer 50 to ALICE - (func $call_2 - (call $ext_dispatch_call - (i32.const 68) - (i32.const 11) - ) - ) - - ;; do nothing - (func $call_else) - - (func $assert (param i32) - (block $ok - (br_if $ok - (get_local 0) - ) - (unreachable) - ) - ) - - ;; Dispatch the call according to input size - (func (export "call") - (local $input_size i32) - (set_local $input_size - (call $ext_scratch_size) - ) - (block $IF_ELSE - (block $IF_2 - (block $IF_1 - (block $IF_0 - (br_table $IF_0 $IF_1 $IF_2 $IF_ELSE - (get_local $input_size) - ) - (unreachable) - ) - (call $call_0) - return - ) - (call $call_1) - return - ) - (call $call_2) - return - ) - (call $call_else) - ) - - ;; Set into storage a 4 bytes value - ;; Set call set_rent_allowance with input - (func (export "deploy") - (local $input_size i32) - (set_local $input_size - (call $ext_scratch_size) - ) - (call $ext_set_storage - (i32.const 0) - (i32.const 1) - (i32.const 0) - (i32.const 4) - ) - (call $ext_scratch_read - (i32.const 0) - (i32.const 0) - (get_local $input_size) - ) - (call $ext_set_rent_allowance - (i32.const 0) - (get_local $input_size) - ) - ) - - ;; Encoding of 10 in balance - (data (i32.const 0) "\28") - - ;; Encoding of call transfer 50 to CHARLIE - (data (i32.const 68) "\00\00\03\00\00\00\00\00\00\00\C8") -) -"#; - -/// Input data for each call in set_rent code -mod call { - pub fn set_storage_4_byte() -> Vec { vec![] } - pub fn remove_storage_4_byte() -> Vec { vec![0] } - pub fn transfer() -> Vec { vec![0, 0] } - pub fn null() -> Vec { vec![0, 0, 0] } -} - -/// Test correspondence of set_rent code and its hash. -/// Also test that encoded extrinsic in code correspond to the correct transfer -#[test] -fn test_set_rent_code_and_hash() { - // This test can fail due to the encoding changes. In case it becomes too annoying - // let's rewrite so as we use this module controlled call or we serialize it in runtime. - let encoded = Encode::encode(&Call::Balances(balances::Call::transfer(CHARLIE, 50))); - assert_eq!(&encoded[..], &hex!("00000300000000000000C8")[..]); - - let (wasm, code_hash) = compile_module::(CODE_SET_RENT).unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - Balances::deposit_creating(&ALICE, 1_000_000); - assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); - - // If you ever need to update the wasm source this test will fail - // and will show you the actual hash. - assert_eq!(System::events(), vec![ - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::balances(balances::RawEvent::NewAccount(1, 1_000_000)), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::contract(RawEvent::CodeStored(code_hash.into())), - topics: vec![], - }, - ]); - }); -} - -#[test] -fn storage_size() { - let (wasm, code_hash) = compile_module::(CODE_SET_RENT).unwrap(); - - // Storage size - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - // Create - Balances::deposit_creating(&ALICE, 1_000_000); - assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); - assert_ok!(Contract::instantiate( - Origin::signed(ALICE), - 30_000, - 100_000, code_hash.into(), - ::Balance::from(1_000u32).encode() // rent allowance - )); - let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap(); - assert_eq!(bob_contract.storage_size, ::StorageSizeOffset::get() + 4); - - assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::set_storage_4_byte())); - let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap(); - assert_eq!(bob_contract.storage_size, ::StorageSizeOffset::get() + 4 + 4); - - assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::remove_storage_4_byte())); - let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap(); - assert_eq!(bob_contract.storage_size, ::StorageSizeOffset::get() + 4); - }); -} - -#[test] -fn deduct_blocks() { - let (wasm, code_hash) = compile_module::(CODE_SET_RENT).unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - // Create - Balances::deposit_creating(&ALICE, 1_000_000); - assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); - assert_ok!(Contract::instantiate( - Origin::signed(ALICE), - 30_000, - 100_000, code_hash.into(), - ::Balance::from(1_000u32).encode() // rent allowance - )); - - // Check creation - let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap(); - assert_eq!(bob_contract.rent_allowance, 1_000); - - // Advance 4 blocks - System::initialize(&5, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); - - // Trigger rent through call - assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null())); - - // Check result - let rent = (8 + 4 - 3) // storage size = size_offset + deploy_set_storage - deposit_offset - * 4 // rent byte price - * 4; // blocks to rent - let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap(); - assert_eq!(bob_contract.rent_allowance, 1_000 - rent); - assert_eq!(bob_contract.deduct_block, 5); - assert_eq!(Balances::free_balance(BOB), 30_000 - rent); - - // Advance 7 blocks more - System::initialize(&12, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); - - // Trigger rent through call - assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null())); - - // Check result - let rent_2 = (8 + 4 - 2) // storage size = size_offset + deploy_set_storage - deposit_offset - * 4 // rent byte price - * 7; // blocks to rent - let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap(); - assert_eq!(bob_contract.rent_allowance, 1_000 - rent - rent_2); - assert_eq!(bob_contract.deduct_block, 12); - assert_eq!(Balances::free_balance(BOB), 30_000 - rent - rent_2); - - // Second call on same block should have no effect on rent - assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null())); - - let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap(); - assert_eq!(bob_contract.rent_allowance, 1_000 - rent - rent_2); - assert_eq!(bob_contract.deduct_block, 12); - assert_eq!(Balances::free_balance(BOB), 30_000 - rent - rent_2); - }); -} - -#[test] -fn call_contract_removals() { - removals(|| { - // Call on already-removed account might fail, and this is fine. - Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()); - true - }); -} - -#[test] -fn inherent_claim_surcharge_contract_removals() { - removals(|| Contract::claim_surcharge(Origin::NONE, BOB, Some(ALICE)).is_ok()); -} - -#[test] -fn signed_claim_surcharge_contract_removals() { - removals(|| Contract::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok()); -} - -#[test] -fn claim_surcharge_malus() { - // Test surcharge malus for inherent - claim_surcharge(4, || Contract::claim_surcharge(Origin::NONE, BOB, Some(ALICE)).is_ok(), true); - claim_surcharge(3, || Contract::claim_surcharge(Origin::NONE, BOB, Some(ALICE)).is_ok(), true); - claim_surcharge(2, || Contract::claim_surcharge(Origin::NONE, BOB, Some(ALICE)).is_ok(), true); - claim_surcharge(1, || Contract::claim_surcharge(Origin::NONE, BOB, Some(ALICE)).is_ok(), false); - - // Test surcharge malus for signed - claim_surcharge(4, || Contract::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok(), true); - claim_surcharge(3, || Contract::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok(), false); - claim_surcharge(2, || Contract::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok(), false); - claim_surcharge(1, || Contract::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok(), false); -} - -/// Claim surcharge with the given trigger_call at the given blocks. -/// if removes is true then assert that the contract is a tombstonedead -fn claim_surcharge(blocks: u64, trigger_call: impl Fn() -> bool, removes: bool) { - let (wasm, code_hash) = compile_module::(CODE_SET_RENT).unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - // Create - Balances::deposit_creating(&ALICE, 1_000_000); - assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); - assert_ok!(Contract::instantiate( - Origin::signed(ALICE), - 100, - 100_000, code_hash.into(), - ::Balance::from(1_000u32).encode() // rent allowance - )); - - // Advance blocks - System::initialize(&blocks, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); - - // Trigger rent through call - assert!(trigger_call()); - - if removes { - assert!(ContractInfoOf::::get(BOB).unwrap().get_tombstone().is_some()); - } else { - assert!(ContractInfoOf::::get(BOB).unwrap().get_alive().is_some()); - } - }); -} - -/// Test for all kind of removals for the given trigger: -/// * if balance is reached and balance > subsistence threshold -/// * if allowance is exceeded -/// * if balance is reached and balance < subsistence threshold -fn removals(trigger_call: impl Fn() -> bool) { - let (wasm, code_hash) = compile_module::(CODE_SET_RENT).unwrap(); - - // Balance reached and superior to subsistence threshold - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - // Create - Balances::deposit_creating(&ALICE, 1_000_000); - assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm.clone())); - assert_ok!(Contract::instantiate( - Origin::signed(ALICE), - 100, - 100_000, code_hash.into(), - ::Balance::from(1_000u32).encode() // rent allowance - )); - - let subsistence_threshold = 50 /*existential_deposit*/ + 16 /*tombstone_deposit*/; - - // Trigger rent must have no effect - assert!(trigger_call()); - assert_eq!(ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap().rent_allowance, 1_000); - assert_eq!(Balances::free_balance(&BOB), 100); - - // Advance blocks - System::initialize(&10, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); - - // Trigger rent through call - assert!(trigger_call()); - assert!(ContractInfoOf::::get(BOB).unwrap().get_tombstone().is_some()); - assert_eq!(Balances::free_balance(&BOB), subsistence_threshold); - - // Advance blocks - System::initialize(&20, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); - - // Trigger rent must have no effect - assert!(trigger_call()); - assert!(ContractInfoOf::::get(BOB).unwrap().get_tombstone().is_some()); - assert_eq!(Balances::free_balance(&BOB), subsistence_threshold); - }); - - // Allowance exceeded - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - // Create - Balances::deposit_creating(&ALICE, 1_000_000); - assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm.clone())); - assert_ok!(Contract::instantiate( - Origin::signed(ALICE), - 1_000, - 100_000, code_hash.into(), - ::Balance::from(100u32).encode() // rent allowance - )); - - // Trigger rent must have no effect - assert!(trigger_call()); - assert_eq!(ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap().rent_allowance, 100); - assert_eq!(Balances::free_balance(&BOB), 1_000); - - // Advance blocks - System::initialize(&10, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); - - // Trigger rent through call - assert!(trigger_call()); - assert!(ContractInfoOf::::get(BOB).unwrap().get_tombstone().is_some()); - // Balance should be initial balance - initial rent_allowance - assert_eq!(Balances::free_balance(&BOB), 900); - - // Advance blocks - System::initialize(&20, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); - - // Trigger rent must have no effect - assert!(trigger_call()); - assert!(ContractInfoOf::::get(BOB).unwrap().get_tombstone().is_some()); - assert_eq!(Balances::free_balance(&BOB), 900); - }); - - // Balance reached and inferior to subsistence threshold - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - // Create - Balances::deposit_creating(&ALICE, 1_000_000); - assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm.clone())); - assert_ok!(Contract::instantiate( - Origin::signed(ALICE), - 50+Balances::minimum_balance(), - 100_000, code_hash.into(), - ::Balance::from(1_000u32).encode() // rent allowance - )); - - // Trigger rent must have no effect - assert!(trigger_call()); - assert_eq!(ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap().rent_allowance, 1_000); - assert_eq!(Balances::free_balance(&BOB), 50 + Balances::minimum_balance()); - - // Transfer funds - assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::transfer())); - assert_eq!(ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap().rent_allowance, 1_000); - assert_eq!(Balances::free_balance(&BOB), Balances::minimum_balance()); - - // Advance blocks - System::initialize(&10, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); - - // Trigger rent through call - assert!(trigger_call()); - assert!(ContractInfoOf::::get(BOB).is_none()); - assert_eq!(Balances::free_balance(&BOB), Balances::minimum_balance()); - - // Advance blocks - System::initialize(&20, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); - - // Trigger rent must have no effect - assert!(trigger_call()); - assert!(ContractInfoOf::::get(BOB).is_none()); - assert_eq!(Balances::free_balance(&BOB), Balances::minimum_balance()); - }); -} - -#[test] -fn call_removed_contract() { - let (wasm, code_hash) = compile_module::(CODE_SET_RENT).unwrap(); - - // Balance reached and superior to subsistence threshold - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - // Create - Balances::deposit_creating(&ALICE, 1_000_000); - assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm.clone())); - assert_ok!(Contract::instantiate( - Origin::signed(ALICE), - 100, - 100_000, code_hash.into(), - ::Balance::from(1_000u32).encode() // rent allowance - )); - - // Calling contract should succeed. - assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null())); - - // Advance blocks - System::initialize(&10, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); - - // Calling contract should remove contract and fail. - assert_err!( - Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()), - "contract has been evicted" - ); - - // Subsequent contract calls should also fail. - assert_err!( - Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()), - "contract has been evicted" - ); - }) -} - -const CODE_CHECK_DEFAULT_RENT_ALLOWANCE: &str = r#" -(module - (import "env" "ext_rent_allowance" (func $ext_rent_allowance)) - (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" "memory" (memory 1 1)) - - (func $assert (param i32) - (block $ok - (br_if $ok - (get_local 0) - ) - (unreachable) - ) - ) - - (func (export "call")) - - (func (export "deploy") - ;; fill the scratch buffer with the rent allowance. - (call $ext_rent_allowance) - - ;; assert $ext_scratch_size == 8 - (call $assert - (i32.eq - (call $ext_scratch_size) - (i32.const 8) - ) - ) - - ;; copy contents of the scratch buffer into the contract's memory. - (call $ext_scratch_read - (i32.const 8) ;; Pointer in memory to the place where to copy. - (i32.const 0) ;; Offset from the start of the scratch buffer. - (i32.const 8) ;; Count of bytes to copy. - ) - - ;; assert that contents of the buffer is equal to >::max_value(). - (call $assert - (i64.eq - (i64.load - (i32.const 8) - ) - (i64.const 0xFFFFFFFFFFFFFFFF) - ) - ) - ) -) -"#; - -#[test] -fn default_rent_allowance_on_instantiate() { - let (wasm, code_hash) = compile_module::(CODE_CHECK_DEFAULT_RENT_ALLOWANCE).unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - // Create - Balances::deposit_creating(&ALICE, 1_000_000); - assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); - assert_ok!(Contract::instantiate( - Origin::signed(ALICE), - 30_000, - 100_000, - code_hash.into(), - vec![], - )); - - // Check creation - let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap(); - assert_eq!(bob_contract.rent_allowance, >::max_value()); - - // Advance blocks - System::initialize(&5, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); - - // Trigger rent through call - assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null())); - - // Check contract is still alive - let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive(); - assert!(bob_contract.is_some()) - }); -} - -const CODE_RESTORATION: &str = r#" -(module - (import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32 i32))) - (import "env" "ext_restore_to" - (func $ext_restore_to - (param i32 i32 i32 i32 i32 i32 i32 i32) - (result i32) - ) - ) - (import "env" "memory" (memory 1 1)) - - (func (export "call") - ;; Drop the status code. - (drop - (call $ext_restore_to - ;; Pointer and length of the encoded dest buffer. - (i32.const 256) - (i32.const 8) - ;; Pointer and length of the encoded code hash buffer - (i32.const 264) - (i32.const 32) - ;; Pointer and length of the encoded rent_allowance buffer - (i32.const 296) - (i32.const 8) - ;; Pointer and number of items in the delta buffer. - ;; This buffer specifies multiple keys for removal before restoration. - (i32.const 100) - (i32.const 1) - ) - ) - ) - (func (export "deploy") - ;; Data to restore - (call $ext_set_storage - (i32.const 0) - (i32.const 1) - (i32.const 0) - (i32.const 4) - ) - - ;; ACL - (call $ext_set_storage - (i32.const 100) - (i32.const 1) - (i32.const 0) - (i32.const 4) - ) - ) - - ;; Data to restore - (data (i32.const 0) "\28") - - ;; Buffer that has ACL storage keys. - (data (i32.const 100) "\01") - - ;; Address of bob - (data (i32.const 256) "\02\00\00\00\00\00\00\00") - - ;; Code hash of SET_RENT - (data (i32.const 264) - "\14\eb\65\3c\86\98\d6\b2\3d\8d\3c\4a\54\c6\c4\71" - "\b9\fc\19\36\df\ca\a0\a1\f2\dc\ad\9d\e5\36\0b\25" - ) - - ;; Rent allowance - (data (i32.const 296) "\32\00\00\00\00\00\00\00") -) -"#; - -#[test] -fn restorations_dirty_storage_and_different_storage() { - restoration(true, true); -} - -#[test] -fn restorations_dirty_storage() { - restoration(false, true); -} - -#[test] -fn restoration_different_storage() { - restoration(true, false); -} - -#[test] -fn restoration_success() { - restoration(false, false); -} - -fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: bool) { - let (set_rent_wasm, set_rent_code_hash) = compile_module::(CODE_SET_RENT).unwrap(); - let (restoration_wasm, restoration_code_hash) = - compile_module::(CODE_RESTORATION).unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - Balances::deposit_creating(&ALICE, 1_000_000); - assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, restoration_wasm)); - assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, set_rent_wasm)); - - // If you ever need to update the wasm source this test will fail - // and will show you the actual hash. - assert_eq!(System::events(), vec![ - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::balances(balances::RawEvent::NewAccount(1, 1_000_000)), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::contract(RawEvent::CodeStored(restoration_code_hash.into())), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::contract(RawEvent::CodeStored(set_rent_code_hash.into())), - topics: vec![], - }, - ]); - - // Create an account with address `BOB` with code `CODE_SET_RENT`. - // The input parameter sets the rent allowance to 0. - assert_ok!(Contract::instantiate( - Origin::signed(ALICE), - 30_000, - 100_000, - set_rent_code_hash.into(), - ::Balance::from(0u32).encode() - )); - - // Check if `BOB` was created successfully and that the rent allowance is - // set to 0. - let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap(); - assert_eq!(bob_contract.rent_allowance, 0); - - if test_different_storage { - assert_ok!(Contract::call( - Origin::signed(ALICE), - BOB, 0, 100_000, - call::set_storage_4_byte()) - ); - } - - // Advance 4 blocks, to the 5th. - System::initialize(&5, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); - - // Call `BOB`, which makes it pay rent. Since the rent allowance is set to 0 - // we expect that it will get removed leaving tombstone. - assert_err!( - Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()), - "contract has been evicted" - ); - assert!(ContractInfoOf::::get(BOB).unwrap().get_tombstone().is_some()); - - // Create another account with the address `DJANGO` with `CODE_RESTORATION`. - // - // Note that we can't use `ALICE` for creating `DJANGO` so we create yet another - // account `CHARLIE` and create `DJANGO` with it. - Balances::deposit_creating(&CHARLIE, 1_000_000); - assert_ok!(Contract::instantiate( - Origin::signed(CHARLIE), - 30_000, - 100_000, - restoration_code_hash.into(), - ::Balance::from(0u32).encode() - )); - - // Before performing a call to `DJANGO` save its original trie id. - let django_trie_id = ContractInfoOf::::get(DJANGO).unwrap() - .get_alive().unwrap().trie_id; - - if !test_restore_to_with_dirty_storage { - // Advance 1 block, to the 6th. - System::initialize(&6, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); - } - - // Perform a call to `DJANGO`. This should either perform restoration successfully or - // fail depending on the test parameters. - assert_ok!(Contract::call( - Origin::signed(ALICE), - DJANGO, - 0, - 100_000, - vec![], - )); - - if test_different_storage || test_restore_to_with_dirty_storage { - // Parametrization of the test imply restoration failure. Check that `DJANGO` aka - // restoration contract is still in place and also that `BOB` doesn't exist. - assert!(ContractInfoOf::::get(BOB).unwrap().get_tombstone().is_some()); - let django_contract = ContractInfoOf::::get(DJANGO).unwrap() - .get_alive().unwrap(); - assert_eq!(django_contract.storage_size, 16); - assert_eq!(django_contract.trie_id, django_trie_id); - assert_eq!(django_contract.deduct_block, System::block_number()); - } else { - // Here we expect that the restoration is succeeded. Check that the restoration - // contract `DJANGO` ceased to exist and that `BOB` returned back. - println!("{:?}", ContractInfoOf::::get(BOB)); - let bob_contract = ContractInfoOf::::get(BOB).unwrap() - .get_alive().unwrap(); - assert_eq!(bob_contract.rent_allowance, 50); - assert_eq!(bob_contract.storage_size, 12); - assert_eq!(bob_contract.trie_id, django_trie_id); - assert_eq!(bob_contract.deduct_block, System::block_number()); - assert!(ContractInfoOf::::get(DJANGO).is_none()); - } - }); -} - -const CODE_STORAGE_SIZE: &str = r#" -(module - (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_scratch_size" (func $ext_scratch_size (result i32))) - (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) - (import "env" "memory" (memory 16 16)) - - (func $assert (param i32) - (block $ok - (br_if $ok - (get_local 0) - ) - (unreachable) - ) - ) - - (func (export "call") - ;; assert $ext_scratch_size == 8 - (call $assert - (i32.eq - (call $ext_scratch_size) - (i32.const 4) - ) - ) - - ;; copy contents of the scratch buffer into the contract's memory. - (call $ext_scratch_read - (i32.const 32) ;; Pointer in memory to the place where to copy. - (i32.const 0) ;; Offset from the start of the scratch buffer. - (i32.const 4) ;; Count of bytes to copy. - ) - - ;; place a garbage value in storage, the size of which is specified by the call input. - (call $ext_set_storage - (i32.const 0) ;; Pointer to storage key - (i32.const 1) ;; Value is not null - (i32.const 0) ;; Pointer to value - (i32.load (i32.const 32)) ;; Size of value - ) - - (call $assert - (i32.eq - (call $ext_get_storage - (i32.const 0) ;; Pointer to storage key - ) - (i32.const 0) - ) - ) - - (call $assert - (i32.eq - (call $ext_scratch_size) - (i32.load (i32.const 32)) - ) - ) - ) - - (func (export "deploy")) - - (data (i32.const 0) "\01") ;; Storage key (32 B) -) -"#; - -#[test] -fn storage_max_value_limit() { - let (wasm, code_hash) = compile_module::(CODE_STORAGE_SIZE).unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - // Create - Balances::deposit_creating(&ALICE, 1_000_000); - assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); - assert_ok!(Contract::instantiate( - Origin::signed(ALICE), - 30_000, - 100_000, - code_hash.into(), - vec![], - )); - - // Check creation - let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap(); - assert_eq!(bob_contract.rent_allowance, >::max_value()); - - // Call contract with allowed storage value. - assert_ok!(Contract::call( - Origin::signed(ALICE), - BOB, - 0, - 100_000, - Encode::encode(&self::MaxValueSize::get()), - )); - - // Call contract with too large a storage value. - assert_err!( - Contract::call( - Origin::signed(ALICE), - BOB, - 0, - 100_000, - Encode::encode(&(self::MaxValueSize::get() + 1)), - ), - "during execution" - ); - }); -} - -const CODE_RETURN_WITH_DATA: &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_scratch_write" (func $ext_scratch_write (param i32 i32))) - (import "env" "memory" (memory 1 1)) - - ;; Deploy routine is the same as call. - (func (export "deploy") (result i32) - (call $call) - ) - - ;; Call reads the first 4 bytes (LE) as the exit status and returns the rest as output data. - (func $call (export "call") (result i32) - (local $buf_size i32) - (local $exit_status i32) - - ;; Find out the size of the scratch buffer - (set_local $buf_size (call $ext_scratch_size)) - - ;; Copy scratch buffer into this contract memory. - (call $ext_scratch_read - (i32.const 0) ;; The pointer where to store the scratch buffer contents, - (i32.const 0) ;; Offset from the start of the scratch buffer. - (get_local $buf_size) ;; Count of bytes to copy. - ) - - ;; Copy all but the first 4 bytes of the input data as the output data. - (call $ext_scratch_write - (i32.const 4) ;; Pointer to the data to return. - (i32.sub ;; Count of bytes to copy. - (get_local $buf_size) - (i32.const 4) - ) - ) - - ;; Return the first 4 bytes of the input data as the exit status. - (i32.load (i32.const 0)) - ) -) -"#; - -const CODE_CALLER_CONTRACT: &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" "ext_instantiate" (func $ext_instantiate (param i32 i32 i64 i32 i32 i32 i32) (result i32))) - (import "env" "ext_println" (func $ext_println (param i32 i32))) - (import "env" "memory" (memory 1 1)) - - (func $assert (param i32) - (block $ok - (br_if $ok - (get_local 0) - ) - (unreachable) - ) - ) - - (func $current_balance (param $sp i32) (result i64) - (call $ext_balance) - (call $assert - (i32.eq (call $ext_scratch_size) (i32.const 8)) - ) - (call $ext_scratch_read - (i32.sub (get_local $sp) (i32.const 8)) - (i32.const 0) - (i32.const 8) - ) - (i64.load (i32.sub (get_local $sp) (i32.const 8))) - ) - - (func (export "deploy")) - - (func (export "call") - (local $sp i32) - (local $exit_code i32) - (local $balance i64) - - ;; 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 24) ;; 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. - ) - - ;; Read current balance into local variable. - (set_local $sp (i32.const 1024)) - (set_local $balance - (call $current_balance (get_local $sp)) - ) - - ;; Fail to deploy the contract since it returns a non-zero exit status. - (set_local $exit_code - (call $ext_instantiate - (i32.const 24) ;; 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 9) ;; Pointer to input data buffer address - (i32.const 7) ;; Length of input data buffer - ) - ) - - ;; Check non-zero exit status. - (call $assert - (i32.eq (get_local $exit_code) (i32.const 0x11)) - ) - - ;; Check that scratch buffer is empty since contract instantiation failed. - (call $assert - (i32.eq (call $ext_scratch_size) (i32.const 0)) - ) - - ;; Check that balance has not changed. - (call $assert - (i64.eq (get_local $balance) (call $current_balance (get_local $sp))) - ) - - ;; Fail to deploy the contract due to insufficient gas. - (set_local $exit_code - (call $ext_instantiate - (i32.const 24) ;; Pointer to the code hash. - (i32.const 32) ;; Length of the code hash. - (i64.const 200) ;; How much gas to devote for the execution. - (i32.const 0) ;; Pointer to the buffer with value to transfer - (i32.const 8) ;; Length of the buffer with value to transfer. - (i32.const 8) ;; Pointer to input data buffer address - (i32.const 8) ;; Length of input data buffer - ) - ) - - ;; Check for special trap exit status. - (call $assert - (i32.eq (get_local $exit_code) (i32.const 0x0100)) - ) - - ;; Check that scratch buffer is empty since contract instantiation failed. - (call $assert - (i32.eq (call $ext_scratch_size) (i32.const 0)) - ) - - ;; Check that balance has not changed. - (call $assert - (i64.eq (get_local $balance) (call $current_balance (get_local $sp))) - ) - - ;; Deploy the contract successfully. - (set_local $exit_code - (call $ext_instantiate - (i32.const 24) ;; 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 8) ;; Pointer to input data buffer address - (i32.const 8) ;; Length of input data buffer - ) - ) - - ;; Check for success exit status. - (call $assert - (i32.eq (get_local $exit_code) (i32.const 0x00)) - ) - - ;; Check that scratch buffer contains the address of the new contract. - (call $assert - (i32.eq (call $ext_scratch_size) (i32.const 8)) - ) - - ;; Copy contract address from scratch buffer into this contract's memory. - (call $ext_scratch_read - (i32.const 16) ;; 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. - ) - - ;; Check that balance has been deducted. - (set_local $balance - (i64.sub (get_local $balance) (i64.load (i32.const 0))) - ) - (call $assert - (i64.eq (get_local $balance) (call $current_balance (get_local $sp))) - ) - - ;; Call the new contract and expect it to return failing exit code. - (set_local $exit_code - (call $ext_call - (i32.const 16) ;; Pointer to "callee" address. - (i32.const 8) ;; Length of "callee" 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 9) ;; Pointer to input data buffer address - (i32.const 7) ;; Length of input data buffer - ) - ) - - ;; Check non-zero exit status. - (call $assert - (i32.eq (get_local $exit_code) (i32.const 0x11)) - ) - - ;; Check that scratch buffer contains the expected return data. - (call $assert - (i32.eq (call $ext_scratch_size) (i32.const 3)) - ) - (i32.store - (i32.sub (get_local $sp) (i32.const 4)) - (i32.const 0) - ) - (call $ext_scratch_read - (i32.sub (get_local $sp) (i32.const 4)) - (i32.const 0) - (i32.const 3) - ) - (call $assert - (i32.eq - (i32.load (i32.sub (get_local $sp) (i32.const 4))) - (i32.const 0x00776655) - ) - ) - - ;; Check that balance has not changed. - (call $assert - (i64.eq (get_local $balance) (call $current_balance (get_local $sp))) - ) - - ;; Fail to call the contract due to insufficient gas. - (set_local $exit_code - (call $ext_call - (i32.const 16) ;; Pointer to "callee" address. - (i32.const 8) ;; Length of "callee" address. - (i64.const 100) ;; How much gas to devote for the execution. - (i32.const 0) ;; Pointer to the buffer with value to transfer - (i32.const 8) ;; Length of the buffer with value to transfer. - (i32.const 8) ;; Pointer to input data buffer address - (i32.const 8) ;; Length of input data buffer - ) - ) - - ;; Check for special trap exit status. - (call $assert - (i32.eq (get_local $exit_code) (i32.const 0x0100)) - ) - - ;; Check that scratch buffer is empty since call trapped. - (call $assert - (i32.eq (call $ext_scratch_size) (i32.const 0)) - ) - - ;; Check that balance has not changed. - (call $assert - (i64.eq (get_local $balance) (call $current_balance (get_local $sp))) - ) - - ;; Call the contract successfully. - (set_local $exit_code - (call $ext_call - (i32.const 16) ;; Pointer to "callee" address. - (i32.const 8) ;; Length of "callee" 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 8) ;; Pointer to input data buffer address - (i32.const 8) ;; Length of input data buffer - ) - ) - - ;; Check for success exit status. - (call $assert - (i32.eq (get_local $exit_code) (i32.const 0x00)) - ) - - ;; Check that scratch buffer contains the expected return data. - (call $assert - (i32.eq (call $ext_scratch_size) (i32.const 4)) - ) - (i32.store - (i32.sub (get_local $sp) (i32.const 4)) - (i32.const 0) - ) - (call $ext_scratch_read - (i32.sub (get_local $sp) (i32.const 4)) - (i32.const 0) - (i32.const 4) - ) - (call $assert - (i32.eq - (i32.load (i32.sub (get_local $sp) (i32.const 4))) - (i32.const 0x77665544) - ) - ) - - ;; Check that balance has been deducted. - (set_local $balance - (i64.sub (get_local $balance) (i64.load (i32.const 0))) - ) - (call $assert - (i64.eq (get_local $balance) (call $current_balance (get_local $sp))) - ) - ) - - (data (i32.const 0) "\00\80") ;; The value to transfer on instantiation and calls. - ;; Chosen to be greater than existential deposit. - (data (i32.const 8) "\00\11\22\33\44\55\66\77") ;; The input data to instantiations and calls. -) -"#; - -#[test] -fn deploy_and_call_other_contract() { - let (callee_wasm, callee_code_hash) = compile_module::(CODE_RETURN_WITH_DATA).unwrap(); - let (caller_wasm, caller_code_hash) = compile_module::(CODE_CALLER_CONTRACT).unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - // 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)); - - assert_ok!(Contract::instantiate( - Origin::signed(ALICE), - 100_000, - 100_000, - caller_code_hash.into(), - vec![], - )); - - // Call BOB contract, which attempts to instantiate and call the callee contract and - // makes various assertions on the results from those calls. - assert_ok!(Contract::call( - Origin::signed(ALICE), - BOB, - 0, - 200_000, - callee_code_hash.as_ref().to_vec(), - )); - }); -} - -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::(CODE_SELF_DESTRUCT).unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - 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::instantiate( - Origin::signed(ALICE), - 100_000, - 100_000, - code_hash.into(), - vec![], - )); - - // Check that the BOB contract has been instantiated. - assert_matches!( - ContractInfoOf::::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::::get(BOB).is_none()); - }); -} - -#[test] -fn cannot_self_destruct_while_live() { - let (wasm, code_hash) = compile_module::(CODE_SELF_DESTRUCT).unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - 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::instantiate( - Origin::signed(ALICE), - 100_000, - 100_000, - code_hash.into(), - vec![], - )); - - // Check that the BOB contract has been instantiated. - assert_matches!( - ContractInfoOf::::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::::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_instantiate" (func $ext_instantiate (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_instantiate - (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::(CODE_SELF_DESTRUCT).unwrap(); - let (caller_wasm, caller_code_hash) = compile_module::(CODE_DESTROY_AND_TRANSFER).unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - // 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::instantiate( - 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::::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::::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::(CODE_SELF_DESTRUCTING_CONSTRUCTOR).unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - 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::instantiate( - Origin::signed(ALICE), - 100_000, - 100_000, - code_hash.into(), - vec![], - ), - "insufficient remaining balance" - ); - }); -} - -#[test] -fn check_block_gas_limit_works() { - ExtBuilder::default().block_gas_limit(50).build().execute_with(|| { - let info = DispatchInfo { weight: 100, class: DispatchClass::Normal, pays_fee: true }; - let check = CheckBlockGasLimit::(Default::default()); - let call: Call = crate::Call::put_code(1000, vec![]).into(); - - assert_eq!( - check.validate(&0, &call, info, 0), InvalidTransaction::ExhaustsResources.into(), - ); - - let call: Call = crate::Call::update_schedule(Default::default()).into(); - assert_eq!(check.validate(&0, &call, info, 0), Ok(Default::default())); - }); -} - -const CODE_GET_RUNTIME_STORAGE: &str = r#" -(module - (import "env" "ext_get_runtime_storage" - (func $ext_get_runtime_storage (param i32 i32) (result i32)) - ) - (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_scratch_write" (func $ext_scratch_write (param i32 i32))) - (import "env" "memory" (memory 1 1)) - - (func (export "deploy")) - - (func $assert (param i32) - (block $ok - (br_if $ok - (get_local 0) - ) - (unreachable) - ) - ) - - (func $call (export "call") - ;; Load runtime storage for the first key and assert that it exists. - (call $assert - (i32.eq - (call $ext_get_runtime_storage - (i32.const 16) - (i32.const 4) - ) - (i32.const 0) - ) - ) - - ;; assert $ext_scratch_size == 4 - (call $assert - (i32.eq - (call $ext_scratch_size) - (i32.const 4) - ) - ) - - ;; copy contents of the scratch buffer into the contract's memory. - (call $ext_scratch_read - (i32.const 4) ;; Pointer in memory to the place where to copy. - (i32.const 0) ;; Offset from the start of the scratch buffer. - (i32.const 4) ;; Count of bytes to copy. - ) - - ;; assert that contents of the buffer is equal to the i32 value of 0x14144020. - (call $assert - (i32.eq - (i32.load - (i32.const 4) - ) - (i32.const 0x14144020) - ) - ) - - ;; Load the second key and assert that it doesn't exist. - (call $assert - (i32.eq - (call $ext_get_runtime_storage - (i32.const 20) - (i32.const 4) - ) - (i32.const 1) - ) - ) - ) - - ;; The first key, 4 bytes long. - (data (i32.const 16) "\01\02\03\04") - ;; The second key, 4 bytes long. - (data (i32.const 20) "\02\03\04\05") -) -"#; - -#[test] -fn get_runtime_storage() { - let (wasm, code_hash) = compile_module::(CODE_GET_RUNTIME_STORAGE).unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - Balances::deposit_creating(&ALICE, 1_000_000); - - support::storage::unhashed::put_raw( - &[1, 2, 3, 4], - 0x14144020u32.to_le_bytes().to_vec().as_ref() - ); - - assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm)); - assert_ok!(Contract::instantiate( - Origin::signed(ALICE), - 100, - 100_000, - code_hash.into(), - vec![], - )); - assert_ok!(Contract::call( - Origin::signed(ALICE), - BOB, - 0, - 100_000, - vec![], - )); - }); -} From 125ba66a7adca313ef7581834893d3c4d0f6e636 Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Thu, 11 Jun 2020 13:14:32 +0200 Subject: [PATCH 10/33] Make it compile. --- frame/contracts/src/exec.rs | 19 +++++-------------- frame/contracts/src/lib.rs | 3 +-- frame/contracts/src/storage.rs | 2 +- frame/contracts/src/util.rs | 18 ------------------ 4 files changed, 7 insertions(+), 35 deletions(-) delete mode 100644 frame/contracts/src/util.rs diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index ba650175f00d7..de0a0129379c1 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -21,10 +21,10 @@ use crate::rent; use crate::storage; use sp_std::prelude::*; -use sp_runtime::traits::{Bounded, CheckedAdd, CheckedSub, Zero}; +use sp_runtime::traits::{Bounded, Zero}; use frame_support::{ storage::unhashed, dispatch::DispatchError, - traits::{ExistenceRequirement, WithdrawReason, Currency, Time, Randomness}, + traits::{ExistenceRequirement, Currency, Time, Randomness}, }; pub type AccountIdOf = ::AccountId; @@ -587,11 +587,11 @@ where { let (output, deferred) = { let mut nested = self.nested(dest, trie_id); - let output = crate::util::with_transaction(|| { + let output = frame_support::storage::with_transaction(|| { let output = func(&mut nested); match output { - Ok(ref rv) if rv.is_success() => (output, crate::util::TxOutcome::Commit), - _ => (output, crate::util::TxOutcome::Discard), + Ok(ref rv) if rv.is_success() => (output, frame_support::storage::TransactionOutcome::Commit), + _ => (output, frame_support::storage::TransactionOutcome::Rollback), } })?; (output, nested.deferred) @@ -670,15 +670,6 @@ fn transfer<'a, T: Trait, V: Vm, L: Loader>( use self::TransferCause::*; use self::TransferFeeKind::*; - let to_balance = T::Currency::free_balance(dest); - - // `would_create` indicates whether the account will be created if this transfer gets executed. - // This flag is orthogonal to `cause. - // For example, we can instantiate a contract at the address which already has some funds. In this - // `would_create` will be `false`. Another example would be when this function is called from `call`, - // and account with the address `dest` doesn't exist yet `would_create` will be `true`. - let would_create = to_balance.is_zero(); - let token = { let kind: TransferFeeKind = match cause { // If this function is called from `Instantiate` routine, then we always diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 8230e978446e0..a5aef1596dba9 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -85,7 +85,6 @@ mod storage; mod exec; mod wasm; mod rent; -mod util; #[cfg(test)] mod tests; @@ -112,7 +111,7 @@ use frame_support::dispatch::{ }; use frame_support::{ Parameter, decl_module, decl_event, decl_storage, decl_error, - parameter_types, IsSubType, storage::child::{self, ChildInfo}, + parameter_types, IsSubType, storage::child::ChildInfo, }; use frame_support::traits::{OnUnbalanced, Currency, Get, Time, Randomness}; use frame_support::weights::GetDispatchInfo; diff --git a/frame/contracts/src/storage.rs b/frame/contracts/src/storage.rs index 493146cba49b0..13d6ef7ea0fb9 100644 --- a/frame/contracts/src/storage.rs +++ b/frame/contracts/src/storage.rs @@ -21,7 +21,7 @@ use crate::{ use sp_std::prelude::*; use sp_io::hashing::blake2_256; use sp_runtime::traits::Bounded; -use frame_support::{storage::child, traits::Get, StorageMap}; +use frame_support::{storage::child, StorageMap}; pub fn read_contract_storage(trie_id: &TrieId, key: &StorageKey) -> Option> { child::get_raw(&crate::child_trie_info(&trie_id), &blake2_256(key)) diff --git a/frame/contracts/src/util.rs b/frame/contracts/src/util.rs deleted file mode 100644 index 2734cbbffc578..0000000000000 --- a/frame/contracts/src/util.rs +++ /dev/null @@ -1,18 +0,0 @@ -pub enum TxOutcome { - Commit, - Discard, -} - -pub fn with_transaction(f: impl FnOnce() -> (R, TxOutcome)) -> R { - use sp_io::storage::{ - start_transaction, commit_transaction, discard_transaction, - }; - - start_transaction(); - let (result, outcome) = f(); - match outcome { - TxOutcome::Commit => commit_transaction(), - TxOutcome::Discard => discard_transaction(), - } - result -} From 5b2c57107e83cffd1ab84c767bde8c60c7023bea Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Thu, 11 Jun 2020 15:14:00 +0200 Subject: [PATCH 11/33] Make the tests compile --- frame/contracts/src/exec.rs | 11 +++-- frame/contracts/src/tests.rs | 91 +++++++++++++++++++----------------- 2 files changed, 54 insertions(+), 48 deletions(-) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index de0a0129379c1..11ffff899b374 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -891,13 +891,12 @@ mod tests { RawEvent, TransferFeeKind, TransferFeeToken, Vm, }; use crate::{ - gas::GasMeter, tests::{ExtBuilder, Test, set_balance, get_balance, place_contract}, + gas::GasMeter, tests::{ExtBuilder, Test, MetaEvent}, exec::{ExecReturnValue, ExecError, STATUS_SUCCESS}, CodeHash, Config, gas::Gas, storage, - tests::{get_balance, place_contract, set_balance, ExtBuilder, MetaEvent, Test}, - CodeHash, Config, }; + use crate::tests::test_utils::{place_contract, set_balance, get_balance}; use sp_runtime::DispatchError; use assert_matches::assert_matches; use std::{cell::RefCell, collections::HashMap, marker::PhantomData, rc::Rc}; @@ -906,11 +905,13 @@ mod tests { const BOB: u64 = 2; const CHARLIE: u64 = 3; + const GAS_LIMIT: Gas = 10_000_000_000; + fn events() -> Vec> { >::events() .into_iter() .filter_map(|meta| match meta.event { - MetaEvent::contract(contract_event) => Some(contract_event), + MetaEvent::contracts(contract_event) => Some(contract_event), _ => None, }) .collect() @@ -1718,7 +1719,7 @@ mod tests { ); assert_eq!( - &ctx.events(), + &events(), &[] ); }); diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index e16b681849d01..27542c4f2a5ad 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -62,6 +62,33 @@ impl_outer_dispatch! { } } +pub mod test_utils { + use super::{Test, Balances}; + use crate::{ContractInfoOf, TrieIdGenerator, CodeHash}; + use crate::storage::{write_contract_storage, read_contract_storage}; + use crate::exec::StorageKey; + use frame_support::{StorageMap, traits::Currency}; + + pub fn set_storage(addr: &u64, key: &StorageKey, value: Option>) { + let contract_info = >::get(&addr).unwrap().get_alive().unwrap(); + write_contract_storage::(&1, &contract_info.trie_id, key, value); + } + pub fn get_storage(addr: &u64, key: &StorageKey) -> Option> { + let contract_info = >::get(&addr).unwrap().get_alive().unwrap(); + read_contract_storage(&contract_info.trie_id, key) + } + pub fn place_contract(address: &u64, code_hash: CodeHash) { + let trie_id = ::TrieIdGenerator::trie_id(address); + crate::storage::place_contract::(&address, trie_id, code_hash).unwrap() + } + pub fn set_balance(who: &u64, amount: u64) { + Balances::deposit_creating(who, amount); + } + pub fn get_balance(who: &u64) -> u64 { + Balances::free_balance(who) + } +} + thread_local! { static EXISTENTIAL_DEPOSIT: RefCell = RefCell::new(0); } @@ -278,6 +305,8 @@ fn returns_base_call_cost() { #[test] fn account_removal_does_not_remove_storage() { + use self::test_utils::{set_storage, get_storage}; + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { let trie_id1 = ::TrieIdGenerator::trie_id(&1); let trie_id2 = ::TrieIdGenerator::trie_id(&2); @@ -286,8 +315,7 @@ fn account_removal_does_not_remove_storage() { // Set up two accounts with free balance above the existential threshold. { - let _ = Balances::deposit_creating(&1, 110); - ContractInfoOf::::insert(1, &ContractInfo::Alive(RawAliveContractInfo { + let alice_contract_info = ContractInfo::Alive(RawAliveContractInfo { trie_id: trie_id1.clone(), storage_size: 0, empty_pair_count: 0, @@ -296,15 +324,13 @@ fn account_removal_does_not_remove_storage() { code_hash: H256::repeat_byte(1), rent_allowance: 40, last_write: None, - })); - - let mut overlay = OverlayAccountDb::::new(&DirectAccountDb); - overlay.set_storage(&1, key1.clone(), Some(b"1".to_vec())); - overlay.set_storage(&1, key2.clone(), Some(b"2".to_vec())); - DirectAccountDb.commit(overlay.into_change_set()); + }); + let _ = Balances::deposit_creating(&ALICE, 110); + ContractInfoOf::::insert(ALICE, &alice_contract_info); + set_storage(&ALICE, &key1, Some(b"1".to_vec())); + set_storage(&ALICE, &key2, Some(b"2".to_vec())); - let _ = Balances::deposit_creating(&2, 110); - ContractInfoOf::::insert(2, &ContractInfo::Alive(RawAliveContractInfo { + let bob_contract_info = ContractInfo::Alive(RawAliveContractInfo { trie_id: trie_id2.clone(), storage_size: 0, empty_pair_count: 0, @@ -313,40 +339,39 @@ fn account_removal_does_not_remove_storage() { code_hash: H256::repeat_byte(2), rent_allowance: 40, last_write: None, - })); - - let mut overlay = OverlayAccountDb::::new(&DirectAccountDb); - overlay.set_storage(&2, key1.clone(), Some(b"3".to_vec())); - overlay.set_storage(&2, key2.clone(), Some(b"4".to_vec())); - DirectAccountDb.commit(overlay.into_change_set()); + }); + let _ = Balances::deposit_creating(&BOB, 110); + ContractInfoOf::::insert(BOB, &bob_contract_info); + set_storage(&BOB, &key1, Some(b"3".to_vec())); + set_storage(&BOB, &key2, Some(b"4".to_vec())); } - // Transfer funds from account 1 of such amount that after this transfer - // the balance of account 1 will be below the existential threshold. + // Transfer funds from ALICE account of such amount that after this transfer + // the balance of the ALICE account will be below the existential threshold. // // This does not remove the contract storage as we are not notified about a // account removal. This cannot happen in reality because a contract can only // remove itself by `ext_terminate`. There is no external event that can remove // the account appart from that. - assert_ok!(Balances::transfer(Origin::signed(1), 2, 20)); + assert_ok!(Balances::transfer(Origin::signed(ALICE), BOB, 20)); // Verify that no entries are removed. { assert_eq!( - >::get_storage(&DirectAccountDb, &1, Some(&trie_id1), key1), + get_storage(&ALICE, key1), Some(b"1".to_vec()) ); assert_eq!( - >::get_storage(&DirectAccountDb, &1, Some(&trie_id1), key2), + get_storage(&ALICE, key2), Some(b"2".to_vec()) ); assert_eq!( - >::get_storage(&DirectAccountDb, &2, Some(&trie_id2), key1), + get_storage(&BOB, key1), Some(b"3".to_vec()) ); assert_eq!( - >::get_storage(&DirectAccountDb, &2, Some(&trie_id2), key2), + get_storage(&BOB, key2), Some(b"4".to_vec()) ); } @@ -402,11 +427,6 @@ fn instantiate_and_call_and_deposit_event() { ), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: MetaEvent::contracts(RawEvent::Transfer(ALICE, BOB, 100)), - topics: vec![], - }, EventRecord { phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::ContractExecution(BOB, vec![1, 2, 3, 4])), @@ -505,11 +525,6 @@ fn dispatch_call() { ), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: MetaEvent::contracts(RawEvent::Transfer(ALICE, BOB, 100)), - topics: vec![], - }, EventRecord { phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::Instantiated(ALICE, BOB)), @@ -632,11 +647,6 @@ fn dispatch_call_not_dispatched_after_top_level_transaction_failure() { ), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: MetaEvent::contracts(RawEvent::Transfer(ALICE, BOB, 100)), - topics: vec![], - }, EventRecord { phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::Instantiated(ALICE, BOB)), @@ -1421,11 +1431,6 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: event: MetaEvent::balances(pallet_balances::RawEvent::Endowed(DJANGO, 30_000)), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: MetaEvent::contracts(RawEvent::Transfer(CHARLIE, DJANGO, 30_000)), - topics: vec![], - }, EventRecord { phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::Instantiated(CHARLIE, DJANGO)), From 3a6edb457a94b18eab02370b892612fbace87a06 Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Thu, 11 Jun 2020 15:14:12 +0200 Subject: [PATCH 12/33] Get rid of account_db --- frame/contracts/src/account_db.rs | 450 ------------------------------ 1 file changed, 450 deletions(-) delete mode 100644 frame/contracts/src/account_db.rs diff --git a/frame/contracts/src/account_db.rs b/frame/contracts/src/account_db.rs deleted file mode 100644 index 5e1b0c34b5734..0000000000000 --- a/frame/contracts/src/account_db.rs +++ /dev/null @@ -1,450 +0,0 @@ -// Copyright 2018-2020 Parity Technologies (UK) Ltd. -// This file is part of Substrate. - -// Substrate is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Substrate is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Substrate. If not, see . - -//! Auxiliaries to help with managing partial changes to accounts state. - -use super::{ - AliveContractInfo, BalanceOf, CodeHash, ContractInfo, ContractInfoOf, Trait, TrieId, - TrieIdGenerator, -}; -use crate::exec::StorageKey; -use sp_std::cell::RefCell; -use sp_std::collections::btree_map::{BTreeMap, Entry}; -use sp_std::prelude::*; -use sp_io::hashing::blake2_256; -use sp_runtime::traits::{Bounded, Zero}; -use frame_support::traits::{Currency, Imbalance, SignedImbalance}; -use frame_support::{storage::child, StorageMap}; -use frame_system; - -// Note: we don't provide Option because we can't create -// the trie_id in the overlay, thus we provide an overlay on the fields -// specifically. -pub struct ChangeEntry { - /// 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>, - /// If Some(_), then a contract is instantiated 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>, - /// 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>, - storage: BTreeMap>>, - /// 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 ChangeEntry { - fn balance(&self) -> Option> { - self.balance.or_else(|| { - if self.reset { - Some(>::zero()) - } else { - None - } - }) - } - - fn code_hash(&self) -> Option>> { - if self.reset { - Some(self.code_hash) - } else { - self.code_hash.map(Some) - } - } - - fn rent_allowance(&self) -> Option>> { - if self.reset { - Some(self.rent_allowance) - } else { - self.rent_allowance.map(Some) - } - } - - fn storage(&self, location: &StorageKey) -> Option>> { - 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. -impl Default for ChangeEntry { - fn default() -> Self { - ChangeEntry { - rent_allowance: Default::default(), - balance: Default::default(), - code_hash: Default::default(), - storage: Default::default(), - reset: false, - } - } -} - -pub type ChangeSet = BTreeMap<::AccountId, ChangeEntry>; - -pub trait AccountDb { - /// Account is used when overlayed otherwise trie_id must be provided. - /// This is for performance reason. - /// - /// Trie id is None iff account doesn't have an associated trie id in >. - /// Because DirectAccountDb bypass the lookup for this association. - fn get_storage( - &self, - account: &T::AccountId, - trie_id: Option<&TrieId>, - location: &StorageKey, - ) -> Option>; - /// If account has an alive contract then return the code hash associated. - fn get_code_hash(&self, account: &T::AccountId) -> Option>; - /// If account has an alive contract then return the rent allowance associated. - fn get_rent_allowance(&self, account: &T::AccountId) -> Option>; - /// Returns false iff account has no alive contract nor tombstone. - fn contract_exists(&self, account: &T::AccountId) -> bool; - fn get_balance(&self, account: &T::AccountId) -> BalanceOf; - - fn commit(&mut self, change_set: ChangeSet); -} - -pub struct DirectAccountDb; -impl AccountDb for DirectAccountDb { - fn get_storage( - &self, - _account: &T::AccountId, - trie_id: Option<&TrieId>, - location: &StorageKey, - ) -> Option> { - trie_id - .and_then(|id| child::get_raw(&crate::child_trie_info(&id[..]), &blake2_256(location))) - } - fn get_code_hash(&self, account: &T::AccountId) -> Option> { - >::get(account).and_then(|i| i.as_alive().map(|i| i.code_hash)) - } - fn get_rent_allowance(&self, account: &T::AccountId) -> Option> { - >::get(account).and_then(|i| i.as_alive().map(|i| i.rent_allowance)) - } - fn contract_exists(&self, account: &T::AccountId) -> bool { - >::contains_key(account) - } - fn get_balance(&self, account: &T::AccountId) -> BalanceOf { - T::Currency::free_balance(account) - } - fn commit(&mut self, s: ChangeSet) { - let mut total_imbalance = SignedImbalance::zero(); - for (address, changed) in s.into_iter() { - if let Some(balance) = changed.balance() { - let imbalance = T::Currency::make_free_balance_be(&address, balance); - total_imbalance = total_imbalance.merge(imbalance); - } - - if changed.code_hash().is_some() - || changed.rent_allowance().is_some() - || !changed.storage.is_empty() - || changed.reset - { - let old_info = match >::get(&address) { - Some(ContractInfo::Alive(alive)) => Some(alive), - None => None, - // Cannot commit changes to tombstone contract - Some(ContractInfo::Tombstone(_)) => continue, - }; - - 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.child_trie_info()); - >::remove(&address); - continue; - } - // Existing contract is being replaced by a new one. - (true, Some(info), Some(code_hash)) => { - child::kill_storage(&info.child_trie_info()); - AliveContractInfo:: { - code_hash, - storage_size: 0, - empty_pair_count: 0, - total_pair_count: 0, - trie_id: ::TrieIdGenerator::trie_id(&address), - deduct_block: >::block_number(), - rent_allowance: >::max_value(), - last_write: None, - } - } - // New contract is being instantiated. - (_, None, Some(code_hash)) => AliveContractInfo:: { - code_hash, - storage_size: 0, - empty_pair_count: 0, - total_pair_count: 0, - trie_id: ::TrieIdGenerator::trie_id(&address), - deduct_block: >::block_number(), - rent_allowance: >::max_value(), - last_write: None, - }, - // There is no existing at the address nor a new one to be instantiated. - (_, None, None) => continue, - }; - - if let Some(rent_allowance) = changed.rent_allowance { - new_info.rent_allowance = rent_allowance; - } - - if let Some(code_hash) = changed.code_hash { - new_info.code_hash = code_hash; - } - - if !changed.storage.is_empty() { - new_info.last_write = Some(>::block_number()); - } - - // NB: this call allocates internally. To keep allocations to the minimum we cache - // the child trie info here. - let child_trie_info = new_info.child_trie_info(); - - // Here we iterate over all storage key-value pairs that were changed throughout the - // execution of a contract and apply them to the substrate storage. - for (key, opt_new_value) in changed.storage.into_iter() { - let hashed_key = blake2_256(&key); - - // In order to correctly update the book keeping we need to fetch the previous - // value of the key-value pair. - // - // It might be a bit more clean if we had an API that supported getting the size - // of the value without going through the loading of it. But at the moment of - // writing, there is no such API. - // - // That's not a show stopper in any case, since the performance cost is - // dominated by the trie traversal anyway. - let opt_prev_value = child::get_raw(&child_trie_info, &hashed_key); - - // Update the total number of KV pairs and the number of empty pairs. - match (&opt_prev_value, &opt_new_value) { - (Some(prev_value), None) => { - new_info.total_pair_count -= 1; - if prev_value.is_empty() { - new_info.empty_pair_count -= 1; - } - }, - (None, Some(new_value)) => { - new_info.total_pair_count += 1; - if new_value.is_empty() { - new_info.empty_pair_count += 1; - } - }, - (Some(prev_value), Some(new_value)) => { - if prev_value.is_empty() { - new_info.empty_pair_count -= 1; - } - if new_value.is_empty() { - new_info.empty_pair_count += 1; - } - } - (None, None) => {} - } - - // Update the total storage size. - let prev_value_len = opt_prev_value - .as_ref() - .map(|old_value| old_value.len() as u32) - .unwrap_or(0); - let new_value_len = opt_new_value - .as_ref() - .map(|new_value| new_value.len() as u32) - .unwrap_or(0); - new_info.storage_size = new_info - .storage_size - .saturating_add(new_value_len) - .saturating_sub(prev_value_len); - - // Finally, perform the change on the storage. - match opt_new_value { - Some(new_value) => child::put_raw(&child_trie_info, &hashed_key, &new_value[..]), - None => child::kill(&child_trie_info, &hashed_key), - } - } - - if old_info - .map(|old_info| old_info != new_info) - .unwrap_or(true) - { - >::insert(&address, ContractInfo::Alive(new_info)); - } - } - } - - match total_imbalance { - // If we've detected a positive imbalance as a result of our contract-level machinations - // then it's indicative of a buggy contracts system. - // Panicking is far from ideal as it opens up a DoS attack on block validators, however - // it's a less bad option than allowing arbitrary value to be created. - SignedImbalance::Positive(ref p) if !p.peek().is_zero() => { - panic!("contract subsystem resulting in positive imbalance!") - } - _ => {} - } - } -} - -pub struct OverlayAccountDb<'a, T: Trait + 'a> { - local: RefCell>, - underlying: &'a dyn AccountDb, -} -impl<'a, T: Trait> OverlayAccountDb<'a, T> { - pub fn new(underlying: &'a dyn AccountDb) -> OverlayAccountDb<'a, T> { - OverlayAccountDb { - local: RefCell::new(ChangeSet::new()), - underlying, - } - } - - pub fn into_change_set(self) -> ChangeSet { - self.local.into_inner() - } - - pub fn set_storage( - &mut self, - account: &T::AccountId, - location: StorageKey, - value: Option>, - ) { - self.local - .borrow_mut() - .entry(account.clone()) - .or_insert(Default::default()) - .storage - .insert(location, value); - } - - /// Return an error if contract already exists (either if it is alive or tombstone) - pub fn instantiate_contract( - &mut self, - account: &T::AccountId, - code_hash: CodeHash, - ) -> Result<(), &'static str> { - if self.contract_exists(account) { - return Err("Alive contract or tombstone already exists"); - } - - let mut local = self.local.borrow_mut(); - let contract = local.entry(account.clone()).or_default(); - - contract.code_hash = Some(code_hash); - contract.rent_allowance = Some(>::max_value()); - - 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) { - self.local - .borrow_mut() - .entry(account.clone()) - .or_insert(Default::default()) - .rent_allowance = Some(rent_allowance); - } - pub fn set_balance(&mut self, account: &T::AccountId, balance: BalanceOf) { - self.local - .borrow_mut() - .entry(account.clone()) - .or_insert(Default::default()) - .balance = Some(balance); - } -} - -impl<'a, T: Trait> AccountDb for OverlayAccountDb<'a, T> { - fn get_storage( - &self, - account: &T::AccountId, - trie_id: Option<&TrieId>, - location: &StorageKey, - ) -> Option> { - self.local - .borrow() - .get(account) - .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> { - self.local - .borrow() - .get(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> { - self.local - .borrow() - .get(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) - .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 { - self.local - .borrow() - .get(account) - .and_then(|changes| changes.balance()) - .unwrap_or_else(|| self.underlying.get_balance(account)) - } - fn commit(&mut self, s: ChangeSet) { - let mut local = self.local.borrow_mut(); - - for (address, changed) in s.into_iter() { - match local.entry(address) { - Entry::Occupied(e) => { - let mut value = e.into_mut(); - 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); - } - } - } - } -} From 57aa37ce281be4299551bedc21b124131f578ffe Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Thu, 11 Jun 2020 15:43:32 +0200 Subject: [PATCH 13/33] Drop the result. --- frame/contracts/fixtures/restoration.wat | 37 ++++++++++++++---------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/frame/contracts/fixtures/restoration.wat b/frame/contracts/fixtures/restoration.wat index 4e11f97d5a2cc..d73a13f1d9e8e 100644 --- a/frame/contracts/fixtures/restoration.wat +++ b/frame/contracts/fixtures/restoration.wat @@ -1,23 +1,30 @@ (module (import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32))) - (import "env" "ext_restore_to" (func $ext_restore_to (param i32 i32 i32 i32 i32 i32 i32 i32))) + (import "env" "ext_restore_to" + (func $ext_restore_to + (param i32 i32 i32 i32 i32 i32 i32 i32) + (result i32) + ) + ) (import "env" "memory" (memory 1 1)) (func (export "call") - (call $ext_restore_to - ;; Pointer and length of the encoded dest buffer. - (i32.const 256) - (i32.const 8) - ;; Pointer and length of the encoded code hash buffer - (i32.const 264) - (i32.const 32) - ;; Pointer and length of the encoded rent_allowance buffer - (i32.const 296) - (i32.const 8) - ;; Pointer and number of items in the delta buffer. - ;; This buffer specifies multiple keys for removal before restoration. - (i32.const 100) - (i32.const 1) + (drop + (call $ext_restore_to + ;; Pointer and length of the encoded dest buffer. + (i32.const 256) + (i32.const 8) + ;; Pointer and length of the encoded code hash buffer + (i32.const 264) + (i32.const 32) + ;; Pointer and length of the encoded rent_allowance buffer + (i32.const 296) + (i32.const 8) + ;; Pointer and number of items in the delta buffer. + ;; This buffer specifies multiple keys for removal before restoration. + (i32.const 100) + (i32.const 1) + ) ) ) (func (export "deploy") From 73dce0c442caeee97de0dc08d6d6c3c57dba5ac5 Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Thu, 11 Jun 2020 16:52:21 +0200 Subject: [PATCH 14/33] Backport the book keeping. --- frame/contracts/src/storage.rs | 87 ++++++++++++++++++++++++---------- 1 file changed, 61 insertions(+), 26 deletions(-) diff --git a/frame/contracts/src/storage.rs b/frame/contracts/src/storage.rs index 13d6ef7ea0fb9..f7c22752f0c03 100644 --- a/frame/contracts/src/storage.rs +++ b/frame/contracts/src/storage.rs @@ -31,39 +31,74 @@ pub fn write_contract_storage( account: &AccountIdOf, trie_id: &TrieId, key: &StorageKey, - value: Option>, + opt_new_value: Option>, ) { - let hashed_key = blake2_256(key); + let mut new_info = match >::get(account) { + Some(ContractInfo::Alive(alive)) => alive, + None | Some(ContractInfo::Tombstone(_)) => panic!(), // TODO: Justify this + }; + let hashed_key = blake2_256(key); let child_trie_info = &crate::child_trie_info(&trie_id); - // We need to accurately track the size of the storage of the contract. + // In order to correctly update the book keeping we need to fetch the previous + // value of the key-value pair. // - // For that we query the previous value and get its size. - let existing_size = child::get_raw(&child_trie_info, &hashed_key) - .map(|v| v.len()) - .unwrap_or(0); - let new_size = match value { - Some(v) => { - child::put_raw(&child_trie_info, &hashed_key, &v); - v.len() - } - None => { - child::kill(&child_trie_info, &hashed_key); - 0 - } - }; - - >::mutate(account, |maybe_contract_info| { - match maybe_contract_info { - Some(ContractInfo::Alive(ref mut alive_info)) => { - alive_info.storage_size -= existing_size as u32; - alive_info.storage_size += new_size as u32; - alive_info.last_write = Some(>::block_number()); + // It might be a bit more clean if we had an API that supported getting the size + // of the value without going through the loading of it. But at the moment of + // writing, there is no such API. + // + // That's not a show stopper in any case, since the performance cost is + // dominated by the trie traversal anyway. + let opt_prev_value = child::get_raw(&child_trie_info, &hashed_key); + + // Update the total number of KV pairs and the number of empty pairs. + match (&opt_prev_value, &opt_new_value) { + (Some(prev_value), None) => { + new_info.total_pair_count -= 1; + if prev_value.is_empty() { + new_info.empty_pair_count -= 1; + } + }, + (None, Some(new_value)) => { + new_info.total_pair_count += 1; + if new_value.is_empty() { + new_info.empty_pair_count += 1; + } + }, + (Some(prev_value), Some(new_value)) => { + if prev_value.is_empty() { + new_info.empty_pair_count -= 1; + } + if new_value.is_empty() { + new_info.empty_pair_count += 1; } - _ => panic!(), // TODO: Justify this. } - }); + (None, None) => {} + } + + // Update the total storage size. + let prev_value_len = opt_prev_value + .as_ref() + .map(|old_value| old_value.len() as u32) + .unwrap_or(0); + let new_value_len = opt_new_value + .as_ref() + .map(|new_value| new_value.len() as u32) + .unwrap_or(0); + new_info.storage_size = new_info + .storage_size + .saturating_add(new_value_len) + .saturating_sub(prev_value_len); + + new_info.last_write = Some(>::block_number()); + >::insert(&account, ContractInfo::Alive(new_info)); + + // Finally, perform the change on the storage. + match opt_new_value { + Some(new_value) => child::put_raw(&child_trie_info, &hashed_key, &new_value[..]), + None => child::kill(&child_trie_info, &hashed_key), + } } pub fn rent_allowance(account: &AccountIdOf) -> Option> { From 571ea1fafedda7e7e17220505f0a8241564d1d25 Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Thu, 11 Jun 2020 19:08:11 +0200 Subject: [PATCH 15/33] Fix all remaining tests. --- Cargo.lock | 1 + frame/contracts/Cargo.toml | 1 + frame/contracts/src/exec.rs | 52 ++++++++++++++++-------------------- frame/contracts/src/tests.rs | 39 +++++++++++++++++++++++---- 4 files changed, 59 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 930cb554c7df5..859a84cf6803c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4009,6 +4009,7 @@ dependencies = [ "pallet-transaction-payment", "parity-scale-codec", "parity-wasm 0.41.0", + "pretty_assertions", "pwasm-utils", "serde", "sp-core", diff --git a/frame/contracts/Cargo.toml b/frame/contracts/Cargo.toml index b2ba8d014ae78..57c278a3fb2a9 100644 --- a/frame/contracts/Cargo.toml +++ b/frame/contracts/Cargo.toml @@ -31,6 +31,7 @@ pallet-transaction-payment = { version = "2.0.0-rc3", default-features = false, wabt = "0.9.2" assert_matches = "1.3.0" hex-literal = "0.2.1" +pretty_assertions = "0.6.1" pallet-balances = { version = "2.0.0-rc3", path = "../balances" } pallet-timestamp = { version = "2.0.0-rc3", path = "../timestamp" } pallet-randomness-collective-flip = { version = "2.0.0-rc3", path = "../randomness-collective-flip" } diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 11ffff899b374..cdbb44b236aeb 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -415,28 +415,6 @@ where input_data, gas_meter, )?; - - // TODO: Handle the case when after returning from the nested call the balance - // is below existential deposit. - - // Destroy contract if insufficient remaining balance. - if T::Currency::free_balance(&dest) < nested.config.existential_deposit { - let caller = nested.caller - .expect("a nested execution context must have a parent; qed"); - if caller.is_live(&dest) { - return Err(ExecError { - reason: "contract cannot be destroyed during recursive execution".into(), - buffer: output.data, - }); - } - storage::destroy_contract::( - &dest, - nested.self_trie_id.as_ref().expect( - "a nested exexcution context must have trie id assgined; qed" - ) - ); - } - Ok(output) } None => Ok(ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() }), @@ -688,8 +666,12 @@ fn transfer<'a, T: Trait, V: Vm, L: Loader>( Err("not enough gas to pay transfer fee")? } - // Make the transfer. - T::Currency::transfer(transactor, dest, value, ExistenceRequirement::AllowDeath)?; + // Only ext_terminate is allowed to bring the sender below the existential deposit + let existence_requirement = match cause { + Terminate => ExistenceRequirement::AllowDeath, + _ => ExistenceRequirement::KeepAlive, + }; + T::Currency::transfer(transactor, dest, value, existence_requirement)?; Ok(()) } @@ -789,13 +771,25 @@ where rent_allowance: BalanceOf, delta: Vec, ) -> Result<(), &'static str> { - crate::rent::restore_to::( + let result = crate::rent::restore_to::( self.ctx.self_account.clone(), - dest, - code_hash, + dest.clone(), + code_hash.clone(), rent_allowance, delta, - ) + ); + // Deposit an event accordingly regardless if it succeeded. + deposit_event::( + vec![], + RawEvent::Restored( + self.ctx.self_account.clone(), + dest, + code_hash, + rent_allowance, + result.is_ok(), + ), + ); + result } fn address(&self) -> &T::AccountId { @@ -1250,7 +1244,7 @@ mod tests { assert_matches!( result, Err(ExecError { - reason: DispatchError::Other("balance too low to send value"), + reason: DispatchError::Module { message: Some("InsufficientBalance"), .. }, buffer: _, }) ); diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index 27542c4f2a5ad..05f6066a0acfe 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -82,7 +82,8 @@ pub mod test_utils { crate::storage::place_contract::(&address, trie_id, code_hash).unwrap() } pub fn set_balance(who: &u64, amount: u64) { - Balances::deposit_creating(who, amount); + let imbalance = Balances::deposit_creating(who, amount); + drop(imbalance); } pub fn get_balance(who: &u64) -> u64 { Balances::free_balance(who) @@ -399,7 +400,7 @@ fn instantiate_and_call_and_deposit_event() { vec![], ); - assert_eq!(System::events(), vec![ + pretty_assertions::assert_eq!(System::events(), vec![ EventRecord { phase: Phase::Initialization, event: MetaEvent::system(frame_system::RawEvent::NewAccount(1)), @@ -427,6 +428,13 @@ fn instantiate_and_call_and_deposit_event() { ), topics: vec![], }, + EventRecord { + phase: Phase::Initialization, + event: MetaEvent::balances( + pallet_balances::RawEvent::Transfer(ALICE, BOB, 100) + ), + topics: vec![], + }, EventRecord { phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::ContractExecution(BOB, vec![1, 2, 3, 4])), @@ -497,7 +505,7 @@ fn dispatch_call() { vec![], )); - assert_eq!(System::events(), vec![ + pretty_assertions::assert_eq!(System::events(), vec![ EventRecord { phase: Phase::Initialization, event: MetaEvent::system(frame_system::RawEvent::NewAccount(1)), @@ -525,6 +533,13 @@ fn dispatch_call() { ), topics: vec![], }, + EventRecord { + phase: Phase::Initialization, + event: MetaEvent::balances( + pallet_balances::RawEvent::Transfer(ALICE, BOB, 100) + ), + topics: vec![], + }, EventRecord { phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::Instantiated(ALICE, BOB)), @@ -619,7 +634,7 @@ fn dispatch_call_not_dispatched_after_top_level_transaction_failure() { ), "contract trapped during execution" ); - assert_eq!(System::events(), vec![ + pretty_assertions::assert_eq!(System::events(), vec![ EventRecord { phase: Phase::Initialization, event: MetaEvent::system(frame_system::RawEvent::NewAccount(1)), @@ -647,6 +662,13 @@ fn dispatch_call_not_dispatched_after_top_level_transaction_failure() { ), topics: vec![], }, + EventRecord { + phase: Phase::Initialization, + event: MetaEvent::balances( + pallet_balances::RawEvent::Transfer(ALICE, BOB, 100) + ), + topics: vec![], + }, EventRecord { phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::Instantiated(ALICE, BOB)), @@ -1405,7 +1427,7 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: ]); } (_, true) => { - assert_eq!(System::events(), vec![ + pretty_assertions::assert_eq!(System::events(), vec![ EventRecord { phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::Evicted(BOB, true)), @@ -1431,6 +1453,13 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: event: MetaEvent::balances(pallet_balances::RawEvent::Endowed(DJANGO, 30_000)), topics: vec![], }, + EventRecord { + phase: Phase::Initialization, + event: MetaEvent::balances( + pallet_balances::RawEvent::Transfer(CHARLIE, DJANGO, 30_000) + ), + topics: vec![], + }, EventRecord { phase: Phase::Initialization, event: MetaEvent::contracts(RawEvent::Instantiated(CHARLIE, DJANGO)), From f68bff93573c8dc1933c9c607a5b50ecf6eb7a1a Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Fri, 12 Jun 2020 15:22:18 +0200 Subject: [PATCH 16/33] Make it compile for std --- frame/contracts/src/rent.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/contracts/src/rent.rs b/frame/contracts/src/rent.rs index 329e59e860dd8..6afd85aa8eb42 100644 --- a/frame/contracts/src/rent.rs +++ b/frame/contracts/src/rent.rs @@ -20,6 +20,8 @@ use crate::{ AliveContractInfo, BalanceOf, ContractInfo, ContractInfoOf, Module, RawEvent, TombstoneContractInfo, Trait, CodeHash, }; +use sp_std::prelude::*; +use sp_io::hashing::blake2_256; use frame_support::storage::child; use frame_support::traits::{Currency, ExistenceRequirement, Get, OnUnbalanced, WithdrawReason}; use frame_support::StorageMap; @@ -415,8 +417,6 @@ pub fn restore_to( rent_allowance: BalanceOf, delta: Vec, ) -> Result<(), &'static str> { - use sp_core::blake2_256; - let mut origin_contract = >::get(&origin) .and_then(|c| c.get_alive()) .ok_or("Cannot restore from inexisting or tombstone contract")?; From d3d2351141869a5dd8e17bf828bf3d4dc1b1968d Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Wed, 17 Jun 2020 13:05:15 +0200 Subject: [PATCH 17/33] Remove a stale TODO marker --- frame/contracts/src/exec.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index cdbb44b236aeb..794788786908c 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -703,15 +703,10 @@ where } fn set_storage(&mut self, key: StorageKey, value: Option>) -> Result<(), &'static str> { - if let Some(ref value) = value { - // TODO: Move this earlier to runtime.rs? This way the buffer won't get loaded. - if self.max_value_size() < value.len() as u32 { - return Err("value size exceeds maximum"); - } - } - match self.ctx.self_trie_id { - Some(ref trie_id) => storage::write_contract_storage::(&self.ctx.self_account, trie_id, &key, value), + Some(ref trie_id) => { + storage::write_contract_storage::(&self.ctx.self_account, trie_id, &key, value); + } // TODO: My current understanding is that `self.ctx.self_trie_id` can be `None` only // for the top-level account, i.e. EOA. As such, it cannot issue any reads to the // storage, because it has no. Furthermore, EOA cannot have contract storage. From a2ff22305395a176c787f40f80410c83652f8483 Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Wed, 17 Jun 2020 13:16:43 +0200 Subject: [PATCH 18/33] Remove another stale TODO --- frame/contracts/src/exec.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 794788786908c..b53513a5d2132 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -497,8 +497,6 @@ where gas_meter, )?; - // TODO: Handle the case when after returning from the nested call the balance - // is below existential deposit. // Error out if insufficient remaining balance. if T::Currency::free_balance(&dest) < nested.config.existential_deposit { return Err(ExecError { From 0b4b7ac4a9b99170ca9c60942ca23c4ecfff4ad4 Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Wed, 17 Jun 2020 13:17:26 +0200 Subject: [PATCH 19/33] Add proof for `terminate` --- frame/contracts/src/exec.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index b53513a5d2132..69a742f0aeeac 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -514,7 +514,8 @@ where Ok((dest, output)) } - pub fn terminate( + /// Perform termination of the contract associated with the current context. + fn terminate( &mut self, beneficiary: &T::AccountId, gas_meter: &mut GasMeter, @@ -536,7 +537,14 @@ where value, self, )?; - storage::destroy_contract::(&self_id, self.self_trie_id.as_ref().expect("")); // TODO: Proof + let self_trie_id = self + .self_trie_id + .as_ref() + .expect("this function is only invoked by in the context of a contract;\ + a contract has a trie id;\ + this can't be None; qed" + ); + storage::destroy_contract::(&self_id, self_trie_id); Ok(()) } @@ -573,7 +581,6 @@ where (output, nested.deferred) }; if output.is_success() { - // self.overlay.commit(change_set); self.deferred.extend(deferred); } Ok(output) From 9755c6d14781059f1b2c8d77e720c709027d604a Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Wed, 17 Jun 2020 13:57:10 +0200 Subject: [PATCH 20/33] Remove a stale comment. --- frame/contracts/src/exec.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 69a742f0aeeac..f6ae2b09c7ca7 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -873,11 +873,6 @@ fn deposit_event( /// In these tests the VM/loader are mocked. Instead of dealing with wasm bytecode they use simple closures. /// This allows you to tackle executive logic more thoroughly without writing a /// wasm VM code. -/// -/// Because it's the executive layer: -/// -/// - no gas meter setup and teardown logic. All balances are *AFTER* gas purchase. -/// - executive layer doesn't alter any storage! #[cfg(test)] mod tests { use super::{ From 164a7675410c63886276de37041f0b95bb8767ac Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Wed, 17 Jun 2020 14:26:34 +0200 Subject: [PATCH 21/33] Make restoration diverging. --- frame/contracts/fixtures/restoration.wat | 31 +++++++-------- frame/contracts/src/exec.rs | 47 ++++++++++++++++------ frame/contracts/src/lib.rs | 5 +-- frame/contracts/src/storage.rs | 22 +++++++---- frame/contracts/src/tests.rs | 50 +++++++++--------------- frame/contracts/src/wasm/runtime.rs | 16 ++++++-- 6 files changed, 97 insertions(+), 74 deletions(-) diff --git a/frame/contracts/fixtures/restoration.wat b/frame/contracts/fixtures/restoration.wat index d73a13f1d9e8e..225fdde817800 100644 --- a/frame/contracts/fixtures/restoration.wat +++ b/frame/contracts/fixtures/restoration.wat @@ -3,28 +3,25 @@ (import "env" "ext_restore_to" (func $ext_restore_to (param i32 i32 i32 i32 i32 i32 i32 i32) - (result i32) ) ) (import "env" "memory" (memory 1 1)) (func (export "call") - (drop - (call $ext_restore_to - ;; Pointer and length of the encoded dest buffer. - (i32.const 256) - (i32.const 8) - ;; Pointer and length of the encoded code hash buffer - (i32.const 264) - (i32.const 32) - ;; Pointer and length of the encoded rent_allowance buffer - (i32.const 296) - (i32.const 8) - ;; Pointer and number of items in the delta buffer. - ;; This buffer specifies multiple keys for removal before restoration. - (i32.const 100) - (i32.const 1) - ) + (call $ext_restore_to + ;; Pointer and length of the encoded dest buffer. + (i32.const 256) + (i32.const 8) + ;; Pointer and length of the encoded code hash buffer + (i32.const 264) + (i32.const 32) + ;; Pointer and length of the encoded rent_allowance buffer + (i32.const 296) + (i32.const 8) + ;; Pointer and number of items in the delta buffer. + ;; This buffer specifies multiple keys for removal before restoration. + (i32.const 100) + (i32.const 1) ) ) (func (export "deploy") diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index f6ae2b09c7ca7..4c211ee59a72a 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -129,6 +129,9 @@ pub trait Ext { ) -> Result<(), DispatchError>; /// Transfer all funds to `beneficiary` and delete the contract. + /// + /// Since this function removes the self contract eagerly, no further actions should be performed + /// on this `Ext` instance. fn terminate( &mut self, beneficiary: &AccountIdOf, @@ -148,6 +151,9 @@ pub trait Ext { fn note_dispatch_call(&mut self, call: CallOf); /// Restores the given destination contract sacrificing the current one. + /// + /// Since this function removes the self contract eagerly, no further actions should be performed + /// on this `Ext` instance. fn restore_to( &mut self, dest: AccountIdOf, @@ -681,6 +687,17 @@ fn transfer<'a, T: Trait, V: Vm, L: Loader>( Ok(()) } +/// A context that is active within a call. +/// +/// This context has some invariants that must be held at all times. Specifically: +///`ctx` always points to a context of an alive contract. That implies that it has an existent +/// `self_trie_id`. +/// +/// Be advised that there are brief time spans where these invariants could be invalidated. +/// For example, when a contract requests self-termination the contract is removed eagerly. That +/// implies that the control won't be returned to the contract anymore, but there is still some code +/// on the path of the return from that call context. Therefore, take must be taken in these +/// situations. struct CallContext<'a, 'b: 'a, T: Trait + 'b, V: Vm + 'b, L: Loader> { ctx: &'a mut ExecutionContext<'b, T, V, L>, caller: T::AccountId, @@ -778,17 +795,17 @@ where rent_allowance, delta, ); - // Deposit an event accordingly regardless if it succeeded. - deposit_event::( - vec![], - RawEvent::Restored( - self.ctx.self_account.clone(), - dest, - code_hash, - rent_allowance, - result.is_ok(), - ), - ); + if let Ok(_) = result { + deposit_event::( + vec![], + RawEvent::Restored( + self.ctx.self_account.clone(), + dest, + code_hash, + rent_allowance, + ), + ); + } result } @@ -832,7 +849,13 @@ where } fn set_rent_allowance(&mut self, rent_allowance: BalanceOf) { - storage::set_rent_allowance::(&self.ctx.self_account, rent_allowance); + match storage::set_rent_allowance::(&self.ctx.self_account, rent_allowance) { + Ok(()) => {}, + Err(storage::AllowanceSetError) => { + panic!("`self_account` points to an alive contract within the `CallContext`; + set_rent_allowance cannot return `Err`; qed"); + } + } } fn rent_allowance(&self) -> BalanceOf { diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index a5aef1596dba9..e656949b251ff 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -695,7 +695,7 @@ decl_event! { /// - `tombstone`: `bool`: True if the evicted contract left behind a tombstone. Evicted(AccountId, bool), - /// Restoration for a contract has been initiated. + /// Restoration for a contract has been successful. /// /// # Params /// @@ -703,8 +703,7 @@ decl_event! { /// - `dest`: `AccountId`: Account ID of the restored contract /// - `code_hash`: `Hash`: Code hash of the restored contract /// - `rent_allowance: `Balance`: Rent allowance of the restored contract - /// - `success`: `bool`: True if the restoration was successful - Restored(AccountId, AccountId, Hash, Balance, bool), + Restored(AccountId, AccountId, Hash, Balance), /// Code with the specified hash has been stored. CodeStored(Hash), diff --git a/frame/contracts/src/storage.rs b/frame/contracts/src/storage.rs index f7c22752f0c03..2f23ff2e6f900 100644 --- a/frame/contracts/src/storage.rs +++ b/frame/contracts/src/storage.rs @@ -105,14 +105,22 @@ pub fn rent_allowance(account: &AccountIdOf) -> Option >::get(account).and_then(|i| i.as_alive().map(|i| i.rent_allowance)) } -pub fn set_rent_allowance(account: &AccountIdOf, rent_allowance: BalanceOf) { - >::mutate(account, |maybe_contract_info| { - match maybe_contract_info { - Some(ContractInfo::Alive(ref mut alive_info)) => { - alive_info.rent_allowance = rent_allowance - } - _ => panic!(), // TODO: Justify this. +/// An error returned if `set_rent_allowance` was called on an account which doesn't have a contract. +pub struct AllowanceSetError; + +/// Set the rent allowance for the contract given by the account id. +/// +/// Returns `Err` if the contract is not alive. +pub fn set_rent_allowance( + account: &AccountIdOf, + rent_allowance: BalanceOf, +) -> Result<(), AllowanceSetError> { + >::mutate(account, |maybe_contract_info| match maybe_contract_info { + Some(ContractInfo::Alive(ref mut alive_info)) => { + alive_info.rent_allowance = rent_allowance; + Ok(()) } + _ => Err(AllowanceSetError), }) } diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index 05f6066a0acfe..0d07f92405b40 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -1353,9 +1353,6 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: // Advance 4 blocks, to the 5th. initialize_block(5); - // Preserve `BOB`'s code hash for later introspection. - let bob_code_hash = ContractInfoOf::::get(BOB).unwrap() - .get_alive().unwrap().code_hash; // Call `BOB`, which makes it pay rent. Since the rent allowance is set to 0 // we expect that it will get removed leaving tombstone. assert_err_ignore_postinfo!( @@ -1397,17 +1394,25 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: // Perform a call to `DJANGO`. This should either perform restoration successfully or // fail depending on the test parameters. - assert_ok!(Contracts::call( - Origin::signed(ALICE), - DJANGO, - 0, - GAS_LIMIT, - vec![], - )); + let perform_the_restoration = || { + Contracts::call( + Origin::signed(ALICE), + DJANGO, + 0, + GAS_LIMIT, + vec![], + ) + }; if test_different_storage || test_restore_to_with_dirty_storage { // Parametrization of the test imply restoration failure. Check that `DJANGO` aka // restoration contract is still in place and also that `BOB` doesn't exist. + + assert_err_ignore_postinfo!( + perform_the_restoration(), + "contract trapped during execution" + ); + assert!(ContractInfoOf::::get(BOB).unwrap().get_tombstone().is_some()); let django_contract = ContractInfoOf::::get(DJANGO).unwrap() .get_alive().unwrap(); @@ -1416,15 +1421,7 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: assert_eq!(django_contract.deduct_block, System::block_number()); match (test_different_storage, test_restore_to_with_dirty_storage) { (true, false) => { - assert_eq!(System::events(), vec![ - EventRecord { - phase: Phase::Initialization, - event: MetaEvent::contracts( - RawEvent::Restored(DJANGO, BOB, bob_code_hash, 50, false) - ), - topics: vec![], - }, - ]); + assert_eq!(System::events(), vec![]); } (_, true) => { pretty_assertions::assert_eq!(System::events(), vec![ @@ -1465,22 +1462,13 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: event: MetaEvent::contracts(RawEvent::Instantiated(CHARLIE, DJANGO)), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: MetaEvent::contracts(RawEvent::Restored( - DJANGO, - BOB, - bob_code_hash, - 50, - false, - )), - topics: vec![], - }, ]); } _ => unreachable!(), } } else { + assert_ok!(perform_the_restoration()); + // Here we expect that the restoration is succeeded. Check that the restoration // contract `DJANGO` ceased to exist and that `BOB` returned back. println!("{:?}", ContractInfoOf::::get(BOB)); @@ -1500,7 +1488,7 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: EventRecord { phase: Phase::Initialization, event: MetaEvent::contracts( - RawEvent::Restored(DJANGO, BOB, bob_contract.code_hash, 50, true) + RawEvent::Restored(DJANGO, BOB, bob_contract.code_hash, 50) ), topics: vec![], }, diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 147f524fde4b6..2e860de8b4ddf 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -51,6 +51,8 @@ enum SpecialTrap { /// Signals that a trap was generated in response to a succesful call to the /// `ext_terminate` host function. Termination, + /// Signals that a trap was generated because of a successful restoration. + Restoration, } /// Can only be used for one call. @@ -100,6 +102,12 @@ pub(crate) fn to_execution_result( data: Vec::new(), }) }, + Some(SpecialTrap::Restoration) => { + return Ok(ExecReturnValue { + status: STATUS_SUCCESS, + data: Vec::new(), + }) + } Some(SpecialTrap::OutOfGas) => { return Err(ExecError { reason: "ran out of gas during contract execution".into(), @@ -830,7 +838,7 @@ define_env!(Env, , rent_allowance_len: u32, delta_ptr: u32, delta_count: u32 - ) -> u32 => { + ) => { let dest: <::T as frame_system::Trait>::AccountId = read_sandbox_memory_as(ctx, dest_ptr, dest_len)?; let code_hash: CodeHash<::T> = @@ -858,15 +866,15 @@ define_env!(Env, , delta }; - match ctx.ext.restore_to( + if let Ok(()) = ctx.ext.restore_to( dest, code_hash, rent_allowance, delta, ) { - Ok(_) => Ok(0), - Err(_) => Ok(1), + ctx.special_trap = Some(SpecialTrap::Restoration); } + Err(sp_sandbox::HostError) }, // Returns the size of the scratch buffer. From b21f66f3907d8b684d161c111d994f733a30758d Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Fri, 19 Jun 2020 14:15:25 +0200 Subject: [PATCH 22/33] Remove redudnant trait: `ComputeDispatchFee` --- frame/contracts/src/lib.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index e656949b251ff..7ae330d53f12d 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -126,11 +126,6 @@ pub trait ContractAddressFor { fn contract_address_for(code_hash: &CodeHash, data: &[u8], origin: &AccountId) -> AccountId; } -/// A function that returns the fee for dispatching a `Call`. -pub trait ComputeDispatchFee { - fn compute_dispatch_fee(call: &Call) -> Balance; -} - /// Information for managing an account and its sub trie abstraction. /// This is the required info to cache for an account #[derive(Encode, Decode, RuntimeDebug)] From 04ca866c1ec67e7494ac7c18e97487c29b7617b8 Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Fri, 19 Jun 2020 14:35:53 +0200 Subject: [PATCH 23/33] Update frame/contracts/src/exec.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexander Theißen --- frame/contracts/src/exec.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 4c211ee59a72a..f33b71a86c3bd 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -696,7 +696,7 @@ fn transfer<'a, T: Trait, V: Vm, L: Loader>( /// Be advised that there are brief time spans where these invariants could be invalidated. /// For example, when a contract requests self-termination the contract is removed eagerly. That /// implies that the control won't be returned to the contract anymore, but there is still some code -/// on the path of the return from that call context. Therefore, take must be taken in these +/// on the path of the return from that call context. Therefore, care must be taken in these /// situations. struct CallContext<'a, 'b: 'a, T: Trait + 'b, V: Vm + 'b, L: Loader> { ctx: &'a mut ExecutionContext<'b, T, V, L>, From cefce046ab786a9dcdaa569339e40488ecf352e5 Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Fri, 19 Jun 2020 15:14:41 +0200 Subject: [PATCH 24/33] Introduce proper errors into the storage module. --- frame/contracts/src/exec.rs | 49 ++++++++++++++++------------- frame/contracts/src/lib.rs | 12 +------ frame/contracts/src/storage.rs | 34 +++++++++++++------- frame/contracts/src/tests.rs | 2 +- frame/contracts/src/wasm/mod.rs | 9 ++---- frame/contracts/src/wasm/runtime.rs | 4 +-- 6 files changed, 56 insertions(+), 54 deletions(-) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index f33b71a86c3bd..987f72baebf05 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -105,8 +105,8 @@ pub trait Ext { fn get_storage(&self, key: &StorageKey) -> Option>; /// Sets the storage entry by the given key to the specified value. If `value` is `None` then - /// the storage entry is deleted. Returns an Err if the value size is too large. - fn set_storage(&mut self, key: StorageKey, value: Option>) -> Result<(), &'static str>; + /// the storage entry is deleted. + fn set_storage(&mut self, key: StorageKey, value: Option>); /// Instantiate a contract from the given code. /// @@ -409,7 +409,7 @@ where // If code_hash is not none, then the destination account is a live contract, otherwise // it is a regular account since tombstone accounts have already been rejected. match storage::code_hash::(&dest) { - Some(dest_code_hash) => { + Ok(dest_code_hash) => { let executable = try_or_exec_error!( nested.loader.load_main(&dest_code_hash), input_data @@ -423,7 +423,7 @@ where )?; Ok(output) } - None => Ok(ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() }), + Err(storage::ContractAbsentError) => Ok(ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() }), } }) } @@ -724,17 +724,23 @@ where } } - fn set_storage(&mut self, key: StorageKey, value: Option>) -> Result<(), &'static str> { - match self.ctx.self_trie_id { - Some(ref trie_id) => { - storage::write_contract_storage::(&self.ctx.self_account, trie_id, &key, value); - } - // TODO: My current understanding is that `self.ctx.self_trie_id` can be `None` only - // for the top-level account, i.e. EOA. As such, it cannot issue any reads to the - // storage, because it has no. Furthermore, EOA cannot have contract storage. - None => unimplemented!(), + fn set_storage(&mut self, key: StorageKey, value: Option>) { + let trie_id = self.ctx.self_trie_id.as_ref().expect( + "`ctx.self_trie_id` points to an alive contract within the `CallContext`;\ + it cannot be `None`;\ + expect can't fail;\ + qed", + ); + if let Err(storage::ContractAbsentError) = + storage::write_contract_storage::(&self.ctx.self_account, trie_id, &key, value) + { + panic!( + "the contract must be in the alive state within the `CallContext`;\ + the contract cannot be absent in storage; + write_contract_storage cannot return `None`; + qed" + ); } - Ok(()) } fn instantiate( @@ -849,12 +855,13 @@ where } fn set_rent_allowance(&mut self, rent_allowance: BalanceOf) { - match storage::set_rent_allowance::(&self.ctx.self_account, rent_allowance) { - Ok(()) => {}, - Err(storage::AllowanceSetError) => { - panic!("`self_account` points to an alive contract within the `CallContext`; - set_rent_allowance cannot return `Err`; qed"); - } + if let Err(storage::ContractAbsentError) = + storage::set_rent_allowance::(&self.ctx.self_account, rent_allowance) + { + panic!( + "`self_account` points to an alive contract within the `CallContext`; + set_rent_allowance cannot return `Err`; qed" + ); } } @@ -1602,7 +1609,7 @@ mod tests { ); // Check that the account has not been created. - assert!(storage::code_hash::(&instantiated_contract_address).is_none()); + assert!(storage::code_hash::(&instantiated_contract_address).is_err()); assert!(events().is_empty()); }); } diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 7ae330d53f12d..c12029a856c9d 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -629,7 +629,7 @@ impl Module { fn execute_wasm( origin: T::AccountId, gas_meter: &mut GasMeter, - func: impl FnOnce(&mut ExecutionContext, &mut GasMeter) -> ExecResult + func: impl FnOnce(&mut ExecutionContext, &mut GasMeter) -> ExecResult, ) -> ExecResult { let cfg = Config::preload(); let vm = WasmVm::new(&cfg.schedule); @@ -638,16 +638,6 @@ impl Module { let result = func(&mut ctx, gas_meter); - if result.as_ref().map(|output| output.is_success()).unwrap_or(false) { - // TODO: Audit this. - // - // We no longer need to commit now, since all changes are committed eagearly, but - // rollbacked on error. - - // Commit all changes that made it thus far into the persistent storage. - // DirectAccountDb.commit(ctx.overlay.into_change_set()); - } - // Execute deferred actions. ctx.deferred.into_iter().for_each(|deferred| { use self::exec::DeferredAction::*; diff --git a/frame/contracts/src/storage.rs b/frame/contracts/src/storage.rs index 2f23ff2e6f900..d2d943c013396 100644 --- a/frame/contracts/src/storage.rs +++ b/frame/contracts/src/storage.rs @@ -23,6 +23,11 @@ use sp_io::hashing::blake2_256; use sp_runtime::traits::Bounded; use frame_support::{storage::child, StorageMap}; +/// An error that means that the account requested either doesn't exist or represents a tombstone +/// account. +#[cfg_attr(test, derive(PartialEq, Eq, Debug))] +pub struct ContractAbsentError; + pub fn read_contract_storage(trie_id: &TrieId, key: &StorageKey) -> Option> { child::get_raw(&crate::child_trie_info(&trie_id), &blake2_256(key)) } @@ -32,10 +37,10 @@ pub fn write_contract_storage( trie_id: &TrieId, key: &StorageKey, opt_new_value: Option>, -) { +) -> Result<(), ContractAbsentError> { let mut new_info = match >::get(account) { Some(ContractInfo::Alive(alive)) => alive, - None | Some(ContractInfo::Tombstone(_)) => panic!(), // TODO: Justify this + None | Some(ContractInfo::Tombstone(_)) => return Err(ContractAbsentError), }; let hashed_key = blake2_256(key); @@ -99,33 +104,38 @@ pub fn write_contract_storage( Some(new_value) => child::put_raw(&child_trie_info, &hashed_key, &new_value[..]), None => child::kill(&child_trie_info, &hashed_key), } -} -pub fn rent_allowance(account: &AccountIdOf) -> Option> { - >::get(account).and_then(|i| i.as_alive().map(|i| i.rent_allowance)) + Ok(()) } -/// An error returned if `set_rent_allowance` was called on an account which doesn't have a contract. -pub struct AllowanceSetError; +pub fn rent_allowance( + account: &AccountIdOf, +) -> Result, ContractAbsentError> { + >::get(account) + .and_then(|i| i.as_alive().map(|i| i.rent_allowance)) + .ok_or(ContractAbsentError) +} /// Set the rent allowance for the contract given by the account id. /// -/// Returns `Err` if the contract is not alive. +/// Returns `Err` if the contract doesn't exist or is a tombstone. pub fn set_rent_allowance( account: &AccountIdOf, rent_allowance: BalanceOf, -) -> Result<(), AllowanceSetError> { +) -> Result<(), ContractAbsentError> { >::mutate(account, |maybe_contract_info| match maybe_contract_info { Some(ContractInfo::Alive(ref mut alive_info)) => { alive_info.rent_allowance = rent_allowance; Ok(()) } - _ => Err(AllowanceSetError), + _ => Err(ContractAbsentError), }) } -pub fn code_hash(account: &AccountIdOf) -> Option> { - >::get(account).and_then(|i| i.as_alive().map(|i| i.code_hash)) +pub fn code_hash(account: &AccountIdOf) -> Result, ContractAbsentError> { + >::get(account) + .and_then(|i| i.as_alive().map(|i| i.code_hash)) + .ok_or(ContractAbsentError) } /// Creates a new contract descriptor in the storage with the given code hash at the given address. diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index 0d07f92405b40..df6afa8ac51e0 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -71,7 +71,7 @@ pub mod test_utils { pub fn set_storage(addr: &u64, key: &StorageKey, value: Option>) { let contract_info = >::get(&addr).unwrap().get_alive().unwrap(); - write_contract_storage::(&1, &contract_info.trie_id, key, value); + write_contract_storage::(&1, &contract_info.trie_id, key, value).unwrap(); } pub fn get_storage(addr: &u64, key: &StorageKey) -> Option> { let contract_info = >::get(&addr).unwrap().get_alive().unwrap(); diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index 20a84324c437b..890915a793d46 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -229,11 +229,8 @@ mod tests { fn get_storage(&self, key: &StorageKey) -> Option> { self.storage.get(key).cloned() } - fn set_storage(&mut self, key: StorageKey, value: Option>) - -> Result<(), &'static str> - { + fn set_storage(&mut self, key: StorageKey, value: Option>) { *self.storage.entry(key).or_insert(Vec::new()) = value.unwrap_or(Vec::new()); - Ok(()) } fn instantiate( &mut self, @@ -387,9 +384,7 @@ mod tests { fn get_storage(&self, key: &[u8; 32]) -> Option> { (**self).get_storage(key) } - fn set_storage(&mut self, key: [u8; 32], value: Option>) - -> Result<(), &'static str> - { + fn set_storage(&mut self, key: [u8; 32], value: Option>) { (**self).set_storage(key, value) } fn instantiate( diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 2e860de8b4ddf..14ecdc51491d3 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -395,7 +395,7 @@ define_env!(Env, , let mut key: StorageKey = [0; 32]; read_sandbox_memory_into_buf(ctx, key_ptr, &mut key)?; let value = Some(read_sandbox_memory(ctx, value_ptr, value_len)?); - ctx.ext.set_storage(key, value).map_err(|_| sp_sandbox::HostError)?; + ctx.ext.set_storage(key, value); Ok(()) }, @@ -407,7 +407,7 @@ define_env!(Env, , ext_clear_storage(ctx, key_ptr: u32) => { let mut key: StorageKey = [0; 32]; read_sandbox_memory_into_buf(ctx, key_ptr, &mut key)?; - ctx.ext.set_storage(key, None).map_err(|_| sp_sandbox::HostError)?; + ctx.ext.set_storage(key, None); Ok(()) }, From d767cb9821b2dcf85ae45a04dcd508dc84afc681 Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Fri, 19 Jun 2020 15:55:35 +0200 Subject: [PATCH 25/33] Adds comments for contract storage module. --- frame/contracts/src/storage.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/frame/contracts/src/storage.rs b/frame/contracts/src/storage.rs index d2d943c013396..4c5ad892a967b 100644 --- a/frame/contracts/src/storage.rs +++ b/frame/contracts/src/storage.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . +//! This module contains routines for accessing and altering a contract related state. + use crate::{ exec::{AccountIdOf, StorageKey}, AliveContractInfo, BalanceOf, CodeHash, ContractInfo, ContractInfoOf, Trait, TrieId, @@ -28,10 +30,23 @@ use frame_support::{storage::child, StorageMap}; #[cfg_attr(test, derive(PartialEq, Eq, Debug))] pub struct ContractAbsentError; +/// Reads a storage kv pair of a contract. +/// +/// The read is performed from the `trie_id` only. The `address` is not necessary. If the contract +/// doesn't store under the given `key` `None` is returned. pub fn read_contract_storage(trie_id: &TrieId, key: &StorageKey) -> Option> { child::get_raw(&crate::child_trie_info(&trie_id), &blake2_256(key)) } +/// Update a storage entry into a contract's kv storage. +/// +/// If the `opt_new_value` is `None` then the kv pair is removed. +/// +/// This function also updates the bookkeeping info such as: number of total non-empty pairs a +/// contract owns, the last block the storage was written to, etc. That's why, in contrast to +/// `read_contract_storage`, this function also requires the `account` ID. +/// +/// If the contract specified by the id `account` doesn't exist `Err` is returned.` pub fn write_contract_storage( account: &AccountIdOf, trie_id: &TrieId, @@ -108,6 +123,7 @@ pub fn write_contract_storage( Ok(()) } +/// Returns the rent allowance set for the contract give by the account id. pub fn rent_allowance( account: &AccountIdOf, ) -> Result, ContractAbsentError> { @@ -132,6 +148,7 @@ pub fn set_rent_allowance( }) } +/// Returns the code hash of the contract specified by `account` ID. pub fn code_hash(account: &AccountIdOf) -> Result, ContractAbsentError> { >::get(account) .and_then(|i| i.as_alive().map(|i| i.code_hash)) @@ -169,6 +186,9 @@ pub fn place_contract( }) } +/// Removes the contract and all the storage associated with it. +/// +/// This function doesn't affect the account. pub fn destroy_contract(address: &AccountIdOf, trie_id: &TrieId) { >::remove(address); child::kill_storage(&crate::child_trie_info(&trie_id)); From 4166768a945e0e52e925d43c96f1cffe572aac3c Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Fri, 19 Jun 2020 17:03:39 +0200 Subject: [PATCH 26/33] Inline `ExecutionContext::terminate`. --- frame/contracts/src/exec.rs | 59 +++++++++++++++---------------------- 1 file changed, 24 insertions(+), 35 deletions(-) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 987f72baebf05..20bd1fad662ea 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -520,40 +520,6 @@ where Ok((dest, output)) } - /// Perform termination of the contract associated with the current context. - fn terminate( - &mut self, - beneficiary: &T::AccountId, - gas_meter: &mut GasMeter, - ) -> Result<(), DispatchError> { - let self_id = self.self_account.clone(); - let value = T::Currency::free_balance(&self_id); - if let Some(caller) = self.caller { - if caller.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, - )?; - let self_trie_id = self - .self_trie_id - .as_ref() - .expect("this function is only invoked by in the context of a contract;\ - a contract has a trie id;\ - this can't be None; qed" - ); - storage::destroy_contract::(&self_id, self_trie_id); - Ok(()) - } - fn new_call_context<'b>( &'b mut self, caller: T::AccountId, @@ -767,7 +733,30 @@ where beneficiary: &AccountIdOf, gas_meter: &mut GasMeter, ) -> Result<(), DispatchError> { - self.ctx.terminate(beneficiary, gas_meter) + let self_id = self.ctx.self_account.clone(); + let value = T::Currency::free_balance(&self_id); + if let Some(caller_ctx) = self.ctx.caller { + if caller_ctx.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.ctx, + )?; + let self_trie_id = self.ctx.self_trie_id.as_ref().expect( + "this function is only invoked by in the context of a contract;\ + a contract has a trie id;\ + this can't be None; qed", + ); + storage::destroy_contract::(&self_id, self_trie_id); + Ok(()) } fn call( From e54d6a92ef0373c175e073cf19431451df25617a Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Fri, 19 Jun 2020 17:04:24 +0200 Subject: [PATCH 27/33] Restore_to should not let sacrifice itself if the contract present on the stack. --- frame/contracts/src/exec.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 20bd1fad662ea..9d86530895721 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -131,7 +131,8 @@ pub trait Ext { /// Transfer all funds to `beneficiary` and delete the contract. /// /// Since this function removes the self contract eagerly, no further actions should be performed - /// on this `Ext` instance. + /// on this `Ext` instance. This function will fail if the same contract is present on the contract + /// call stack. fn terminate( &mut self, beneficiary: &AccountIdOf, @@ -153,7 +154,8 @@ pub trait Ext { /// Restores the given destination contract sacrificing the current one. /// /// Since this function removes the self contract eagerly, no further actions should be performed - /// on this `Ext` instance. + /// on this `Ext` instance. This function will fail if the same contract is present on the contract + /// call stack. fn restore_to( &mut self, dest: AccountIdOf, @@ -783,6 +785,14 @@ where rent_allowance: BalanceOf, delta: Vec, ) -> Result<(), &'static str> { + if let Some(caller_ctx) = self.ctx.caller { + if caller_ctx.is_live(&self.ctx.self_account) { + return Err( + "Cannot perform restoration of a contract that is present on the call stack", + ); + } + } + let result = crate::rent::restore_to::( self.ctx.self_account.clone(), dest.clone(), From 92b970639bb7e73dd0c86c2a4db637c314b5f6cb Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Fri, 19 Jun 2020 18:37:08 +0200 Subject: [PATCH 28/33] Inline `transfer` function --- frame/contracts/src/exec.rs | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 9d86530895721..44e4399ee7625 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -335,23 +335,6 @@ where } } - /// Transfer balance to `dest` without calling any contract code. - pub fn transfer( - &mut self, - dest: T::AccountId, - value: BalanceOf, - gas_meter: &mut GasMeter - ) -> Result<(), DispatchError> { - transfer( - gas_meter, - TransferCause::Call, - &self.self_account.clone(), - &dest, - value, - self, - ) - } - /// Make a call to the specified address, optionally transferring some funds. pub fn call( &mut self, @@ -727,7 +710,14 @@ where value: BalanceOf, gas_meter: &mut GasMeter, ) -> Result<(), DispatchError> { - self.ctx.transfer(to.clone(), value, gas_meter) + transfer( + gas_meter, + TransferCause::Call, + &self.ctx.self_account.clone(), + &to, + value, + self.ctx, + ) } fn terminate( From d34fad68781603b3dbc581a4ff53854571c0ed2f Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Fri, 19 Jun 2020 18:38:50 +0200 Subject: [PATCH 29/33] Update doc - add "if succeeded" --- frame/contracts/src/exec.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 44e4399ee7625..e24dfdfde9f13 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -130,8 +130,10 @@ pub trait Ext { /// Transfer all funds to `beneficiary` and delete the contract. /// - /// Since this function removes the self contract eagerly, no further actions should be performed - /// on this `Ext` instance. This function will fail if the same contract is present on the contract + /// Since this function removes the self contract eagerly, if succeeded, no further actions should + /// be performed on this `Ext` instance. + /// + /// This function will fail if the same contract is present on the contract /// call stack. fn terminate( &mut self, @@ -153,9 +155,11 @@ pub trait Ext { /// Restores the given destination contract sacrificing the current one. /// - /// Since this function removes the self contract eagerly, no further actions should be performed - /// on this `Ext` instance. This function will fail if the same contract is present on the contract - /// call stack. + /// Since this function removes the self contract eagerly, if succeeded, no further actions should + /// be performed on this `Ext` instance. + /// + /// This function will fail if the same contract is present + /// on the contract call stack. fn restore_to( &mut self, dest: AccountIdOf, From 4e58ce5d1bd2d25f5ab11091fe5ef2aa29a72885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Tue, 23 Jun 2020 11:46:13 +0200 Subject: [PATCH 30/33] Adapt to TransactionOutcome changes --- frame/contracts/src/exec.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index e24dfdfde9f13..d29b93d99a0c6 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -530,13 +530,14 @@ where -> ExecResult where F: FnOnce(&mut ExecutionContext) -> ExecResult { + use frame_support::storage::TransactionOutcome::*; let (output, deferred) = { let mut nested = self.nested(dest, trie_id); let output = frame_support::storage::with_transaction(|| { let output = func(&mut nested); match output { - Ok(ref rv) if rv.is_success() => (output, frame_support::storage::TransactionOutcome::Commit), - _ => (output, frame_support::storage::TransactionOutcome::Rollback), + Ok(ref rv) if rv.is_success() => Commit(output), + _ => Rollback(output), } })?; (output, nested.deferred) From f9e4674370e4cf5cc13b3f4d188caeecbd68a22b Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Tue, 23 Jun 2020 12:51:17 +0200 Subject: [PATCH 31/33] Updates the docs for `ext_restore_to` --- frame/contracts/src/wasm/runtime.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 14ecdc51491d3..f99fc0a7fc4c2 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -815,10 +815,11 @@ define_env!(Env, , // All caller's funds are transfered to the destination. // // If there is no tombstone at the destination address or if the hashes don't match - // then restoration is cancelled and no changes are made. In this case this function - // will return non-zero code. + // then restoration is cancelled and no changes are made. Additionally, this contract instance + // should be not present on the contract call stack. If any of these preconditions are not held, + // then a trap is generated. // - // Otherwise, If the restoration was performed correctly, 0 is returned. + // Otherwise, the contract execution is terminated and the destination contract is restored. // // `dest_ptr`, `dest_len` - the pointer and the length of a buffer that encodes `T::AccountId` // with the address of the to be restored contract. From 88934889233190b4e9a0148b7d08a1f194571672 Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Tue, 23 Jun 2020 12:57:15 +0200 Subject: [PATCH 32/33] Add a proper assert. --- frame/contracts/src/exec.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index d29b93d99a0c6..ff0d4d9dc0de1 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -671,13 +671,13 @@ where type T = T; fn get_storage(&self, key: &StorageKey) -> Option> { - match self.ctx.self_trie_id { - Some(ref trie_id) => storage::read_contract_storage(trie_id, key), - // TODO: My current understanding is that `self.ctx.self_trie_id` can be `None` only - // for the top-level account, i.e. EOA. As such, it cannot issue any reads to the - // storage, because it has no. Furthermore, EOA cannot have contract storage. - None => unimplemented!(), - } + let trie_id = self.ctx.self_trie_id.as_ref().expect( + "`ctx.self_trie_id` points to an alive contract within the `CallContext`;\ + it cannot be `None`;\ + expect can't fail;\ + qed", + ); + storage::read_contract_storage(trie_id, key) } fn set_storage(&mut self, key: StorageKey, value: Option>) { From 68a56dc5137abc8001c5d238218d127bba72156c Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Tue, 23 Jun 2020 13:28:12 +0200 Subject: [PATCH 33/33] Update frame/contracts/src/wasm/runtime.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexander Theißen --- frame/contracts/src/wasm/runtime.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index f99fc0a7fc4c2..b393898835ba3 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -814,12 +814,11 @@ define_env!(Env, , // the caller contract and restore the destination contract and set the specified `rent_allowance`. // All caller's funds are transfered to the destination. // - // If there is no tombstone at the destination address or if the hashes don't match - // then restoration is cancelled and no changes are made. Additionally, this contract instance - // should be not present on the contract call stack. If any of these preconditions are not held, - // then a trap is generated. + // If there is no tombstone at the destination address, the hashes don't match or this contract + // instance is already present on the contract call stack, a trap is generated. // - // Otherwise, the contract execution is terminated and the destination contract is restored. + // Otherwise, the destination contract is restored. This function is diverging and stops execution + // even on success. // // `dest_ptr`, `dest_len` - the pointer and the length of a buffer that encodes `T::AccountId` // with the address of the to be restored contract.