diff --git a/Cargo.lock b/Cargo.lock
index 64aa540ce4e01..5a5a061bded99 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4010,6 +4010,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/fixtures/restoration.wat b/frame/contracts/fixtures/restoration.wat
index 4e11f97d5a2cc..225fdde817800 100644
--- a/frame/contracts/fixtures/restoration.wat
+++ b/frame/contracts/fixtures/restoration.wat
@@ -1,6 +1,10 @@
(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)
+ )
+ )
(import "env" "memory" (memory 1 1))
(func (export "call")
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);
- }
- }
- }
- }
-}
diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs
index 9cc1c50260db9..ff0d4d9dc0de1 100644
--- a/frame/contracts/src/exec.rs
+++ b/frame/contracts/src/exec.rs
@@ -15,16 +15,16 @@
// along with Substrate. If not, see .
use super::{CodeHash, Config, ContractAddressFor, Event, RawEvent, Trait,
- TrieId, BalanceOf, ContractInfo};
-use crate::account_db::{AccountDb, DirectAccountDb, OverlayAccountDb};
+ TrieId, BalanceOf, ContractInfo, TrieIdGenerator};
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 sp_runtime::traits::{Bounded, Zero};
use frame_support::{
storage::unhashed, dispatch::DispatchError,
- traits::{WithdrawReason, Currency, Time, Randomness},
+ traits::{ExistenceRequirement, Currency, Time, Randomness},
};
pub type AccountIdOf = ::AccountId;
@@ -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.
///
@@ -129,6 +129,12 @@ pub trait Ext {
) -> Result<(), DispatchError>;
/// Transfer all funds to `beneficiary` and delete 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,
beneficiary: &AccountIdOf,
@@ -147,14 +153,20 @@ 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.
+ ///
+ /// 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,
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;
@@ -264,38 +276,18 @@ 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,
/// 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> {
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 +312,6 @@ where
caller: None,
self_trie_id: None,
self_account: origin,
- overlay: OverlayAccountDb::::new(&DirectAccountDb),
depth: 0,
deferred: Vec::new(),
config: &cfg,
@@ -338,7 +329,6 @@ where
caller: Some(self),
self_trie_id: trie_id,
self_account: dest,
- overlay: OverlayAccountDb::new(&self.overlay),
depth: self.depth + 1,
deferred: Vec::new(),
config: self.config,
@@ -349,23 +339,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,
@@ -424,8 +397,8 @@ 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) {
- Some(dest_code_hash) => {
+ match storage::code_hash::(&dest) {
+ Ok(dest_code_hash) => {
let executable = try_or_exec_error!(
nested.loader.load_main(&dest_code_hash),
input_data
@@ -437,10 +410,9 @@ where
input_data,
gas_meter,
)?;
-
Ok(output)
}
- None => Ok(ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() }),
+ Err(storage::ContractAbsentError) => Ok(ExecReturnValue { status: STATUS_SUCCESS, data: Vec::new() }),
}
})
}
@@ -477,11 +449,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| {
+ let output = self.with_nested_context(dest.clone(), Some(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
);
@@ -512,7 +493,7 @@ where
)?;
// 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,
@@ -520,10 +501,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)
})?;
@@ -531,32 +509,6 @@ where
Ok((dest, output))
}
- pub fn terminate(
- &mut self,
- beneficiary: &T::AccountId,
- gas_meter: &mut GasMeter,
- ) -> Result<(), DispatchError> {
- let self_id = self.self_account.clone();
- let value = self.overlay.get_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,
- )?;
- self.overlay.destroy_contract(&self_id);
- Ok(())
- }
-
fn new_call_context<'b>(
&'b mut self,
caller: T::AccountId,
@@ -573,21 +525,26 @@ where
}
}
+ /// 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
{
- let (output, change_set, deferred) = {
+ use frame_support::storage::TransactionOutcome::*;
+ 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 = frame_support::storage::with_transaction(|| {
+ let output = func(&mut nested);
+ match output {
+ Ok(ref rv) if rv.is_success() => Commit(output),
+ _ => Rollback(output),
+ }
+ })?;
+ (output, nested.deferred)
};
-
if output.is_success() {
- self.overlay.commit(change_set);
self.deferred.extend(deferred);
}
-
Ok(output)
}
@@ -676,48 +633,27 @@ 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")?,
+ let existence_requirement = match cause {
+ Terminate => ExistenceRequirement::AllowDeath,
+ _ => ExistenceRequirement::KeepAlive,
};
-
- 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(),
- });
- }
+ T::Currency::transfer(transactor, dest, value, existence_requirement)?;
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, 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>,
caller: T::AccountId,
@@ -735,20 +671,32 @@ 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)
+ 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>) -> Result<(), &'static str> {
- if let Some(ref value) = value {
- if self.max_value_size() < value.len() as u32 {
- return Err("value size exceeds maximum");
- }
+ 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"
+ );
}
-
- self.ctx
- .overlay
- .set_storage(&self.ctx.self_account, key, value);
- Ok(())
}
fn instantiate(
@@ -767,7 +715,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(
@@ -775,7 +730,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(
@@ -795,20 +773,40 @@ 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(),
- dest,
- code_hash,
+ ) -> 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(),
+ code_hash.clone(),
rent_allowance,
delta,
- });
+ );
+ if let Ok(_) = result {
+ deposit_event::(
+ vec![],
+ RawEvent::Restored(
+ self.ctx.self_account.clone(),
+ dest,
+ code_hash,
+ rent_allowance,
+ ),
+ );
+ }
+ result
}
fn address(&self) -> &T::AccountId {
@@ -820,7 +818,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 {
@@ -844,18 +842,25 @@ 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) {
- self.ctx.overlay.set_rent_allowance(&self.ctx.self_account, rent_allowance)
+ 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"
+ );
+ }
}
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
}
@@ -877,30 +882,37 @@ 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.
/// 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::{
- BalanceOf, ExecFeeToken, ExecutionContext, Ext, Loader, TransferFeeKind, TransferFeeToken,
- Vm, ExecResult, RawEvent, DeferredAction,
+ BalanceOf, Event, ExecFeeToken, ExecResult, ExecutionContext, Ext, Loader,
+ RawEvent, TransferFeeKind, TransferFeeToken, Vm,
};
use crate::{
- account_db::AccountDb, gas::GasMeter, tests::{ExtBuilder, Test},
+ gas::GasMeter, tests::{ExtBuilder, Test, MetaEvent},
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;
+ 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};
const ALICE: u64 = 1;
const BOB: u64 = 2;
@@ -908,19 +920,14 @@ mod tests {
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::contracts(contract_event) => Some(contract_event),
+ _ => None,
+ })
+ .collect()
}
struct MockCtx<'a> {
@@ -1029,7 +1036,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 +1058,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 +1079,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 +1104,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 +1115,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 +1136,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 +1148,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 +1166,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 +1191,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 +1219,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 +1251,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,
@@ -1256,12 +1263,12 @@ mod tests {
assert_matches!(
result,
Err(ExecError {
- reason: DispatchError::Other("balance too low to send value"),
+ reason: DispatchError::Module { message: Some("InsufficientBalance"), .. },
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 +1288,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 +1319,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 +1347,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,
@@ -1366,7 +1373,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,
@@ -1414,8 +1421,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 +1467,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 +1508,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 +1557,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,16 +1571,9 @@ 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!(&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(),
- }
+ assert_eq!(storage::code_hash::(&instantiated_contract_address).unwrap(), dummy_ch);
+ assert_eq!(&events(), &[
+ RawEvent::Instantiated(ALICE, instantiated_contract_address)
]);
});
}
@@ -1590,7 +1590,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,8 +1603,8 @@ mod tests {
);
// Check that the account has not been created.
- assert!(ctx.overlay.get_code_hash(&instantiated_contract_address).is_none());
- assert!(ctx.events().is_empty());
+ assert!(storage::code_hash::(&instantiated_contract_address).is_err());
+ assert!(events().is_empty());
});
}
@@ -1635,9 +1635,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,20 +1648,9 @@ 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!(&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(),
- },
+ assert_eq!(storage::code_hash::(&instantiated_contract_address).unwrap(), dummy_ch);
+ assert_eq!(&events(), &[
+ RawEvent::Instantiated(BOB, instantiated_contract_address)
]);
});
}
@@ -1695,9 +1684,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![]),
@@ -1706,12 +1695,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!(&events(), &[]);
});
}
@@ -1732,7 +1716,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,7 +1732,7 @@ mod tests {
);
assert_eq!(
- &ctx.events(),
+ &events(),
&[]
);
});
@@ -1768,8 +1752,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 245c95a4fa4e9..c12029a856c9d 100644
--- a/frame/contracts/src/lib.rs
+++ b/frame/contracts/src/lib.rs
@@ -81,8 +81,7 @@
#[macro_use]
mod gas;
-
-mod account_db;
+mod storage;
mod exec;
mod wasm;
mod rent;
@@ -91,7 +90,6 @@ mod rent;
mod tests;
use crate::exec::ExecutionContext;
-use crate::account_db::{AccountDb, DirectAccountDb};
use crate::wasm::{WasmLoader, WasmVm};
pub use crate::gas::{Gas, GasMeter};
@@ -102,7 +100,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,
@@ -114,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;
@@ -129,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)]
@@ -255,6 +247,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).
@@ -612,12 +610,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)
}
@@ -636,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);
@@ -645,22 +638,10 @@ impl Module {
let result = func(&mut ctx, gas_meter);
- if result.as_ref().map(|output| output.is_success()).unwrap_or(false) {
- // 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::*;
match deferred {
- DepositEvent {
- topics,
- event,
- } => >::deposit_event_indexed(
- &*topics,
- ::Event::from(event).into(),
- ),
DispatchRuntimeCall {
origin: who,
call,
@@ -674,112 +655,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! {
@@ -789,9 +669,6 @@ decl_event! {
::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),
@@ -803,7 +680,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
///
@@ -811,8 +688,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/rent.rs b/frame/contracts/src/rent.rs
index 1d8f47462731b..6afd85aa8eb42 100644
--- a/frame/contracts/src/rent.rs
+++ b/frame/contracts/src/rent.rs
@@ -18,8 +18,10 @@
use crate::{
AliveContractInfo, BalanceOf, ContractInfo, ContractInfoOf, Module, RawEvent,
- TombstoneContractInfo, Trait,
+ 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;
@@ -396,3 +398,90 @@ 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 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");
+ }
+
+ 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(&child_trie_info, &blake2_256(key)).map(|value| {
+ child::kill(&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(&child_trie_info)[..],
+ code_hash,
+ );
+
+ if tombstone != dest_tombstone {
+ for (key, value) in key_values_taken {
+ child::put_raw(&child_trie_info, &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(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,
+ 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/storage.rs b/frame/contracts/src/storage.rs
new file mode 100644
index 0000000000000..4c5ad892a967b
--- /dev/null
+++ b/frame/contracts/src/storage.rs
@@ -0,0 +1,195 @@
+// 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 .
+
+//! This module contains routines for accessing and altering a contract related state.
+
+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 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;
+
+/// 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,
+ key: &StorageKey,
+ opt_new_value: Option>,
+) -> Result<(), ContractAbsentError> {
+ let mut new_info = match >::get(account) {
+ Some(ContractInfo::Alive(alive)) => alive,
+ None | Some(ContractInfo::Tombstone(_)) => return Err(ContractAbsentError),
+ };
+
+ let hashed_key = blake2_256(key);
+ let child_trie_info = &crate::child_trie_info(&trie_id);
+
+ // 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);
+
+ 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),
+ }
+
+ Ok(())
+}
+
+/// Returns the rent allowance set for the contract give by the account id.
+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 doesn't exist or is a tombstone.
+pub fn set_rent_allowance(
+ account: &AccountIdOf,
+ rent_allowance: BalanceOf,
+) -> 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(ContractAbsentError),
+ })
+}
+
+/// 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))
+ .ok_or(ContractAbsentError)
+}
+
+/// 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: 0,
+ trie_id,
+ deduct_block: >::block_number(),
+ rent_allowance: >::max_value(),
+ empty_pair_count: 0,
+ total_pair_count: 0,
+ last_write: None,
+ }
+ .into(),
+ );
+
+ Ok(())
+ })
+}
+
+/// 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));
+}
diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs
index a98fdf2d25804..df6afa8ac51e0 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::*;
@@ -64,6 +62,34 @@ 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).unwrap();
+ }
+ 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) {
+ let imbalance = Balances::deposit_creating(who, amount);
+ drop(imbalance);
+ }
+ pub fn get_balance(who: &u64) -> u64 {
+ Balances::free_balance(who)
+ }
+}
+
thread_local! {
static EXISTENTIAL_DEPOSIT: RefCell = RefCell::new(0);
}
@@ -280,6 +306,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);
@@ -288,8 +316,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,
@@ -298,15 +325,13 @@ fn account_removal_does_not_remove_storage() {
code_hash: H256::repeat_byte(1),
rent_allowance: 40,
last_write: None,
- }));
+ });
+ 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 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(&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,
@@ -315,40 +340,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())
);
}
@@ -376,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)),
@@ -406,7 +430,9 @@ fn instantiate_and_call_and_deposit_event() {
},
EventRecord {
phase: Phase::Initialization,
- event: MetaEvent::contracts(RawEvent::Transfer(ALICE, BOB, 100)),
+ event: MetaEvent::balances(
+ pallet_balances::RawEvent::Transfer(ALICE, BOB, 100)
+ ),
topics: vec![],
},
EventRecord {
@@ -479,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)),
@@ -509,7 +535,9 @@ fn dispatch_call() {
},
EventRecord {
phase: Phase::Initialization,
- event: MetaEvent::contracts(RawEvent::Transfer(ALICE, BOB, 100)),
+ event: MetaEvent::balances(
+ pallet_balances::RawEvent::Transfer(ALICE, BOB, 100)
+ ),
topics: vec![],
},
EventRecord {
@@ -606,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)),
@@ -636,7 +664,9 @@ fn dispatch_call_not_dispatched_after_top_level_transaction_failure() {
},
EventRecord {
phase: Phase::Initialization,
- event: MetaEvent::contracts(RawEvent::Transfer(ALICE, BOB, 100)),
+ event: MetaEvent::balances(
+ pallet_balances::RawEvent::Transfer(ALICE, BOB, 100)
+ ),
topics: vec![],
},
EventRecord {
@@ -1323,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!(
@@ -1367,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();
@@ -1386,18 +1421,10 @@ 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) => {
- assert_eq!(System::events(), vec![
+ pretty_assertions::assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::contracts(RawEvent::Evicted(BOB, true)),
@@ -1425,7 +1452,9 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage:
},
EventRecord {
phase: Phase::Initialization,
- event: MetaEvent::contracts(RawEvent::Transfer(CHARLIE, DJANGO, 30_000)),
+ event: MetaEvent::balances(
+ pallet_balances::RawEvent::Transfer(CHARLIE, DJANGO, 30_000)
+ ),
topics: vec![],
},
EventRecord {
@@ -1433,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));
@@ -1468,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/mod.rs b/frame/contracts/src/wasm/mod.rs
index cb69cd689b265..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,
@@ -304,19 +301,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