pallet-contracts: migrate to nested storage transaction mechanism (#6382)

* Add a simple direct storage access module

* WIP

* Completely migrate to the transactional system.

* Format

* Fix wasm compilation

* Get rid of account_db module

* Make deposit event eager

* Make restore_to eager

* It almost compiles.

* Make it compile.

* Make the tests compile

* Get rid of account_db

* Drop the result.

* Backport the book keeping.

* Fix all remaining tests.

* Make it compile for std

* Remove a stale TODO marker

* Remove another stale TODO

* Add proof for `terminate`

* Remove a stale comment.

* Make restoration diverging.

* Remove redudnant trait: `ComputeDispatchFee`

* Update frame/contracts/src/exec.rs

Co-authored-by: Alexander Theißen <alex.theissen@me.com>

* Introduce proper errors into the storage module.

* Adds comments for contract storage module.

* Inline `ExecutionContext::terminate`.

* Restore_to should not let sacrifice itself if the contract present on the stack.

* Inline `transfer` function

* Update doc - add "if succeeded"

* Adapt to TransactionOutcome changes

* Updates the docs for `ext_restore_to`

* Add a proper assert.

* Update frame/contracts/src/wasm/runtime.rs

Co-authored-by: Alexander Theißen <alex.theissen@me.com>

Co-authored-by: Alexander Theißen <alex.theissen@me.com>
Co-authored-by: Alexander Theißen <alexander.theissen@parity.io>
This commit is contained in:
Sergei Shulepov
2020-06-23 19:06:07 +02:00
committed by GitHub
parent 4bf044eac6
commit f36b78570f
11 changed files with 661 additions and 936 deletions
+1
View File
@@ -4009,6 +4009,7 @@ dependencies = [
"pallet-transaction-payment",
"parity-scale-codec",
"parity-wasm 0.41.0",
"pretty_assertions",
"pwasm-utils",
"serde",
"sp-core",
+1
View File
@@ -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" }
@@ -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")
-450
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<Contract> because we can't create
// the trie_id in the overlay, thus we provide an overlay on the fields
// specifically.
pub struct ChangeEntry<T: Trait> {
/// If Some(_), then the account balance is modified to the value. If None and `reset` is false,
/// the balance unmodified. If None and `reset` is true, the balance is reset to 0.
balance: Option<BalanceOf<T>>,
/// If 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<CodeHash<T>>,
/// If Some(_), then the rent allowance is set to the value. If None and `reset` is false, then
/// the rent allowance is unmodified. If None and `reset` is true, the contract is deleted.
rent_allowance: Option<BalanceOf<T>>,
storage: BTreeMap<StorageKey, Option<Vec<u8>>>,
/// If true, indicates that the existing contract and all its storage entries should be removed
/// and replaced with the fields on this change entry. Otherwise, the fields on this change
/// entry are updates merged into the existing contract info and storage.
reset: bool,
}
impl<T: Trait> ChangeEntry<T> {
fn balance(&self) -> Option<BalanceOf<T>> {
self.balance.or_else(|| {
if self.reset {
Some(<BalanceOf<T>>::zero())
} else {
None
}
})
}
fn code_hash(&self) -> Option<Option<CodeHash<T>>> {
if self.reset {
Some(self.code_hash)
} else {
self.code_hash.map(Some)
}
}
fn rent_allowance(&self) -> Option<Option<BalanceOf<T>>> {
if self.reset {
Some(self.rent_allowance)
} else {
self.rent_allowance.map(Some)
}
}
fn storage(&self, location: &StorageKey) -> Option<Option<Vec<u8>>> {
let value = self.storage.get(location).cloned();
if self.reset {
Some(value.unwrap_or(None))
} else {
value
}
}
}
// Cannot derive(Default) since it erroneously bounds T by Default.
impl<T: Trait> Default for ChangeEntry<T> {
fn default() -> Self {
ChangeEntry {
rent_allowance: Default::default(),
balance: Default::default(),
code_hash: Default::default(),
storage: Default::default(),
reset: false,
}
}
}
pub type ChangeSet<T> = BTreeMap<<T as frame_system::Trait>::AccountId, ChangeEntry<T>>;
pub trait AccountDb<T: Trait> {
/// 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 <ContractInfoOf<T>>.
/// Because DirectAccountDb bypass the lookup for this association.
fn get_storage(
&self,
account: &T::AccountId,
trie_id: Option<&TrieId>,
location: &StorageKey,
) -> Option<Vec<u8>>;
/// If account has an alive contract then return the code hash associated.
fn get_code_hash(&self, account: &T::AccountId) -> Option<CodeHash<T>>;
/// If account has an alive contract then return the rent allowance associated.
fn get_rent_allowance(&self, account: &T::AccountId) -> Option<BalanceOf<T>>;
/// 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<T>;
fn commit(&mut self, change_set: ChangeSet<T>);
}
pub struct DirectAccountDb;
impl<T: Trait> AccountDb<T> for DirectAccountDb {
fn get_storage(
&self,
_account: &T::AccountId,
trie_id: Option<&TrieId>,
location: &StorageKey,
) -> Option<Vec<u8>> {
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<CodeHash<T>> {
<ContractInfoOf<T>>::get(account).and_then(|i| i.as_alive().map(|i| i.code_hash))
}
fn get_rent_allowance(&self, account: &T::AccountId) -> Option<BalanceOf<T>> {
<ContractInfoOf<T>>::get(account).and_then(|i| i.as_alive().map(|i| i.rent_allowance))
}
fn contract_exists(&self, account: &T::AccountId) -> bool {
<ContractInfoOf<T>>::contains_key(account)
}
fn get_balance(&self, account: &T::AccountId) -> BalanceOf<T> {
T::Currency::free_balance(account)
}
fn commit(&mut self, s: ChangeSet<T>) {
let mut total_imbalance = SignedImbalance::zero();
for (address, changed) in s.into_iter() {
if let Some(balance) = changed.balance() {
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 <ContractInfoOf<T>>::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());
<ContractInfoOf<T>>::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::<T> {
code_hash,
storage_size: 0,
empty_pair_count: 0,
total_pair_count: 0,
trie_id: <T as Trait>::TrieIdGenerator::trie_id(&address),
deduct_block: <frame_system::Module<T>>::block_number(),
rent_allowance: <BalanceOf<T>>::max_value(),
last_write: None,
}
}
// New contract is being instantiated.
(_, None, Some(code_hash)) => AliveContractInfo::<T> {
code_hash,
storage_size: 0,
empty_pair_count: 0,
total_pair_count: 0,
trie_id: <T as Trait>::TrieIdGenerator::trie_id(&address),
deduct_block: <frame_system::Module<T>>::block_number(),
rent_allowance: <BalanceOf<T>>::max_value(),
last_write: None,
},
// There is no existing at the address nor a new one to be 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(<frame_system::Module<T>>::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)
{
<ContractInfoOf<T>>::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<ChangeSet<T>>,
underlying: &'a dyn AccountDb<T>,
}
impl<'a, T: Trait> OverlayAccountDb<'a, T> {
pub fn new(underlying: &'a dyn AccountDb<T>) -> OverlayAccountDb<'a, T> {
OverlayAccountDb {
local: RefCell::new(ChangeSet::new()),
underlying,
}
}
pub fn into_change_set(self) -> ChangeSet<T> {
self.local.into_inner()
}
pub fn set_storage(
&mut self,
account: &T::AccountId,
location: StorageKey,
value: Option<Vec<u8>>,
) {
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<T>,
) -> 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(<BalanceOf<T>>::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<T>) {
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<T>) {
self.local
.borrow_mut()
.entry(account.clone())
.or_insert(Default::default())
.balance = Some(balance);
}
}
impl<'a, T: Trait> AccountDb<T> for OverlayAccountDb<'a, T> {
fn get_storage(
&self,
account: &T::AccountId,
trie_id: Option<&TrieId>,
location: &StorageKey,
) -> Option<Vec<u8>> {
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<CodeHash<T>> {
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<BalanceOf<T>> {
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<T> {
self.local
.borrow()
.get(account)
.and_then(|changes| changes.balance())
.unwrap_or_else(|| self.underlying.get_balance(account))
}
fn commit(&mut self, s: ChangeSet<T>) {
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);
}
}
}
}
}
+239 -256
View File
@@ -15,16 +15,16 @@
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
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<T> = <T as frame_system::Trait>::AccountId;
@@ -105,8 +105,8 @@ pub trait Ext {
fn get_storage(&self, key: &StorageKey) -> Option<Vec<u8>>;
/// 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<Vec<u8>>) -> Result<(), &'static str>;
/// the storage entry is deleted.
fn set_storage(&mut self, key: StorageKey, value: Option<Vec<u8>>);
/// 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<Self::T>,
@@ -147,14 +153,20 @@ pub trait Ext {
/// Notes a call dispatch.
fn note_dispatch_call(&mut self, call: CallOf<Self::T>);
/// 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<Self::T>,
code_hash: CodeHash<Self::T>,
rent_allowance: BalanceOf<Self::T>,
delta: Vec<StorageKey>,
);
) -> Result<(), &'static str>;
/// Returns a reference to the account id of the caller.
fn caller(&self) -> &AccountIdOf<Self::T>;
@@ -264,38 +276,18 @@ impl<T: Trait> Token<T> for ExecFeeToken {
#[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq, Clone))]
#[derive(sp_runtime::RuntimeDebug)]
pub enum DeferredAction<T: Trait> {
DepositEvent {
/// A list of topics this event will be deposited with.
topics: Vec<T::Hash>,
/// The event to deposit.
event: Event<T>,
},
DispatchRuntimeCall {
/// The account id of the contract who dispatched this call.
origin: T::AccountId,
/// The call to dispatch.
call: <T as Trait>::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<T>,
/// The initial rent allowance to set.
rent_allowance: BalanceOf<T>,
/// The keys to delete upon restoration.
delta: Vec<StorageKey>,
},
}
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<TrieId>,
pub overlay: OverlayAccountDb<'a, T>,
pub depth: usize,
pub deferred: Vec<DeferredAction<T>>,
pub config: &'a Config<T>,
@@ -320,7 +312,6 @@ where
caller: None,
self_trie_id: None,
self_account: origin,
overlay: OverlayAccountDb::<T>::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<T>,
gas_meter: &mut GasMeter<T>
) -> 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::<T>(&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 = <T as Trait>::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::<T>(
&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::<T>(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<T>,
) -> 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<F>(&mut self, dest: T::AccountId, trie_id: Option<TrieId>, func: F)
-> ExecResult
where F: FnOnce(&mut ExecutionContext<T, V, L>) -> 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<T>, L: Loader<T>>(
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
let existence_requirement = match cause {
Terminate => ExistenceRequirement::AllowDeath,
_ => ExistenceRequirement::KeepAlive,
};
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(),
});
}
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<T> + 'b, L: Loader<T>> {
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<Vec<u8>> {
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<Vec<u8>>) -> 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<Vec<u8>>) {
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::<T>(&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<T>,
gas_meter: &mut GasMeter<T>,
) -> 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<Self::T>,
gas_meter: &mut GasMeter<Self::T>,
) -> 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::<T>(&self_id, self_trie_id);
Ok(())
}
fn call(
@@ -795,20 +773,40 @@ where
});
}
fn note_restore_to(
fn restore_to(
&mut self,
dest: AccountIdOf<Self::T>,
code_hash: CodeHash<Self::T>,
rent_allowance: BalanceOf<Self::T>,
delta: Vec<StorageKey>,
) {
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::<T>(
self.ctx.self_account.clone(),
dest.clone(),
code_hash.clone(),
rent_allowance,
delta,
});
);
if let Ok(_) = result {
deposit_event::<Self::T>(
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<T> {
self.ctx.overlay.get_balance(&self.ctx.self_account)
T::Currency::free_balance(&self.ctx.self_account)
}
fn value_transferred(&self) -> BalanceOf<T> {
@@ -844,18 +842,25 @@ where
}
fn deposit_event(&mut self, topics: Vec<T::Hash>, data: Vec<u8>) {
self.ctx.deferred.push(DeferredAction::DepositEvent {
deposit_event::<Self::T>(
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<T>) {
self.ctx.overlay.set_rent_allowance(&self.ctx.self_account, rent_allowance)
if let Err(storage::ContractAbsentError) =
storage::set_rent_allowance::<T>(&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<T> {
self.ctx.overlay.get_rent_allowance(&self.ctx.self_account)
storage::rent_allowance::<T>(&self.ctx.self_account)
.unwrap_or(<BalanceOf<T>>::max_value()) // Must never be triggered actually
}
@@ -877,30 +882,37 @@ where
}
}
fn deposit_event<T: Trait>(
topics: Vec<T::Hash>,
event: Event<T>,
) {
<frame_system::Module<T>>::deposit_event_indexed(
&*topics,
<T as Trait>::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<DeferredAction<T>> {
self.deferred
.iter()
.filter(|action| match *action {
DeferredAction::DepositEvent { .. } => true,
_ => false,
})
.cloned()
.collect()
}
fn events() -> Vec<Event<Test>> {
<frame_system::Module<Test>>::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::<Test>::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::<Test>::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::<Test>::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::<Test>::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::<Test>::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::<Test>(&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::<Test>(&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::<Test>::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::<Test>(&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::<Test>::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,
+12 -136
View File
@@ -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<CodeHash, AccountId> {
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<Call, Balance> {
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<T: Trait> From<AliveContractInfo<T>> for ContractInfo<T> {
fn from(alive_info: AliveContractInfo<T>) -> 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<T: Trait> Module<T> {
.get_alive()
.ok_or(ContractAccessError::IsTombstone)?;
let maybe_value = AccountDb::<T>::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<T: Trait> Module<T> {
fn execute_wasm(
origin: T::AccountId,
gas_meter: &mut GasMeter<T>,
func: impl FnOnce(&mut ExecutionContext<T, WasmVm, WasmLoader>, &mut GasMeter<T>) -> ExecResult
func: impl FnOnce(&mut ExecutionContext<T, WasmVm, WasmLoader>, &mut GasMeter<T>) -> ExecResult,
) -> ExecResult {
let cfg = Config::preload();
let vm = WasmVm::new(&cfg.schedule);
@@ -645,22 +638,10 @@ impl<T: Trait> Module<T> {
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,
} => <frame_system::Module<T>>::deposit_event_indexed(
&*topics,
<T as Trait>::Event::from(event).into(),
),
DispatchRuntimeCall {
origin: who,
call,
@@ -674,112 +655,11 @@ impl<T: Trait> Module<T> {
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<T>,
rent_allowance: BalanceOf<T>,
delta: Vec<exec::StorageKey>,
) -> DispatchResult {
let mut origin_contract = <ContractInfoOf<T>>::get(&origin)
.and_then(|c| c.get_alive())
.ok_or(Error::<T>::InvalidSourceContract)?;
let current_block = <frame_system::Module<T>>::block_number();
if origin_contract.last_write == Some(current_block) {
Err(Error::<T>::InvalidContractOrigin)?
}
let dest_tombstone = <ContractInfoOf<T>>::get(&dest)
.and_then(|c| c.get_tombstone())
.ok_or(Error::<T>::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::<Vec<_>>();
let tombstone = <TombstoneContractInfo<T>>::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::<T>::InvalidTombstone.into());
}
origin_contract.storage_size -= key_values_taken.iter()
.map(|(_, value)| value.len() as u32)
.sum::<u32>();
<ContractInfoOf<T>>::remove(&origin);
<ContractInfoOf<T>>::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, <BalanceOf<T>>::zero());
T::Currency::deposit_creating(&dest, origin_free_balance);
Ok(())
}
}
decl_event! {
@@ -789,9 +669,6 @@ decl_event! {
<T as frame_system::Trait>::AccountId,
<T as frame_system::Trait>::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),
+90 -1
View File
@@ -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<T: Trait>(
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<T: Trait>(
origin: T::AccountId,
dest: T::AccountId,
code_hash: CodeHash<T>,
rent_allowance: BalanceOf<T>,
delta: Vec<crate::exec::StorageKey>,
) -> Result<(), &'static str> {
let mut origin_contract = <ContractInfoOf<T>>::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 = <frame_system::Module<T>>::block_number();
if origin_contract.last_write == Some(current_block) {
return Err("Origin TrieId written in the current block");
}
let dest_tombstone = <ContractInfoOf<T>>::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::<Vec<_>>();
let tombstone = <TombstoneContractInfo<T>>::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::<u32>();
<ContractInfoOf<T>>::remove(&origin);
<ContractInfoOf<T>>::insert(&dest, ContractInfo::Alive(AliveContractInfo::<T> {
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, <BalanceOf<T>>::zero());
T::Currency::deposit_creating(&dest, origin_free_balance);
Ok(())
}
+195
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<Vec<u8>> {
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<T: Trait>(
account: &AccountIdOf<T>,
trie_id: &TrieId,
key: &StorageKey,
opt_new_value: Option<Vec<u8>>,
) -> Result<(), ContractAbsentError> {
let mut new_info = match <ContractInfoOf<T>>::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(<frame_system::Module<T>>::block_number());
<ContractInfoOf<T>>::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<T: Trait>(
account: &AccountIdOf<T>,
) -> Result<BalanceOf<T>, ContractAbsentError> {
<ContractInfoOf<T>>::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<T: Trait>(
account: &AccountIdOf<T>,
rent_allowance: BalanceOf<T>,
) -> Result<(), ContractAbsentError> {
<ContractInfoOf<T>>::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<T: Trait>(account: &AccountIdOf<T>) -> Result<CodeHash<T>, ContractAbsentError> {
<ContractInfoOf<T>>::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<T: Trait>(
account: &AccountIdOf<T>,
trie_id: TrieId,
ch: CodeHash<T>,
) -> Result<(), &'static str> {
<ContractInfoOf<T>>::mutate(account, |maybe_contract_info| {
if maybe_contract_info.is_some() {
return Err("Alive contract or tombstone already exists");
}
*maybe_contract_info = Some(
AliveContractInfo::<T> {
code_hash: ch,
storage_size: 0,
trie_id,
deduct_block: <frame_system::Module<T>>::block_number(),
rent_allowance: <BalanceOf<T>>::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<T: Trait>(address: &AccountIdOf<T>, trie_id: &TrieId) {
<ContractInfoOf<T>>::remove(address);
child::kill_storage(&crate::child_trie_info(&trie_id));
}
+85 -65
View File
@@ -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<Vec<u8>>) {
let contract_info = <ContractInfoOf::<Test>>::get(&addr).unwrap().get_alive().unwrap();
write_contract_storage::<Test>(&1, &contract_info.trie_id, key, value).unwrap();
}
pub fn get_storage(addr: &u64, key: &StorageKey) -> Option<Vec<u8>> {
let contract_info = <ContractInfoOf::<Test>>::get(&addr).unwrap().get_alive().unwrap();
read_contract_storage(&contract_info.trie_id, key)
}
pub fn place_contract(address: &u64, code_hash: CodeHash<Test>) {
let trie_id = <Test as crate::Trait>::TrieIdGenerator::trie_id(address);
crate::storage::place_contract::<Test>(&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<u64> = 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 = <Test as Trait>::TrieIdGenerator::trie_id(&1);
let trie_id2 = <Test as Trait>::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::<Test>::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::<Test>::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::<Test>::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::<Test>::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::<Test>::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::<Test>::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!(
<dyn AccountDb<Test>>::get_storage(&DirectAccountDb, &1, Some(&trie_id1), key1),
get_storage(&ALICE, key1),
Some(b"1".to_vec())
);
assert_eq!(
<dyn AccountDb<Test>>::get_storage(&DirectAccountDb, &1, Some(&trie_id1), key2),
get_storage(&ALICE, key2),
Some(b"2".to_vec())
);
assert_eq!(
<dyn AccountDb<Test>>::get_storage(&DirectAccountDb, &2, Some(&trie_id2), key1),
get_storage(&BOB, key1),
Some(b"3".to_vec())
);
assert_eq!(
<dyn AccountDb<Test>>::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::<Test>::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::<Test>::get(BOB).unwrap().get_tombstone().is_some());
let django_contract = ContractInfoOf::<Test>::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::<Test>::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![],
},
+8 -12
View File
@@ -229,11 +229,8 @@ mod tests {
fn get_storage(&self, key: &StorageKey) -> Option<Vec<u8>> {
self.storage.get(key).cloned()
}
fn set_storage(&mut self, key: StorageKey, value: Option<Vec<u8>>)
-> Result<(), &'static str>
{
fn set_storage(&mut self, key: StorageKey, value: Option<Vec<u8>>) {
*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<StorageKey>,
) {
) -> Result<(), &'static str> {
self.restores.push(RestoreEntry {
dest,
code_hash,
rent_allowance,
delta,
});
Ok(())
}
fn caller(&self) -> &u64 {
&42
@@ -386,9 +384,7 @@ mod tests {
fn get_storage(&self, key: &[u8; 32]) -> Option<Vec<u8>> {
(**self).get_storage(key)
}
fn set_storage(&mut self, key: [u8; 32], value: Option<Vec<u8>>)
-> Result<(), &'static str>
{
fn set_storage(&mut self, key: [u8; 32], value: Option<Vec<u8>>) {
(**self).set_storage(key, value)
}
fn instantiate(
@@ -427,14 +423,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<StorageKey>,
) {
(**self).note_restore_to(
) -> Result<(), &'static str> {
(**self).restore_to(
dest,
code_hash,
rent_allowance,
+25 -15
View File
@@ -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<E: Ext>(
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(),
@@ -387,7 +395,7 @@ define_env!(Env, <E: Ext>,
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(())
},
@@ -399,7 +407,7 @@ define_env!(Env, <E: Ext>,
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(())
},
@@ -799,17 +807,18 @@ define_env!(Env, <E: Ext>,
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, the hashes don't match or this contract
// instance is already present on the contract call stack, a trap is generated.
//
// 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.
@@ -857,14 +866,15 @@ define_env!(Env, <E: Ext>,
delta
};
ctx.ext.note_restore_to(
if let Ok(()) = ctx.ext.restore_to(
dest,
code_hash,
rent_allowance,
delta,
);
Ok(())
) {
ctx.special_trap = Some(SpecialTrap::Restoration);
}
Err(sp_sandbox::HostError)
},
// Returns the size of the scratch buffer.