mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 19:21:13 +00:00
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:
Generated
+1
@@ -4009,6 +4009,7 @@ dependencies = [
|
||||
"pallet-transaction-payment",
|
||||
"parity-scale-codec",
|
||||
"parity-wasm 0.41.0",
|
||||
"pretty_assertions",
|
||||
"pwasm-utils",
|
||||
"serde",
|
||||
"sp-core",
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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![],
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user