contracts: Add salt argument to contract instantiation (#7482)

* pallet-contracts: Fix seal_restore_to to output proper module errors

Those errors where part of the decl_error for some time but where
never actually returned. This allows proper debugging of failed
restorations. Previously, any error did return the misleading
`ContractTrapped`.

* Bind UncheckedFrom<T::Hash> + AsRef<[u8]> everywhere

This allows us to make assumptions about the AccoutId
that are necessary for testing and in order to benchmark
the module properly.

This also groups free standing functions into inherent functions
in order to minimize the places where the new bounds need to
be specified.

* Rework contract address determination

* Do not allow override by runtime author
* Instantiate gained a new parameter "salt"

This change is done now in expecation of the upcoming code rent
which needs to change the instantiation dispatchable and
host function anyways.

The situation in where we have only something that is like CREATE2
makes it impossible for UIs to help the user to create an arbitrary
amount of instantiations from the same code.

With this change we have the same functionality as ethereum with
a CREATE and CREATE2 instantation semantic.

* Remove TrieIdGenerator

The new trait bounds allows us to remove this workaround
from the configuration trait.

* Remove default parameters for config trait

It should be solely the responsiblity to determine proper values for
these parameter. As a matter of fact most runtime weren't using these
values anyways.

* Fix tests for new account id type

Because of the new bounds on the trait tests can't get away by using
u64 as accound id. Replacing the 8 byte value by a 32 byte value
creates out quite a bit of code churn.

* Fix benchmarks

The benchmarks need adaption to the new instantiate semantics.

* Fix compile errors caused by adding new trait bounds
* Fix compile errors caused by renaming storage and rent functions
* Adapt host functions and dispatchables to the new salt
* Add tests for instantiate host functions (was not possible before)

* Add benchmark results

* Adapt to the new WeightInfo

The new benchmarks add a new parameter for salt "s" to the instantiate weights
that needs to be applied.

* Fix deploying_wasm_contract_should_work integration test

This test is adapted to use the new instantiate signature.

* Break overlong line

* Break more long lines

Co-authored-by: Parity Benchmarking Bot <admin@parity.io>
This commit is contained in:
Alexander Theißen
2020-11-24 11:42:20 +01:00
committed by GitHub
parent 26830a20df
commit c3ca78fae3
26 changed files with 2186 additions and 2161 deletions
@@ -29,6 +29,7 @@ use crate::Module as Contracts;
use parity_wasm::elements::{Instruction, Instructions, FuncBody, ValueType, BlockType};
use pwasm_utils::stack_height::inject_limiter;
use sp_core::crypto::UncheckedFrom;
use sp_runtime::traits::Hash;
use sp_sandbox::{EnvironmentDefinitionBuilder, Memory};
use sp_std::{prelude::*, convert::TryFrom};
@@ -86,7 +87,11 @@ pub struct ImportedMemory {
}
impl ImportedMemory {
pub fn max<T: Trait>() -> Self {
pub fn max<T: Trait>() -> Self
where
T: Trait,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
let pages = max_pages::<T>();
Self { min_pages: pages, max_pages: pages }
}
@@ -106,7 +111,11 @@ pub struct WasmModule<T:Trait> {
memory: Option<ImportedMemory>,
}
impl<T: Trait> From<ModuleDefinition> for WasmModule<T> {
impl<T: Trait> From<ModuleDefinition> for WasmModule<T>
where
T: Trait,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
fn from(def: ModuleDefinition) -> Self {
// internal functions start at that offset.
let func_offset = u32::try_from(def.imported_functions.len()).unwrap();
@@ -216,7 +225,11 @@ impl<T: Trait> From<ModuleDefinition> for WasmModule<T> {
}
}
impl<T: Trait> WasmModule<T> {
impl<T: Trait> WasmModule<T>
where
T: Trait,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
/// Creates a wasm module with an empty `call` and `deploy` function and nothing else.
pub fn dummy() -> Self {
ModuleDefinition::default().into()
@@ -470,6 +483,10 @@ pub mod body {
}
/// The maximum amount of pages any contract is allowed to have according to the current `Schedule`.
pub fn max_pages<T: Trait>() -> u32 {
pub fn max_pages<T: Trait>() -> u32
where
T: Trait,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
Contracts::<T>::current_schedule().limits.memory_pages
}
@@ -25,7 +25,9 @@ mod sandbox;
use crate::{
*, Module as Contracts,
exec::StorageKey,
rent::Rent,
schedule::{API_BENCHMARK_BATCH_SIZE, INSTR_BENCHMARK_BATCH_SIZE},
storage::Storage,
};
use self::{
code::{
@@ -75,7 +77,11 @@ impl Endow {
}
}
impl<T: Trait> Contract<T> {
impl<T: Trait> Contract<T>
where
T: Trait,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
/// Create new contract and use a default account id as instantiator.
fn new(
module: WasmModule<T>,
@@ -123,7 +129,8 @@ impl<T: Trait> Contract<T> {
Endow::Max => (0u32.into(), Endow::max::<T>()),
};
T::Currency::make_free_balance_be(&caller, caller_funding::<T>());
let addr = T::DetermineContractAddress::contract_address_for(&module.hash, &data, &caller);
let salt = vec![0xff];
let addr = Contracts::<T>::contract_address(&caller, &module.hash, &salt);
// The default block number is zero. The benchmarking system bumps the block number
// to one for the benchmarking closure when it is set to zero. In order to prevent this
@@ -139,6 +146,7 @@ impl<T: Trait> Contract<T> {
Weight::max_value(),
module.hash,
data,
salt,
)?;
let result = Contract {
@@ -160,7 +168,7 @@ impl<T: Trait> Contract<T> {
fn store(&self, items: &Vec<(StorageKey, Vec<u8>)>) -> Result<(), &'static str> {
let info = self.alive_info()?;
for item in items {
crate::storage::write_contract_storage::<T>(
Storage::<T>::write(
&self.account_id,
&info.trie_id,
&item.0,
@@ -192,7 +200,7 @@ impl<T: Trait> Contract<T> {
/// Get the block number when this contract will be evicted. Returns an error when
/// the rent collection won't happen because the contract has to much endowment.
fn eviction_at(&self) -> Result<T::BlockNumber, &'static str> {
let projection = crate::rent::compute_rent_projection::<T>(&self.account_id)
let projection = Rent::<T>::compute_projection(&self.account_id)
.map_err(|_| "Invalid acc for rent")?;
match projection {
RentProjection::EvictionAt(at) => Ok(at),
@@ -211,7 +219,11 @@ struct Tombstone<T: Trait> {
storage: Vec<(StorageKey, Vec<u8>)>,
}
impl<T: Trait> Tombstone<T> {
impl<T: Trait> Tombstone<T>
where
T: Trait,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
/// Create and evict a new contract with the supplied storage item count and size each.
fn new(stor_num: u32, stor_size: u32) -> Result<Self, &'static str> {
let contract = Contract::<T>::new(WasmModule::dummy(), vec![], Endow::CollectRent)?;
@@ -220,7 +232,7 @@ impl<T: Trait> Tombstone<T> {
System::<T>::set_block_number(
contract.eviction_at()? + T::SignedClaimHandicap::get() + 5u32.into()
);
crate::rent::collect_rent::<T>(&contract.account_id);
Rent::<T>::collect(&contract.account_id);
contract.ensure_tombstone()?;
Ok(Tombstone {
@@ -250,6 +262,11 @@ fn caller_funding<T: Trait>() -> BalanceOf<T> {
}
benchmarks! {
where_clause { where
T::AccountId: UncheckedFrom<T::Hash>,
T::AccountId: AsRef<[u8]>,
}
_ {
}
@@ -276,17 +293,20 @@ benchmarks! {
// The size of the input data influences the runtime because it is hashed in order to determine
// the contract address.
// `n`: Size of the data passed to constructor in kilobytes.
// `s`: Size of the salt in kilobytes.
instantiate {
let n in 0 .. code::max_pages::<T>() * 64;
let s in 0 .. code::max_pages::<T>() * 64;
let data = vec![42u8; (n * 1024) as usize];
let salt = vec![42u8; (s * 1024) as usize];
let endowment = Config::<T>::subsistence_threshold_uncached();
let caller = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, caller_funding::<T>());
let WasmModule { code, hash, .. } = WasmModule::<T>::dummy_with_mem();
let origin = RawOrigin::Signed(caller.clone());
let addr = T::DetermineContractAddress::contract_address_for(&hash, &data, &caller);
let addr = Contracts::<T>::contract_address(&caller, &hash, &salt);
Contracts::<T>::put_code_raw(code)?;
}: _(origin, endowment, Weight::max_value(), hash, data)
}: _(origin, endowment, Weight::max_value(), hash, data, salt)
verify {
// endowment was removed from the caller
assert_eq!(T::Currency::free_balance(&caller), caller_funding::<T>() - endowment);
@@ -1000,7 +1020,7 @@ benchmarks! {
let instance = Contract::<T>::new(code, vec![], Endow::Max)?;
let trie_id = instance.alive_info()?.trie_id;
for key in keys {
crate::storage::write_contract_storage::<T>(
Storage::<T>::write(
&instance.account_id,
&trie_id,
key.as_slice().try_into().map_err(|e| "Key has wrong length")?,
@@ -1045,7 +1065,7 @@ benchmarks! {
let instance = Contract::<T>::new(code, vec![], Endow::Max)?;
let trie_id = instance.alive_info()?.trie_id;
for key in keys {
crate::storage::write_contract_storage::<T>(
Storage::<T>::write(
&instance.account_id,
&trie_id,
key.as_slice().try_into().map_err(|e| "Key has wrong length")?,
@@ -1089,7 +1109,7 @@ benchmarks! {
});
let instance = Contract::<T>::new(code, vec![], Endow::Max)?;
let trie_id = instance.alive_info()?.trie_id;
crate::storage::write_contract_storage::<T>(
Storage::<T>::write(
&instance.account_id,
&trie_id,
key.as_slice().try_into().map_err(|e| "Key has wrong length")?,
@@ -1341,7 +1361,9 @@ benchmarks! {
ValueType::I32,
ValueType::I32,
ValueType::I32,
ValueType::I32
ValueType::I32,
ValueType::I32,
ValueType::I32,
],
return_type: Some(ValueType::I32),
}],
@@ -1371,6 +1393,8 @@ benchmarks! {
Regular(Instruction::I32Const(addr_len_offset as i32)), // address_len_ptr
Regular(Instruction::I32Const(u32::max_value() as i32)), // output_ptr
Regular(Instruction::I32Const(0)), // output_len_ptr
Regular(Instruction::I32Const(0)), // salt_ptr
Regular(Instruction::I32Const(0)), // salt_ptr_len
Regular(Instruction::Call(0)),
Regular(Instruction::Drop),
])),
@@ -1381,8 +1405,8 @@ benchmarks! {
let callee = instance.addr.clone();
let addresses = hashes
.iter()
.map(|hash| T::DetermineContractAddress::contract_address_for(
hash, &[], &instance.account_id
.map(|hash| Contracts::<T>::contract_address(
&instance.account_id, hash, &[],
))
.collect::<Vec<_>>();
@@ -1398,9 +1422,10 @@ benchmarks! {
}
}
seal_instantiate_per_input_output_kb {
seal_instantiate_per_input_output_salt_kb {
let i in 0 .. (code::max_pages::<T>() - 1) * 64;
let o in 0 .. (code::max_pages::<T>() - 1) * 64;
let s in 0 .. (code::max_pages::<T>() - 1) * 64;
let callee_code = WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
imported_functions: vec![ImportedFunction {
@@ -1458,7 +1483,9 @@ benchmarks! {
ValueType::I32,
ValueType::I32,
ValueType::I32,
ValueType::I32
ValueType::I32,
ValueType::I32,
ValueType::I32,
],
return_type: Some(ValueType::I32),
}],
@@ -1496,6 +1523,8 @@ benchmarks! {
Regular(Instruction::I32Const(addr_len_offset as i32)), // address_len_ptr
Regular(Instruction::I32Const(output_offset as i32)), // output_ptr
Regular(Instruction::I32Const(output_len_offset as i32)), // output_len_ptr
Counter(input_offset as u32, input_len as u32), // salt_ptr
Regular(Instruction::I32Const((s * 1024).max(input_len as u32) as i32)), // salt_len
Regular(Instruction::Call(0)),
Regular(Instruction::I32Eqz),
Regular(Instruction::If(BlockType::NoResult)),
@@ -2401,6 +2430,8 @@ mod tests {
create_test!(seal_transfer);
create_test!(seal_call);
create_test!(seal_call_per_transfer_input_output_kb);
create_test!(seal_instantiate);
create_test!(seal_instantiate_per_input_output_salt_kb);
create_test!(seal_clear_storage);
create_test!(seal_hash_sha2_256);
create_test!(seal_hash_sha2_256_per_kb);
@@ -19,8 +19,11 @@
///! sandbox to execute the wasm code. This is because we do not need the full
///! environment that provides the seal interface as imported functions.
use super::code::WasmModule;
use super::Trait;
use super::{
Trait,
code::WasmModule,
};
use sp_core::crypto::UncheckedFrom;
use sp_sandbox::{EnvironmentDefinitionBuilder, Instance, Memory};
/// Minimal execution environment without any exported functions.
@@ -36,7 +39,11 @@ impl Sandbox {
}
}
impl<T: Trait> From<&WasmModule<T>> for Sandbox {
impl<T: Trait> From<&WasmModule<T>> for Sandbox
where
T: Trait,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
/// Creates an instance from the supplied module and supplies as much memory
/// to the instance as the module declares as imported.
fn from(module: &WasmModule<T>) -> Self {
+59 -52
View File
@@ -15,10 +15,11 @@
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
use crate::{
CodeHash, Config, ContractAddressFor, Event, RawEvent, Trait,
TrieId, BalanceOf, ContractInfo, TrieIdGenerator,
gas::GasMeter, rent, storage, Error, ContractInfoOf
CodeHash, Config, Event, RawEvent, Trait, Module as Contracts,
TrieId, BalanceOf, ContractInfo, gas::GasMeter, rent::Rent, storage::{self, Storage},
Error, ContractInfoOf
};
use sp_core::crypto::UncheckedFrom;
use sp_std::prelude::*;
use sp_runtime::traits::{Bounded, Zero, Convert, Saturating};
use frame_support::{
@@ -75,6 +76,7 @@ pub trait Ext {
value: BalanceOf<Self::T>,
gas_meter: &mut GasMeter<Self::T>,
input_data: Vec<u8>,
salt: &[u8],
) -> Result<(AccountIdOf<Self::T>, ExecReturnValue), ExecError>;
/// Transfer some amount of funds into the specified account.
@@ -118,7 +120,7 @@ pub trait Ext {
code_hash: CodeHash<Self::T>,
rent_allowance: BalanceOf<Self::T>,
delta: Vec<StorageKey>,
) -> Result<(), &'static str>;
) -> Result<(), DispatchError>;
/// Returns a reference to the account id of the caller.
fn caller(&self) -> &AccountIdOf<Self::T>;
@@ -215,6 +217,7 @@ pub struct ExecutionContext<'a, T: Trait + 'a, V, L> {
impl<'a, T, E, V, L> ExecutionContext<'a, T, V, L>
where
T: Trait,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
L: Loader<T, Executable = E>,
V: Vm<T, Executable = E>,
{
@@ -264,12 +267,12 @@ where
Err(Error::<T>::MaxCallDepthReached)?
}
// Assumption: `collect_rent` doesn't collide with overlay because
// `collect_rent` will be done on first call and destination contract and balance
// Assumption: `collect` doesn't collide with overlay because
// `collect` will be done on first call and destination contract and balance
// cannot be changed before the first call
// We do not allow 'calling' plain accounts. For transfering value
// `seal_transfer` must be used.
let contract = if let Some(ContractInfo::Alive(info)) = rent::collect_rent::<T>(&dest) {
let contract = if let Some(ContractInfo::Alive(info)) = Rent::<T>::collect(&dest) {
info
} else {
Err(Error::<T>::NotCallable)?
@@ -308,6 +311,7 @@ where
gas_meter: &mut GasMeter<T>,
code_hash: &CodeHash<T>,
input_data: Vec<u8>,
salt: &[u8],
) -> Result<(T::AccountId, ExecReturnValue), ExecError> {
if self.depth == self.config.max_depth as usize {
Err(Error::<T>::MaxCallDepthReached)?
@@ -315,19 +319,15 @@ where
let transactor_kind = self.transactor_kind();
let caller = self.self_account.clone();
let dest = T::DetermineContractAddress::contract_address_for(
code_hash,
&input_data,
&caller,
);
let dest = Contracts::<T>::contract_address(&caller, code_hash, salt);
// TrieId has not been generated yet and storage is empty since contract is new.
//
// Generate it now.
let dest_trie_id = <T as Trait>::TrieIdGenerator::trie_id(&dest);
let dest_trie_id = Storage::<T>::generate_trie_id(&dest);
let output = self.with_nested_context(dest.clone(), dest_trie_id, |nested| {
storage::place_contract::<T>(
Storage::<T>::place_contract(
&dest,
nested
.self_trie_id
@@ -444,7 +444,10 @@ fn transfer<'a, T: Trait, V: Vm<T>, L: Loader<T>>(
dest: &T::AccountId,
value: BalanceOf<T>,
ctx: &mut ExecutionContext<'a, T, V, L>,
) -> Result<(), DispatchError> {
) -> Result<(), DispatchError>
where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
use self::TransferCause::*;
use self::TransactorKind::*;
@@ -491,6 +494,7 @@ struct CallContext<'a, 'b: 'a, T: Trait + 'b, V: Vm<T> + 'b, L: Loader<T>> {
impl<'a, 'b: 'a, T, E, V, L> Ext for CallContext<'a, 'b, T, V, L>
where
T: Trait + 'b,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
V: Vm<T, Executable = E>,
L: Loader<T, Executable = E>,
{
@@ -503,7 +507,7 @@ where
expect can't fail;\
qed",
);
storage::read_contract_storage(trie_id, key)
Storage::<T>::read(trie_id, key)
}
fn set_storage(&mut self, key: StorageKey, value: Option<Vec<u8>>) {
@@ -514,12 +518,12 @@ where
qed",
);
if let Err(storage::ContractAbsentError) =
storage::write_contract_storage::<T>(&self.ctx.self_account, trie_id, &key, value)
Storage::<T>::write(&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`;
write cannot return `None`;
qed"
);
}
@@ -531,8 +535,9 @@ where
endowment: BalanceOf<T>,
gas_meter: &mut GasMeter<T>,
input_data: Vec<u8>,
salt: &[u8],
) -> Result<(AccountIdOf<T>, ExecReturnValue), ExecError> {
self.ctx.instantiate(endowment, gas_meter, code_hash, input_data)
self.ctx.instantiate(endowment, gas_meter, code_hash, input_data, salt)
}
fn transfer(
@@ -558,9 +563,7 @@ where
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",
));
return Err(Error::<T>::ReentranceDenied.into());
}
}
transfer(
@@ -576,7 +579,7 @@ where
a contract has a trie id;\
this can't be None; qed",
);
storage::destroy_contract::<T>(&self_id, self_trie_id);
Storage::<T>::destroy_contract(&self_id, self_trie_id);
Ok(())
}
@@ -596,16 +599,14 @@ where
code_hash: CodeHash<Self::T>,
rent_allowance: BalanceOf<Self::T>,
delta: Vec<StorageKey>,
) -> Result<(), &'static str> {
) -> Result<(), DispatchError> {
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",
);
return Err(Error::<T>::ReentranceDenied.into());
}
}
let result = crate::rent::restore_to::<T>(
let result = Rent::<T>::restore_to(
self.ctx.self_account.clone(),
dest.clone(),
code_hash.clone(),
@@ -667,7 +668,7 @@ where
fn set_rent_allowance(&mut self, rent_allowance: BalanceOf<T>) {
if let Err(storage::ContractAbsentError) =
storage::set_rent_allowance::<T>(&self.ctx.self_account, rent_allowance)
Storage::<T>::set_rent_allowance(&self.ctx.self_account, rent_allowance)
{
panic!(
"`self_account` points to an alive contract within the `CallContext`;
@@ -677,7 +678,7 @@ where
}
fn rent_allowance(&self) -> BalanceOf<T> {
storage::rent_allowance::<T>(&self.ctx.self_account)
Storage::<T>::rent_allowance(&self.ctx.self_account)
.unwrap_or_else(|_| <BalanceOf<T>>::max_value()) // Must never be triggered actually
}
@@ -711,23 +712,21 @@ fn deposit_event<T: Trait>(
mod tests {
use super::{
BalanceOf, Event, ExecResult, ExecutionContext, Ext, Loader,
RawEvent, Vm, ReturnFlags, ExecError, ErrorOrigin
RawEvent, Vm, ReturnFlags, ExecError, ErrorOrigin, AccountIdOf,
};
use crate::{
gas::GasMeter, tests::{ExtBuilder, Test, MetaEvent},
exec::ExecReturnValue, CodeHash, Config,
gas::Gas,
storage, Error
storage::Storage,
tests::{ALICE, BOB, CHARLIE},
Error,
};
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;
const CHARLIE: u64 = 3;
const GAS_LIMIT: Gas = 10_000_000_000;
fn events() -> Vec<Event<Test>> {
@@ -869,7 +868,7 @@ mod tests {
ExtBuilder::default().build().execute_with(|| {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader);
let mut ctx = ExecutionContext::top_level(origin.clone(), &cfg, &vm, &loader);
set_balance(&origin, 100);
set_balance(&dest, 0);
@@ -902,13 +901,13 @@ mod tests {
ExtBuilder::default().build().execute_with(|| {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader);
let mut ctx = ExecutionContext::top_level(origin.clone(), &cfg, &vm, &loader);
place_contract(&BOB, return_ch);
set_balance(&origin, 100);
set_balance(&dest, 0);
let output = ctx.call(
dest,
dest.clone(),
55,
&mut GasMeter::<Test>::new(GAS_LIMIT),
vec![],
@@ -932,7 +931,7 @@ mod tests {
ExtBuilder::default().build().execute_with(|| {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader);
let mut ctx = ExecutionContext::top_level(origin.clone(), &cfg, &vm, &loader);
set_balance(&origin, 0);
let result = super::transfer(
@@ -1061,6 +1060,7 @@ mod tests {
&mut GasMeter::<Test>::new(GAS_LIMIT),
&input_data_ch,
vec![1, 2, 3, 4],
&[],
);
assert_matches!(result, Ok(_));
});
@@ -1120,13 +1120,13 @@ mod tests {
let vm = MockVm::new();
let witnessed_caller_bob = RefCell::new(None::<u64>);
let witnessed_caller_charlie = RefCell::new(None::<u64>);
let witnessed_caller_bob = RefCell::new(None::<AccountIdOf<Test>>);
let witnessed_caller_charlie = RefCell::new(None::<AccountIdOf<Test>>);
let mut loader = MockLoader::empty();
let bob_ch = loader.insert(|ctx| {
// Record the caller for bob.
*witnessed_caller_bob.borrow_mut() = Some(*ctx.ext.caller());
*witnessed_caller_bob.borrow_mut() = Some(ctx.ext.caller().clone());
// Call into CHARLIE contract.
assert_matches!(
@@ -1137,19 +1137,19 @@ mod tests {
});
let charlie_ch = loader.insert(|ctx| {
// Record the caller for charlie.
*witnessed_caller_charlie.borrow_mut() = Some(*ctx.ext.caller());
*witnessed_caller_charlie.borrow_mut() = Some(ctx.ext.caller().clone());
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader);
let mut ctx = ExecutionContext::top_level(origin.clone(), &cfg, &vm, &loader);
place_contract(&dest, bob_ch);
place_contract(&CHARLIE, charlie_ch);
let result = ctx.call(
dest,
dest.clone(),
0,
&mut GasMeter::<Test>::new(GAS_LIMIT),
vec![],
@@ -1217,6 +1217,7 @@ mod tests {
&mut GasMeter::<Test>::new(GAS_LIMIT),
&dummy_ch,
vec![],
&[],
),
Err(_)
);
@@ -1243,13 +1244,14 @@ mod tests {
&mut GasMeter::<Test>::new(GAS_LIMIT),
&dummy_ch,
vec![],
&[],
),
Ok((address, ref output)) if output.data == vec![80, 65, 83, 83] => address
);
// Check that the newly created account has the expected code hash and
// there are instantiation event.
assert_eq!(storage::code_hash::<Test>(&instantiated_contract_address).unwrap(), dummy_ch);
assert_eq!(Storage::<Test>::code_hash(&instantiated_contract_address).unwrap(), dummy_ch);
assert_eq!(&events(), &[
RawEvent::Instantiated(ALICE, instantiated_contract_address)
]);
@@ -1276,12 +1278,13 @@ mod tests {
&mut GasMeter::<Test>::new(GAS_LIMIT),
&dummy_ch,
vec![],
&[],
),
Ok((address, ref output)) if output.data == vec![70, 65, 73, 76] => address
);
// Check that the account has not been created.
assert!(storage::code_hash::<Test>(&instantiated_contract_address).is_err());
assert!(Storage::<Test>::code_hash(&instantiated_contract_address).is_err());
assert!(events().is_empty());
});
}
@@ -1292,7 +1295,7 @@ mod tests {
let mut loader = MockLoader::empty();
let dummy_ch = loader.insert(|_| exec_success());
let instantiated_contract_address = Rc::new(RefCell::new(None::<u64>));
let instantiated_contract_address = Rc::new(RefCell::new(None::<AccountIdOf<Test>>));
let instantiator_ch = loader.insert({
let dummy_ch = dummy_ch.clone();
let instantiated_contract_address = Rc::clone(&instantiated_contract_address);
@@ -1302,7 +1305,8 @@ mod tests {
&dummy_ch,
Config::<Test>::subsistence_threshold_uncached(),
ctx.gas_meter,
vec![]
vec![],
&[48, 49, 50],
).unwrap();
*instantiated_contract_address.borrow_mut() = address.into();
@@ -1326,7 +1330,7 @@ mod tests {
// Check that the newly created account has the expected code hash and
// there are instantiation event.
assert_eq!(storage::code_hash::<Test>(&instantiated_contract_address).unwrap(), dummy_ch);
assert_eq!(Storage::<Test>::code_hash(&instantiated_contract_address).unwrap(), dummy_ch);
assert_eq!(&events(), &[
RawEvent::Instantiated(BOB, instantiated_contract_address)
]);
@@ -1350,7 +1354,8 @@ mod tests {
&dummy_ch,
15u64,
ctx.gas_meter,
vec![]
vec![],
&[],
),
Err(ExecError {
error: DispatchError::Other("It's a trap!"),
@@ -1405,6 +1410,7 @@ mod tests {
&mut GasMeter::<Test>::new(GAS_LIMIT),
&terminate_ch,
vec![],
&[],
),
Err(Error::<Test>::NewContractNotFunded.into())
);
@@ -1437,6 +1443,7 @@ mod tests {
&mut GasMeter::<Test>::new(GAS_LIMIT),
&rent_allowance_ch,
vec![],
&[],
);
assert_matches!(result, Ok(_));
});
+83 -112
View File
@@ -93,14 +93,18 @@ pub mod weights;
#[cfg(test)]
mod tests;
use crate::exec::ExecutionContext;
use crate::wasm::{WasmLoader, WasmVm};
use crate::weights::WeightInfo;
pub use crate::gas::{Gas, GasMeter};
pub use crate::wasm::ReturnCode as RuntimeReturnCode;
pub use crate::schedule::{Schedule, HostFnWeights, InstructionWeights, Limits};
pub use crate::{
gas::{Gas, GasMeter},
wasm::ReturnCode as RuntimeReturnCode,
weights::WeightInfo,
schedule::{Schedule, HostFnWeights, InstructionWeights, Limits},
};
use crate::{
exec::ExecutionContext,
wasm::{WasmLoader, WasmVm},
rent::Rent,
storage::Storage,
};
use sp_core::crypto::UncheckedFrom;
use sp_std::{prelude::*, marker::PhantomData, fmt::Debug};
use codec::{Codec, Encode, Decode};
@@ -112,7 +116,7 @@ use sp_runtime::{
};
use frame_support::{
decl_module, decl_event, decl_storage, decl_error, ensure,
parameter_types, storage::child::ChildInfo,
storage::child::ChildInfo,
dispatch::{DispatchResult, DispatchResultWithPostInfo},
traits::{OnUnbalanced, Currency, Get, Time, Randomness},
};
@@ -125,11 +129,6 @@ use frame_support::weights::Weight;
pub type CodeHash<T> = <T as frame_system::Trait>::Hash;
pub type TrieId = Vec<u8>;
/// A function that generates an `AccountId` for a contract upon instantiation.
pub trait ContractAddressFor<CodeHash, AccountId> {
fn contract_address_for(code_hash: &CodeHash, data: &[u8], origin: &AccountId) -> AccountId;
}
/// Information for managing an account and its sub trie abstraction.
/// This is the required info to cache for an account
#[derive(Encode, Decode, RuntimeDebug)]
@@ -257,66 +256,11 @@ impl<T: Trait> From<AliveContractInfo<T>> for ContractInfo<T> {
}
}
/// 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).
pub trait TrieIdGenerator<AccountId> {
/// Get a trie id for an account, using reference to parent account trie id to ensure
/// uniqueness of trie id.
///
/// The implementation must ensure every new trie id is unique: two consecutive calls with the
/// same parameter needs to return different trie id values.
fn trie_id(account_id: &AccountId) -> TrieId;
}
/// Get trie id from `account_id`.
pub struct TrieIdFromParentCounter<T: Trait>(PhantomData<T>);
/// This generator uses inner counter for account id and applies the hash over `AccountId +
/// accountid_counter`.
impl<T: Trait> TrieIdGenerator<T::AccountId> for TrieIdFromParentCounter<T>
where
T::AccountId: AsRef<[u8]>
{
fn trie_id(account_id: &T::AccountId) -> TrieId {
// Note that skipping a value due to error is not an issue here.
// We only need uniqueness, not sequence.
let new_seed = AccountCounter::mutate(|v| {
*v = v.wrapping_add(1);
*v
});
let mut buf = Vec::new();
buf.extend_from_slice(account_id.as_ref());
buf.extend_from_slice(&new_seed.to_le_bytes()[..]);
T::Hashing::hash(&buf[..]).as_ref().into()
}
}
pub type BalanceOf<T> =
<<T as Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::Balance;
pub type NegativeImbalanceOf<T> =
<<T as Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::NegativeImbalance;
parameter_types! {
/// A reasonable default value for [`Trait::SignedClaimedHandicap`].
pub const DefaultSignedClaimHandicap: u32 = 2;
/// A reasonable default value for [`Trait::TombstoneDeposit`].
pub const DefaultTombstoneDeposit: u32 = 16;
/// A reasonable default value for [`Trait::StorageSizeOffset`].
pub const DefaultStorageSizeOffset: u32 = 8;
/// A reasonable default value for [`Trait::RentByteFee`].
pub const DefaultRentByteFee: u32 = 4;
/// A reasonable default value for [`Trait::RentDepositOffset`].
pub const DefaultRentDepositOffset: u32 = 1000;
/// A reasonable default value for [`Trait::SurchargeReward`].
pub const DefaultSurchargeReward: u32 = 150;
/// A reasonable default value for [`Trait::MaxDepth`].
pub const DefaultMaxDepth: u32 = 32;
/// A reasonable default value for [`Trait::MaxValueSize`].
pub const DefaultMaxValueSize: u32 = 16_384;
}
pub trait Trait: frame_system::Trait {
type Time: Time;
type Randomness: Randomness<Self::Hash>;
@@ -327,12 +271,6 @@ pub trait Trait: frame_system::Trait {
/// The overarching event type.
type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
/// A function type to get the contract address given the instantiator.
type DetermineContractAddress: ContractAddressFor<CodeHash<Self>, Self::AccountId>;
/// trie id generator
type TrieIdGenerator: TrieIdGenerator<Self::AccountId>;
/// Handler for rent payments.
type RentPayment: OnUnbalanced<NegativeImbalanceOf<Self>>;
@@ -383,32 +321,13 @@ pub trait Trait: frame_system::Trait {
type WeightInfo: WeightInfo;
}
/// Simple contract address determiner.
///
/// Address calculated from the code (of the constructor), input data to the constructor,
/// and the account id that requested the account creation.
///
/// Formula: `blake2_256(blake2_256(code) + blake2_256(data) + origin)`
pub struct SimpleAddressDeterminer<T: Trait>(PhantomData<T>);
impl<T: Trait> ContractAddressFor<CodeHash<T>, T::AccountId> for SimpleAddressDeterminer<T>
where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
{
fn contract_address_for(code_hash: &CodeHash<T>, data: &[u8], origin: &T::AccountId) -> T::AccountId {
let data_hash = T::Hashing::hash(data);
let mut buf = Vec::new();
buf.extend_from_slice(code_hash.as_ref());
buf.extend_from_slice(data_hash.as_ref());
buf.extend_from_slice(origin.as_ref());
UncheckedFrom::unchecked_from(T::Hashing::hash(&buf[..]))
}
}
decl_error! {
/// Error for the contracts module.
pub enum Error for Module<T: Trait> {
pub enum Error for Module<T: Trait>
where
T::AccountId: UncheckedFrom<T::Hash>,
T::AccountId: AsRef<[u8]>,
{
/// A new schedule must have a greater version than the current one.
InvalidScheduleVersion,
/// An origin must be signed or inherent and auxiliary sender only provided on inherent.
@@ -455,12 +374,21 @@ decl_error! {
ContractTrapped,
/// The size defined in `T::MaxValueSize` was exceeded.
ValueTooLarge,
/// The action performed is not allowed while the contract performing it is already
/// on the call stack. Those actions are contract self destruction and restoration
/// of a tombstone.
ReentranceDenied,
}
}
decl_module! {
/// Contracts module.
pub struct Module<T: Trait> for enum Call where origin: <T as frame_system::Trait>::Origin {
pub struct Module<T: Trait> for enum Call
where
origin: T::Origin,
T::AccountId: UncheckedFrom<T::Hash>,
T::AccountId: AsRef<[u8]>,
{
type Error = Error<T>;
/// Number of block delay an extrinsic claim surcharge has.
@@ -563,29 +491,38 @@ decl_module! {
gas_meter.into_dispatch_result(result)
}
/// Instantiates a new contract from the `codehash` generated by `put_code`, optionally transferring some balance.
/// Instantiates a new contract from the `code_hash` generated by `put_code`,
/// optionally transferring some balance.
///
/// The supplied `salt` is used for contract address deriviation. See `fn contract_address`.
///
/// Instantiation is executed as follows:
///
/// - The destination address is computed based on the sender and hash of the code.
/// - The destination address is computed based on the sender, code_hash and the salt.
/// - The smart-contract account is created at the computed address.
/// - The `ctor_code` is executed in the context of the newly-created account. Buffer returned
/// after the execution is saved as the `code` of the account. That code will be invoked
/// upon any call received by this account.
/// - The contract is initialized.
#[weight = T::WeightInfo::instantiate(data.len() as u32 / 1024).saturating_add(*gas_limit)]
#[weight =
T::WeightInfo::instantiate(
data.len() as u32 / 1024,
salt.len() as u32 / 1024,
).saturating_add(*gas_limit)
]
pub fn instantiate(
origin,
#[compact] endowment: BalanceOf<T>,
#[compact] gas_limit: Gas,
code_hash: CodeHash<T>,
data: Vec<u8>
data: Vec<u8>,
salt: Vec<u8>,
) -> DispatchResultWithPostInfo {
let origin = ensure_signed(origin)?;
let mut gas_meter = GasMeter::new(gas_limit);
let result = Self::execute_wasm(origin, &mut gas_meter, |ctx, gas_meter| {
ctx.instantiate(endowment, gas_meter, &code_hash, data)
ctx.instantiate(endowment, gas_meter, &code_hash, data, &salt)
.map(|(_address, output)| output)
});
gas_meter.into_dispatch_result(result)
@@ -619,7 +556,7 @@ decl_module! {
};
// If poking the contract has lead to eviction of the contract, give out the rewards.
if rent::snitch_contract_should_be_evicted::<T>(&dest, handicap) {
if Rent::<T>::snitch_contract_should_be_evicted(&dest, handicap) {
T::Currency::deposit_into_existing(&rewarded, T::SurchargeReward::get())?;
}
}
@@ -627,7 +564,10 @@ decl_module! {
}
/// Public APIs provided by the contracts module.
impl<T: Trait> Module<T> {
impl<T: Trait> Module<T>
where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
/// Perform a call to a specified contract.
///
/// This function is similar to `Self::call`, but doesn't perform any address lookups and better
@@ -659,12 +599,12 @@ impl<T: Trait> Module<T> {
.get_alive()
.ok_or(ContractAccessError::IsTombstone)?;
let maybe_value = storage::read_contract_storage(&contract_info.trie_id, &key);
let maybe_value = Storage::<T>::read(&contract_info.trie_id, &key);
Ok(maybe_value)
}
pub fn rent_projection(address: T::AccountId) -> RentProjectionResult<T::BlockNumber> {
rent::compute_rent_projection::<T>(&address)
Rent::<T>::compute_projection(&address)
}
/// Put code for benchmarks which does not check or instrument the code.
@@ -674,9 +614,34 @@ impl<T: Trait> Module<T> {
let result = wasm::save_code_raw::<T>(code, &schedule);
result.map(|_| ()).map_err(Into::into)
}
/// Determine the address of a contract,
///
/// This is the address generation function used by contract instantation. Its result
/// is only dependend on its inputs. It can therefore be used to reliably predict the
/// address of a contract. This is akin to the formular of eth's CRATE2 opcode. There
/// is no CREATE equivalent because CREATE2 is strictly more powerful.
///
/// Formula: `hash(deploying_address ++ code_hash ++ salt)`
pub fn contract_address(
deploying_address: &T::AccountId,
code_hash: &CodeHash<T>,
salt: &[u8],
) -> T::AccountId
{
let buf: Vec<_> = deploying_address.as_ref().iter()
.chain(code_hash.as_ref())
.chain(salt)
.cloned()
.collect();
UncheckedFrom::unchecked_from(T::Hashing::hash(&buf))
}
}
impl<T: Trait> Module<T> {
impl<T: Trait> Module<T>
where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
fn execute_wasm(
origin: T::AccountId,
gas_meter: &mut GasMeter<T>,
@@ -734,7 +699,10 @@ decl_event! {
}
decl_storage! {
trait Store for Module<T: Trait> as Contracts {
trait Store for Module<T: Trait> as Contracts
where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
{
/// Current cost schedule for contracts.
CurrentSchedule get(fn current_schedule) config(): Schedule<T> = Default::default();
/// A mapping from an original code hash to the original code, untouched by instrumentation.
@@ -762,7 +730,10 @@ pub struct Config<T: Trait> {
pub max_value_size: u32,
}
impl<T: Trait> Config<T> {
impl<T: Trait> Config<T>
where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
{
fn preload() -> Config<T> {
Config {
schedule: <Module<T>>::current_schedule(),
+383 -372
View File
@@ -18,15 +18,19 @@
use crate::{
AliveContractInfo, BalanceOf, ContractInfo, ContractInfoOf, Module, RawEvent,
TombstoneContractInfo, Trait, CodeHash, Config
TombstoneContractInfo, Trait, CodeHash, Config, Error,
};
use sp_std::prelude::*;
use sp_io::hashing::blake2_256;
use sp_core::crypto::UncheckedFrom;
use frame_support::storage::child;
use frame_support::traits::{Currency, ExistenceRequirement, Get, OnUnbalanced, WithdrawReasons};
use frame_support::StorageMap;
use pallet_contracts_primitives::{ContractAccessError, RentProjection, RentProjectionResult};
use sp_runtime::traits::{Bounded, CheckedDiv, CheckedMul, SaturatedConversion, Saturating, Zero};
use sp_runtime::{
DispatchError,
traits::{Bounded, CheckedDiv, CheckedMul, SaturatedConversion, Saturating, Zero},
};
/// The amount to charge.
///
@@ -82,405 +86,412 @@ enum Verdict<T: Trait> {
Charge { amount: OutstandingAmount<T> },
}
/// Returns a fee charged per block from the contract.
///
/// This function accounts for the storage rent deposit. I.e. if the contract possesses enough funds
/// then the fee can drop to zero.
fn compute_fee_per_block<T: Trait>(
free_balance: &BalanceOf<T>,
contract: &AliveContractInfo<T>,
) -> BalanceOf<T> {
let free_storage = free_balance
.checked_div(&T::RentDepositOffset::get())
.unwrap_or_else(Zero::zero);
pub struct Rent<T>(sp_std::marker::PhantomData<T>);
// For now, we treat every empty KV pair as if it was one byte long.
let empty_pairs_equivalent = contract.empty_pair_count;
impl<T> Rent<T>
where
T: Trait,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
{
/// Returns a fee charged per block from the contract.
///
/// This function accounts for the storage rent deposit. I.e. if the contract possesses enough funds
/// then the fee can drop to zero.
fn compute_fee_per_block(
free_balance: &BalanceOf<T>,
contract: &AliveContractInfo<T>
) -> BalanceOf<T> {
let free_storage = free_balance
.checked_div(&T::RentDepositOffset::get())
.unwrap_or_else(Zero::zero);
let effective_storage_size = <BalanceOf<T>>::from(
contract.storage_size + T::StorageSizeOffset::get() + empty_pairs_equivalent,
)
.saturating_sub(free_storage);
// For now, we treat every empty KV pair as if it was one byte long.
let empty_pairs_equivalent = contract.empty_pair_count;
effective_storage_size
.checked_mul(&T::RentByteFee::get())
.unwrap_or_else(|| <BalanceOf<T>>::max_value())
}
let effective_storage_size = <BalanceOf<T>>::from(
contract.storage_size + T::StorageSizeOffset::get() + empty_pairs_equivalent,
)
.saturating_sub(free_storage);
/// Returns amount of funds available to consume by rent mechanism.
///
/// Rent mechanism cannot consume more than `rent_allowance` set by the contract and it cannot make
/// the balance lower than [`subsistence_threshold`].
///
/// In case the toal_balance is below the subsistence threshold, this function returns `None`.
fn rent_budget<T: Trait>(
total_balance: &BalanceOf<T>,
free_balance: &BalanceOf<T>,
contract: &AliveContractInfo<T>,
) -> Option<BalanceOf<T>> {
let subsistence_threshold = Config::<T>::subsistence_threshold_uncached();
// Reserved balance contributes towards the subsistence threshold to stay consistent
// with the existential deposit where the reserved balance is also counted.
if *total_balance < subsistence_threshold {
return None;
effective_storage_size
.checked_mul(&T::RentByteFee::get())
.unwrap_or_else(|| <BalanceOf<T>>::max_value())
}
// However, reserved balance cannot be charged so we need to use the free balance
// to calculate the actual budget (which can be 0).
let rent_allowed_to_charge = free_balance.saturating_sub(subsistence_threshold);
Some(<BalanceOf<T>>::min(
contract.rent_allowance,
rent_allowed_to_charge,
))
}
/// Consider the case for rent payment of the given account and returns a `Verdict`.
///
/// Use `handicap` in case you want to change the reference block number. (To get more details see
/// `snitch_contract_should_be_evicted` ).
fn consider_case<T: Trait>(
account: &T::AccountId,
current_block_number: T::BlockNumber,
handicap: T::BlockNumber,
contract: &AliveContractInfo<T>,
) -> Verdict<T> {
// How much block has passed since the last deduction for the contract.
let blocks_passed = {
// Calculate an effective block number, i.e. after adjusting for handicap.
let effective_block_number = current_block_number.saturating_sub(handicap);
effective_block_number.saturating_sub(contract.deduct_block)
};
if blocks_passed.is_zero() {
// Rent has already been paid
return Verdict::Exempt;
}
let total_balance = T::Currency::total_balance(account);
let free_balance = T::Currency::free_balance(account);
// An amount of funds to charge per block for storage taken up by the contract.
let fee_per_block = compute_fee_per_block::<T>(&free_balance, contract);
if fee_per_block.is_zero() {
// The rent deposit offset reduced the fee to 0. This means that the contract
// gets the rent for free.
return Verdict::Exempt;
}
let rent_budget = match rent_budget::<T>(&total_balance, &free_balance, contract) {
Some(rent_budget) => rent_budget,
None => {
// The contract's total balance is already below subsistence threshold. That
// indicates that the contract cannot afford to leave a tombstone.
//
// So cleanly wipe the contract.
return Verdict::Kill;
/// Returns amount of funds available to consume by rent mechanism.
///
/// Rent mechanism cannot consume more than `rent_allowance` set by the contract and it cannot make
/// the balance lower than [`subsistence_threshold`].
///
/// In case the toal_balance is below the subsistence threshold, this function returns `None`.
fn rent_budget(
total_balance: &BalanceOf<T>,
free_balance: &BalanceOf<T>,
contract: &AliveContractInfo<T>,
) -> Option<BalanceOf<T>> {
let subsistence_threshold = Config::<T>::subsistence_threshold_uncached();
// Reserved balance contributes towards the subsistence threshold to stay consistent
// with the existential deposit where the reserved balance is also counted.
if *total_balance < subsistence_threshold {
return None;
}
};
let dues = fee_per_block
.checked_mul(&blocks_passed.saturated_into::<u32>().into())
.unwrap_or_else(|| <BalanceOf<T>>::max_value());
let insufficient_rent = rent_budget < dues;
// However, reserved balance cannot be charged so we need to use the free balance
// to calculate the actual budget (which can be 0).
let rent_allowed_to_charge = free_balance.saturating_sub(subsistence_threshold);
Some(<BalanceOf<T>>::min(
contract.rent_allowance,
rent_allowed_to_charge,
))
}
// If the rent payment cannot be withdrawn due to locks on the account balance, then evict the
// account.
//
// NOTE: This seems problematic because it provides a way to tombstone an account while
// avoiding the last rent payment. In effect, someone could retroactively set rent_allowance
// for their contract to 0.
let dues_limited = dues.min(rent_budget);
let can_withdraw_rent = T::Currency::ensure_can_withdraw(
account,
dues_limited,
WithdrawReasons::FEE,
free_balance.saturating_sub(dues_limited),
)
.is_ok();
if insufficient_rent || !can_withdraw_rent {
// The contract cannot afford the rent payment and has a balance above the subsistence
// threshold, so it leaves a tombstone.
let amount = if can_withdraw_rent {
Some(OutstandingAmount::new(dues_limited))
} else {
None
/// Consider the case for rent payment of the given account and returns a `Verdict`.
///
/// Use `handicap` in case you want to change the reference block number. (To get more details see
/// `snitch_contract_should_be_evicted` ).
fn consider_case(
account: &T::AccountId,
current_block_number: T::BlockNumber,
handicap: T::BlockNumber,
contract: &AliveContractInfo<T>,
) -> Verdict<T> {
// How much block has passed since the last deduction for the contract.
let blocks_passed = {
// Calculate an effective block number, i.e. after adjusting for handicap.
let effective_block_number = current_block_number.saturating_sub(handicap);
effective_block_number.saturating_sub(contract.deduct_block)
};
return Verdict::Evict { amount };
}
return Verdict::Charge {
// We choose to use `dues_limited` here instead of `dues` just to err on the safer side.
amount: OutstandingAmount::new(dues_limited),
};
}
/// Enacts the given verdict and returns the updated `ContractInfo`.
///
/// `alive_contract_info` should be from the same address as `account`.
fn enact_verdict<T: Trait>(
account: &T::AccountId,
alive_contract_info: AliveContractInfo<T>,
current_block_number: T::BlockNumber,
verdict: Verdict<T>,
) -> Option<ContractInfo<T>> {
match verdict {
Verdict::Exempt => return Some(ContractInfo::Alive(alive_contract_info)),
Verdict::Kill => {
<ContractInfoOf<T>>::remove(account);
child::kill_storage(
&alive_contract_info.child_trie_info(),
);
<Module<T>>::deposit_event(RawEvent::Evicted(account.clone(), false));
None
if blocks_passed.is_zero() {
// Rent has already been paid
return Verdict::Exempt;
}
Verdict::Evict { amount } => {
if let Some(amount) = amount {
amount.withdraw(account);
let total_balance = T::Currency::total_balance(account);
let free_balance = T::Currency::free_balance(account);
// An amount of funds to charge per block for storage taken up by the contract.
let fee_per_block = Self::compute_fee_per_block(&free_balance, contract);
if fee_per_block.is_zero() {
// The rent deposit offset reduced the fee to 0. This means that the contract
// gets the rent for free.
return Verdict::Exempt;
}
let rent_budget = match Self::rent_budget(&total_balance, &free_balance, contract) {
Some(rent_budget) => rent_budget,
None => {
// The contract's total balance is already below subsistence threshold. That
// indicates that the contract cannot afford to leave a tombstone.
//
// So cleanly wipe the contract.
return Verdict::Kill;
}
};
// Note: this operation is heavy.
let child_storage_root = child::root(
&alive_contract_info.child_trie_info(),
);
let dues = fee_per_block
.checked_mul(&blocks_passed.saturated_into::<u32>().into())
.unwrap_or_else(|| <BalanceOf<T>>::max_value());
let insufficient_rent = rent_budget < dues;
let tombstone = <TombstoneContractInfo<T>>::new(
&child_storage_root[..],
alive_contract_info.code_hash,
);
let tombstone_info = ContractInfo::Tombstone(tombstone);
<ContractInfoOf<T>>::insert(account, &tombstone_info);
// If the rent payment cannot be withdrawn due to locks on the account balance, then evict the
// account.
//
// NOTE: This seems problematic because it provides a way to tombstone an account while
// avoiding the last rent payment. In effect, someone could retroactively set rent_allowance
// for their contract to 0.
let dues_limited = dues.min(rent_budget);
let can_withdraw_rent = T::Currency::ensure_can_withdraw(
account,
dues_limited,
WithdrawReasons::FEE,
free_balance.saturating_sub(dues_limited),
)
.is_ok();
child::kill_storage(
&alive_contract_info.child_trie_info(),
);
<Module<T>>::deposit_event(RawEvent::Evicted(account.clone(), true));
Some(tombstone_info)
if insufficient_rent || !can_withdraw_rent {
// The contract cannot afford the rent payment and has a balance above the subsistence
// threshold, so it leaves a tombstone.
let amount = if can_withdraw_rent {
Some(OutstandingAmount::new(dues_limited))
} else {
None
};
return Verdict::Evict { amount };
}
Verdict::Charge { amount } => {
let contract_info = ContractInfo::Alive(AliveContractInfo::<T> {
rent_allowance: alive_contract_info.rent_allowance - amount.peek(),
deduct_block: current_block_number,
..alive_contract_info
});
<ContractInfoOf<T>>::insert(account, &contract_info);
amount.withdraw(account);
Some(contract_info)
}
}
}
/// Make account paying the rent for the current block number
///
/// NOTE this function performs eviction eagerly. All changes are read and written directly to
/// storage.
pub fn collect_rent<T: Trait>(account: &T::AccountId) -> Option<ContractInfo<T>> {
let contract_info = <ContractInfoOf<T>>::get(account);
let alive_contract_info = match contract_info {
None | Some(ContractInfo::Tombstone(_)) => return contract_info,
Some(ContractInfo::Alive(contract)) => contract,
};
let current_block_number = <frame_system::Module<T>>::block_number();
let verdict = consider_case::<T>(
account,
current_block_number,
Zero::zero(),
&alive_contract_info,
);
enact_verdict(account, alive_contract_info, current_block_number, verdict)
}
/// Process a report that a contract under the given address should be evicted.
///
/// Enact the eviction right away if the contract should be evicted and return true.
/// Otherwise, **do nothing** and return false.
///
/// The `handicap` parameter gives a way to check the rent to a moment in the past instead
/// of current block. E.g. if the contract is going to be evicted at the current block,
/// `handicap = 1` can defer the eviction for 1 block. This is useful to handicap certain snitchers
/// relative to others.
///
/// NOTE this function performs eviction eagerly. All changes are read and written directly to
/// storage.
pub fn snitch_contract_should_be_evicted<T: Trait>(
account: &T::AccountId,
handicap: T::BlockNumber,
) -> bool {
let contract_info = <ContractInfoOf<T>>::get(account);
let alive_contract_info = match contract_info {
None | Some(ContractInfo::Tombstone(_)) => return false,
Some(ContractInfo::Alive(contract)) => contract,
};
let current_block_number = <frame_system::Module<T>>::block_number();
let verdict = consider_case::<T>(
account,
current_block_number,
handicap,
&alive_contract_info,
);
// Enact the verdict only if the contract gets removed.
match verdict {
Verdict::Kill | Verdict::Evict { .. } => {
enact_verdict(account, alive_contract_info, current_block_number, verdict);
true
}
_ => false,
}
}
/// Returns the projected time a given contract will be able to sustain paying its rent. The
/// returned projection is relevant for the current block, i.e. it is as if the contract was
/// accessed at the beginning of the current block. Returns `None` in case if the contract was
/// evicted before or as a result of the rent collection.
///
/// The returned value is only an estimation. It doesn't take into account any top ups, changing the
/// rent allowance, or any problems coming from withdrawing the dues.
///
/// NOTE that this is not a side-effect free function! It will actually collect rent and then
/// compute the projection. This function is only used for implementation of an RPC method through
/// `RuntimeApi` meaning that the changes will be discarded anyway.
pub fn compute_rent_projection<T: Trait>(
account: &T::AccountId,
) -> RentProjectionResult<T::BlockNumber> {
let contract_info = <ContractInfoOf<T>>::get(account);
let alive_contract_info = match contract_info {
None | Some(ContractInfo::Tombstone(_)) => return Err(ContractAccessError::IsTombstone),
Some(ContractInfo::Alive(contract)) => contract,
};
let current_block_number = <frame_system::Module<T>>::block_number();
let verdict = consider_case::<T>(
account,
current_block_number,
Zero::zero(),
&alive_contract_info,
);
let new_contract_info =
enact_verdict(account, alive_contract_info, current_block_number, verdict);
// Check what happened after enaction of the verdict.
let alive_contract_info = match new_contract_info {
None | Some(ContractInfo::Tombstone(_)) => return Err(ContractAccessError::IsTombstone),
Some(ContractInfo::Alive(contract)) => contract,
};
// Compute how much would the fee per block be with the *updated* balance.
let total_balance = T::Currency::total_balance(account);
let free_balance = T::Currency::free_balance(account);
let fee_per_block = compute_fee_per_block::<T>(&free_balance, &alive_contract_info);
if fee_per_block.is_zero() {
return Ok(RentProjection::NoEviction);
return Verdict::Charge {
// We choose to use `dues_limited` here instead of `dues` just to err on the safer side.
amount: OutstandingAmount::new(dues_limited),
};
}
// Then compute how much the contract will sustain under these circumstances.
let rent_budget = rent_budget::<T>(&total_balance, &free_balance, &alive_contract_info).expect(
"the contract exists and in the alive state;
the updated balance must be greater than subsistence deposit;
this function doesn't return `None`;
qed
",
);
let blocks_left = match rent_budget.checked_div(&fee_per_block) {
Some(blocks_left) => blocks_left,
None => {
// `fee_per_block` is not zero here, so `checked_div` can return `None` if
// there is an overflow. This cannot happen with integers though. Return
// `NoEviction` here just in case.
/// Enacts the given verdict and returns the updated `ContractInfo`.
///
/// `alive_contract_info` should be from the same address as `account`.
fn enact_verdict(
account: &T::AccountId,
alive_contract_info: AliveContractInfo<T>,
current_block_number: T::BlockNumber,
verdict: Verdict<T>,
) -> Option<ContractInfo<T>> {
match verdict {
Verdict::Exempt => return Some(ContractInfo::Alive(alive_contract_info)),
Verdict::Kill => {
<ContractInfoOf<T>>::remove(account);
child::kill_storage(
&alive_contract_info.child_trie_info(),
);
<Module<T>>::deposit_event(RawEvent::Evicted(account.clone(), false));
None
}
Verdict::Evict { amount } => {
if let Some(amount) = amount {
amount.withdraw(account);
}
// Note: this operation is heavy.
let child_storage_root = child::root(
&alive_contract_info.child_trie_info(),
);
let tombstone = <TombstoneContractInfo<T>>::new(
&child_storage_root[..],
alive_contract_info.code_hash,
);
let tombstone_info = ContractInfo::Tombstone(tombstone);
<ContractInfoOf<T>>::insert(account, &tombstone_info);
child::kill_storage(
&alive_contract_info.child_trie_info(),
);
<Module<T>>::deposit_event(RawEvent::Evicted(account.clone(), true));
Some(tombstone_info)
}
Verdict::Charge { amount } => {
let contract_info = ContractInfo::Alive(AliveContractInfo::<T> {
rent_allowance: alive_contract_info.rent_allowance - amount.peek(),
deduct_block: current_block_number,
..alive_contract_info
});
<ContractInfoOf<T>>::insert(account, &contract_info);
amount.withdraw(account);
Some(contract_info)
}
}
}
/// Make account paying the rent for the current block number
///
/// NOTE this function performs eviction eagerly. All changes are read and written directly to
/// storage.
pub fn collect(account: &T::AccountId) -> Option<ContractInfo<T>> {
let contract_info = <ContractInfoOf<T>>::get(account);
let alive_contract_info = match contract_info {
None | Some(ContractInfo::Tombstone(_)) => return contract_info,
Some(ContractInfo::Alive(contract)) => contract,
};
let current_block_number = <frame_system::Module<T>>::block_number();
let verdict = Self::consider_case(
account,
current_block_number,
Zero::zero(),
&alive_contract_info,
);
Self::enact_verdict(account, alive_contract_info, current_block_number, verdict)
}
/// Process a report that a contract under the given address should be evicted.
///
/// Enact the eviction right away if the contract should be evicted and return true.
/// Otherwise, **do nothing** and return false.
///
/// The `handicap` parameter gives a way to check the rent to a moment in the past instead
/// of current block. E.g. if the contract is going to be evicted at the current block,
/// `handicap = 1` can defer the eviction for 1 block. This is useful to handicap certain snitchers
/// relative to others.
///
/// NOTE this function performs eviction eagerly. All changes are read and written directly to
/// storage.
pub fn snitch_contract_should_be_evicted(
account: &T::AccountId,
handicap: T::BlockNumber,
) -> bool {
let contract_info = <ContractInfoOf<T>>::get(account);
let alive_contract_info = match contract_info {
None | Some(ContractInfo::Tombstone(_)) => return false,
Some(ContractInfo::Alive(contract)) => contract,
};
let current_block_number = <frame_system::Module<T>>::block_number();
let verdict = Self::consider_case(
account,
current_block_number,
handicap,
&alive_contract_info,
);
// Enact the verdict only if the contract gets removed.
match verdict {
Verdict::Kill | Verdict::Evict { .. } => {
Self::enact_verdict(account, alive_contract_info, current_block_number, verdict);
true
}
_ => false,
}
}
/// Returns the projected time a given contract will be able to sustain paying its rent. The
/// returned projection is relevant for the current block, i.e. it is as if the contract was
/// accessed at the beginning of the current block. Returns `None` in case if the contract was
/// evicted before or as a result of the rent collection.
///
/// The returned value is only an estimation. It doesn't take into account any top ups, changing the
/// rent allowance, or any problems coming from withdrawing the dues.
///
/// NOTE that this is not a side-effect free function! It will actually collect rent and then
/// compute the projection. This function is only used for implementation of an RPC method through
/// `RuntimeApi` meaning that the changes will be discarded anyway.
pub fn compute_projection(
account: &T::AccountId,
) -> RentProjectionResult<T::BlockNumber> {
let contract_info = <ContractInfoOf<T>>::get(account);
let alive_contract_info = match contract_info {
None | Some(ContractInfo::Tombstone(_)) => return Err(ContractAccessError::IsTombstone),
Some(ContractInfo::Alive(contract)) => contract,
};
let current_block_number = <frame_system::Module<T>>::block_number();
let verdict = Self::consider_case(
account,
current_block_number,
Zero::zero(),
&alive_contract_info,
);
let new_contract_info =
Self::enact_verdict(account, alive_contract_info, current_block_number, verdict);
// Check what happened after enaction of the verdict.
let alive_contract_info = match new_contract_info {
None | Some(ContractInfo::Tombstone(_)) => return Err(ContractAccessError::IsTombstone),
Some(ContractInfo::Alive(contract)) => contract,
};
// Compute how much would the fee per block be with the *updated* balance.
let total_balance = T::Currency::total_balance(account);
let free_balance = T::Currency::free_balance(account);
let fee_per_block = Self::compute_fee_per_block(&free_balance, &alive_contract_info);
if fee_per_block.is_zero() {
return Ok(RentProjection::NoEviction);
}
};
let blocks_left = blocks_left.saturated_into::<u32>().into();
Ok(RentProjection::EvictionAt(
current_block_number + blocks_left,
))
}
// Then compute how much the contract will sustain under these circumstances.
let rent_budget = Self::rent_budget(&total_balance, &free_balance, &alive_contract_info).expect(
"the contract exists and in the alive state;
the updated balance must be greater than subsistence deposit;
this function doesn't return `None`;
qed
",
);
let blocks_left = match rent_budget.checked_div(&fee_per_block) {
Some(blocks_left) => blocks_left,
None => {
// `fee_per_block` is not zero here, so `checked_div` can return `None` if
// there is an overflow. This cannot happen with integers though. Return
// `NoEviction` here just in case.
return Ok(RentProjection::NoEviction);
}
};
/// 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 blocks_left = blocks_left.saturated_into::<u32>().into();
Ok(RentProjection::EvictionAt(
current_block_number + blocks_left,
))
}
let dest_tombstone = <ContractInfoOf<T>>::get(&dest)
.and_then(|c| c.get_tombstone())
.ok_or("Cannot restore to inexisting or alive contract")?;
/// Restores the destination account using the origin as prototype.
///
/// The restoration will be performed iff:
/// - origin exists and is alive,
/// - the origin's storage is not written in the current block
/// - the restored account has tombstone
/// - the tombstone matches the hash of the origin storage root, and code hash.
///
/// Upon succesful restoration, `origin` will be destroyed, all its funds are transferred to
/// the restored account. The restored account will inherit the last write block and its last
/// deduct block will be set to the current block.
pub fn restore_to(
origin: T::AccountId,
dest: T::AccountId,
code_hash: CodeHash<T>,
rent_allowance: BalanceOf<T>,
delta: Vec<crate::exec::StorageKey>,
) -> Result<(), DispatchError> {
let mut origin_contract = <ContractInfoOf<T>>::get(&origin)
.and_then(|c| c.get_alive())
.ok_or(Error::<T>::InvalidSourceContract)?;
let last_write = if !delta.is_empty() {
Some(current_block)
} else {
origin_contract.last_write
};
let child_trie_info = origin_contract.child_trie_info();
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 current_block = <frame_system::Module<T>>::block_number();
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);
if origin_contract.last_write == Some(current_block) {
return Err(Error::<T>::InvalidContractOrigin.into());
}
return Err("Tombstones don't match");
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(&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(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(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(())
}
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(())
}
+6 -2
View File
@@ -302,6 +302,9 @@ pub struct HostFnWeights<T: Trait> {
/// Weight per output byte received through `seal_instantiate`.
pub instantiate_per_output_byte: Weight,
/// Weight per salt byte supplied to `seal_instantiate`.
pub instantiate_per_salt_byte: Weight,
/// Weight of calling `seal_hash_sha_256`.
pub hash_sha2_256: Weight,
@@ -535,8 +538,9 @@ impl<T: Trait> Default for HostFnWeights<T> {
call_per_input_byte: cost_byte_batched_args!(seal_call_per_transfer_input_output_kb, 0, 1, 0),
call_per_output_byte: cost_byte_batched_args!(seal_call_per_transfer_input_output_kb, 0, 0, 1),
instantiate: cost_batched!(seal_instantiate),
instantiate_per_input_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_kb, 1, 0),
instantiate_per_output_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_kb, 0, 1),
instantiate_per_input_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_salt_kb, 1, 0, 0),
instantiate_per_output_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_salt_kb, 0, 1, 0),
instantiate_per_salt_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_salt_kb, 0, 0, 1),
hash_sha2_256: cost_batched!(seal_hash_sha2_256),
hash_sha2_256_per_byte: cost_byte_batched!(seal_hash_sha2_256_per_kb),
hash_keccak_256: cost_batched!(seal_hash_keccak_256),
+186 -154
View File
@@ -19,10 +19,13 @@
use crate::{
exec::{AccountIdOf, StorageKey},
AliveContractInfo, BalanceOf, CodeHash, ContractInfo, ContractInfoOf, Trait, TrieId,
AccountCounter,
};
use sp_std::prelude::*;
use sp_std::marker::PhantomData;
use sp_io::hashing::blake2_256;
use sp_runtime::traits::Bounded;
use sp_core::crypto::UncheckedFrom;
use frame_support::{storage::child, StorageMap};
/// An error that means that the account requested either doesn't exist or represents a tombstone
@@ -30,167 +33,196 @@ use frame_support::{storage::child, StorageMap};
#[cfg_attr(test, derive(PartialEq, Eq, Debug))]
pub struct ContractAbsentError;
/// Reads a storage kv pair of a contract.
///
/// The read is performed from the `trie_id` only. The `address` is not necessary. If the contract
/// doesn't store under the given `key` `None` is returned.
pub fn read_contract_storage(trie_id: &TrieId, key: &StorageKey) -> Option<Vec<u8>> {
child::get_raw(&crate::child_trie_info(&trie_id), &blake2_256(key))
}
pub struct Storage<T>(PhantomData<T>);
/// 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) => {}
impl<T> Storage<T>
where
T: Trait,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
{
/// 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(trie_id: &TrieId, key: &StorageKey) -> Option<Vec<u8>> {
child::get_raw(&crate::child_trie_info(&trie_id), &blake2_256(key))
}
// 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);
/// 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`, this function also requires the `account` ID.
///
/// If the contract specified by the id `account` doesn't exist `Err` is returned.`
pub fn write(
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),
};
new_info.last_write = Some(<frame_system::Module<T>>::block_number());
<ContractInfoOf<T>>::insert(&account, ContractInfo::Alive(new_info));
let hashed_key = blake2_256(key);
let child_trie_info = &crate::child_trie_info(&trie_id);
// 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),
}
// 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);
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.
#[cfg(test)]
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,
// 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;
}
}
.into(),
);
(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(())
})
}
}
/// 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));
}
/// Returns the rent allowance set for the contract give by the account id.
pub fn rent_allowance(
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(
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),
})
}
/// Creates a new contract descriptor in the storage with the given code hash at the given address.
///
/// Returns `Err` if there is already a contract (or a tombstone) exists at the given address.
pub fn place_contract(
account: &AccountIdOf<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(address: &AccountIdOf<T>, trie_id: &TrieId) {
<ContractInfoOf<T>>::remove(address);
child::kill_storage(&crate::child_trie_info(&trie_id));
}
/// This generator uses inner counter for account id and applies the hash over `AccountId +
/// accountid_counter`.
pub fn generate_trie_id(account_id: &AccountIdOf<T>) -> TrieId {
use frame_support::StorageValue;
use sp_runtime::traits::Hash;
// Note that skipping a value due to error is not an issue here.
// We only need uniqueness, not sequence.
let new_seed = AccountCounter::mutate(|v| {
*v = v.wrapping_add(1);
*v
});
let buf: Vec<_> = account_id.as_ref().iter()
.chain(&new_seed.to_le_bytes())
.cloned()
.collect();
T::Hashing::hash(&buf).as_ref().into()
}
/// Returns the code hash of the contract specified by `account` ID.
#[cfg(test)]
pub fn code_hash(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)
}
}
File diff suppressed because it is too large Load Diff
@@ -30,6 +30,7 @@ use crate::wasm::{prepare, runtime::Env, PrefabWasmModule};
use crate::{CodeHash, CodeStorage, PristineCode, Schedule, Trait};
use sp_std::prelude::*;
use sp_runtime::traits::Hash;
use sp_core::crypto::UncheckedFrom;
use frame_support::StorageMap;
/// Put code in the storage. The hash of code is used as a key and is returned
@@ -39,7 +40,7 @@ use frame_support::StorageMap;
pub fn save<T: Trait>(
original_code: Vec<u8>,
schedule: &Schedule<T>,
) -> Result<CodeHash<T>, &'static str> {
) -> Result<CodeHash<T>, &'static str> where T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]> {
let prefab_module = prepare::prepare_contract::<Env, T>(&original_code, schedule)?;
let code_hash = T::Hashing::hash(&original_code);
@@ -57,7 +58,7 @@ pub fn save<T: Trait>(
pub fn save_raw<T: Trait>(
original_code: Vec<u8>,
schedule: &Schedule<T>,
) -> Result<CodeHash<T>, &'static str> {
) -> Result<CodeHash<T>, &'static str> where T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]> {
let prefab_module = prepare::benchmarking::prepare_contract::<T>(&original_code, schedule)?;
let code_hash = T::Hashing::hash(&original_code);
@@ -75,7 +76,7 @@ pub fn save_raw<T: Trait>(
pub fn load<T: Trait>(
code_hash: &CodeHash<T>,
schedule: &Schedule<T>,
) -> Result<PrefabWasmModule, &'static str> {
) -> Result<PrefabWasmModule, &'static str> where T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]> {
let mut prefab_module =
<CodeStorage<T>>::get(code_hash).ok_or_else(|| "code is not found")?;
@@ -127,7 +127,12 @@ macro_rules! define_func {
fn $name< E: $seal_ty >(
$ctx: &mut $crate::wasm::Runtime<E>,
args: &[sp_sandbox::Value],
) -> Result<sp_sandbox::ReturnValue, sp_sandbox::HostError> {
) -> Result<sp_sandbox::ReturnValue, sp_sandbox::HostError>
where
<E::T as frame_system::Trait>::AccountId:
sp_core::crypto::UncheckedFrom<<E::T as frame_system::Trait>::Hash> +
AsRef<[u8]>
{
#[allow(unused)]
let mut args = args.iter();
@@ -183,7 +188,12 @@ macro_rules! define_env {
}
}
impl<E: Ext> $crate::wasm::env_def::FunctionImplProvider<E> for $init_name {
impl<E: Ext> $crate::wasm::env_def::FunctionImplProvider<E> for $init_name
where
<E::T as frame_system::Trait>::AccountId:
sp_core::crypto::UncheckedFrom<<E::T as frame_system::Trait>::Hash> +
AsRef<[u8]>
{
fn impls<F: FnMut(&[u8], $crate::wasm::env_def::HostFunc<E>)>(f: &mut F) {
register_func!(f, < E: $seal_ty > ; $( $name ( $ctx $( , $names : $params )* ) $( -> $returns)* => $body )* );
}
+113 -81
View File
@@ -23,6 +23,7 @@ use crate::exec::Ext;
use crate::gas::GasMeter;
use sp_std::prelude::*;
use sp_core::crypto::UncheckedFrom;
use codec::{Encode, Decode};
use sp_sandbox;
@@ -32,7 +33,7 @@ mod code_cache;
mod prepare;
mod runtime;
use self::runtime::{to_execution_result, Runtime};
use self::runtime::Runtime;
use self::code_cache::load as load_code;
use pallet_contracts_primitives::ExecResult;
@@ -71,13 +72,16 @@ pub struct WasmLoader<'a, T: Trait> {
schedule: &'a Schedule<T>,
}
impl<'a, T: Trait> WasmLoader<'a, T> {
impl<'a, T: Trait> WasmLoader<'a, T> where T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]> {
pub fn new(schedule: &'a Schedule<T>) -> Self {
WasmLoader { schedule }
}
}
impl<'a, T: Trait> crate::exec::Loader<T> for WasmLoader<'a, T> {
impl<'a, T: Trait> crate::exec::Loader<T> for WasmLoader<'a, T>
where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
{
type Executable = WasmExecutable;
fn load_init(&self, code_hash: &CodeHash<T>) -> Result<WasmExecutable, &'static str> {
@@ -97,17 +101,20 @@ impl<'a, T: Trait> crate::exec::Loader<T> for WasmLoader<'a, T> {
}
/// Implementation of `Vm` that takes `WasmExecutable` and executes it.
pub struct WasmVm<'a, T: Trait> {
pub struct WasmVm<'a, T: Trait> where T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]> {
schedule: &'a Schedule<T>,
}
impl<'a, T: Trait> WasmVm<'a, T> {
impl<'a, T: Trait> WasmVm<'a, T> where T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]> {
pub fn new(schedule: &'a Schedule<T>) -> Self {
WasmVm { schedule }
}
}
impl<'a, T: Trait> crate::exec::Vm<T> for WasmVm<'a, T> {
impl<'a, T: Trait> crate::exec::Vm<T> for WasmVm<'a, T>
where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
{
type Executable = WasmExecutable;
fn execute<E: Ext<T = T>>(
@@ -147,20 +154,22 @@ impl<'a, T: Trait> crate::exec::Vm<T> for WasmVm<'a, T> {
// entrypoint.
let result = sp_sandbox::Instance::new(&exec.prefab_module.code, &imports, &mut runtime)
.and_then(|mut instance| instance.invoke(exec.entrypoint_name, &[], &mut runtime));
to_execution_result(runtime, result)
runtime.to_execution_result(result)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
CodeHash, BalanceOf, Error, Module as Contracts,
exec::{Ext, StorageKey, AccountIdOf},
gas::{Gas, GasMeter},
tests::{Test, Call, ALICE, BOB},
wasm::prepare::prepare_contract,
};
use std::collections::HashMap;
use sp_core::H256;
use crate::exec::{Ext, StorageKey};
use crate::gas::{Gas, GasMeter};
use crate::tests::{Test, Call};
use crate::wasm::prepare::prepare_contract;
use crate::{CodeHash, BalanceOf, Error};
use hex_literal::hex;
use sp_runtime::DispatchError;
use frame_support::weights::Weight;
@@ -174,7 +183,7 @@ mod tests {
#[derive(Debug, PartialEq, Eq)]
struct RestoreEntry {
dest: u64,
dest: AccountIdOf<Test>,
code_hash: H256,
rent_allowance: u64,
delta: Vec<StorageKey>,
@@ -186,16 +195,17 @@ mod tests {
endowment: u64,
data: Vec<u8>,
gas_left: u64,
salt: Vec<u8>,
}
#[derive(Debug, PartialEq, Eq)]
struct TerminationEntry {
beneficiary: u64,
beneficiary: AccountIdOf<Test>,
}
#[derive(Debug, PartialEq, Eq)]
struct TransferEntry {
to: u64,
to: AccountIdOf<Test>,
value: u64,
data: Vec<u8>,
}
@@ -210,7 +220,6 @@ mod tests {
restores: Vec<RestoreEntry>,
// (topics, data)
events: Vec<(Vec<H256>, Vec<u8>)>,
next_account_id: u64,
}
impl Ext for MockExt {
@@ -228,18 +237,17 @@ mod tests {
endowment: u64,
gas_meter: &mut GasMeter<Test>,
data: Vec<u8>,
) -> Result<(u64, ExecReturnValue), ExecError> {
salt: &[u8],
) -> Result<(AccountIdOf<Self::T>, ExecReturnValue), ExecError> {
self.instantiates.push(InstantiateEntry {
code_hash: code_hash.clone(),
endowment,
data: data.to_vec(),
gas_left: gas_meter.gas_left(),
salt: salt.to_vec(),
});
let address = self.next_account_id;
self.next_account_id += 1;
Ok((
address,
Contracts::<Test>::contract_address(&ALICE, code_hash, salt),
ExecReturnValue {
flags: ReturnFlags::empty(),
data: Vec::new(),
@@ -248,11 +256,11 @@ mod tests {
}
fn transfer(
&mut self,
to: &u64,
to: &AccountIdOf<Self::T>,
value: u64,
) -> Result<(), DispatchError> {
self.transfers.push(TransferEntry {
to: *to,
to: to.clone(),
value,
data: Vec::new(),
});
@@ -260,13 +268,13 @@ mod tests {
}
fn call(
&mut self,
to: &u64,
to: &AccountIdOf<Self::T>,
value: u64,
_gas_meter: &mut GasMeter<Test>,
data: Vec<u8>,
) -> ExecResult {
self.transfers.push(TransferEntry {
to: *to,
to: to.clone(),
value,
data: data,
});
@@ -276,20 +284,20 @@ mod tests {
}
fn terminate(
&mut self,
beneficiary: &u64,
beneficiary: &AccountIdOf<Self::T>,
) -> Result<(), DispatchError> {
self.terminations.push(TerminationEntry {
beneficiary: *beneficiary,
beneficiary: beneficiary.clone(),
});
Ok(())
}
fn restore_to(
&mut self,
dest: u64,
dest: AccountIdOf<Self::T>,
code_hash: H256,
rent_allowance: u64,
delta: Vec<StorageKey>,
) -> Result<(), &'static str> {
) -> Result<(), DispatchError> {
self.restores.push(RestoreEntry {
dest,
code_hash,
@@ -298,11 +306,11 @@ mod tests {
});
Ok(())
}
fn caller(&self) -> &u64 {
&42
fn caller(&self) -> &AccountIdOf<Self::T> {
&ALICE
}
fn address(&self) -> &u64 {
&69
fn address(&self) -> &AccountIdOf<Self::T> {
&BOB
}
fn balance(&self) -> u64 {
228
@@ -363,25 +371,26 @@ mod tests {
value: u64,
gas_meter: &mut GasMeter<Test>,
input_data: Vec<u8>,
) -> Result<(u64, ExecReturnValue), ExecError> {
(**self).instantiate(code, value, gas_meter, input_data)
salt: &[u8],
) -> Result<(AccountIdOf<Self::T>, ExecReturnValue), ExecError> {
(**self).instantiate(code, value, gas_meter, input_data, salt)
}
fn transfer(
&mut self,
to: &u64,
to: &AccountIdOf<Self::T>,
value: u64,
) -> Result<(), DispatchError> {
(**self).transfer(to, value)
}
fn terminate(
&mut self,
beneficiary: &u64,
beneficiary: &AccountIdOf<Self::T>,
) -> Result<(), DispatchError> {
(**self).terminate(beneficiary)
}
fn call(
&mut self,
to: &u64,
to: &AccountIdOf<Self::T>,
value: u64,
gas_meter: &mut GasMeter<Test>,
input_data: Vec<u8>,
@@ -390,11 +399,11 @@ mod tests {
}
fn restore_to(
&mut self,
dest: u64,
dest: AccountIdOf<Self::T>,
code_hash: H256,
rent_allowance: u64,
delta: Vec<StorageKey>,
) -> Result<(), &'static str> {
) -> Result<(), DispatchError> {
(**self).restore_to(
dest,
code_hash,
@@ -402,10 +411,10 @@ mod tests {
delta,
)
}
fn caller(&self) -> &u64 {
fn caller(&self) -> &AccountIdOf<Self::T> {
(**self).caller()
}
fn address(&self) -> &u64 {
fn address(&self) -> &AccountIdOf<Self::T> {
(**self).address()
}
fn balance(&self) -> u64 {
@@ -451,7 +460,11 @@ mod tests {
input_data: Vec<u8>,
ext: E,
gas_meter: &mut GasMeter<E::T>,
) -> ExecResult {
) -> ExecResult
where
<E::T as frame_system::Trait>::AccountId:
UncheckedFrom<<E::T as frame_system::Trait>::Hash> + AsRef<[u8]>
{
use crate::exec::Vm;
let wasm = wat::parse_str(wat).unwrap();
@@ -485,21 +498,23 @@ mod tests {
(drop
(call $seal_transfer
(i32.const 4) ;; Pointer to "account" address.
(i32.const 8) ;; Length of "account" address.
(i32.const 12) ;; Pointer to the buffer with value to transfer
(i32.const 32) ;; Length of "account" address.
(i32.const 36) ;; Pointer to the buffer with value to transfer
(i32.const 8) ;; Length of the buffer with value to transfer.
)
)
)
(func (export "deploy"))
;; Destination AccountId to transfer the funds.
;; Represented by u64 (8 bytes long) in little endian.
(data (i32.const 4) "\07\00\00\00\00\00\00\00")
;; Destination AccountId (ALICE)
(data (i32.const 4)
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
)
;; Amount of value to transfer.
;; Represented by u64 (8 bytes long) in little endian.
(data (i32.const 12) "\99\00\00\00\00\00\00\00")
(data (i32.const 36) "\99\00\00\00\00\00\00\00")
)
"#;
@@ -516,7 +531,7 @@ mod tests {
assert_eq!(
&mock_ext.transfers,
&[TransferEntry {
to: 7,
to: ALICE,
value: 153,
data: Vec::new(),
}]
@@ -542,11 +557,11 @@ mod tests {
(drop
(call $seal_call
(i32.const 4) ;; Pointer to "callee" address.
(i32.const 8) ;; Length of "callee" address.
(i32.const 32) ;; Length of "callee" address.
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
(i32.const 12) ;; Pointer to the buffer with value to transfer
(i32.const 36) ;; Pointer to the buffer with value to transfer
(i32.const 8) ;; Length of the buffer with value to transfer.
(i32.const 20) ;; Pointer to input data buffer address
(i32.const 44) ;; Pointer to input data buffer address
(i32.const 4) ;; Length of input data buffer
(i32.const 4294967295) ;; u32 max value is the sentinel value: do not copy output
(i32.const 0) ;; Length is ignored in this case
@@ -555,14 +570,17 @@ mod tests {
)
(func (export "deploy"))
;; Destination AccountId to transfer the funds.
;; Represented by u64 (8 bytes long) in little endian.
(data (i32.const 4) "\09\00\00\00\00\00\00\00")
;; Destination AccountId (ALICE)
(data (i32.const 4)
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
)
;; Amount of value to transfer.
;; Represented by u64 (8 bytes long) in little endian.
(data (i32.const 12) "\06\00\00\00\00\00\00\00")
(data (i32.const 36) "\06\00\00\00\00\00\00\00")
(data (i32.const 20) "\01\02\03\04")
(data (i32.const 44) "\01\02\03\04")
)
"#;
@@ -579,7 +597,7 @@ mod tests {
assert_eq!(
&mock_ext.transfers,
&[TransferEntry {
to: 9,
to: ALICE,
value: 6,
data: vec![1, 2, 3, 4],
}]
@@ -602,7 +620,9 @@ mod tests {
;; output_ptr: u32,
;; output_len_ptr: u32
;; ) -> u32
(import "seal0" "seal_instantiate" (func $seal_instantiate (param i32 i32 i64 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)))
(import "seal0" "seal_instantiate" (func $seal_instantiate
(param i32 i32 i64 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)
))
(import "env" "memory" (memory 1 1))
(func (export "call")
(drop
@@ -618,11 +638,15 @@ mod tests {
(i32.const 0) ;; Length is ignored in this case
(i32.const 4294967295) ;; u32 max value is the sentinel value: do not copy output
(i32.const 0) ;; Length is ignored in this case
(i32.const 0) ;; salt_ptr
(i32.const 4) ;; salt_len
)
)
)
(func (export "deploy"))
;; Salt
(data (i32.const 0) "\42\43\44\45")
;; Amount of value to transfer.
;; Represented by u64 (8 bytes long) in little endian.
(data (i32.const 4) "\03\00\00\00\00\00\00\00")
@@ -653,7 +677,11 @@ mod tests {
endowment: 3,
data,
gas_left: _,
}] if code_hash == &[0x11; 32].into() && data == &vec![1, 2, 3, 4]
salt,
}] if
code_hash == &[0x11; 32].into() &&
data == &vec![1, 2, 3, 4] &&
salt == &vec![0x42, 0x43, 0x44, 0x45]
);
}
@@ -668,14 +696,16 @@ mod tests {
(func (export "call")
(call $seal_terminate
(i32.const 4) ;; Pointer to "beneficiary" address.
(i32.const 8) ;; Length of "beneficiary" address.
(i32.const 32) ;; Length of "beneficiary" address.
)
)
(func (export "deploy"))
;; Beneficiary AccountId to transfer the funds.
;; Represented by u64 (8 bytes long) in little endian.
(data (i32.const 4) "\09\00\00\00\00\00\00\00")
(data (i32.const 4)
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
)
)
"#;
@@ -692,7 +722,7 @@ mod tests {
assert_eq!(
&mock_ext.terminations,
&[TerminationEntry {
beneficiary: 0x09,
beneficiary: ALICE,
}]
);
}
@@ -716,11 +746,11 @@ mod tests {
(drop
(call $seal_call
(i32.const 4) ;; Pointer to "callee" address.
(i32.const 8) ;; Length of "callee" address.
(i32.const 32) ;; Length of "callee" address.
(i64.const 228) ;; How much gas to devote for the execution.
(i32.const 12) ;; Pointer to the buffer with value to transfer
(i32.const 36) ;; Pointer to the buffer with value to transfer
(i32.const 8) ;; Length of the buffer with value to transfer.
(i32.const 20) ;; Pointer to input data buffer address
(i32.const 44) ;; Pointer to input data buffer address
(i32.const 4) ;; Length of input data buffer
(i32.const 4294967295) ;; u32 max value is the sentinel value: do not copy output
(i32.const 0) ;; Length is ignored in this cas
@@ -730,13 +760,15 @@ mod tests {
(func (export "deploy"))
;; Destination AccountId to transfer the funds.
;; Represented by u64 (8 bytes long) in little endian.
(data (i32.const 4) "\09\00\00\00\00\00\00\00")
(data (i32.const 4)
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
)
;; Amount of value to transfer.
;; Represented by u64 (8 bytes long) in little endian.
(data (i32.const 12) "\06\00\00\00\00\00\00\00")
(data (i32.const 36) "\06\00\00\00\00\00\00\00")
(data (i32.const 20) "\01\02\03\04")
(data (i32.const 44) "\01\02\03\04")
)
"#;
@@ -753,7 +785,7 @@ mod tests {
assert_eq!(
&mock_ext.transfers,
&[TransferEntry {
to: 9,
to: ALICE,
value: 6,
data: vec![1, 2, 3, 4],
}]
@@ -863,19 +895,19 @@ mod tests {
;; fill the buffer with the caller.
(call $seal_caller (i32.const 0) (i32.const 32))
;; assert len == 8
;; assert len == 32
(call $assert
(i32.eq
(i32.load (i32.const 32))
(i32.const 8)
(i32.const 32)
)
)
;; assert that contents of the buffer is equal to the i64 value of 42.
;; assert that the first 64 byte are the beginning of "ALICE"
(call $assert
(i64.eq
(i64.load (i32.const 0))
(i64.const 42)
(i64.const 0x0101010101010101)
)
)
)
@@ -916,19 +948,19 @@ mod tests {
;; fill the buffer with the self address.
(call $seal_address (i32.const 0) (i32.const 32))
;; assert size == 8
;; assert size == 32
(call $assert
(i32.eq
(i32.load (i32.const 32))
(i32.const 8)
(i32.const 32)
)
)
;; assert that contents of the buffer is equal to the i64 value of 69.
;; assert that the first 64 byte are the beginning of "BOB"
(call $assert
(i64.eq
(i64.load (i32.const 0))
(i64.const 69)
(i64.const 0x0202020202020202)
)
)
)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff