From 407920bdf4aad64f82fc16bc9e5b0db3855c123f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Fri, 31 Jul 2020 14:16:48 +0200 Subject: [PATCH 01/29] seal: Add capability to put uninstrumented code (for benchmarks) Benchmarks should only measure the overhead of the API calls itself. For that reason we want to run them without instrumentation. --- frame/contracts/src/lib.rs | 8 ++ frame/contracts/src/wasm/code_cache.rs | 18 +++++ frame/contracts/src/wasm/mod.rs | 2 + frame/contracts/src/wasm/prepare.rs | 108 ++++++++++++++++--------- 4 files changed, 98 insertions(+), 38 deletions(-) diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 138c8e995a0a2..02eeafcf013c1 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -659,6 +659,14 @@ impl Module { ) -> sp_std::result::Result, ContractAccessError> { rent::compute_rent_projection::(&address) } + + /// Put code for benchmarks which does not check or instrument the code. + #[cfg(feature = "runtime-benchmarks")] + pub fn put_code_raw(code: Vec) -> DispatchResult { + let schedule = >::current_schedule(); + let result = wasm::save_code_raw::(code, &schedule); + result.map(|_| ()).map_err(Into::into) + } } impl Module { diff --git a/frame/contracts/src/wasm/code_cache.rs b/frame/contracts/src/wasm/code_cache.rs index ba7a02356d282..6394101bd76c9 100644 --- a/frame/contracts/src/wasm/code_cache.rs +++ b/frame/contracts/src/wasm/code_cache.rs @@ -49,6 +49,24 @@ pub fn save( Ok(code_hash) } +/// Version of `save` to be used in runtime benchmarks. +// +/// This version neither checks nor instruments the passed in code. This is useful +/// when code needs to be benchmarked without the injected instrumentation. +#[cfg(feature = "runtime-benchmarks")] +pub fn save_raw( + original_code: Vec, + schedule: &Schedule, +) -> Result, &'static str> { + let prefab_module = prepare::benchmarking::prepare_contract(&original_code, schedule)?; + let code_hash = T::Hashing::hash(&original_code); + + >::insert(code_hash, prefab_module); + >::insert(code_hash, original_code); + + Ok(code_hash) +} + /// Load code with the given code hash. /// /// If the module was instrumented with a lower version of schedule than diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index e74adfcf3caca..9b247219b3a80 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -36,6 +36,8 @@ use self::runtime::{to_execution_result, Runtime}; use self::code_cache::load as load_code; pub use self::code_cache::save as save_code; +#[cfg(feature = "runtime-benchmarks")] +pub use self::code_cache::save_raw as save_code_raw; pub use self::runtime::ReturnCode; /// A prepared wasm module ready for execution. diff --git a/frame/contracts/src/wasm/prepare.rs b/frame/contracts/src/wasm/prepare.rs index 97cb06fa26042..895777e46557d 100644 --- a/frame/contracts/src/wasm/prepare.rs +++ b/frame/contracts/src/wasm/prepare.rs @@ -269,7 +269,10 @@ impl<'a> ContractModule<'a> { /// - checks any imported function against defined host functions set, incl. /// their signatures. /// - if there is a memory import, returns it's descriptor - fn scan_imports(&self) -> Result, &'static str> { + /// `import_fn_banlist`: list of function names that are disallowed to be imported + fn scan_imports(&self, import_fn_banlist: &[&[u8]]) + -> Result, &'static str> + { let module = &self.module; let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]); @@ -315,8 +318,7 @@ impl<'a> ContractModule<'a> { return Err("module imports `seal_println` but debug features disabled"); } - // We disallow importing `gas` function here since it is treated as implementation detail. - if import.field().as_bytes() == b"gas" + if import_fn_banlist.iter().any(|f| import.field().as_bytes() == *f) || !C::can_satisfy(import.field().as_bytes(), func_ty) { return Err("module imports a non-existent function"); @@ -331,33 +333,10 @@ impl<'a> ContractModule<'a> { } } -/// Loads the given module given in `original_code`, performs some checks on it and -/// does some preprocessing. -/// -/// The checks are: -/// -/// - provided code is a valid wasm module. -/// - the module doesn't define an internal memory instance, -/// - imported memory (if any) doesn't reserve more memory than permitted by the `schedule`, -/// - all imported functions from the external environment matches defined by `env` module, -/// -/// The preprocessing includes injecting code for gas metering and metering the height of stack. -pub fn prepare_contract( - original_code: &[u8], - schedule: &Schedule, -) -> Result { - let mut contract_module = ContractModule::new(original_code, schedule)?; - contract_module.scan_exports()?; - contract_module.ensure_no_internal_memory()?; - contract_module.ensure_table_size_limit(schedule.max_table_size)?; - contract_module.ensure_no_floating_types()?; - - struct MemoryDefinition { - initial: u32, - maximum: u32, - } - - let memory_def = if let Some(memory_type) = contract_module.scan_imports::()? { +fn get_memory_limits(module: Option<&MemoryType>, schedule: &Schedule) + -> Result<(u32, u32), &'static str> +{ + if let Some(memory_type) = module { // Inspect the module to extract the initial and maximum page count. let limits = memory_type.limits(); match (limits.initial(), limits.maximum()) { @@ -369,7 +348,7 @@ pub fn prepare_contract( (_, Some(maximum)) if maximum > schedule.max_memory_pages => { return Err("Maximum number of pages should not exceed the configured maximum."); } - (initial, Some(maximum)) => MemoryDefinition { initial, maximum }, + (initial, Some(maximum)) => Ok((initial, maximum)), (_, None) => { // Maximum number of pages should be always declared. // This isn't a hard requirement and can be treated as a maximum set @@ -380,11 +359,37 @@ pub fn prepare_contract( } else { // If none memory imported then just crate an empty placeholder. // Any access to it will lead to out of bounds trap. - MemoryDefinition { - initial: 0, - maximum: 0, - } - }; + Ok((0, 0)) + } +} + +/// Loads the given module given in `original_code`, performs some checks on it and +/// does some preprocessing. +/// +/// The checks are: +/// +/// - provided code is a valid wasm module. +/// - the module doesn't define an internal memory instance, +/// - imported memory (if any) doesn't reserve more memory than permitted by the `schedule`, +/// - all imported functions from the external environment matches defined by `env` module, +/// +/// The preprocessing includes injecting code for gas metering and metering the height of stack. +pub fn prepare_contract( + original_code: &[u8], + schedule: &Schedule, +) -> Result { + let mut contract_module = ContractModule::new(original_code, schedule)?; + contract_module.scan_exports()?; + contract_module.ensure_no_internal_memory()?; + contract_module.ensure_table_size_limit(schedule.max_table_size)?; + contract_module.ensure_no_floating_types()?; + + // We disallow importing `gas` function here since it is treated as implementation detail. + let disallowed_imports = [b"gas".as_ref()]; + let memory_limits = get_memory_limits( + contract_module.scan_imports::(&disallowed_imports)?, + schedule + )?; contract_module = contract_module .inject_gas_metering()? @@ -392,13 +397,40 @@ pub fn prepare_contract( Ok(PrefabWasmModule { schedule_version: schedule.version, - initial: memory_def.initial, - maximum: memory_def.maximum, + initial: memory_limits.0, + maximum: memory_limits.1, _reserved: None, code: contract_module.into_wasm_code()?, }) } +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking { + use super::{ContractModule, PrefabWasmModule, ImportSatisfyCheck, Schedule, get_memory_limits}; + use parity_wasm::elements::FunctionType; + + impl ImportSatisfyCheck for () { + fn can_satisfy(_name: &[u8], _func_type: &FunctionType) -> bool { + true + } + } + + /// Prepare function that neither checks nor instruments the passed in code. + pub fn prepare_contract(original_code: &[u8], schedule: &Schedule) + -> Result + { + let contract_module = ContractModule::new(original_code, schedule)?; + let memory_limits = get_memory_limits(contract_module.scan_imports::<()>(&[])?, schedule)?; + Ok(PrefabWasmModule { + schedule_version: schedule.version, + initial: memory_limits.0, + maximum: memory_limits.1, + _reserved: None, + code: contract_module.into_wasm_code()?, + }) + } +} + #[cfg(test)] mod tests { use super::*; From 343f0fb20d605536e08f6d1c4e6060dae6c8907a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Wed, 19 Aug 2020 13:11:49 +0200 Subject: [PATCH 02/29] seal: Cap the the data length for deposited events Data used in events has storage implications for archive nodes. Those need to keep the events in storage forever. For that reason we want to limit the amount of storage that can be used inside events. --- frame/contracts/fixtures/event_size.wat | 39 ++++++++++++++++++++ frame/contracts/src/lib.rs | 4 ++- frame/contracts/src/tests.rs | 48 ++++++++++++++++++++++++- frame/contracts/src/wasm/runtime.rs | 7 ++-- 4 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 frame/contracts/fixtures/event_size.wat diff --git a/frame/contracts/fixtures/event_size.wat b/frame/contracts/fixtures/event_size.wat new file mode 100644 index 0000000000000..e12915e21e38d --- /dev/null +++ b/frame/contracts/fixtures/event_size.wat @@ -0,0 +1,39 @@ +(module + (import "seal0" "seal_deposit_event" (func $seal_deposit_event (param i32 i32 i32 i32))) + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "env" "memory" (memory 16 16)) + + ;; [0, 4) size of the input buffer + (data (i32.const 0) "\04") + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "call") + (call $seal_input (i32.const 4) (i32.const 0)) + + ;; assert input size == 4 + (call $assert + (i32.eq + (i32.load (i32.const 0)) + (i32.const 4) + ) + ) + + ;; place a garbage value in storage, the size of which is specified by the call input. + (call $seal_deposit_event + (i32.const 0) ;; topics_ptr + (i32.const 0) ;; topics_len + (i32.const 0) ;; data_ptr + (i32.load (i32.const 4)) ;; data_len + ) + ) + + (func (export "deploy")) +) diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 02eeafcf013c1..387b110423f0c 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -367,7 +367,7 @@ pub trait Trait: frame_system::Trait { /// The maximum nesting level of a call/instantiate stack. type MaxDepth: Get; - /// The maximum size of a storage value in bytes. + /// The maximum size of a storage value and event payload in bytes. type MaxValueSize: Get; /// Used to answer contracts's queries regarding the current weight price. This is **not** @@ -445,6 +445,8 @@ decl_error! { DecodingFailed, /// Contract trapped during execution. ContractTrapped, + /// The size defined in `T::MaxValueSize` was exceeded. + ValueTooLarge, } } diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index bd1242ff6701a..eaeab4edc1cb8 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -459,6 +459,52 @@ fn instantiate_and_call_and_deposit_event() { }); } +#[test] +fn deposit_event_max_value_limit() { + let (wasm, code_hash) = compile_module::("event_size").unwrap(); + + ExtBuilder::default() + .existential_deposit(50) + .build() + .execute_with(|| { + // Create + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm)); + assert_ok!(Contracts::instantiate( + Origin::signed(ALICE), + 30_000, + GAS_LIMIT, + code_hash.into(), + vec![], + )); + + // Check creation + let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap(); + assert_eq!(bob_contract.rent_allowance, >::max_value()); + + // Call contract with allowed storage value. + assert_ok!(Contracts::call( + Origin::signed(ALICE), + BOB, + 0, + GAS_LIMIT * 2, // we are copying a huge buffer + Encode::encode(&self::MaxValueSize::get()), + )); + + // Call contract with too large a storage value. + assert_err_ignore_postinfo!( + Contracts::call( + Origin::signed(ALICE), + BOB, + 0, + GAS_LIMIT, + Encode::encode(&(self::MaxValueSize::get() + 1)), + ), + Error::::ValueTooLarge, + ); + }); +} + #[test] fn run_out_of_gas() { let (wasm, code_hash) = compile_module::("run_out_of_gas").unwrap(); @@ -1321,7 +1367,7 @@ fn storage_max_value_limit() { GAS_LIMIT, Encode::encode(&(self::MaxValueSize::get() + 1)), ), - Error::::ContractTrapped, + Error::::ValueTooLarge, ); }); } diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 806c956d292a0..387e206e90561 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -540,8 +540,7 @@ define_env!(Env, , // - Upon trying to set an empty storage entry (value length is 0). seal_set_storage(ctx, key_ptr: u32, value_ptr: u32, value_len: u32) => { if value_len > ctx.ext.max_value_size() { - // Bail out if value length exceeds the set maximum value size. - return Err(sp_sandbox::HostError); + Err(store_err(ctx, Error::::ValueTooLarge))?; } let mut key: StorageKey = [0; 32]; read_sandbox_memory_into_buf(ctx, key_ptr, &mut key)?; @@ -1078,6 +1077,10 @@ define_env!(Env, , // - data_ptr - a pointer to a raw data buffer which will saved along the event. // - data_len - the length of the data buffer. seal_deposit_event(ctx, topics_ptr: u32, topics_len: u32, data_ptr: u32, data_len: u32) => { + if data_len > ctx.ext.max_value_size() { + Err(store_err(ctx, Error::::ValueTooLarge))?; + } + let mut topics: Vec::::T>> = match topics_len { 0 => Vec::new(), _ => read_sandbox_memory_as(ctx, topics_ptr, topics_len)?, From 1235f26634854b12b1d1de468dd26e4365438c46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Thu, 27 Aug 2020 11:10:23 +0200 Subject: [PATCH 03/29] seal: Fix error reporting in the case out of bound sandbox access --- frame/contracts/src/wasm/runtime.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 387e206e90561..db1505aaebb93 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -408,8 +408,10 @@ fn write_sandbox_output( RuntimeToken::WriteMemory(buf_len.saturating_add(4)), )?; - ctx.memory.set(out_ptr, buf)?; - ctx.memory.set(out_len_ptr, &buf_len.encode())?; + ctx.memory.set(out_ptr, buf).and_then(|_| { + ctx.memory.set(out_len_ptr, &buf_len.encode()) + }) + .map_err(|_| store_err(ctx, Error::::OutOfBounds))?; Ok(()) } From ec69d057874b4bf463e259456d4a992fac64e906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Mon, 3 Aug 2020 10:56:16 +0200 Subject: [PATCH 04/29] seal: Refactor existing benchmarks --- frame/contracts/src/benchmarking.rs | 393 ++++++++++++++++------------ 1 file changed, 230 insertions(+), 163 deletions(-) diff --git a/frame/contracts/src/benchmarking.rs b/frame/contracts/src/benchmarking.rs index 1a04db4defdb6..429c3bac3ece4 100644 --- a/frame/contracts/src/benchmarking.rs +++ b/frame/contracts/src/benchmarking.rs @@ -25,7 +25,19 @@ use crate::Module as Contracts; use frame_benchmarking::{benchmarks, account}; use frame_system::{Module as System, RawOrigin}; use parity_wasm::elements::FuncBody; -use sp_runtime::traits::Hash; +use sp_runtime::traits::{Hash, Bounded, SaturatedConversion, CheckedDiv}; + +struct WasmModule { + code: Vec, + hash: ::Output, +} + +struct Contract { + caller: T::AccountId, + account_id: T::AccountId, + addr: ::Source, + endowment: BalanceOf, +} macro_rules! load_module { ($name:expr) => {{ @@ -34,15 +46,84 @@ macro_rules! load_module { }}; } -fn compile_module(code: &[u8]) -> (Vec, ::Output) { - let code = sp_std::str::from_utf8(code).expect("Invalid utf8 in wat file."); - let binary = wat::parse_str(code).expect("Failed to compile wat file."); - let hash = T::Hashing::hash(&binary); - (binary, hash) +fn compile_module(code: &[u8]) -> Result, &str> { + let text = sp_std::str::from_utf8(code).map_err(|_| "Invalid utf8 in wat file.")?; + let code = wat::parse_str(text).map_err(|_| "Failed to compile wat file.")?; + let hash = T::Hashing::hash(&code); + Ok(WasmModule { + code, + hash, + }) +} + +fn contract_with_call_body(body: FuncBody) -> WasmModule { + use parity_wasm::elements::{ + Instructions, Instruction::End, + }; + let contract = parity_wasm::builder::ModuleBuilder::new() + // deploy function (idx 0) + .function() + .signature().with_params(vec![]).with_return_type(None).build() + .body().with_instructions(Instructions::new(vec![End])).build() + .build() + // call function (idx 1) + .function() + .signature().with_params(vec![]).with_return_type(None).build() + .with_body(body) + .build() + .export().field("deploy").internal().func(0).build() + .export().field("call").internal().func(1).build() + .build(); + let code = contract.to_bytes().unwrap(); + let hash = T::Hashing::hash(&code); + WasmModule { + code, + hash + } +} + +fn expanded_contract(target_bytes: u32) -> WasmModule { + use parity_wasm::elements::{ + Instruction::{self, If, I32Const, Return, End}, + BlockType, Instructions, + }; + // Base size of a contract is 47 bytes and each expansion adds 6 bytes. + // We do one expansion less to account for the code section and function body + // size fields inside the binary wasm module representation which are leb128 encoded + // and therefore grow in size when the contract grows. We are not allowed to overshoot + // because of the maximum code size that is enforced by `put_code`. + let expansions = (target_bytes.saturating_sub(47) / 6).saturating_sub(1) as usize; + const EXPANSION: [Instruction; 4] = [ + I32Const(0), + If(BlockType::NoResult), + Return, + End, + ]; + let instructions = Instructions::new( + EXPANSION + .iter() + .cycle() + .take(EXPANSION.len() * expansions) + .cloned() + .chain(sp_std::iter::once(End)) + .collect() + ); + contract_with_call_body::(FuncBody::new(Vec::new(), instructions)) +} + +/// Set the block number to one. +/// +/// 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 +/// undesired implicit bump (which messes with rent collection), wo do the bump ourselfs +/// in the setup closure so that both the instantiate and subsequent call are run with the +/// same block number. +fn init_block_number() { + System::::set_block_number(1.into()); } fn funding() -> BalanceOf { - T::Currency::minimum_balance() * 10_000.into() + BalanceOf::::max_value() / 2.into() } fn create_funded_user(string: &'static str, n: u32) -> T::AccountId { @@ -51,61 +132,66 @@ fn create_funded_user(string: &'static str, n: u32) -> T::AccountId { user } -fn contract_with_call_body(body: FuncBody) -> (Vec, ::Output) { - use parity_wasm::elements::{ - Instructions, Instruction::End, - }; - let contract = parity_wasm::builder::ModuleBuilder::new() - // deploy function (idx 0) - .function() - .signature().with_params(vec![]).with_return_type(None).build() - .body().with_instructions(Instructions::new(vec![End])).build() - .build() - // call function (idx 1) - .function() - .signature().with_params(vec![]).with_return_type(None).build() - .with_body(body) - .build() - .export().field("deploy").internal().func(0).build() - .export().field("call").internal().func(1).build() - .build(); - let bytes = contract.to_bytes().unwrap(); - let hash = T::Hashing::hash(&bytes); - (bytes, hash) +fn eviction_at(addr: &T::AccountId) -> Result { + match crate::rent::compute_rent_projection::(addr).map_err(|_| "Invalid acc for rent")? { + RentProjection::EvictionAt(at) => Ok(at), + _ => Err("Account does not pay rent.")?, + } } -fn expanded_contract(target_bytes: u32) -> (Vec, ::Output) { - use parity_wasm::elements::{ - Instruction::{self, If, I32Const, Return, End}, - BlockType, Instructions, - }; - // Base size of a contract is 47 bytes and each expansion adds 6 bytes. - // We do one expansion less to account for the code section and function body - // size fields inside the binary wasm module representation which are leb128 encoded - // and therefore grow in size when the contract grows. We are not allowed to overshoot - // because of the maximum code size that is enforced by `put_code`. - let expansions = (target_bytes.saturating_sub(47) / 6).saturating_sub(1) as usize; - const EXPANSION: [Instruction; 4] = [ - I32Const(0), - If(BlockType::NoResult), - Return, - End, - ]; - let instructions = Instructions::new( - EXPANSION - .iter() - .cycle() - .take(EXPANSION.len() * expansions) - .cloned() - .chain(sp_std::iter::once(End)) - .collect() - ); - contract_with_call_body::(FuncBody::new(Vec::new(), instructions)) +fn instantiate_raw_contract( + caller_name: &'static str, + module: WasmModule, + data: Vec, +) -> Result, &'static str> +{ + // storage_size cannot be zero because otherwise a contract that is just above the subsistence + // threshold does not pay rent given a large enough subsistence threshold. But we need rent + // payments to occur in order to benchmark for worst cases. + let storage_size = Config::::subsistence_threshold_uncached() + .checked_div(&T::RentDepositOffset::get()) + .unwrap_or_else(Zero::zero); + + // Endowment should be large but not as large to inhibit rent payments. + let endowment = T::RentDepositOffset::get() + .saturating_mul(storage_size + T::StorageSizeOffset::get().into()) + .saturating_sub(1.into()); + + let caller = create_funded_user::(caller_name, 0); + let addr = T::DetermineContractAddress::contract_address_for(&module.hash, &data, &caller); + init_block_number::(); + Contracts::::put_code_raw(module.code)?; + Contracts::::instantiate( + RawOrigin::Signed(caller.clone()).into(), + endowment, + Weight::max_value(), + module.hash, + data, + )?; + let mut contract = get_alive::(&addr)?; + contract.storage_size = storage_size.saturated_into::(); + ContractInfoOf::::insert(&addr, ContractInfo::Alive(contract)); + Ok(Contract { + caller, + account_id: addr.clone(), + addr: T::Lookup::unlookup(addr), + endowment, + }) } -fn advance_block(num: ::BlockNumber) { - let now = System::::block_number(); - System::::set_block_number(now + num); +fn get_alive(addr: &T::AccountId) -> Result, &'static str> { + ContractInfoOf::::get(&addr).and_then(|c| c.get_alive()) + .ok_or("Expected contract to be alive at this point.") +} + +fn ensure_alive(addr: &T::AccountId) -> Result<(), &'static str> { + get_alive::(addr).map(|_| ()) +} + +fn ensure_tombstone(addr: &T::AccountId) -> Result<(), &'static str> { + ContractInfoOf::::get(&addr).and_then(|c| c.get_tombstone()) + .ok_or("Expected contract to be a tombstone at this point.") + .map(|_| ()) } benchmarks! { @@ -120,112 +206,93 @@ benchmarks! { }; }: _(RawOrigin::Root, schedule) - // This constructs a contract that is maximal expensive to instrument. - // It creates a maximum number of metering blocks per byte. - put_code { - let n in 0 .. Contracts::::current_schedule().max_code_size; - let caller = create_funded_user::("caller", 0); - let (binary, hash) = expanded_contract::(n); - }: _(RawOrigin::Signed(caller), binary) - - // Instantiate uses a dummy contract constructor to measure the overhead of the instantiate. - // The size of the data has no influence on the costs of this extrinsic as long as the contract - // won't call `seal_input` in its constructor to copy the data to contract memory. - // The dummy contract used here does not do this. The costs for the data copy is billed as - // part of `seal_input`. - instantiate { - let data = vec![0u8; 128]; - let endowment = Config::::subsistence_threshold_uncached(); - let caller = create_funded_user::("caller", 0); - let (binary, hash) = load_module!("dummy"); - Contracts::::put_code(RawOrigin::Signed(caller.clone()).into(), binary.to_vec()) - .unwrap(); - - }: _( - RawOrigin::Signed(caller.clone()), - endowment, - Weight::max_value(), - hash, - data - ) - verify { - assert_eq!( - funding::() - endowment, - T::Currency::free_balance(&caller), - ) - } + // This constructs a contract that is maximal expensive to instrument. + // It creates a maximum number of metering blocks per byte. + put_code { + let n in 0 .. Contracts::::current_schedule().max_code_size; + let caller = create_funded_user::("caller", 0); + let module = expanded_contract::(n); + let origin = RawOrigin::Signed(caller); + }: _(origin, module.code) - // We just call a dummy contract to measure to overhead of the call extrinsic. - // As for instantiate the size of the data does not influence the costs. - call { - let data = vec![0u8; 128]; - let endowment = Config::::subsistence_threshold_uncached(); - let value = T::Currency::minimum_balance() * 100.into(); - let caller = create_funded_user::("caller", 0); - let (binary, hash) = load_module!("dummy"); - let addr = T::DetermineContractAddress::contract_address_for(&hash, &[], &caller); - Contracts::::put_code(RawOrigin::Signed(caller.clone()).into(), binary.to_vec()) - .unwrap(); - Contracts::::instantiate( - RawOrigin::Signed(caller.clone()).into(), - endowment, - Weight::max_value(), - hash, - vec![], - ).unwrap(); - }: _( - RawOrigin::Signed(caller.clone()), - T::Lookup::unlookup(addr), - value, - Weight::max_value(), - data - ) - verify { - assert_eq!( - funding::() - endowment - value, - T::Currency::free_balance(&caller), - ) - } + // Instantiate uses a dummy contract constructor to measure the overhead of the instantiate. + // The size of the data has no influence on the costs of this extrinsic as long as the contract + // won't call `seal_input` in its constructor to copy the data to contract memory. + // The dummy contract used here does not do this. The costs for the data copy is billed as + // part of `seal_input`. + // However, we still use data size as component here as it will be removed by the benchmarking + // as it has no influence on the weight. This works as "proof" and as regression test. + instantiate { + let n in 0 .. u16::max_value() as u32; + let data = vec![0u8; n as usize]; + let endowment = Config::::subsistence_threshold_uncached(); + let caller = create_funded_user::("caller", 0); + let WasmModule { code, hash } = load_module!("dummy")?; + let origin = RawOrigin::Signed(caller.clone()); + let addr = T::DetermineContractAddress::contract_address_for(&hash, &data, &caller); + Contracts::::put_code_raw(code)?; + }: _(origin, endowment, Weight::max_value(), hash, data) + verify { + // endowment was removed from the caller + assert_eq!(T::Currency::free_balance(&caller), funding::() - endowment); + // contract has the full endowment because no rent collection happended + assert_eq!(T::Currency::free_balance(&addr), endowment); + // instantiate should leave a alive contract + ensure_alive::(&addr)?; + } - // We benchmark the costs for sucessfully evicting an empty contract. - // The actual costs are depending on how many storage items the evicted contract - // does have. However, those costs are not to be payed by the sender but - // will be distributed over multiple blocks using a scheduler. Otherwise there is - // no incentive to remove large contracts when the removal is more expensive than - // the reward for removing them. - claim_surcharge { - let endowment = Config::::subsistence_threshold_uncached(); - let value = T::Currency::minimum_balance() * 100.into(); - let caller = create_funded_user::("caller", 0); - let (binary, hash) = load_module!("dummy"); - let addr = T::DetermineContractAddress::contract_address_for(&hash, &[], &caller); - Contracts::::put_code(RawOrigin::Signed(caller.clone()).into(), binary.to_vec()) - .unwrap(); - Contracts::::instantiate( - RawOrigin::Signed(caller.clone()).into(), - endowment, - Weight::max_value(), - hash, - vec![], - ).unwrap(); - - // instantiate should leave us with an alive contract - ContractInfoOf::::get(addr.clone()).unwrap().get_alive().unwrap(); - - // generate some rent - advance_block::(::SignedClaimHandicap::get() + 1.into()); - - }: _(RawOrigin::Signed(caller.clone()), addr.clone(), None) - verify { - // the claim surcharge should have evicted the contract - ContractInfoOf::::get(addr.clone()).unwrap().get_tombstone().unwrap(); - - // the caller should get the reward for being a good snitch - assert_eq!( - funding::() - endowment + ::SurchargeReward::get(), - T::Currency::free_balance(&caller), - ); - } + // We just call a dummy contract to measure to overhead of the call extrinsic. + // As for instantiate the size of the data does not influence the costs. + call { + let n in 0 .. u16::max_value() as u32; + let data = vec![0u8; n as usize]; + let instance = instantiate_raw_contract("caller", load_module!("dummy")?, vec![])?; + let value = T::Currency::minimum_balance() * 100.into(); + let origin = RawOrigin::Signed(instance.caller.clone()); + + // trigger rent collection for worst case performance of call + System::::set_block_number(eviction_at::(&instance.account_id)? - 5.into()); + }: _(origin, instance.addr, value, Weight::max_value(), data) + verify { + // endowment and value transfered via call should be removed from the caller + assert_eq!( + T::Currency::free_balance(&instance.caller), + funding::() - instance.endowment - value, + ); + // rent should have lowered the amount of balance of the contract + assert!(T::Currency::free_balance(&instance.account_id) < instance.endowment); + // but it should not have been evicted by the rent collection + ensure_alive::(&instance.account_id)?; + } + + // We benchmark the costs for sucessfully evicting an empty contract. + // The actual costs are depending on how many storage items the evicted contract + // does have. However, those costs are not to be payed by the sender but + // will be distributed over multiple blocks using a scheduler. Otherwise there is + // no incentive to remove large contracts when the removal is more expensive than + // the reward for removing them. + claim_surcharge { + let instance = instantiate_raw_contract("caller", load_module!("dummy")?, vec![])?; + let origin = RawOrigin::Signed(instance.caller.clone()); + let account_id = instance.account_id.clone(); + + // instantiate should leave us with an alive contract + ContractInfoOf::::get(instance.account_id.clone()).and_then(|c| c.get_alive()) + .ok_or("Instantiate must return an alive contract.")?; + + // generate enough rent so that the contract is evicted + System::::set_block_number(eviction_at::(&instance.account_id)? + 5.into()); + }: _(origin, account_id, None) + verify { + // the claim surcharge should have evicted the contract + ensure_tombstone::(&instance.account_id)?; + + // the caller should get the reward for being a good snitch + assert_eq!( + T::Currency::free_balance(&instance.caller), + funding::() - instance.endowment + ::SurchargeReward::get(), + ); + } } #[cfg(test)] @@ -267,5 +334,5 @@ mod tests { ExtBuilder::default().build().execute_with(|| { assert_ok!(test_benchmark_claim_surcharge::()); }); - } + } } From b1a113341656a851e47649ae6cd6627024b805fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Wed, 5 Aug 2020 14:06:05 +0200 Subject: [PATCH 05/29] seal: Convert benchmark file to tabs --- frame/contracts/src/benchmarking.rs | 392 ++++++++++++++-------------- 1 file changed, 196 insertions(+), 196 deletions(-) diff --git a/frame/contracts/src/benchmarking.rs b/frame/contracts/src/benchmarking.rs index 429c3bac3ece4..ccf185d4849c6 100644 --- a/frame/contracts/src/benchmarking.rs +++ b/frame/contracts/src/benchmarking.rs @@ -28,15 +28,15 @@ use parity_wasm::elements::FuncBody; use sp_runtime::traits::{Hash, Bounded, SaturatedConversion, CheckedDiv}; struct WasmModule { - code: Vec, - hash: ::Output, + code: Vec, + hash: ::Output, } struct Contract { - caller: T::AccountId, - account_id: T::AccountId, - addr: ::Source, - endowment: BalanceOf, + caller: T::AccountId, + account_id: T::AccountId, + addr: ::Source, + endowment: BalanceOf, } macro_rules! load_module { @@ -47,68 +47,68 @@ macro_rules! load_module { } fn compile_module(code: &[u8]) -> Result, &str> { - let text = sp_std::str::from_utf8(code).map_err(|_| "Invalid utf8 in wat file.")?; - let code = wat::parse_str(text).map_err(|_| "Failed to compile wat file.")?; - let hash = T::Hashing::hash(&code); - Ok(WasmModule { - code, - hash, - }) + let text = sp_std::str::from_utf8(code).map_err(|_| "Invalid utf8 in wat file.")?; + let code = wat::parse_str(text).map_err(|_| "Failed to compile wat file.")?; + let hash = T::Hashing::hash(&code); + Ok(WasmModule { + code, + hash, + }) } fn contract_with_call_body(body: FuncBody) -> WasmModule { - use parity_wasm::elements::{ - Instructions, Instruction::End, - }; - let contract = parity_wasm::builder::ModuleBuilder::new() - // deploy function (idx 0) - .function() - .signature().with_params(vec![]).with_return_type(None).build() - .body().with_instructions(Instructions::new(vec![End])).build() - .build() - // call function (idx 1) - .function() - .signature().with_params(vec![]).with_return_type(None).build() - .with_body(body) - .build() - .export().field("deploy").internal().func(0).build() - .export().field("call").internal().func(1).build() - .build(); - let code = contract.to_bytes().unwrap(); - let hash = T::Hashing::hash(&code); - WasmModule { - code, - hash - } + use parity_wasm::elements::{ + Instructions, Instruction::End, + }; + let contract = parity_wasm::builder::ModuleBuilder::new() + // deploy function (idx 0) + .function() + .signature().with_params(vec![]).with_return_type(None).build() + .body().with_instructions(Instructions::new(vec![End])).build() + .build() + // call function (idx 1) + .function() + .signature().with_params(vec![]).with_return_type(None).build() + .with_body(body) + .build() + .export().field("deploy").internal().func(0).build() + .export().field("call").internal().func(1).build() + .build(); + let code = contract.to_bytes().unwrap(); + let hash = T::Hashing::hash(&code); + WasmModule { + code, + hash + } } fn expanded_contract(target_bytes: u32) -> WasmModule { - use parity_wasm::elements::{ - Instruction::{self, If, I32Const, Return, End}, - BlockType, Instructions, - }; - // Base size of a contract is 47 bytes and each expansion adds 6 bytes. - // We do one expansion less to account for the code section and function body - // size fields inside the binary wasm module representation which are leb128 encoded - // and therefore grow in size when the contract grows. We are not allowed to overshoot - // because of the maximum code size that is enforced by `put_code`. - let expansions = (target_bytes.saturating_sub(47) / 6).saturating_sub(1) as usize; - const EXPANSION: [Instruction; 4] = [ - I32Const(0), - If(BlockType::NoResult), - Return, - End, - ]; - let instructions = Instructions::new( - EXPANSION - .iter() - .cycle() - .take(EXPANSION.len() * expansions) - .cloned() - .chain(sp_std::iter::once(End)) - .collect() - ); - contract_with_call_body::(FuncBody::new(Vec::new(), instructions)) + use parity_wasm::elements::{ + Instruction::{self, If, I32Const, Return, End}, + BlockType, Instructions, + }; + // Base size of a contract is 47 bytes and each expansion adds 6 bytes. + // We do one expansion less to account for the code section and function body + // size fields inside the binary wasm module representation which are leb128 encoded + // and therefore grow in size when the contract grows. We are not allowed to overshoot + // because of the maximum code size that is enforced by `put_code`. + let expansions = (target_bytes.saturating_sub(47) / 6).saturating_sub(1) as usize; + const EXPANSION: [Instruction; 4] = [ + I32Const(0), + If(BlockType::NoResult), + Return, + End, + ]; + let instructions = Instructions::new( + EXPANSION + .iter() + .cycle() + .take(EXPANSION.len() * expansions) + .cloned() + .chain(sp_std::iter::once(End)) + .collect() + ); + contract_with_call_body::(FuncBody::new(Vec::new(), instructions)) } /// Set the block number to one. @@ -123,7 +123,7 @@ fn init_block_number() { } fn funding() -> BalanceOf { - BalanceOf::::max_value() / 2.into() + BalanceOf::::max_value() / 2.into() } fn create_funded_user(string: &'static str, n: u32) -> T::AccountId { @@ -133,65 +133,65 @@ fn create_funded_user(string: &'static str, n: u32) -> T::AccountId { } fn eviction_at(addr: &T::AccountId) -> Result { - match crate::rent::compute_rent_projection::(addr).map_err(|_| "Invalid acc for rent")? { - RentProjection::EvictionAt(at) => Ok(at), - _ => Err("Account does not pay rent.")?, - } + match crate::rent::compute_rent_projection::(addr).map_err(|_| "Invalid acc for rent")? { + RentProjection::EvictionAt(at) => Ok(at), + _ => Err("Account does not pay rent.")?, + } } fn instantiate_raw_contract( - caller_name: &'static str, - module: WasmModule, - data: Vec, + caller_name: &'static str, + module: WasmModule, + data: Vec, ) -> Result, &'static str> { - // storage_size cannot be zero because otherwise a contract that is just above the subsistence - // threshold does not pay rent given a large enough subsistence threshold. But we need rent - // payments to occur in order to benchmark for worst cases. - let storage_size = Config::::subsistence_threshold_uncached() - .checked_div(&T::RentDepositOffset::get()) - .unwrap_or_else(Zero::zero); - - // Endowment should be large but not as large to inhibit rent payments. - let endowment = T::RentDepositOffset::get() - .saturating_mul(storage_size + T::StorageSizeOffset::get().into()) - .saturating_sub(1.into()); - - let caller = create_funded_user::(caller_name, 0); - let addr = T::DetermineContractAddress::contract_address_for(&module.hash, &data, &caller); - init_block_number::(); - Contracts::::put_code_raw(module.code)?; - Contracts::::instantiate( - RawOrigin::Signed(caller.clone()).into(), - endowment, - Weight::max_value(), - module.hash, - data, - )?; - let mut contract = get_alive::(&addr)?; - contract.storage_size = storage_size.saturated_into::(); - ContractInfoOf::::insert(&addr, ContractInfo::Alive(contract)); - Ok(Contract { - caller, - account_id: addr.clone(), - addr: T::Lookup::unlookup(addr), - endowment, - }) + // storage_size cannot be zero because otherwise a contract that is just above the subsistence + // threshold does not pay rent given a large enough subsistence threshold. But we need rent + // payments to occur in order to benchmark for worst cases. + let storage_size = Config::::subsistence_threshold_uncached() + .checked_div(&T::RentDepositOffset::get()) + .unwrap_or_else(Zero::zero); + + // Endowment should be large but not as large to inhibit rent payments. + let endowment = T::RentDepositOffset::get() + .saturating_mul(storage_size + T::StorageSizeOffset::get().into()) + .saturating_sub(1.into()); + + let caller = create_funded_user::(caller_name, 0); + let addr = T::DetermineContractAddress::contract_address_for(&module.hash, &data, &caller); + init_block_number::(); + Contracts::::put_code_raw(module.code)?; + Contracts::::instantiate( + RawOrigin::Signed(caller.clone()).into(), + endowment, + Weight::max_value(), + module.hash, + data, + )?; + let mut contract = get_alive::(&addr)?; + contract.storage_size = storage_size.saturated_into::(); + ContractInfoOf::::insert(&addr, ContractInfo::Alive(contract)); + Ok(Contract { + caller, + account_id: addr.clone(), + addr: T::Lookup::unlookup(addr), + endowment, + }) } fn get_alive(addr: &T::AccountId) -> Result, &'static str> { - ContractInfoOf::::get(&addr).and_then(|c| c.get_alive()) - .ok_or("Expected contract to be alive at this point.") + ContractInfoOf::::get(&addr).and_then(|c| c.get_alive()) + .ok_or("Expected contract to be alive at this point.") } fn ensure_alive(addr: &T::AccountId) -> Result<(), &'static str> { - get_alive::(addr).map(|_| ()) + get_alive::(addr).map(|_| ()) } fn ensure_tombstone(addr: &T::AccountId) -> Result<(), &'static str> { - ContractInfoOf::::get(&addr).and_then(|c| c.get_tombstone()) - .ok_or("Expected contract to be a tombstone at this point.") - .map(|_| ()) + ContractInfoOf::::get(&addr).and_then(|c| c.get_tombstone()) + .ok_or("Expected contract to be a tombstone at this point.") + .map(|_| ()) } benchmarks! { @@ -206,93 +206,93 @@ benchmarks! { }; }: _(RawOrigin::Root, schedule) - // This constructs a contract that is maximal expensive to instrument. - // It creates a maximum number of metering blocks per byte. - put_code { - let n in 0 .. Contracts::::current_schedule().max_code_size; - let caller = create_funded_user::("caller", 0); - let module = expanded_contract::(n); - let origin = RawOrigin::Signed(caller); - }: _(origin, module.code) - - // Instantiate uses a dummy contract constructor to measure the overhead of the instantiate. - // The size of the data has no influence on the costs of this extrinsic as long as the contract - // won't call `seal_input` in its constructor to copy the data to contract memory. - // The dummy contract used here does not do this. The costs for the data copy is billed as - // part of `seal_input`. - // However, we still use data size as component here as it will be removed by the benchmarking - // as it has no influence on the weight. This works as "proof" and as regression test. - instantiate { - let n in 0 .. u16::max_value() as u32; - let data = vec![0u8; n as usize]; - let endowment = Config::::subsistence_threshold_uncached(); - let caller = create_funded_user::("caller", 0); - let WasmModule { code, hash } = load_module!("dummy")?; - let origin = RawOrigin::Signed(caller.clone()); - let addr = T::DetermineContractAddress::contract_address_for(&hash, &data, &caller); - Contracts::::put_code_raw(code)?; - }: _(origin, endowment, Weight::max_value(), hash, data) - verify { - // endowment was removed from the caller - assert_eq!(T::Currency::free_balance(&caller), funding::() - endowment); - // contract has the full endowment because no rent collection happended - assert_eq!(T::Currency::free_balance(&addr), endowment); - // instantiate should leave a alive contract - ensure_alive::(&addr)?; - } - - // We just call a dummy contract to measure to overhead of the call extrinsic. - // As for instantiate the size of the data does not influence the costs. - call { - let n in 0 .. u16::max_value() as u32; - let data = vec![0u8; n as usize]; - let instance = instantiate_raw_contract("caller", load_module!("dummy")?, vec![])?; - let value = T::Currency::minimum_balance() * 100.into(); - let origin = RawOrigin::Signed(instance.caller.clone()); - - // trigger rent collection for worst case performance of call - System::::set_block_number(eviction_at::(&instance.account_id)? - 5.into()); - }: _(origin, instance.addr, value, Weight::max_value(), data) - verify { - // endowment and value transfered via call should be removed from the caller - assert_eq!( - T::Currency::free_balance(&instance.caller), - funding::() - instance.endowment - value, - ); - // rent should have lowered the amount of balance of the contract - assert!(T::Currency::free_balance(&instance.account_id) < instance.endowment); - // but it should not have been evicted by the rent collection - ensure_alive::(&instance.account_id)?; - } - - // We benchmark the costs for sucessfully evicting an empty contract. - // The actual costs are depending on how many storage items the evicted contract - // does have. However, those costs are not to be payed by the sender but - // will be distributed over multiple blocks using a scheduler. Otherwise there is - // no incentive to remove large contracts when the removal is more expensive than - // the reward for removing them. - claim_surcharge { - let instance = instantiate_raw_contract("caller", load_module!("dummy")?, vec![])?; - let origin = RawOrigin::Signed(instance.caller.clone()); - let account_id = instance.account_id.clone(); - - // instantiate should leave us with an alive contract - ContractInfoOf::::get(instance.account_id.clone()).and_then(|c| c.get_alive()) - .ok_or("Instantiate must return an alive contract.")?; - - // generate enough rent so that the contract is evicted - System::::set_block_number(eviction_at::(&instance.account_id)? + 5.into()); - }: _(origin, account_id, None) - verify { - // the claim surcharge should have evicted the contract - ensure_tombstone::(&instance.account_id)?; - - // the caller should get the reward for being a good snitch - assert_eq!( - T::Currency::free_balance(&instance.caller), - funding::() - instance.endowment + ::SurchargeReward::get(), - ); - } + // This constructs a contract that is maximal expensive to instrument. + // It creates a maximum number of metering blocks per byte. + put_code { + let n in 0 .. Contracts::::current_schedule().max_code_size; + let caller = create_funded_user::("caller", 0); + let module = expanded_contract::(n); + let origin = RawOrigin::Signed(caller); + }: _(origin, module.code) + + // Instantiate uses a dummy contract constructor to measure the overhead of the instantiate. + // The size of the data has no influence on the costs of this extrinsic as long as the contract + // won't call `seal_input` in its constructor to copy the data to contract memory. + // The dummy contract used here does not do this. The costs for the data copy is billed as + // part of `seal_input`. + // However, we still use data size as component here as it will be removed by the benchmarking + // as it has no influence on the weight. This works as "proof" and as regression test. + instantiate { + let n in 0 .. u16::max_value() as u32; + let data = vec![0u8; n as usize]; + let endowment = Config::::subsistence_threshold_uncached(); + let caller = create_funded_user::("caller", 0); + let WasmModule { code, hash } = load_module!("dummy")?; + let origin = RawOrigin::Signed(caller.clone()); + let addr = T::DetermineContractAddress::contract_address_for(&hash, &data, &caller); + Contracts::::put_code_raw(code)?; + }: _(origin, endowment, Weight::max_value(), hash, data) + verify { + // endowment was removed from the caller + assert_eq!(T::Currency::free_balance(&caller), funding::() - endowment); + // contract has the full endowment because no rent collection happended + assert_eq!(T::Currency::free_balance(&addr), endowment); + // instantiate should leave a alive contract + ensure_alive::(&addr)?; + } + + // We just call a dummy contract to measure to overhead of the call extrinsic. + // As for instantiate the size of the data does not influence the costs. + call { + let n in 0 .. u16::max_value() as u32; + let data = vec![0u8; n as usize]; + let instance = instantiate_raw_contract("caller", load_module!("dummy")?, vec![])?; + let value = T::Currency::minimum_balance() * 100.into(); + let origin = RawOrigin::Signed(instance.caller.clone()); + + // trigger rent collection for worst case performance of call + System::::set_block_number(eviction_at::(&instance.account_id)? - 5.into()); + }: _(origin, instance.addr, value, Weight::max_value(), data) + verify { + // endowment and value transfered via call should be removed from the caller + assert_eq!( + T::Currency::free_balance(&instance.caller), + funding::() - instance.endowment - value, + ); + // rent should have lowered the amount of balance of the contract + assert!(T::Currency::free_balance(&instance.account_id) < instance.endowment); + // but it should not have been evicted by the rent collection + ensure_alive::(&instance.account_id)?; + } + + // We benchmark the costs for sucessfully evicting an empty contract. + // The actual costs are depending on how many storage items the evicted contract + // does have. However, those costs are not to be payed by the sender but + // will be distributed over multiple blocks using a scheduler. Otherwise there is + // no incentive to remove large contracts when the removal is more expensive than + // the reward for removing them. + claim_surcharge { + let instance = instantiate_raw_contract("caller", load_module!("dummy")?, vec![])?; + let origin = RawOrigin::Signed(instance.caller.clone()); + let account_id = instance.account_id.clone(); + + // instantiate should leave us with an alive contract + ContractInfoOf::::get(instance.account_id.clone()).and_then(|c| c.get_alive()) + .ok_or("Instantiate must return an alive contract.")?; + + // generate enough rent so that the contract is evicted + System::::set_block_number(eviction_at::(&instance.account_id)? + 5.into()); + }: _(origin, account_id, None) + verify { + // the claim surcharge should have evicted the contract + ensure_tombstone::(&instance.account_id)?; + + // the caller should get the reward for being a good snitch + assert_eq!( + T::Currency::free_balance(&instance.caller), + funding::() - instance.endowment + ::SurchargeReward::get(), + ); + } } #[cfg(test)] @@ -334,5 +334,5 @@ mod tests { ExtBuilder::default().build().execute_with(|| { assert_ok!(test_benchmark_claim_surcharge::()); }); - } + } } From dfa60765c6bbd0e7159b489ce3df95a0648441b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Mon, 10 Aug 2020 18:46:58 +0200 Subject: [PATCH 06/29] seal: Add benchmarks for functions called by contracts --- Cargo.lock | 15 +- frame/contracts/Cargo.toml | 8 +- frame/contracts/fixtures/benchmarks/dummy.wat | 4 - frame/contracts/src/benchmarking.rs | 1751 +++++++++++++++-- frame/contracts/src/lib.rs | 1 + 5 files changed, 1638 insertions(+), 141 deletions(-) delete mode 100644 frame/contracts/fixtures/benchmarks/dummy.wat diff --git a/Cargo.lock b/Cargo.lock index fbf7f307962b5..1072fbeb506b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1581,7 +1581,7 @@ dependencies = [ "hex-literal", "linregress", "parity-scale-codec", - "paste", + "paste 0.1.18", "sp-api", "sp-io", "sp-runtime", @@ -1650,7 +1650,7 @@ dependencies = [ "once_cell 1.4.0", "parity-scale-codec", "parity-util-mem", - "paste", + "paste 0.1.18", "pretty_assertions", "serde", "smallvec 1.4.1", @@ -3340,7 +3340,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c50092e40e0ccd1bf2015a10333fde0502ff95b832b0895dc1ca0d7ac6c52f6" dependencies = [ - "paste", + "paste 0.1.18", ] [[package]] @@ -4437,6 +4437,7 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "parity-wasm 0.41.0", + "paste 1.0.0", "pretty_assertions", "pwasm-utils", "serde", @@ -5379,6 +5380,12 @@ dependencies = [ "proc-macro-hack", ] +[[package]] +name = "paste" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ddc8e145de01d9180ac7b78b9676f95a9c2447f6a88b2c2a04702211bc5d71" + [[package]] name = "paste-impl" version = "0.1.18" @@ -8289,7 +8296,7 @@ dependencies = [ "log", "parity-scale-codec", "parity-util-mem", - "paste", + "paste 0.1.18", "rand 0.7.3", "serde", "serde_json", diff --git a/frame/contracts/Cargo.toml b/frame/contracts/Cargo.toml index 05fbd85bc698a..7d0b3dc62377d 100644 --- a/frame/contracts/Cargo.toml +++ b/frame/contracts/Cargo.toml @@ -27,7 +27,6 @@ sp-io = { version = "2.0.0-rc6", default-features = false, path = "../../primiti sp-std = { version = "2.0.0-rc6", default-features = false, path = "../../primitives/std" } sp-sandbox = { version = "0.8.0-rc6", default-features = false, path = "../../primitives/sandbox" } wasmi-validation = { version = "0.3.0", default-features = false } -wat = { version = "1.0", optional = true, default-features = false } [dev-dependencies] assert_matches = "1.3.0" @@ -35,6 +34,7 @@ hex-literal = "0.3.1" pallet-balances = { version = "2.0.0-rc6", path = "../balances" } pallet-timestamp = { version = "2.0.0-rc6", path = "../timestamp" } pallet-randomness-collective-flip = { version = "2.0.0-rc6", path = "../randomness-collective-flip" } +paste = "1.0" pretty_assertions = "0.6.1" wat = "1.0" @@ -57,10 +57,4 @@ std = [ ] runtime-benchmarks = [ "frame-benchmarking", - "wat", - # We are linking the wat crate which uses std and therefore brings with it the - # std panic handler. Therefore we need to disable out own panic handlers. Mind that - # we still override the std memory allocator. - "sp-io/disable_panic_handler", - "sp-io/disable_oom", ] diff --git a/frame/contracts/fixtures/benchmarks/dummy.wat b/frame/contracts/fixtures/benchmarks/dummy.wat deleted file mode 100644 index b878d26ef9185..0000000000000 --- a/frame/contracts/fixtures/benchmarks/dummy.wat +++ /dev/null @@ -1,4 +0,0 @@ -(module - (func (export "call")) - (func (export "deploy")) -) diff --git a/frame/contracts/src/benchmarking.rs b/frame/contracts/src/benchmarking.rs index ccf185d4849c6..3f73c6eadc485 100644 --- a/frame/contracts/src/benchmarking.rs +++ b/frame/contracts/src/benchmarking.rs @@ -21,12 +21,23 @@ use crate::*; use crate::Module as Contracts; +use crate::exec::StorageKey; use frame_benchmarking::{benchmarks, account}; use frame_system::{Module as System, RawOrigin}; -use parity_wasm::elements::FuncBody; +use parity_wasm::elements::{Instruction, Instructions, FuncBody, ValueType, BlockType}; use sp_runtime::traits::{Hash, Bounded, SaturatedConversion, CheckedDiv}; +use sp_std::{default::Default, convert::{TryFrom, TryInto}}; +/// How many batches we do per API benchmark. +const API_BENCHMARK_BATCHES: u32 = 20; + +/// How many API calls are executed in a single batch. The reason for increasing the amount +/// of API calls in batches (per benchmark component increase) is so that the linear regression +/// has an easier time determining the contribution of that component. +const API_BENCHMARK_BATCH_SIZE: u32 = 100; + +#[derive(Clone)] struct WasmModule { code: Vec, hash: ::Output, @@ -37,44 +48,117 @@ struct Contract { account_id: T::AccountId, addr: ::Source, endowment: BalanceOf, + code_hash: ::Output, } -macro_rules! load_module { - ($name:expr) => {{ - let code = include_bytes!(concat!("../fixtures/benchmarks/", $name, ".wat")); - compile_module::(code) - }}; +struct Tombstone { + contract: Contract, + storage: Vec<(StorageKey, Vec)>, } -fn compile_module(code: &[u8]) -> Result, &str> { - let text = sp_std::str::from_utf8(code).map_err(|_| "Invalid utf8 in wat file.")?; - let code = wat::parse_str(text).map_err(|_| "Failed to compile wat file.")?; - let hash = T::Hashing::hash(&code); - Ok(WasmModule { - code, - hash, - }) +struct ModuleDefinition { + data_segments: Vec, + memory: Option, + imported_functions: Vec, + deploy_body: Option, + call_body: Option, } -fn contract_with_call_body(body: FuncBody) -> WasmModule { - use parity_wasm::elements::{ - Instructions, Instruction::End, - }; - let contract = parity_wasm::builder::ModuleBuilder::new() - // deploy function (idx 0) +impl Default for ModuleDefinition { + fn default() -> Self { + Self { + data_segments: vec![], + memory: None, + imported_functions: vec![], + deploy_body: None, + call_body: None, + } + } +} + +struct ImportedFunction { + name: &'static str, + params: Vec, + return_type: Option, +} + +struct ImportedMemory { + min_pages: u32, + max_pages: u32, +} + +impl ImportedMemory { + fn max() -> Self { + let pages = max_pages::(); + Self { min_pages: pages, max_pages: pages } + } +} + +struct DataSegment { + offset: u32, + value: Vec, +} + +enum CountedInstruction { + // (offset, increment_by) + Counter(u32, u32), + Regular(Instruction), +} + +fn create_code(def: ModuleDefinition) -> WasmModule { + // internal functions start at that offset. + let func_offset = u32::try_from(def.imported_functions.len()).unwrap(); + + // Every contract must export "deploy" and "call" functions + let mut contract = parity_wasm::builder::module() + // deploy function (first internal function) .function() .signature().with_params(vec![]).with_return_type(None).build() - .body().with_instructions(Instructions::new(vec![End])).build() + .with_body(def.deploy_body.unwrap_or_else(|| + FuncBody::new(Vec::new(), Instructions::empty()) + )) .build() - // call function (idx 1) + // call function (second internal function) .function() .signature().with_params(vec![]).with_return_type(None).build() - .with_body(body) + .with_body(def.call_body.unwrap_or_else(|| + FuncBody::new(Vec::new(), Instructions::empty()) + )) + .build() + .export().field("deploy").internal().func(func_offset).build() + .export().field("call").internal().func(func_offset + 1).build(); + + // Grant access to linear memory. + if let Some(memory) = def.memory { + contract = contract.import() + .module("env").field("memory") + .external().memory(memory.min_pages, Some(memory.max_pages)) + .build(); + } + + // Import supervisor functions. They start with idx 0. + for func in def.imported_functions { + let sig = parity_wasm::builder::signature() + .with_params(func.params) + .with_return_type(func.return_type) + .build_sig(); + let sig = contract.push_signature(sig); + contract = contract.import() + .module("seal0") + .field(func.name) + .with_external(parity_wasm::elements::External::Function(sig)) + .build(); + } + + // Initialize memory + for data in def.data_segments { + contract = contract.data() + .offset(Instruction::I32Const(data.offset as i32)) + .value(data.value) .build() - .export().field("deploy").internal().func(0).build() - .export().field("call").internal().func(1).build() - .build(); - let code = contract.to_bytes().unwrap(); + } + + let code = contract.build().to_bytes().unwrap(); let hash = T::Hashing::hash(&code); WasmModule { code, @@ -82,82 +166,142 @@ fn contract_with_call_body(body: FuncBody) -> WasmModule { } } -fn expanded_contract(target_bytes: u32) -> WasmModule { - use parity_wasm::elements::{ - Instruction::{self, If, I32Const, Return, End}, - BlockType, Instructions, - }; +fn body(instructions: Vec) -> FuncBody { + FuncBody::new(Vec::new(), Instructions::new(instructions)) +} + +fn body_repeated(repetitions: u32, instructions: &[Instruction]) -> FuncBody { + let instructions = Instructions::new( + instructions + .iter() + .cycle() + .take(instructions.len() * usize::try_from(repetitions).unwrap()) + .cloned() + .chain(sp_std::iter::once(Instruction::End)) + .collect() + ); + FuncBody::new(Vec::new(), instructions) +} + +fn body_counted(repetitions: u32, mut instructions: Vec) -> FuncBody { + // We need to iterate over indices because we cannot cycle over mutable references + let body = (0..instructions.len()) + .cycle() + .take(instructions.len() * usize::try_from(repetitions).unwrap()) + .map(|idx| { + match &mut instructions[idx] { + CountedInstruction::Counter(offset, increment_by) => { + let current = *offset; + *offset += *increment_by; + Instruction::I32Const(current as i32) + }, + CountedInstruction::Regular(instruction) => instruction.clone(), + } + }) + .chain(sp_std::iter::once(Instruction::End)) + .collect(); + FuncBody::new(Vec::new(), Instructions::new(body)) +} + +fn dummy_code() -> WasmModule { + create_code::(Default::default()) +} + +fn sized_code(target_bytes: u32) -> WasmModule { + use parity_wasm::elements::Instruction::{If, I32Const, Return, End}; // Base size of a contract is 47 bytes and each expansion adds 6 bytes. // We do one expansion less to account for the code section and function body // size fields inside the binary wasm module representation which are leb128 encoded // and therefore grow in size when the contract grows. We are not allowed to overshoot // because of the maximum code size that is enforced by `put_code`. - let expansions = (target_bytes.saturating_sub(47) / 6).saturating_sub(1) as usize; + let expansions = (target_bytes.saturating_sub(47) / 6).saturating_sub(1); const EXPANSION: [Instruction; 4] = [ I32Const(0), If(BlockType::NoResult), Return, End, ]; - let instructions = Instructions::new( - EXPANSION - .iter() - .cycle() - .take(EXPANSION.len() * expansions) - .cloned() - .chain(sp_std::iter::once(End)) - .collect() - ); - contract_with_call_body::(FuncBody::new(Vec::new(), instructions)) -} - -/// Set the block number to one. -/// -/// 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 -/// undesired implicit bump (which messes with rent collection), wo do the bump ourselfs -/// in the setup closure so that both the instantiate and subsequent call are run with the -/// same block number. -fn init_block_number() { - System::::set_block_number(1.into()); + create_code::(ModuleDefinition { + call_body: Some(body_repeated(expansions, &EXPANSION)), + .. Default::default() + }) } -fn funding() -> BalanceOf { - BalanceOf::::max_value() / 2.into() +fn getter_code(getter_name: &'static str, repeat: u32) -> WasmModule { + let pages = max_pages::(); + create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: getter_name, + params: vec![ValueType::I32, ValueType::I32], + return_type: None, + }], + // Write the output buffer size. The output size will be overwritten by the + // supervisor with the real size when calling the getter. Since this size does not + // change between calls it suffices to start with an initial value and then just + // leave as whatever value was written there. + data_segments: vec![DataSegment { + offset: 0, + value: (pages * 64 * 1024 - 4).to_le_bytes().to_vec(), + }], + call_body: Some(body_repeated(repeat, &[ + Instruction::I32Const(4), // ptr where to store output + Instruction::I32Const(0), // ptr to length + Instruction::Call(0), // call the imported function + ])), + .. Default::default() + }) } -fn create_funded_user(string: &'static str, n: u32) -> T::AccountId { - let user = account(string, n, 0); - T::Currency::make_free_balance_be(&user, funding::()); - user +fn hasher_code(name: &'static str, repeat: u32, data_size: u32) -> WasmModule { + create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: name, + params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + return_type: None, + }], + call_body: Some(body_repeated(repeat, &[ + Instruction::I32Const(0), // input_ptr + Instruction::I32Const(data_size as i32), // input_len + Instruction::I32Const(0), // output_ptr + Instruction::Call(0), + ])), + .. Default::default() + }) } -fn eviction_at(addr: &T::AccountId) -> Result { - match crate::rent::compute_rent_projection::(addr).map_err(|_| "Invalid acc for rent")? { - RentProjection::EvictionAt(at) => Ok(at), - _ => Err("Account does not pay rent.")?, - } +enum Endow { + Max, + CollectRent, } -fn instantiate_raw_contract( - caller_name: &'static str, +fn instantiate_contract_from_account( + caller: T::AccountId, module: WasmModule, data: Vec, + endowment: Endow, ) -> Result, &'static str> { - // storage_size cannot be zero because otherwise a contract that is just above the subsistence - // threshold does not pay rent given a large enough subsistence threshold. But we need rent - // payments to occur in order to benchmark for worst cases. - let storage_size = Config::::subsistence_threshold_uncached() - .checked_div(&T::RentDepositOffset::get()) - .unwrap_or_else(Zero::zero); - - // Endowment should be large but not as large to inhibit rent payments. - let endowment = T::RentDepositOffset::get() - .saturating_mul(storage_size + T::StorageSizeOffset::get().into()) - .saturating_sub(1.into()); - - let caller = create_funded_user::(caller_name, 0); + let (storage_size, endowment) = match endowment { + Endow::CollectRent => { + // storage_size cannot be zero because otherwise a contract that is just above + // the subsistence threshold does not pay rent given a large enough subsistence + // threshold. But we need rent payments to occur in order to benchmark for worst cases. + let storage_size = Config::::subsistence_threshold_uncached() + .checked_div(&T::RentDepositOffset::get()) + .unwrap_or_else(Zero::zero); + + // Endowment should be large but not as large to inhibit rent payments. + let endowment = T::RentDepositOffset::get() + .saturating_mul(storage_size + T::StorageSizeOffset::get().into()) + .saturating_sub(1.into()); + + (storage_size, endowment) + }, + Endow::Max => (0.into(), max_endowment::()), + }; + T::Currency::make_free_balance_be(&caller, funding::()); let addr = T::DetermineContractAddress::contract_address_for(&module.hash, &data, &caller); init_block_number::(); Contracts::::put_code_raw(module.code)?; @@ -176,6 +320,70 @@ fn instantiate_raw_contract( account_id: addr.clone(), addr: T::Lookup::unlookup(addr), endowment, + code_hash: module.hash.clone(), + }) +} + +fn instantiate_contract_from_index( + index: u32, + module: WasmModule, + data: Vec, + endowment: Endow, +) -> Result, &'static str> { + instantiate_contract_from_account(account("instantiator", index, 0), module, data, endowment) +} + +fn instantiate_contract( + module: WasmModule, + data: Vec, + endowment: Endow, +) -> Result, &'static str> { + instantiate_contract_from_index(0, module, data, endowment) +} + +fn store_items( + account: &T::AccountId, + items: &Vec<(StorageKey, Vec)> +) -> Result<(), &'static str> { + let info = get_alive::(account)?; + for item in items { + crate::storage::write_contract_storage::( + account, + &info.trie_id, + &item.0, + Some(item.1.clone()), + ) + .map_err(|_| "Failed to write storage to restoration dest")?; + } + Ok(()) +} + +fn create_storage( + stor_num: u32, + stor_size: u32 +) -> Result)>, &'static str> { + (0..stor_num).map(|i| { + let hash = T::Hashing::hash_of(&i) + .as_ref() + .try_into() + .map_err(|_| "Hash too big for storage key")?; + Ok((hash, vec![42u8; stor_size as usize])) + }).collect::, &'static str>>() +} + +fn create_tombstone(stor_num: u32, stor_size: u32) -> Result, &'static str> { + let contract = instantiate_contract::(dummy_code(), vec![], Endow::CollectRent)?; + let storage_items = create_storage::(stor_num, stor_size)?; + store_items::(&contract.account_id, &storage_items)?; + System::::set_block_number( + eviction_at::(&contract.account_id)? + T::SignedClaimHandicap::get() + 5.into() + ); + crate::rent::collect_rent::(&contract.account_id); + ensure_tombstone::(&contract.account_id)?; + + Ok(Tombstone { + contract, + storage: storage_items, }) } @@ -194,6 +402,42 @@ fn ensure_tombstone(addr: &T::AccountId) -> Result<(), &'static str> { .map(|_| ()) } +fn max_pages() -> u32 { + Contracts::::current_schedule().max_memory_pages +} + +fn funding() -> BalanceOf { + BalanceOf::::max_value() / 2.into() +} + +fn max_endowment() -> BalanceOf { + funding::().saturating_sub(T::Currency::minimum_balance()) +} + +fn create_funded_user(string: &'static str, n: u32) -> T::AccountId { + let user = account(string, n, 0); + T::Currency::make_free_balance_be(&user, funding::()); + user +} + +fn eviction_at(addr: &T::AccountId) -> Result { + match crate::rent::compute_rent_projection::(addr).map_err(|_| "Invalid acc for rent")? { + RentProjection::EvictionAt(at) => Ok(at), + _ => Err("Account does not pay rent.")?, + } +} + +/// Set the block number to one. +/// +/// 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 +/// undesired implicit bump (which messes with rent collection), wo do the bump ourselfs +/// in the setup closure so that both the instantiate and subsequent call are run with the +/// same block number. +fn init_block_number() { + System::::set_block_number(1.into()); +} + benchmarks! { _ { } @@ -208,26 +452,24 @@ benchmarks! { // This constructs a contract that is maximal expensive to instrument. // It creates a maximum number of metering blocks per byte. + // `n`: Size of the code in kilobytes. put_code { - let n in 0 .. Contracts::::current_schedule().max_code_size; + let n in 0 .. Contracts::::current_schedule().max_code_size / 1024; let caller = create_funded_user::("caller", 0); - let module = expanded_contract::(n); + let module = sized_code::(n * 1024); let origin = RawOrigin::Signed(caller); }: _(origin, module.code) // Instantiate uses a dummy contract constructor to measure the overhead of the instantiate. - // The size of the data has no influence on the costs of this extrinsic as long as the contract - // won't call `seal_input` in its constructor to copy the data to contract memory. - // The dummy contract used here does not do this. The costs for the data copy is billed as - // part of `seal_input`. - // However, we still use data size as component here as it will be removed by the benchmarking - // as it has no influence on the weight. This works as "proof" and as regression test. + // 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. instantiate { - let n in 0 .. u16::max_value() as u32; - let data = vec![0u8; n as usize]; + let n in 0 .. max_pages::() * 64; + let data = vec![42u8; (n * 1024) as usize]; let endowment = Config::::subsistence_threshold_uncached(); let caller = create_funded_user::("caller", 0); - let WasmModule { code, hash } = load_module!("dummy")?; + let WasmModule { code, hash } = dummy_code::(); let origin = RawOrigin::Signed(caller.clone()); let addr = T::DetermineContractAddress::contract_address_for(&hash, &data, &caller); Contracts::::put_code_raw(code)?; @@ -242,16 +484,19 @@ benchmarks! { } // We just call a dummy contract to measure to overhead of the call extrinsic. - // As for instantiate the size of the data does not influence the costs. + // The size of the data has no influence on the costs of this extrinsic as long as the contract + // won't call `seal_input` in its constructor to copy the data to contract memory. + // The dummy contract used here does not do this. The costs for the data copy is billed as + // part of `seal_input`. call { - let n in 0 .. u16::max_value() as u32; - let data = vec![0u8; n as usize]; - let instance = instantiate_raw_contract("caller", load_module!("dummy")?, vec![])?; + let data = vec![42u8; 1024]; + let instance = instantiate_contract::(dummy_code(), vec![], Endow::CollectRent)?; let value = T::Currency::minimum_balance() * 100.into(); let origin = RawOrigin::Signed(instance.caller.clone()); // trigger rent collection for worst case performance of call System::::set_block_number(eviction_at::(&instance.account_id)? - 5.into()); + let before = T::Currency::free_balance(&instance.account_id); }: _(origin, instance.addr, value, Weight::max_value(), data) verify { // endowment and value transfered via call should be removed from the caller @@ -260,7 +505,7 @@ benchmarks! { funding::() - instance.endowment - value, ); // rent should have lowered the amount of balance of the contract - assert!(T::Currency::free_balance(&instance.account_id) < instance.endowment); + assert!(T::Currency::free_balance(&instance.account_id) < before + value); // but it should not have been evicted by the rent collection ensure_alive::(&instance.account_id)?; } @@ -272,16 +517,17 @@ benchmarks! { // no incentive to remove large contracts when the removal is more expensive than // the reward for removing them. claim_surcharge { - let instance = instantiate_raw_contract("caller", load_module!("dummy")?, vec![])?; + let instance = instantiate_contract::(dummy_code(), vec![], Endow::CollectRent)?; let origin = RawOrigin::Signed(instance.caller.clone()); let account_id = instance.account_id.clone(); // instantiate should leave us with an alive contract - ContractInfoOf::::get(instance.account_id.clone()).and_then(|c| c.get_alive()) - .ok_or("Instantiate must return an alive contract.")?; + ensure_alive::(&instance.account_id)?; // generate enough rent so that the contract is evicted - System::::set_block_number(eviction_at::(&instance.account_id)? + 5.into()); + System::::set_block_number( + eviction_at::(&instance.account_id)? + T::SignedClaimHandicap::get() + 5.into() + ); }: _(origin, account_id, None) verify { // the claim surcharge should have evicted the contract @@ -293,46 +539,1299 @@ benchmarks! { funding::() - instance.endowment + ::SurchargeReward::get(), ); } -} -#[cfg(test)] -mod tests { - use super::*; - use crate::tests::{ExtBuilder, Test}; - use frame_support::assert_ok; + seal_caller { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = instantiate_contract::(getter_code( + "seal_caller", r * API_BENCHMARK_BATCH_SIZE + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_address { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = instantiate_contract::(getter_code( + "seal_address", r * API_BENCHMARK_BATCH_SIZE + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_gas_left { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = instantiate_contract::(getter_code( + "seal_gas_left", r * API_BENCHMARK_BATCH_SIZE + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) - #[test] - fn update_schedule() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(test_benchmark_update_schedule::()); + seal_balance { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = instantiate_contract::(getter_code( + "seal_balance", r * API_BENCHMARK_BATCH_SIZE + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_value_transferred { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = instantiate_contract::(getter_code( + "seal_value_transferred", r * API_BENCHMARK_BATCH_SIZE + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_minimum_balance { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = instantiate_contract::(getter_code( + "seal_minimum_balance", r * API_BENCHMARK_BATCH_SIZE + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_tombstone_deposit { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = instantiate_contract::(getter_code( + "seal_tombstone_deposit", r * API_BENCHMARK_BATCH_SIZE + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_rent_allowance { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = instantiate_contract::(getter_code( + "seal_rent_allowance", r * API_BENCHMARK_BATCH_SIZE + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_block_number { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = instantiate_contract::(getter_code( + "seal_block_number", r * API_BENCHMARK_BATCH_SIZE + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_now { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = instantiate_contract::(getter_code( + "seal_now", r * API_BENCHMARK_BATCH_SIZE + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_weight_to_fee { + let r in 0 .. API_BENCHMARK_BATCHES; + let pages = max_pages::(); + let code = create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_weight_to_fee", + params: vec![ValueType::I64, ValueType::I32, ValueType::I32], + return_type: None, + }], + data_segments: vec![DataSegment { + offset: 0, + value: (pages * 64 * 1024 - 4).to_le_bytes().to_vec(), + }], + call_body: Some(body_repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + Instruction::I64Const(500_000), + Instruction::I32Const(4), + Instruction::I32Const(0), + Instruction::Call(0), + ])), + .. Default::default() + }); + let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_gas { + let r in 0 .. API_BENCHMARK_BATCHES; + let code = create_code(ModuleDefinition { + imported_functions: vec![ImportedFunction { + name: "gas", + params: vec![ValueType::I32], + return_type: None, + }], + call_body: Some(body_repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + Instruction::I32Const(42), + Instruction::Call(0), + ])), + .. Default::default() }); + let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // We cannot call seal_input multiple times. Therefore our weight determination is not + // as precise as with other APIs. Because this function can only be called once per + // contract it cannot be used for Dos. + seal_input { + let r in 0 .. 1; + let code = create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_input", + params: vec![ValueType::I32, ValueType::I32], + return_type: None, + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: 0u32.to_le_bytes().to_vec(), + }, + ], + call_body: Some(body_repeated(r, &[ + Instruction::I32Const(4), // ptr where to store output + Instruction::I32Const(0), // ptr to length + Instruction::Call(0), + ])), + .. Default::default() + }); + let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_input_per_kb { + let n in 0 .. max_pages::() * 64; + let pages = max_pages::(); + let buffer_size = pages * 64 * 1024 - 4; + let code = create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_input", + params: vec![ValueType::I32, ValueType::I32], + return_type: None, + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: buffer_size.to_le_bytes().to_vec(), + }, + ], + call_body: Some(body(vec![ + Instruction::I32Const(4), // ptr where to store output + Instruction::I32Const(0), // ptr to length + Instruction::Call(0), + Instruction::End, + ])), + .. Default::default() + }); + let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let data = vec![42u8; (n * 1024).min(buffer_size) as usize]; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), data) + + // The same argument as for `seal_input` is true here. + seal_return { + let r in 0 .. 1; + let code = create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_return", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + return_type: None, + }], + call_body: Some(body_repeated(r, &[ + Instruction::I32Const(0), // flags + Instruction::I32Const(0), // data_ptr + Instruction::I32Const(0), // data_len + Instruction::Call(0), + ])), + .. Default::default() + }); + let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_return_per_kb { + let n in 0 .. max_pages::() * 64; + let code = create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_return", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + return_type: None, + }], + call_body: Some(body(vec![ + Instruction::I32Const(0), // flags + Instruction::I32Const(0), // data_ptr + Instruction::I32Const((n * 1024) as i32), // data_len + Instruction::Call(0), + Instruction::End, + ])), + .. Default::default() + }); + let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // The same argument as for `seal_input` is true here. + seal_terminate { + let r in 0 .. 1; + let beneficiary = account::("beneficiary", 0, 0); + let beneficiary_bytes = beneficiary.encode(); + let beneficiary_len = beneficiary_bytes.len(); + let code = create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_terminate", + params: vec![ValueType::I32, ValueType::I32], + return_type: None, + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: beneficiary_bytes, + }, + ], + call_body: Some(body_repeated(r, &[ + Instruction::I32Const(0), // beneficiary_ptr + Instruction::I32Const(beneficiary_len as i32), // beneficiary_len + Instruction::Call(0), + ])), + .. Default::default() + }); + let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + assert_eq!(T::Currency::total_balance(&beneficiary), 0.into()); + assert_eq!(T::Currency::total_balance(&instance.account_id), max_endowment::()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + verify { + if r > 0 { + assert_eq!(T::Currency::total_balance(&instance.account_id), 0.into()); + assert_eq!(T::Currency::total_balance(&beneficiary), max_endowment::()); + } } - #[test] - fn put_code() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(test_benchmark_put_code::()); + seal_restore_to { + let r in 0 .. 1; + + // Restore just moves the trie id from origin to destination and therefore + // does not depend on the size of the destination contract. However, to not + // trigger any edge case we won't use an empty contract as destination. + let tombstone = create_tombstone::(10, T::MaxValueSize::get())?; + + let dest = tombstone.contract.account_id.encode(); + let dest_len = dest.len(); + let code_hash = tombstone.contract.code_hash.encode(); + let code_hash_len = code_hash.len(); + let rent_allowance = BalanceOf::::max_value().encode(); + let rent_allowance_len = rent_allowance.len(); + + let dest_offset = 0; + let code_hash_offset = dest_offset + dest_len; + let rent_allowance_offset = code_hash_offset + code_hash_len; + + let code = create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_restore_to", + params: vec![ + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ], + return_type: None, + }], + data_segments: vec![ + DataSegment { + offset: dest_offset as u32, + value: dest, + }, + DataSegment { + offset: code_hash_offset as u32, + value: code_hash, + }, + DataSegment { + offset: rent_allowance_offset as u32, + value: rent_allowance, + }, + ], + call_body: Some(body_repeated(r, &[ + Instruction::I32Const(dest_offset as i32), + Instruction::I32Const(dest_len as i32), + Instruction::I32Const(code_hash_offset as i32), + Instruction::I32Const(code_hash_len as i32), + Instruction::I32Const(rent_allowance_offset as i32), + Instruction::I32Const(rent_allowance_len as i32), + Instruction::I32Const(0), // delta_ptr + Instruction::I32Const(0), // delta_count + Instruction::Call(0), + ])), + .. Default::default() }); + + let instance = instantiate_contract_from_account::( + account("origin", 0, 0), code, vec![], Endow::Max + )?; + store_items::(&instance.account_id, &tombstone.storage)?; + System::::set_block_number(System::::block_number() + 1.into()); + + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + verify { + if r > 0 { + ensure_alive::(&tombstone.contract.account_id)?; + } } - #[test] - fn instantiate() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(test_benchmark_instantiate::()); + seal_restore_to_per_delta { + let d in 0 .. API_BENCHMARK_BATCHES; + let tombstone = create_tombstone::(0, 0)?; + let delta = create_storage::(d * API_BENCHMARK_BATCH_SIZE, T::MaxValueSize::get())?; + + let dest = tombstone.contract.account_id.encode(); + let dest_len = dest.len(); + let code_hash = tombstone.contract.code_hash.encode(); + let code_hash_len = code_hash.len(); + let rent_allowance = BalanceOf::::max_value().encode(); + let rent_allowance_len = rent_allowance.len(); + let delta_keys = delta.iter().flat_map(|(key, _)| key).cloned().collect::>(); + + let dest_offset = 0; + let code_hash_offset = dest_offset + dest_len; + let rent_allowance_offset = code_hash_offset + code_hash_len; + let delta_keys_offset = rent_allowance_offset + rent_allowance_len; + + let code = create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_restore_to", + params: vec![ + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ], + return_type: None, + }], + data_segments: vec![ + DataSegment { + offset: dest_offset as u32, + value: dest, + }, + DataSegment { + offset: code_hash_offset as u32, + value: code_hash, + }, + DataSegment { + offset: rent_allowance_offset as u32, + value: rent_allowance, + }, + DataSegment { + offset: delta_keys_offset as u32, + value: delta_keys, + }, + ], + call_body: Some(body(vec![ + Instruction::I32Const(dest_offset as i32), + Instruction::I32Const(dest_len as i32), + Instruction::I32Const(code_hash_offset as i32), + Instruction::I32Const(code_hash_len as i32), + Instruction::I32Const(rent_allowance_offset as i32), + Instruction::I32Const(rent_allowance_len as i32), + Instruction::I32Const(delta_keys_offset as i32), // delta_ptr + Instruction::I32Const(delta.len() as i32), // delta_count + Instruction::Call(0), + Instruction::End, + ])), + .. Default::default() }); + + let instance = instantiate_contract_from_account::( + account("origin", 0, 0), code, vec![], Endow::Max + )?; + store_items::(&instance.account_id, &tombstone.storage)?; + store_items::(&instance.account_id, &delta)?; + System::::set_block_number(System::::block_number() + 1.into()); + + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + verify { + ensure_alive::(&tombstone.contract.account_id)?; } - #[test] - fn call() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(test_benchmark_call::()); + // We benchmark only for the maximum subject length. We assume that this is some lowish + // number (< 1 KB). Therefore we are not overcharging too much in case a smaller subject is + // used. + seal_random { + let r in 0 .. API_BENCHMARK_BATCHES; + let pages = max_pages::(); + let subject_len = Contracts::::current_schedule().max_subject_len; + assert!(subject_len < 1024); + let code = create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_random", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], + return_type: None, + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: (pages * 64 * 1024 - subject_len - 4).to_le_bytes().to_vec(), + }, + ], + call_body: Some(body_repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + Instruction::I32Const(4), // subject_ptr + Instruction::I32Const(subject_len as i32), // subject_len + Instruction::I32Const((subject_len + 4) as i32), // out_ptr + Instruction::I32Const(0), // out_len_ptr + Instruction::Call(0), + ])), + .. Default::default() + }); + let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // Overhead of calling the function without any topic. + // We benchmark for the worst case (largest event). + seal_deposit_event { + let r in 0 .. API_BENCHMARK_BATCHES; + let code = create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_deposit_event", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], + return_type: None, + }], + call_body: Some(body_repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + Instruction::I32Const(0), // topics_ptr + Instruction::I32Const(0), // topics_len + Instruction::I32Const(0), // data_ptr + Instruction::I32Const(0), // data_len + Instruction::Call(0), + ])), + .. Default::default() + }); + let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // Benchmark the overhead that topics generate. + // `t`: Number of topics + // `n`: Size of event payload in kb + seal_deposit_event_per_topic_and_kb { + let t in 0 .. Contracts::::current_schedule().max_event_topics; + let n in 0 .. T::MaxValueSize::get() / 1024; + let mut topics = (0..API_BENCHMARK_BATCH_SIZE) + .map(|n| (n * t..n * t + t).map(|i| T::Hashing::hash_of(&i)).collect::>().encode()) + .peekable(); + let topics_len = topics.peek().map(|i| i.len()).unwrap_or(0); + let topics = topics.flatten().collect(); + use CountedInstruction::{Counter, Regular}; + let code = create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_deposit_event", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], + return_type: None, + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: topics, + }, + ], + call_body: Some(body_counted(API_BENCHMARK_BATCH_SIZE, vec![ + Counter(0, topics_len as u32), // topics_ptr + Regular(Instruction::I32Const(topics_len as i32)), // topics_len + Regular(Instruction::I32Const(0)), // data_ptr + Regular(Instruction::I32Const((n * 1024) as i32)), // data_len + Regular(Instruction::Call(0)), + ])), + .. Default::default() }); + let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_set_rent_allowance { + let r in 0 .. API_BENCHMARK_BATCHES; + let allowance = funding::().encode(); + let allowance_len = allowance.len(); + let code = create_code::(ModuleDefinition { + memory: Some(ImportedMemory { min_pages: 1, max_pages: 1 }), + imported_functions: vec![ImportedFunction { + name: "seal_set_rent_allowance", + params: vec![ValueType::I32, ValueType::I32], + return_type: None, + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: allowance, + }, + ], + call_body: Some(body_repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + Instruction::I32Const(0), // value_ptr + Instruction::I32Const(allowance_len as i32), // value_len + Instruction::Call(0), + ])), + .. Default::default() + }); + let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // Only the overhead of calling the function itself with minimal arguments. + // The contract is a bit more complex because I needs to use different keys in order + // to generate unique storage accesses. However, it is still dominated by the storage + // accesses. + seal_set_storage { + let r in 0 .. API_BENCHMARK_BATCHES; + let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) + .flat_map(|n| T::Hashing::hash_of(&n).as_ref().to_vec()) + .collect::>(); + let key_len = sp_std::mem::size_of::<::Output>(); + use CountedInstruction::{Counter, Regular}; + let code = create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_set_storage", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + return_type: None, + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: keys, + }, + ], + call_body: Some(body_counted(r * API_BENCHMARK_BATCH_SIZE, vec![ + Counter(0, key_len as u32), // key_ptr + Regular(Instruction::I32Const(0)), // value_ptr + Regular(Instruction::I32Const(0)), // value_len + Regular(Instruction::Call(0)), + ])), + .. Default::default() + }); + let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_set_storage_per_kb { + let n in 0 .. T::MaxValueSize::get() / 1024; + let key = T::Hashing::hash_of(&1u32).as_ref().to_vec(); + let key_len = key.len(); + let code = create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_set_storage", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + return_type: None, + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: key, + }, + ], + call_body: Some(body_repeated(API_BENCHMARK_BATCH_SIZE, &[ + Instruction::I32Const(0), // key_ptr + Instruction::I32Const(0), // value_ptr + Instruction::I32Const((n * 1024) as i32), // value_len + Instruction::Call(0), + ])), + .. Default::default() + }); + let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // Similar to seal_set_storage. However, we store all the keys that we are about to + // delete beforehand in order to prevent any optimizations that could occur when + // deleting a non existing key. + seal_clear_storage { + let r in 0 .. API_BENCHMARK_BATCHES; + let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) + .map(|n| T::Hashing::hash_of(&n).as_ref().to_vec()) + .collect::>(); + let key_bytes = keys.iter().flatten().cloned().collect::>(); + let key_len = sp_std::mem::size_of::<::Output>(); + use CountedInstruction::{Counter, Regular}; + let code = create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_clear_storage", + params: vec![ValueType::I32], + return_type: None, + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: key_bytes, + }, + ], + call_body: Some(body_counted(r * API_BENCHMARK_BATCH_SIZE, vec![ + Counter(0, key_len as u32), + Regular(Instruction::Call(0)), + ])), + .. Default::default() + }); + let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let trie_id = get_alive::(&instance.account_id)?.trie_id; + for key in keys { + crate::storage::write_contract_storage::( + &instance.account_id, + &trie_id, + key.as_slice().try_into().map_err(|e| "Key has wrong length")?, + Some(vec![42; T::MaxValueSize::get() as usize]) + ) + .map_err(|_| "Failed to write to storage during setup.")?; + } + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // We make sure that all storage accesses are to unique keys. + seal_get_storage { + let r in 0 .. API_BENCHMARK_BATCHES; + let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) + .map(|n| T::Hashing::hash_of(&n).as_ref().to_vec()) + .collect::>(); + let key_len = sp_std::mem::size_of::<::Output>(); + let key_bytes = keys.iter().flatten().cloned().collect::>(); + let key_bytes_len = key_bytes.len(); + use CountedInstruction::{Counter, Regular}; + let code = create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_get_storage", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: key_bytes, + }, + ], + call_body: Some(body_counted(r * API_BENCHMARK_BATCH_SIZE, vec![ + Counter(0, key_len as u32), // key_ptr + Regular(Instruction::I32Const((key_bytes_len + 4) as i32)), // out_ptr + Regular(Instruction::I32Const(key_bytes_len as i32)), // out_len_ptr + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let trie_id = get_alive::(&instance.account_id)?.trie_id; + for key in keys { + crate::storage::write_contract_storage::( + &instance.account_id, + &trie_id, + key.as_slice().try_into().map_err(|e| "Key has wrong length")?, + Some(vec![]) + ) + .map_err(|_| "Failed to write to storage during setup.")?; + } + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_get_storage_per_kb { + let n in 0 .. T::MaxValueSize::get() / 1024; + let key = T::Hashing::hash_of(&1u32).as_ref().to_vec(); + let key_len = key.len(); + let code = create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_get_storage", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: key.clone(), + }, + DataSegment { + offset: key_len as u32, + value: T::MaxValueSize::get().to_le_bytes().into(), + }, + ], + call_body: Some(body_repeated(API_BENCHMARK_BATCH_SIZE, &[ + // call at key_ptr + Instruction::I32Const(0), // key_ptr + Instruction::I32Const((key_len + 4) as i32), // out_ptr + Instruction::I32Const(key_len as i32), // out_len_ptr + Instruction::Call(0), + Instruction::Drop, + ])), + .. Default::default() + }); + let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let trie_id = get_alive::(&instance.account_id)?.trie_id; + crate::storage::write_contract_storage::( + &instance.account_id, + &trie_id, + key.as_slice().try_into().map_err(|e| "Key has wrong length")?, + Some(vec![42u8; (n * 1024) as usize]) + ) + .map_err(|_| "Failed to write to storage during setup.")?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // We transfer to unique accounts. + seal_transfer { + let r in 0 .. API_BENCHMARK_BATCHES; + let accounts = (0..r * API_BENCHMARK_BATCH_SIZE) + .map(|i| account::("receiver", i, 0)) + .collect::>(); + let account_len = accounts.get(0).map(|i| i.encode().len()).unwrap_or(0); + let account_bytes = accounts.iter().flat_map(|x| x.encode()).collect(); + let value = Config::::subsistence_threshold_uncached(); + assert!(value > 0.into()); + let value_bytes = value.encode(); + let value_len = value_bytes.len(); + use CountedInstruction::{Counter, Regular}; + let code = create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_transfer", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: value_bytes, + }, + DataSegment { + offset: value_len as u32, + value: account_bytes, + }, + ], + call_body: Some(body_counted(r * API_BENCHMARK_BATCH_SIZE, vec![ + Counter(value_len as u32, account_len as u32), // account_ptr + Regular(Instruction::I32Const(account_len as i32)), // account_len + Regular(Instruction::I32Const(0)), // value_ptr + Regular(Instruction::I32Const(value_len as i32)), // value_len + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + for account in &accounts { + assert_eq!(T::Currency::total_balance(account), 0.into()); + } + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + verify { + for account in &accounts { + assert_eq!(T::Currency::total_balance(account), value); + } + } + + // We call unique accounts. + seal_call { + let r in 0 .. API_BENCHMARK_BATCHES; + let dummy_code = dummy_code::(); + let callees = (0..r * API_BENCHMARK_BATCH_SIZE) + .map(|i| instantiate_contract_from_index(i + 1, dummy_code.clone(), vec![], Endow::Max)) + .collect::, _>>()?; + let callee_len = callees.get(0).map(|i| i.account_id.encode().len()).unwrap_or(0); + let callee_bytes = callees.iter().flat_map(|x| x.account_id.encode()).collect(); + let value: BalanceOf = 0.into(); + let value_bytes = value.encode(); + let value_len = value_bytes.len(); + use CountedInstruction::{Counter, Regular}; + let code = create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_call", + params: vec![ + ValueType::I32, + ValueType::I32, + ValueType::I64, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: value_bytes, + }, + DataSegment { + offset: value_len as u32, + value: callee_bytes, + }, + ], + call_body: Some(body_counted(r * API_BENCHMARK_BATCH_SIZE, vec![ + Counter(value_len as u32, callee_len as u32), // callee_ptr + Regular(Instruction::I32Const(callee_len as i32)), // callee_len + Regular(Instruction::I64Const(0)), // gas + Regular(Instruction::I32Const(0)), // value_ptr + Regular(Instruction::I32Const(value_len as i32)), // value_len + Regular(Instruction::I32Const(0)), // input_data_ptr + Regular(Instruction::I32Const(0)), // input_data_len + Regular(Instruction::I32Const(u32::max_value() as i32)), // output_ptr + Regular(Instruction::I32Const(0)), // output_len_ptr + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_call_per_transfer_input_output_kb { + let t in 0 .. 1; + let i in 0 .. max_pages::() * 64; + let o in 0 .. (max_pages::() - 1) * 64; + let callee_code = create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_return", + params: vec![ + ValueType::I32, + ValueType::I32, + ValueType::I32, + ], + return_type: None, + }], + call_body: Some(body(vec![ + Instruction::I32Const(0), // flags + Instruction::I32Const(0), // data_ptr + Instruction::I32Const((o * 1024) as i32), // data_len + Instruction::Call(0), + Instruction::End, + ])), + .. Default::default() + }); + let callees = (0..API_BENCHMARK_BATCH_SIZE) + .map(|i| instantiate_contract_from_index(i + 1, callee_code.clone(), vec![], Endow::Max)) + .collect::, _>>()?; + let callee_len = callees.get(0).map(|i| i.account_id.encode().len()).unwrap_or(0); + let callee_bytes = callees.iter().flat_map(|x| x.account_id.encode()).collect::>(); + let callees_len = callee_bytes.len(); + let value: BalanceOf = t.into(); + let value_bytes = value.encode(); + let value_len = value_bytes.len(); + use CountedInstruction::{Counter, Regular}; + let code = create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_call", + params: vec![ + ValueType::I32, + ValueType::I32, + ValueType::I64, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: value_bytes, + }, + DataSegment { + offset: value_len as u32, + value: callee_bytes, + }, + DataSegment { + offset: (value_len + callees_len) as u32, + value: (o * 1024).to_le_bytes().into(), + }, + ], + call_body: Some(body_counted(API_BENCHMARK_BATCH_SIZE, vec![ + Counter(value_len as u32, callee_len as u32), // callee_ptr + Regular(Instruction::I32Const(callee_len as i32)), // callee_len + Regular(Instruction::I64Const(0)), // gas + Regular(Instruction::I32Const(0)), // value_ptr + Regular(Instruction::I32Const(value_len as i32)), // value_len + Regular(Instruction::I32Const(0)), // input_data_ptr + Regular(Instruction::I32Const((i * 1024) as i32)), // input_data_len + Regular(Instruction::I32Const((value_len + callees_len + 4) as i32)), // output_ptr + Regular(Instruction::I32Const((value_len + callees_len) as i32)), // output_len_ptr + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // We assume that every instantiate sends at least the subsistence amount. + seal_instantiate { + let r in 0 .. API_BENCHMARK_BATCHES; + let hashes = (0..r * API_BENCHMARK_BATCH_SIZE) + .map(|i| { + let code = create_code::(ModuleDefinition { + call_body: Some(body(vec![ + Instruction::I32Const(i as i32), + Instruction::Drop, + Instruction::End, + ])), + .. Default::default() + }); + Contracts::::put_code_raw(code.code)?; + Ok(code.hash) + }) + .collect::, &'static str>>()?; + let hash_len = hashes.get(0).map(|x| x.encode().len()).unwrap_or(0); + let hashes_bytes = hashes.iter().flat_map(|x| x.encode()).collect::>(); + let hashes_len = hashes_bytes.len(); + let value = Config::::subsistence_threshold_uncached(); + assert!(value > 0.into()); + let value_bytes = value.encode(); + let value_len = value_bytes.len(); + let addr_len = sp_std::mem::size_of::(); + + // offsets where to place static data in contract memory + let value_offset = 0; + let hashes_offset = value_offset + value_len; + let addr_len_offset = hashes_offset + hashes_len; + let addr_offset = addr_len_offset + addr_len; + + use CountedInstruction::{Counter, Regular}; + let code = create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_instantiate", + params: vec![ + ValueType::I32, + ValueType::I32, + ValueType::I64, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32 + ], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: value_offset as u32, + value: value_bytes, + }, + DataSegment { + offset: hashes_offset as u32, + value: hashes_bytes, + }, + DataSegment { + offset: addr_len_offset as u32, + value: addr_len.to_le_bytes().into(), + }, + ], + call_body: Some(body_counted(r * API_BENCHMARK_BATCH_SIZE, vec![ + Counter(hashes_offset as u32, hash_len as u32), // code_hash_ptr + Regular(Instruction::I32Const(hash_len as i32)), // code_hash_len + Regular(Instruction::I64Const(0)), // gas + Regular(Instruction::I32Const(value_offset as i32)), // value_ptr + Regular(Instruction::I32Const(value_len as i32)), // value_len + Regular(Instruction::I32Const(0)), // input_data_ptr + Regular(Instruction::I32Const(0)), // input_data_len + Regular(Instruction::I32Const(addr_offset as i32)), // address_ptr + 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::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + let addresses = hashes + .iter() + .map(|hash| T::DetermineContractAddress::contract_address_for( + hash, &[], &instance.account_id + )) + .collect::>(); + + for addr in &addresses { + if let Some(_) = ContractInfoOf::::get(&addr) { + return Err("Expected that contract does not exist at this point."); + } + } + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + verify { + for addr in &addresses { + ensure_alive::(addr)?; + } } - #[test] - fn claim_surcharge() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(test_benchmark_claim_surcharge::()); + seal_instantiate_per_input_output_kb { + let i in 0 .. (max_pages::() - 1) * 64; + let o in 0 .. (max_pages::() - 1) * 64; + let callee_code = create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_return", + params: vec![ + ValueType::I32, + ValueType::I32, + ValueType::I32, + ], + return_type: None, + }], + deploy_body: Some(body(vec![ + Instruction::I32Const(0), // flags + Instruction::I32Const(0), // data_ptr + Instruction::I32Const((o * 1024) as i32), // data_len + Instruction::Call(0), + Instruction::End, + ])), + .. Default::default() }); + let hash = callee_code.hash.clone(); + let hash_bytes = callee_code.hash.encode(); + let hash_len = hash_bytes.len(); + Contracts::::put_code_raw(callee_code.code)?; + let inputs = (0..API_BENCHMARK_BATCH_SIZE).map(|x| x.encode()).collect::>(); + let input_len = inputs.get(0).map(|x| x.len()).unwrap_or(0); + let input_bytes = inputs.iter().cloned().flatten().collect::>(); + let inputs_len = input_bytes.len(); + let value = Config::::subsistence_threshold_uncached(); + assert!(value > 0.into()); + let value_bytes = value.encode(); + let value_len = value_bytes.len(); + let addr_len = sp_std::mem::size_of::(); + + // offsets where to place static data in contract memory + let input_offset = 0; + let value_offset = inputs_len; + let hash_offset = value_offset + value_len; + let addr_len_offset = hash_offset + hash_len; + let output_len_offset = addr_len_offset + 4; + let output_offset = output_len_offset + 4; + + use CountedInstruction::{Counter, Regular}; + let code = create_code::(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_instantiate", + params: vec![ + ValueType::I32, + ValueType::I32, + ValueType::I64, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32 + ], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: input_offset as u32, + value: input_bytes, + }, + DataSegment { + offset: value_offset as u32, + value: value_bytes, + }, + DataSegment { + offset: hash_offset as u32, + value: hash_bytes, + }, + DataSegment { + offset: addr_len_offset as u32, + value: (addr_len as u32).to_le_bytes().into(), + }, + DataSegment { + offset: output_len_offset as u32, + value: (o * 1024).to_le_bytes().into(), + }, + ], + call_body: Some(body_counted(API_BENCHMARK_BATCH_SIZE, vec![ + Regular(Instruction::I32Const(hash_offset as i32)), // code_hash_ptr + Regular(Instruction::I32Const(hash_len as i32)), // code_hash_len + Regular(Instruction::I64Const(0)), // gas + Regular(Instruction::I32Const(value_offset as i32)), // value_ptr + Regular(Instruction::I32Const(value_len as i32)), // value_len + Counter(input_offset as u32, input_len as u32), // input_data_ptr + Regular(Instruction::I32Const((i * 1024).max(input_len as u32) as i32)), // input_data_len + Regular(Instruction::I32Const((addr_len_offset + addr_len) as i32)), // address_ptr + 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 + Regular(Instruction::Call(0)), + Regular(Instruction::I32Eqz), + Regular(Instruction::If(BlockType::NoResult)), + Regular(Instruction::Nop), + Regular(Instruction::Else), + Regular(Instruction::Unreachable), + Regular(Instruction::End), + ])), + .. Default::default() + }); + let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // Only the overhead of calling the function itself with minimal arguments. + seal_hash_sha2_256 { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = instantiate_contract::(hasher_code( + "seal_hash_sha2_256", r * API_BENCHMARK_BATCH_SIZE, 0, + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // `n`: Input to hash in kilobytes + seal_hash_sha2_256_per_kb { + let n in 0 .. max_pages::() * 64; + let instance = instantiate_contract::(hasher_code( + "seal_hash_sha2_256", API_BENCHMARK_BATCH_SIZE, n * 1024, + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // Only the overhead of calling the function itself with minimal arguments. + seal_hash_keccak_256 { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = instantiate_contract::(hasher_code( + "seal_hash_keccak_256", r * API_BENCHMARK_BATCH_SIZE, 0, + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // `n`: Input to hash in kilobytes + seal_hash_keccak_256_per_kb { + let n in 0 .. max_pages::() * 64; + let instance = instantiate_contract::(hasher_code( + "seal_hash_keccak_256", API_BENCHMARK_BATCH_SIZE, n * 1024, + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // Only the overhead of calling the function itself with minimal arguments. + seal_hash_blake2_256 { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = instantiate_contract::(hasher_code( + "seal_hash_blake2_256", r * API_BENCHMARK_BATCH_SIZE, 0, + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // `n`: Input to hash in kilobytes + seal_hash_blake2_256_per_kb { + let n in 0 .. max_pages::() * 64; + let instance = instantiate_contract::(hasher_code( + "seal_hash_blake2_256", API_BENCHMARK_BATCH_SIZE, n * 1024, + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // Only the overhead of calling the function itself with minimal arguments. + seal_hash_blake2_128 { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = instantiate_contract::(hasher_code( + "seal_hash_blake2_128", r * API_BENCHMARK_BATCH_SIZE, 0, + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // `n`: Input to hash in kilobytes + seal_hash_blake2_128_per_kb { + let n in 0 .. max_pages::() * 64; + let instance = instantiate_contract::(hasher_code( + "seal_hash_blake2_128", API_BENCHMARK_BATCH_SIZE, n * 1024, + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::{ExtBuilder, Test}; + use frame_support::assert_ok; + use paste::paste; + + macro_rules! create_test { + ($name:ident) => { + #[test] + fn $name() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(paste!{ + []::() + }); + }); + } + } } + + create_test!(update_schedule); + create_test!(put_code); + create_test!(instantiate); + create_test!(call); + create_test!(claim_surcharge); + create_test!(seal_caller); + create_test!(seal_address); + create_test!(seal_gas_left); + create_test!(seal_balance); + create_test!(seal_value_transferred); + create_test!(seal_minimum_balance); + create_test!(seal_tombstone_deposit); + create_test!(seal_rent_allowance); + create_test!(seal_block_number); + create_test!(seal_now); + create_test!(seal_weight_to_fee); + create_test!(seal_gas); + create_test!(seal_input); + create_test!(seal_input_per_kb); + create_test!(seal_return); + create_test!(seal_return_per_kb); + create_test!(seal_terminate); + create_test!(seal_restore_to); + create_test!(seal_restore_to_per_delta); + create_test!(seal_random); + create_test!(seal_deposit_event); + create_test!(seal_deposit_event_per_topic_and_kb); + create_test!(seal_set_rent_allowance); + create_test!(seal_set_storage); + create_test!(seal_set_storage_per_kb); + create_test!(seal_get_storage); + create_test!(seal_get_storage_per_kb); + create_test!(seal_transfer); + create_test!(seal_call); + create_test!(seal_call_per_transfer_input_output_kb); + create_test!(seal_clear_storage); + create_test!(seal_hash_sha2_256); + create_test!(seal_hash_sha2_256_per_kb); + create_test!(seal_hash_keccak_256); + create_test!(seal_hash_keccak_256_per_kb); + create_test!(seal_hash_blake2_256); + create_test!(seal_hash_blake2_256_per_kb); + create_test!(seal_hash_blake2_128); + create_test!(seal_hash_blake2_128_per_kb); } diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 387b110423f0c..14c4207b2ead0 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -78,6 +78,7 @@ //! * [Balances](../pallet_balances/index.html) #![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(feature = "runtime-benchmarks", recursion_limit="256")] #[macro_use] mod gas; From 2cbe4728c1f0cae40ff4510709513c8b638566d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Mon, 31 Aug 2020 17:09:23 +0200 Subject: [PATCH 07/29] seal: Create a default schedule from benchmark generated WeightInfo --- bin/node/runtime/src/lib.rs | 1 + frame/contracts/src/benchmarking.rs | 6 +- frame/contracts/src/lib.rs | 7 + frame/contracts/src/schedule.rs | 346 ++++++++++++++++++++++++++++ frame/contracts/src/tests.rs | 1 + frame/contracts/src/weight_info.rs | 341 +++++++++++++++++++++++++++ 6 files changed, 697 insertions(+), 5 deletions(-) create mode 100644 frame/contracts/src/schedule.rs create mode 100644 frame/contracts/src/weight_info.rs diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 54dea704bd7f6..111abf4d907eb 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -652,6 +652,7 @@ impl pallet_contracts::Trait for Runtime { type MaxDepth = pallet_contracts::DefaultMaxDepth; type MaxValueSize = pallet_contracts::DefaultMaxValueSize; type WeightPrice = pallet_transaction_payment::Module; + type WeightInfo = (); } impl pallet_sudo::Trait for Runtime { diff --git a/frame/contracts/src/benchmarking.rs b/frame/contracts/src/benchmarking.rs index 3f73c6eadc485..e0c160bb70230 100644 --- a/frame/contracts/src/benchmarking.rs +++ b/frame/contracts/src/benchmarking.rs @@ -22,6 +22,7 @@ use crate::*; use crate::Module as Contracts; use crate::exec::StorageKey; +use crate::schedule::API_BENCHMARK_BATCH_SIZE; use frame_benchmarking::{benchmarks, account}; use frame_system::{Module as System, RawOrigin}; @@ -32,11 +33,6 @@ use sp_std::{default::Default, convert::{TryFrom, TryInto}}; /// How many batches we do per API benchmark. const API_BENCHMARK_BATCHES: u32 = 20; -/// How many API calls are executed in a single batch. The reason for increasing the amount -/// of API calls in batches (per benchmark component increase) is so that the linear regression -/// has an easier time determining the contribution of that component. -const API_BENCHMARK_BATCH_SIZE: u32 = 100; - #[derive(Clone)] struct WasmModule { code: Vec, diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 14c4207b2ead0..00feea925d99b 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -87,6 +87,8 @@ mod exec; mod wasm; mod rent; mod benchmarking; +mod schedule; +mod weight_info; #[cfg(test)] mod tests; @@ -97,6 +99,7 @@ use crate::wasm::{WasmLoader, WasmVm}; pub use crate::gas::{Gas, GasMeter}; pub use crate::exec::{ExecResult, ExecReturnValue}; pub use crate::wasm::ReturnCode as RuntimeReturnCode; +pub use crate::weight_info::WeightInfo; #[cfg(feature = "std")] use serde::{Serialize, Deserialize}; @@ -374,6 +377,10 @@ pub trait Trait: frame_system::Trait { /// Used to answer contracts's queries regarding the current weight price. This is **not** /// used to calculate the actual fee and is only for informational purposes. type WeightPrice: Convert>; + + /// Describes the weights of the dispatchables of this module and is also used to + /// construct a default cost schedule. + type WeightInfo: WeightInfo; } /// Simple contract address determiner. diff --git a/frame/contracts/src/schedule.rs b/frame/contracts/src/schedule.rs new file mode 100644 index 0000000000000..b86113a762c29 --- /dev/null +++ b/frame/contracts/src/schedule.rs @@ -0,0 +1,346 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! This module contains the cost schedule and supporting code that constructs a +//! sane default schedule from a `WeightInfo` implementation. + +use crate::{Trait, WeightInfo}; + +#[cfg(feature = "std")] +use serde::{Serialize, Deserialize}; +use frame_support::weights::Weight; +use sp_std::{marker::PhantomData, fmt}; +use codec::{Encode, Decode}; + +/// How many API calls are executed in a single batch. The reason for increasing the amount +/// of API calls in batches (per benchmark component increase) is so that the linear regression +/// has an easier time determining the contribution of that component. +pub const API_BENCHMARK_BATCH_SIZE: u32 = 100; + +/// Definition of the cost schedule and other parameterizations for wasm vm. +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Clone, Encode, Decode, PartialEq, Eq)] +pub struct Schedule { + /// The type parameter is used in the default implementation. + pub phantom: PhantomData, + + /// Version of the schedule. + pub version: u32, + + /// Weight of a growing memory by single page. + pub op_cost_grow_mem: Weight, + + /// Weight of a regular operation. + pub op_cost_regular: Weight, + + /// Weight of calling `seal_caller`. + pub api_cost_caller: Weight, + + /// Weight of calling `seal_address`. + pub api_cost_address: Weight, + + /// Weight of calling `seal_gas_left`. + pub api_cost_gas_left: Weight, + + /// Weight of calling `seal_balance`. + pub api_cost_balance: Weight, + + /// Weight of calling `seal_value_transferred`. + pub api_cost_value_transferred: Weight, + + /// Weight of calling `seal_minimum_balance`. + pub api_cost_minimum_balance: Weight, + + /// Weight of calling `seal_tombstone_deposit`. + pub api_cost_tombstone_deposit: Weight, + + /// Weight of calling `seal_rent_allowance`. + pub api_cost_rent_allowance: Weight, + + /// Weight of calling `seal_block_number`. + pub api_cost_block_number: Weight, + + /// Weight of calling `seal_now`. + pub api_cost_now: Weight, + + /// Weight of calling `seal_weight_to_fee`. + pub api_cost_weight_to_fee: Weight, + + /// Weight of calling `gas`. + pub api_cost_gas: Weight, + + /// Weight of calling `seal_input`. + pub api_cost_input: Weight, + + /// Weight per input byte copied to contract memory by `seal_input`. + pub api_cost_input_per_byte: Weight, + + /// Weight of calling `seal_return`. + pub api_cost_return: Weight, + + /// Weight per byte returned through `seal_return`. + pub api_cost_return_per_byte: Weight, + + /// Weight of calling `seal_terminate`. + pub api_cost_terminate: Weight, + + /// Weight of calling `seal_restore_to`. + pub api_cost_restore_to: Weight, + + /// Weight per delta key supplied to `seal_restore_to`. + pub api_cost_restore_to_per_delta: Weight, + + /// Weight of calling `seal_random`. + pub api_cost_random: Weight, + + /// Weight of calling `seal_reposit_event`. + pub api_cost_deposit_event: Weight, + + /// Weight per topic supplied to `seal_deposit_event`. + pub api_cost_deposit_event_per_topic: Weight, + + /// Weight per byte of an event deposited through `seal_deposit_event`. + pub api_cost_deposit_event_per_byte: Weight, + + /// Weight of calling `seal_set_rent_allowance`. + pub api_cost_set_rent_allowance: Weight, + + /// Weight of calling `seal_set_storage`. + pub api_cost_set_storage: Weight, + + /// Weight per byte of an item stored with `seal_set_storage`. + pub api_cost_set_storage_per_byte: Weight, + + /// Weight of calling `seal_clear_storage`. + pub api_cost_clear_storage: Weight, + + /// Weight of calling `seal_get_storage`. + pub api_cost_get_storage: Weight, + + /// Weight per byte of an item received via `seal_get_storage`. + pub api_cost_get_storage_per_byte: Weight, + + /// Weight of calling `seal_transfer`. + pub api_cost_transfer: Weight, + + /// Weight of calling `seal_call`. + pub api_cost_call: Weight, + + /// Weight surcharge that is claimed if `seal_call` does a balance transfer. + pub api_cost_call_transfer_surcharge: Weight, + + /// Weight per input byte supplied to `seal_call`. + pub api_cost_call_per_input_byte: Weight, + + /// Weight per output byte received through `seal_call`. + pub api_cost_call_per_output_byte: Weight, + + /// Weight of calling `seal_instantiate`. + pub api_cost_instantiate: Weight, + + /// Weight per input byte supplied to `seal_instantiate`. + pub api_cost_instantiate_per_input_byte: Weight, + + /// Weight per output byte received through `seal_instantiate`. + pub api_cost_instantiate_per_output_byte: Weight, + + /// Weight of calling `seal_hash_sha_256`. + pub api_cost_hash_sha2_256: Weight, + + /// Weight per byte hashed by `seal_hash_sha_256`. + pub api_cost_hash_sha2_256_per_byte: Weight, + + /// Weight of calling `seal_hash_keccak_256`. + pub api_cost_hash_keccak_256: Weight, + + /// Weight per byte hashed by `seal_hash_keccak_256`. + pub api_cost_hash_keccak_256_per_byte: Weight, + + /// Weight of calling `seal_hash_blake2_256`. + pub api_cost_hash_blake2_256: Weight, + + /// Weight per byte hashed by `seal_hash_blake2_256`. + pub api_cost_hash_blake2_256_per_byte: Weight, + + /// Weight of calling `seal_hash_blake2_128`. + pub api_cost_hash_blake2_128: Weight, + + /// Weight per byte hashed by `seal_hash_blake2_128`. + pub api_cost_hash_blake2_128_per_byte: Weight, + + /// Whether the `seal_println` function is allowed to be used contracts. + /// MUST only be enabled for `dev` chains, NOT for production chains + pub enable_println: bool, + + /// The maximum number of topics supported by an event. + pub max_event_topics: u32, + + /// Maximum allowed stack height. + /// + /// See https://wiki.parity.io/WebAssembly-StackHeight to find out + /// how the stack frame cost is calculated. + pub max_stack_height: u32, + + /// Maximum number of memory pages allowed for a contract. + pub max_memory_pages: u32, + + /// Maximum allowed size of a declared table. + pub max_table_size: u32, + + /// The maximum length of a subject used for PRNG generation. + pub max_subject_len: u32, + + /// The maximum length of a contract code in bytes. This limit applies to the uninstrumented + /// and pristine form of the code as supplied to `put_code`. + pub max_code_size: u32, +} + +/// We need to implement Debug manually because the automatic derive enforces T +/// to also implement Debug. +impl fmt::Debug for Schedule { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Schedule").finish() + } +} + +/// 500 (2 instructions per nano second on 2GHZ) * 1000x slowdown through wasmi +/// This is a wild guess and should be viewed as a rough estimation. +/// Proper benchmarks are needed before this value and its derivatives can be used in production. +const WASM_INSTRUCTION_COST: Weight = 500_000; + +macro_rules! replace_token { + ($_in:tt $replacement:tt) => { $replacement }; +} + +macro_rules! call_zero { + ($name:ident, $( $arg:expr ),*) => { + T::WeightInfo::$name($( replace_token!($arg 0) ),*) + }; +} + +macro_rules! cost_args { + ($name:ident, $( $arg: expr ),+) => { + (T::WeightInfo::$name($( $arg ),+).saturating_sub(call_zero!($name, $( $arg ),+))) + } +} + +macro_rules! cost_batched_args { + ($name:ident, $( $arg: expr ),+) => { + cost_args!($name, $( $arg ),+) / Weight::from(API_BENCHMARK_BATCH_SIZE) + } +} + +macro_rules! cost_byte_args { + ($name:ident, $( $arg: expr ),+) => { + cost_args!($name, $( $arg ),+) / 1024 + } +} + +macro_rules! cost_byte_batched_args { + ($name:ident, $( $arg: expr ),+) => { + cost_batched_args!($name, $( $arg ),+) / 1024 + } +} + +macro_rules! cost { + ($name:ident) => { + cost_args!($name, 1) + } +} + +macro_rules! cost_batched { + ($name:ident) => { + cost_batched_args!($name, 1) + } +} + +macro_rules! cost_byte { + ($name:ident) => { + cost_byte_args!($name, 1) + } +} + +macro_rules! cost_byte_batched { + ($name:ident) => { + cost_byte_batched_args!($name, 1) + } +} + +impl Default for Schedule { + fn default() -> Self { + Self { + phantom: PhantomData, + version: 0, + + op_cost_grow_mem: WASM_INSTRUCTION_COST, + op_cost_regular: WASM_INSTRUCTION_COST, + + api_cost_caller: cost_batched!(seal_caller), + api_cost_address: cost_batched!(seal_address), + api_cost_gas_left: cost_batched!(seal_gas_left), + api_cost_balance: cost_batched!(seal_balance), + api_cost_value_transferred: cost_batched!(seal_value_transferred), + api_cost_minimum_balance: cost_batched!(seal_minimum_balance), + api_cost_tombstone_deposit: cost_batched!(seal_tombstone_deposit), + api_cost_rent_allowance: cost_batched!(seal_rent_allowance), + api_cost_block_number: cost_batched!(seal_block_number), + api_cost_now: cost_batched!(seal_now), + api_cost_weight_to_fee: cost_batched!(seal_weight_to_fee), + api_cost_gas: cost_batched!(seal_gas), + api_cost_input: cost!(seal_input), + api_cost_input_per_byte: cost_byte!(seal_input_per_kb), + api_cost_return: cost!(seal_input), + api_cost_return_per_byte: cost_byte!(seal_return_per_kb), + api_cost_terminate: cost!(seal_terminate), + api_cost_restore_to: cost!(seal_restore_to), + api_cost_restore_to_per_delta: cost_batched!(seal_restore_to_per_delta), + api_cost_random: cost_batched!(seal_random), + api_cost_deposit_event: cost_batched!(seal_deposit_event), + api_cost_deposit_event_per_topic: cost_batched_args!(seal_deposit_event_per_topic_and_kb, 1, 0), + api_cost_deposit_event_per_byte: cost_byte_batched_args!(seal_deposit_event_per_topic_and_kb, 0, 1), + api_cost_set_rent_allowance: cost_batched!(seal_set_rent_allowance), + api_cost_set_storage: cost_batched!(seal_set_storage), + api_cost_set_storage_per_byte: cost_byte_batched!(seal_set_storage_per_kb), + api_cost_clear_storage: cost_batched!(seal_clear_storage), + api_cost_get_storage: cost_batched!(seal_get_storage), + api_cost_get_storage_per_byte: cost_byte_batched!(seal_get_storage_per_kb), + api_cost_transfer: cost_batched!(seal_transfer), + api_cost_call: cost_batched!(seal_call), + api_cost_call_transfer_surcharge: cost_batched_args!(seal_call_per_transfer_input_output_kb, 1, 0, 0), + api_cost_call_per_input_byte: cost_byte_batched_args!(seal_call_per_transfer_input_output_kb, 0, 1, 0), + api_cost_call_per_output_byte: cost_byte_batched_args!(seal_call_per_transfer_input_output_kb, 0, 0, 1), + api_cost_instantiate: cost_batched!(seal_instantiate), + api_cost_instantiate_per_input_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_kb, 1, 0), + api_cost_instantiate_per_output_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_kb, 0, 1), + api_cost_hash_sha2_256: cost_batched!(seal_hash_sha2_256), + api_cost_hash_sha2_256_per_byte: cost_byte_batched!(seal_hash_sha2_256_per_kb), + api_cost_hash_keccak_256: cost_batched!(seal_hash_keccak_256), + api_cost_hash_keccak_256_per_byte: cost_byte_batched!(seal_hash_keccak_256_per_kb), + api_cost_hash_blake2_256: cost_batched!(seal_hash_blake2_256), + api_cost_hash_blake2_256_per_byte: cost_byte_batched!(seal_hash_blake2_256_per_kb), + api_cost_hash_blake2_128: cost_batched!(seal_hash_blake2_128), + api_cost_hash_blake2_128_per_byte: cost_byte_batched!(seal_hash_blake2_128_per_kb), + + enable_println: false, + max_event_topics: 4, + max_stack_height: 64 * 1024, + max_memory_pages: 16, + max_table_size: 16 * 1024, + max_subject_len: 32, + max_code_size: 512 * 1024, + } + } +} diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index eaeab4edc1cb8..8018229ed0687 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -198,6 +198,7 @@ impl Trait for Test { type MaxDepth = MaxDepth; type MaxValueSize = MaxValueSize; type WeightPrice = Self; + type WeightInfo = (); } type Balances = pallet_balances::Module; diff --git a/frame/contracts/src/weight_info.rs b/frame/contracts/src/weight_info.rs new file mode 100644 index 0000000000000..3a0881ed78d9a --- /dev/null +++ b/frame/contracts/src/weight_info.rs @@ -0,0 +1,341 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! This module contains the `WeightInfo` trait and its unsafe implementation on `()`. + +use frame_support::weights::{Weight, constants::RocksDbWeight as DbWeight}; + +/// Should be implemented by automatically generated code of the benchmarking system for +/// every runtime that makes use of this pallet. +/// This trait is also implemented on `()`. The implemention on `()` is **unsafe** and must +/// only be used during development. Proper weights can be generated by running the +/// pallet_contracts benchmark suite for the runtime in question. +pub trait WeightInfo { + fn update_schedule() -> Weight; + fn put_code(n: u32, ) -> Weight; + fn instantiate(n: u32, ) -> Weight; + fn call() -> Weight; + fn claim_surcharge() -> Weight; + fn seal_caller(r: u32, ) -> Weight; + fn seal_address(r: u32, ) -> Weight; + fn seal_gas_left(r: u32, ) -> Weight; + fn seal_balance(r: u32, ) -> Weight; + fn seal_value_transferred(r: u32, ) -> Weight; + fn seal_minimum_balance(r: u32, ) -> Weight; + fn seal_tombstone_deposit(r: u32, ) -> Weight; + fn seal_rent_allowance(r: u32, ) -> Weight; + fn seal_block_number(r: u32, ) -> Weight; + fn seal_now(r: u32, ) -> Weight; + fn seal_weight_to_fee(r: u32, ) -> Weight; + fn seal_gas(r: u32, ) -> Weight; + fn seal_input(r: u32, ) -> Weight; + fn seal_input_per_kb(n: u32, ) -> Weight; + fn seal_return(r: u32, ) -> Weight; + fn seal_return_per_kb(n: u32, ) -> Weight; + fn seal_terminate(r: u32, ) -> Weight; + fn seal_restore_to(r: u32, ) -> Weight; + fn seal_restore_to_per_delta(d: u32, ) -> Weight; + fn seal_random(r: u32, ) -> Weight; + fn seal_deposit_event(r: u32, ) -> Weight; + fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32, ) -> Weight; + fn seal_set_rent_allowance(r: u32, ) -> Weight; + fn seal_set_storage(r: u32, ) -> Weight; + fn seal_set_storage_per_kb(n: u32, ) -> Weight; + fn seal_clear_storage(r: u32, ) -> Weight; + fn seal_get_storage(r: u32, ) -> Weight; + fn seal_get_storage_per_kb(n: u32, ) -> Weight; + fn seal_transfer(r: u32, ) -> Weight; + fn seal_call(r: u32, ) -> Weight; + fn seal_call_per_transfer_input_output_kb(t: u32, i: u32, o: u32, ) -> Weight; + fn seal_instantiate(r: u32, ) -> Weight; + fn seal_instantiate_per_input_output_kb(i: u32, o: u32, ) -> Weight; + fn seal_hash_sha2_256(r: u32, ) -> Weight; + fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight; + fn seal_hash_keccak_256(r: u32, ) -> Weight; + fn seal_hash_keccak_256_per_kb(n: u32, ) -> Weight; + fn seal_hash_blake2_256(r: u32, ) -> Weight; + fn seal_hash_blake2_256_per_kb(n: u32, ) -> Weight; + fn seal_hash_blake2_128(r: u32, ) -> Weight; + fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight; +} + +/// Unsafe implementation that must only be used for development. +impl WeightInfo for () { + fn update_schedule() -> Weight { + (45000000 as Weight) + .saturating_add(DbWeight::get().reads(1 as Weight)) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn put_code(n: u32, ) -> Weight { + (263409000 as Weight) + .saturating_add((169269000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(1 as Weight)) + .saturating_add(DbWeight::get().writes(2 as Weight)) + } + fn instantiate(n: u32, ) -> Weight { + (309311000 as Weight) + .saturating_add((1018000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(7 as Weight)) + .saturating_add(DbWeight::get().writes(4 as Weight)) + } + fn call() -> Weight { + (291000000 as Weight) + .saturating_add(DbWeight::get().reads(6 as Weight)) + .saturating_add(DbWeight::get().writes(3 as Weight)) + } + fn claim_surcharge() -> Weight { + (766000000 as Weight) + .saturating_add(DbWeight::get().reads(4 as Weight)) + .saturating_add(DbWeight::get().writes(3 as Weight)) + } + fn seal_caller(r: u32, ) -> Weight { + (182241000 as Weight) + .saturating_add((697428000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_address(r: u32, ) -> Weight { + (193846000 as Weight) + .saturating_add((695989000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_gas_left(r: u32, ) -> Weight { + (166031000 as Weight) + .saturating_add((702533000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_balance(r: u32, ) -> Weight { + (251892000 as Weight) + .saturating_add((1392900000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(5 as Weight)) + } + fn seal_value_transferred(r: u32, ) -> Weight { + (178472000 as Weight) + .saturating_add((694921000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_minimum_balance(r: u32, ) -> Weight { + (191301000 as Weight) + .saturating_add((697871000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_tombstone_deposit(r: u32, ) -> Weight { + (241315000 as Weight) + .saturating_add((686403000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_rent_allowance(r: u32, ) -> Weight { + (104958000 as Weight) + .saturating_add((1459573000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_block_number(r: u32, ) -> Weight { + (174140000 as Weight) + .saturating_add((698152000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_now(r: u32, ) -> Weight { + (203157000 as Weight) + .saturating_add((713595000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_weight_to_fee(r: u32, ) -> Weight { + (178413000 as Weight) + .saturating_add((1071275000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(5 as Weight)) + } + fn seal_gas(r: u32, ) -> Weight { + (171395000 as Weight) + .saturating_add((371653000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_input(r: u32, ) -> Weight { + (184462000 as Weight) + .saturating_add((10538000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_input_per_kb(n: u32, ) -> Weight { + (194668000 as Weight) + .saturating_add((301000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_return(r: u32, ) -> Weight { + (175538000 as Weight) + .saturating_add((7462000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_return_per_kb(n: u32, ) -> Weight { + (189759000 as Weight) + .saturating_add((754000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_terminate(r: u32, ) -> Weight { + (184385000 as Weight) + .saturating_add((542615000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + .saturating_add(DbWeight::get().reads((2 as Weight).saturating_mul(r as Weight))) + .saturating_add(DbWeight::get().writes((3 as Weight).saturating_mul(r as Weight))) + } + fn seal_restore_to(r: u32, ) -> Weight { + (380385000 as Weight) + .saturating_add((160308000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(5 as Weight)) + .saturating_add(DbWeight::get().reads((3 as Weight).saturating_mul(r as Weight))) + .saturating_add(DbWeight::get().writes((4 as Weight).saturating_mul(r as Weight))) + } + fn seal_restore_to_per_delta(d: u32, ) -> Weight { + (0 as Weight) + .saturating_add((4786197000 as Weight).saturating_mul(d as Weight)) + .saturating_add(DbWeight::get().reads(7 as Weight)) + .saturating_add(DbWeight::get().reads((100 as Weight).saturating_mul(d as Weight))) + .saturating_add(DbWeight::get().writes(5 as Weight)) + .saturating_add(DbWeight::get().writes((100 as Weight).saturating_mul(d as Weight))) + } + fn seal_random(r: u32, ) -> Weight { + (187944000 as Weight) + .saturating_add((1592530000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(5 as Weight)) + } + fn seal_deposit_event(r: u32, ) -> Weight { + (126517000 as Weight) + .saturating_add((2346945000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32, ) -> Weight { + (2953428000 as Weight) + .saturating_add((1117651000 as Weight).saturating_mul(t as Weight)) + .saturating_add((299890000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + .saturating_add(DbWeight::get().reads((100 as Weight).saturating_mul(t as Weight))) + .saturating_add(DbWeight::get().writes((100 as Weight).saturating_mul(t as Weight))) + } + fn seal_set_rent_allowance(r: u32, ) -> Weight { + (142094000 as Weight) + .saturating_add((1726665000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn seal_set_storage(r: u32, ) -> Weight { + (4091409000 as Weight) + .saturating_add((26440116000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + .saturating_add(DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(DbWeight::get().writes(1 as Weight)) + .saturating_add(DbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + } + fn seal_set_storage_per_kb(n: u32, ) -> Weight { + (3683270000 as Weight) + .saturating_add((233826000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(5 as Weight)) + .saturating_add(DbWeight::get().writes(2 as Weight)) + } + fn seal_clear_storage(r: u32, ) -> Weight { + (0 as Weight) + .saturating_add((7152747000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + .saturating_add(DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(DbWeight::get().writes(1 as Weight)) + .saturating_add(DbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + } + fn seal_get_storage(r: u32, ) -> Weight { + (19007000 as Weight) + .saturating_add((1774675000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + .saturating_add(DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + } + fn seal_get_storage_per_kb(n: u32, ) -> Weight { + (1477332000 as Weight) + .saturating_add((176601000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(5 as Weight)) + } + fn seal_transfer(r: u32, ) -> Weight { + (0 as Weight) + .saturating_add((10274385000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(5 as Weight)) + .saturating_add(DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(DbWeight::get().writes(1 as Weight)) + .saturating_add(DbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + } + fn seal_call(r: u32, ) -> Weight { + (241916000 as Weight) + .saturating_add((14633108000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(5 as Weight)) + .saturating_add(DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + } + fn seal_call_per_transfer_input_output_kb(t: u32, i: u32, o: u32, ) -> Weight { + (15664107000 as Weight) + .saturating_add((8529984000 as Weight).saturating_mul(t as Weight)) + .saturating_add((52860000 as Weight).saturating_mul(i as Weight)) + .saturating_add((81175000 as Weight).saturating_mul(o as Weight)) + .saturating_add(DbWeight::get().reads(105 as Weight)) + .saturating_add(DbWeight::get().reads((101 as Weight).saturating_mul(t as Weight))) + .saturating_add(DbWeight::get().writes((101 as Weight).saturating_mul(t as Weight))) + } + fn seal_instantiate(r: u32, ) -> Weight { + (0 as Weight) + .saturating_add((32247550000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(5 as Weight)) + .saturating_add(DbWeight::get().reads((300 as Weight).saturating_mul(r as Weight))) + .saturating_add(DbWeight::get().writes(1 as Weight)) + .saturating_add(DbWeight::get().writes((200 as Weight).saturating_mul(r as Weight))) + } + fn seal_instantiate_per_input_output_kb(i: u32, o: u32, ) -> Weight { + (34376003000 as Weight) + .saturating_add((151350000 as Weight).saturating_mul(i as Weight)) + .saturating_add((82364000 as Weight).saturating_mul(o as Weight)) + .saturating_add(DbWeight::get().reads(207 as Weight)) + .saturating_add(DbWeight::get().writes(202 as Weight)) + } + fn seal_hash_sha2_256(r: u32, ) -> Weight { + (164203000 as Weight) + .saturating_add((565206000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight { + (0 as Weight) + .saturating_add((330063000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_keccak_256(r: u32, ) -> Weight { + (219038000 as Weight) + .saturating_add((567992000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_keccak_256_per_kb(n: u32, ) -> Weight { + (434654000 as Weight) + .saturating_add((271134000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_blake2_256(r: u32, ) -> Weight { + (116374000 as Weight) + .saturating_add((566612000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_blake2_256_per_kb(n: u32, ) -> Weight { + (756028000 as Weight) + .saturating_add((150363000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_blake2_128(r: u32, ) -> Weight { + (150126000 as Weight) + .saturating_add((564827000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight { + (1021689000 as Weight) + .saturating_add((149452000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } +} From 1e065cff5e72a19243e8db09891c9287715401b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Tue, 1 Sep 2020 15:14:20 +0200 Subject: [PATCH 08/29] seal: Make use of WeightInfo in extrinsic weight annotations --- frame/contracts/src/lib.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 00feea925d99b..932a88ba3abde 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -508,7 +508,7 @@ decl_module! { /// Updates the schedule for metering contracts. /// /// The schedule must have a greater version than the stored schedule. - #[weight = 0] + #[weight = T::WeightInfo::update_schedule()] pub fn update_schedule(origin, schedule: Schedule) -> DispatchResult { ensure_root(origin)?; if >::current_schedule().version >= schedule.version { @@ -523,7 +523,7 @@ decl_module! { /// Stores the given binary Wasm code into the chain's storage and returns its `codehash`. /// You can instantiate contracts only with stored code. - #[weight = Module::::calc_code_put_costs(&code)] + #[weight = T::WeightInfo::put_code(code.len() as u32)] pub fn put_code( origin, code: Vec @@ -545,7 +545,7 @@ decl_module! { /// * If the account is a regular account, any value will be transferred. /// * If no account exists and the call value is not less than `existential_deposit`, /// a regular account will be created and any value will be transferred. - #[weight = *gas_limit] + #[weight = T::WeightInfo::call().saturating_add(*gas_limit)] pub fn call( origin, dest: ::Source, @@ -573,7 +573,7 @@ decl_module! { /// 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 = *gas_limit] + #[weight = T::WeightInfo::instantiate(data.len() as u32).saturating_add(*gas_limit)] pub fn instantiate( origin, #[compact] endowment: BalanceOf, @@ -596,7 +596,7 @@ decl_module! { /// /// If contract is not evicted as a result of this call, no actions are taken and /// the sender is not eligible for the reward. - #[weight = 0] + #[weight = T::WeightInfo::claim_surcharge()] fn claim_surcharge(origin, dest: T::AccountId, aux_sender: Option) { let origin = origin.into(); let (signed, rewarded) = match (origin, aux_sender) { @@ -680,10 +680,6 @@ impl Module { } impl Module { - fn calc_code_put_costs(code: &Vec) -> Gas { - >::current_schedule().put_code_per_byte_cost.saturating_mul(code.len() as Gas) - } - fn execute_wasm( origin: T::AccountId, gas_meter: &mut GasMeter, From c680745cd88024ec2863e919e1c3fcf2a44dd2b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Tue, 1 Sep 2020 18:30:51 +0200 Subject: [PATCH 09/29] seal: Replace the old schedule by the benchmark generated one --- bin/node/runtime/src/lib.rs | 2 +- frame/contracts/fixtures/caller_contract.wat | 4 +- frame/contracts/src/exec.rs | 254 +------------ frame/contracts/src/lib.rs | 121 +------ frame/contracts/src/tests.rs | 7 +- frame/contracts/src/wasm/code_cache.rs | 12 +- frame/contracts/src/wasm/mod.rs | 45 +-- frame/contracts/src/wasm/prepare.rs | 34 +- frame/contracts/src/wasm/runtime.rs | 352 ++++++++++++------- 9 files changed, 281 insertions(+), 550 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 111abf4d907eb..87ef6de946f7d 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -882,7 +882,7 @@ construct_runtime!( FinalityTracker: pallet_finality_tracker::{Module, Call, Inherent}, Grandpa: pallet_grandpa::{Module, Call, Storage, Config, Event, ValidateUnsigned}, Treasury: pallet_treasury::{Module, Call, Storage, Config, Event}, - Contracts: pallet_contracts::{Module, Call, Config, Storage, Event}, + Contracts: pallet_contracts::{Module, Call, Config, Storage, Event}, Sudo: pallet_sudo::{Module, Call, Config, Storage, Event}, ImOnline: pallet_im_online::{Module, Call, Storage, Event, ValidateUnsigned, Config}, AuthorityDiscovery: pallet_authority_discovery::{Module, Call, Config}, diff --git a/frame/contracts/fixtures/caller_contract.wat b/frame/contracts/fixtures/caller_contract.wat index e8ff2e379716d..408af92e18296 100644 --- a/frame/contracts/fixtures/caller_contract.wat +++ b/frame/contracts/fixtures/caller_contract.wat @@ -89,7 +89,7 @@ (call $seal_instantiate (i32.const 24) ;; Pointer to the code hash. (i32.const 32) ;; Length of the code hash. - (i64.const 187500000) ;; Just enough to pay for the instantiate + (i64.const 1) ;; Supply too little gas (i32.const 0) ;; Pointer to the buffer with value to transfer (i32.const 8) ;; Length of the buffer with value to transfer. (i32.const 8) ;; Pointer to input data buffer address @@ -206,7 +206,7 @@ (call $seal_call (i32.const 16) ;; Pointer to "callee" address. (i32.const 8) ;; Length of "callee" address. - (i64.const 117500000) ;; Just enough to make the call + (i64.const 1) ;; Supply too little gas (i32.const 0) ;; Pointer to the buffer with value to transfer (i32.const 8) ;; Length of the buffer with value to transfer. (i32.const 8) ;; Pointer to input data buffer address diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index ce4e17cd1b9fa..3cfa86cf115ef 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -17,7 +17,7 @@ use crate::{ CodeHash, Config, ContractAddressFor, Event, RawEvent, Trait, TrieId, BalanceOf, ContractInfo, TrieIdGenerator, - gas::{Gas, GasMeter, Token}, rent, storage, Error, ContractInfoOf + gas::GasMeter, rent, storage, Error, ContractInfoOf }; use bitflags::bitflags; use sp_std::prelude::*; @@ -140,7 +140,6 @@ pub trait Ext { &mut self, to: &AccountIdOf, value: BalanceOf, - gas_meter: &mut GasMeter, ) -> Result<(), DispatchError>; /// Transfer all funds to `beneficiary` and delete the contract. @@ -153,7 +152,6 @@ pub trait Ext { fn terminate( &mut self, beneficiary: &AccountIdOf, - gas_meter: &mut GasMeter, ) -> Result<(), DispatchError>; /// Call (possibly transferring some amount of funds) into the specified account. @@ -260,26 +258,6 @@ pub trait Vm { ) -> ExecResult; } -#[cfg_attr(test, derive(Debug, PartialEq, Eq))] -#[derive(Copy, Clone)] -pub enum ExecFeeToken { - /// Base fee charged for a call. - Call, - /// Base fee charged for a instantiate. - Instantiate, -} - -impl Token for ExecFeeToken { - type Metadata = Config; - #[inline] - fn calculate_amount(&self, metadata: &Config) -> Gas { - match *self { - ExecFeeToken::Call => metadata.schedule.call_base_cost, - ExecFeeToken::Instantiate => metadata.schedule.instantiate_base_cost, - } - } -} - pub struct ExecutionContext<'a, T: Trait + 'a, V, L> { pub caller: Option<&'a ExecutionContext<'a, T, V, L>>, pub self_account: T::AccountId, @@ -344,13 +322,6 @@ where Err(Error::::MaxCallDepthReached)? } - if gas_meter - .charge(self.config, ExecFeeToken::Call) - .is_out_of_gas() - { - Err(Error::::OutOfGas)? - } - // Assumption: `collect_rent` doesn't collide with overlay because // `collect_rent` will be done on first call and destination contract and balance // cannot be changed before the first call @@ -368,7 +339,6 @@ where self.with_nested_context(dest.clone(), contract.trie_id.clone(), |nested| { if value > BalanceOf::::zero() { transfer( - gas_meter, TransferCause::Call, transactor_kind, &caller, @@ -401,13 +371,6 @@ where Err(Error::::MaxCallDepthReached)? } - if gas_meter - .charge(self.config, ExecFeeToken::Instantiate) - .is_out_of_gas() - { - Err(Error::::OutOfGas)? - } - let transactor_kind = self.transactor_kind(); let caller = self.self_account.clone(); let dest = T::DetermineContractAddress::contract_address_for( @@ -434,7 +397,6 @@ where // Send funds unconditionally here. If the `endowment` is below existential_deposit // then error will be returned here. transfer( - gas_meter, TransferCause::Instantiate, transactor_kind, &caller, @@ -520,31 +482,6 @@ where } } -#[cfg_attr(test, derive(Debug, PartialEq, Eq))] -#[derive(Copy, Clone)] -pub enum TransferFeeKind { - ContractInstantiate, - Transfer, -} - -#[cfg_attr(test, derive(Debug, PartialEq, Eq))] -#[derive(Copy, Clone)] -pub struct TransferFeeToken { - kind: TransferFeeKind, -} - -impl Token for TransferFeeToken { - type Metadata = Config; - - #[inline] - fn calculate_amount(&self, metadata: &Config) -> Gas { - match self.kind { - TransferFeeKind::ContractInstantiate => metadata.schedule.instantiate_cost, - TransferFeeKind::Transfer => metadata.schedule.transfer_cost, - } - } -} - /// Describes possible transfer causes. enum TransferCause { Call, @@ -569,7 +506,6 @@ enum TransferCause { /// can go below existential deposit, essentially giving a contract /// the chance to give up it's life. fn transfer<'a, T: Trait, V: Vm, L: Loader>( - gas_meter: &mut GasMeter, cause: TransferCause, origin: TransactorKind, transactor: &T::AccountId, @@ -578,27 +514,8 @@ fn transfer<'a, T: Trait, V: Vm, L: Loader>( ctx: &mut ExecutionContext<'a, T, V, L>, ) -> Result<(), DispatchError> { use self::TransferCause::*; - use self::TransferFeeKind::*; use self::TransactorKind::*; - let token = { - let kind: TransferFeeKind = match cause { - // If this function is called from `Instantiate` routine, then we always - // charge contract account creation fee. - Instantiate => ContractInstantiate, - - // Otherwise the fee is to transfer to an account. - Call | Terminate => TransferFeeKind::Transfer, - }; - TransferFeeToken { - kind, - } - }; - - if gas_meter.charge(ctx.config, token).is_out_of_gas() { - Err(Error::::OutOfGas)? - } - // Only seal_terminate is allowed to bring the sender below the subsistence // threshold or even existential deposit. let existence_requirement = match (cause, origin) { @@ -690,14 +607,12 @@ where &mut self, to: &T::AccountId, value: BalanceOf, - gas_meter: &mut GasMeter, ) -> Result<(), DispatchError> { transfer( - gas_meter, TransferCause::Call, TransactorKind::Contract, &self.ctx.self_account.clone(), - &to, + to, value, self.ctx, ) @@ -706,7 +621,6 @@ where fn terminate( &mut self, beneficiary: &AccountIdOf, - gas_meter: &mut GasMeter, ) -> Result<(), DispatchError> { let self_id = self.ctx.self_account.clone(); let value = T::Currency::free_balance(&self_id); @@ -718,7 +632,6 @@ where } } transfer( - gas_meter, TransferCause::Terminate, TransactorKind::Contract, &self_id, @@ -865,8 +778,8 @@ fn deposit_event( #[cfg(test)] mod tests { use super::{ - BalanceOf, Event, ExecFeeToken, ExecResult, ExecutionContext, Ext, Loader, - RawEvent, TransferFeeKind, TransferFeeToken, Vm, ReturnFlags, ExecError, ErrorOrigin + BalanceOf, Event, ExecResult, ExecutionContext, Ext, Loader, + RawEvent, Vm, ReturnFlags, ExecError, ErrorOrigin }; use crate::{ gas::GasMeter, tests::{ExtBuilder, Test, MetaEvent}, @@ -1012,58 +925,6 @@ mod tests { assert_eq!(&*test_data.borrow(), &vec![0, 1]); } - #[test] - fn base_fees() { - let origin = ALICE; - let dest = BOB; - - // This test verifies that base fee for call is taken. - ExtBuilder::default().build().execute_with(|| { - let vm = MockVm::new(); - let loader = MockLoader::empty(); - let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); - set_balance(&origin, 100); - set_balance(&dest, 0); - - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - - let result = super::transfer( - &mut gas_meter, - super::TransferCause::Call, - super::TransactorKind::PlainAccount, - &origin, - &dest, - 0, - &mut ctx, - ); - assert_matches!(result, Ok(_)); - - let mut toks = gas_meter.tokens().iter(); - match_tokens!(toks, TransferFeeToken { kind: TransferFeeKind::Transfer },); - }); - - // This test verifies that base fee for instantiation is taken. - ExtBuilder::default().build().execute_with(|| { - let mut loader = MockLoader::empty(); - let code = loader.insert(|_| exec_success()); - - let vm = MockVm::new(); - let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); - - set_balance(&origin, 100); - - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - - let result = ctx.instantiate(cfg.subsistence_threshold(), &mut gas_meter, &code, vec![]); - assert_matches!(result, Ok(_)); - - let mut toks = gas_meter.tokens().iter(); - match_tokens!(toks, ExecFeeToken::Instantiate,); - }); - } - #[test] fn transfer_works() { // This test verifies that a contract is able to transfer @@ -1080,10 +941,7 @@ mod tests { set_balance(&origin, 100); set_balance(&dest, 0); - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - super::transfer( - &mut gas_meter, super::TransferCause::Call, super::TransactorKind::PlainAccount, &origin, @@ -1130,105 +988,6 @@ mod tests { }); } - #[test] - fn transfer_fees() { - let origin = ALICE; - let dest = BOB; - - // This test sends 50 units of currency to a non-existent account. - // This should lead to creation of a new account thus - // a fee should be charged. - ExtBuilder::default().existential_deposit(15).build().execute_with(|| { - let vm = MockVm::new(); - let loader = MockLoader::empty(); - let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); - set_balance(&origin, 100); - set_balance(&dest, 0); - - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - - let result = super::transfer( - &mut gas_meter, - super::TransferCause::Call, - super::TransactorKind::PlainAccount, - &origin, - &dest, - 50, - &mut ctx, - ); - assert_matches!(result, Ok(_)); - - let mut toks = gas_meter.tokens().iter(); - match_tokens!( - toks, - TransferFeeToken { - kind: TransferFeeKind::Transfer, - }, - ); - }); - - // This one is similar to the previous one but transfer to an existing account. - // In this test we expect that a regular transfer fee is charged. - ExtBuilder::default().existential_deposit(15).build().execute_with(|| { - let vm = MockVm::new(); - let loader = MockLoader::empty(); - let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); - set_balance(&origin, 100); - set_balance(&dest, 15); - - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - - let result = super::transfer( - &mut gas_meter, - super::TransferCause::Call, - super::TransactorKind::PlainAccount, - &origin, - &dest, - 50, - &mut ctx, - ); - assert_matches!(result, Ok(_)); - - let mut toks = gas_meter.tokens().iter(); - match_tokens!( - toks, - TransferFeeToken { - kind: TransferFeeKind::Transfer, - }, - ); - }); - - // This test sends 50 units of currency as an endowment to a newly - // instantiated contract. - ExtBuilder::default().existential_deposit(15).build().execute_with(|| { - let mut loader = MockLoader::empty(); - let code = loader.insert(|_| exec_success()); - - let vm = MockVm::new(); - let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); - - set_balance(&origin, 100); - set_balance(&dest, 15); - - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - - let result = ctx.instantiate(50, &mut gas_meter, &code, vec![]); - assert_matches!(result, Ok(_)); - - let mut toks = gas_meter.tokens().iter(); - match_tokens!( - toks, - ExecFeeToken::Instantiate, - TransferFeeToken { - kind: TransferFeeKind::ContractInstantiate, - }, - ); - }); - } - #[test] fn balance_too_low() { // This test verifies that a contract can't send value if it's @@ -1245,7 +1004,6 @@ mod tests { set_balance(&origin, 0); let result = super::transfer( - &mut GasMeter::::new(GAS_LIMIT), super::TransferCause::Call, super::TransactorKind::PlainAccount, &origin, @@ -1696,8 +1454,8 @@ mod tests { let mut loader = MockLoader::empty(); - let terminate_ch = loader.insert(|mut ctx| { - ctx.ext.terminate(&ALICE, &mut ctx.gas_meter).unwrap(); + let terminate_ch = loader.insert(|ctx| { + ctx.ext.terminate(&ALICE).unwrap(); exec_success() }); diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 932a88ba3abde..15aaada42fa61 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -100,9 +100,8 @@ pub use crate::gas::{Gas, GasMeter}; pub use crate::exec::{ExecResult, ExecReturnValue}; pub use crate::wasm::ReturnCode as RuntimeReturnCode; pub use crate::weight_info::WeightInfo; +pub use crate::schedule::Schedule; -#[cfg(feature = "std")] -use serde::{Serialize, Deserialize}; use sp_core::crypto::UncheckedFrom; use sp_std::{prelude::*, marker::PhantomData, fmt::Debug}; use codec::{Codec, Encode, Decode}; @@ -509,7 +508,7 @@ decl_module! { /// /// The schedule must have a greater version than the stored schedule. #[weight = T::WeightInfo::update_schedule()] - pub fn update_schedule(origin, schedule: Schedule) -> DispatchResult { + pub fn update_schedule(origin, schedule: Schedule) -> DispatchResult { ensure_root(origin)?; if >::current_schedule().version >= schedule.version { Err(Error::::InvalidScheduleVersion)? @@ -683,7 +682,7 @@ impl Module { fn execute_wasm( origin: T::AccountId, gas_meter: &mut GasMeter, - func: impl FnOnce(&mut ExecutionContext, &mut GasMeter) -> ExecResult, + func: impl FnOnce(&mut ExecutionContext, WasmLoader>, &mut GasMeter) -> ExecResult, ) -> ExecResult { let cfg = Config::preload(); let vm = WasmVm::new(&cfg.schedule); @@ -739,7 +738,7 @@ decl_event! { decl_storage! { trait Store for Module as Contracts { /// Current cost schedule for contracts. - CurrentSchedule get(fn current_schedule) config(): Schedule = Schedule::default(); + CurrentSchedule get(fn current_schedule) config(): Schedule = Default::default(); /// A mapping from an original code hash to the original code, untouched by instrumentation. pub PristineCode: map hasher(identity) CodeHash => Option>; /// A mapping between an original code hash and instrumented wasm code, ready for execution. @@ -758,7 +757,7 @@ decl_storage! { /// We assume that these values can't be changed in the /// course of transaction execution. pub struct Config { - pub schedule: Schedule, + pub schedule: Schedule, pub existential_deposit: BalanceOf, pub tombstone_deposit: BalanceOf, pub max_depth: u32, @@ -795,113 +794,3 @@ impl Config { T::Currency::minimum_balance().saturating_add(T::TombstoneDeposit::get()) } } - -/// Definition of the cost schedule and other parameterizations for wasm vm. -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug)] -pub struct Schedule { - /// Version of the schedule. - pub version: u32, - - /// Cost of putting a byte of code into storage. - pub put_code_per_byte_cost: Gas, - - /// Gas cost of a growing memory by single page. - pub grow_mem_cost: Gas, - - /// Gas cost of a regular operation. - pub regular_op_cost: Gas, - - /// Gas cost per one byte returned. - pub return_data_per_byte_cost: Gas, - - /// Gas cost to deposit an event; the per-byte portion. - pub event_data_per_byte_cost: Gas, - - /// Gas cost to deposit an event; the cost per topic. - pub event_per_topic_cost: Gas, - - /// Gas cost to deposit an event; the base. - pub event_base_cost: Gas, - - /// Base gas cost to call into a contract. - pub call_base_cost: Gas, - - /// Base gas cost to instantiate a contract. - pub instantiate_base_cost: Gas, - - /// Base gas cost to dispatch a runtime call. - pub dispatch_base_cost: Gas, - - /// Gas cost per one byte read from the sandbox memory. - pub sandbox_data_read_cost: Gas, - - /// Gas cost per one byte written to the sandbox memory. - pub sandbox_data_write_cost: Gas, - - /// Cost for a simple balance transfer. - pub transfer_cost: Gas, - - /// Cost for instantiating a new contract. - pub instantiate_cost: Gas, - - /// The maximum number of topics supported by an event. - pub max_event_topics: u32, - - /// Maximum allowed stack height. - /// - /// See https://wiki.parity.io/WebAssembly-StackHeight to find out - /// how the stack frame cost is calculated. - pub max_stack_height: u32, - - /// Maximum number of memory pages allowed for a contract. - pub max_memory_pages: u32, - - /// Maximum allowed size of a declared table. - pub max_table_size: u32, - - /// Whether the `seal_println` function is allowed to be used contracts. - /// MUST only be enabled for `dev` chains, NOT for production chains - pub enable_println: bool, - - /// The maximum length of a subject used for PRNG generation. - pub max_subject_len: u32, - - /// The maximum length of a contract code in bytes. This limit applies to the uninstrumented - // and pristine form of the code as supplied to `put_code`. - pub max_code_size: u32, -} - -// 500 (2 instructions per nano second on 2GHZ) * 1000x slowdown through wasmi -// This is a wild guess and should be viewed as a rough estimation. -// Proper benchmarks are needed before this value and its derivatives can be used in production. -const WASM_INSTRUCTION_COST: Gas = 500_000; - -impl Default for Schedule { - fn default() -> Schedule { - Schedule { - version: 0, - put_code_per_byte_cost: WASM_INSTRUCTION_COST, - grow_mem_cost: WASM_INSTRUCTION_COST, - regular_op_cost: WASM_INSTRUCTION_COST, - return_data_per_byte_cost: WASM_INSTRUCTION_COST, - event_data_per_byte_cost: WASM_INSTRUCTION_COST, - event_per_topic_cost: WASM_INSTRUCTION_COST, - event_base_cost: WASM_INSTRUCTION_COST, - call_base_cost: 135 * WASM_INSTRUCTION_COST, - dispatch_base_cost: 135 * WASM_INSTRUCTION_COST, - instantiate_base_cost: 175 * WASM_INSTRUCTION_COST, - sandbox_data_read_cost: WASM_INSTRUCTION_COST, - sandbox_data_write_cost: WASM_INSTRUCTION_COST, - transfer_cost: 100 * WASM_INSTRUCTION_COST, - instantiate_cost: 200 * WASM_INSTRUCTION_COST, - max_event_topics: 4, - max_stack_height: 64 * 1024, - max_memory_pages: 16, - max_table_size: 16 * 1024, - enable_println: false, - max_subject_len: 32, - max_code_size: 512 * 1024, - } - } -} diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index 8018229ed0687..ca3549219c913 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -261,7 +261,7 @@ impl ExtBuilder { balances: vec![], }.assimilate_storage(&mut t).unwrap(); GenesisConfig { - current_schedule: Schedule { + current_schedule: Schedule:: { enable_println: true, ..Default::default() }, @@ -290,7 +290,8 @@ where // Perform a call to a plain account. // The actual transfer fails because we can only call contracts. -// Then we check that only the base costs are returned as actual costs. +// Then we check that no gas was used because the base costs for calling are either charged +// as part of the `call` extrinsic or by `seal_call`. #[test] fn calling_plain_account_fails() { ExtBuilder::default().build().execute_with(|| { @@ -302,7 +303,7 @@ fn calling_plain_account_fails() { DispatchErrorWithPostInfo { error: Error::::NotCallable.into(), post_info: PostDispatchInfo { - actual_weight: Some(67500000), + actual_weight: Some(0), pays_fee: Default::default(), }, } diff --git a/frame/contracts/src/wasm/code_cache.rs b/frame/contracts/src/wasm/code_cache.rs index 6394101bd76c9..34b8ea7443538 100644 --- a/frame/contracts/src/wasm/code_cache.rs +++ b/frame/contracts/src/wasm/code_cache.rs @@ -38,9 +38,9 @@ use frame_support::StorageMap; /// This function instruments the given code and caches it in the storage. pub fn save( original_code: Vec, - schedule: &Schedule, + schedule: &Schedule, ) -> Result, &'static str> { - let prefab_module = prepare::prepare_contract::(&original_code, schedule)?; + let prefab_module = prepare::prepare_contract::(&original_code, schedule)?; let code_hash = T::Hashing::hash(&original_code); >::insert(code_hash, prefab_module); @@ -56,9 +56,9 @@ pub fn save( #[cfg(feature = "runtime-benchmarks")] pub fn save_raw( original_code: Vec, - schedule: &Schedule, + schedule: &Schedule, ) -> Result, &'static str> { - let prefab_module = prepare::benchmarking::prepare_contract(&original_code, schedule)?; + let prefab_module = prepare::benchmarking::prepare_contract::(&original_code, schedule)?; let code_hash = T::Hashing::hash(&original_code); >::insert(code_hash, prefab_module); @@ -74,7 +74,7 @@ pub fn save_raw( /// re-instrumentation and update the cache in the storage. pub fn load( code_hash: &CodeHash, - schedule: &Schedule, + schedule: &Schedule, ) -> Result { let mut prefab_module = >::get(code_hash).ok_or_else(|| "code is not found")?; @@ -86,7 +86,7 @@ pub fn load( // We need to re-instrument the code with the latest schedule here. let original_code = >::get(code_hash).ok_or_else(|| "pristine code is not found")?; - prefab_module = prepare::prepare_contract::(&original_code, schedule)?; + prefab_module = prepare::prepare_contract::(&original_code, schedule)?; >::insert(&code_hash, &prefab_module); } Ok(prefab_module) diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index 9b247219b3a80..100148b18dcd4 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -66,17 +66,17 @@ pub struct WasmExecutable { } /// Loader which fetches `WasmExecutable` from the code cache. -pub struct WasmLoader<'a> { - schedule: &'a Schedule, +pub struct WasmLoader<'a, T: Trait> { + schedule: &'a Schedule, } -impl<'a> WasmLoader<'a> { - pub fn new(schedule: &'a Schedule) -> Self { +impl<'a, T: Trait> WasmLoader<'a, T> { + pub fn new(schedule: &'a Schedule) -> Self { WasmLoader { schedule } } } -impl<'a, T: Trait> crate::exec::Loader for WasmLoader<'a> { +impl<'a, T: Trait> crate::exec::Loader for WasmLoader<'a, T> { type Executable = WasmExecutable; fn load_init(&self, code_hash: &CodeHash) -> Result { @@ -96,17 +96,17 @@ impl<'a, T: Trait> crate::exec::Loader for WasmLoader<'a> { } /// Implementation of `Vm` that takes `WasmExecutable` and executes it. -pub struct WasmVm<'a> { - schedule: &'a Schedule, +pub struct WasmVm<'a, T: Trait> { + schedule: &'a Schedule, } -impl<'a> WasmVm<'a> { - pub fn new(schedule: &'a Schedule) -> Self { +impl<'a, T: Trait> WasmVm<'a, T> { + pub fn new(schedule: &'a Schedule) -> Self { WasmVm { schedule } } } -impl<'a, T: Trait> crate::exec::Vm for WasmVm<'a> { +impl<'a, T: Trait> crate::exec::Vm for WasmVm<'a, T> { type Executable = WasmExecutable; fn execute>( @@ -188,7 +188,6 @@ mod tests { #[derive(Debug, PartialEq, Eq)] struct TerminationEntry { beneficiary: u64, - gas_left: u64, } #[derive(Debug, PartialEq, Eq)] @@ -196,7 +195,6 @@ mod tests { to: u64, value: u64, data: Vec, - gas_left: u64, } #[derive(Default)] @@ -249,13 +247,11 @@ mod tests { &mut self, to: &u64, value: u64, - gas_meter: &mut GasMeter, ) -> Result<(), DispatchError> { self.transfers.push(TransferEntry { to: *to, value, data: Vec::new(), - gas_left: gas_meter.gas_left(), }); Ok(()) } @@ -263,14 +259,13 @@ mod tests { &mut self, to: &u64, value: u64, - gas_meter: &mut GasMeter, + _gas_meter: &mut GasMeter, data: Vec, ) -> ExecResult { self.transfers.push(TransferEntry { to: *to, value, data: data, - gas_left: gas_meter.gas_left(), }); // Assume for now that it was just a plain transfer. // TODO: Add tests for different call outcomes. @@ -279,11 +274,9 @@ mod tests { fn terminate( &mut self, beneficiary: &u64, - gas_meter: &mut GasMeter, ) -> Result<(), DispatchError> { self.terminations.push(TerminationEntry { beneficiary: *beneficiary, - gas_left: gas_meter.gas_left(), }); Ok(()) } @@ -374,16 +367,14 @@ mod tests { &mut self, to: &u64, value: u64, - gas_meter: &mut GasMeter, ) -> Result<(), DispatchError> { - (**self).transfer(to, value, gas_meter) + (**self).transfer(to, value) } fn terminate( &mut self, beneficiary: &u64, - gas_meter: &mut GasMeter, ) -> Result<(), DispatchError> { - (**self).terminate(beneficiary, gas_meter) + (**self).terminate(beneficiary) } fn call( &mut self, @@ -463,7 +454,7 @@ mod tests { let wasm = wat::parse_str(wat).unwrap(); let schedule = crate::Schedule::default(); let prefab_module = - prepare_contract::(&wasm, &schedule).unwrap(); + prepare_contract::(&wasm, &schedule).unwrap(); let exec = WasmExecutable { // Use a "call" convention. @@ -525,7 +516,6 @@ mod tests { to: 7, value: 153, data: Vec::new(), - gas_left: 9989000000, }] ); } @@ -589,7 +579,6 @@ mod tests { to: 9, value: 6, data: vec![1, 2, 3, 4], - gas_left: 9984500000, }] ); } @@ -660,7 +649,7 @@ mod tests { code_hash: [0x11; 32].into(), endowment: 3, data: vec![1, 2, 3, 4], - gas_left: 9971500000, + gas_left: 9392302058, }] ); } @@ -701,7 +690,6 @@ mod tests { &mock_ext.terminations, &[TerminationEntry { beneficiary: 0x09, - gas_left: 9994500000, }] ); } @@ -765,7 +753,6 @@ mod tests { to: 9, value: 6, data: vec![1, 2, 3, 4], - gas_left: 228, }] ); } @@ -1472,7 +1459,7 @@ mod tests { vec![0x00, 0x01, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe5, 0x14, 0x00]) ]); - assert_eq!(gas_meter.gas_left(), 9967000000); + assert_eq!(gas_meter.gas_left(), 9834099446); } const CODE_DEPOSIT_EVENT_MAX_TOPICS: &str = r#" diff --git a/frame/contracts/src/wasm/prepare.rs b/frame/contracts/src/wasm/prepare.rs index 895777e46557d..a23821c1a92fc 100644 --- a/frame/contracts/src/wasm/prepare.rs +++ b/frame/contracts/src/wasm/prepare.rs @@ -20,7 +20,7 @@ use crate::wasm::env_def::ImportSatisfyCheck; use crate::wasm::PrefabWasmModule; -use crate::Schedule; +use crate::{Schedule, Trait}; use parity_wasm::elements::{self, Internal, External, MemoryType, Type, ValueType}; use pwasm_utils; @@ -36,20 +36,20 @@ pub const IMPORT_MODULE_FN: &str = "seal0"; /// compiler toolchains might not support specifying other modules than "env" for memory imports. pub const IMPORT_MODULE_MEMORY: &str = "env"; -struct ContractModule<'a> { +struct ContractModule<'a, T: Trait> { /// A deserialized module. The module is valid (this is Guaranteed by `new` method). module: elements::Module, - schedule: &'a Schedule, + schedule: &'a Schedule, } -impl<'a> ContractModule<'a> { +impl<'a, T: Trait> ContractModule<'a, T> { /// Creates a new instance of `ContractModule`. /// /// Returns `Err` if the `original_code` couldn't be decoded or /// if it contains an invalid module. fn new( original_code: &[u8], - schedule: &'a Schedule, + schedule: &'a Schedule, ) -> Result { use wasmi_validation::{validate_module, PlainValidator}; @@ -148,10 +148,10 @@ impl<'a> ContractModule<'a> { fn inject_gas_metering(self) -> Result { let gas_rules = rules::Set::new( - self.schedule.regular_op_cost.clone().saturated_into(), + self.schedule.op_cost_regular.clone().saturated_into(), Default::default(), ) - .with_grow_cost(self.schedule.grow_mem_cost.clone().saturated_into()) + .with_grow_cost(self.schedule.op_cost_grow_mem.clone().saturated_into()) .with_forbidden_floats(); let contract_module = pwasm_utils::inject_gas_counter( @@ -333,7 +333,7 @@ impl<'a> ContractModule<'a> { } } -fn get_memory_limits(module: Option<&MemoryType>, schedule: &Schedule) +fn get_memory_limits(module: Option<&MemoryType>, schedule: &Schedule) -> Result<(u32, u32), &'static str> { if let Some(memory_type) = module { @@ -374,9 +374,9 @@ fn get_memory_limits(module: Option<&MemoryType>, schedule: &Schedule) /// - all imported functions from the external environment matches defined by `env` module, /// /// The preprocessing includes injecting code for gas metering and metering the height of stack. -pub fn prepare_contract( +pub fn prepare_contract( original_code: &[u8], - schedule: &Schedule, + schedule: &Schedule, ) -> Result { let mut contract_module = ContractModule::new(original_code, schedule)?; contract_module.scan_exports()?; @@ -406,7 +406,9 @@ pub fn prepare_contract( #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking { - use super::{ContractModule, PrefabWasmModule, ImportSatisfyCheck, Schedule, get_memory_limits}; + use super::{ + Trait, ContractModule, PrefabWasmModule, ImportSatisfyCheck, Schedule, get_memory_limits + }; use parity_wasm::elements::FunctionType; impl ImportSatisfyCheck for () { @@ -416,7 +418,7 @@ pub mod benchmarking { } /// Prepare function that neither checks nor instruments the passed in code. - pub fn prepare_contract(original_code: &[u8], schedule: &Schedule) + pub fn prepare_contract(original_code: &[u8], schedule: &Schedule) -> Result { let contract_module = ContractModule::new(original_code, schedule)?; @@ -463,7 +465,7 @@ mod tests { fn $name() { let wasm = wat::parse_str($wat).unwrap(); let schedule = Schedule::default(); - let r = prepare_contract::(wasm.as_ref(), &schedule); + let r = prepare_contract::(wasm.as_ref(), &schedule); assert_matches!(r, $($expected)*); } }; @@ -491,7 +493,7 @@ mod tests { // Tests below assumes that maximum page number is configured to a certain number. #[test] fn assume_memory_size() { - assert_eq!(Schedule::default().max_memory_pages, 16); + assert_eq!(>::default().max_memory_pages, 16); } prepare_test!(memory_with_one_page, @@ -620,7 +622,7 @@ mod tests { // Tests below assumes that maximum table size is configured to a certain number. #[test] fn assume_table_size() { - assert_eq!(Schedule::default().max_table_size, 16384); + assert_eq!(>::default().max_table_size, 16384); } prepare_test!(no_tables, @@ -789,7 +791,7 @@ mod tests { ).unwrap(); let mut schedule = Schedule::default(); schedule.enable_println = true; - let r = prepare_contract::(wasm.as_ref(), &schedule); + let r = prepare_contract::(wasm.as_ref(), &schedule); assert_matches!(r, Ok(_)); } } diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index db1505aaebb93..b3a71b1500e8a 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -28,7 +28,7 @@ use frame_system; use frame_support::dispatch::DispatchError; use sp_std::prelude::*; use codec::{Decode, Encode}; -use sp_runtime::traits::{Bounded, SaturatedConversion}; +use sp_runtime::traits::SaturatedConversion; use sp_io::hashing::{ keccak_256, blake2_256, @@ -119,7 +119,7 @@ enum TrapReason { pub(crate) struct Runtime<'a, E: Ext + 'a> { ext: &'a mut E, input_data: Option>, - schedule: &'a Schedule, + schedule: &'a Schedule, memory: sp_sandbox::Memory, gas_meter: &'a mut GasMeter, trap_reason: Option, @@ -128,7 +128,7 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { pub(crate) fn new( ext: &'a mut E, input_data: Vec, - schedule: &'a Schedule, + schedule: &'a Schedule, memory: sp_sandbox::Memory, gas_meter: &'a mut GasMeter, ) -> Self { @@ -204,75 +204,147 @@ pub(crate) fn to_execution_result( #[cfg_attr(test, derive(Debug, PartialEq, Eq))] #[derive(Copy, Clone)] pub enum RuntimeToken { - /// Explicit call to the `gas` function. Charge the gas meter - /// with the value provided. - Explicit(u32), - /// The given number of bytes is read from the sandbox memory. - ReadMemory(u32), - /// The given number of bytes is written to the sandbox memory. - WriteMemory(u32), - /// The given number of bytes is read from the sandbox memory and - /// is returned as the return data buffer of the call. - ReturnData(u32), - /// (topic_count, data_bytes): A buffer of the given size is posted as an event indexed with the - /// given number of topics. - DepositEvent(u32, u32), + /// Charge the gas meter with the cost of a metering block. The charged costs are + /// the supplied cost of the block plus the overhead of the metering itself. + MeteringBlock(u32), + /// Weight of calling `seal_caller`. + Caller, + /// Weight of calling `seal_address`. + Address, + /// Weight of calling `seal_gas_left`. + GasLeft, + /// Weight of calling `seal_balance`. + Balance, + /// Weight of calling `seal_value_transferred`. + ValueTransferred, + /// Weight of calling `seal_minimum_balance`. + MinimumBalance, + /// Weight of calling `seal_tombstone_deposit`. + TombstoneDeposit, + /// Weight of calling `seal_rent_allowance`. + RentAllowance, + /// Weight of calling `seal_block_number`. + BlockNumber, + /// Weight of calling `seal_now`. + Now, + /// Weight of calling `seal_weight_to_fee`. + WeightToFee, + /// Weight of calling `seal_input` without the weight of copying the input. + InputBase, + /// Weight of copying the input data for the given size. + InputCopyOut(u32), + /// Weight of calling `seal_return` for the given output size. + Return(u32), + /// Weight of calling `seal_terminate`. + Terminate, + /// Weight of calling `seal_restore_to` per number of supplied delta entries. + RestoreTo(u32), + /// Weight of calling `seal_random`. It includes the weight for copying the subject. + Random, + /// Weight of calling `seal_reposit_event` with the given number of topics and event size. + DepositEvent{num_topic: u32, len: u32}, + /// Weight of calling `seal_set_rent_allowance`. + SetRentAllowance, + /// Weight of calling `seal_set_storage` for the given storage item size. + SetStorage(u32), + /// Weight of calling `seal_clear_storage`. + ClearStorage, + /// Weight of calling `seal_get_storage` without output weight. + GetStorageBase, + /// Weight of an item received via `seal_get_storage` for the given size. + GetStorageCopyOut(u32), + /// Weight of calling `seal_transfer`. + Transfer, + /// Weight of calling `seal_call` for the given input size. + CallBase(u32), + /// Weight of the transfer performed during a call. + CallSurchargeTransfer, + /// Weight of output received through `seal_call` for the given size. + CallCopyOut(u32), + /// Weight of calling `seal_instantiate` for the given input size without output weight. + /// This includes the transfer as an instantiate without a value will always be below + /// the existential deposit and is disregarded as corner case. + InstantiateBase(u32), + /// Weight of output received through `seal_instantiate` for the given size. + InstantiateCopyOut(u32), + /// Weight of calling `seal_hash_sha_256` for the given input size. + HashSha256(u32), + /// Weight of calling `seal_hash_keccak_256` for the given input size. + HashKeccak256(u32), + /// Weight of calling `seal_hash_blake2_256` for the given input size. + HashBlake256(u32), + /// Weight of calling `seal_hash_blake2_128` for the given input size. + HashBlake128(u32), } impl Token for RuntimeToken { - type Metadata = Schedule; + type Metadata = Schedule; - fn calculate_amount(&self, metadata: &Schedule) -> Gas { + fn calculate_amount(&self, s: &Schedule) -> Gas { use self::RuntimeToken::*; - let value = match *self { - Explicit(amount) => Some(amount.into()), - ReadMemory(byte_count) => metadata - .sandbox_data_read_cost - .checked_mul(byte_count.into()), - WriteMemory(byte_count) => metadata - .sandbox_data_write_cost - .checked_mul(byte_count.into()), - ReturnData(byte_count) => metadata - .return_data_per_byte_cost - .checked_mul(byte_count.into()), - DepositEvent(topic_count, data_byte_count) => { - let data_cost = metadata - .event_data_per_byte_cost - .checked_mul(data_byte_count.into()); - - let topics_cost = metadata - .event_per_topic_cost - .checked_mul(topic_count.into()); - - data_cost - .and_then(|data_cost| { - topics_cost.and_then(|topics_cost| { - data_cost.checked_add(topics_cost) - }) - }) - .and_then(|data_and_topics_cost| - data_and_topics_cost.checked_add(metadata.event_base_cost) - ) - }, - }; - - value.unwrap_or_else(|| Bounded::max_value()) + match *self { + MeteringBlock(amount) => s.api_cost_gas.saturating_add(amount.into()), + Caller => s.api_cost_caller, + Address => s.api_cost_address, + GasLeft => s.api_cost_gas_left, + Balance => s.api_cost_balance, + ValueTransferred => s.api_cost_value_transferred, + MinimumBalance => s.api_cost_minimum_balance, + TombstoneDeposit => s.api_cost_tombstone_deposit, + RentAllowance => s.api_cost_rent_allowance, + BlockNumber => s.api_cost_block_number, + Now => s.api_cost_now, + WeightToFee => s.api_cost_weight_to_fee, + InputBase => s.api_cost_input, + InputCopyOut(len) => s.api_cost_input_per_byte.saturating_mul(len.into()), + Return(len) => s.api_cost_return + .saturating_add(s.api_cost_return_per_byte.saturating_mul(len.into())), + Terminate => s.api_cost_terminate, + RestoreTo(delta) => s.api_cost_restore_to + .saturating_add(s.api_cost_restore_to_per_delta.saturating_mul(delta.into())), + Random => s.api_cost_random, + DepositEvent{num_topic, len} => s.api_cost_deposit_event + .saturating_add(s.api_cost_deposit_event_per_topic.saturating_mul(num_topic.into())) + .saturating_add(s.api_cost_deposit_event_per_byte.saturating_mul(len.into())), + SetRentAllowance => s.api_cost_set_rent_allowance, + SetStorage(len) => s.api_cost_set_storage + .saturating_add(s.api_cost_set_storage_per_byte.saturating_mul(len.into())), + ClearStorage => s.api_cost_clear_storage, + GetStorageBase => s.api_cost_get_storage, + GetStorageCopyOut(len) => s.api_cost_get_storage_per_byte.saturating_mul(len.into()), + Transfer => s.api_cost_transfer, + CallBase(len) => s.api_cost_call + .saturating_add(s.api_cost_call_per_input_byte.saturating_mul(len.into())), + CallSurchargeTransfer => s.api_cost_call_transfer_surcharge, + CallCopyOut(len) => s.api_cost_call_per_output_byte.saturating_mul(len.into()), + InstantiateBase(len) => s.api_cost_instantiate + .saturating_add(s.api_cost_instantiate_per_input_byte.saturating_mul(len.into())), + InstantiateCopyOut(len) => s.api_cost_instantiate_per_output_byte + .saturating_mul(len.into()), + HashSha256(len) => s.api_cost_hash_sha2_256 + .saturating_add(s.api_cost_hash_sha2_256_per_byte.saturating_mul(len.into())), + HashKeccak256(len) => s.api_cost_hash_keccak_256 + .saturating_add(s.api_cost_hash_keccak_256_per_byte.saturating_mul(len.into())), + HashBlake256(len) => s.api_cost_hash_blake2_256 + .saturating_add(s.api_cost_hash_blake2_256_per_byte.saturating_mul(len.into())), + HashBlake128(len) => s.api_cost_hash_blake2_128 + .saturating_add(s.api_cost_hash_blake2_128_per_byte.saturating_mul(len.into())), + } } } /// Charge the gas meter with the specified token. /// /// Returns `Err(HostError)` if there is not enough gas. -fn charge_gas>( - gas_meter: &mut GasMeter, - metadata: &Tok::Metadata, - trap_reason: &mut Option, - token: Tok, -) -> Result<(), sp_sandbox::HostError> { - match gas_meter.charge(metadata, token) { +fn charge_gas(ctx: &mut Runtime, token: Tok) -> Result<(), sp_sandbox::HostError> +where + E: Ext, + Tok: Token>, +{ + match ctx.gas_meter.charge(ctx.schedule, token) { GasMeterResult::Proceed => Ok(()), GasMeterResult::OutOfGas => { - *trap_reason = Some(TrapReason::SupervisorError(Error::::OutOfGas.into())); + ctx.trap_reason = Some(TrapReason::SupervisorError(Error::::OutOfGas.into())); Err(sp_sandbox::HostError) }, } @@ -291,13 +363,6 @@ fn read_sandbox_memory( ptr: u32, len: u32, ) -> Result, sp_sandbox::HostError> { - charge_gas( - ctx.gas_meter, - ctx.schedule, - &mut ctx.trap_reason, - RuntimeToken::ReadMemory(len), - )?; - let mut buf = vec![0u8; len as usize]; ctx.memory.get(ptr, buf.as_mut_slice()) .map_err(|_| store_err(ctx, Error::::OutOfBounds))?; @@ -317,13 +382,6 @@ fn read_sandbox_memory_into_buf( ptr: u32, buf: &mut [u8], ) -> Result<(), sp_sandbox::HostError> { - charge_gas( - ctx.gas_meter, - ctx.schedule, - &mut ctx.trap_reason, - RuntimeToken::ReadMemory(buf.len() as u32), - )?; - ctx.memory.get(ptr, buf).map_err(|_| store_err(ctx, Error::::OutOfBounds)) } @@ -358,15 +416,7 @@ fn write_sandbox_memory( ptr: u32, buf: &[u8], ) -> Result<(), sp_sandbox::HostError> { - charge_gas( - ctx.gas_meter, - ctx.schedule, - &mut ctx.trap_reason, - RuntimeToken::WriteMemory(buf.len() as u32), - )?; - - ctx.memory.set(ptr, buf) - .map_err(|_| store_err(ctx, Error::::OutOfBounds)) + ctx.memory.set(ptr, buf).map_err(|_| store_err(ctx, Error::::OutOfBounds)) } /// Write the given buffer and its length to the designated locations in sandbox memory. @@ -389,6 +439,7 @@ fn write_sandbox_output( out_len_ptr: u32, buf: &[u8], allow_skip: bool, + create_token: impl FnOnce(u32) -> Option, ) -> Result<(), sp_sandbox::HostError> { if allow_skip && out_ptr == u32::max_value() { return Ok(()); @@ -401,12 +452,9 @@ fn write_sandbox_output( Err(store_err(ctx, Error::::OutputBufferTooSmall))? } - charge_gas( - ctx.gas_meter, - ctx.schedule, - &mut ctx.trap_reason, - RuntimeToken::WriteMemory(buf_len.saturating_add(4)), - )?; + if let Some(token) = create_token(buf_len) { + charge_gas(ctx, token)?; + } ctx.memory.set(out_ptr, buf).and_then(|_| { ctx.memory.set(out_len_ptr, &buf_len.encode()) @@ -416,6 +464,12 @@ fn write_sandbox_output( Ok(()) } +/// Can be supplied to `write_sandbox_output` to indicate that the gas meter is not be +/// be charged for the copdied data. +fn no_charge(_: u32) -> Option { + None +} + /// Stores a DispatchError returned from an Ext function into the trap_reason. /// /// This allows through supervisor generated errors to the caller. @@ -516,12 +570,7 @@ define_env!(Env, , // // - amount: How much gas is used. gas(ctx, amount: u32) => { - charge_gas( - &mut ctx.gas_meter, - ctx.schedule, - &mut ctx.trap_reason, - RuntimeToken::Explicit(amount) - )?; + charge_gas(ctx, RuntimeToken::MeteringBlock(amount))?; Ok(()) }, @@ -541,6 +590,7 @@ define_env!(Env, , // - If value length exceeds the configured maximum value length of a storage entry. // - Upon trying to set an empty storage entry (value length is 0). seal_set_storage(ctx, key_ptr: u32, value_ptr: u32, value_len: u32) => { + charge_gas(ctx, RuntimeToken::SetStorage(value_len))?; if value_len > ctx.ext.max_value_size() { Err(store_err(ctx, Error::::ValueTooLarge))?; } @@ -557,6 +607,7 @@ define_env!(Env, , // // - `key_ptr`: pointer into the linear memory where the location to clear the value is placed. seal_clear_storage(ctx, key_ptr: u32) => { + charge_gas(ctx, RuntimeToken::ClearStorage)?; let mut key: StorageKey = [0; 32]; read_sandbox_memory_into_buf(ctx, key_ptr, &mut key)?; ctx.ext.set_storage(key, None); @@ -576,10 +627,13 @@ define_env!(Env, , // // `ReturnCode::KeyNotFound` seal_get_storage(ctx, key_ptr: u32, out_ptr: u32, out_len_ptr: u32) -> ReturnCode => { + charge_gas(ctx, RuntimeToken::GetStorageBase)?; let mut key: StorageKey = [0; 32]; read_sandbox_memory_into_buf(ctx, key_ptr, &mut key)?; if let Some(value) = ctx.ext.get_storage(&key) { - write_sandbox_output(ctx, out_ptr, out_len_ptr, &value, false)?; + write_sandbox_output(ctx, out_ptr, out_len_ptr, &value, false, |len| { + Some(RuntimeToken::GetStorageCopyOut(len)) + })?; Ok(ReturnCode::Success) } else { Ok(ReturnCode::KeyNotFound) @@ -608,12 +662,13 @@ define_env!(Env, , value_ptr: u32, value_len: u32 ) -> ReturnCode => { + charge_gas(ctx, RuntimeToken::Transfer)?; let callee: <::T as frame_system::Trait>::AccountId = read_sandbox_memory_as(ctx, account_ptr, account_len)?; let value: BalanceOf<::T> = read_sandbox_memory_as(ctx, value_ptr, value_len)?; - let result = ctx.ext.transfer(&callee, value, ctx.gas_meter); + let result = ctx.ext.transfer(&callee, value); map_dispatch_result(ctx, result) }, @@ -660,11 +715,16 @@ define_env!(Env, , output_ptr: u32, output_len_ptr: u32 ) -> ReturnCode => { + charge_gas(ctx, RuntimeToken::CallBase(input_data_len))?; let callee: <::T as frame_system::Trait>::AccountId = read_sandbox_memory_as(ctx, callee_ptr, callee_len)?; let value: BalanceOf<::T> = read_sandbox_memory_as(ctx, value_ptr, value_len)?; let input_data = read_sandbox_memory(ctx, input_data_ptr, input_data_len)?; + if value > 0.into() { + charge_gas(ctx, RuntimeToken::CallSurchargeTransfer)?; + } + let nested_gas_limit = if gas == 0 { ctx.gas_meter.gas_left() } else { @@ -687,7 +747,9 @@ define_env!(Env, , }); if let Ok(output) = &call_outcome { - write_sandbox_output(ctx, output_ptr, output_len_ptr, &output.data, true)?; + write_sandbox_output(ctx, output_ptr, output_len_ptr, &output.data, true, |len| { + Some(RuntimeToken::CallCopyOut(len)) + })?; } map_exec_result(ctx, call_outcome) }, @@ -749,6 +811,7 @@ define_env!(Env, , output_ptr: u32, output_len_ptr: u32 ) -> ReturnCode => { + charge_gas(ctx, RuntimeToken::InstantiateBase(input_data_len))?; let code_hash: CodeHash<::T> = read_sandbox_memory_as(ctx, code_hash_ptr, code_hash_len)?; let value: BalanceOf<::T> = read_sandbox_memory_as(ctx, value_ptr, value_len)?; @@ -777,10 +840,12 @@ define_env!(Env, , if let Ok((address, output)) = &instantiate_outcome { if !output.flags.contains(ReturnFlags::REVERT) { write_sandbox_output( - ctx, address_ptr, address_len_ptr, &address.encode(), true + ctx, address_ptr, address_len_ptr, &address.encode(), true, no_charge, )?; } - write_sandbox_output(ctx, output_ptr, output_len_ptr, &output.data, true)?; + write_sandbox_output(ctx, output_ptr, output_len_ptr, &output.data, true, |len| { + Some(RuntimeToken::InstantiateCopyOut(len)) + })?; } map_exec_result(ctx, instantiate_outcome.map(|(_id, retval)| retval)) }, @@ -804,18 +869,22 @@ define_env!(Env, , beneficiary_ptr: u32, beneficiary_len: u32 ) => { + charge_gas(ctx, RuntimeToken::Terminate)?; let beneficiary: <::T as frame_system::Trait>::AccountId = read_sandbox_memory_as(ctx, beneficiary_ptr, beneficiary_len)?; - if let Ok(_) = ctx.ext.terminate(&beneficiary, ctx.gas_meter) { + if let Ok(_) = ctx.ext.terminate(&beneficiary) { ctx.trap_reason = Some(TrapReason::Termination); } Err(sp_sandbox::HostError) }, seal_input(ctx, buf_ptr: u32, buf_len_ptr: u32) => { + charge_gas(ctx, RuntimeToken::InputBase)?; if let Some(input) = ctx.input_data.take() { - write_sandbox_output(ctx, buf_ptr, buf_len_ptr, &input, false) + write_sandbox_output(ctx, buf_ptr, buf_len_ptr, &input, false, |len| { + Some(RuntimeToken::InputCopyOut(len)) + }) } else { Err(sp_sandbox::HostError) } @@ -839,13 +908,7 @@ define_env!(Env, , // // Using a reserved bit triggers a trap. seal_return(ctx, flags: u32, data_ptr: u32, data_len: u32) => { - charge_gas( - ctx.gas_meter, - ctx.schedule, - &mut ctx.trap_reason, - RuntimeToken::ReturnData(data_len) - )?; - + charge_gas(ctx, RuntimeToken::Return(data_len))?; ctx.trap_reason = Some(TrapReason::Return(ReturnData { flags, data: read_sandbox_memory(ctx, data_ptr, data_len)?, @@ -868,7 +931,10 @@ define_env!(Env, , // extrinsic will be returned. Otherwise, if this call is initiated by another contract then the // address of the contract will be returned. The value is encoded as T::AccountId. seal_caller(ctx, out_ptr: u32, out_len_ptr: u32) => { - write_sandbox_output(ctx, out_ptr, out_len_ptr, &ctx.ext.caller().encode(), false) + charge_gas(ctx, RuntimeToken::Caller)?; + write_sandbox_output( + ctx, out_ptr, out_len_ptr, &ctx.ext.caller().encode(), false, no_charge + ) }, // Stores the address of the current contract into the supplied buffer. @@ -878,7 +944,10 @@ define_env!(Env, , // `out_ptr`. This call overwrites it with the size of the value. If the available // space at `out_ptr` is less than the size of the value a trap is triggered. seal_address(ctx, out_ptr: u32, out_len_ptr: u32) => { - write_sandbox_output(ctx, out_ptr, out_len_ptr, &ctx.ext.address().encode(), false) + charge_gas(ctx, RuntimeToken::Address)?; + write_sandbox_output( + ctx, out_ptr, out_len_ptr, &ctx.ext.address().encode(), false, no_charge + ) }, // Stores the price for the specified amount of gas into the supplied buffer. @@ -895,8 +964,9 @@ define_env!(Env, , // It is recommended to avoid specifying very small values for `gas` as the prices for a single // gas can be smaller than one. seal_weight_to_fee(ctx, gas: u64, out_ptr: u32, out_len_ptr: u32) => { + charge_gas(ctx, RuntimeToken::WeightToFee)?; write_sandbox_output( - ctx, out_ptr, out_len_ptr, &ctx.ext.get_weight_price(gas).encode(), false + ctx, out_ptr, out_len_ptr, &ctx.ext.get_weight_price(gas).encode(), false, no_charge ) }, @@ -909,7 +979,10 @@ define_env!(Env, , // // The data is encoded as Gas. seal_gas_left(ctx, out_ptr: u32, out_len_ptr: u32) => { - write_sandbox_output(ctx, out_ptr, out_len_ptr, &ctx.gas_meter.gas_left().encode(), false) + charge_gas(ctx, RuntimeToken::GasLeft)?; + write_sandbox_output( + ctx, out_ptr, out_len_ptr, &ctx.gas_meter.gas_left().encode(), false, no_charge + ) }, // Stores the balance of the current account into the supplied buffer. @@ -921,7 +994,10 @@ define_env!(Env, , // // The data is encoded as T::Balance. seal_balance(ctx, out_ptr: u32, out_len_ptr: u32) => { - write_sandbox_output(ctx, out_ptr, out_len_ptr, &ctx.ext.balance().encode(), false) + charge_gas(ctx, RuntimeToken::Balance)?; + write_sandbox_output( + ctx, out_ptr, out_len_ptr, &ctx.ext.balance().encode(), false, no_charge + ) }, // Stores the value transferred along with this call or as endowment into the supplied buffer. @@ -933,8 +1009,9 @@ define_env!(Env, , // // The data is encoded as T::Balance. seal_value_transferred(ctx, out_ptr: u32, out_len_ptr: u32) => { + charge_gas(ctx, RuntimeToken::ValueTransferred)?; write_sandbox_output( - ctx, out_ptr, out_len_ptr, &ctx.ext.value_transferred().encode(), false + ctx, out_ptr, out_len_ptr, &ctx.ext.value_transferred().encode(), false, no_charge ) }, @@ -947,13 +1024,14 @@ define_env!(Env, , // // The data is encoded as T::Hash. seal_random(ctx, subject_ptr: u32, subject_len: u32, out_ptr: u32, out_len_ptr: u32) => { + charge_gas(ctx, RuntimeToken::Random)?; // The length of a subject can't exceed `max_subject_len`. if subject_len > ctx.schedule.max_subject_len { return Err(sp_sandbox::HostError); } let subject_buf = read_sandbox_memory(ctx, subject_ptr, subject_len)?; write_sandbox_output( - ctx, out_ptr, out_len_ptr, &ctx.ext.random(&subject_buf).encode(), false + ctx, out_ptr, out_len_ptr, &ctx.ext.random(&subject_buf).encode(), false, no_charge ) }, @@ -964,14 +1042,20 @@ define_env!(Env, , // `out_ptr`. This call overwrites it with the size of the value. If the available // space at `out_ptr` is less than the size of the value a trap is triggered. seal_now(ctx, out_ptr: u32, out_len_ptr: u32) => { - write_sandbox_output(ctx, out_ptr, out_len_ptr, &ctx.ext.now().encode(), false) + charge_gas(ctx, RuntimeToken::Now)?; + write_sandbox_output( + ctx, out_ptr, out_len_ptr, &ctx.ext.now().encode(), false, no_charge + ) }, // Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer. // // The data is encoded as T::Balance. seal_minimum_balance(ctx, out_ptr: u32, out_len_ptr: u32) => { - write_sandbox_output(ctx, out_ptr, out_len_ptr, &ctx.ext.minimum_balance().encode(), false) + charge_gas(ctx, RuntimeToken::MinimumBalance)?; + write_sandbox_output( + ctx, out_ptr, out_len_ptr, &ctx.ext.minimum_balance().encode(), false, no_charge + ) }, // Stores the tombstone deposit into the supplied buffer. @@ -990,8 +1074,9 @@ define_env!(Env, , // below the sum of existential deposit and the tombstone deposit. The sum // is commonly referred as subsistence threshold in code. seal_tombstone_deposit(ctx, out_ptr: u32, out_len_ptr: u32) => { + charge_gas(ctx, RuntimeToken::TombstoneDeposit)?; write_sandbox_output( - ctx, out_ptr, out_len_ptr, &ctx.ext.tombstone_deposit().encode(), false + ctx, out_ptr, out_len_ptr, &ctx.ext.tombstone_deposit().encode(), false, no_charge ) }, @@ -1032,6 +1117,7 @@ define_env!(Env, , delta_ptr: u32, delta_count: u32 ) => { + charge_gas(ctx, RuntimeToken::RestoreTo(delta_count))?; let dest: <::T as frame_system::Trait>::AccountId = read_sandbox_memory_as(ctx, dest_ptr, dest_len)?; let code_hash: CodeHash<::T> = @@ -1039,9 +1125,8 @@ define_env!(Env, , let rent_allowance: BalanceOf<::T> = read_sandbox_memory_as(ctx, rent_allowance_ptr, rent_allowance_len)?; let delta = { - // We don't use `with_capacity` here to not eagerly allocate the user specified amount - // of memory. - let mut delta = Vec::new(); + // We can eagerly allocate because we charged for the complete delta count already + let mut delta = Vec::with_capacity(delta_count as usize); let mut key_ptr = delta_ptr; for _ in 0..delta_count { @@ -1079,6 +1164,10 @@ define_env!(Env, , // - data_ptr - a pointer to a raw data buffer which will saved along the event. // - data_len - the length of the data buffer. seal_deposit_event(ctx, topics_ptr: u32, topics_len: u32, data_ptr: u32, data_len: u32) => { + charge_gas(ctx, RuntimeToken::DepositEvent { + num_topic: topics_len / sp_std::mem::size_of::>() as u32, + len: data_len, + })?; if data_len > ctx.ext.max_value_size() { Err(store_err(ctx, Error::::ValueTooLarge))?; } @@ -1100,12 +1189,6 @@ define_env!(Env, , let event_data = read_sandbox_memory(ctx, data_ptr, data_len)?; - charge_gas( - ctx.gas_meter, - ctx.schedule, - &mut ctx.trap_reason, - RuntimeToken::DepositEvent(topics.len() as u32, data_len) - )?; ctx.ext.deposit_event(topics, event_data); Ok(()) @@ -1117,6 +1200,7 @@ define_env!(Env, , // Should be decodable as a `T::Balance`. Traps otherwise. // - value_len: length of the value buffer. seal_set_rent_allowance(ctx, value_ptr: u32, value_len: u32) => { + charge_gas(ctx, RuntimeToken::SetRentAllowance)?; let value: BalanceOf<::T> = read_sandbox_memory_as(ctx, value_ptr, value_len)?; ctx.ext.set_rent_allowance(value); @@ -1133,7 +1217,10 @@ define_env!(Env, , // // The data is encoded as T::Balance. seal_rent_allowance(ctx, out_ptr: u32, out_len_ptr: u32) => { - write_sandbox_output(ctx, out_ptr, out_len_ptr, &ctx.ext.rent_allowance().encode(), false) + charge_gas(ctx, RuntimeToken::RentAllowance)?; + write_sandbox_output( + ctx, out_ptr, out_len_ptr, &ctx.ext.rent_allowance().encode(), false, no_charge + ) }, // Prints utf8 encoded string from the data buffer. @@ -1154,7 +1241,10 @@ define_env!(Env, , // `out_ptr`. This call overwrites it with the size of the value. If the available // space at `out_ptr` is less than the size of the value a trap is triggered. seal_block_number(ctx, out_ptr: u32, out_len_ptr: u32) => { - write_sandbox_output(ctx, out_ptr, out_len_ptr, &ctx.ext.block_number().encode(), false) + charge_gas(ctx, RuntimeToken::BlockNumber)?; + write_sandbox_output( + ctx, out_ptr, out_len_ptr, &ctx.ext.block_number().encode(), false, no_charge + ) }, // Computes the SHA2 256-bit hash on the given input buffer. @@ -1178,6 +1268,7 @@ define_env!(Env, , // data is placed. The function will write the result // directly into this buffer. seal_hash_sha2_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => { + charge_gas(ctx, RuntimeToken::HashSha256(input_len))?; compute_hash_on_intermediate_buffer(ctx, sha2_256, input_ptr, input_len, output_ptr) }, @@ -1202,6 +1293,7 @@ define_env!(Env, , // data is placed. The function will write the result // directly into this buffer. seal_hash_keccak_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => { + charge_gas(ctx, RuntimeToken::HashKeccak256(input_len))?; compute_hash_on_intermediate_buffer(ctx, keccak_256, input_ptr, input_len, output_ptr) }, @@ -1226,6 +1318,7 @@ define_env!(Env, , // data is placed. The function will write the result // directly into this buffer. seal_hash_blake2_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => { + charge_gas(ctx, RuntimeToken::HashBlake256(input_len))?; compute_hash_on_intermediate_buffer(ctx, blake2_256, input_ptr, input_len, output_ptr) }, @@ -1250,6 +1343,7 @@ define_env!(Env, , // data is placed. The function will write the result // directly into this buffer. seal_hash_blake2_128(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => { + charge_gas(ctx, RuntimeToken::HashBlake128(input_len))?; compute_hash_on_intermediate_buffer(ctx, blake2_128, input_ptr, input_len, output_ptr) }, ); From ca7742d1a75e900326a9da33d53c6266f320ee04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Wed, 23 Sep 2020 12:38:45 +0200 Subject: [PATCH 10/29] Review: Fix copy paste typo in schedule construction --- frame/contracts/src/schedule.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/contracts/src/schedule.rs b/frame/contracts/src/schedule.rs index b86113a762c29..cd5bf8ec792b7 100644 --- a/frame/contracts/src/schedule.rs +++ b/frame/contracts/src/schedule.rs @@ -302,7 +302,7 @@ impl Default for Schedule { api_cost_gas: cost_batched!(seal_gas), api_cost_input: cost!(seal_input), api_cost_input_per_byte: cost_byte!(seal_input_per_kb), - api_cost_return: cost!(seal_input), + api_cost_return: cost!(seal_return), api_cost_return_per_byte: cost_byte!(seal_return_per_kb), api_cost_terminate: cost!(seal_terminate), api_cost_restore_to: cost!(seal_restore_to), From cdec9ae9db07c412a4dcc42d5b07d6c8dcedf4cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Wed, 23 Sep 2020 13:27:18 +0200 Subject: [PATCH 11/29] Review: Fix stale docs --- frame/contracts/src/exec.rs | 18 ++++------------- frame/contracts/src/wasm/runtime.rs | 31 +++++++++++------------------ 2 files changed, 16 insertions(+), 33 deletions(-) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 3cfa86cf115ef..27ce56137f08e 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -491,20 +491,10 @@ enum TransferCause { /// Transfer some funds from `transactor` to `dest`. /// -/// All balance changes are performed in the `overlay`. -/// -/// This function also handles charging the fee. The fee depends -/// on whether the transfer happening because of contract instantiation -/// (transferring endowment) or because of a transfer via `call`. This -/// is specified using the `cause` parameter. -/// -/// NOTE: that the fee is denominated in `BalanceOf` units, but -/// charged in `Gas` from the provided `gas_meter`. This means -/// that the actual amount charged might differ. -/// -/// NOTE: that we allow for draining all funds of the contract so it -/// can go below existential deposit, essentially giving a contract -/// the chance to give up it's life. +/// We only allow allow for draining all funds of the sender if `cause` is +/// is specified as`Terminate`. Otherwise, any transfer that would bring the sender below the +/// subsistence threshold (for contracts) or the existential deposit (for plain accounts) +/// results in an error. fn transfer<'a, T: Trait, V: Vm, L: Loader>( cause: TransferCause, origin: TransactorKind, diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index b3a71b1500e8a..046c8382b8758 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -350,13 +350,10 @@ where } } -/// Read designated chunk from the sandbox memory, consuming an appropriate amount of -/// gas. +/// Read designated chunk from the sandbox memory. /// /// Returns `Err` if one of the following conditions occurs: /// -/// - calculating the gas cost resulted in overflow. -/// - out of gas /// - requested buffer is not within the bounds of the sandbox memory. fn read_sandbox_memory( ctx: &mut Runtime, @@ -369,13 +366,10 @@ fn read_sandbox_memory( Ok(buf) } -/// Read designated chunk from the sandbox memory into the supplied buffer, consuming -/// an appropriate amount of gas. +/// Read designated chunk from the sandbox memory into the supplied buffer. /// /// Returns `Err` if one of the following conditions occurs: /// -/// - calculating the gas cost resulted in overflow. -/// - out of gas /// - requested buffer is not within the bounds of the sandbox memory. fn read_sandbox_memory_into_buf( ctx: &mut Runtime, @@ -385,13 +379,10 @@ fn read_sandbox_memory_into_buf( ctx.memory.get(ptr, buf).map_err(|_| store_err(ctx, Error::::OutOfBounds)) } -/// Read designated chunk from the sandbox memory, consuming an appropriate amount of -/// gas, and attempt to decode into the specified type. +/// Read designated chunk from the sandbox memory and attempt to decode into the specified type. /// /// Returns `Err` if one of the following conditions occurs: /// -/// - calculating the gas cost resulted in overflow. -/// - out of gas /// - requested buffer is not within the bounds of the sandbox memory. /// - the buffer contents cannot be decoded as the required type. fn read_sandbox_memory_as( @@ -403,13 +394,10 @@ fn read_sandbox_memory_as( D::decode(&mut &buf[..]).map_err(|_| store_err(ctx, Error::::DecodingFailed)) } -/// Write the given buffer to the designated location in the sandbox memory, consuming -/// an appropriate amount of gas. +/// Write the given buffer to the designated location in the sandbox memory. /// /// Returns `Err` if one of the following conditions occurs: /// -/// - calculating the gas cost resulted in overflow. -/// - out of gas /// - designated area is not within the bounds of the sandbox memory. fn write_sandbox_memory( ctx: &mut Runtime, @@ -419,11 +407,12 @@ fn write_sandbox_memory( ctx.memory.set(ptr, buf).map_err(|_| store_err(ctx, Error::::OutOfBounds)) } -/// Write the given buffer and its length to the designated locations in sandbox memory. +/// Write the given buffer and its length to the designated locations in sandbox memory and +/// charge gas according to the token returned by `create_token`. // /// `out_ptr` is the location in sandbox memory where `buf` should be written to. /// `out_len_ptr` is an in-out location in sandbox memory. It is read to determine the -/// lenght of the buffer located at `out_ptr`. If that buffer is large enough the actual +/// length of the buffer located at `out_ptr`. If that buffer is large enough the actual /// `buf.len()` is written to this location. /// /// If `out_ptr` is set to the sentinel value of `u32::max_value()` and `allow_skip` is true the @@ -431,6 +420,10 @@ fn write_sandbox_memory( /// output optional. For example to skip copying back the output buffer of an `seal_call` /// when the caller is not interested in the result. /// +/// `create_token` can optionally instruct this function to charge the gas meter with the token +/// it returns. `create_token` receives the variable amount of bytes that are about to be copied by +/// this function. +/// /// In addition to the error conditions of `write_sandbox_memory` this functions returns /// `Err` if the size of the buffer located at `out_ptr` is too small to fit `buf`. fn write_sandbox_output( @@ -465,7 +458,7 @@ fn write_sandbox_output( } /// Can be supplied to `write_sandbox_output` to indicate that the gas meter is not be -/// be charged for the copdied data. +/// be charged for the copied data. fn no_charge(_: u32) -> Option { None } From 0a2bd2a5b71fd0daa6576ad9d4359cb1460fe454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Wed, 23 Sep 2020 16:08:50 +0200 Subject: [PATCH 12/29] Fix whitespace errors Co-authored-by: Sergei Shulepov --- frame/contracts/src/exec.rs | 2 +- frame/contracts/src/schedule.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 27ce56137f08e..bc99431c85e65 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -492,7 +492,7 @@ enum TransferCause { /// Transfer some funds from `transactor` to `dest`. /// /// We only allow allow for draining all funds of the sender if `cause` is -/// is specified as`Terminate`. Otherwise, any transfer that would bring the sender below the +/// is specified as `Terminate`. Otherwise, any transfer that would bring the sender below the /// subsistence threshold (for contracts) or the existential deposit (for plain accounts) /// results in an error. fn transfer<'a, T: Trait, V: Vm, L: Loader>( diff --git a/frame/contracts/src/schedule.rs b/frame/contracts/src/schedule.rs index cd5bf8ec792b7..37440d26000b7 100644 --- a/frame/contracts/src/schedule.rs +++ b/frame/contracts/src/schedule.rs @@ -287,7 +287,6 @@ impl Default for Schedule { op_cost_grow_mem: WASM_INSTRUCTION_COST, op_cost_regular: WASM_INSTRUCTION_COST, - api_cost_caller: cost_batched!(seal_caller), api_cost_address: cost_batched!(seal_address), api_cost_gas_left: cost_batched!(seal_gas_left), From 771bf8a2c23955e22852aa8b4308d6c1f1208ddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Wed, 23 Sep 2020 17:55:59 +0200 Subject: [PATCH 13/29] Review: Use checked_div in order to be more defensive --- frame/contracts/src/wasm/runtime.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 046c8382b8758..e2e8d00e7e192 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -1157,8 +1157,11 @@ define_env!(Env, , // - data_ptr - a pointer to a raw data buffer which will saved along the event. // - data_len - the length of the data buffer. seal_deposit_event(ctx, topics_ptr: u32, topics_len: u32, data_ptr: u32, data_len: u32) => { + let num_topic = topics_len + .checked_div(sp_std::mem::size_of::>() as u32) + .ok_or_else(|| store_err(ctx, "Zero sized topics are not allowed"))?; charge_gas(ctx, RuntimeToken::DepositEvent { - num_topic: topics_len / sp_std::mem::size_of::>() as u32, + num_topic, len: data_len, })?; if data_len > ctx.ext.max_value_size() { From d76bcde664c15694e37e4ba56f580bde4cc89944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Thu, 24 Sep 2020 11:29:24 +0200 Subject: [PATCH 14/29] Review: Rename no_charge to already_charged --- frame/contracts/src/wasm/runtime.rs | 41 +++++++++++++++++------------ 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index e2e8d00e7e192..65854258ab33f 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -415,7 +415,7 @@ fn write_sandbox_memory( /// length of the buffer located at `out_ptr`. If that buffer is large enough the actual /// `buf.len()` is written to this location. /// -/// If `out_ptr` is set to the sentinel value of `u32::max_value()` and `allow_skip` is true the +/// If `out_ptr` is set to the sentinel value of `u32::max_value()` and `allow_skip` is true the /// operation is skipped and `Ok` is returned. This is supposed to help callers to make copying /// output optional. For example to skip copying back the output buffer of an `seal_call` /// when the caller is not interested in the result. @@ -457,9 +457,12 @@ fn write_sandbox_output( Ok(()) } -/// Can be supplied to `write_sandbox_output` to indicate that the gas meter is not be -/// be charged for the copied data. -fn no_charge(_: u32) -> Option { +/// Supply to `write_sandbox_output` to indicate that the gas meter should not be charged. +/// +/// This is only appropriate when writing out data of constant size that does not depend on user +/// input. In this case the costs for this copy was already charged as part of the token at +/// the beginning of the API entry point. +fn already_charged(_: u32) -> Option { None } @@ -833,7 +836,7 @@ define_env!(Env, , if let Ok((address, output)) = &instantiate_outcome { if !output.flags.contains(ReturnFlags::REVERT) { write_sandbox_output( - ctx, address_ptr, address_len_ptr, &address.encode(), true, no_charge, + ctx, address_ptr, address_len_ptr, &address.encode(), true, already_charged, )?; } write_sandbox_output(ctx, output_ptr, output_len_ptr, &output.data, true, |len| { @@ -926,7 +929,7 @@ define_env!(Env, , seal_caller(ctx, out_ptr: u32, out_len_ptr: u32) => { charge_gas(ctx, RuntimeToken::Caller)?; write_sandbox_output( - ctx, out_ptr, out_len_ptr, &ctx.ext.caller().encode(), false, no_charge + ctx, out_ptr, out_len_ptr, &ctx.ext.caller().encode(), false, already_charged ) }, @@ -939,7 +942,7 @@ define_env!(Env, , seal_address(ctx, out_ptr: u32, out_len_ptr: u32) => { charge_gas(ctx, RuntimeToken::Address)?; write_sandbox_output( - ctx, out_ptr, out_len_ptr, &ctx.ext.address().encode(), false, no_charge + ctx, out_ptr, out_len_ptr, &ctx.ext.address().encode(), false, already_charged ) }, @@ -959,7 +962,8 @@ define_env!(Env, , seal_weight_to_fee(ctx, gas: u64, out_ptr: u32, out_len_ptr: u32) => { charge_gas(ctx, RuntimeToken::WeightToFee)?; write_sandbox_output( - ctx, out_ptr, out_len_ptr, &ctx.ext.get_weight_price(gas).encode(), false, no_charge + ctx, out_ptr, out_len_ptr, &ctx.ext.get_weight_price(gas).encode(), false, + already_charged ) }, @@ -974,7 +978,7 @@ define_env!(Env, , seal_gas_left(ctx, out_ptr: u32, out_len_ptr: u32) => { charge_gas(ctx, RuntimeToken::GasLeft)?; write_sandbox_output( - ctx, out_ptr, out_len_ptr, &ctx.gas_meter.gas_left().encode(), false, no_charge + ctx, out_ptr, out_len_ptr, &ctx.gas_meter.gas_left().encode(), false, already_charged ) }, @@ -989,7 +993,7 @@ define_env!(Env, , seal_balance(ctx, out_ptr: u32, out_len_ptr: u32) => { charge_gas(ctx, RuntimeToken::Balance)?; write_sandbox_output( - ctx, out_ptr, out_len_ptr, &ctx.ext.balance().encode(), false, no_charge + ctx, out_ptr, out_len_ptr, &ctx.ext.balance().encode(), false, already_charged ) }, @@ -1004,7 +1008,8 @@ define_env!(Env, , seal_value_transferred(ctx, out_ptr: u32, out_len_ptr: u32) => { charge_gas(ctx, RuntimeToken::ValueTransferred)?; write_sandbox_output( - ctx, out_ptr, out_len_ptr, &ctx.ext.value_transferred().encode(), false, no_charge + ctx, out_ptr, out_len_ptr, &ctx.ext.value_transferred().encode(), false, + already_charged ) }, @@ -1024,7 +1029,8 @@ define_env!(Env, , } let subject_buf = read_sandbox_memory(ctx, subject_ptr, subject_len)?; write_sandbox_output( - ctx, out_ptr, out_len_ptr, &ctx.ext.random(&subject_buf).encode(), false, no_charge + ctx, out_ptr, out_len_ptr, &ctx.ext.random(&subject_buf).encode(), false, + already_charged ) }, @@ -1037,7 +1043,7 @@ define_env!(Env, , seal_now(ctx, out_ptr: u32, out_len_ptr: u32) => { charge_gas(ctx, RuntimeToken::Now)?; write_sandbox_output( - ctx, out_ptr, out_len_ptr, &ctx.ext.now().encode(), false, no_charge + ctx, out_ptr, out_len_ptr, &ctx.ext.now().encode(), false, already_charged ) }, @@ -1047,7 +1053,7 @@ define_env!(Env, , seal_minimum_balance(ctx, out_ptr: u32, out_len_ptr: u32) => { charge_gas(ctx, RuntimeToken::MinimumBalance)?; write_sandbox_output( - ctx, out_ptr, out_len_ptr, &ctx.ext.minimum_balance().encode(), false, no_charge + ctx, out_ptr, out_len_ptr, &ctx.ext.minimum_balance().encode(), false, already_charged ) }, @@ -1069,7 +1075,8 @@ define_env!(Env, , seal_tombstone_deposit(ctx, out_ptr: u32, out_len_ptr: u32) => { charge_gas(ctx, RuntimeToken::TombstoneDeposit)?; write_sandbox_output( - ctx, out_ptr, out_len_ptr, &ctx.ext.tombstone_deposit().encode(), false, no_charge + ctx, out_ptr, out_len_ptr, &ctx.ext.tombstone_deposit().encode(), false, + already_charged ) }, @@ -1215,7 +1222,7 @@ define_env!(Env, , seal_rent_allowance(ctx, out_ptr: u32, out_len_ptr: u32) => { charge_gas(ctx, RuntimeToken::RentAllowance)?; write_sandbox_output( - ctx, out_ptr, out_len_ptr, &ctx.ext.rent_allowance().encode(), false, no_charge + ctx, out_ptr, out_len_ptr, &ctx.ext.rent_allowance().encode(), false, already_charged ) }, @@ -1239,7 +1246,7 @@ define_env!(Env, , seal_block_number(ctx, out_ptr: u32, out_len_ptr: u32) => { charge_gas(ctx, RuntimeToken::BlockNumber)?; write_sandbox_output( - ctx, out_ptr, out_len_ptr, &ctx.ext.block_number().encode(), false, no_charge + ctx, out_ptr, out_len_ptr, &ctx.ext.block_number().encode(), false, already_charged ) }, From b3aa6acd2c248365c56892688358fc83e464df8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Thu, 24 Sep 2020 12:10:58 +0200 Subject: [PATCH 15/29] Review: Whitelist caller of extrinsics --- frame/contracts/src/benchmarking.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/frame/contracts/src/benchmarking.rs b/frame/contracts/src/benchmarking.rs index e0c160bb70230..5e2378b2678eb 100644 --- a/frame/contracts/src/benchmarking.rs +++ b/frame/contracts/src/benchmarking.rs @@ -24,7 +24,7 @@ use crate::Module as Contracts; use crate::exec::StorageKey; use crate::schedule::API_BENCHMARK_BATCH_SIZE; -use frame_benchmarking::{benchmarks, account}; +use frame_benchmarking::{benchmarks, account, whitelisted_caller}; use frame_system::{Module as System, RawOrigin}; use parity_wasm::elements::{Instruction, Instructions, FuncBody, ValueType, BlockType}; use sp_runtime::traits::{Hash, Bounded, SaturatedConversion, CheckedDiv}; @@ -410,12 +410,6 @@ fn max_endowment() -> BalanceOf { funding::().saturating_sub(T::Currency::minimum_balance()) } -fn create_funded_user(string: &'static str, n: u32) -> T::AccountId { - let user = account(string, n, 0); - T::Currency::make_free_balance_be(&user, funding::()); - user -} - fn eviction_at(addr: &T::AccountId) -> Result { match crate::rent::compute_rent_projection::(addr).map_err(|_| "Invalid acc for rent")? { RentProjection::EvictionAt(at) => Ok(at), @@ -451,7 +445,8 @@ benchmarks! { // `n`: Size of the code in kilobytes. put_code { let n in 0 .. Contracts::::current_schedule().max_code_size / 1024; - let caller = create_funded_user::("caller", 0); + let caller = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, funding::()); let module = sized_code::(n * 1024); let origin = RawOrigin::Signed(caller); }: _(origin, module.code) @@ -464,7 +459,8 @@ benchmarks! { let n in 0 .. max_pages::() * 64; let data = vec![42u8; (n * 1024) as usize]; let endowment = Config::::subsistence_threshold_uncached(); - let caller = create_funded_user::("caller", 0); + let caller = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, funding::()); let WasmModule { code, hash } = dummy_code::(); let origin = RawOrigin::Signed(caller.clone()); let addr = T::DetermineContractAddress::contract_address_for(&hash, &data, &caller); @@ -486,7 +482,9 @@ benchmarks! { // part of `seal_input`. call { let data = vec![42u8; 1024]; - let instance = instantiate_contract::(dummy_code(), vec![], Endow::CollectRent)?; + let instance = instantiate_contract_from_account::( + whitelisted_caller(), dummy_code(), vec![], Endow::CollectRent + )?; let value = T::Currency::minimum_balance() * 100.into(); let origin = RawOrigin::Signed(instance.caller.clone()); @@ -513,7 +511,9 @@ benchmarks! { // no incentive to remove large contracts when the removal is more expensive than // the reward for removing them. claim_surcharge { - let instance = instantiate_contract::(dummy_code(), vec![], Endow::CollectRent)?; + let instance = instantiate_contract_from_account::( + whitelisted_caller(), dummy_code(), vec![], Endow::CollectRent + )?; let origin = RawOrigin::Signed(instance.caller.clone()); let account_id = instance.account_id.clone(); From 678f155fff507da4b3d722714304a3f32f880e5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Thu, 24 Sep 2020 14:53:33 +0200 Subject: [PATCH 16/29] Review: Remove trailing whitespace --- frame/contracts/fixtures/event_size.wat | 2 +- frame/contracts/src/benchmarking.rs | 4 ++-- frame/contracts/src/wasm/runtime.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frame/contracts/fixtures/event_size.wat b/frame/contracts/fixtures/event_size.wat index e12915e21e38d..4bd6158d72fb9 100644 --- a/frame/contracts/fixtures/event_size.wat +++ b/frame/contracts/fixtures/event_size.wat @@ -28,7 +28,7 @@ ;; place a garbage value in storage, the size of which is specified by the call input. (call $seal_deposit_event - (i32.const 0) ;; topics_ptr + (i32.const 0) ;; topics_ptr (i32.const 0) ;; topics_len (i32.const 0) ;; data_ptr (i32.load (i32.const 4)) ;; data_len diff --git a/frame/contracts/src/benchmarking.rs b/frame/contracts/src/benchmarking.rs index 5e2378b2678eb..e4e45214e1c4e 100644 --- a/frame/contracts/src/benchmarking.rs +++ b/frame/contracts/src/benchmarking.rs @@ -30,7 +30,7 @@ use parity_wasm::elements::{Instruction, Instructions, FuncBody, ValueType, Bloc use sp_runtime::traits::{Hash, Bounded, SaturatedConversion, CheckedDiv}; use sp_std::{default::Default, convert::{TryFrom, TryInto}}; -/// How many batches we do per API benchmark. +/// How many batches we do per API benchmark. const API_BENCHMARK_BATCHES: u32 = 20; #[derive(Clone)] @@ -453,7 +453,7 @@ benchmarks! { // Instantiate uses a dummy contract constructor to measure the overhead of the instantiate. // The size of the input data influences the runtime because it is hashed in order to determine - // the contract address. + // the contract address. // `n`: Size of the data passed to constructor in kilobytes. instantiate { let n in 0 .. max_pages::() * 64; diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 65854258ab33f..ec7e9238847ec 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -878,7 +878,7 @@ define_env!(Env, , seal_input(ctx, buf_ptr: u32, buf_len_ptr: u32) => { charge_gas(ctx, RuntimeToken::InputBase)?; if let Some(input) = ctx.input_data.take() { - write_sandbox_output(ctx, buf_ptr, buf_len_ptr, &input, false, |len| { + write_sandbox_output(ctx, buf_ptr, buf_len_ptr, &input, false, |len| { Some(RuntimeToken::InputCopyOut(len)) }) } else { From 190144e9dec49e2f2f8d3cfcd66460fbb816dbaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Thu, 24 Sep 2020 15:10:13 +0200 Subject: [PATCH 17/29] Review: Remove confusing "self::" syntax --- frame/contracts/src/tests.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index 0efd00c2ff317..1c14e3e35f248 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -490,8 +490,8 @@ fn deposit_event_max_value_limit() { Origin::signed(ALICE), BOB, 0, - GAS_LIMIT * 2, // we are copying a huge buffer - Encode::encode(&self::MaxValueSize::get()), + GAS_LIMIT * 2, // we are copying a huge buffer, + ::MaxValueSize::get().encode(), )); // Call contract with too large a storage value. @@ -501,7 +501,7 @@ fn deposit_event_max_value_limit() { BOB, 0, GAS_LIMIT, - Encode::encode(&(self::MaxValueSize::get() + 1)), + (::MaxValueSize::get() + 1).encode(), ), Error::::ValueTooLarge, ); @@ -1358,7 +1358,7 @@ fn storage_max_value_limit() { BOB, 0, GAS_LIMIT * 2, // we are copying a huge buffer - Encode::encode(&self::MaxValueSize::get()), + ::MaxValueSize::get().encode(), )); // Call contract with too large a storage value. @@ -1368,7 +1368,7 @@ fn storage_max_value_limit() { BOB, 0, GAS_LIMIT, - Encode::encode(&(self::MaxValueSize::get() + 1)), + (::MaxValueSize::get() + 1).encode(), ), Error::::ValueTooLarge, ); From dd2edeb07907619e1cc4ab20073d6943d985c485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Thu, 24 Sep 2020 15:23:14 +0200 Subject: [PATCH 18/29] Review: Add docs for the benchmark prepration submodule --- frame/contracts/src/wasm/prepare.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frame/contracts/src/wasm/prepare.rs b/frame/contracts/src/wasm/prepare.rs index a23821c1a92fc..f53b47013b659 100644 --- a/frame/contracts/src/wasm/prepare.rs +++ b/frame/contracts/src/wasm/prepare.rs @@ -404,6 +404,12 @@ pub fn prepare_contract( }) } +/// Alternate (possibly unsafe) preparation functions used only for benchmarking. +/// +/// For benchmarking we need to construct special contracts that might not pass our +/// sanity checks or need to skip instrumentation for correct results. We hide functions +/// allowing this behind a feature that is only set during benchmarking to prevent usage +/// in production code. #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking { use super::{ From 4e65609dbd0999bb410d0117ccb1353d10d98deb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Thu, 24 Sep 2020 16:16:57 +0200 Subject: [PATCH 19/29] Review: Move code generation functions to own module --- frame/contracts/src/benchmarking/code.rs | 272 +++++++++++ .../{benchmarking.rs => benchmarking/mod.rs} | 435 +++++------------- 2 files changed, 380 insertions(+), 327 deletions(-) create mode 100644 frame/contracts/src/benchmarking/code.rs rename frame/contracts/src/{benchmarking.rs => benchmarking/mod.rs} (81%) diff --git a/frame/contracts/src/benchmarking/code.rs b/frame/contracts/src/benchmarking/code.rs new file mode 100644 index 0000000000000..4e4cb314ceac2 --- /dev/null +++ b/frame/contracts/src/benchmarking/code.rs @@ -0,0 +1,272 @@ +// This file is part of Substrate. + +// Copyright (C) 2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Functions to prodecurally construct contract code used for benchmarking. +//! +//! In order to be able to benchmark events that are triggered by contract execution +//! (API calls into seal, individual instructions), we need to generate contracts that +//! perform those events. Because those contracts can get very big we cannot simply define +//! them as text (.wat) as this will be too slow and consume too much memory. Therefore +//! we define this simple definition of a contract that can be passed to `create_code` that +//! compiles it down into a `WasmModule` that can be used as a contract's code. + +use crate::Trait; +use crate::Module as Contracts; + +use parity_wasm::elements::{Instruction, Instructions, FuncBody, ValueType, BlockType}; +use sp_runtime::traits::Hash; +use std::convert::TryFrom; + +/// Pass to `create_code` in order to create a compiled `WasmModule`. +pub struct ModuleDefinition { + pub data_segments: Vec, + pub memory: Option, + pub imported_functions: Vec, + pub deploy_body: Option, + pub call_body: Option, +} + +impl Default for ModuleDefinition { + fn default() -> Self { + Self { + data_segments: vec![], + memory: None, + imported_functions: vec![], + deploy_body: None, + call_body: None, + } + } +} + +pub struct DataSegment { + pub offset: u32, + pub value: Vec, +} + +pub struct ImportedMemory { + pub min_pages: u32, + pub max_pages: u32, +} + +impl ImportedMemory { + pub fn max() -> Self { + let pages = max_pages::(); + Self { min_pages: pages, max_pages: pages } + } +} + +pub struct ImportedFunction { + pub name: &'static str, + pub params: Vec, + pub return_type: Option, +} + +/// A wasm module ready to be put on chain with `put_code`. +#[derive(Clone)] +pub struct WasmModule { + pub code: Vec, + pub hash: ::Output, +} + +impl From for WasmModule { + fn from(def: ModuleDefinition) -> Self { + // internal functions start at that offset. + let func_offset = u32::try_from(def.imported_functions.len()).unwrap(); + + // Every contract must export "deploy" and "call" functions + let mut contract = parity_wasm::builder::module() + // deploy function (first internal function) + .function() + .signature().with_params(vec![]).with_return_type(None).build() + .with_body(def.deploy_body.unwrap_or_else(|| + FuncBody::new(Vec::new(), Instructions::empty()) + )) + .build() + // call function (second internal function) + .function() + .signature().with_params(vec![]).with_return_type(None).build() + .with_body(def.call_body.unwrap_or_else(|| + FuncBody::new(Vec::new(), Instructions::empty()) + )) + .build() + .export().field("deploy").internal().func(func_offset).build() + .export().field("call").internal().func(func_offset + 1).build(); + + // Grant access to linear memory. + if let Some(memory) = def.memory { + contract = contract.import() + .module("env").field("memory") + .external().memory(memory.min_pages, Some(memory.max_pages)) + .build(); + } + + // Import supervisor functions. They start with idx 0. + for func in def.imported_functions { + let sig = parity_wasm::builder::signature() + .with_params(func.params) + .with_return_type(func.return_type) + .build_sig(); + let sig = contract.push_signature(sig); + contract = contract.import() + .module("seal0") + .field(func.name) + .with_external(parity_wasm::elements::External::Function(sig)) + .build(); + } + + // Initialize memory + for data in def.data_segments { + contract = contract.data() + .offset(Instruction::I32Const(data.offset as i32)) + .value(data.value) + .build() + } + + let code = contract.build().to_bytes().unwrap(); + let hash = T::Hashing::hash(&code); + Self { + code, + hash + } + } +} + +impl WasmModule { + pub fn dummy() -> Self { + ModuleDefinition::default().into() + } + + pub fn sized(target_bytes: u32) -> Self { + use parity_wasm::elements::Instruction::{If, I32Const, Return, End}; + // Base size of a contract is 47 bytes and each expansion adds 6 bytes. + // We do one expansion less to account for the code section and function body + // size fields inside the binary wasm module representation which are leb128 encoded + // and therefore grow in size when the contract grows. We are not allowed to overshoot + // because of the maximum code size that is enforced by `put_code`. + let expansions = (target_bytes.saturating_sub(47) / 6).saturating_sub(1); + const EXPANSION: [Instruction; 4] = [ + I32Const(0), + If(BlockType::NoResult), + Return, + End, + ]; + ModuleDefinition { + call_body: Some(body::repeated(expansions, &EXPANSION)), + .. Default::default() + } + .into() + } + + pub fn getter(getter_name: &'static str, repeat: u32) -> Self { + let pages = max_pages::(); + ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: getter_name, + params: vec![ValueType::I32, ValueType::I32], + return_type: None, + }], + // Write the output buffer size. The output size will be overwritten by the + // supervisor with the real size when calling the getter. Since this size does not + // change between calls it suffices to start with an initial value and then just + // leave as whatever value was written there. + data_segments: vec![DataSegment { + offset: 0, + value: (pages * 64 * 1024 - 4).to_le_bytes().to_vec(), + }], + call_body: Some(body::repeated(repeat, &[ + Instruction::I32Const(4), // ptr where to store output + Instruction::I32Const(0), // ptr to length + Instruction::Call(0), // call the imported function + ])), + .. Default::default() + } + .into() + } + + pub fn hasher(name: &'static str, repeat: u32, data_size: u32) -> Self { + ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: name, + params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + return_type: None, + }], + call_body: Some(body::repeated(repeat, &[ + Instruction::I32Const(0), // input_ptr + Instruction::I32Const(data_size as i32), // input_len + Instruction::I32Const(0), // output_ptr + Instruction::Call(0), + ])), + .. Default::default() + } + .into() + } +} + +/// Mechanisms to create a function body that can be used inside a `ModuleDefinition`. +pub mod body { + use super::*; + + pub enum CountedInstruction { + // (offset, increment_by) + Counter(u32, u32), + Regular(Instruction), + } + + pub fn plain(instructions: Vec) -> FuncBody { + FuncBody::new(Vec::new(), Instructions::new(instructions)) + } + + pub fn repeated(repetitions: u32, instructions: &[Instruction]) -> FuncBody { + let instructions = Instructions::new( + instructions + .iter() + .cycle() + .take(instructions.len() * usize::try_from(repetitions).unwrap()) + .cloned() + .chain(sp_std::iter::once(Instruction::End)) + .collect() + ); + FuncBody::new(Vec::new(), instructions) + } + + pub fn counted(repetitions: u32, mut instructions: Vec) -> FuncBody { + // We need to iterate over indices because we cannot cycle over mutable references + let body = (0..instructions.len()) + .cycle() + .take(instructions.len() * usize::try_from(repetitions).unwrap()) + .map(|idx| { + match &mut instructions[idx] { + CountedInstruction::Counter(offset, increment_by) => { + let current = *offset; + *offset += *increment_by; + Instruction::I32Const(current as i32) + }, + CountedInstruction::Regular(instruction) => instruction.clone(), + } + }) + .chain(sp_std::iter::once(Instruction::End)) + .collect(); + FuncBody::new(Vec::new(), Instructions::new(body)) + } +} + +/// The maximum amount of pages any contract is allowed to have according to the current `Schedule`. +pub fn max_pages() -> u32 { + Contracts::::current_schedule().max_memory_pages +} diff --git a/frame/contracts/src/benchmarking.rs b/frame/contracts/src/benchmarking/mod.rs similarity index 81% rename from frame/contracts/src/benchmarking.rs rename to frame/contracts/src/benchmarking/mod.rs index e4e45214e1c4e..3107b0d25f931 100644 --- a/frame/contracts/src/benchmarking.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -19,26 +19,25 @@ #![cfg(feature = "runtime-benchmarks")] +mod code; + use crate::*; use crate::Module as Contracts; use crate::exec::StorageKey; use crate::schedule::API_BENCHMARK_BATCH_SIZE; +use self::code::{ + body, ModuleDefinition, DataSegment, ImportedMemory, ImportedFunction, WasmModule, +}; use frame_benchmarking::{benchmarks, account, whitelisted_caller}; use frame_system::{Module as System, RawOrigin}; -use parity_wasm::elements::{Instruction, Instructions, FuncBody, ValueType, BlockType}; -use sp_runtime::traits::{Hash, Bounded, SaturatedConversion, CheckedDiv}; -use sp_std::{default::Default, convert::{TryFrom, TryInto}}; +use parity_wasm::elements::{Instruction, ValueType, BlockType}; +use sp_runtime::traits::{Hash, Bounded}; +use sp_std::{default::Default, convert::{TryInto}}; /// How many batches we do per API benchmark. const API_BENCHMARK_BATCHES: u32 = 20; -#[derive(Clone)] -struct WasmModule { - code: Vec, - hash: ::Output, -} - struct Contract { caller: T::AccountId, account_id: T::AccountId, @@ -52,221 +51,6 @@ struct Tombstone { storage: Vec<(StorageKey, Vec)>, } -struct ModuleDefinition { - data_segments: Vec, - memory: Option, - imported_functions: Vec, - deploy_body: Option, - call_body: Option, -} - -impl Default for ModuleDefinition { - fn default() -> Self { - Self { - data_segments: vec![], - memory: None, - imported_functions: vec![], - deploy_body: None, - call_body: None, - } - } -} - -struct ImportedFunction { - name: &'static str, - params: Vec, - return_type: Option, -} - -struct ImportedMemory { - min_pages: u32, - max_pages: u32, -} - -impl ImportedMemory { - fn max() -> Self { - let pages = max_pages::(); - Self { min_pages: pages, max_pages: pages } - } -} - -struct DataSegment { - offset: u32, - value: Vec, -} - -enum CountedInstruction { - // (offset, increment_by) - Counter(u32, u32), - Regular(Instruction), -} - -fn create_code(def: ModuleDefinition) -> WasmModule { - // internal functions start at that offset. - let func_offset = u32::try_from(def.imported_functions.len()).unwrap(); - - // Every contract must export "deploy" and "call" functions - let mut contract = parity_wasm::builder::module() - // deploy function (first internal function) - .function() - .signature().with_params(vec![]).with_return_type(None).build() - .with_body(def.deploy_body.unwrap_or_else(|| - FuncBody::new(Vec::new(), Instructions::empty()) - )) - .build() - // call function (second internal function) - .function() - .signature().with_params(vec![]).with_return_type(None).build() - .with_body(def.call_body.unwrap_or_else(|| - FuncBody::new(Vec::new(), Instructions::empty()) - )) - .build() - .export().field("deploy").internal().func(func_offset).build() - .export().field("call").internal().func(func_offset + 1).build(); - - // Grant access to linear memory. - if let Some(memory) = def.memory { - contract = contract.import() - .module("env").field("memory") - .external().memory(memory.min_pages, Some(memory.max_pages)) - .build(); - } - - // Import supervisor functions. They start with idx 0. - for func in def.imported_functions { - let sig = parity_wasm::builder::signature() - .with_params(func.params) - .with_return_type(func.return_type) - .build_sig(); - let sig = contract.push_signature(sig); - contract = contract.import() - .module("seal0") - .field(func.name) - .with_external(parity_wasm::elements::External::Function(sig)) - .build(); - } - - // Initialize memory - for data in def.data_segments { - contract = contract.data() - .offset(Instruction::I32Const(data.offset as i32)) - .value(data.value) - .build() - } - - let code = contract.build().to_bytes().unwrap(); - let hash = T::Hashing::hash(&code); - WasmModule { - code, - hash - } -} - -fn body(instructions: Vec) -> FuncBody { - FuncBody::new(Vec::new(), Instructions::new(instructions)) -} - -fn body_repeated(repetitions: u32, instructions: &[Instruction]) -> FuncBody { - let instructions = Instructions::new( - instructions - .iter() - .cycle() - .take(instructions.len() * usize::try_from(repetitions).unwrap()) - .cloned() - .chain(sp_std::iter::once(Instruction::End)) - .collect() - ); - FuncBody::new(Vec::new(), instructions) -} - -fn body_counted(repetitions: u32, mut instructions: Vec) -> FuncBody { - // We need to iterate over indices because we cannot cycle over mutable references - let body = (0..instructions.len()) - .cycle() - .take(instructions.len() * usize::try_from(repetitions).unwrap()) - .map(|idx| { - match &mut instructions[idx] { - CountedInstruction::Counter(offset, increment_by) => { - let current = *offset; - *offset += *increment_by; - Instruction::I32Const(current as i32) - }, - CountedInstruction::Regular(instruction) => instruction.clone(), - } - }) - .chain(sp_std::iter::once(Instruction::End)) - .collect(); - FuncBody::new(Vec::new(), Instructions::new(body)) -} - -fn dummy_code() -> WasmModule { - create_code::(Default::default()) -} - -fn sized_code(target_bytes: u32) -> WasmModule { - use parity_wasm::elements::Instruction::{If, I32Const, Return, End}; - // Base size of a contract is 47 bytes and each expansion adds 6 bytes. - // We do one expansion less to account for the code section and function body - // size fields inside the binary wasm module representation which are leb128 encoded - // and therefore grow in size when the contract grows. We are not allowed to overshoot - // because of the maximum code size that is enforced by `put_code`. - let expansions = (target_bytes.saturating_sub(47) / 6).saturating_sub(1); - const EXPANSION: [Instruction; 4] = [ - I32Const(0), - If(BlockType::NoResult), - Return, - End, - ]; - create_code::(ModuleDefinition { - call_body: Some(body_repeated(expansions, &EXPANSION)), - .. Default::default() - }) -} - -fn getter_code(getter_name: &'static str, repeat: u32) -> WasmModule { - let pages = max_pages::(); - create_code::(ModuleDefinition { - memory: Some(ImportedMemory::max::()), - imported_functions: vec![ImportedFunction { - name: getter_name, - params: vec![ValueType::I32, ValueType::I32], - return_type: None, - }], - // Write the output buffer size. The output size will be overwritten by the - // supervisor with the real size when calling the getter. Since this size does not - // change between calls it suffices to start with an initial value and then just - // leave as whatever value was written there. - data_segments: vec![DataSegment { - offset: 0, - value: (pages * 64 * 1024 - 4).to_le_bytes().to_vec(), - }], - call_body: Some(body_repeated(repeat, &[ - Instruction::I32Const(4), // ptr where to store output - Instruction::I32Const(0), // ptr to length - Instruction::Call(0), // call the imported function - ])), - .. Default::default() - }) -} - -fn hasher_code(name: &'static str, repeat: u32, data_size: u32) -> WasmModule { - create_code::(ModuleDefinition { - memory: Some(ImportedMemory::max::()), - imported_functions: vec![ImportedFunction { - name: name, - params: vec![ValueType::I32, ValueType::I32, ValueType::I32], - return_type: None, - }], - call_body: Some(body_repeated(repeat, &[ - Instruction::I32Const(0), // input_ptr - Instruction::I32Const(data_size as i32), // input_len - Instruction::I32Const(0), // output_ptr - Instruction::Call(0), - ])), - .. Default::default() - }) -} - enum Endow { Max, CollectRent, @@ -279,6 +63,7 @@ fn instantiate_contract_from_account( endowment: Endow, ) -> Result, &'static str> { + use sp_runtime::traits::{CheckedDiv, SaturatedConversion}; let (storage_size, endowment) = match endowment { Endow::CollectRent => { // storage_size cannot be zero because otherwise a contract that is just above @@ -368,7 +153,7 @@ fn create_storage( } fn create_tombstone(stor_num: u32, stor_size: u32) -> Result, &'static str> { - let contract = instantiate_contract::(dummy_code(), vec![], Endow::CollectRent)?; + let contract = instantiate_contract::(WasmModule::dummy(), vec![], Endow::CollectRent)?; let storage_items = create_storage::(stor_num, stor_size)?; store_items::(&contract.account_id, &storage_items)?; System::::set_block_number( @@ -398,10 +183,6 @@ fn ensure_tombstone(addr: &T::AccountId) -> Result<(), &'static str> { .map(|_| ()) } -fn max_pages() -> u32 { - Contracts::::current_schedule().max_memory_pages -} - fn funding() -> BalanceOf { BalanceOf::::max_value() / 2.into() } @@ -447,7 +228,7 @@ benchmarks! { let n in 0 .. Contracts::::current_schedule().max_code_size / 1024; let caller = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, funding::()); - let module = sized_code::(n * 1024); + let module = WasmModule::::sized(n * 1024); let origin = RawOrigin::Signed(caller); }: _(origin, module.code) @@ -456,12 +237,12 @@ benchmarks! { // the contract address. // `n`: Size of the data passed to constructor in kilobytes. instantiate { - let n in 0 .. max_pages::() * 64; + let n in 0 .. code::max_pages::() * 64; let data = vec![42u8; (n * 1024) as usize]; let endowment = Config::::subsistence_threshold_uncached(); let caller = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, funding::()); - let WasmModule { code, hash } = dummy_code::(); + let WasmModule { code, hash } = WasmModule::::dummy(); let origin = RawOrigin::Signed(caller.clone()); let addr = T::DetermineContractAddress::contract_address_for(&hash, &data, &caller); Contracts::::put_code_raw(code)?; @@ -483,7 +264,7 @@ benchmarks! { call { let data = vec![42u8; 1024]; let instance = instantiate_contract_from_account::( - whitelisted_caller(), dummy_code(), vec![], Endow::CollectRent + whitelisted_caller(), WasmModule::dummy(), vec![], Endow::CollectRent )?; let value = T::Currency::minimum_balance() * 100.into(); let origin = RawOrigin::Signed(instance.caller.clone()); @@ -512,7 +293,7 @@ benchmarks! { // the reward for removing them. claim_surcharge { let instance = instantiate_contract_from_account::( - whitelisted_caller(), dummy_code(), vec![], Endow::CollectRent + whitelisted_caller(), WasmModule::dummy(), vec![], Endow::CollectRent )?; let origin = RawOrigin::Signed(instance.caller.clone()); let account_id = instance.account_id.clone(); @@ -538,7 +319,7 @@ benchmarks! { seal_caller { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(getter_code( + let instance = instantiate_contract::(WasmModule::getter( "seal_caller", r * API_BENCHMARK_BATCH_SIZE ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -546,7 +327,7 @@ benchmarks! { seal_address { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(getter_code( + let instance = instantiate_contract::(WasmModule::getter( "seal_address", r * API_BENCHMARK_BATCH_SIZE ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -554,7 +335,7 @@ benchmarks! { seal_gas_left { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(getter_code( + let instance = instantiate_contract::(WasmModule::getter( "seal_gas_left", r * API_BENCHMARK_BATCH_SIZE ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -562,7 +343,7 @@ benchmarks! { seal_balance { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(getter_code( + let instance = instantiate_contract::(WasmModule::getter( "seal_balance", r * API_BENCHMARK_BATCH_SIZE ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -570,7 +351,7 @@ benchmarks! { seal_value_transferred { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(getter_code( + let instance = instantiate_contract::(WasmModule::getter( "seal_value_transferred", r * API_BENCHMARK_BATCH_SIZE ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -578,7 +359,7 @@ benchmarks! { seal_minimum_balance { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(getter_code( + let instance = instantiate_contract::(WasmModule::getter( "seal_minimum_balance", r * API_BENCHMARK_BATCH_SIZE ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -586,7 +367,7 @@ benchmarks! { seal_tombstone_deposit { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(getter_code( + let instance = instantiate_contract::(WasmModule::getter( "seal_tombstone_deposit", r * API_BENCHMARK_BATCH_SIZE ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -594,7 +375,7 @@ benchmarks! { seal_rent_allowance { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(getter_code( + let instance = instantiate_contract::(WasmModule::getter( "seal_rent_allowance", r * API_BENCHMARK_BATCH_SIZE ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -602,7 +383,7 @@ benchmarks! { seal_block_number { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(getter_code( + let instance = instantiate_contract::(WasmModule::getter( "seal_block_number", r * API_BENCHMARK_BATCH_SIZE ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -610,7 +391,7 @@ benchmarks! { seal_now { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(getter_code( + let instance = instantiate_contract::(WasmModule::getter( "seal_now", r * API_BENCHMARK_BATCH_SIZE ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -618,8 +399,8 @@ benchmarks! { seal_weight_to_fee { let r in 0 .. API_BENCHMARK_BATCHES; - let pages = max_pages::(); - let code = create_code::(ModuleDefinition { + let pages = code::max_pages::(); + let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { name: "seal_weight_to_fee", @@ -630,7 +411,7 @@ benchmarks! { offset: 0, value: (pages * 64 * 1024 - 4).to_le_bytes().to_vec(), }], - call_body: Some(body_repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ Instruction::I64Const(500_000), Instruction::I32Const(4), Instruction::I32Const(0), @@ -644,13 +425,13 @@ benchmarks! { seal_gas { let r in 0 .. API_BENCHMARK_BATCHES; - let code = create_code(ModuleDefinition { + let code = WasmModule::::from(ModuleDefinition { imported_functions: vec![ImportedFunction { name: "gas", params: vec![ValueType::I32], return_type: None, }], - call_body: Some(body_repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ Instruction::I32Const(42), Instruction::Call(0), ])), @@ -666,7 +447,7 @@ benchmarks! { // contract it cannot be used for Dos. seal_input { let r in 0 .. 1; - let code = create_code::(ModuleDefinition { + let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { name: "seal_input", @@ -679,7 +460,7 @@ benchmarks! { value: 0u32.to_le_bytes().to_vec(), }, ], - call_body: Some(body_repeated(r, &[ + call_body: Some(body::repeated(r, &[ Instruction::I32Const(4), // ptr where to store output Instruction::I32Const(0), // ptr to length Instruction::Call(0), @@ -691,10 +472,10 @@ benchmarks! { }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) seal_input_per_kb { - let n in 0 .. max_pages::() * 64; - let pages = max_pages::(); + let n in 0 .. code::max_pages::() * 64; + let pages = code::max_pages::(); let buffer_size = pages * 64 * 1024 - 4; - let code = create_code::(ModuleDefinition { + let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { name: "seal_input", @@ -707,7 +488,7 @@ benchmarks! { value: buffer_size.to_le_bytes().to_vec(), }, ], - call_body: Some(body(vec![ + call_body: Some(body::plain(vec![ Instruction::I32Const(4), // ptr where to store output Instruction::I32Const(0), // ptr to length Instruction::Call(0), @@ -723,14 +504,14 @@ benchmarks! { // The same argument as for `seal_input` is true here. seal_return { let r in 0 .. 1; - let code = create_code::(ModuleDefinition { + let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { name: "seal_return", params: vec![ValueType::I32, ValueType::I32, ValueType::I32], return_type: None, }], - call_body: Some(body_repeated(r, &[ + call_body: Some(body::repeated(r, &[ Instruction::I32Const(0), // flags Instruction::I32Const(0), // data_ptr Instruction::I32Const(0), // data_len @@ -743,15 +524,15 @@ benchmarks! { }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) seal_return_per_kb { - let n in 0 .. max_pages::() * 64; - let code = create_code::(ModuleDefinition { + let n in 0 .. code::max_pages::() * 64; + let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { name: "seal_return", params: vec![ValueType::I32, ValueType::I32, ValueType::I32], return_type: None, }], - call_body: Some(body(vec![ + call_body: Some(body::plain(vec![ Instruction::I32Const(0), // flags Instruction::I32Const(0), // data_ptr Instruction::I32Const((n * 1024) as i32), // data_len @@ -770,7 +551,7 @@ benchmarks! { let beneficiary = account::("beneficiary", 0, 0); let beneficiary_bytes = beneficiary.encode(); let beneficiary_len = beneficiary_bytes.len(); - let code = create_code::(ModuleDefinition { + let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { name: "seal_terminate", @@ -783,7 +564,7 @@ benchmarks! { value: beneficiary_bytes, }, ], - call_body: Some(body_repeated(r, &[ + call_body: Some(body::repeated(r, &[ Instruction::I32Const(0), // beneficiary_ptr Instruction::I32Const(beneficiary_len as i32), // beneficiary_len Instruction::Call(0), @@ -821,7 +602,7 @@ benchmarks! { let code_hash_offset = dest_offset + dest_len; let rent_allowance_offset = code_hash_offset + code_hash_len; - let code = create_code::(ModuleDefinition { + let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { name: "seal_restore_to", @@ -851,7 +632,7 @@ benchmarks! { value: rent_allowance, }, ], - call_body: Some(body_repeated(r, &[ + call_body: Some(body::repeated(r, &[ Instruction::I32Const(dest_offset as i32), Instruction::I32Const(dest_len as i32), Instruction::I32Const(code_hash_offset as i32), @@ -897,7 +678,7 @@ benchmarks! { let rent_allowance_offset = code_hash_offset + code_hash_len; let delta_keys_offset = rent_allowance_offset + rent_allowance_len; - let code = create_code::(ModuleDefinition { + let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { name: "seal_restore_to", @@ -931,7 +712,7 @@ benchmarks! { value: delta_keys, }, ], - call_body: Some(body(vec![ + call_body: Some(body::plain(vec![ Instruction::I32Const(dest_offset as i32), Instruction::I32Const(dest_len as i32), Instruction::I32Const(code_hash_offset as i32), @@ -964,10 +745,10 @@ benchmarks! { // used. seal_random { let r in 0 .. API_BENCHMARK_BATCHES; - let pages = max_pages::(); + let pages = code::max_pages::(); let subject_len = Contracts::::current_schedule().max_subject_len; assert!(subject_len < 1024); - let code = create_code::(ModuleDefinition { + let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { name: "seal_random", @@ -980,7 +761,7 @@ benchmarks! { value: (pages * 64 * 1024 - subject_len - 4).to_le_bytes().to_vec(), }, ], - call_body: Some(body_repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ Instruction::I32Const(4), // subject_ptr Instruction::I32Const(subject_len as i32), // subject_len Instruction::I32Const((subject_len + 4) as i32), // out_ptr @@ -997,14 +778,14 @@ benchmarks! { // We benchmark for the worst case (largest event). seal_deposit_event { let r in 0 .. API_BENCHMARK_BATCHES; - let code = create_code::(ModuleDefinition { + let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { name: "seal_deposit_event", params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], return_type: None, }], - call_body: Some(body_repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ Instruction::I32Const(0), // topics_ptr Instruction::I32Const(0), // topics_len Instruction::I32Const(0), // data_ptr @@ -1028,8 +809,8 @@ benchmarks! { .peekable(); let topics_len = topics.peek().map(|i| i.len()).unwrap_or(0); let topics = topics.flatten().collect(); - use CountedInstruction::{Counter, Regular}; - let code = create_code::(ModuleDefinition { + use body::CountedInstruction::{Counter, Regular}; + let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { name: "seal_deposit_event", @@ -1042,7 +823,7 @@ benchmarks! { value: topics, }, ], - call_body: Some(body_counted(API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::counted(API_BENCHMARK_BATCH_SIZE, vec![ Counter(0, topics_len as u32), // topics_ptr Regular(Instruction::I32Const(topics_len as i32)), // topics_len Regular(Instruction::I32Const(0)), // data_ptr @@ -1059,7 +840,7 @@ benchmarks! { let r in 0 .. API_BENCHMARK_BATCHES; let allowance = funding::().encode(); let allowance_len = allowance.len(); - let code = create_code::(ModuleDefinition { + let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory { min_pages: 1, max_pages: 1 }), imported_functions: vec![ImportedFunction { name: "seal_set_rent_allowance", @@ -1072,7 +853,7 @@ benchmarks! { value: allowance, }, ], - call_body: Some(body_repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ Instruction::I32Const(0), // value_ptr Instruction::I32Const(allowance_len as i32), // value_len Instruction::Call(0), @@ -1093,8 +874,8 @@ benchmarks! { .flat_map(|n| T::Hashing::hash_of(&n).as_ref().to_vec()) .collect::>(); let key_len = sp_std::mem::size_of::<::Output>(); - use CountedInstruction::{Counter, Regular}; - let code = create_code::(ModuleDefinition { + use body::CountedInstruction::{Counter, Regular}; + let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { name: "seal_set_storage", @@ -1107,7 +888,7 @@ benchmarks! { value: keys, }, ], - call_body: Some(body_counted(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::counted(r * API_BENCHMARK_BATCH_SIZE, vec![ Counter(0, key_len as u32), // key_ptr Regular(Instruction::I32Const(0)), // value_ptr Regular(Instruction::I32Const(0)), // value_len @@ -1123,7 +904,7 @@ benchmarks! { let n in 0 .. T::MaxValueSize::get() / 1024; let key = T::Hashing::hash_of(&1u32).as_ref().to_vec(); let key_len = key.len(); - let code = create_code::(ModuleDefinition { + let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { name: "seal_set_storage", @@ -1136,7 +917,7 @@ benchmarks! { value: key, }, ], - call_body: Some(body_repeated(API_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::repeated(API_BENCHMARK_BATCH_SIZE, &[ Instruction::I32Const(0), // key_ptr Instruction::I32Const(0), // value_ptr Instruction::I32Const((n * 1024) as i32), // value_len @@ -1158,8 +939,8 @@ benchmarks! { .collect::>(); let key_bytes = keys.iter().flatten().cloned().collect::>(); let key_len = sp_std::mem::size_of::<::Output>(); - use CountedInstruction::{Counter, Regular}; - let code = create_code::(ModuleDefinition { + use body::CountedInstruction::{Counter, Regular}; + let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { name: "seal_clear_storage", @@ -1172,7 +953,7 @@ benchmarks! { value: key_bytes, }, ], - call_body: Some(body_counted(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::counted(r * API_BENCHMARK_BATCH_SIZE, vec![ Counter(0, key_len as u32), Regular(Instruction::Call(0)), ])), @@ -1201,8 +982,8 @@ benchmarks! { let key_len = sp_std::mem::size_of::<::Output>(); let key_bytes = keys.iter().flatten().cloned().collect::>(); let key_bytes_len = key_bytes.len(); - use CountedInstruction::{Counter, Regular}; - let code = create_code::(ModuleDefinition { + use body::CountedInstruction::{Counter, Regular}; + let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { name: "seal_get_storage", @@ -1215,7 +996,7 @@ benchmarks! { value: key_bytes, }, ], - call_body: Some(body_counted(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::counted(r * API_BENCHMARK_BATCH_SIZE, vec![ Counter(0, key_len as u32), // key_ptr Regular(Instruction::I32Const((key_bytes_len + 4) as i32)), // out_ptr Regular(Instruction::I32Const(key_bytes_len as i32)), // out_len_ptr @@ -1242,7 +1023,7 @@ benchmarks! { let n in 0 .. T::MaxValueSize::get() / 1024; let key = T::Hashing::hash_of(&1u32).as_ref().to_vec(); let key_len = key.len(); - let code = create_code::(ModuleDefinition { + let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { name: "seal_get_storage", @@ -1259,7 +1040,7 @@ benchmarks! { value: T::MaxValueSize::get().to_le_bytes().into(), }, ], - call_body: Some(body_repeated(API_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::repeated(API_BENCHMARK_BATCH_SIZE, &[ // call at key_ptr Instruction::I32Const(0), // key_ptr Instruction::I32Const((key_len + 4) as i32), // out_ptr @@ -1293,8 +1074,8 @@ benchmarks! { assert!(value > 0.into()); let value_bytes = value.encode(); let value_len = value_bytes.len(); - use CountedInstruction::{Counter, Regular}; - let code = create_code::(ModuleDefinition { + use body::CountedInstruction::{Counter, Regular}; + let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { name: "seal_transfer", @@ -1311,7 +1092,7 @@ benchmarks! { value: account_bytes, }, ], - call_body: Some(body_counted(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::counted(r * API_BENCHMARK_BATCH_SIZE, vec![ Counter(value_len as u32, account_len as u32), // account_ptr Regular(Instruction::I32Const(account_len as i32)), // account_len Regular(Instruction::I32Const(0)), // value_ptr @@ -1336,7 +1117,7 @@ benchmarks! { // We call unique accounts. seal_call { let r in 0 .. API_BENCHMARK_BATCHES; - let dummy_code = dummy_code::(); + let dummy_code = WasmModule::::dummy(); let callees = (0..r * API_BENCHMARK_BATCH_SIZE) .map(|i| instantiate_contract_from_index(i + 1, dummy_code.clone(), vec![], Endow::Max)) .collect::, _>>()?; @@ -1345,8 +1126,8 @@ benchmarks! { let value: BalanceOf = 0.into(); let value_bytes = value.encode(); let value_len = value_bytes.len(); - use CountedInstruction::{Counter, Regular}; - let code = create_code::(ModuleDefinition { + use body::CountedInstruction::{Counter, Regular}; + let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { name: "seal_call", @@ -1373,7 +1154,7 @@ benchmarks! { value: callee_bytes, }, ], - call_body: Some(body_counted(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::counted(r * API_BENCHMARK_BATCH_SIZE, vec![ Counter(value_len as u32, callee_len as u32), // callee_ptr Regular(Instruction::I32Const(callee_len as i32)), // callee_len Regular(Instruction::I64Const(0)), // gas @@ -1394,9 +1175,9 @@ benchmarks! { seal_call_per_transfer_input_output_kb { let t in 0 .. 1; - let i in 0 .. max_pages::() * 64; - let o in 0 .. (max_pages::() - 1) * 64; - let callee_code = create_code::(ModuleDefinition { + let i in 0 .. code::max_pages::() * 64; + let o in 0 .. (code::max_pages::() - 1) * 64; + let callee_code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { name: "seal_return", @@ -1407,7 +1188,7 @@ benchmarks! { ], return_type: None, }], - call_body: Some(body(vec![ + call_body: Some(body::plain(vec![ Instruction::I32Const(0), // flags Instruction::I32Const(0), // data_ptr Instruction::I32Const((o * 1024) as i32), // data_len @@ -1425,8 +1206,8 @@ benchmarks! { let value: BalanceOf = t.into(); let value_bytes = value.encode(); let value_len = value_bytes.len(); - use CountedInstruction::{Counter, Regular}; - let code = create_code::(ModuleDefinition { + use body::CountedInstruction::{Counter, Regular}; + let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { name: "seal_call", @@ -1457,7 +1238,7 @@ benchmarks! { value: (o * 1024).to_le_bytes().into(), }, ], - call_body: Some(body_counted(API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::counted(API_BENCHMARK_BATCH_SIZE, vec![ Counter(value_len as u32, callee_len as u32), // callee_ptr Regular(Instruction::I32Const(callee_len as i32)), // callee_len Regular(Instruction::I64Const(0)), // gas @@ -1481,8 +1262,8 @@ benchmarks! { let r in 0 .. API_BENCHMARK_BATCHES; let hashes = (0..r * API_BENCHMARK_BATCH_SIZE) .map(|i| { - let code = create_code::(ModuleDefinition { - call_body: Some(body(vec![ + let code = WasmModule::::from(ModuleDefinition { + call_body: Some(body::plain(vec![ Instruction::I32Const(i as i32), Instruction::Drop, Instruction::End, @@ -1508,8 +1289,8 @@ benchmarks! { let addr_len_offset = hashes_offset + hashes_len; let addr_offset = addr_len_offset + addr_len; - use CountedInstruction::{Counter, Regular}; - let code = create_code::(ModuleDefinition { + use body::CountedInstruction::{Counter, Regular}; + let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { name: "seal_instantiate", @@ -1542,7 +1323,7 @@ benchmarks! { value: addr_len.to_le_bytes().into(), }, ], - call_body: Some(body_counted(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::counted(r * API_BENCHMARK_BATCH_SIZE, vec![ Counter(hashes_offset as u32, hash_len as u32), // code_hash_ptr Regular(Instruction::I32Const(hash_len as i32)), // code_hash_len Regular(Instruction::I64Const(0)), // gas @@ -1581,9 +1362,9 @@ benchmarks! { } seal_instantiate_per_input_output_kb { - let i in 0 .. (max_pages::() - 1) * 64; - let o in 0 .. (max_pages::() - 1) * 64; - let callee_code = create_code::(ModuleDefinition { + let i in 0 .. (code::max_pages::() - 1) * 64; + let o in 0 .. (code::max_pages::() - 1) * 64; + let callee_code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { name: "seal_return", @@ -1594,7 +1375,7 @@ benchmarks! { ], return_type: None, }], - deploy_body: Some(body(vec![ + deploy_body: Some(body::plain(vec![ Instruction::I32Const(0), // flags Instruction::I32Const(0), // data_ptr Instruction::I32Const((o * 1024) as i32), // data_len @@ -1625,8 +1406,8 @@ benchmarks! { let output_len_offset = addr_len_offset + 4; let output_offset = output_len_offset + 4; - use CountedInstruction::{Counter, Regular}; - let code = create_code::(ModuleDefinition { + use body::CountedInstruction::{Counter, Regular}; + let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { name: "seal_instantiate", @@ -1667,7 +1448,7 @@ benchmarks! { value: (o * 1024).to_le_bytes().into(), }, ], - call_body: Some(body_counted(API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::counted(API_BENCHMARK_BATCH_SIZE, vec![ Regular(Instruction::I32Const(hash_offset as i32)), // code_hash_ptr Regular(Instruction::I32Const(hash_len as i32)), // code_hash_len Regular(Instruction::I64Const(0)), // gas @@ -1696,7 +1477,7 @@ benchmarks! { // Only the overhead of calling the function itself with minimal arguments. seal_hash_sha2_256 { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(hasher_code( + let instance = instantiate_contract::(WasmModule::hasher( "seal_hash_sha2_256", r * API_BENCHMARK_BATCH_SIZE, 0, ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -1704,8 +1485,8 @@ benchmarks! { // `n`: Input to hash in kilobytes seal_hash_sha2_256_per_kb { - let n in 0 .. max_pages::() * 64; - let instance = instantiate_contract::(hasher_code( + let n in 0 .. code::max_pages::() * 64; + let instance = instantiate_contract::(WasmModule::hasher( "seal_hash_sha2_256", API_BENCHMARK_BATCH_SIZE, n * 1024, ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -1714,7 +1495,7 @@ benchmarks! { // Only the overhead of calling the function itself with minimal arguments. seal_hash_keccak_256 { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(hasher_code( + let instance = instantiate_contract::(WasmModule::hasher( "seal_hash_keccak_256", r * API_BENCHMARK_BATCH_SIZE, 0, ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -1722,8 +1503,8 @@ benchmarks! { // `n`: Input to hash in kilobytes seal_hash_keccak_256_per_kb { - let n in 0 .. max_pages::() * 64; - let instance = instantiate_contract::(hasher_code( + let n in 0 .. code::max_pages::() * 64; + let instance = instantiate_contract::(WasmModule::hasher( "seal_hash_keccak_256", API_BENCHMARK_BATCH_SIZE, n * 1024, ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -1732,7 +1513,7 @@ benchmarks! { // Only the overhead of calling the function itself with minimal arguments. seal_hash_blake2_256 { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(hasher_code( + let instance = instantiate_contract::(WasmModule::hasher( "seal_hash_blake2_256", r * API_BENCHMARK_BATCH_SIZE, 0, ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -1740,8 +1521,8 @@ benchmarks! { // `n`: Input to hash in kilobytes seal_hash_blake2_256_per_kb { - let n in 0 .. max_pages::() * 64; - let instance = instantiate_contract::(hasher_code( + let n in 0 .. code::max_pages::() * 64; + let instance = instantiate_contract::(WasmModule::hasher( "seal_hash_blake2_256", API_BENCHMARK_BATCH_SIZE, n * 1024, ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -1750,7 +1531,7 @@ benchmarks! { // Only the overhead of calling the function itself with minimal arguments. seal_hash_blake2_128 { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(hasher_code( + let instance = instantiate_contract::(WasmModule::hasher( "seal_hash_blake2_128", r * API_BENCHMARK_BATCH_SIZE, 0, ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -1758,8 +1539,8 @@ benchmarks! { // `n`: Input to hash in kilobytes seal_hash_blake2_128_per_kb { - let n in 0 .. max_pages::() * 64; - let instance = instantiate_contract::(hasher_code( + let n in 0 .. code::max_pages::() * 64; + let instance = instantiate_contract::(WasmModule::hasher( "seal_hash_blake2_128", API_BENCHMARK_BATCH_SIZE, n * 1024, ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); From 687c2a5a113d3e6374491c15896f2f01e3d0f9e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Thu, 24 Sep 2020 18:00:43 +0200 Subject: [PATCH 20/29] Review: Refactor and document benchmark helper functions --- frame/contracts/src/benchmarking/mod.rs | 423 +++++++++++++----------- 1 file changed, 230 insertions(+), 193 deletions(-) diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index 3107b0d25f931..e135ac94afd33 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -38,6 +38,7 @@ use sp_std::{default::Default, convert::{TryInto}}; /// How many batches we do per API benchmark. const API_BENCHMARK_BATCHES: u32 = 20; +/// An instantiated and deployed contract. struct Contract { caller: T::AccountId, account_id: T::AccountId, @@ -46,99 +47,174 @@ struct Contract { code_hash: ::Output, } -struct Tombstone { - contract: Contract, - storage: Vec<(StorageKey, Vec)>, -} - +/// Describes how much balance should be transferred on instantiate from the caller. enum Endow { + /// Endow the contract with a maximum amount of balance. This value is described by + /// `Contract::max_endowment`. Max, + /// Endow so that the amount of balance that is transferred is big but not so big + /// to offset the rent payment. This is needed in order to test rent collection. CollectRent, } -fn instantiate_contract_from_account( - caller: T::AccountId, - module: WasmModule, - data: Vec, - endowment: Endow, -) -> Result, &'static str> -{ - use sp_runtime::traits::{CheckedDiv, SaturatedConversion}; - let (storage_size, endowment) = match endowment { - Endow::CollectRent => { - // storage_size cannot be zero because otherwise a contract that is just above - // the subsistence threshold does not pay rent given a large enough subsistence - // threshold. But we need rent payments to occur in order to benchmark for worst cases. - let storage_size = Config::::subsistence_threshold_uncached() - .checked_div(&T::RentDepositOffset::get()) - .unwrap_or_else(Zero::zero); - - // Endowment should be large but not as large to inhibit rent payments. - let endowment = T::RentDepositOffset::get() - .saturating_mul(storage_size + T::StorageSizeOffset::get().into()) - .saturating_sub(1.into()); - - (storage_size, endowment) - }, - Endow::Max => (0.into(), max_endowment::()), - }; - T::Currency::make_free_balance_be(&caller, funding::()); - let addr = T::DetermineContractAddress::contract_address_for(&module.hash, &data, &caller); - init_block_number::(); - Contracts::::put_code_raw(module.code)?; - Contracts::::instantiate( - RawOrigin::Signed(caller.clone()).into(), - endowment, - Weight::max_value(), - module.hash, - data, - )?; - let mut contract = get_alive::(&addr)?; - contract.storage_size = storage_size.saturated_into::(); - ContractInfoOf::::insert(&addr, ContractInfo::Alive(contract)); - Ok(Contract { - caller, - account_id: addr.clone(), - addr: T::Lookup::unlookup(addr), - endowment, - code_hash: module.hash.clone(), - }) +impl Endow { + /// The maximum amount of balance a caller can transfer without being brought below + /// the existential deposit. This assumes that every caller is funded with the amount + /// returned by `caller_funding`. + fn max() -> BalanceOf { + caller_funding::().saturating_sub(T::Currency::minimum_balance()) + } } -fn instantiate_contract_from_index( - index: u32, - module: WasmModule, - data: Vec, - endowment: Endow, -) -> Result, &'static str> { - instantiate_contract_from_account(account("instantiator", index, 0), module, data, endowment) +impl Contract { + /// Create new contract and use a default account id as instantiator. + fn new( + module: WasmModule, + data: Vec, + endowment: Endow, + ) -> Result, &'static str> { + Self::with_index(0, module, data, endowment) + } + + /// Create new contract and use an account id derived from the supplied index as instantiator. + fn with_index( + index: u32, + module: WasmModule, + data: Vec, + endowment: Endow, + ) -> Result, &'static str> { + Self::with_caller(account("instantiator", index, 0), module, data, endowment) + } + + /// Create new contract and use the supplied `caller` as instantiator. + fn with_caller( + caller: T::AccountId, + module: WasmModule, + data: Vec, + endowment: Endow, + ) -> Result, &'static str> + { + use sp_runtime::traits::{CheckedDiv, SaturatedConversion}; + let (storage_size, endowment) = match endowment { + Endow::CollectRent => { + // storage_size cannot be zero because otherwise a contract that is just above + // the subsistence threshold does not pay rent given a large enough subsistence + // threshold. But we need rent payments to occur in order to benchmark for worst cases. + let storage_size = Config::::subsistence_threshold_uncached() + .checked_div(&T::RentDepositOffset::get()) + .unwrap_or_else(Zero::zero); + + // Endowment should be large but not as large to inhibit rent payments. + let endowment = T::RentDepositOffset::get() + .saturating_mul(storage_size + T::StorageSizeOffset::get().into()) + .saturating_sub(1.into()); + + (storage_size, endowment) + }, + Endow::Max => (0.into(), Endow::max::()), + }; + T::Currency::make_free_balance_be(&caller, caller_funding::()); + let addr = T::DetermineContractAddress::contract_address_for(&module.hash, &data, &caller); + init_block_number::(); + Contracts::::put_code_raw(module.code)?; + Contracts::::instantiate( + RawOrigin::Signed(caller.clone()).into(), + endowment, + Weight::max_value(), + module.hash, + data, + )?; + + let result = Contract { + caller, + account_id: addr.clone(), + addr: T::Lookup::unlookup(addr), + endowment, + code_hash: module.hash.clone(), + }; + + let mut contract = result.alive_info()?; + contract.storage_size = storage_size.saturated_into::(); + ContractInfoOf::::insert(&result.account_id, ContractInfo::Alive(contract)); + + Ok(result) + } + + /// Store the supplied storage items into this contracts storage. + fn store(&self, items: &Vec<(StorageKey, Vec)>) -> Result<(), &'static str> { + let info = self.alive_info()?; + for item in items { + crate::storage::write_contract_storage::( + &self.account_id, + &info.trie_id, + &item.0, + Some(item.1.clone()), + ) + .map_err(|_| "Failed to write storage to restoration dest")?; + } + Ok(()) + } + + /// Get the `AliveContractInfo` of the `addr` or an error if it is no longer alive. + fn address_alive_info(addr: &T::AccountId) -> Result, &'static str> { + ContractInfoOf::::get(addr).and_then(|c| c.get_alive()) + .ok_or("Expected contract to be alive at this point.") + } + + /// Get the `AliveContractInfo` of this contract or an error if it is no longer alive. + fn alive_info(&self) -> Result, &'static str> { + Self::address_alive_info(&self.account_id) + } + + /// Return an error if this contract is no tombstone. + fn ensure_tombstone(&self) -> Result<(), &'static str> { + ContractInfoOf::::get(&self.account_id).and_then(|c| c.get_tombstone()) + .ok_or("Expected contract to be a tombstone at this point.") + .map(|_| ()) + } + + /// 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 { + let projection = crate::rent::compute_rent_projection::(&self.account_id) + .map_err(|_| "Invalid acc for rent")?; + match projection { + RentProjection::EvictionAt(at) => Ok(at), + _ => Err("Account does not pay rent.")?, + } + } } -fn instantiate_contract( - module: WasmModule, - data: Vec, - endowment: Endow, -) -> Result, &'static str> { - instantiate_contract_from_index(0, module, data, endowment) +/// A `Contract` that was evicted after accumulating some storage. +/// +/// This is used to benchmark contract resurrection. +struct Tombstone { + /// The contract that was evicted. + contract: Contract, + /// The storage the contract held when it was avicted. + storage: Vec<(StorageKey, Vec)>, } -fn store_items( - account: &T::AccountId, - items: &Vec<(StorageKey, Vec)> -) -> Result<(), &'static str> { - let info = get_alive::(account)?; - for item in items { - crate::storage::write_contract_storage::( - account, - &info.trie_id, - &item.0, - Some(item.1.clone()), - ) - .map_err(|_| "Failed to write storage to restoration dest")?; +impl Tombstone { + /// Create and evict a new contract with the supplied storage item count and size each. + fn new(stor_num: u32, stor_size: u32) -> Result { + let contract = Contract::::new(WasmModule::dummy(), vec![], Endow::CollectRent)?; + let storage_items = create_storage::(stor_num, stor_size)?; + contract.store(&storage_items)?; + System::::set_block_number( + contract.eviction_at()? + T::SignedClaimHandicap::get() + 5.into() + ); + crate::rent::collect_rent::(&contract.account_id); + contract.ensure_tombstone()?; + + Ok(Tombstone { + contract, + storage: storage_items, + }) } - Ok(()) } +/// Crate `stor_num` storage items. Each has the size `stor_size`. fn create_storage( stor_num: u32, stor_size: u32 @@ -152,51 +228,10 @@ fn create_storage( }).collect::, &'static str>>() } -fn create_tombstone(stor_num: u32, stor_size: u32) -> Result, &'static str> { - let contract = instantiate_contract::(WasmModule::dummy(), vec![], Endow::CollectRent)?; - let storage_items = create_storage::(stor_num, stor_size)?; - store_items::(&contract.account_id, &storage_items)?; - System::::set_block_number( - eviction_at::(&contract.account_id)? + T::SignedClaimHandicap::get() + 5.into() - ); - crate::rent::collect_rent::(&contract.account_id); - ensure_tombstone::(&contract.account_id)?; - - Ok(Tombstone { - contract, - storage: storage_items, - }) -} - -fn get_alive(addr: &T::AccountId) -> Result, &'static str> { - ContractInfoOf::::get(&addr).and_then(|c| c.get_alive()) - .ok_or("Expected contract to be alive at this point.") -} - -fn ensure_alive(addr: &T::AccountId) -> Result<(), &'static str> { - get_alive::(addr).map(|_| ()) -} - -fn ensure_tombstone(addr: &T::AccountId) -> Result<(), &'static str> { - ContractInfoOf::::get(&addr).and_then(|c| c.get_tombstone()) - .ok_or("Expected contract to be a tombstone at this point.") - .map(|_| ()) -} - -fn funding() -> BalanceOf { +fn caller_funding() -> BalanceOf { BalanceOf::::max_value() / 2.into() } -fn max_endowment() -> BalanceOf { - funding::().saturating_sub(T::Currency::minimum_balance()) -} - -fn eviction_at(addr: &T::AccountId) -> Result { - match crate::rent::compute_rent_projection::(addr).map_err(|_| "Invalid acc for rent")? { - RentProjection::EvictionAt(at) => Ok(at), - _ => Err("Account does not pay rent.")?, - } -} /// Set the block number to one. /// @@ -227,7 +262,7 @@ benchmarks! { put_code { let n in 0 .. Contracts::::current_schedule().max_code_size / 1024; let caller = whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, funding::()); + T::Currency::make_free_balance_be(&caller, caller_funding::()); let module = WasmModule::::sized(n * 1024); let origin = RawOrigin::Signed(caller); }: _(origin, module.code) @@ -241,7 +276,7 @@ benchmarks! { let data = vec![42u8; (n * 1024) as usize]; let endowment = Config::::subsistence_threshold_uncached(); let caller = whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, funding::()); + T::Currency::make_free_balance_be(&caller, caller_funding::()); let WasmModule { code, hash } = WasmModule::::dummy(); let origin = RawOrigin::Signed(caller.clone()); let addr = T::DetermineContractAddress::contract_address_for(&hash, &data, &caller); @@ -249,11 +284,11 @@ benchmarks! { }: _(origin, endowment, Weight::max_value(), hash, data) verify { // endowment was removed from the caller - assert_eq!(T::Currency::free_balance(&caller), funding::() - endowment); + assert_eq!(T::Currency::free_balance(&caller), caller_funding::() - endowment); // contract has the full endowment because no rent collection happended assert_eq!(T::Currency::free_balance(&addr), endowment); // instantiate should leave a alive contract - ensure_alive::(&addr)?; + Contract::::address_alive_info(&addr)?; } // We just call a dummy contract to measure to overhead of the call extrinsic. @@ -263,26 +298,27 @@ benchmarks! { // part of `seal_input`. call { let data = vec![42u8; 1024]; - let instance = instantiate_contract_from_account::( + let instance = Contract::::with_caller( whitelisted_caller(), WasmModule::dummy(), vec![], Endow::CollectRent )?; let value = T::Currency::minimum_balance() * 100.into(); let origin = RawOrigin::Signed(instance.caller.clone()); + let callee = instance.addr.clone(); // trigger rent collection for worst case performance of call - System::::set_block_number(eviction_at::(&instance.account_id)? - 5.into()); + System::::set_block_number(instance.eviction_at()? - 5.into()); let before = T::Currency::free_balance(&instance.account_id); - }: _(origin, instance.addr, value, Weight::max_value(), data) + }: _(origin, callee, value, Weight::max_value(), data) verify { // endowment and value transfered via call should be removed from the caller assert_eq!( T::Currency::free_balance(&instance.caller), - funding::() - instance.endowment - value, + caller_funding::() - instance.endowment - value, ); // rent should have lowered the amount of balance of the contract assert!(T::Currency::free_balance(&instance.account_id) < before + value); // but it should not have been evicted by the rent collection - ensure_alive::(&instance.account_id)?; + instance.alive_info()?; } // We benchmark the costs for sucessfully evicting an empty contract. @@ -292,34 +328,34 @@ benchmarks! { // no incentive to remove large contracts when the removal is more expensive than // the reward for removing them. claim_surcharge { - let instance = instantiate_contract_from_account::( + let instance = Contract::::with_caller( whitelisted_caller(), WasmModule::dummy(), vec![], Endow::CollectRent )?; let origin = RawOrigin::Signed(instance.caller.clone()); let account_id = instance.account_id.clone(); // instantiate should leave us with an alive contract - ensure_alive::(&instance.account_id)?; + instance.alive_info()?; // generate enough rent so that the contract is evicted System::::set_block_number( - eviction_at::(&instance.account_id)? + T::SignedClaimHandicap::get() + 5.into() + instance.eviction_at()? + T::SignedClaimHandicap::get() + 5.into() ); }: _(origin, account_id, None) verify { // the claim surcharge should have evicted the contract - ensure_tombstone::(&instance.account_id)?; + instance.ensure_tombstone()?; // the caller should get the reward for being a good snitch assert_eq!( T::Currency::free_balance(&instance.caller), - funding::() - instance.endowment + ::SurchargeReward::get(), + caller_funding::() - instance.endowment + ::SurchargeReward::get(), ); } seal_caller { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(WasmModule::getter( + let instance = Contract::::new(WasmModule::getter( "seal_caller", r * API_BENCHMARK_BATCH_SIZE ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -327,7 +363,7 @@ benchmarks! { seal_address { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(WasmModule::getter( + let instance = Contract::::new(WasmModule::getter( "seal_address", r * API_BENCHMARK_BATCH_SIZE ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -335,7 +371,7 @@ benchmarks! { seal_gas_left { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(WasmModule::getter( + let instance = Contract::::new(WasmModule::getter( "seal_gas_left", r * API_BENCHMARK_BATCH_SIZE ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -343,7 +379,7 @@ benchmarks! { seal_balance { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(WasmModule::getter( + let instance = Contract::::new(WasmModule::getter( "seal_balance", r * API_BENCHMARK_BATCH_SIZE ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -351,7 +387,7 @@ benchmarks! { seal_value_transferred { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(WasmModule::getter( + let instance = Contract::::new(WasmModule::getter( "seal_value_transferred", r * API_BENCHMARK_BATCH_SIZE ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -359,7 +395,7 @@ benchmarks! { seal_minimum_balance { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(WasmModule::getter( + let instance = Contract::::new(WasmModule::getter( "seal_minimum_balance", r * API_BENCHMARK_BATCH_SIZE ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -367,7 +403,7 @@ benchmarks! { seal_tombstone_deposit { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(WasmModule::getter( + let instance = Contract::::new(WasmModule::getter( "seal_tombstone_deposit", r * API_BENCHMARK_BATCH_SIZE ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -375,7 +411,7 @@ benchmarks! { seal_rent_allowance { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(WasmModule::getter( + let instance = Contract::::new(WasmModule::getter( "seal_rent_allowance", r * API_BENCHMARK_BATCH_SIZE ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -383,7 +419,7 @@ benchmarks! { seal_block_number { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(WasmModule::getter( + let instance = Contract::::new(WasmModule::getter( "seal_block_number", r * API_BENCHMARK_BATCH_SIZE ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -391,7 +427,7 @@ benchmarks! { seal_now { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(WasmModule::getter( + let instance = Contract::::new(WasmModule::getter( "seal_now", r * API_BENCHMARK_BATCH_SIZE ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -419,7 +455,7 @@ benchmarks! { ])), .. Default::default() }); - let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let instance = Contract::::new(code, vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) @@ -437,7 +473,7 @@ benchmarks! { ])), .. Default::default() }); - let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let instance = Contract::::new(code, vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) @@ -467,7 +503,7 @@ benchmarks! { ])), .. Default::default() }); - let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let instance = Contract::::new(code, vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) @@ -496,7 +532,7 @@ benchmarks! { ])), .. Default::default() }); - let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let instance = Contract::::new(code, vec![], Endow::Max)?; let data = vec![42u8; (n * 1024).min(buffer_size) as usize]; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0.into(), Weight::max_value(), data) @@ -519,7 +555,7 @@ benchmarks! { ])), .. Default::default() }); - let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let instance = Contract::::new(code, vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) @@ -541,7 +577,7 @@ benchmarks! { ])), .. Default::default() }); - let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let instance = Contract::::new(code, vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) @@ -571,15 +607,15 @@ benchmarks! { ])), .. Default::default() }); - let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let instance = Contract::::new(code, vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); assert_eq!(T::Currency::total_balance(&beneficiary), 0.into()); - assert_eq!(T::Currency::total_balance(&instance.account_id), max_endowment::()); + assert_eq!(T::Currency::total_balance(&instance.account_id), Endow::max::()); }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) verify { if r > 0 { assert_eq!(T::Currency::total_balance(&instance.account_id), 0.into()); - assert_eq!(T::Currency::total_balance(&beneficiary), max_endowment::()); + assert_eq!(T::Currency::total_balance(&beneficiary), Endow::max::()); } } @@ -589,7 +625,7 @@ benchmarks! { // Restore just moves the trie id from origin to destination and therefore // does not depend on the size of the destination contract. However, to not // trigger any edge case we won't use an empty contract as destination. - let tombstone = create_tombstone::(10, T::MaxValueSize::get())?; + let tombstone = Tombstone::::new(10, T::MaxValueSize::get())?; let dest = tombstone.contract.account_id.encode(); let dest_len = dest.len(); @@ -646,23 +682,23 @@ benchmarks! { .. Default::default() }); - let instance = instantiate_contract_from_account::( + let instance = Contract::::with_caller( account("origin", 0, 0), code, vec![], Endow::Max )?; - store_items::(&instance.account_id, &tombstone.storage)?; + instance.store(&tombstone.storage)?; System::::set_block_number(System::::block_number() + 1.into()); let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) verify { if r > 0 { - ensure_alive::(&tombstone.contract.account_id)?; + tombstone.contract.alive_info()?; } } seal_restore_to_per_delta { let d in 0 .. API_BENCHMARK_BATCHES; - let tombstone = create_tombstone::(0, 0)?; + let tombstone = Tombstone::::new(0, 0)?; let delta = create_storage::(d * API_BENCHMARK_BATCH_SIZE, T::MaxValueSize::get())?; let dest = tombstone.contract.account_id.encode(); @@ -727,17 +763,17 @@ benchmarks! { .. Default::default() }); - let instance = instantiate_contract_from_account::( + let instance = Contract::::with_caller( account("origin", 0, 0), code, vec![], Endow::Max )?; - store_items::(&instance.account_id, &tombstone.storage)?; - store_items::(&instance.account_id, &delta)?; + instance.store(&tombstone.storage)?; + instance.store(&delta)?; System::::set_block_number(System::::block_number() + 1.into()); let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) verify { - ensure_alive::(&tombstone.contract.account_id)?; + tombstone.contract.alive_info()?; } // We benchmark only for the maximum subject length. We assume that this is some lowish @@ -770,7 +806,7 @@ benchmarks! { ])), .. Default::default() }); - let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let instance = Contract::::new(code, vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) @@ -794,7 +830,7 @@ benchmarks! { ])), .. Default::default() }); - let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let instance = Contract::::new(code, vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) @@ -832,13 +868,13 @@ benchmarks! { ])), .. Default::default() }); - let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let instance = Contract::::new(code, vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) seal_set_rent_allowance { let r in 0 .. API_BENCHMARK_BATCHES; - let allowance = funding::().encode(); + let allowance = caller_funding::().encode(); let allowance_len = allowance.len(); let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory { min_pages: 1, max_pages: 1 }), @@ -860,7 +896,7 @@ benchmarks! { ])), .. Default::default() }); - let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let instance = Contract::::new(code, vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) @@ -896,7 +932,7 @@ benchmarks! { ])), .. Default::default() }); - let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let instance = Contract::::new(code, vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) @@ -925,7 +961,7 @@ benchmarks! { ])), .. Default::default() }); - let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let instance = Contract::::new(code, vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) @@ -959,8 +995,8 @@ benchmarks! { ])), .. Default::default() }); - let instance = instantiate_contract::(code, vec![], Endow::Max)?; - let trie_id = get_alive::(&instance.account_id)?.trie_id; + let instance = Contract::::new(code, vec![], Endow::Max)?; + let trie_id = instance.alive_info()?.trie_id; for key in keys { crate::storage::write_contract_storage::( &instance.account_id, @@ -1005,8 +1041,8 @@ benchmarks! { ])), .. Default::default() }); - let instance = instantiate_contract::(code, vec![], Endow::Max)?; - let trie_id = get_alive::(&instance.account_id)?.trie_id; + let instance = Contract::::new(code, vec![], Endow::Max)?; + let trie_id = instance.alive_info()?.trie_id; for key in keys { crate::storage::write_contract_storage::( &instance.account_id, @@ -1050,8 +1086,8 @@ benchmarks! { ])), .. Default::default() }); - let instance = instantiate_contract::(code, vec![], Endow::Max)?; - let trie_id = get_alive::(&instance.account_id)?.trie_id; + let instance = Contract::::new(code, vec![], Endow::Max)?; + let trie_id = instance.alive_info()?.trie_id; crate::storage::write_contract_storage::( &instance.account_id, &trie_id, @@ -1102,7 +1138,7 @@ benchmarks! { ])), .. Default::default() }); - let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let instance = Contract::::new(code, vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); for account in &accounts { assert_eq!(T::Currency::total_balance(account), 0.into()); @@ -1119,7 +1155,7 @@ benchmarks! { let r in 0 .. API_BENCHMARK_BATCHES; let dummy_code = WasmModule::::dummy(); let callees = (0..r * API_BENCHMARK_BATCH_SIZE) - .map(|i| instantiate_contract_from_index(i + 1, dummy_code.clone(), vec![], Endow::Max)) + .map(|i| Contract::with_index(i + 1, dummy_code.clone(), vec![], Endow::Max)) .collect::, _>>()?; let callee_len = callees.get(0).map(|i| i.account_id.encode().len()).unwrap_or(0); let callee_bytes = callees.iter().flat_map(|x| x.account_id.encode()).collect(); @@ -1169,7 +1205,7 @@ benchmarks! { ])), .. Default::default() }); - let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let instance = Contract::::new(code, vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) @@ -1198,7 +1234,7 @@ benchmarks! { .. Default::default() }); let callees = (0..API_BENCHMARK_BATCH_SIZE) - .map(|i| instantiate_contract_from_index(i + 1, callee_code.clone(), vec![], Endow::Max)) + .map(|i| Contract::with_index(i + 1, callee_code.clone(), vec![], Endow::Max)) .collect::, _>>()?; let callee_len = callees.get(0).map(|i| i.account_id.encode().len()).unwrap_or(0); let callee_bytes = callees.iter().flat_map(|x| x.account_id.encode()).collect::>(); @@ -1253,7 +1289,7 @@ benchmarks! { ])), .. Default::default() }); - let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let instance = Contract::::new(code, vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) @@ -1340,8 +1376,9 @@ benchmarks! { ])), .. Default::default() }); - let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let instance = Contract::::new(code, vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); + let callee = instance.addr.clone(); let addresses = hashes .iter() .map(|hash| T::DetermineContractAddress::contract_address_for( @@ -1354,10 +1391,10 @@ benchmarks! { return Err("Expected that contract does not exist at this point."); } } - }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + }: call(origin, callee, 0.into(), Weight::max_value(), vec![]) verify { for addr in &addresses { - ensure_alive::(addr)?; + instance.alive_info()?; } } @@ -1470,14 +1507,14 @@ benchmarks! { ])), .. Default::default() }); - let instance = instantiate_contract::(code, vec![], Endow::Max)?; + let instance = Contract::::new(code, vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) // Only the overhead of calling the function itself with minimal arguments. seal_hash_sha2_256 { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(WasmModule::hasher( + let instance = Contract::::new(WasmModule::hasher( "seal_hash_sha2_256", r * API_BENCHMARK_BATCH_SIZE, 0, ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -1486,7 +1523,7 @@ benchmarks! { // `n`: Input to hash in kilobytes seal_hash_sha2_256_per_kb { let n in 0 .. code::max_pages::() * 64; - let instance = instantiate_contract::(WasmModule::hasher( + let instance = Contract::::new(WasmModule::hasher( "seal_hash_sha2_256", API_BENCHMARK_BATCH_SIZE, n * 1024, ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -1495,7 +1532,7 @@ benchmarks! { // Only the overhead of calling the function itself with minimal arguments. seal_hash_keccak_256 { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(WasmModule::hasher( + let instance = Contract::::new(WasmModule::hasher( "seal_hash_keccak_256", r * API_BENCHMARK_BATCH_SIZE, 0, ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -1504,7 +1541,7 @@ benchmarks! { // `n`: Input to hash in kilobytes seal_hash_keccak_256_per_kb { let n in 0 .. code::max_pages::() * 64; - let instance = instantiate_contract::(WasmModule::hasher( + let instance = Contract::::new(WasmModule::hasher( "seal_hash_keccak_256", API_BENCHMARK_BATCH_SIZE, n * 1024, ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -1513,7 +1550,7 @@ benchmarks! { // Only the overhead of calling the function itself with minimal arguments. seal_hash_blake2_256 { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(WasmModule::hasher( + let instance = Contract::::new(WasmModule::hasher( "seal_hash_blake2_256", r * API_BENCHMARK_BATCH_SIZE, 0, ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -1522,7 +1559,7 @@ benchmarks! { // `n`: Input to hash in kilobytes seal_hash_blake2_256_per_kb { let n in 0 .. code::max_pages::() * 64; - let instance = instantiate_contract::(WasmModule::hasher( + let instance = Contract::::new(WasmModule::hasher( "seal_hash_blake2_256", API_BENCHMARK_BATCH_SIZE, n * 1024, ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -1531,7 +1568,7 @@ benchmarks! { // Only the overhead of calling the function itself with minimal arguments. seal_hash_blake2_128 { let r in 0 .. API_BENCHMARK_BATCHES; - let instance = instantiate_contract::(WasmModule::hasher( + let instance = Contract::::new(WasmModule::hasher( "seal_hash_blake2_128", r * API_BENCHMARK_BATCH_SIZE, 0, ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); @@ -1540,7 +1577,7 @@ benchmarks! { // `n`: Input to hash in kilobytes seal_hash_blake2_128_per_kb { let n in 0 .. code::max_pages::() * 64; - let instance = instantiate_contract::(WasmModule::hasher( + let instance = Contract::::new(WasmModule::hasher( "seal_hash_blake2_128", API_BENCHMARK_BATCH_SIZE, n * 1024, ), vec![], Endow::Max)?; let origin = RawOrigin::Signed(instance.caller.clone()); From b42e400943378aa7df59aedf945853db0c89cefe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Thu, 24 Sep 2020 18:47:45 +0200 Subject: [PATCH 21/29] Remove additional empty line --- frame/contracts/src/benchmarking/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index e135ac94afd33..c26bad413e4a9 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -232,7 +232,6 @@ fn caller_funding() -> BalanceOf { BalanceOf::::max_value() / 2.into() } - /// Set the block number to one. /// /// The default block number is zero. The benchmarking system bumps the block number From 56b8d31978541eb421b8ce8f03786231f9d7aa76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Thu, 24 Sep 2020 18:48:47 +0200 Subject: [PATCH 22/29] Added missing comment on caller_funding --- frame/contracts/src/benchmarking/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index c26bad413e4a9..22bcc3bc4e860 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -214,7 +214,7 @@ impl Tombstone { } } -/// Crate `stor_num` storage items. Each has the size `stor_size`. +/// Generate `stor_num` storage items. Each has the size `stor_size`. fn create_storage( stor_num: u32, stor_size: u32 @@ -228,6 +228,7 @@ fn create_storage( }).collect::, &'static str>>() } +/// The funding that each account that either calls or instantiates contracts is funded with. fn caller_funding() -> BalanceOf { BalanceOf::::max_value() / 2.into() } From a7d3da19c3074d984cdb9d196a136a0f4ca51eab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Thu, 24 Sep 2020 19:58:00 +0200 Subject: [PATCH 23/29] Update frame/contracts/src/benchmarking/code.rs Co-authored-by: Sergei Shulepov --- frame/contracts/src/benchmarking/code.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/contracts/src/benchmarking/code.rs b/frame/contracts/src/benchmarking/code.rs index 4e4cb314ceac2..588a4f1a955d5 100644 --- a/frame/contracts/src/benchmarking/code.rs +++ b/frame/contracts/src/benchmarking/code.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Functions to prodecurally construct contract code used for benchmarking. +//! Functions to procedurally construct contract code used for benchmarking. //! //! In order to be able to benchmark events that are triggered by contract execution //! (API calls into seal, individual instructions), we need to generate contracts that From 09766bb0578c49bfdc2783a2a8d5725b354ce066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Fri, 25 Sep 2020 09:39:12 +0200 Subject: [PATCH 24/29] Fix missing sp_std::prelude import in code.rs --- frame/contracts/src/benchmarking/code.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/contracts/src/benchmarking/code.rs b/frame/contracts/src/benchmarking/code.rs index 588a4f1a955d5..dc3730e95ca1f 100644 --- a/frame/contracts/src/benchmarking/code.rs +++ b/frame/contracts/src/benchmarking/code.rs @@ -29,7 +29,7 @@ use crate::Module as Contracts; use parity_wasm::elements::{Instruction, Instructions, FuncBody, ValueType, BlockType}; use sp_runtime::traits::Hash; -use std::convert::TryFrom; +use sp_std::{prelude::*, convert::TryFrom}; /// Pass to `create_code` in order to create a compiled `WasmModule`. pub struct ModuleDefinition { From e565433bb4042fcb110a2fb1dd7657df0c89bdfa Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Tue, 6 Oct 2020 20:11:24 +0000 Subject: [PATCH 25/29] cargo run --release --features runtime-benchmarks --manifest-path bin/node/cli/Cargo.toml -- benchmark --chain dev --steps 50 --repeat 20 --extrinsic * --execution=wasm --wasm-execution=compiled --output ./bin/node/runtime/src/weights --header ./HEADER --pallet pallet_contracts --heap-pages 4096 --- .../runtime/src/weights/pallet_contracts.rs | 294 ++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 bin/node/runtime/src/weights/pallet_contracts.rs diff --git a/bin/node/runtime/src/weights/pallet_contracts.rs b/bin/node/runtime/src/weights/pallet_contracts.rs new file mode 100644 index 0000000000000..8cd97b4a72191 --- /dev/null +++ b/bin/node/runtime/src/weights/pallet_contracts.rs @@ -0,0 +1,294 @@ +// This file is part of Substrate. + +// Copyright (C) 2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Weights for pallet_contracts +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 2.0.0 +//! DATE: 2020-10-06, STEPS: [50], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] + +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +pub struct WeightInfo(PhantomData); +impl pallet_contracts::WeightInfo for WeightInfo { + fn update_schedule() -> Weight { + (33_207_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn put_code(n: u32, ) -> Weight { + (0 as Weight) + .saturating_add((144_833_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn instantiate(n: u32, ) -> Weight { + (223_974_000 as Weight) + .saturating_add((1_007_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(6 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + fn call() -> Weight { + (210_638_000 as Weight) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn claim_surcharge() -> Weight { + (508_079_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn seal_caller(r: u32, ) -> Weight { + (143_336_000 as Weight) + .saturating_add((397_788_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_address(r: u32, ) -> Weight { + (147_296_000 as Weight) + .saturating_add((396_962_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_gas_left(r: u32, ) -> Weight { + (141_677_000 as Weight) + .saturating_add((393_308_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_balance(r: u32, ) -> Weight { + (157_556_000 as Weight) + .saturating_add((879_861_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + } + fn seal_value_transferred(r: u32, ) -> Weight { + (148_867_000 as Weight) + .saturating_add((391_678_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_minimum_balance(r: u32, ) -> Weight { + (147_252_000 as Weight) + .saturating_add((393_977_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_tombstone_deposit(r: u32, ) -> Weight { + (144_208_000 as Weight) + .saturating_add((394_625_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_rent_allowance(r: u32, ) -> Weight { + (135_320_000 as Weight) + .saturating_add((925_541_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_block_number(r: u32, ) -> Weight { + (145_849_000 as Weight) + .saturating_add((390_065_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_now(r: u32, ) -> Weight { + (146_363_000 as Weight) + .saturating_add((391_772_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_weight_to_fee(r: u32, ) -> Weight { + (129_872_000 as Weight) + .saturating_add((670_744_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + } + fn seal_gas(r: u32, ) -> Weight { + (130_985_000 as Weight) + .saturating_add((198_427_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_input(r: u32, ) -> Weight { + (138_647_000 as Weight) + .saturating_add((8_363_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_input_per_kb(n: u32, ) -> Weight { + (149_418_000 as Weight) + .saturating_add((272_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_return(r: u32, ) -> Weight { + (129_116_000 as Weight) + .saturating_add((5_745_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_return_per_kb(n: u32, ) -> Weight { + (139_601_000 as Weight) + .saturating_add((680_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_terminate(r: u32, ) -> Weight { + (138_548_000 as Weight) + .saturating_add((355_473_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(r as Weight))) + } + fn seal_restore_to(r: u32, ) -> Weight { + (239_880_000 as Weight) + .saturating_add((138_305_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes((4 as Weight).saturating_mul(r as Weight))) + } + fn seal_restore_to_per_delta(d: u32, ) -> Weight { + (40_572_000 as Weight) + .saturating_add((3_748_632_000 as Weight).saturating_mul(d as Weight)) + .saturating_add(T::DbWeight::get().reads(7 as Weight)) + .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(d as Weight))) + .saturating_add(T::DbWeight::get().writes(5 as Weight)) + .saturating_add(T::DbWeight::get().writes((100 as Weight).saturating_mul(d as Weight))) + } + fn seal_random(r: u32, ) -> Weight { + (148_156_000 as Weight) + .saturating_add((1_036_452_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + } + fn seal_deposit_event(r: u32, ) -> Weight { + (176_039_000 as Weight) + .saturating_add((1_497_705_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32, ) -> Weight { + (1_923_547_000 as Weight) + .saturating_add((783_354_000 as Weight).saturating_mul(t as Weight)) + .saturating_add((240_600_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(t as Weight))) + .saturating_add(T::DbWeight::get().writes((100 as Weight).saturating_mul(t as Weight))) + } + fn seal_set_rent_allowance(r: u32, ) -> Weight { + (151_095_000 as Weight) + .saturating_add((1_104_696_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn seal_set_storage(r: u32, ) -> Weight { + (0 as Weight) + .saturating_add((14_975_467_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + .saturating_add(T::DbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + } + fn seal_set_storage_per_kb(n: u32, ) -> Weight { + (2_465_724_000 as Weight) + .saturating_add((203_125_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn seal_clear_storage(r: u32, ) -> Weight { + (0 as Weight) + .saturating_add((5_254_595_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + .saturating_add(T::DbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + } + fn seal_get_storage(r: u32, ) -> Weight { + (60_303_000 as Weight) + .saturating_add((1_135_486_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + } + fn seal_get_storage_per_kb(n: u32, ) -> Weight { + (931_900_000 as Weight) + .saturating_add((144_572_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + } + fn seal_transfer(r: u32, ) -> Weight { + (50_722_000 as Weight) + .saturating_add((6_701_164_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + .saturating_add(T::DbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + } + fn seal_call(r: u32, ) -> Weight { + (0 as Weight) + .saturating_add((10_589_747_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + } + fn seal_call_per_transfer_input_output_kb(t: u32, i: u32, o: u32, ) -> Weight { + (11_223_388_000 as Weight) + .saturating_add((4_965_182_000 as Weight).saturating_mul(t as Weight)) + .saturating_add((50_603_000 as Weight).saturating_mul(i as Weight)) + .saturating_add((72_972_000 as Weight).saturating_mul(o as Weight)) + .saturating_add(T::DbWeight::get().reads(105 as Weight)) + .saturating_add(T::DbWeight::get().reads((101 as Weight).saturating_mul(t as Weight))) + .saturating_add(T::DbWeight::get().writes((101 as Weight).saturating_mul(t as Weight))) + } + fn seal_instantiate(r: u32, ) -> Weight { + (0 as Weight) + .saturating_add((22_933_938_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(6 as Weight)) + .saturating_add(T::DbWeight::get().reads((300 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + .saturating_add(T::DbWeight::get().writes((200 as Weight).saturating_mul(r as Weight))) + } + fn seal_instantiate_per_input_output_kb(i: u32, o: u32, ) -> Weight { + (20_986_307_000 as Weight) + .saturating_add((152_611_000 as Weight).saturating_mul(i as Weight)) + .saturating_add((73_457_000 as Weight).saturating_mul(o as Weight)) + .saturating_add(T::DbWeight::get().reads(207 as Weight)) + .saturating_add(T::DbWeight::get().writes(202 as Weight)) + } + fn seal_hash_sha2_256(r: u32, ) -> Weight { + (145_988_000 as Weight) + .saturating_add((343_540_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight { + (719_758_000 as Weight) + .saturating_add((420_306_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_keccak_256(r: u32, ) -> Weight { + (116_261_000 as Weight) + .saturating_add((360_601_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_keccak_256_per_kb(n: u32, ) -> Weight { + (583_726_000 as Weight) + .saturating_add((333_091_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_blake2_256(r: u32, ) -> Weight { + (144_609_000 as Weight) + .saturating_add((332_388_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_blake2_256_per_kb(n: u32, ) -> Weight { + (612_987_000 as Weight) + .saturating_add((150_030_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_blake2_128(r: u32, ) -> Weight { + (142_085_000 as Weight) + .saturating_add((329_426_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight { + (632_517_000 as Weight) + .saturating_add((149_974_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } +} From 036a8ba26aa734f0b2c038c98965658d3bf9c643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Tue, 6 Oct 2020 23:42:16 +0200 Subject: [PATCH 26/29] Use weights from the benchmark machine for the substrate node --- bin/node/runtime/src/lib.rs | 2 +- bin/node/runtime/src/weights/mod.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 8d7e323628e4b..d528904c0c0e3 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -696,7 +696,7 @@ impl pallet_contracts::Trait for Runtime { type MaxDepth = pallet_contracts::DefaultMaxDepth; type MaxValueSize = pallet_contracts::DefaultMaxValueSize; type WeightPrice = pallet_transaction_payment::Module; - type WeightInfo = (); + type WeightInfo = weights::pallet_contracts::WeightInfo; } impl pallet_sudo::Trait for Runtime { diff --git a/bin/node/runtime/src/weights/mod.rs b/bin/node/runtime/src/weights/mod.rs index 19269d02611ef..c75ff83085b6e 100644 --- a/bin/node/runtime/src/weights/mod.rs +++ b/bin/node/runtime/src/weights/mod.rs @@ -18,6 +18,7 @@ pub mod frame_system; pub mod pallet_balances; pub mod pallet_collective; +pub mod pallet_contracts; pub mod pallet_democracy; pub mod pallet_elections_phragmen; pub mod pallet_identity; From 2c154dd57aea58cf155a27845b992fb49da15558 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Wed, 7 Oct 2020 09:40:00 +0200 Subject: [PATCH 27/29] Remove prefixes from Schedule members --- frame/contracts/src/lib.rs | 6 +- frame/contracts/src/schedule.rs | 274 +++++++++++++++------------- frame/contracts/src/wasm/prepare.rs | 4 +- frame/contracts/src/wasm/runtime.rs | 100 +++++----- 4 files changed, 203 insertions(+), 181 deletions(-) diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 5ffc841e9edf9..41d0945d0a8b3 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -100,7 +100,7 @@ pub use crate::gas::{Gas, GasMeter}; pub use crate::exec::{ExecResult, ExecReturnValue}; pub use crate::wasm::ReturnCode as RuntimeReturnCode; pub use crate::weight_info::WeightInfo; -pub use crate::schedule::Schedule; +pub use crate::schedule::{Schedule, ApiWeights, InstructionWeights}; use sp_core::crypto::UncheckedFrom; use sp_std::{prelude::*, marker::PhantomData, fmt::Debug}; @@ -704,7 +704,7 @@ decl_event! { /// Contract has been evicted and is now in tombstone state. /// \[contract, tombstone\] - /// + /// /// # Params /// /// - `contract`: `AccountId`: The account ID of the evicted contract. @@ -713,7 +713,7 @@ decl_event! { /// Restoration for a contract has been successful. /// \[donor, dest, code_hash, rent_allowance\] - /// + /// /// # Params /// /// - `donor`: `AccountId`: Account ID of the restoring contract diff --git a/frame/contracts/src/schedule.rs b/frame/contracts/src/schedule.rs index 37440d26000b7..599a907bd789f 100644 --- a/frame/contracts/src/schedule.rs +++ b/frame/contracts/src/schedule.rs @@ -34,178 +34,194 @@ pub const API_BENCHMARK_BATCH_SIZE: u32 = 100; #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(Clone, Encode, Decode, PartialEq, Eq)] pub struct Schedule { - /// The type parameter is used in the default implementation. - pub phantom: PhantomData, - /// Version of the schedule. pub version: u32, + /// The weights for individual wasm instructions. + pub instruction_weights: InstructionWeights, + + /// The weights for each imported function a contract is allowed to call. + pub api_weights: ApiWeights, + + /// Whether the `seal_println` function is allowed to be used contracts. + /// MUST only be enabled for `dev` chains, NOT for production chains + pub enable_println: bool, + + /// The maximum number of topics supported by an event. + pub max_event_topics: u32, + + /// Maximum allowed stack height. + /// + /// See https://wiki.parity.io/WebAssembly-StackHeight to find out + /// how the stack frame cost is calculated. + pub max_stack_height: u32, + + /// Maximum number of memory pages allowed for a contract. + pub max_memory_pages: u32, + + /// Maximum allowed size of a declared table. + pub max_table_size: u32, + + /// The maximum length of a subject used for PRNG generation. + pub max_subject_len: u32, + + /// The maximum length of a contract code in bytes. This limit applies to the uninstrumented + /// and pristine form of the code as supplied to `put_code`. + pub max_code_size: u32, + + /// The type parameter is used in the default implementation. + pub _phantom: PhantomData, +} + +/// Describes the weight for all categories of supported wasm instructions. +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Clone, Encode, Decode, PartialEq, Eq)] +pub struct InstructionWeights { /// Weight of a growing memory by single page. - pub op_cost_grow_mem: Weight, + pub grow_mem: Weight, /// Weight of a regular operation. - pub op_cost_regular: Weight, + pub regular: Weight, +} +/// Describes the weight for each imported function that a contract is allowed to call. +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Clone, Encode, Decode, PartialEq, Eq)] +pub struct ApiWeights { /// Weight of calling `seal_caller`. - pub api_cost_caller: Weight, + pub caller: Weight, /// Weight of calling `seal_address`. - pub api_cost_address: Weight, + pub address: Weight, /// Weight of calling `seal_gas_left`. - pub api_cost_gas_left: Weight, + pub gas_left: Weight, /// Weight of calling `seal_balance`. - pub api_cost_balance: Weight, + pub balance: Weight, /// Weight of calling `seal_value_transferred`. - pub api_cost_value_transferred: Weight, + pub value_transferred: Weight, /// Weight of calling `seal_minimum_balance`. - pub api_cost_minimum_balance: Weight, + pub minimum_balance: Weight, /// Weight of calling `seal_tombstone_deposit`. - pub api_cost_tombstone_deposit: Weight, + pub tombstone_deposit: Weight, /// Weight of calling `seal_rent_allowance`. - pub api_cost_rent_allowance: Weight, + pub rent_allowance: Weight, /// Weight of calling `seal_block_number`. - pub api_cost_block_number: Weight, + pub block_number: Weight, /// Weight of calling `seal_now`. - pub api_cost_now: Weight, + pub now: Weight, /// Weight of calling `seal_weight_to_fee`. - pub api_cost_weight_to_fee: Weight, + pub weight_to_fee: Weight, /// Weight of calling `gas`. - pub api_cost_gas: Weight, + pub gas: Weight, /// Weight of calling `seal_input`. - pub api_cost_input: Weight, + pub input: Weight, /// Weight per input byte copied to contract memory by `seal_input`. - pub api_cost_input_per_byte: Weight, + pub input_per_byte: Weight, /// Weight of calling `seal_return`. - pub api_cost_return: Weight, + pub r#return: Weight, /// Weight per byte returned through `seal_return`. - pub api_cost_return_per_byte: Weight, + pub return_per_byte: Weight, /// Weight of calling `seal_terminate`. - pub api_cost_terminate: Weight, + pub terminate: Weight, /// Weight of calling `seal_restore_to`. - pub api_cost_restore_to: Weight, + pub restore_to: Weight, /// Weight per delta key supplied to `seal_restore_to`. - pub api_cost_restore_to_per_delta: Weight, + pub restore_to_per_delta: Weight, /// Weight of calling `seal_random`. - pub api_cost_random: Weight, + pub random: Weight, /// Weight of calling `seal_reposit_event`. - pub api_cost_deposit_event: Weight, + pub deposit_event: Weight, /// Weight per topic supplied to `seal_deposit_event`. - pub api_cost_deposit_event_per_topic: Weight, + pub deposit_event_per_topic: Weight, /// Weight per byte of an event deposited through `seal_deposit_event`. - pub api_cost_deposit_event_per_byte: Weight, + pub deposit_event_per_byte: Weight, /// Weight of calling `seal_set_rent_allowance`. - pub api_cost_set_rent_allowance: Weight, + pub set_rent_allowance: Weight, /// Weight of calling `seal_set_storage`. - pub api_cost_set_storage: Weight, + pub set_storage: Weight, /// Weight per byte of an item stored with `seal_set_storage`. - pub api_cost_set_storage_per_byte: Weight, + pub set_storage_per_byte: Weight, /// Weight of calling `seal_clear_storage`. - pub api_cost_clear_storage: Weight, + pub clear_storage: Weight, /// Weight of calling `seal_get_storage`. - pub api_cost_get_storage: Weight, + pub get_storage: Weight, /// Weight per byte of an item received via `seal_get_storage`. - pub api_cost_get_storage_per_byte: Weight, + pub get_storage_per_byte: Weight, /// Weight of calling `seal_transfer`. - pub api_cost_transfer: Weight, + pub transfer: Weight, /// Weight of calling `seal_call`. - pub api_cost_call: Weight, + pub call: Weight, /// Weight surcharge that is claimed if `seal_call` does a balance transfer. - pub api_cost_call_transfer_surcharge: Weight, + pub call_transfer_surcharge: Weight, /// Weight per input byte supplied to `seal_call`. - pub api_cost_call_per_input_byte: Weight, + pub call_per_input_byte: Weight, /// Weight per output byte received through `seal_call`. - pub api_cost_call_per_output_byte: Weight, + pub call_per_output_byte: Weight, /// Weight of calling `seal_instantiate`. - pub api_cost_instantiate: Weight, + pub instantiate: Weight, /// Weight per input byte supplied to `seal_instantiate`. - pub api_cost_instantiate_per_input_byte: Weight, + pub instantiate_per_input_byte: Weight, /// Weight per output byte received through `seal_instantiate`. - pub api_cost_instantiate_per_output_byte: Weight, + pub instantiate_per_output_byte: Weight, /// Weight of calling `seal_hash_sha_256`. - pub api_cost_hash_sha2_256: Weight, + pub hash_sha2_256: Weight, /// Weight per byte hashed by `seal_hash_sha_256`. - pub api_cost_hash_sha2_256_per_byte: Weight, + pub hash_sha2_256_per_byte: Weight, /// Weight of calling `seal_hash_keccak_256`. - pub api_cost_hash_keccak_256: Weight, + pub hash_keccak_256: Weight, /// Weight per byte hashed by `seal_hash_keccak_256`. - pub api_cost_hash_keccak_256_per_byte: Weight, + pub hash_keccak_256_per_byte: Weight, /// Weight of calling `seal_hash_blake2_256`. - pub api_cost_hash_blake2_256: Weight, + pub hash_blake2_256: Weight, /// Weight per byte hashed by `seal_hash_blake2_256`. - pub api_cost_hash_blake2_256_per_byte: Weight, + pub hash_blake2_256_per_byte: Weight, /// Weight of calling `seal_hash_blake2_128`. - pub api_cost_hash_blake2_128: Weight, + pub hash_blake2_128: Weight, /// Weight per byte hashed by `seal_hash_blake2_128`. - pub api_cost_hash_blake2_128_per_byte: Weight, - - /// Whether the `seal_println` function is allowed to be used contracts. - /// MUST only be enabled for `dev` chains, NOT for production chains - pub enable_println: bool, - - /// The maximum number of topics supported by an event. - pub max_event_topics: u32, - - /// Maximum allowed stack height. - /// - /// See https://wiki.parity.io/WebAssembly-StackHeight to find out - /// how the stack frame cost is calculated. - pub max_stack_height: u32, - - /// Maximum number of memory pages allowed for a contract. - pub max_memory_pages: u32, - - /// Maximum allowed size of a declared table. - pub max_table_size: u32, - - /// The maximum length of a subject used for PRNG generation. - pub max_subject_len: u32, - - /// The maximum length of a contract code in bytes. This limit applies to the uninstrumented - /// and pristine form of the code as supplied to `put_code`. - pub max_code_size: u32, + pub hash_blake2_128_per_byte: Weight, } /// We need to implement Debug manually because the automatic derive enforces T @@ -281,58 +297,63 @@ macro_rules! cost_byte_batched { impl Default for Schedule { fn default() -> Self { + let instruction_weights = InstructionWeights { + grow_mem: WASM_INSTRUCTION_COST, + regular: WASM_INSTRUCTION_COST, + }; + + let api_weights = ApiWeights { + caller: cost_batched!(seal_caller), + address: cost_batched!(seal_address), + gas_left: cost_batched!(seal_gas_left), + balance: cost_batched!(seal_balance), + value_transferred: cost_batched!(seal_value_transferred), + minimum_balance: cost_batched!(seal_minimum_balance), + tombstone_deposit: cost_batched!(seal_tombstone_deposit), + rent_allowance: cost_batched!(seal_rent_allowance), + block_number: cost_batched!(seal_block_number), + now: cost_batched!(seal_now), + weight_to_fee: cost_batched!(seal_weight_to_fee), + gas: cost_batched!(seal_gas), + input: cost!(seal_input), + input_per_byte: cost_byte!(seal_input_per_kb), + r#return: cost!(seal_return), + return_per_byte: cost_byte!(seal_return_per_kb), + terminate: cost!(seal_terminate), + restore_to: cost!(seal_restore_to), + restore_to_per_delta: cost_batched!(seal_restore_to_per_delta), + random: cost_batched!(seal_random), + deposit_event: cost_batched!(seal_deposit_event), + deposit_event_per_topic: cost_batched_args!(seal_deposit_event_per_topic_and_kb, 1, 0), + deposit_event_per_byte: cost_byte_batched_args!(seal_deposit_event_per_topic_and_kb, 0, 1), + set_rent_allowance: cost_batched!(seal_set_rent_allowance), + set_storage: cost_batched!(seal_set_storage), + set_storage_per_byte: cost_byte_batched!(seal_set_storage_per_kb), + clear_storage: cost_batched!(seal_clear_storage), + get_storage: cost_batched!(seal_get_storage), + get_storage_per_byte: cost_byte_batched!(seal_get_storage_per_kb), + transfer: cost_batched!(seal_transfer), + call: cost_batched!(seal_call), + call_transfer_surcharge: cost_batched_args!(seal_call_per_transfer_input_output_kb, 1, 0, 0), + 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), + 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), + hash_keccak_256_per_byte: cost_byte_batched!(seal_hash_keccak_256_per_kb), + hash_blake2_256: cost_batched!(seal_hash_blake2_256), + hash_blake2_256_per_byte: cost_byte_batched!(seal_hash_blake2_256_per_kb), + hash_blake2_128: cost_batched!(seal_hash_blake2_128), + hash_blake2_128_per_byte: cost_byte_batched!(seal_hash_blake2_128_per_kb), + }; + Self { - phantom: PhantomData, version: 0, - - op_cost_grow_mem: WASM_INSTRUCTION_COST, - op_cost_regular: WASM_INSTRUCTION_COST, - api_cost_caller: cost_batched!(seal_caller), - api_cost_address: cost_batched!(seal_address), - api_cost_gas_left: cost_batched!(seal_gas_left), - api_cost_balance: cost_batched!(seal_balance), - api_cost_value_transferred: cost_batched!(seal_value_transferred), - api_cost_minimum_balance: cost_batched!(seal_minimum_balance), - api_cost_tombstone_deposit: cost_batched!(seal_tombstone_deposit), - api_cost_rent_allowance: cost_batched!(seal_rent_allowance), - api_cost_block_number: cost_batched!(seal_block_number), - api_cost_now: cost_batched!(seal_now), - api_cost_weight_to_fee: cost_batched!(seal_weight_to_fee), - api_cost_gas: cost_batched!(seal_gas), - api_cost_input: cost!(seal_input), - api_cost_input_per_byte: cost_byte!(seal_input_per_kb), - api_cost_return: cost!(seal_return), - api_cost_return_per_byte: cost_byte!(seal_return_per_kb), - api_cost_terminate: cost!(seal_terminate), - api_cost_restore_to: cost!(seal_restore_to), - api_cost_restore_to_per_delta: cost_batched!(seal_restore_to_per_delta), - api_cost_random: cost_batched!(seal_random), - api_cost_deposit_event: cost_batched!(seal_deposit_event), - api_cost_deposit_event_per_topic: cost_batched_args!(seal_deposit_event_per_topic_and_kb, 1, 0), - api_cost_deposit_event_per_byte: cost_byte_batched_args!(seal_deposit_event_per_topic_and_kb, 0, 1), - api_cost_set_rent_allowance: cost_batched!(seal_set_rent_allowance), - api_cost_set_storage: cost_batched!(seal_set_storage), - api_cost_set_storage_per_byte: cost_byte_batched!(seal_set_storage_per_kb), - api_cost_clear_storage: cost_batched!(seal_clear_storage), - api_cost_get_storage: cost_batched!(seal_get_storage), - api_cost_get_storage_per_byte: cost_byte_batched!(seal_get_storage_per_kb), - api_cost_transfer: cost_batched!(seal_transfer), - api_cost_call: cost_batched!(seal_call), - api_cost_call_transfer_surcharge: cost_batched_args!(seal_call_per_transfer_input_output_kb, 1, 0, 0), - api_cost_call_per_input_byte: cost_byte_batched_args!(seal_call_per_transfer_input_output_kb, 0, 1, 0), - api_cost_call_per_output_byte: cost_byte_batched_args!(seal_call_per_transfer_input_output_kb, 0, 0, 1), - api_cost_instantiate: cost_batched!(seal_instantiate), - api_cost_instantiate_per_input_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_kb, 1, 0), - api_cost_instantiate_per_output_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_kb, 0, 1), - api_cost_hash_sha2_256: cost_batched!(seal_hash_sha2_256), - api_cost_hash_sha2_256_per_byte: cost_byte_batched!(seal_hash_sha2_256_per_kb), - api_cost_hash_keccak_256: cost_batched!(seal_hash_keccak_256), - api_cost_hash_keccak_256_per_byte: cost_byte_batched!(seal_hash_keccak_256_per_kb), - api_cost_hash_blake2_256: cost_batched!(seal_hash_blake2_256), - api_cost_hash_blake2_256_per_byte: cost_byte_batched!(seal_hash_blake2_256_per_kb), - api_cost_hash_blake2_128: cost_batched!(seal_hash_blake2_128), - api_cost_hash_blake2_128_per_byte: cost_byte_batched!(seal_hash_blake2_128_per_kb), - + instruction_weights, + api_weights, enable_println: false, max_event_topics: 4, max_stack_height: 64 * 1024, @@ -340,6 +361,7 @@ impl Default for Schedule { max_table_size: 16 * 1024, max_subject_len: 32, max_code_size: 512 * 1024, + _phantom: PhantomData, } } } diff --git a/frame/contracts/src/wasm/prepare.rs b/frame/contracts/src/wasm/prepare.rs index f53b47013b659..171fca6339fd3 100644 --- a/frame/contracts/src/wasm/prepare.rs +++ b/frame/contracts/src/wasm/prepare.rs @@ -148,10 +148,10 @@ impl<'a, T: Trait> ContractModule<'a, T> { fn inject_gas_metering(self) -> Result { let gas_rules = rules::Set::new( - self.schedule.op_cost_regular.clone().saturated_into(), + self.schedule.instruction_weights.regular.clone().saturated_into(), Default::default(), ) - .with_grow_cost(self.schedule.op_cost_grow_mem.clone().saturated_into()) + .with_grow_cost(self.schedule.instruction_weights.grow_mem.clone().saturated_into()) .with_forbidden_floats(); let contract_module = pwasm_utils::inject_gas_counter( diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index ec7e9238847ec..9622f11f8e91d 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -16,7 +16,7 @@ //! Environment definition of the wasm smart-contract runtime. -use crate::{Schedule, Trait, CodeHash, BalanceOf, Error}; +use crate::{ApiWeights, Schedule, Trait, CodeHash, BalanceOf, Error}; use crate::exec::{ Ext, ExecResult, ExecReturnValue, StorageKey, TopicOf, ReturnFlags, ExecError }; @@ -278,57 +278,57 @@ pub enum RuntimeToken { } impl Token for RuntimeToken { - type Metadata = Schedule; + type Metadata = ApiWeights; - fn calculate_amount(&self, s: &Schedule) -> Gas { + fn calculate_amount(&self, s: &Self::Metadata) -> Gas { use self::RuntimeToken::*; match *self { - MeteringBlock(amount) => s.api_cost_gas.saturating_add(amount.into()), - Caller => s.api_cost_caller, - Address => s.api_cost_address, - GasLeft => s.api_cost_gas_left, - Balance => s.api_cost_balance, - ValueTransferred => s.api_cost_value_transferred, - MinimumBalance => s.api_cost_minimum_balance, - TombstoneDeposit => s.api_cost_tombstone_deposit, - RentAllowance => s.api_cost_rent_allowance, - BlockNumber => s.api_cost_block_number, - Now => s.api_cost_now, - WeightToFee => s.api_cost_weight_to_fee, - InputBase => s.api_cost_input, - InputCopyOut(len) => s.api_cost_input_per_byte.saturating_mul(len.into()), - Return(len) => s.api_cost_return - .saturating_add(s.api_cost_return_per_byte.saturating_mul(len.into())), - Terminate => s.api_cost_terminate, - RestoreTo(delta) => s.api_cost_restore_to - .saturating_add(s.api_cost_restore_to_per_delta.saturating_mul(delta.into())), - Random => s.api_cost_random, - DepositEvent{num_topic, len} => s.api_cost_deposit_event - .saturating_add(s.api_cost_deposit_event_per_topic.saturating_mul(num_topic.into())) - .saturating_add(s.api_cost_deposit_event_per_byte.saturating_mul(len.into())), - SetRentAllowance => s.api_cost_set_rent_allowance, - SetStorage(len) => s.api_cost_set_storage - .saturating_add(s.api_cost_set_storage_per_byte.saturating_mul(len.into())), - ClearStorage => s.api_cost_clear_storage, - GetStorageBase => s.api_cost_get_storage, - GetStorageCopyOut(len) => s.api_cost_get_storage_per_byte.saturating_mul(len.into()), - Transfer => s.api_cost_transfer, - CallBase(len) => s.api_cost_call - .saturating_add(s.api_cost_call_per_input_byte.saturating_mul(len.into())), - CallSurchargeTransfer => s.api_cost_call_transfer_surcharge, - CallCopyOut(len) => s.api_cost_call_per_output_byte.saturating_mul(len.into()), - InstantiateBase(len) => s.api_cost_instantiate - .saturating_add(s.api_cost_instantiate_per_input_byte.saturating_mul(len.into())), - InstantiateCopyOut(len) => s.api_cost_instantiate_per_output_byte + MeteringBlock(amount) => s.gas.saturating_add(amount.into()), + Caller => s.caller, + Address => s.address, + GasLeft => s.gas_left, + Balance => s.balance, + ValueTransferred => s.value_transferred, + MinimumBalance => s.minimum_balance, + TombstoneDeposit => s.tombstone_deposit, + RentAllowance => s.rent_allowance, + BlockNumber => s.block_number, + Now => s.now, + WeightToFee => s.weight_to_fee, + InputBase => s.input, + InputCopyOut(len) => s.input_per_byte.saturating_mul(len.into()), + Return(len) => s.r#return + .saturating_add(s.return_per_byte.saturating_mul(len.into())), + Terminate => s.terminate, + RestoreTo(delta) => s.restore_to + .saturating_add(s.restore_to_per_delta.saturating_mul(delta.into())), + Random => s.random, + DepositEvent{num_topic, len} => s.deposit_event + .saturating_add(s.deposit_event_per_topic.saturating_mul(num_topic.into())) + .saturating_add(s.deposit_event_per_byte.saturating_mul(len.into())), + SetRentAllowance => s.set_rent_allowance, + SetStorage(len) => s.set_storage + .saturating_add(s.set_storage_per_byte.saturating_mul(len.into())), + ClearStorage => s.clear_storage, + GetStorageBase => s.get_storage, + GetStorageCopyOut(len) => s.get_storage_per_byte.saturating_mul(len.into()), + Transfer => s.transfer, + CallBase(len) => s.call + .saturating_add(s.call_per_input_byte.saturating_mul(len.into())), + CallSurchargeTransfer => s.call_transfer_surcharge, + CallCopyOut(len) => s.call_per_output_byte.saturating_mul(len.into()), + InstantiateBase(len) => s.instantiate + .saturating_add(s.instantiate_per_input_byte.saturating_mul(len.into())), + InstantiateCopyOut(len) => s.instantiate_per_output_byte .saturating_mul(len.into()), - HashSha256(len) => s.api_cost_hash_sha2_256 - .saturating_add(s.api_cost_hash_sha2_256_per_byte.saturating_mul(len.into())), - HashKeccak256(len) => s.api_cost_hash_keccak_256 - .saturating_add(s.api_cost_hash_keccak_256_per_byte.saturating_mul(len.into())), - HashBlake256(len) => s.api_cost_hash_blake2_256 - .saturating_add(s.api_cost_hash_blake2_256_per_byte.saturating_mul(len.into())), - HashBlake128(len) => s.api_cost_hash_blake2_128 - .saturating_add(s.api_cost_hash_blake2_128_per_byte.saturating_mul(len.into())), + HashSha256(len) => s.hash_sha2_256 + .saturating_add(s.hash_sha2_256_per_byte.saturating_mul(len.into())), + HashKeccak256(len) => s.hash_keccak_256 + .saturating_add(s.hash_keccak_256_per_byte.saturating_mul(len.into())), + HashBlake256(len) => s.hash_blake2_256 + .saturating_add(s.hash_blake2_256_per_byte.saturating_mul(len.into())), + HashBlake128(len) => s.hash_blake2_128 + .saturating_add(s.hash_blake2_128_per_byte.saturating_mul(len.into())), } } } @@ -339,9 +339,9 @@ impl Token for RuntimeToken { fn charge_gas(ctx: &mut Runtime, token: Tok) -> Result<(), sp_sandbox::HostError> where E: Ext, - Tok: Token>, + Tok: Token, { - match ctx.gas_meter.charge(ctx.schedule, token) { + match ctx.gas_meter.charge(&ctx.schedule.api_weights, token) { GasMeterResult::Proceed => Ok(()), GasMeterResult::OutOfGas => { ctx.trap_reason = Some(TrapReason::SupervisorError(Error::::OutOfGas.into())); From 6f2f333d467d03c3ee236690ed24ce0b1dd01104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Wed, 7 Oct 2020 13:09:50 +0200 Subject: [PATCH 28/29] Data lengths in the WeightInfo Trait are specified in kilobytes --- frame/contracts/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 41d0945d0a8b3..e57a79f5f64e3 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -522,7 +522,7 @@ decl_module! { /// Stores the given binary Wasm code into the chain's storage and returns its `codehash`. /// You can instantiate contracts only with stored code. - #[weight = T::WeightInfo::put_code(code.len() as u32)] + #[weight = T::WeightInfo::put_code(code.len() as u32 / 1024)] pub fn put_code( origin, code: Vec @@ -572,7 +572,7 @@ decl_module! { /// 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).saturating_add(*gas_limit)] + #[weight = T::WeightInfo::instantiate(data.len() as u32 / 1024).saturating_add(*gas_limit)] pub fn instantiate( origin, #[compact] endowment: BalanceOf, From 51a8c385cae25fecd55157b320b08cfdc0daa19d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Thu, 8 Oct 2020 15:02:51 +0200 Subject: [PATCH 29/29] Rename ApiWeights to HostFunctionWeights --- frame/contracts/src/lib.rs | 2 +- frame/contracts/src/schedule.rs | 8 ++++---- frame/contracts/src/wasm/runtime.rs | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index e57a79f5f64e3..cd5cbe5d32a40 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -100,7 +100,7 @@ pub use crate::gas::{Gas, GasMeter}; pub use crate::exec::{ExecResult, ExecReturnValue}; pub use crate::wasm::ReturnCode as RuntimeReturnCode; pub use crate::weight_info::WeightInfo; -pub use crate::schedule::{Schedule, ApiWeights, InstructionWeights}; +pub use crate::schedule::{Schedule, HostFnWeights, InstructionWeights}; use sp_core::crypto::UncheckedFrom; use sp_std::{prelude::*, marker::PhantomData, fmt::Debug}; diff --git a/frame/contracts/src/schedule.rs b/frame/contracts/src/schedule.rs index 599a907bd789f..fb38b1b895d18 100644 --- a/frame/contracts/src/schedule.rs +++ b/frame/contracts/src/schedule.rs @@ -41,7 +41,7 @@ pub struct Schedule { pub instruction_weights: InstructionWeights, /// The weights for each imported function a contract is allowed to call. - pub api_weights: ApiWeights, + pub host_fn_weights: HostFnWeights, /// Whether the `seal_println` function is allowed to be used contracts. /// MUST only be enabled for `dev` chains, NOT for production chains @@ -87,7 +87,7 @@ pub struct InstructionWeights { /// Describes the weight for each imported function that a contract is allowed to call. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(Clone, Encode, Decode, PartialEq, Eq)] -pub struct ApiWeights { +pub struct HostFnWeights { /// Weight of calling `seal_caller`. pub caller: Weight, @@ -302,7 +302,7 @@ impl Default for Schedule { regular: WASM_INSTRUCTION_COST, }; - let api_weights = ApiWeights { + let host_fn_weights = HostFnWeights { caller: cost_batched!(seal_caller), address: cost_batched!(seal_address), gas_left: cost_batched!(seal_gas_left), @@ -353,7 +353,7 @@ impl Default for Schedule { Self { version: 0, instruction_weights, - api_weights, + host_fn_weights, enable_println: false, max_event_topics: 4, max_stack_height: 64 * 1024, diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 9622f11f8e91d..d966ff85d9652 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -16,7 +16,7 @@ //! Environment definition of the wasm smart-contract runtime. -use crate::{ApiWeights, Schedule, Trait, CodeHash, BalanceOf, Error}; +use crate::{HostFnWeights, Schedule, Trait, CodeHash, BalanceOf, Error}; use crate::exec::{ Ext, ExecResult, ExecReturnValue, StorageKey, TopicOf, ReturnFlags, ExecError }; @@ -278,7 +278,7 @@ pub enum RuntimeToken { } impl Token for RuntimeToken { - type Metadata = ApiWeights; + type Metadata = HostFnWeights; fn calculate_amount(&self, s: &Self::Metadata) -> Gas { use self::RuntimeToken::*; @@ -339,9 +339,9 @@ impl Token for RuntimeToken { fn charge_gas(ctx: &mut Runtime, token: Tok) -> Result<(), sp_sandbox::HostError> where E: Ext, - Tok: Token, + Tok: Token, { - match ctx.gas_meter.charge(&ctx.schedule.api_weights, token) { + match ctx.gas_meter.charge(&ctx.schedule.host_fn_weights, token) { GasMeterResult::Proceed => Ok(()), GasMeterResult::OutOfGas => { ctx.trap_reason = Some(TrapReason::SupervisorError(Error::::OutOfGas.into()));