From eacb01782bc105a9bec6247a53af4ea76364f835 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Tue, 7 Mar 2023 16:46:08 +0200 Subject: [PATCH 01/29] added [unstable][seal2] call() --- frame/contracts/src/wasm/runtime.rs | 51 +++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index e80e2cde302fe..3494e1b6dc5d1 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -437,7 +437,7 @@ bitflags! { /// The kind of call that should be performed. enum CallType { /// Execute another instantiated contract - Call { callee_ptr: u32, value_ptr: u32, gas: u64 }, + Call { callee_ptr: u32, value_ptr: u32, weight: Weight }, /// Execute deployed code in the context (storage, account ID, value) of the caller contract DelegateCall { code_hash_ptr: u32 }, } @@ -894,7 +894,7 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { }; let call_outcome = match call_type { - CallType::Call { callee_ptr, value_ptr, gas } => { + CallType::Call { callee_ptr, value_ptr, weight } => { let callee: <::T as frame_system::Config>::AccountId = self.read_sandbox_memory_as(memory, callee_ptr)?; let value: BalanceOf<::T> = @@ -903,7 +903,7 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { self.charge_gas(RuntimeCosts::CallSurchargeTransfer)?; } self.ext.call( - Weight::from_parts(gas, 0), + weight, callee, value, input_data, @@ -947,7 +947,7 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { &mut self, memory: &mut [u8], code_hash_ptr: u32, - gas: u64, + weight: Weight, value_ptr: u32, input_data_ptr: u32, input_data_len: u32, @@ -958,7 +958,6 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { salt_ptr: u32, salt_len: u32, ) -> Result { - let gas = Weight::from_parts(gas, 0); self.charge_gas(RuntimeCosts::InstantiateBase { input_data_len, salt_len })?; let value: BalanceOf<::T> = self.read_sandbox_memory_as(memory, value_ptr)?; if value > 0u32.into() { @@ -968,7 +967,7 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { self.read_sandbox_memory_as(memory, code_hash_ptr)?; let input_data = self.read_sandbox_memory(memory, input_data_ptr, input_data_len)?; let salt = self.read_sandbox_memory(memory, salt_ptr, salt_len)?; - let instantiate_outcome = self.ext.instantiate(gas, code_hash, value, input_data, &salt); + let instantiate_outcome = self.ext.instantiate(weight, code_hash, value, input_data, &salt); if let Ok((address, output)) = &instantiate_outcome { if !output.flags.contains(ReturnFlags::REVERT) { self.write_sandbox_output( @@ -1014,6 +1013,7 @@ pub mod env { /// /// NOTE: This is a implementation defined call and is NOT a part of the public API. /// This call is supposed to be called only by instrumentation injected code. + /// TODO: It deals only with the ref_time part of the weight. /// /// - `amount`: How much gas is used. fn gas(ctx: _, _memory: _, amount: u64) -> Result<(), TrapReason> { @@ -1320,7 +1320,7 @@ pub mod env { ctx.call( memory, CallFlags::ALLOW_REENTRY, - CallType::Call { callee_ptr, value_ptr, gas }, + CallType::Call { callee_ptr, value_ptr, weight: Weight::from_parts(gas, 0) }, input_data_ptr, input_data_len, output_ptr, @@ -1374,7 +1374,38 @@ pub mod env { ctx.call( memory, CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, - CallType::Call { callee_ptr, value_ptr, gas }, + CallType::Call { callee_ptr, value_ptr, weight: Weight::from_parts(gas, 0) }, + input_data_ptr, + input_data_len, + output_ptr, + output_len_ptr, + ) + } + + /// TODO + #[version(2)] + #[unstable] + fn call( + ctx: _, + memory: _, + flags: u32, + callee_ptr: u32, + ref_time: u64, + proof_limit: u64, + value_ptr: u32, + input_data_ptr: u32, + input_data_len: u32, + output_ptr: u32, + output_len_ptr: u32, + ) -> Result { + ctx.call( + memory, + CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, + CallType::Call { + callee_ptr, + value_ptr, + weight: Weight::from_parts(ref_time, proof_limit), + }, input_data_ptr, input_data_len, output_ptr, @@ -1462,7 +1493,7 @@ pub mod env { ctx.instantiate( memory, code_hash_ptr, - gas, + Weight::from_parts(gas, 0), value_ptr, input_data_ptr, input_data_len, @@ -1535,7 +1566,7 @@ pub mod env { ctx.instantiate( memory, code_hash_ptr, - gas, + Weight::from_parts(gas, 0), value_ptr, input_data_ptr, input_data_len, From b3c92650a031ced4e240dd230e4318cec7bd9de9 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Tue, 7 Mar 2023 20:06:22 +0200 Subject: [PATCH 02/29] updated test to cover new seal_call proof_limit --- frame/contracts/fixtures/call_with_limit.wat | 20 +++++++++--------- frame/contracts/src/tests.rs | 22 ++++++++++++++++---- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/frame/contracts/fixtures/call_with_limit.wat b/frame/contracts/fixtures/call_with_limit.wat index abb8708267271..d21c7ac3100af 100644 --- a/frame/contracts/fixtures/call_with_limit.wat +++ b/frame/contracts/fixtures/call_with_limit.wat @@ -1,8 +1,8 @@ -;; This expects [account_id, gas_limit] as input and calls the account_id with the supplied gas_limit. +;; This expects [account_id, ref_time, proof_size] as input and calls the account_id with the supplied 2D Weight limit. ;; It returns the result of the call as output data. (module (import "seal0" "seal_input" (func $seal_input (param i32 i32))) - (import "seal0" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32 i32) (result i32))) + (import "seal2" "call" (func $seal_call (param i32 i32 i64 i64 i32 i32 i32 i32 i32) (result i32))) (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) (import "env" "memory" (memory 1 1)) @@ -13,24 +13,24 @@ (func (export "deploy")) (func (export "call") - ;; Receive the encoded call + gas_limit + ;; Receive the encoded account_id, ref_time, proof_size (call $seal_input (i32.const 4) ;; Pointer to the input buffer - (i32.const 0) ;; Size of the length buffer + (i32.const 0) ;; Pointer to the length of the input buffer ) (i32.store (i32.const 0) (call $seal_call + (i32.const 8) ;; set ALLOW_REENTRY (i32.const 4) ;; Pointer to "callee" address. - (i32.const 32) ;; Length of "callee" address. - (i64.load (i32.const 36)) ;; How much gas to devote for the execution. + (i64.load (i32.const 36)) ;; How much ref_time to devote for the execution. + (i64.load (i32.const 44)) ;; How much proof_size to devote for the execution. (i32.const 0) ;; Pointer to the buffer with value to transfer - (i32.const 0) ;; Length of the buffer with value to transfer. (i32.const 0) ;; Pointer to input data buffer address - (i32.const 0) ;; Length of input data buffer + (i32.const 0) ;; Length of input data buffer (i32.const 0xffffffff) ;; u32 max sentinel value: do not copy output - (i32.const 0) ;; Ptr to output buffer len - ) + (i32.const 0) ;; Length is ignored in this case + ) ) (call $seal_return (i32.const 0) (i32.const 0) (i32.const 4)) ) diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index d74b08243df4b..cc01e3fceb11f 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -2696,7 +2696,6 @@ fn gas_estimation_nested_call_fixed_limit() { ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); - let _ = Balances::deposit_creating(&CHARLIE, 1000 * min_balance); let addr_caller = Contracts::bare_instantiate( ALICE, @@ -2730,6 +2729,7 @@ fn gas_estimation_nested_call_fixed_limit() { .iter() .cloned() .chain((GAS_LIMIT / 5).ref_time().to_le_bytes()) + .chain((GAS_LIMIT / 5).proof_size().to_le_bytes()) .collect(); // Call in order to determine the gas that is required for this call @@ -2746,22 +2746,36 @@ fn gas_estimation_nested_call_fixed_limit() { assert_ok!(&result.result); // We have a subcall with a fixed gas limit. This constitutes precharging. - assert!(result.gas_required.ref_time() > result.gas_consumed.ref_time()); + assert!(result.gas_required.all_gt(result.gas_consumed)); // Make the same call using the estimated gas. Should succeed. assert_ok!( Contracts::bare_call( ALICE, - addr_caller, + addr_caller.clone(), 0, result.gas_required, Some(result.storage_deposit.charge_or_zero()), - input, + input.clone(), false, Determinism::Deterministic, ) .result ); + + // Make the same call using proof_size a but less than estimated. Should fail with OutOfGas. + let result = Contracts::bare_call( + ALICE, + addr_caller, + 0, + result.gas_required.sub_proof_size(1), + Some(result.storage_deposit.charge_or_zero()), + input, + false, + Determinism::Deterministic, + ) + .result; + assert_err!(result, >::OutOfGas); }); } From e226add665f9fc9b5219d8661f5e95f419ee03bd Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Tue, 7 Mar 2023 20:56:27 +0200 Subject: [PATCH 03/29] docs updated --- frame/contracts/src/wasm/runtime.rs | 71 ++++++++++++++++------------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 3494e1b6dc5d1..80f3f74660eb9 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -1013,7 +1013,7 @@ pub mod env { /// /// NOTE: This is a implementation defined call and is NOT a part of the public API. /// This call is supposed to be called only by instrumentation injected code. - /// TODO: It deals only with the ref_time part of the weight. + /// It deals only with the *ref_time* Weight. /// /// - `amount`: How much gas is used. fn gas(ctx: _, _memory: _, amount: u64) -> Result<(), TrapReason> { @@ -1023,8 +1023,9 @@ pub mod env { /// Set the value at the given key in the contract storage. /// - /// Equivalent to the newer version [`super::seal1::Api::set_storage`] with the exception of the - /// return type. Still a valid thing to call when not interested in the return value. + /// Equivalent to the newer [`seal1`][`super::api_doc::Version1::set_storage`] version with the + /// exception of the return type. Still a valid thing to call when not interested in the return + /// value. #[prefixed_alias] fn set_storage( ctx: _, @@ -1097,8 +1098,9 @@ pub mod env { /// Clear the value at the given key in the contract storage. /// - /// Equivalent to the newer version [`super::seal1::Api::clear_storage`] with the exception of - /// the return type. Still a valid thing to call when not interested in the return value. + /// Equivalent to the newer [`seal1`][`super::api_doc::Version1::clear_storage`] version with + /// the exception of the return type. Still a valid thing to call when not interested in the + /// return value. #[prefixed_alias] fn clear_storage(ctx: _, memory: _, key_ptr: u32) -> Result<(), TrapReason> { ctx.clear_storage(memory, KeyType::Fix, key_ptr).map(|_| ()) @@ -1328,6 +1330,36 @@ pub mod env { ) } + /// Make a call to another contract. + /// + /// Equivalent to the newer [`seal2`][`super::api_doc::Version2::call`] version but works with + /// *ref_time* Weight only. It is recommended to switch to the latest version, once it's + /// stabilized. + #[version(1)] + #[prefixed_alias] + fn call( + ctx: _, + memory: _, + flags: u32, + callee_ptr: u32, + gas: u64, + value_ptr: u32, + input_data_ptr: u32, + input_data_len: u32, + output_ptr: u32, + output_len_ptr: u32, + ) -> Result { + ctx.call( + memory, + CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, + CallType::Call { callee_ptr, value_ptr, weight: Weight::from_parts(gas, 0) }, + input_data_ptr, + input_data_len, + output_ptr, + output_len_ptr, + ) + } + /// Make a call to another contract. /// /// The callees output buffer is copied to `output_ptr` and its length to `output_len_ptr`. @@ -1339,7 +1371,8 @@ pub mod env { /// - `flags`: See `crate::wasm::runtime::CallFlags` for a documenation of the supported flags. /// - `callee_ptr`: a pointer to the address of the callee contract. Should be decodable as an /// `T::AccountId`. Traps otherwise. - /// - `gas`: how much gas to devote to the execution. + /// - `ref_time`: how much *ref_time* Weight to devote to the execution. + /// - `proof_limit`: how much *proof_limit* Weight to devote to the execution. /// - `value_ptr`: a pointer to the buffer with value, how much value to send. Should be /// decodable as a `T::Balance`. Traps otherwise. /// - `input_data_ptr`: a pointer to a buffer to be used as input data to the callee. @@ -1357,32 +1390,6 @@ pub mod env { /// - `ReturnCode::CalleeTrapped` /// - `ReturnCode::TransferFailed` /// - `ReturnCode::NotCallable` - #[version(1)] - #[prefixed_alias] - fn call( - ctx: _, - memory: _, - flags: u32, - callee_ptr: u32, - gas: u64, - value_ptr: u32, - input_data_ptr: u32, - input_data_len: u32, - output_ptr: u32, - output_len_ptr: u32, - ) -> Result { - ctx.call( - memory, - CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, - CallType::Call { callee_ptr, value_ptr, weight: Weight::from_parts(gas, 0) }, - input_data_ptr, - input_data_len, - output_ptr, - output_len_ptr, - ) - } - - /// TODO #[version(2)] #[unstable] fn call( From aba8782f9e17b9b45765573825188d1642e207c4 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Tue, 7 Mar 2023 23:16:36 +0200 Subject: [PATCH 04/29] add [seal2][unstable] instantiate() and test --- frame/contracts/fixtures/call_with_limit.wat | 2 +- frame/contracts/fixtures/caller_contract.wat | 129 ++++++++++++------- frame/contracts/src/wasm/runtime.rs | 50 ++++++- 3 files changed, 131 insertions(+), 50 deletions(-) diff --git a/frame/contracts/fixtures/call_with_limit.wat b/frame/contracts/fixtures/call_with_limit.wat index d21c7ac3100af..6d5cc07d5af2f 100644 --- a/frame/contracts/fixtures/call_with_limit.wat +++ b/frame/contracts/fixtures/call_with_limit.wat @@ -21,7 +21,7 @@ (i32.store (i32.const 0) (call $seal_call - (i32.const 8) ;; set ALLOW_REENTRY + (i32.const 0) ;; Set no flag. (i32.const 4) ;; Pointer to "callee" address. (i64.load (i32.const 36)) ;; How much ref_time to devote for the execution. (i64.load (i32.const 44)) ;; How much proof_size to devote for the execution. diff --git a/frame/contracts/fixtures/caller_contract.wat b/frame/contracts/fixtures/caller_contract.wat index f9caf49f29ca8..6f2cb0af8a13a 100644 --- a/frame/contracts/fixtures/caller_contract.wat +++ b/frame/contracts/fixtures/caller_contract.wat @@ -1,9 +1,9 @@ (module (import "seal0" "seal_input" (func $seal_input (param i32 i32))) (import "seal0" "seal_balance" (func $seal_balance (param i32 i32))) - (import "seal0" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32 i32) (result i32))) - (import "seal0" "seal_instantiate" (func $seal_instantiate - (param i32 i32 i64 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32) + (import "seal2" "call" (func $seal_call (param i32 i32 i64 i64 i32 i32 i32 i32 i32) (result i32))) + (import "seal2" "instantiate" (func $seal_instantiate + (param i32 i64 i64 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32) )) (import "env" "memory" (memory 1 1)) @@ -43,18 +43,17 @@ (set_local $exit_code (call $seal_instantiate (i32.const 24) ;; Pointer to the code hash. - (i32.const 32) ;; Length of the code hash. - (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i64.const 0) ;; How much ref_time weight to devote for the execution. 0 = all. + (i64.const 0) ;; How much proof_size weight to devote for the execution. 0 = all. (i32.const 0) ;; Pointer to the buffer with value to transfer - (i32.const 8) ;; Length of the buffer with value to transfer. (i32.const 9) ;; Pointer to input data buffer address (i32.const 7) ;; Length of input data buffer - (i32.const 4294967295) ;; u32 max sentinel value: do not copy address - (i32.const 0) ;; Length is ignored in this case + (i32.const 4294967295) ;; u32 max sentinel value: do not copy address + (i32.const 0) ;; Length is ignored in this case (i32.const 4294967295) ;; u32 max sentinel value: do not copy output - (i32.const 0) ;; Length is ignored in this case - (i32.const 0) ;; salt_ptr - (i32.const 0) ;; salt_le + (i32.const 0) ;; Length is ignored in this case + (i32.const 0) ;; salt_ptr + (i32.const 0) ;; salt_le ) ) @@ -63,22 +62,45 @@ (i32.eq (get_local $exit_code) (i32.const 2)) ;; ReturnCode::CalleeReverted ) - ;; Fail to deploy the contract due to insufficient gas. + ;; Fail to deploy the contract due to insufficient ref_time weight. (set_local $exit_code (call $seal_instantiate (i32.const 24) ;; Pointer to the code hash. - (i32.const 32) ;; Length of the code hash. - (i64.const 1) ;; Supply too little gas + (i64.const 1) ;; Supply too little ref_time weight + (i64.const 0) ;; How much proof_size weight to devote for the execution. 0 = all. (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 (i32.const 8) ;; Length of input data buffer (i32.const 4294967295) ;; u32 max sentinel value: do not copy address - (i32.const 0) ;; Length is ignored in this case + (i32.const 0) ;; Length is ignored in this case (i32.const 4294967295) ;; u32 max sentinel value: do not copy output - (i32.const 0) ;; Length is ignored in this case - (i32.const 0) ;; salt_ptr - (i32.const 0) ;; salt_le + (i32.const 0) ;; Length is ignored in this case + (i32.const 0) ;; salt_ptr + (i32.const 0) ;; salt_le + + ) + ) + + ;; Check for special trap exit status. + (call $assert + (i32.eq (get_local $exit_code) (i32.const 1)) ;; ReturnCode::CalleeTrapped + ) + + ;; Fail to deploy the contract due to insufficient ref_time weight. + (set_local $exit_code + (call $seal_instantiate + (i32.const 24) ;; Pointer to the code hash. + (i64.const 0) ;; How much ref_time weight to devote for the execution. 0 = all. + (i64.const 1) ;; Supply too little proof_size weight + (i32.const 0) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Pointer to input data buffer address + (i32.const 8) ;; Length of input data buffer + (i32.const 4294967295) ;; u32 max sentinel value: do not copy address + (i32.const 0) ;; Length is ignored in this case + (i32.const 4294967295) ;; u32 max sentinel value: do not copy output + (i32.const 0) ;; Length is ignored in this case + (i32.const 0) ;; salt_ptr + (i32.const 0) ;; salt_le ) ) @@ -98,18 +120,17 @@ (set_local $exit_code (call $seal_instantiate (i32.const 24) ;; Pointer to the code hash. - (i32.const 32) ;; Length of the code hash. - (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i64.const 0) ;; How much ref_time weight to devote for the execution. 0 = all. + (i64.const 0) ;; How much proof_size weight to devote for the execution. 0 = all. (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 (i32.const 8) ;; Length of input data buffer - (i32.const 16) ;; Pointer to the address output buffer - (i32.sub (get_local $sp) (i32.const 4)) ;; Pointer to the address buffer length - (i32.const 4294967295) ;; u32 max sentinel value: do not copy output - (i32.const 0) ;; Length is ignored in this case - (i32.const 0) ;; salt_ptr - (i32.const 0) ;; salt_le + (i32.const 16) ;; Pointer to the address output buffer + (i32.sub (get_local $sp) (i32.const 4)) ;; Pointer to the address buffer length + (i32.const 4294967295) ;; u32 max sentinel value: do not copy output + (i32.const 0) ;; Length is ignored in this case + (i32.const 0) ;; salt_ptr + (i32.const 0) ;; salt_le ) ) @@ -139,15 +160,15 @@ ;; Call the new contract and expect it to return failing exit code. (set_local $exit_code (call $seal_call + (i32.const 0) ;; Set no flag (i32.const 16) ;; Pointer to "callee" address. - (i32.const 32) ;; Length of "callee" address. - (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i64.const 0) ;; How much ref_time weight to devote for the execution. 0 = all. + (i64.const 0) ;; How much proof_size weight to devote for the execution. 0 = all. (i32.const 0) ;; Pointer to the buffer with value to transfer - (i32.const 8) ;; Length of the buffer with value to transfer. (i32.const 9) ;; Pointer to input data buffer address (i32.const 7) ;; Length of input data buffer - (i32.sub (get_local $sp) (i32.const 4)) ;; Ptr to output buffer - (i32.sub (get_local $sp) (i32.const 8)) ;; Ptr to output buffer len + (i32.sub (get_local $sp) (i32.const 4)) ;; Ptr to output buffer + (i32.sub (get_local $sp) (i32.const 8)) ;; Ptr to output buffer len ) ) @@ -167,18 +188,38 @@ ) ) - ;; Fail to call the contract due to insufficient gas. + ;; Fail to call the contract due to insufficient ref_time weight. (set_local $exit_code (call $seal_call + (i32.const 0) ;; Set no flag (i32.const 16) ;; Pointer to "callee" address. - (i32.const 32) ;; Length of "callee" address. - (i64.const 1) ;; Supply too little gas + (i64.const 1) ;; Supply too little ref_time weight + (i64.const 0) ;; How much proof_size weight to devote for the execution. 0 = all. (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 (i32.const 8) ;; Length of input data buffer - (i32.const 4294967295) ;; u32 max sentinel value: do not copy output - (i32.const 0) ;; Length is ignored in this cas + (i32.const 4294967295) ;; u32 max sentinel value: do not copy output + (i32.const 0) ;; Length is ignored in this cas + ) + ) + + ;; Check for special trap exit status. + (call $assert + (i32.eq (get_local $exit_code) (i32.const 1)) ;; ReturnCode::CalleeTrapped + ) + + ;; Fail to call the contract due to insufficient proof_size weight. + (set_local $exit_code + (call $seal_call + (i32.const 0) ;; Set no flag + (i32.const 16) ;; Pointer to "callee" address. + (i64.const 0) ;; How much ref_time weight to devote for the execution. 0 = all. + (i64.const 1) ;; Supply too little proof_size weight + (i32.const 0) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Pointer to input data buffer address + (i32.const 8) ;; Length of input data buffer + (i32.const 4294967295) ;; u32 max sentinel value: do not copy output + (i32.const 0) ;; Length is ignored in this cas ) ) @@ -202,15 +243,15 @@ ;; Call the contract successfully. (set_local $exit_code (call $seal_call + (i32.const 0) ;; Set no flag (i32.const 16) ;; Pointer to "callee" address. - (i32.const 32) ;; Length of "callee" address. - (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i64.const 0) ;; How much ref_time weight to devote for the execution. 0 = all. + (i64.const 0) ;; How much proof_size weight to devote for the execution. 0 = all. (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 (i32.const 8) ;; Length of input data buffer - (i32.sub (get_local $sp) (i32.const 4)) ;; Ptr to output buffer - (i32.sub (get_local $sp) (i32.const 8)) ;; Ptr to output buffer len + (i32.sub (get_local $sp) (i32.const 4)) ;; Ptr to output buffer + (i32.sub (get_local $sp) (i32.const 8)) ;; Ptr to output buffer len ) ) diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 80f3f74660eb9..2cf9be8925b83 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -1513,6 +1513,44 @@ pub mod env { ) } + /// Instantiate a contract with the specified code hash. + /// + /// Equivalent to the newer [`seal2`][`super::api_doc::Version2::instantiate`] version but works with + /// *ref_time* Weight only. It is recommended to switch to the latest version, once it's + /// stabilized. + #[version(1)] + #[prefixed_alias] + fn instantiate( + ctx: _, + memory: _, + code_hash_ptr: u32, + gas: u64, + value_ptr: u32, + input_data_ptr: u32, + input_data_len: u32, + address_ptr: u32, + address_len_ptr: u32, + output_ptr: u32, + output_len_ptr: u32, + salt_ptr: u32, + salt_len: u32, + ) -> Result { + ctx.instantiate( + memory, + code_hash_ptr, + Weight::from_parts(gas, 0), + value_ptr, + input_data_ptr, + input_data_len, + address_ptr, + address_len_ptr, + output_ptr, + output_len_ptr, + salt_ptr, + salt_len, + ) + } + /// Instantiate a contract with the specified code hash. /// /// This function creates an account and executes the constructor defined in the code specified @@ -1527,7 +1565,8 @@ pub mod env { /// # Parameters /// /// - `code_hash_ptr`: a pointer to the buffer that contains the initializer code. - /// - `gas`: how much gas to devote to the execution of the initializer code. + /// - `ref_time`: how much *ref_time* Weight to devote to the execution. + /// - `proof_limit`: how much *proof_limit* Weight to devote to the execution. /// - `value_ptr`: a pointer to the buffer with value, how much value to send. Should be /// decodable as a `T::Balance`. Traps otherwise. /// - `input_data_ptr`: a pointer to a buffer to be used as input data to the initializer code. @@ -1553,13 +1592,14 @@ pub mod env { /// - `ReturnCode::CalleeTrapped` /// - `ReturnCode::TransferFailed` /// - `ReturnCode::CodeNotFound` - #[version(1)] - #[prefixed_alias] + #[version(2)] + #[unstable] fn instantiate( ctx: _, memory: _, code_hash_ptr: u32, - gas: u64, + ref_time: u64, + proof_limit: u64, value_ptr: u32, input_data_ptr: u32, input_data_len: u32, @@ -1573,7 +1613,7 @@ pub mod env { ctx.instantiate( memory, code_hash_ptr, - Weight::from_parts(gas, 0), + Weight::from_parts(ref_time, proof_limit), value_ptr, input_data_ptr, input_data_len, From 53d8fde45bdd7cb28cf2a975bfd9041c5c255abe Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Tue, 7 Mar 2023 23:45:58 +0200 Subject: [PATCH 05/29] add [seal2][unstable] weight_to_fee() + docs and test --- frame/contracts/src/wasm/mod.rs | 11 ++++--- frame/contracts/src/wasm/runtime.rs | 51 ++++++++++++++++++++++------- 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index f8c5a6ed96c57..f11276f825c28 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -615,7 +615,8 @@ mod tests { 16_384 } fn get_weight_price(&self, weight: Weight) -> BalanceOf { - BalanceOf::::from(1312_u32).saturating_mul(weight.ref_time().into()) + BalanceOf::::from(1312_u32).saturating_mul(weight.ref_time().into()).saturating_add( + BalanceOf::::from(103_u32).saturating_mul(weight.proof_size())) } fn schedule(&self) -> &Schedule { &self.schedule @@ -1570,7 +1571,7 @@ mod tests { const CODE_GAS_PRICE: &str = r#" (module - (import "seal0" "seal_weight_to_fee" (func $seal_weight_to_fee (param i64 i32 i32))) + (import "seal1" "weight_to_fee" (func $seal_weight_to_fee (param i64 i64 i32 i32))) (import "env" "memory" (memory 1 1)) ;; size of our buffer is 32 bytes @@ -1587,7 +1588,7 @@ mod tests { (func (export "call") ;; This stores the gas price in the buffer - (call $seal_weight_to_fee (i64.const 2) (i32.const 0) (i32.const 32)) + (call $seal_weight_to_fee (i64.const 2) (i64.const 1) (i32.const 0) (i32.const 32)) ;; assert len == 8 (call $assert @@ -1597,11 +1598,11 @@ mod tests { ) ) - ;; assert that contents of the buffer is equal to the i64 value of 2 * 1312. + ;; assert that contents of the buffer is equal to the i64 value of 2 * 1312 + 103 = 2727. (call $assert (i64.eq (i64.load (i32.const 0)) - (i64.const 2624) + (i64.const 2727) ) ) ) diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 2cf9be8925b83..3a2263762b011 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -1515,8 +1515,8 @@ pub mod env { /// Instantiate a contract with the specified code hash. /// - /// Equivalent to the newer [`seal2`][`super::api_doc::Version2::instantiate`] version but works with - /// *ref_time* Weight only. It is recommended to switch to the latest version, once it's + /// Equivalent to the newer [`seal2`][`super::api_doc::Version2::instantiate`] version but works + /// with *ref_time* Weight only. It is recommended to switch to the latest version, once it's /// stabilized. #[version(1)] #[prefixed_alias] @@ -1861,26 +1861,55 @@ pub mod env { /// Stores the price for the specified amount of gas into the supplied buffer. /// - /// The value is stored to linear memory at the address pointed to by `out_ptr`. - /// `out_len_ptr` must point to a u32 value that describes the available space at - /// `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. + /// Equivalent to the newer [`seal1`][`super::api_doc::Version2::weight_to_fee`] version but + /// works with *ref_time* Weight only. It is recommended to switch to the latest version, once + /// it's stabilized. + #[prefixed_alias] + fn weight_to_fee( + ctx: _, + memory: _, + gas: u64, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + let gas = Weight::from_parts(gas, 0); + ctx.charge_gas(RuntimeCosts::WeightToFee)?; + Ok(ctx.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &ctx.ext.get_weight_price(gas).encode(), + false, + already_charged, + )?) + } + + /// Stores the price for the specified amount of weight into the supplied buffer. + /// + /// # Parameters + /// + /// - `out_ptr`: pointer to the linear memory where the returning value is written to. If the + /// available space at `out_ptr` is less than the size of the value a trap is triggered. + /// - `out_len_ptr`: in-out pointer into linear memory where the buffer length is read from and + /// the value length is written to. /// /// The data is encoded as `T::Balance`. /// /// # Note /// - /// It is recommended to avoid specifying very small values for `gas` as the prices for a single - /// gas can be smaller than one. - #[prefixed_alias] + /// It is recommended to avoid specifying very small values for `ref_time` and `proof_limit` as + /// the prices for a single gas can be smaller than the basic balance unit. + #[version(1)] + #[unstable] fn weight_to_fee( ctx: _, memory: _, - gas: u64, + ref_time: u64, + proof_limit: u64, out_ptr: u32, out_len_ptr: u32, ) -> Result<(), TrapReason> { - let gas = Weight::from_parts(gas, 0); + let gas = Weight::from_parts(ref_time, proof_limit); ctx.charge_gas(RuntimeCosts::WeightToFee)?; Ok(ctx.write_sandbox_output( memory, From 09d1df6deb32b6e733c1dc255fd4a1fd9a5259ac Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Wed, 8 Mar 2023 10:51:06 +0200 Subject: [PATCH 06/29] add [seal2][unstable] gas_left() + docs and test --- frame/contracts/src/wasm/mod.rs | 41 +++++++++++++++-------------- frame/contracts/src/wasm/runtime.rs | 32 +++++++++++++++++----- 2 files changed, 47 insertions(+), 26 deletions(-) diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index f11276f825c28..2ddd3c01db63e 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -377,9 +377,7 @@ mod tests { }; use assert_matches::assert_matches; use frame_support::{ - assert_err, assert_ok, - dispatch::DispatchResultWithPostInfo, - weights::{OldWeight, Weight}, + assert_err, assert_ok, dispatch::DispatchResultWithPostInfo, weights::Weight, }; use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags}; use pretty_assertions::assert_eq; @@ -615,8 +613,11 @@ mod tests { 16_384 } fn get_weight_price(&self, weight: Weight) -> BalanceOf { - BalanceOf::::from(1312_u32).saturating_mul(weight.ref_time().into()).saturating_add( - BalanceOf::::from(103_u32).saturating_mul(weight.proof_size())) + BalanceOf::::from(1312_u32) + .saturating_mul(weight.ref_time().into()) + .saturating_add( + BalanceOf::::from(103_u32).saturating_mul(weight.proof_size()), + ) } fn schedule(&self) -> &Schedule { &self.schedule @@ -1617,12 +1618,12 @@ mod tests { const CODE_GAS_LEFT: &str = r#" (module - (import "seal0" "seal_gas_left" (func $seal_gas_left (param i32 i32))) + (import "seal1" "gas_left" (func $seal_gas_left (param i32 i32))) (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) (import "env" "memory" (memory 1 1)) - ;; size of our buffer is 32 bytes - (data (i32.const 32) "\20") + ;; Make output buffer size 20 bytes + (data (i32.const 20) "\14") (func $assert (param i32) (block $ok @@ -1634,19 +1635,19 @@ mod tests { ) (func (export "call") - ;; This stores the gas left in the buffer - (call $seal_gas_left (i32.const 0) (i32.const 32)) + ;; This stores the weight left to the buffer + (call $seal_gas_left (i32.const 0) (i32.const 20)) - ;; assert len == 8 + ;; Assert len <= 16 (max encoded Weight len) (call $assert - (i32.eq - (i32.load (i32.const 32)) - (i32.const 8) + (i32.le_u + (i32.load (i32.const 20)) + (i32.const 16) ) ) - ;; return gas left - (call $seal_return (i32.const 0) (i32.const 0) (i32.const 8)) + ;; Return weight left and its encoded value len + (call $seal_return (i32.const 0) (i32.const 0) (i32.load (i32.const 20))) (unreachable) ) @@ -1661,11 +1662,11 @@ mod tests { let output = execute(CODE_GAS_LEFT, vec![], &mut ext).unwrap(); - let OldWeight(gas_left) = OldWeight::decode(&mut &*output.data).unwrap(); + let weight_left = Weight::decode(&mut &*output.data).unwrap(); let actual_left = ext.gas_meter.gas_left(); - // TODO: account for proof size weight - assert!(gas_left < gas_limit.ref_time(), "gas_left must be less than initial"); - assert!(gas_left > actual_left.ref_time(), "gas_left must be greater than final"); + + assert!(weight_left.all_lt(gas_limit), "gas_left must be less than initial"); + assert!(weight_left.all_gt(actual_left), "gas_left must be greater than final"); } const CODE_VALUE_TRANSFERRED: &str = r#" diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 3a2263762b011..7d5d22a5abd82 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -1909,30 +1909,50 @@ pub mod env { out_ptr: u32, out_len_ptr: u32, ) -> Result<(), TrapReason> { - let gas = Weight::from_parts(ref_time, proof_limit); + let weight = Weight::from_parts(ref_time, proof_limit); ctx.charge_gas(RuntimeCosts::WeightToFee)?; Ok(ctx.write_sandbox_output( memory, out_ptr, out_len_ptr, - &ctx.ext.get_weight_price(gas).encode(), + &ctx.ext.get_weight_price(weight).encode(), + false, + already_charged, + )?) + } + + /// Stores the weight left into the supplied buffer. + /// + /// Equivalent to the newer [`seal1`][`super::api_doc::Version2::gas_left`] version but + /// works with *ref_time* Weight only. It is recommended to switch to the latest version, once + /// it's stabilized. + #[prefixed_alias] + fn gas_left(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { + ctx.charge_gas(RuntimeCosts::GasLeft)?; + let gas_left = &ctx.ext.gas_meter().gas_left().ref_time().encode(); + Ok(ctx.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + gas_left, false, already_charged, )?) } - /// Stores the amount of gas left into the supplied buffer. + /// Stores the amount of weight left into the supplied buffer. /// /// The value is stored to linear memory at the address pointed to by `out_ptr`. /// `out_len_ptr` must point to a u32 value that describes the available space at /// `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. /// - /// The data is encoded as Gas. - #[prefixed_alias] + /// The data is encoded as Weight. + #[version(1)] + #[unstable] fn gas_left(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::GasLeft)?; - let gas_left = &ctx.ext.gas_meter().gas_left().ref_time().encode(); + let gas_left = &ctx.ext.gas_meter().gas_left().encode(); Ok(ctx.write_sandbox_output( memory, out_ptr, From c644fa169fe105f5f69850190dcb6575b1071dcb Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Wed, 8 Mar 2023 11:13:43 +0200 Subject: [PATCH 07/29] update benchmarks --- frame/contracts/src/benchmarking/mod.rs | 43 ++++++++++++------------- frame/contracts/src/wasm/runtime.rs | 3 +- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index c18d9ffb4b584..22173843dfadc 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -553,7 +553,7 @@ benchmarks! { seal_gas_left { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal0", "seal_gas_left", r * API_BENCHMARK_BATCH_SIZE + "seal1", "gas_left", r * API_BENCHMARK_BATCH_SIZE ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -610,9 +610,9 @@ benchmarks! { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "seal0", - name: "seal_weight_to_fee", - params: vec![ValueType::I64, ValueType::I32, ValueType::I32], + module: "seal1", + name: "weight_to_fee", + params: vec![ValueType::I64,ValueType::I64, ValueType::I32, ValueType::I32], return_type: None, }], data_segments: vec![DataSegment { @@ -621,6 +621,7 @@ benchmarks! { }], call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ Instruction::I64Const(500_000), + Instruction::I64Const(300_000), Instruction::I32Const(4), Instruction::I32Const(0), Instruction::Call(0), @@ -1617,17 +1618,17 @@ benchmarks! { let callee_bytes = callees.iter().flat_map(|x| x.account_id.encode()).collect(); let value: BalanceOf = 0u32.into(); let value_bytes = value.encode(); - let value_len = value_bytes.len(); + let value_len = BalanceOf::::max_encoded_len() as u32; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "seal0", - name: "seal_call", + module: "seal2", + name: "call", params: vec![ ValueType::I32, ValueType::I32, ValueType::I64, - ValueType::I32, + ValueType::I64, ValueType::I32, ValueType::I32, ValueType::I32, @@ -1642,16 +1643,16 @@ benchmarks! { value: value_bytes, }, DataSegment { - offset: value_len as u32, + offset: value_len, value: callee_bytes, }, ], call_body: Some(body::repeated_dyn(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)), // flags + Counter(4, callee_len as u32), // callee_ptr + Regular(Instruction::I64Const(0)), // ref_time weight + Regular(Instruction::I64Const(0)), // proof_size weight 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(SENTINEL as i32)), // output_ptr @@ -1803,7 +1804,7 @@ benchmarks! { let value = Pallet::::min_balance(); assert!(value > 0u32.into()); let value_bytes = value.encode(); - let value_len = value_bytes.len(); + let value_len = BalanceOf::::max_encoded_len(); let addr_len = T::AccountId::max_encoded_len(); // offsets where to place static data in contract memory @@ -1815,13 +1816,12 @@ benchmarks! { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "seal0", - name: "seal_instantiate", + module: "seal2", + name: "instantiate", params: vec![ - ValueType::I32, ValueType::I32, ValueType::I64, - ValueType::I32, + ValueType::I64, ValueType::I32, ValueType::I32, ValueType::I32, @@ -1850,10 +1850,9 @@ benchmarks! { ], call_body: Some(body::repeated_dyn(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::I64Const(0)), // ref_time weight + Regular(Instruction::I64Const(0)), // proof_size weight 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 @@ -1861,7 +1860,7 @@ benchmarks! { Regular(Instruction::I32Const(SENTINEL as i32)), // output_ptr Regular(Instruction::I32Const(0)), // output_len_ptr Regular(Instruction::I32Const(0)), // salt_ptr - Regular(Instruction::I32Const(0)), // salt_ptr_len + Regular(Instruction::I32Const(0)), // salt_len_ptr Regular(Instruction::Call(0)), Regular(Instruction::Drop), ])), diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 7d5d22a5abd82..c393b7b80d804 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -1476,8 +1476,7 @@ pub mod env { /// # Note /// /// The values `_code_hash_len` and `_value_len` are ignored because the encoded sizes - /// of those types are fixed through - /// [`codec::MaxEncodedLen`]. The fields exist + /// of those types are fixed through [`codec::MaxEncodedLen`]. The fields exist /// for backwards compatibility. Consider switching to the newest version of this function. #[prefixed_alias] fn instantiate( From 2cb30c70896d880945013b2f184c3c310c672dad Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Wed, 8 Mar 2023 19:18:09 +0200 Subject: [PATCH 08/29] add DefaultDepositLimit to pallet Config --- bin/node/runtime/src/lib.rs | 2 ++ frame/contracts/src/lib.rs | 4 ++++ frame/contracts/src/storage/meter.rs | 3 ++- frame/contracts/src/tests.rs | 3 +++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 3d160108f62bb..d261632c1f443 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1190,6 +1190,7 @@ impl pallet_tips::Config for Runtime { parameter_types! { pub const DepositPerItem: Balance = deposit(1, 0); pub const DepositPerByte: Balance = deposit(0, 1); + pub const DefaultDepositLimit: Balance = deposit(1024 , 1024 * 1024); pub const DeletionQueueDepth: u32 = 128; // The lazy deletion runs inside on_initialize. pub DeletionWeightLimit: Weight = RuntimeBlockWeights::get() @@ -1215,6 +1216,7 @@ impl pallet_contracts::Config for Runtime { type CallFilter = Nothing; type DepositPerItem = DepositPerItem; type DepositPerByte = DepositPerByte; + type DefaultDepositLimit = DefaultDepositLimit; type CallStack = [pallet_contracts::Frame; 5]; type WeightPrice = pallet_transaction_payment::Pallet; type WeightInfo = pallet_contracts::weights::SubstrateWeight; diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 4f5d7ba305fc1..78a736efe9737 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -280,6 +280,10 @@ pub mod pallet { #[pallet::constant] type DepositPerByte: Get>; + /// Fallback value to limit the storage deposit if it's not being set by the caller. + #[pallet::constant] + type DefaultDepositLimit: Get>; + /// The amount of balance a caller has to pay for each storage item. /// /// # Note diff --git a/frame/contracts/src/storage/meter.rs b/frame/contracts/src/storage/meter.rs index ef6fb3277c11b..22d1d662090a9 100644 --- a/frame/contracts/src/storage/meter.rs +++ b/frame/contracts/src/storage/meter.rs @@ -459,7 +459,8 @@ impl Ext for ReservingExt { let max = T::Currency::reducible_balance(origin, true) .saturating_sub(min_leftover) .saturating_sub(Pallet::::min_balance()); - let limit = limit.unwrap_or(max); + let default = max.min(T::DefaultDepositLimit::get()); + let limit = limit.unwrap_or(default); ensure!( limit <= max && matches!(T::Currency::can_withdraw(origin, limit), WithdrawConsequence::Success), diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index cc01e3fceb11f..4068c7933cbde 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -340,6 +340,8 @@ parameter_types! { }; pub static DepositPerByte: BalanceOf = 1; pub const DepositPerItem: BalanceOf = 2; + // We need this one set high enough for running benchmarks. + pub const DefaultDepositLimit: BalanceOf = 10_000_000; } impl Convert> for Test { @@ -400,6 +402,7 @@ impl Config for Test { type Schedule = MySchedule; type DepositPerByte = DepositPerByte; type DepositPerItem = DepositPerItem; + type DefaultDepositLimit = DefaultDepositLimit; type AddressGenerator = DefaultAddressGenerator; type MaxCodeLen = ConstU32<{ 123 * 1024 }>; type MaxStorageKeyLen = ConstU32<128>; From a968b29df87ab127f3c690dc8eb49d562accca70 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Thu, 9 Mar 2023 22:41:49 +0200 Subject: [PATCH 09/29] specify deposit limit for nested call add test for nested call deposit limit save: separate deposit limit for nested calls --- frame/contracts/fixtures/call_with_limit.wat | 3 +- frame/contracts/fixtures/caller_contract.wat | 6 +- .../fixtures/create_storage_and_call.wat | 13 ++- frame/contracts/src/exec.rs | 91 ++++++++++++++----- frame/contracts/src/storage/meter.rs | 76 ++++++++++++---- frame/contracts/src/tests.rs | 69 +++++++++++--- frame/contracts/src/wasm/mod.rs | 1 + frame/contracts/src/wasm/runtime.rs | 30 +++++- 8 files changed, 223 insertions(+), 66 deletions(-) diff --git a/frame/contracts/fixtures/call_with_limit.wat b/frame/contracts/fixtures/call_with_limit.wat index 6d5cc07d5af2f..04da59551a8ce 100644 --- a/frame/contracts/fixtures/call_with_limit.wat +++ b/frame/contracts/fixtures/call_with_limit.wat @@ -2,7 +2,7 @@ ;; It returns the result of the call as output data. (module (import "seal0" "seal_input" (func $seal_input (param i32 i32))) - (import "seal2" "call" (func $seal_call (param i32 i32 i64 i64 i32 i32 i32 i32 i32) (result i32))) + (import "seal2" "call" (func $seal_call (param i32 i32 i64 i64 i32 i32 i32 i32 i32 i32) (result i32))) (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) (import "env" "memory" (memory 1 1)) @@ -25,6 +25,7 @@ (i32.const 4) ;; Pointer to "callee" address. (i64.load (i32.const 36)) ;; How much ref_time to devote for the execution. (i64.load (i32.const 44)) ;; How much proof_size to devote for the execution. + (i32.const 0xffffffff) ;; u32 max sentinel value: pass no deposit limit. (i32.const 0) ;; Pointer to the buffer with value to transfer (i32.const 0) ;; Pointer to input data buffer address (i32.const 0) ;; Length of input data buffer diff --git a/frame/contracts/fixtures/caller_contract.wat b/frame/contracts/fixtures/caller_contract.wat index 6f2cb0af8a13a..c6d52194f5bc8 100644 --- a/frame/contracts/fixtures/caller_contract.wat +++ b/frame/contracts/fixtures/caller_contract.wat @@ -1,7 +1,7 @@ (module (import "seal0" "seal_input" (func $seal_input (param i32 i32))) (import "seal0" "seal_balance" (func $seal_balance (param i32 i32))) - (import "seal2" "call" (func $seal_call (param i32 i32 i64 i64 i32 i32 i32 i32 i32) (result i32))) + (import "seal2" "call" (func $seal_call (param i32 i32 i64 i64 i32 i32 i32 i32 i32 i32) (result i32))) (import "seal2" "instantiate" (func $seal_instantiate (param i32 i64 i64 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32) )) @@ -164,6 +164,7 @@ (i32.const 16) ;; Pointer to "callee" address. (i64.const 0) ;; How much ref_time weight to devote for the execution. 0 = all. (i64.const 0) ;; How much proof_size weight to devote for the execution. 0 = all. + (i32.const 0xffffffff) ;; u32 max sentinel value: pass no deposit limit. (i32.const 0) ;; Pointer to the buffer with value to transfer (i32.const 9) ;; Pointer to input data buffer address (i32.const 7) ;; Length of input data buffer @@ -195,6 +196,7 @@ (i32.const 16) ;; Pointer to "callee" address. (i64.const 1) ;; Supply too little ref_time weight (i64.const 0) ;; How much proof_size weight to devote for the execution. 0 = all. + (i32.const 0xffffffff) ;; u32 max sentinel value: pass no deposit limit. (i32.const 0) ;; Pointer to the buffer with value to transfer (i32.const 8) ;; Pointer to input data buffer address (i32.const 8) ;; Length of input data buffer @@ -215,6 +217,7 @@ (i32.const 16) ;; Pointer to "callee" address. (i64.const 0) ;; How much ref_time weight to devote for the execution. 0 = all. (i64.const 1) ;; Supply too little proof_size weight + (i32.const 0xffffffff) ;; u32 max sentinel value: pass no deposit limit. (i32.const 0) ;; Pointer to the buffer with value to transfer (i32.const 8) ;; Pointer to input data buffer address (i32.const 8) ;; Length of input data buffer @@ -247,6 +250,7 @@ (i32.const 16) ;; Pointer to "callee" address. (i64.const 0) ;; How much ref_time weight to devote for the execution. 0 = all. (i64.const 0) ;; How much proof_size weight to devote for the execution. 0 = all. + (i32.const 0xffffffff) ;; u32 max sentinel value: pass no deposit limit. (i32.const 0) ;; Pointer to the buffer with value to transfer (i32.const 8) ;; Pointer to input data buffer address (i32.const 8) ;; Length of input data buffer diff --git a/frame/contracts/fixtures/create_storage_and_call.wat b/frame/contracts/fixtures/create_storage_and_call.wat index 2a1e53f7ce08a..3265cb56b0ec3 100644 --- a/frame/contracts/fixtures/create_storage_and_call.wat +++ b/frame/contracts/fixtures/create_storage_and_call.wat @@ -2,7 +2,7 @@ (module (import "seal0" "seal_input" (func $seal_input (param i32 i32))) (import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32))) - (import "seal1" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32) (result i32))) + (import "seal2" "call" (func $seal_call (param i32 i32 i64 i64 i32 i32 i32 i32 i32 i32) (result i32))) (import "env" "memory" (memory 1 1)) (func $assert (param i32) @@ -20,7 +20,10 @@ ;; store length of input buffer (i32.store (i32.const 0) (i32.const 512)) - ;; copy input at address 4 + ;; copy input at address 4: + ;; first 4 bytes for the storage size + ;; next 32 bytes are for the callee address + ;; next bytes for the encoded deposit limit (call $seal_input (i32.const 4) (i32.const 0)) ;; create 4 byte of storage before calling @@ -34,8 +37,10 @@ (call $assert (i32.eqz (call $seal_call (i32.const 0) ;; No flags - (i32.const 8) ;; Pointer to "callee" address. - (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 8) ;; Pointer to "callee" address + (i64.const 0) ;; How much ref_time to devote for the execution. 0 = all + (i64.const 0) ;; How much proof_limit to devote for the execution. 0 = all + (i32.const 40) ;; Pointer to storage deposit limit (i32.const 512) ;; Pointer to the buffer with value to transfer (i32.const 4) ;; Pointer to input data buffer address (i32.const 4) ;; Length of input data buffer diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 37be12d50d689..6bb187d6aa5bf 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -23,7 +23,9 @@ use crate::{ }; use frame_support::{ crypto::ecdsa::ECDSAExt, - dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo, Dispatchable}, + dispatch::{ + fmt::Debug, DispatchError, DispatchResult, DispatchResultWithPostInfo, Dispatchable, + }, storage::{with_transaction, TransactionOutcome}, traits::{Contains, Currency, ExistenceRequirement, OriginTrait, Randomness, Time}, weights::Weight, @@ -34,7 +36,7 @@ use pallet_contracts_primitives::ExecReturnValue; use smallvec::{Array, SmallVec}; use sp_core::ecdsa::Public as ECDSAPublic; use sp_io::{crypto::secp256k1_ecdsa_recover_compressed, hashing::blake2_256}; -use sp_runtime::traits::{Convert, Hash}; +use sp_runtime::traits::{Convert, Hash, Zero}; use sp_std::{marker::PhantomData, mem, prelude::*}; pub type AccountIdOf = ::AccountId; @@ -124,6 +126,7 @@ pub trait Ext: sealing::Sealed { fn call( &mut self, gas_limit: Weight, + deposit_limit: BalanceOf, to: AccountIdOf, value: BalanceOf, input_data: Vec, @@ -701,8 +704,9 @@ where args, value, gas_meter, - storage_meter, Weight::zero(), + storage_meter, + BalanceOf::::zero(), schedule, determinism, )?; @@ -728,12 +732,13 @@ where /// /// This does not take `self` because when constructing the first frame `self` is /// not initialized, yet. - fn new_frame( + fn new_frame( frame_args: FrameArgs, value_transferred: BalanceOf, gas_meter: &mut GasMeter, - storage_meter: &mut storage::meter::GenericMeter, gas_limit: Weight, + storage_meter: &mut storage::meter::GenericMeter, + deposit_limit: BalanceOf, schedule: &Schedule, determinism: Determinism, ) -> Result<(Frame, E, Option), ExecError> { @@ -790,7 +795,7 @@ where account_id, entry_point, nested_gas: gas_meter.nested(gas_limit)?, - nested_storage: storage_meter.nested(), + nested_storage: storage_meter.nested(deposit_limit), allows_reentry: true, }; @@ -803,6 +808,7 @@ where frame_args: FrameArgs, value_transferred: BalanceOf, gas_limit: Weight, + deposit_limit: BalanceOf, ) -> Result { if self.frames.len() == T::CallStack::size() { return Err(Error::::MaxCallDepthReached.into()) @@ -826,8 +832,9 @@ where frame_args, value_transferred, nested_gas, - nested_storage, gas_limit, + nested_storage, + deposit_limit, self.schedule, self.determinism, )?; @@ -868,8 +875,10 @@ where return Ok(output) } - // Storage limit is enforced as late as possible (when the last frame returns) so that - // the ordering of storage accesses does not matter. + // Storage limit is normally enforced as late as possible (when the last frame returns) + // so that the ordering of storage accesses does not matter. + // (However, if a special limit was set for a sub-call, it should be enforced right + // after the sub-call returned. See below for this case of enforcement). if self.frames.is_empty() { let frame = &mut self.first_frame; frame.contract_info.load(&frame.account_id); @@ -878,7 +887,7 @@ where } let frame = self.top_frame(); - let account_id = &frame.account_id; + let account_id = &frame.account_id.clone(); match (entry_point, delegated_code_hash) { (ExportedFunction::Constructor, _) => { // It is not allowed to terminate a contract inside its constructor. @@ -902,9 +911,15 @@ where ); }, (ExportedFunction::Call, None) => { + // If a special limit was set for the sub-call, we enforce it here. + // The sub-call will be rolled back in case the limit is exhausted. + let frame = self.top_frame_mut(); + let contract = frame.contract_info.as_contract(); + frame.nested_storage.enforce_subcall_limit(contract)?; + let caller = self.caller(); Contracts::::deposit_event( - vec![T::Hashing::hash_of(caller), T::Hashing::hash_of(account_id)], + vec![T::Hashing::hash_of(caller), T::Hashing::hash_of(&account_id)], Event::Called { caller: caller.clone(), contract: account_id.clone() }, ); }, @@ -1116,6 +1131,7 @@ where fn call( &mut self, gas_limit: Weight, + deposit_limit: BalanceOf, to: T::AccountId, value: BalanceOf, input_data: Vec, @@ -1144,6 +1160,7 @@ where FrameArgs::Call { dest: to, cached_info, delegated_call: None }, value, gas_limit, + deposit_limit, )?; self.run(executable, input_data) }; @@ -1175,6 +1192,7 @@ where }, value, Weight::zero(), + BalanceOf::::zero(), )?; self.run(executable, input_data) } @@ -1199,6 +1217,7 @@ where }, value, gas_limit, + BalanceOf::::zero(), )?; let account_id = self.top_frame().account_id.clone(); self.run(executable, input_data).map(|ret| (account_id, ret)) @@ -1941,7 +1960,7 @@ mod tests { let value = Default::default(); let recurse_ch = MockLoader::insert(Call, |ctx, _| { // Try to call into yourself. - let r = ctx.ext.call(Weight::zero(), BOB, 0, vec![], true); + let r = ctx.ext.call(Weight::zero(), BalanceOf::::zero(), BOB, 0, vec![], true); ReachedBottom::mutate(|reached_bottom| { if !*reached_bottom { @@ -1995,7 +2014,11 @@ mod tests { WitnessedCallerBob::mutate(|caller| *caller = Some(ctx.ext.caller().clone())); // Call into CHARLIE contract. - assert_matches!(ctx.ext.call(Weight::zero(), CHARLIE, 0, vec![], true), Ok(_)); + assert_matches!( + ctx.ext + .call(Weight::zero(), BalanceOf::::zero(), CHARLIE, 0, vec![], true), + Ok(_) + ); exec_success() }); let charlie_ch = MockLoader::insert(Call, |ctx, _| { @@ -2129,7 +2152,8 @@ mod tests { // ALICE is the origin of the call stack assert!(ctx.ext.caller_is_origin()); // BOB calls CHARLIE - ctx.ext.call(Weight::zero(), CHARLIE, 0, vec![], true) + ctx.ext + .call(Weight::zero(), BalanceOf::::zero(), CHARLIE, 0, vec![], true) }); ExtBuilder::default().build().execute_with(|| { @@ -2160,7 +2184,11 @@ mod tests { assert_eq!(*ctx.ext.address(), BOB); // Call into charlie contract. - assert_matches!(ctx.ext.call(Weight::zero(), CHARLIE, 0, vec![], true), Ok(_)); + assert_matches!( + ctx.ext + .call(Weight::zero(), BalanceOf::::zero(), CHARLIE, 0, vec![], true), + Ok(_) + ); exec_success() }); let charlie_ch = MockLoader::insert(Call, |ctx, _| { @@ -2467,13 +2495,26 @@ mod tests { let info = ctx.ext.contract_info(); assert_eq!(info.storage_byte_deposit, 0); info.storage_byte_deposit = 42; - assert_eq!(ctx.ext.call(Weight::zero(), CHARLIE, 0, vec![], true), exec_trapped()); + assert_eq!( + ctx.ext.call( + Weight::zero(), + BalanceOf::::zero(), + CHARLIE, + 0, + vec![], + true + ), + exec_trapped() + ); assert_eq!(ctx.ext.contract_info().storage_byte_deposit, 42); } exec_success() }); let code_charlie = MockLoader::insert(Call, |ctx, _| { - assert!(ctx.ext.call(Weight::zero(), BOB, 0, vec![99], true).is_ok()); + assert!(ctx + .ext + .call(Weight::zero(), BalanceOf::::zero(), BOB, 0, vec![99], true) + .is_ok()); exec_trapped() }); @@ -2503,7 +2544,7 @@ mod tests { fn recursive_call_during_constructor_fails() { let code = MockLoader::insert(Constructor, |ctx, _| { assert_matches!( - ctx.ext.call(Weight::zero(), ctx.ext.address().clone(), 0, vec![], true), + ctx.ext.call(Weight::zero(), BalanceOf::::zero(), ctx.ext.address().clone(), 0, vec![], true), Err(ExecError{error, ..}) if error == >::ContractNotFound.into() ); exec_success() @@ -2642,7 +2683,7 @@ mod tests { // call the contract passed as input with disabled reentry let code_bob = MockLoader::insert(Call, |ctx, _| { let dest = Decode::decode(&mut ctx.input_data.as_ref()).unwrap(); - ctx.ext.call(Weight::zero(), dest, 0, vec![], false) + ctx.ext.call(Weight::zero(), BalanceOf::::zero(), dest, 0, vec![], false) }); let code_charlie = MockLoader::insert(Call, |_, _| exec_success()); @@ -2689,15 +2730,17 @@ mod tests { fn call_deny_reentry() { let code_bob = MockLoader::insert(Call, |ctx, _| { if ctx.input_data[0] == 0 { - ctx.ext.call(Weight::zero(), CHARLIE, 0, vec![], false) + ctx.ext + .call(Weight::zero(), BalanceOf::::zero(), CHARLIE, 0, vec![], false) } else { exec_success() } }); // call BOB with input set to '1' - let code_charlie = - MockLoader::insert(Call, |ctx, _| ctx.ext.call(Weight::zero(), BOB, 0, vec![1], true)); + let code_charlie = MockLoader::insert(Call, |ctx, _| { + ctx.ext.call(Weight::zero(), BalanceOf::::zero(), BOB, 0, vec![1], true) + }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); @@ -2905,7 +2948,9 @@ mod tests { .unwrap(); // a plain call should not influence the account counter - ctx.ext.call(Weight::zero(), account_id, 0, vec![], false).unwrap(); + ctx.ext + .call(Weight::zero(), BalanceOf::::zero(), account_id, 0, vec![], false) + .unwrap(); exec_success() }); diff --git a/frame/contracts/src/storage/meter.rs b/frame/contracts/src/storage/meter.rs index 22d1d662090a9..93bfd60545fab 100644 --- a/frame/contracts/src/storage/meter.rs +++ b/frame/contracts/src/storage/meter.rs @@ -23,13 +23,16 @@ use crate::{ }; use codec::Encode; use frame_support::{ - dispatch::DispatchError, + dispatch::{fmt::Debug, DispatchError}, ensure, traits::{tokens::WithdrawConsequence, Currency, ExistenceRequirement, Get}, DefaultNoBound, RuntimeDebugNoBound, }; use pallet_contracts_primitives::StorageDeposit as Deposit; -use sp_runtime::{traits::Saturating, FixedPointNumber, FixedU128}; +use sp_runtime::{ + traits::{Saturating, Zero}, + FixedPointNumber, FixedU128, +}; use sp_std::{marker::PhantomData, vec::Vec}; /// Deposit that uses the native currency's balance type. @@ -93,17 +96,27 @@ pub enum ReservingExt {} pub trait State: private::Sealed {} /// State parameter that constitutes a meter that is in its root state. -pub enum Root {} +#[derive(Default, Debug)] +pub enum Root { + #[default] + Unit, +} /// State parameter that constitutes a meter that is in its nested state. -pub enum Nested {} +/// Its value indicates whether the nested meter has its own limit. +#[derive(DefaultNoBound, RuntimeDebugNoBound)] +pub enum Nested { + #[default] + DerivedLimit, + OwnLimit, +} impl State for Root {} impl State for Nested {} /// A type that allows the metering of consumed or freed storage of a single contract call stack. #[derive(DefaultNoBound, RuntimeDebugNoBound)] -pub struct RawMeter { +pub struct RawMeter { /// The limit of how much balance this meter is allowed to consume. limit: BalanceOf, /// The amount of balance that was used in this meter and all of its already absorbed children. @@ -115,8 +128,10 @@ pub struct RawMeter { /// We only have one charge per contract hence the size of this vector is /// limited by the maximum call depth. charges: Vec>, + /// We store the nested state to determine if it has a special limit for sub-call. + nested: S, /// Type parameters are only used in impls. - _phantom: PhantomData<(E, S)>, + _phantom: PhantomData, } /// This type is used to describe a storage change when charging from the meter. @@ -211,6 +226,9 @@ impl Diff { /// this we can do all the refunds before doing any charge. This way a plain account can use /// more deposit than it has balance as along as it is covered by a refund. This /// essentially makes the order of storage changes irrelevant with regard to the deposit system. +/// The only exception is when a special (tougher) deposit limit is specified for a cross-contract +/// call. In that case the limit is enforced once the call is returned, rolling it back if +/// exhausted. #[derive(RuntimeDebugNoBound, Clone)] struct Charge { deposit_account: DepositAccount, @@ -252,16 +270,24 @@ impl RawMeter where T: Config, E: Ext, - S: State, + S: State + Default + Debug, { - /// Create a new child that has its `limit` set to whatever is remaining of it. + /// Create a new child that has its `limit`. + /// Passing `0` as the limit is interpreted as to take whatever is remaining from its parent. /// /// This is called whenever a new subcall is initiated in order to track the storage /// usage for this sub call separately. This is necessary because we want to exchange balance /// with the current contract we are interacting with. - pub fn nested(&self) -> RawMeter { + pub fn nested(&self, limit: BalanceOf) -> RawMeter { debug_assert!(self.is_alive()); - RawMeter { limit: self.available(), ..Default::default() } + // If a special limit is specified higher than it is available, + // we want to enforce the lesser limit to the nested meter, to fail in the sub-call. + let limit = self.available().min(limit); + if limit.is_zero() { + RawMeter { limit: self.available(), ..Default::default() } + } else { + RawMeter { limit, nested: Nested::OwnLimit, ..Default::default() } + } } /// Absorb a child that was spawned to handle a sub call. @@ -426,8 +452,10 @@ where /// /// # Note /// - /// We only need to call this **once** for every call stack and not for every cross contract - /// call. Hence this is only called when the last call frame returns. + /// We normally need to call this **once** for every call stack and not for every cross contract + /// call. However, if a dedicated limit is specified for a sub-call, this needs to be called + /// once the sub-call has returned. For this, the [`Self::enforce_subcall_limit`] wrapper is + /// used. pub fn enforce_limit( &mut self, info: Option<&mut ContractInfo>, @@ -445,6 +473,18 @@ where } Ok(()) } + + /// This is a wrapper around [`Self::enforce_limit`] to use on the exit from a sub-call to + /// enforce its special limit if needed. + pub fn enforce_subcall_limit( + &mut self, + info: Option<&mut ContractInfo>, + ) -> Result<(), DispatchError> { + match self.nested { + Nested::OwnLimit => self.enforce_limit(info), + Nested::DerivedLimit => Ok(()), + } + } } impl Ext for ReservingExt { @@ -671,7 +711,7 @@ mod tests { assert_eq!(meter.available(), 1_000); // an empty charge does not create a `Charge` entry - let mut nested0 = meter.nested(); + let mut nested0 = meter.nested(BalanceOf::::zero()); nested0.charge(&Default::default()); meter.absorb(nested0, DepositAccount(BOB), None); @@ -693,7 +733,7 @@ mod tests { let mut nested0_info = new_info(StorageInfo { bytes: 100, items: 5, bytes_deposit: 100, items_deposit: 10 }); - let mut nested0 = meter.nested(); + let mut nested0 = meter.nested(BalanceOf::::zero()); nested0.charge(&Diff { bytes_added: 108, bytes_removed: 5, @@ -704,13 +744,13 @@ mod tests { let mut nested1_info = new_info(StorageInfo { bytes: 100, items: 10, bytes_deposit: 100, items_deposit: 20 }); - let mut nested1 = nested0.nested(); + let mut nested1 = nested0.nested(BalanceOf::::zero()); nested1.charge(&Diff { items_removed: 5, ..Default::default() }); nested0.absorb(nested1, DepositAccount(CHARLIE), Some(&mut nested1_info)); let mut nested2_info = new_info(StorageInfo { bytes: 100, items: 7, bytes_deposit: 100, items_deposit: 20 }); - let mut nested2 = nested0.nested(); + let mut nested2 = nested0.nested(BalanceOf::::zero()); nested2.charge(&Diff { items_removed: 7, ..Default::default() }); nested0.absorb(nested2, DepositAccount(CHARLIE), Some(&mut nested2_info)); @@ -758,7 +798,7 @@ mod tests { let mut meter = TestMeter::new(&ALICE, Some(1_000), 0).unwrap(); assert_eq!(meter.available(), 1_000); - let mut nested0 = meter.nested(); + let mut nested0 = meter.nested(BalanceOf::::zero()); nested0.charge(&Diff { bytes_added: 5, bytes_removed: 1, @@ -769,7 +809,7 @@ mod tests { let mut nested1_info = new_info(StorageInfo { bytes: 100, items: 10, bytes_deposit: 100, items_deposit: 20 }); - let mut nested1 = nested0.nested(); + let mut nested1 = nested0.nested(BalanceOf::::zero()); nested1.charge(&Diff { items_removed: 5, ..Default::default() }); nested1.charge(&Diff { bytes_added: 20, ..Default::default() }); nested1.terminate(&nested1_info); diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index 4068c7933cbde..fa018f5f8fa09 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -16,6 +16,7 @@ // limitations under the License. use self::test_utils::hash; +use crate as pallet_contracts; use crate::{ chain_extension::{ ChainExtension, Environment, Ext, InitState, RegisteredChainExtension, @@ -26,7 +27,7 @@ use crate::{ wasm::{Determinism, PrefabWasmModule, ReturnCode as RuntimeReturnCode}, weights::WeightInfo, BalanceOf, Code, CodeStorage, Config, ContractInfo, ContractInfoOf, DefaultAddressGenerator, - DeletionQueue, Error, Pallet, Schedule, + DeletionQueue, Error, Pallet, Schedule, SENTINEL, }; use assert_matches::assert_matches; use codec::Encode; @@ -52,8 +53,6 @@ use sp_runtime::{ }; use std::{ops::Deref, sync::Arc}; -use crate as pallet_contracts; - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -2859,7 +2858,7 @@ fn gas_estimation_call_runtime() { } #[test] -fn gas_call_runtime_reentrancy_guarded() { +fn call_runtime_reentrancy_guarded() { let (caller_code, _caller_hash) = compile_module::("call_runtime").unwrap(); let (callee_code, _callee_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { @@ -4027,7 +4026,7 @@ fn storage_deposit_limit_is_enforced() { } #[test] -fn storage_deposit_limit_is_enforced_late() { +fn storage_deposit_limit_in_nested_calls() { let (wasm_caller, _code_hash_caller) = compile_module::("create_storage_and_call").unwrap(); let (wasm_callee, _code_hash_callee) = compile_module::("store").unwrap(); @@ -4073,27 +4072,35 @@ fn storage_deposit_limit_is_enforced_late() { 100u32.to_le_bytes().to_vec() )); - // We do not remove any storage but require 14 bytes of storage for the new - // storage created in the immediate contract. + // We do not remove any storage but add a storage item of 12 bytes in the caller + // contract. This would cost 12 + 2 = 14 Balance. + // The nested call doesn't get a special limit, which is set by passing SENTINEL as its + // reference. This should fail as the specified parent's limit is less than the cost: 13 < + // 14. assert_err_ignore_postinfo!( Contracts::call( RuntimeOrigin::signed(ALICE), addr_caller.clone(), 0, GAS_LIMIT, - Some(codec::Compact(5)), + Some(codec::Compact(13)), 100u32 .to_le_bytes() .as_ref() .iter() .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) + .chain(SENTINEL.to_le_bytes().as_ref()) .cloned() .collect(), ), >::StorageDepositLimitExhausted, ); - // Allow for the additional 14 bytes but demand an additional byte in the callee contract. + // Now we specify the parent's limit high enough to cover the caller's storage additions. + // However, we use a single byte more in the callee, hence the storage deposit should be 15 + // Balance. The nested call doesn't get a special limit, which is set by passing SENTINEL as + // its reference. This should fail as the specified parent's limit is less than the cost: 14 + // < 15. assert_err_ignore_postinfo!( Contracts::call( RuntimeOrigin::signed(ALICE), @@ -4106,14 +4113,40 @@ fn storage_deposit_limit_is_enforced_late() { .as_ref() .iter() .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) + .chain(SENTINEL.to_le_bytes().as_ref()) + .cloned() + .collect(), + ), + >::StorageDepositLimitExhausted, + ); + + // Now we specify the parent's limit high enough to cover both the caller's and callee's + // storage additions. However, we set a special deposit limit of 1 Balance for the nested + // call. This should fail as callee adds up 2 bytes to the storage, meaning that the nested + // call should have a deposit limit of at least 2 Balance. The sub-call should be rolled + // back, which is covered by the next test case. + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(ALICE), + addr_caller.clone(), + 0, + GAS_LIMIT, + Some(codec::Compact(16)), + 102u32 + .to_le_bytes() + .as_ref() + .iter() + .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) + .chain(&[1u8]) .cloned() .collect(), ), >::StorageDepositLimitExhausted, ); - // Refund in the callee contract but not enough to cover the 14 balance required by the - // caller. + // Refund in the callee contract but not enough to cover the 14 Balance required by the + // caller. Note that if previous sub-call wouldn't roll back, this call would pass making + // the test case fail. We don't specify special limit for the nested call here. assert_err_ignore_postinfo!( Contracts::call( RuntimeOrigin::signed(ALICE), @@ -4126,6 +4159,7 @@ fn storage_deposit_limit_is_enforced_late() { .as_ref() .iter() .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) + .chain(SENTINEL.to_le_bytes().as_ref()) .cloned() .collect(), ), @@ -4134,26 +4168,30 @@ fn storage_deposit_limit_is_enforced_late() { let _ = Balances::make_free_balance_be(&ALICE, 1_000); - // Send more than the sender has balance. + // Require more than the sender's balance. + // We don't specify special limit for the nested call. assert_err_ignore_postinfo!( Contracts::call( RuntimeOrigin::signed(ALICE), addr_caller.clone(), 0, GAS_LIMIT, - Some(codec::Compact(50)), + None, 1_200u32 .to_le_bytes() .as_ref() .iter() .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) + .chain(SENTINEL.to_le_bytes().as_ref()) .cloned() .collect(), ), >::StorageDepositLimitExhausted, ); - // Same as above but allow for the additional balance. + // Same as above but allow for the additional deposit of 1 Balance in parent. + // We set the special deposit limit of 1 Balance for the nested call, which isn't + // enforced as callee frees up storage. This should pass. assert_ok!(Contracts::call( RuntimeOrigin::signed(ALICE), addr_caller.clone(), @@ -4165,6 +4203,7 @@ fn storage_deposit_limit_is_enforced_late() { .as_ref() .iter() .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) + .chain(&[1u8]) .cloned() .collect(), )); @@ -4285,7 +4324,7 @@ fn deposit_limit_honors_min_leftover() { assert_eq!(get_contract(&addr).total_deposit(), min_balance); assert_eq!(::Currency::total_balance(&addr), min_balance); - // check that the minumum leftover (value send) is considered + // check that the minimum leftover (value send) is considered assert_err_ignore_postinfo!( Contracts::call( RuntimeOrigin::signed(BOB), diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index 2ddd3c01db63e..90108d1244121 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -471,6 +471,7 @@ mod tests { fn call( &mut self, _gas_limit: Weight, + _deposit_limit: BalanceOf, to: AccountIdOf, value: u64, data: Vec, diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index c393b7b80d804..ec3ec20076887 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -437,7 +437,7 @@ bitflags! { /// The kind of call that should be performed. enum CallType { /// Execute another instantiated contract - Call { callee_ptr: u32, value_ptr: u32, weight: Weight }, + Call { callee_ptr: u32, value_ptr: u32, deposit_ptr: u32, weight: Weight }, /// Execute deployed code in the context (storage, account ID, value) of the caller contract DelegateCall { code_hash_ptr: u32 }, } @@ -894,9 +894,14 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { }; let call_outcome = match call_type { - CallType::Call { callee_ptr, value_ptr, weight } => { + CallType::Call { callee_ptr, value_ptr, deposit_ptr, weight } => { let callee: <::T as frame_system::Config>::AccountId = self.read_sandbox_memory_as(memory, callee_ptr)?; + let deposit_limit: BalanceOf<::T> = if deposit_ptr == SENTINEL { + BalanceOf::<::T>::zero() + } else { + self.read_sandbox_memory_as(memory, deposit_ptr)? + }; let value: BalanceOf<::T> = self.read_sandbox_memory_as(memory, value_ptr)?; if value > 0u32.into() { @@ -904,6 +909,7 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { } self.ext.call( weight, + deposit_limit, callee, value, input_data, @@ -1322,7 +1328,12 @@ pub mod env { ctx.call( memory, CallFlags::ALLOW_REENTRY, - CallType::Call { callee_ptr, value_ptr, weight: Weight::from_parts(gas, 0) }, + CallType::Call { + callee_ptr, + value_ptr, + deposit_ptr: SENTINEL, + weight: Weight::from_parts(gas, 0), + }, input_data_ptr, input_data_len, output_ptr, @@ -1352,7 +1363,12 @@ pub mod env { ctx.call( memory, CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, - CallType::Call { callee_ptr, value_ptr, weight: Weight::from_parts(gas, 0) }, + CallType::Call { + callee_ptr, + value_ptr, + deposit_ptr: SENTINEL, + weight: Weight::from_parts(gas, 0), + }, input_data_ptr, input_data_len, output_ptr, @@ -1373,6 +1389,10 @@ pub mod env { /// `T::AccountId`. Traps otherwise. /// - `ref_time`: how much *ref_time* Weight to devote to the execution. /// - `proof_limit`: how much *proof_limit* Weight to devote to the execution. + /// - `deposit_ptr`: a pointer to the buffer with value of the storage deposit limit for the + /// call. Should be decodable as a `T::Balance`. Traps otherwise. Passing `SENTINEL` means + /// setting no specific limit for the call, which implies storage usage up to the limit of the + /// parent call. /// - `value_ptr`: a pointer to the buffer with value, how much value to send. Should be /// decodable as a `T::Balance`. Traps otherwise. /// - `input_data_ptr`: a pointer to a buffer to be used as input data to the callee. @@ -1399,6 +1419,7 @@ pub mod env { callee_ptr: u32, ref_time: u64, proof_limit: u64, + deposit_ptr: u32, value_ptr: u32, input_data_ptr: u32, input_data_len: u32, @@ -1411,6 +1432,7 @@ pub mod env { CallType::Call { callee_ptr, value_ptr, + deposit_ptr, weight: Weight::from_parts(ref_time, proof_limit), }, input_data_ptr, From 45b48c2f27f1b8390eab35e8cefaa3dc4f3a06c8 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Mon, 13 Mar 2023 19:49:33 +0200 Subject: [PATCH 10/29] specify deposit limit for nested instantiate save: works with test cleaned up debugging outputs --- frame/contracts/fixtures/caller_contract.wat | 6 +- .../fixtures/create_storage_and_call.wat | 4 +- frame/contracts/fixtures/store.wat | 45 ---- frame/contracts/src/exec.rs | 35 ++- frame/contracts/src/storage/meter.rs | 2 +- frame/contracts/src/tests.rs | 224 ++++++++++++++++-- frame/contracts/src/wasm/mod.rs | 1 + frame/contracts/src/wasm/runtime.rs | 23 +- 8 files changed, 266 insertions(+), 74 deletions(-) delete mode 100644 frame/contracts/fixtures/store.wat diff --git a/frame/contracts/fixtures/caller_contract.wat b/frame/contracts/fixtures/caller_contract.wat index c6d52194f5bc8..929171b9a26f6 100644 --- a/frame/contracts/fixtures/caller_contract.wat +++ b/frame/contracts/fixtures/caller_contract.wat @@ -3,7 +3,7 @@ (import "seal0" "seal_balance" (func $seal_balance (param i32 i32))) (import "seal2" "call" (func $seal_call (param i32 i32 i64 i64 i32 i32 i32 i32 i32 i32) (result i32))) (import "seal2" "instantiate" (func $seal_instantiate - (param i32 i64 i64 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32) + (param i32 i64 i64 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32) )) (import "env" "memory" (memory 1 1)) @@ -45,6 +45,7 @@ (i32.const 24) ;; Pointer to the code hash. (i64.const 0) ;; How much ref_time weight to devote for the execution. 0 = all. (i64.const 0) ;; How much proof_size weight to devote for the execution. 0 = all. + (i32.const 0xffffffff) ;; u32 max sentinel value: pass no deposit limit. (i32.const 0) ;; Pointer to the buffer with value to transfer (i32.const 9) ;; Pointer to input data buffer address (i32.const 7) ;; Length of input data buffer @@ -68,6 +69,7 @@ (i32.const 24) ;; Pointer to the code hash. (i64.const 1) ;; Supply too little ref_time weight (i64.const 0) ;; How much proof_size weight to devote for the execution. 0 = all. + (i32.const 0xffffffff) ;; u32 max sentinel value: pass no deposit limit. (i32.const 0) ;; Pointer to the buffer with value to transfer (i32.const 8) ;; Pointer to input data buffer address (i32.const 8) ;; Length of input data buffer @@ -92,6 +94,7 @@ (i32.const 24) ;; Pointer to the code hash. (i64.const 0) ;; How much ref_time weight to devote for the execution. 0 = all. (i64.const 1) ;; Supply too little proof_size weight + (i32.const 0xffffffff) ;; u32 max sentinel value: pass no deposit limit. (i32.const 0) ;; Pointer to the buffer with value to transfer (i32.const 8) ;; Pointer to input data buffer address (i32.const 8) ;; Length of input data buffer @@ -122,6 +125,7 @@ (i32.const 24) ;; Pointer to the code hash. (i64.const 0) ;; How much ref_time weight to devote for the execution. 0 = all. (i64.const 0) ;; How much proof_size weight to devote for the execution. 0 = all. + (i32.const 0xffffffff) ;; u32 max sentinel value: pass no deposit limit. (i32.const 0) ;; Pointer to the buffer with value to transfer (i32.const 8) ;; Pointer to input data buffer address (i32.const 8) ;; Length of input data buffer diff --git a/frame/contracts/fixtures/create_storage_and_call.wat b/frame/contracts/fixtures/create_storage_and_call.wat index 3265cb56b0ec3..5592e7e96a980 100644 --- a/frame/contracts/fixtures/create_storage_and_call.wat +++ b/frame/contracts/fixtures/create_storage_and_call.wat @@ -21,7 +21,7 @@ (i32.store (i32.const 0) (i32.const 512)) ;; copy input at address 4: - ;; first 4 bytes for the storage size + ;; first 4 bytes for the size of the storage to be created in callee ;; next 32 bytes are for the callee address ;; next bytes for the encoded deposit limit (call $seal_input (i32.const 4) (i32.const 0)) @@ -40,7 +40,7 @@ (i32.const 8) ;; Pointer to "callee" address (i64.const 0) ;; How much ref_time to devote for the execution. 0 = all (i64.const 0) ;; How much proof_limit to devote for the execution. 0 = all - (i32.const 40) ;; Pointer to storage deposit limit + (i32.const 40) ;; Pointer to the storage deposit limit (i32.const 512) ;; Pointer to the buffer with value to transfer (i32.const 4) ;; Pointer to input data buffer address (i32.const 4) ;; Length of input data buffer diff --git a/frame/contracts/fixtures/store.wat b/frame/contracts/fixtures/store.wat deleted file mode 100644 index 9e090d31801f8..0000000000000 --- a/frame/contracts/fixtures/store.wat +++ /dev/null @@ -1,45 +0,0 @@ -;; Stores a value of the passed size. -(module - (import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32))) - (import "seal0" "seal_input" (func $seal_input (param i32 i32))) - (import "env" "memory" (memory 16 16)) - - ;; [0, 32) storage key - (data (i32.const 0) "\01") - - ;; [32, 36) buffer where input is copied (expected size of storage item) - - ;; [36, 40) size of the input buffer - (data (i32.const 36) "\04") - - (func $assert (param i32) - (block $ok - (br_if $ok - (get_local 0) - ) - (unreachable) - ) - ) - - (func (export "call") - (call $seal_input (i32.const 32) (i32.const 36)) - - ;; assert input size == 4 - (call $assert - (i32.eq - (i32.load (i32.const 36)) - (i32.const 4) - ) - ) - - ;; place a value in storage, the size of which is specified by the call input. - ;; we don't care about the contents of the storage item - (call $seal_set_storage - (i32.const 0) ;; Pointer to storage key - (i32.const 0) ;; Pointer to value - (i32.load (i32.const 32)) ;; Size of value - ) - ) - - (func (export "deploy")) -) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 6bb187d6aa5bf..3ad89455ae271 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -150,6 +150,7 @@ pub trait Ext: sealing::Sealed { fn instantiate( &mut self, gas_limit: Weight, + deposit_limit: BalanceOf, code: CodeHash, value: BalanceOf, input_data: Vec, @@ -895,6 +896,13 @@ where return Err(Error::::TerminatedInConstructor.into()) } + // If a special limit was set for the sub-call, we enforce it here. + // This is needed because contract constructor might write to storage. + // The sub-call will be rolled back in case the limit is exhausted. + let frame = self.top_frame_mut(); + let contract = frame.contract_info.as_contract(); + frame.nested_storage.enforce_subcall_limit(contract)?; + // Deposit an instantiation event. Contracts::::deposit_event( vec![T::Hashing::hash_of(self.caller()), T::Hashing::hash_of(account_id)], @@ -1200,6 +1208,7 @@ where fn instantiate( &mut self, gas_limit: Weight, + deposit_limit: BalanceOf, code_hash: CodeHash, value: BalanceOf, input_data: Vec, @@ -1217,7 +1226,7 @@ where }, value, gas_limit, - BalanceOf::::zero(), + deposit_limit, )?; let account_id = self.top_frame().account_id.clone(); self.run(executable, input_data).map(|ret| (account_id, ret)) @@ -2339,6 +2348,7 @@ mod tests { .ext .instantiate( Weight::zero(), + BalanceOf::::zero(), dummy_ch, ::Currency::minimum_balance(), vec![], @@ -2403,6 +2413,7 @@ mod tests { assert_matches!( ctx.ext.instantiate( Weight::zero(), + BalanceOf::::zero(), dummy_ch, ::Currency::minimum_balance(), vec![], @@ -2927,6 +2938,7 @@ mod tests { ctx.ext .instantiate( Weight::zero(), + BalanceOf::::zero(), fail_code, ctx.ext.minimum_balance() * 100, vec![], @@ -2940,6 +2952,7 @@ mod tests { .ext .instantiate( Weight::zero(), + BalanceOf::::zero(), success_code, ctx.ext.minimum_balance() * 100, vec![], @@ -3454,7 +3467,14 @@ mod tests { assert_eq!(ctx.ext.nonce(), 1); // Should not change with a failed instantiation assert_err!( - ctx.ext.instantiate(Weight::zero(), fail_code, 0, vec![], &[],), + ctx.ext.instantiate( + Weight::zero(), + BalanceOf::::zero(), + fail_code, + 0, + vec![], + &[], + ), ExecError { error: >::ContractTrapped.into(), origin: ErrorOrigin::Callee @@ -3462,7 +3482,16 @@ mod tests { ); assert_eq!(ctx.ext.nonce(), 1); // Successful instantation increments - ctx.ext.instantiate(Weight::zero(), success_code, 0, vec![], &[]).unwrap(); + ctx.ext + .instantiate( + Weight::zero(), + BalanceOf::::zero(), + success_code, + 0, + vec![], + &[], + ) + .unwrap(); assert_eq!(ctx.ext.nonce(), 2); exec_success() }); diff --git a/frame/contracts/src/storage/meter.rs b/frame/contracts/src/storage/meter.rs index 93bfd60545fab..61937a310d3df 100644 --- a/frame/contracts/src/storage/meter.rs +++ b/frame/contracts/src/storage/meter.rs @@ -420,7 +420,7 @@ where self.total_deposit = deposit.clone(); info.storage_base_deposit = deposit.charge_or_zero(); - // Usually, deposit charges are deferred to be able to coalesce them with refunds. + // Normally, deposit charges are deferred to be able to coalesce them with refunds. // However, we need to charge immediately so that the account is created before // charges possibly below the ed are collected and fail. E::charge( diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index fa018f5f8fa09..2433ccec83800 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -27,7 +27,7 @@ use crate::{ wasm::{Determinism, PrefabWasmModule, ReturnCode as RuntimeReturnCode}, weights::WeightInfo, BalanceOf, Code, CodeStorage, Config, ContractInfo, ContractInfoOf, DefaultAddressGenerator, - DeletionQueue, Error, Pallet, Schedule, SENTINEL, + DeletionQueue, Error, Pallet, Schedule, }; use assert_matches::assert_matches; use codec::Encode; @@ -44,6 +44,7 @@ use frame_support::{ }; use frame_system::{self as system, EventRecord, Phase}; use pretty_assertions::{assert_eq, assert_ne}; +use sp_core::ByteArray; use sp_io::hashing::blake2_256; use sp_keystore::{testing::KeyStore, KeystoreExt}; use sp_runtime::{ @@ -1108,7 +1109,7 @@ fn cannot_self_destruct_through_draning() { #[test] fn cannot_self_destruct_through_storage_refund_after_price_change() { - let (wasm, _code_hash) = compile_module::("store").unwrap(); + let (wasm, _code_hash) = compile_module::("store_call").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); let min_balance = ::Currency::minimum_balance(); @@ -1686,7 +1687,7 @@ fn instantiate_return_code() { assert_return_code!(result, RuntimeReturnCode::TransferFailed); // Contract has enough total balance in order to not go below the min_balance - // threshold when transfering the balance but this balance is reserved so + // threshold when transferring the balance but this balance is reserved so // the transfer still fails. Balances::make_free_balance_be(&addr, min_balance + 10_000); Balances::reserve(&addr, min_balance + 10_000).unwrap(); @@ -3986,7 +3987,7 @@ fn set_code_hash() { #[test] fn storage_deposit_limit_is_enforced() { - let (wasm, _code_hash) = compile_module::("store").unwrap(); + let (wasm, _code_hash) = compile_module::("store_call").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); let min_balance = ::Currency::minimum_balance(); @@ -4026,10 +4027,10 @@ fn storage_deposit_limit_is_enforced() { } #[test] -fn storage_deposit_limit_in_nested_calls() { +fn deposit_limit_in_nested_calls() { let (wasm_caller, _code_hash_caller) = compile_module::("create_storage_and_call").unwrap(); - let (wasm_callee, _code_hash_callee) = compile_module::("store").unwrap(); + let (wasm_callee, _code_hash_callee) = compile_module::("store_call").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); @@ -4074,8 +4075,8 @@ fn storage_deposit_limit_in_nested_calls() { // We do not remove any storage but add a storage item of 12 bytes in the caller // contract. This would cost 12 + 2 = 14 Balance. - // The nested call doesn't get a special limit, which is set by passing SENTINEL as its - // reference. This should fail as the specified parent's limit is less than the cost: 13 < + // The nested call doesn't get a special limit, which is set by passing 0 to it. + // This should fail as the specified parent's limit is less than the cost: 13 < // 14. assert_err_ignore_postinfo!( Contracts::call( @@ -4089,7 +4090,7 @@ fn storage_deposit_limit_in_nested_calls() { .as_ref() .iter() .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) - .chain(SENTINEL.to_le_bytes().as_ref()) + .chain(0u64.to_le_bytes().as_ref()) .cloned() .collect(), ), @@ -4098,8 +4099,9 @@ fn storage_deposit_limit_in_nested_calls() { // Now we specify the parent's limit high enough to cover the caller's storage additions. // However, we use a single byte more in the callee, hence the storage deposit should be 15 - // Balance. The nested call doesn't get a special limit, which is set by passing SENTINEL as - // its reference. This should fail as the specified parent's limit is less than the cost: 14 + // Balance. + // The nested call doesn't get a special limit, which is set by passing 0 to it. + // This should fail as the specified parent's limit is less than the cost: 14 // < 15. assert_err_ignore_postinfo!( Contracts::call( @@ -4113,7 +4115,7 @@ fn storage_deposit_limit_in_nested_calls() { .as_ref() .iter() .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) - .chain(SENTINEL.to_le_bytes().as_ref()) + .chain(0u64.to_le_bytes().as_ref()) .cloned() .collect(), ), @@ -4146,7 +4148,7 @@ fn storage_deposit_limit_in_nested_calls() { // Refund in the callee contract but not enough to cover the 14 Balance required by the // caller. Note that if previous sub-call wouldn't roll back, this call would pass making - // the test case fail. We don't specify special limit for the nested call here. + // the test case fail. We don't set a special limit for the nested call here. assert_err_ignore_postinfo!( Contracts::call( RuntimeOrigin::signed(ALICE), @@ -4159,7 +4161,7 @@ fn storage_deposit_limit_in_nested_calls() { .as_ref() .iter() .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) - .chain(SENTINEL.to_le_bytes().as_ref()) + .chain(0u64.to_le_bytes().as_ref()) .cloned() .collect(), ), @@ -4169,7 +4171,7 @@ fn storage_deposit_limit_in_nested_calls() { let _ = Balances::make_free_balance_be(&ALICE, 1_000); // Require more than the sender's balance. - // We don't specify special limit for the nested call. + // We don't set a special limit for the nested call. assert_err_ignore_postinfo!( Contracts::call( RuntimeOrigin::signed(ALICE), @@ -4182,7 +4184,7 @@ fn storage_deposit_limit_in_nested_calls() { .as_ref() .iter() .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) - .chain(SENTINEL.to_le_bytes().as_ref()) + .chain(0u64.to_le_bytes().as_ref()) .cloned() .collect(), ), @@ -4210,9 +4212,193 @@ fn storage_deposit_limit_in_nested_calls() { }); } +#[test] +fn deposit_limit_in_nested_instantiate() { + let (wasm_caller, _code_hash_caller) = + compile_module::("create_storage_and_instantiate").unwrap(); + let (wasm_callee, code_hash_callee) = compile_module::("store_deploy").unwrap(); + const ED: u64 = 5; + ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + let _ = Balances::deposit_creating(&BOB, 1_000_000); + // Create caller contract + let addr_caller = Contracts::bare_instantiate( + ALICE, + 10_000u64, // this balance is later passed to the deployed contract + GAS_LIMIT, + None, + Code::Upload(wasm_caller), + vec![], + vec![], + false, + ) + .result + .unwrap() + .account_id; + // Deploy a contract to get its occupied storage size + let addr = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(wasm_callee), + vec![0, 0, 0, 0], + vec![], + false, + ) + .result + .unwrap() + .account_id; + + let callee_info_len = ContractInfoOf::::get(&addr).unwrap().encoded_size() as u64; + + // We don't set a special deposit limit for the netsted instantiation. + // + // The deposit limit set for the parent is insufficient for the instantiation, which + // requires: + // - callee_info_len + 2 for storing the new contract info, + // - ED for deployed contract account, + // - 2 for the storage item of 0 bytes being created in the callee constructor + // or (callee_info_len + 2 + ED + 2) Balance in total. + // + // Provided the limit is set to be 1 Balance less, + // this call should fail on the return from the caller contract. + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(BOB), + addr_caller.clone(), + 0, + GAS_LIMIT, + Some(codec::Compact(callee_info_len + 2 + ED + 1)), + 0u32.to_le_bytes() + .as_ref() + .iter() + .chain(code_hash_callee.as_ref()) + .chain(0u64.to_le_bytes().as_ref()) + .cloned() + .collect(), + ), + >::StorageDepositLimitExhausted, + ); + // The charges made on instantiation should be rolled back. + assert_eq!(::Currency::free_balance(&BOB), 1_000_000); + + // Now we give enough limit for the instantiation itself, but require for 1 more storage + // byte in the constructor. Hence +1 Balance to the limit is needed. This should fail on the + // return from constructor. + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(BOB), + addr_caller.clone(), + 0, + GAS_LIMIT, + Some(codec::Compact(callee_info_len + 2 + ED + 2)), + 1u32.to_le_bytes() + .as_ref() + .iter() + .chain(code_hash_callee.as_ref()) + .chain(0u64.to_le_bytes().as_ref()) + .cloned() + .collect(), + ), + >::StorageDepositLimitExhausted, + ); + // The charges made on the instantiation should be rolled back. + assert_eq!(::Currency::free_balance(&BOB), 1_000_000); + + // Now we set enough limit in parent call, but an insufficient limit for child instantiate. + // This should fail during the charging for the instantiation in + // `RawMeter::charge_instantiate()` + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(BOB), + addr_caller.clone(), + 0, + GAS_LIMIT, + Some(codec::Compact(callee_info_len + 2 + ED + 2)), + 0u32.to_le_bytes() + .as_ref() + .iter() + .chain(code_hash_callee.as_ref()) + .chain((callee_info_len + 2 + ED + 1).to_le_bytes().as_ref()) + .cloned() + .collect(), + ), + >::StorageDepositLimitExhausted, + ); + // The charges made on the instantiation should be rolled back. + assert_eq!(::Currency::free_balance(&BOB), 1_000_000); + + // Same as above but requires for single added storage + // item of 1 byte to be covered by the limit, which implies 3 more Balance. + // Now we set enough limit for the parent call, but insufficient limit for child + // instantiate. This should fail right after the constructor execution. + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(BOB), + addr_caller.clone(), + 0, + GAS_LIMIT, + Some(codec::Compact(callee_info_len + 2 + ED + 3)), // enough parent limit + 1u32.to_le_bytes() + .as_ref() + .iter() + .chain(code_hash_callee.as_ref()) + .chain((callee_info_len + 2 + ED + 2).to_le_bytes().as_ref()) // not enough child limit + .cloned() + .collect(), + ), + >::StorageDepositLimitExhausted, + ); + // The charges made on the instantiation should be rolled back. + assert_eq!(::Currency::free_balance(&BOB), 1_000_000); + + // Set enough deposit limit for the child instantiate. This should succeed. + let result = Contracts::bare_call( + BOB, + addr_caller.clone(), + 0, + GAS_LIMIT, + Some(codec::Compact(callee_info_len + 2 + ED + 4).into()), + 1u32.to_le_bytes() + .as_ref() + .iter() + .chain(code_hash_callee.as_ref()) + .chain((callee_info_len + 2 + ED + 3).to_le_bytes().as_ref()) // child limit is tougher but is still enough + .cloned() + .collect(), + false, + Determinism::Deterministic, + ); + + let returned = result.result.unwrap(); + // All balance of the caller except ED has been transferred to the callee. + // No deposit has been taken from it. + assert_eq!(::Currency::free_balance(&addr_caller), ED); + // Get address of the deployed contract. + let addr_callee = AccountId32::from_slice(&returned.data[0..32]).unwrap(); + // 10_000 should be sent to callee from the caller contract, plus ED to be sent from the + // origin. + assert_eq!(::Currency::free_balance(&addr_callee), 10_000 + ED); + // The origin should be charged with: + // - callee instantiation deposit = (callee_info_len + 2) + // - callee account ED + // - for writing an item of 1 byte to storage = 3 Balance + // + // Still, the latter is to be charged at the end of the call stack, hence + // only (callee_info_len + 2 + ED) is charged so far + assert_eq!( + ::Currency::free_balance(&BOB), + 1_000_000 - (callee_info_len + 2 + ED) + ); + // Check that deposit due to be charged still includes these 3 Balance + assert_eq!(result.storage_deposit.charge_or_zero(), (callee_info_len + 2 + ED + 3),) + }); +} + #[test] fn deposit_limit_honors_liquidity_restrictions() { - let (wasm, _code_hash) = compile_module::("store").unwrap(); + let (wasm, _code_hash) = compile_module::("store_call").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); let _ = Balances::deposit_creating(&BOB, 1_000); @@ -4256,7 +4442,7 @@ fn deposit_limit_honors_liquidity_restrictions() { #[test] fn deposit_limit_honors_existential_deposit() { - let (wasm, _code_hash) = compile_module::("store").unwrap(); + let (wasm, _code_hash) = compile_module::("store_call").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); let _ = Balances::deposit_creating(&BOB, 1_000); @@ -4299,7 +4485,7 @@ fn deposit_limit_honors_existential_deposit() { #[test] fn deposit_limit_honors_min_leftover() { - let (wasm, _code_hash) = compile_module::("store").unwrap(); + let (wasm, _code_hash) = compile_module::("store_call").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); let _ = Balances::deposit_creating(&BOB, 1_000); diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index 90108d1244121..89b7007b73dd6 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -491,6 +491,7 @@ mod tests { fn instantiate( &mut self, gas_limit: Weight, + _deposit_limit: BalanceOf, code_hash: CodeHash, value: u64, data: Vec, diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index ec3ec20076887..c5f692193c67f 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -954,6 +954,7 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { memory: &mut [u8], code_hash_ptr: u32, weight: Weight, + deposit_ptr: u32, value_ptr: u32, input_data_ptr: u32, input_data_len: u32, @@ -965,6 +966,11 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { salt_len: u32, ) -> Result { self.charge_gas(RuntimeCosts::InstantiateBase { input_data_len, salt_len })?; + let deposit_limit: BalanceOf<::T> = if deposit_ptr == SENTINEL { + BalanceOf::<::T>::zero() + } else { + self.read_sandbox_memory_as(memory, deposit_ptr)? + }; let value: BalanceOf<::T> = self.read_sandbox_memory_as(memory, value_ptr)?; if value > 0u32.into() { self.charge_gas(RuntimeCosts::InstantiateSurchargeTransfer)?; @@ -973,7 +979,8 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { self.read_sandbox_memory_as(memory, code_hash_ptr)?; let input_data = self.read_sandbox_memory(memory, input_data_ptr, input_data_len)?; let salt = self.read_sandbox_memory(memory, salt_ptr, salt_len)?; - let instantiate_outcome = self.ext.instantiate(weight, code_hash, value, input_data, &salt); + let instantiate_outcome = + self.ext.instantiate(weight, deposit_limit, code_hash, value, input_data, &salt); if let Ok((address, output)) = &instantiate_outcome { if !output.flags.contains(ReturnFlags::REVERT) { self.write_sandbox_output( @@ -1522,6 +1529,7 @@ pub mod env { memory, code_hash_ptr, Weight::from_parts(gas, 0), + SENTINEL, value_ptr, input_data_ptr, input_data_len, @@ -1560,6 +1568,7 @@ pub mod env { memory, code_hash_ptr, Weight::from_parts(gas, 0), + SENTINEL, value_ptr, input_data_ptr, input_data_len, @@ -1588,14 +1597,20 @@ pub mod env { /// - `code_hash_ptr`: a pointer to the buffer that contains the initializer code. /// - `ref_time`: how much *ref_time* Weight to devote to the execution. /// - `proof_limit`: how much *proof_limit* Weight to devote to the execution. + /// - `deposit_ptr`: a pointer to the buffer with value of the storage deposit limit for + /// instantiation. Should be decodable as a `T::Balance`. Traps otherwise. Passing `SENTINEL` + /// means setting no specific limit for the call, which implies storage usage up to the limit + /// of the parent call. /// - `value_ptr`: a pointer to the buffer with value, how much value to send. Should be /// decodable as a `T::Balance`. Traps otherwise. /// - `input_data_ptr`: a pointer to a buffer to be used as input data to the initializer code. /// - `input_data_len`: length of the input data buffer. - /// - `address_ptr`: a pointer where the new account's address is copied to. + /// - `address_ptr`: a pointer where the new account's address is copied to. `SENTINEL` means + /// not to copy. /// - `address_len_ptr`: in-out pointer to where the length of the buffer is read from and the /// actual length is written to. - /// - `output_ptr`: a pointer where the output buffer is copied to. + /// - `output_ptr`: a pointer where the output buffer is copied to. `SENTINEL` means not to + /// copy. /// - `output_len_ptr`: in-out pointer to where the length of the buffer is read from and the /// actual length is written to. /// - `salt_ptr`: Pointer to raw bytes used for address derivation. See `fn contract_address`. @@ -1621,6 +1636,7 @@ pub mod env { code_hash_ptr: u32, ref_time: u64, proof_limit: u64, + deposit_ptr: u32, value_ptr: u32, input_data_ptr: u32, input_data_len: u32, @@ -1635,6 +1651,7 @@ pub mod env { memory, code_hash_ptr, Weight::from_parts(ref_time, proof_limit), + deposit_ptr, value_ptr, input_data_ptr, input_data_len, From edf1aacef37ea1cb08252c59a44f5db599029d36 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Tue, 14 Mar 2023 20:52:38 +0200 Subject: [PATCH 11/29] update benchmarks --- bin/node/runtime/src/lib.rs | 2 +- frame/contracts/src/benchmarking/mod.rs | 36 +++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 5cea910424d32..7c5001bfe5577 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1183,7 +1183,7 @@ impl pallet_tips::Config for Runtime { parameter_types! { pub const DepositPerItem: Balance = deposit(1, 0); pub const DepositPerByte: Balance = deposit(0, 1); - pub const DefaultDepositLimit: Balance = deposit(1024 , 1024 * 1024); + pub const DefaultDepositLimit: Balance = deposit(1024, 1024 * 1024); pub const DeletionQueueDepth: u32 = 128; // The lazy deletion runs inside on_initialize. pub DeletionWeightLimit: Weight = RuntimeBlockWeights::get() diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index 22173843dfadc..3aef24e4bf36d 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -1619,6 +1619,17 @@ benchmarks! { let value: BalanceOf = 0u32.into(); let value_bytes = value.encode(); let value_len = BalanceOf::::max_encoded_len() as u32; + // Set an own limit every 2nd call + let deposits = (0..r * API_BENCHMARK_BATCH_SIZE) + .enumerate() + .map(|(i,x)| if i % 2 == 0 { 0u32.into() } else { x.into() } ) + .collect::>>(); + let deposits_bytes: Vec = deposits.iter().flat_map(|i| i.encode()).collect(); + let deposits_len = deposits_bytes.len() as u32; + let deposit_len = value_len.clone() as u32; + + let callee_offset = value_len + deposits_len; + let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -1634,6 +1645,7 @@ benchmarks! { ValueType::I32, ValueType::I32, ValueType::I32, + ValueType::I32, ], return_type: Some(ValueType::I32), }], @@ -1644,14 +1656,19 @@ benchmarks! { }, DataSegment { offset: value_len, + value: deposits_bytes, + }, + DataSegment { + offset: callee_offset, value: callee_bytes, }, ], call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ Regular(Instruction::I32Const(0)), // flags - Counter(4, callee_len as u32), // callee_ptr + Counter(callee_offset, callee_len as u32), // callee_ptr Regular(Instruction::I64Const(0)), // ref_time weight Regular(Instruction::I64Const(0)), // proof_size weight + Counter(value_len, deposit_len as u32), // deposit_limit_ptr Regular(Instruction::I32Const(0)), // value_ptr Regular(Instruction::I32Const(0)), // input_data_ptr Regular(Instruction::I32Const(0)), // input_data_len @@ -1805,13 +1822,22 @@ benchmarks! { assert!(value > 0u32.into()); let value_bytes = value.encode(); let value_len = BalanceOf::::max_encoded_len(); + // Set an own deposit limit every 2nd call + let deposits = (0..r * API_BENCHMARK_BATCH_SIZE) + .enumerate() + .map(|(i,x)| if i % 2 == 0 { 0u32.into() } else { x.into() } ) + .collect::>>(); + let deposits_bytes: Vec = deposits.iter().flat_map(|i| i.encode()).collect(); + let deposits_len = deposits_bytes.len(); + let deposit_len = value_len.clone(); let addr_len = T::AccountId::max_encoded_len(); // 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; + let deposits_offset = addr_len_offset + addr_len; + let addr_offset = deposits_offset + deposits_len; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), @@ -1831,6 +1857,7 @@ benchmarks! { ValueType::I32, ValueType::I32, ValueType::I32, + ValueType::I32, ], return_type: Some(ValueType::I32), }], @@ -1847,11 +1874,16 @@ benchmarks! { offset: addr_len_offset as u32, value: addr_len.to_le_bytes().into(), }, + DataSegment { + offset: addr_offset as u32, + value: deposits_bytes, + }, ], call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ Counter(hashes_offset as u32, hash_len as u32), // code_hash_ptr Regular(Instruction::I64Const(0)), // ref_time weight Regular(Instruction::I64Const(0)), // proof_size weight + Counter(deposits_offset as u32, deposit_len as u32), // deposit limit ptr Regular(Instruction::I32Const(value_offset as i32)), // value_ptr Regular(Instruction::I32Const(0)), // input_data_ptr Regular(Instruction::I32Const(0)), // input_data_len From 2bfb866464f17fb0d6064a6cc23c1d4262fe7809 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Thu, 16 Mar 2023 13:22:45 +0200 Subject: [PATCH 12/29] added missing fixtures --- .../create_storage_and_instantiate.wat | 66 +++++++++++++++++++ frame/contracts/fixtures/store_call.wat | 45 +++++++++++++ frame/contracts/fixtures/store_deploy.wat | 45 +++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 frame/contracts/fixtures/create_storage_and_instantiate.wat create mode 100644 frame/contracts/fixtures/store_call.wat create mode 100644 frame/contracts/fixtures/store_deploy.wat diff --git a/frame/contracts/fixtures/create_storage_and_instantiate.wat b/frame/contracts/fixtures/create_storage_and_instantiate.wat new file mode 100644 index 0000000000000..cd7202478437b --- /dev/null +++ b/frame/contracts/fixtures/create_storage_and_instantiate.wat @@ -0,0 +1,66 @@ +;; This instantiates another contract and passes some input to its constructor. +(module + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32))) + (import "seal2" "instantiate" (func $seal_instantiate + (param i32 i64 i64 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32) + )) + (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) + (import "env" "memory" (memory 1 1)) + + ;; [0, 8) send 10_000 balance + (data (i32.const 48) "\10\27\00\00\00\00\00\00") + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "deploy")) + + (func (export "call") + ;; store length of input buffer + (i32.store (i32.const 0) (i32.const 512)) + ;; store length of contract address + (i32.store (i32.const 84) (i32.const 32)) + + ;; copy input at address 4 + (call $seal_input (i32.const 4) (i32.const 0)) + + ;; memory layout is: + ;; [0,4): size of input buffer + ;; [4,8): size of the storage to be created in callee + ;; [8,40): the code hash of the contract to instantiate + ;; [40,48): for the encoded deposit limit + ;; [48,52): value to transfer + ;; [52,84): address of the deployed contract + ;; [84,88): len of the address + + ;; instantiate a contract + (call $assert (i32.eqz +;; (i32.store +;; (i32.const 64) + (call $seal_instantiate + (i32.const 8) ;; Pointer to the code hash. + (i64.const 0) ;; How much ref_time weight to devote for the execution. 0 = all. + (i64.const 0) ;; How much proof_size weight to devote for the execution. 0 = all. + (i32.const 40) ;; Pointer to the storage deposit limit + (i32.const 48) ;; Pointer to the buffer with value to transfer + (i32.const 4) ;; Pointer to input data buffer address + (i32.const 4) ;; Length of input data buffer + (i32.const 52) ;; Pointer to where to copy address + (i32.const 84) ;; Pointer to address len ptr + (i32.const 0xffffffff) ;; u32 max sentinel value: do not copy output + (i32.const 0) ;; Length is ignored in this case + (i32.const 0) ;; salt_ptr + (i32.const 0) ;; salt_len + ) + )) + ;; return the deployed contract address + (call $seal_return (i32.const 0) (i32.const 52) (i32.const 32)) + ) +) diff --git a/frame/contracts/fixtures/store_call.wat b/frame/contracts/fixtures/store_call.wat new file mode 100644 index 0000000000000..9e090d31801f8 --- /dev/null +++ b/frame/contracts/fixtures/store_call.wat @@ -0,0 +1,45 @@ +;; Stores a value of the passed size. +(module + (import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32))) + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "env" "memory" (memory 16 16)) + + ;; [0, 32) storage key + (data (i32.const 0) "\01") + + ;; [32, 36) buffer where input is copied (expected size of storage item) + + ;; [36, 40) size of the input buffer + (data (i32.const 36) "\04") + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "call") + (call $seal_input (i32.const 32) (i32.const 36)) + + ;; assert input size == 4 + (call $assert + (i32.eq + (i32.load (i32.const 36)) + (i32.const 4) + ) + ) + + ;; place a value in storage, the size of which is specified by the call input. + ;; we don't care about the contents of the storage item + (call $seal_set_storage + (i32.const 0) ;; Pointer to storage key + (i32.const 0) ;; Pointer to value + (i32.load (i32.const 32)) ;; Size of value + ) + ) + + (func (export "deploy")) +) diff --git a/frame/contracts/fixtures/store_deploy.wat b/frame/contracts/fixtures/store_deploy.wat new file mode 100644 index 0000000000000..cc428e9623bfb --- /dev/null +++ b/frame/contracts/fixtures/store_deploy.wat @@ -0,0 +1,45 @@ +;; Stores a value of the passed size in constructor. +(module + (import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32))) + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "env" "memory" (memory 16 16)) + + ;; [0, 32) storage key + (data (i32.const 0) "\01") + + ;; [32, 36) buffer where input is copied (expected size of storage item) + + ;; [36, 40) size of the input buffer + (data (i32.const 36) "\04") + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "deploy") + (call $seal_input (i32.const 32) (i32.const 36)) + + ;; assert input size == 4 + (call $assert + (i32.eq + (i32.load (i32.const 36)) + (i32.const 4) + ) + ) + + ;; place a value in storage, the size of which is specified by the call input. + ;; we don't care about the contents of the storage item + (call $seal_set_storage + (i32.const 0) ;; Pointer to storage key + (i32.const 0) ;; Pointer to value + (i32.load (i32.const 32)) ;; Size of value + ) + ) + + (func (export "call")) +) From 1ff3f16539a3f41196517897d1da80cb57e82665 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Thu, 16 Mar 2023 16:29:45 +0200 Subject: [PATCH 13/29] fix benches --- frame/contracts/src/benchmarking/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index 3aef24e4bf36d..fa9965e615b17 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -1620,9 +1620,10 @@ benchmarks! { let value_bytes = value.encode(); let value_len = BalanceOf::::max_encoded_len() as u32; // Set an own limit every 2nd call + let own_limit = T::DefaultDepositLimit::get() - 100u32.into(); let deposits = (0..r * API_BENCHMARK_BATCH_SIZE) .enumerate() - .map(|(i,x)| if i % 2 == 0 { 0u32.into() } else { x.into() } ) + .map(|(i,x)| if i % 2 == 0 { 0u32.into() } else { own_limit } ) .collect::>>(); let deposits_bytes: Vec = deposits.iter().flat_map(|i| i.encode()).collect(); let deposits_len = deposits_bytes.len() as u32; @@ -1823,9 +1824,10 @@ benchmarks! { let value_bytes = value.encode(); let value_len = BalanceOf::::max_encoded_len(); // Set an own deposit limit every 2nd call + let own_limit = T::DefaultDepositLimit::get() - 100u32.into(); let deposits = (0..r * API_BENCHMARK_BATCH_SIZE) .enumerate() - .map(|(i,x)| if i % 2 == 0 { 0u32.into() } else { x.into() } ) + .map(|(i,x)| if i % 2 == 0 { 0u32.into() } else { own_limit } ) .collect::>>(); let deposits_bytes: Vec = deposits.iter().flat_map(|i| i.encode()).collect(); let deposits_len = deposits_bytes.len(); From b0f3103c2911cd94b4cabccd11133ee7f17bac91 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Thu, 16 Mar 2023 16:53:18 +0200 Subject: [PATCH 14/29] pass explicit deposit limit to storage bench --- frame/contracts/src/benchmarking/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index fa9965e615b17..9c0bbb8aa7c85 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -1140,7 +1140,7 @@ benchmarks! { .map_err(|_| "Failed to write to storage during setup.")?; } let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, Some(BalanceOf::::from(1_000_000u32).into()), vec![]) // Similar to seal_set_storage. We store all the keys that we are about to // delete beforehand in order to prevent any optimizations that could occur when From 3af87129d38c07ed18334b33340441bd904c7893 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Thu, 16 Mar 2023 17:20:38 +0200 Subject: [PATCH 15/29] explicit deposit limit for another set_storage bench --- frame/contracts/src/benchmarking/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index 9c0bbb8aa7c85..028278939ac2c 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -1092,7 +1092,7 @@ benchmarks! { .map_err(|_| "Failed to write to storage during setup.")?; } let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, Some(BalanceOf::::from(1_000_000u32).into()), vec![]) #[skip_meta] #[pov_mode = Measured] From 825a6bc8b415bf0c65c7a30bed9a8c9c7b43ded7 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Thu, 16 Mar 2023 17:32:12 +0200 Subject: [PATCH 16/29] add more deposit limit for storage benches --- frame/contracts/src/benchmarking/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index 028278939ac2c..6a642cf34fd80 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -1092,7 +1092,7 @@ benchmarks! { .map_err(|_| "Failed to write to storage during setup.")?; } let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::MAX, Some(BalanceOf::::from(1_000_000u32).into()), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, Some(BalanceOf::::from(10_000_000u32).into()), vec![]) #[skip_meta] #[pov_mode = Measured] @@ -1140,7 +1140,7 @@ benchmarks! { .map_err(|_| "Failed to write to storage during setup.")?; } let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::MAX, Some(BalanceOf::::from(1_000_000u32).into()), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, Some(BalanceOf::::from(10_000_000u32).into()), vec![]) // Similar to seal_set_storage. We store all the keys that we are about to // delete beforehand in order to prevent any optimizations that could occur when From 8f9ea1760a555744ca3e8b2600553ad192aada66 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Thu, 16 Mar 2023 22:45:28 +0200 Subject: [PATCH 17/29] moving to simplified benchmarks --- frame/contracts/src/benchmarking/code.rs | 2 +- frame/contracts/src/benchmarking/mod.rs | 815 ++++++++++------------- 2 files changed, 366 insertions(+), 451 deletions(-) diff --git a/frame/contracts/src/benchmarking/code.rs b/frame/contracts/src/benchmarking/code.rs index aa44cc3976a2c..96eb5e501d068 100644 --- a/frame/contracts/src/benchmarking/code.rs +++ b/frame/contracts/src/benchmarking/code.rs @@ -512,7 +512,7 @@ pub fn max_pages() -> u32 { fn inject_gas_metering(module: Module) -> Module { let schedule = T::Schedule::get(); - let gas_rules = schedule.rules(&module, Determinism::Deterministic); + let gas_rules = schedule.rules(Determinism::Deterministic); let backend = gas_metering::host_function::Injector::new("seal0", "gas"); gas_metering::inject(module, backend, &gas_rules).unwrap() } diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index 6a642cf34fd80..31546194198c5 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -31,7 +31,6 @@ use self::{ }; use crate::{ exec::{AccountIdOf, FixSizedKey, VarSizedKey}, - schedule::{API_BENCHMARK_BATCH_SIZE, INSTR_BENCHMARK_BATCH_SIZE}, wasm::CallFlags, Pallet as Contracts, *, }; @@ -47,10 +46,17 @@ use sp_std::prelude::*; use wasm_instrument::parity_wasm::elements::{BlockType, BrTableData, Instruction, ValueType}; /// How many batches we do per API benchmark. -const API_BENCHMARK_BATCHES: u32 = 20; +/// +/// This is picked more or less arbitrary. We experimented with different numbers until +/// the results appeared to be stable. Reducing the number would speed up the benchmarks +/// but might make the results less precise. +const API_BENCHMARK_BATCHES: u32 = 1600; -/// How many batches we do per Instruction benchmark. -const INSTR_BENCHMARK_BATCHES: u32 = 50; +/// How many batches we do per instruction benchmark. +/// +/// Same rationale as for [`API_BENCHMARK_BATCHES`]. The number is bigger because instruction +/// benchmarks are faster. +const INSTR_BENCHMARK_BATCHES: u32 = 5000; /// An instantiated and deployed contract. struct Contract { @@ -244,7 +250,7 @@ benchmarks! { // the sandbox. This does **not** include the actual execution for which the gas meter // is responsible. This is achieved by generating all code to the `deploy` function // which is in the wasm module but not executed on `call`. - // The results are supposed to be used as `call_with_code_kb(c) - call_with_code_kb(0)`. + // The results are supposed to be used as `call_with_code_per_byte(c) - call_with_code_per_byte(0)`. #[pov_mode = Measured] call_with_code_per_byte { let c in 0 .. T::MaxCodeLen::get(); @@ -263,9 +269,9 @@ benchmarks! { // we don't benchmark the actual execution of this code but merely what it takes to load // a code of that size into the sandbox. // - // `c`: Size of the code in kilobytes. - // `i`: Size of the input in kilobytes. - // `s`: Size of the salt in kilobytes. + // `c`: Size of the code in bytes. + // `i`: Size of the input in bytes. + // `s`: Size of the salt in bytes. // // # Note // @@ -299,8 +305,8 @@ benchmarks! { } // Instantiate uses a dummy contract constructor to measure the overhead of the instantiate. - // `i`: Size of the input in kilobytes. - // `s`: Size of the salt in kilobytes. + // `i`: Size of the input in bytes. + // `s`: Size of the salt in bytes. #[pov_mode = Measured] instantiate { let i in 0 .. code::max_pages::() * 64 * 1024; @@ -333,7 +339,7 @@ benchmarks! { // The dummy contract used here does not do this. The costs for the data copy is billed as // part of `seal_input`. The costs for invoking a contract of a specific size are not part // of this benchmark because we cannot know the size of the contract when issuing a call - // transaction. See `invoke_per_code_kb` for this. + // transaction. See `call_with_code_per_byte` for this. #[pov_mode = Measured] call { let data = vec![42u8; 1024]; @@ -362,7 +368,7 @@ benchmarks! { // This constructs a contract that is maximal expensive to instrument. // It creates a maximum number of metering blocks per byte. - // `c`: Size of the code in kilobytes. + // `c`: Size of the code in bytes. // // # Note // @@ -421,7 +427,7 @@ benchmarks! { seal_caller { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal0", "seal_caller", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_caller", r ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -429,7 +435,7 @@ benchmarks! { #[pov_mode = Measured] seal_is_contract { let r in 0 .. API_BENCHMARK_BATCHES; - let accounts = (0 .. r * API_BENCHMARK_BATCH_SIZE) + let accounts = (0 .. r) .map(|n| account::("account", n, 0)) .collect::>(); let account_len = accounts.get(0).map(|i| i.encode().len()).unwrap_or(0); @@ -448,7 +454,7 @@ benchmarks! { value: accounts_bytes }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Counter(0, account_len as u32), // address_ptr Regular(Instruction::Call(0)), Regular(Instruction::Drop), @@ -467,7 +473,7 @@ benchmarks! { #[pov_mode = Measured] seal_code_hash { let r in 0 .. API_BENCHMARK_BATCHES; - let accounts = (0 .. r * API_BENCHMARK_BATCH_SIZE) + let accounts = (0 .. r) .map(|n| account::("account", n, 0)) .collect::>(); let account_len = accounts.get(0).map(|i| i.encode().len()).unwrap_or(0); @@ -492,7 +498,7 @@ benchmarks! { value: accounts_bytes, }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Counter(36, account_len as u32), // address_ptr Regular(Instruction::I32Const(4)), // ptr to output data Regular(Instruction::I32Const(0)), // ptr to output length @@ -514,7 +520,7 @@ benchmarks! { seal_own_code_hash { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal0", "seal_own_code_hash", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_own_code_hash", r ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -530,7 +536,7 @@ benchmarks! { params: vec![], return_type: Some(ValueType::I32), }], - call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::repeated(r, &[ Instruction::Call(0), Instruction::Drop, ])), @@ -544,7 +550,7 @@ benchmarks! { seal_address { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal0", "seal_address", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_address", r ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -553,7 +559,7 @@ benchmarks! { seal_gas_left { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal1", "gas_left", r * API_BENCHMARK_BATCH_SIZE + "seal1", "seal_gas_left", r ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -562,7 +568,7 @@ benchmarks! { seal_balance { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal0", "seal_balance", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_balance", r ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -571,7 +577,7 @@ benchmarks! { seal_value_transferred { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal0", "seal_value_transferred", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_value_transferred", r ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -580,7 +586,7 @@ benchmarks! { seal_minimum_balance { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal0", "seal_minimum_balance", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_minimum_balance", r ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -589,7 +595,7 @@ benchmarks! { seal_block_number { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal0", "seal_block_number", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_block_number", r ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -598,7 +604,7 @@ benchmarks! { seal_now { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal0", "seal_now", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_now", r ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -611,15 +617,15 @@ benchmarks! { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { module: "seal1", - name: "weight_to_fee", - params: vec![ValueType::I64,ValueType::I64, ValueType::I32, ValueType::I32], + name: "seal_weight_to_fee", + params: vec![ValueType::I64, 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, &[ + call_body: Some(body::repeated(r, &[ Instruction::I64Const(500_000), Instruction::I64Const(300_000), Instruction::I32Const(4), @@ -642,7 +648,7 @@ benchmarks! { params: vec![ValueType::I64], return_type: None, }], - call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::repeated(r, &[ Instruction::I64Const(42), Instruction::Call(0), ])), @@ -670,7 +676,7 @@ benchmarks! { value: 0u32.to_le_bytes().to_vec(), }, ], - call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::repeated(r, &[ Instruction::I32Const(4), // ptr where to store output Instruction::I32Const(0), // ptr to length Instruction::Call(0), @@ -682,10 +688,9 @@ benchmarks! { }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) #[pov_mode = Measured] - seal_input_per_kb { - let n in 0 .. code::max_pages::() * 64; - let pages = code::max_pages::(); - let buffer_size = pages * 64 * 1024 - 4; + seal_input_per_byte { + let n in 0 .. code::max_pages::() * 64 * 1024; + let buffer_size = code::max_pages::() * 64 * 1024 - 4; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -700,15 +705,16 @@ benchmarks! { value: buffer_size.to_le_bytes().to_vec(), }, ], - call_body: Some(body::repeated(API_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::plain(vec![ Instruction::I32Const(4), // ptr where to store output Instruction::I32Const(0), // ptr to length Instruction::Call(0), + Instruction::End, ])), .. Default::default() }); let instance = Contract::::new(code, vec![])?; - let data = vec![42u8; (n * 1024).min(buffer_size) as usize]; + let data = vec![42u8; n.min(buffer_size) as usize]; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, data) @@ -739,8 +745,8 @@ benchmarks! { }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) #[pov_mode = Measured] - seal_return_per_kb { - let n in 0 .. code::max_pages::() * 64; + seal_return_per_byte { + let n in 0 .. code::max_pages::() * 64 * 1024; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -752,7 +758,7 @@ benchmarks! { call_body: Some(body::plain(vec![ Instruction::I32Const(0), // flags Instruction::I32Const(0), // data_ptr - Instruction::I32Const((n * 1024) as i32), // data_len + Instruction::I32Const(n as i32), // data_len Instruction::Call(0), Instruction::End, ])), @@ -828,7 +834,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, &[ Instruction::I32Const(4), // subject_ptr Instruction::I32Const(subject_len as i32), // subject_len Instruction::I32Const((subject_len + 4) as i32), // out_ptr @@ -854,7 +860,7 @@ benchmarks! { 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, &[ Instruction::I32Const(0), // topics_ptr Instruction::I32Const(0), // topics_len Instruction::I32Const(0), // data_ptr @@ -869,16 +875,13 @@ benchmarks! { // Benchmark the overhead that topics generate. // `t`: Number of topics - // `n`: Size of event payload in kb + // `n`: Size of event payload in bytes #[pov_mode = Measured] - seal_deposit_event_per_topic_and_kb { + seal_deposit_event_per_topic_and_byte { let t in 0 .. T::Schedule::get().limits.event_topics; - let n in 0 .. T::Schedule::get().limits.payload_len / 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(); + let n in 0 .. T::Schedule::get().limits.payload_len; + let topics = (0..t).map(|i| T::Hashing::hash_of(&i)).collect::>().encode(); + let topics_len = topics.len(); let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -893,12 +896,13 @@ benchmarks! { value: topics, }, ], - call_body: Some(body::repeated_dyn(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)), + call_body: Some(body::plain(vec![ + Instruction::I32Const(0), // topics_ptr + Instruction::I32Const(topics_len as i32), // topics_len + Instruction::I32Const(0), // data_ptr + Instruction::I32Const(n as i32), // data_len + Instruction::Call(0), + Instruction::End, ])), .. Default::default() }); @@ -920,7 +924,7 @@ benchmarks! { params: vec![ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), }], - call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::repeated(r, &[ Instruction::I32Const(0), // value_ptr Instruction::I32Const(0), // value_len Instruction::Call(0), @@ -943,10 +947,10 @@ benchmarks! { .result?; } - seal_debug_message_per_kb { - // Vary size of input in kilobytes up to maximum allowed contract memory + seal_debug_message_per_byte { + // Vary size of input in bytes up to maximum allowed contract memory // or maximum allowed debug buffer size, whichever is less. - let i in 0 .. (T::Schedule::get().limits.memory_pages * 64).min(T::MaxDebugBufferLen::get() / 1024); + let i in 0 .. (T::Schedule::get().limits.memory_pages * 64 * 1024).min(T::MaxDebugBufferLen::get()); // We benchmark versus messages containing printable ASCII codes. // About 1Kb goes to the instrumented contract code instructions, // whereas all the space left we use for the initialization of the debug messages data. @@ -970,7 +974,7 @@ benchmarks! { ], call_body: Some(body::plain(vec![ Instruction::I32Const(0), // value_ptr - Instruction::I32Const((i * 1024) as i32), // value_len increments by i Kb + Instruction::I32Const(i as i32), // value_len Instruction::Call(0), Instruction::Drop, Instruction::End, @@ -995,15 +999,20 @@ benchmarks! { // Only the overhead of calling the function itself with minimal arguments. // The contract is a bit more complex because it needs to use different keys in order // to generate unique storage accesses. However, it is still dominated by the storage - // accesses. We store all the keys that we are about to write at beforehand + // accesses. We store something at all the keys that we are about to write to // because re-writing at an existing key is always more expensive than writing - // it at a virgin key. + // to an key with no data behind it. + // + // # Note + // + // We need to use a smaller `r` because the keys are big and writing them all into the wasm + // might exceed the code size. #[skip_meta] #[pov_mode = Measured] seal_set_storage { let r in 0 .. API_BENCHMARK_BATCHES/2; let max_key_len = T::MaxStorageKeyLen::get(); - let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) + let keys = (0 .. r) .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) .collect::>(); @@ -1022,7 +1031,7 @@ benchmarks! { value: keys_bytes, }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Counter(0, max_key_len as u32), // key_ptr Regular(Instruction::I32Const(max_key_len as i32)), // key_len Regular(Instruction::I32Const(0)), // value_ptr @@ -1048,14 +1057,10 @@ benchmarks! { #[skip_meta] #[pov_mode = Measured] - seal_set_storage_per_new_kb { - let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb + seal_set_storage_per_new_byte { + let n in 0 .. T::Schedule::get().limits.payload_len; let max_key_len = T::MaxStorageKeyLen::get(); - let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE) - .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); - h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) - .collect::>(); - let key_bytes = keys.iter().flatten().cloned().collect::>(); + let key = vec![0u8; max_key_len as usize]; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -1067,43 +1072,38 @@ benchmarks! { data_segments: vec![ DataSegment { offset: 0, - value: key_bytes, + value: key.clone(), }, ], - call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, max_key_len as u32), // key_ptr - Regular(Instruction::I32Const(max_key_len as i32)), // key_len - Regular(Instruction::I32Const(0)), // value_ptr - Regular(Instruction::I32Const((n * 2048) as i32)), // value_len increments by 2kb up to max payload_len - Regular(Instruction::Call(0)), - Regular(Instruction::Drop), + call_body: Some(body::plain(vec![ + Instruction::I32Const(0), // key_ptr + Instruction::I32Const(max_key_len as i32), // key_len + Instruction::I32Const(0), // value_ptr + Instruction::I32Const(n as i32), // value_len + Instruction::Call(0), + Instruction::Drop, + Instruction::End, ])), .. Default::default() }); let instance = Contract::::new(code, vec![])?; let info = instance.info()?; - for key in keys { - info.write( - &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, - Some(vec![]), - None, - false, - ) - .map_err(|_| "Failed to write to storage during setup.")?; - } + info.write( + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + Some(vec![]), + None, + false, + ) + .map_err(|_| "Failed to write to storage during setup.")?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::MAX, Some(BalanceOf::::from(10_000_000u32).into()), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) #[skip_meta] #[pov_mode = Measured] - seal_set_storage_per_old_kb { - let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb + seal_set_storage_per_old_byte { + let n in 0 .. T::Schedule::get().limits.payload_len; let max_key_len = T::MaxStorageKeyLen::get(); - let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE) - .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); - h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) - .collect::>(); - let key_bytes = keys.iter().flatten().cloned().collect::>(); + let key = vec![0u8; max_key_len as usize]; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -1115,32 +1115,31 @@ benchmarks! { data_segments: vec![ DataSegment { offset: 0, - value: key_bytes, + value: key.clone(), }, ], - call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, max_key_len as u32), // key_ptr - Regular(Instruction::I32Const(max_key_len as i32)), // key_len - Regular(Instruction::I32Const(0)), // value_ptr - Regular(Instruction::I32Const(0)), // value_len is 0 as testing vs pre-existing value len - Regular(Instruction::Call(0)), - Regular(Instruction::Drop), + call_body: Some(body::plain(vec![ + Instruction::I32Const(0), // key_ptr + Instruction::I32Const(max_key_len as i32), // key_len + Instruction::I32Const(0), // value_ptr + Instruction::I32Const(0), // value_len is 0 as testing vs pre-existing value len + Instruction::Call(0), + Instruction::Drop, + Instruction::End, ])), .. Default::default() }); let instance = Contract::::new(code, vec![])?; let info = instance.info()?; - for key in keys { - info.write( - &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, - Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len - None, - false, - ) - .map_err(|_| "Failed to write to storage during setup.")?; - } + info.write( + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + Some(vec![42u8; n as usize]), + None, + false, + ) + .map_err(|_| "Failed to write to storage during setup.")?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::MAX, Some(BalanceOf::::from(10_000_000u32).into()), vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // Similar to seal_set_storage. We store all the keys that we are about to // delete beforehand in order to prevent any optimizations that could occur when @@ -1151,7 +1150,7 @@ benchmarks! { seal_clear_storage { let r in 0 .. API_BENCHMARK_BATCHES/2; let max_key_len = T::MaxStorageKeyLen::get(); - let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) + let keys = (0 .. r) .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) .collect::>(); @@ -1170,7 +1169,7 @@ benchmarks! { value: key_bytes, }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Counter(0, max_key_len as u32), // key_ptr Regular(Instruction::I32Const(max_key_len as i32)), // key_len Regular(Instruction::Call(0)), @@ -1195,14 +1194,10 @@ benchmarks! { #[skip_meta] #[pov_mode = Measured] - seal_clear_storage_per_kb { - let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb + seal_clear_storage_per_byte { + let n in 0 .. T::Schedule::get().limits.payload_len; let max_key_len = T::MaxStorageKeyLen::get(); - let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE) - .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); - h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) - .collect::>(); - let key_bytes = keys.iter().flatten().cloned().collect::>(); + let key = vec![0u8; max_key_len as usize]; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -1214,28 +1209,27 @@ benchmarks! { data_segments: vec![ DataSegment { offset: 0, - value: key_bytes, + value: key.clone(), }, ], - call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, max_key_len as u32), // key_ptr - Regular(Instruction::I32Const(max_key_len as i32)), // key_len - Regular(Instruction::Call(0)), - Regular(Instruction::Drop), + call_body: Some(body::plain(vec![ + Instruction::I32Const(0), // key_ptr + Instruction::I32Const(max_key_len as i32), // key_len + Instruction::Call(0), + Instruction::Drop, + Instruction::End, ])), .. Default::default() }); let instance = Contract::::new(code, vec![])?; let info = instance.info()?; - for key in keys { - info.write( - &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, - Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len - None, - false, - ) - .map_err(|_| "Failed to write to storage during setup.")?; - } + info.write( + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + Some(vec![42u8; n as usize]), + None, + false, + ) + .map_err(|_| "Failed to write to storage during setup.")?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -1245,7 +1239,7 @@ benchmarks! { seal_get_storage { let r in 0 .. API_BENCHMARK_BATCHES/2; let max_key_len = T::MaxStorageKeyLen::get(); - let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) + let keys = (0 .. r) .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) .collect::>(); @@ -1269,8 +1263,8 @@ benchmarks! { value: T::Schedule::get().limits.payload_len.to_le_bytes().into(), }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, max_key_len as u32), // key_ptr + call_body: Some(body::repeated_dyn(r, vec![ + Counter(0, max_key_len), // key_ptr Regular(Instruction::I32Const(max_key_len as i32)), // key_len Regular(Instruction::I32Const((key_bytes_len + 4) as i32)), // out_ptr Regular(Instruction::I32Const(key_bytes_len as i32)), // out_len_ptr @@ -1296,15 +1290,10 @@ benchmarks! { #[skip_meta] #[pov_mode = Measured] - seal_get_storage_per_kb { - let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb + seal_get_storage_per_byte { + let n in 0 .. T::Schedule::get().limits.payload_len; let max_key_len = T::MaxStorageKeyLen::get(); - let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE) - .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); - h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) - .collect::>(); - let key_bytes = keys.iter().flatten().cloned().collect::>(); - let key_bytes_len = key_bytes.len(); + let key = vec![0u8; max_key_len as usize]; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -1316,34 +1305,33 @@ benchmarks! { data_segments: vec![ DataSegment { offset: 0, - value: key_bytes, + value: key.clone(), }, DataSegment { - offset: key_bytes_len as u32, + offset: max_key_len, value: T::Schedule::get().limits.payload_len.to_le_bytes().into(), }, ], - call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, max_key_len as u32), // key_ptr - Regular(Instruction::I32Const(max_key_len as i32)), // key_len - 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), + call_body: Some(body::plain(vec![ + Instruction::I32Const(0), // key_ptr + Instruction::I32Const(max_key_len as i32), // key_len + Instruction::I32Const((max_key_len + 4) as i32), // out_ptr + Instruction::I32Const(max_key_len as i32), // out_len_ptr + Instruction::Call(0), + Instruction::Drop, + Instruction::End, ])), .. Default::default() }); let instance = Contract::::new(code, vec![])?; let info = instance.info()?; - for key in keys { - info.write( - &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, - Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len - None, - false, - ) - .map_err(|_| "Failed to write to storage during setup.")?; - } + info.write( + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + Some(vec![42u8; n as usize]), + None, + false, + ) + .map_err(|_| "Failed to write to storage during setup.")?; >::insert(&instance.account_id, info); let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -1354,7 +1342,7 @@ benchmarks! { seal_contains_storage { let r in 0 .. API_BENCHMARK_BATCHES/2; let max_key_len = T::MaxStorageKeyLen::get(); - let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) + let keys = (0 .. r) .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) .collect::>(); @@ -1374,7 +1362,7 @@ benchmarks! { value: key_bytes, }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Counter(0, max_key_len as u32), // key_ptr Regular(Instruction::I32Const(max_key_len as i32)), // key_len Regular(Instruction::Call(0)), @@ -1399,14 +1387,10 @@ benchmarks! { #[skip_meta] #[pov_mode = Measured] - seal_contains_storage_per_kb { - let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb + seal_contains_storage_per_byte { + let n in 0 .. T::Schedule::get().limits.payload_len; let max_key_len = T::MaxStorageKeyLen::get(); - let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE) - .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); - h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) - .collect::>(); - let key_bytes = keys.iter().flatten().cloned().collect::>(); + let key = vec![0u8; max_key_len as usize]; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -1418,28 +1402,27 @@ benchmarks! { data_segments: vec![ DataSegment { offset: 0, - value: key_bytes, + value: key.clone(), }, ], - call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, max_key_len as u32), // key_ptr - Regular(Instruction::I32Const(max_key_len as i32)), // key_len - Regular(Instruction::Call(0)), - Regular(Instruction::Drop), + call_body: Some(body::plain(vec![ + Instruction::I32Const(0), // key_ptr + Instruction::I32Const(max_key_len as i32), // key_len + Instruction::Call(0), + Instruction::Drop, + Instruction::End, ])), .. Default::default() }); let instance = Contract::::new(code, vec![])?; let info = instance.info()?; - for key in keys { - info.write( - &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, - Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len - None, - false, - ) - .map_err(|_| "Failed to write to storage during setup.")?; - } + info.write( + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + Some(vec![42u8; n as usize]), + None, + false, + ) + .map_err(|_| "Failed to write to storage during setup.")?; >::insert(&instance.account_id, info); let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -1449,7 +1432,7 @@ benchmarks! { seal_take_storage { let r in 0 .. API_BENCHMARK_BATCHES/2; let max_key_len = T::MaxStorageKeyLen::get(); - let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) + let keys = (0 .. r) .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) .collect::>(); @@ -1473,7 +1456,7 @@ benchmarks! { value: T::Schedule::get().limits.payload_len.to_le_bytes().into(), }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Counter(0, max_key_len as u32), // key_ptr Regular(Instruction::I32Const(max_key_len as i32)), // key_len Regular(Instruction::I32Const((key_bytes_len + 4) as i32)), // out_ptr @@ -1500,15 +1483,10 @@ benchmarks! { #[skip_meta] #[pov_mode = Measured] - seal_take_storage_per_kb { - let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb + seal_take_storage_per_byte { + let n in 0 .. T::Schedule::get().limits.payload_len; let max_key_len = T::MaxStorageKeyLen::get(); - let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE) - .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); - h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) - .collect::>(); - let key_bytes = keys.iter().flatten().cloned().collect::>(); - let key_bytes_len = key_bytes.len(); + let key = vec![0u8; max_key_len as usize]; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -1520,34 +1498,33 @@ benchmarks! { data_segments: vec![ DataSegment { offset: 0, - value: key_bytes, + value: key.clone(), }, DataSegment { - offset: key_bytes_len as u32, + offset: max_key_len, value: T::Schedule::get().limits.payload_len.to_le_bytes().into(), }, ], - call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, max_key_len as u32), // key_ptr - Regular(Instruction::I32Const(max_key_len as i32)), // key_len - 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), + call_body: Some(body::plain(vec![ + Instruction::I32Const(0), // key_ptr + Instruction::I32Const(max_key_len as i32), // key_len + Instruction::I32Const((max_key_len + 4) as i32), // out_ptr + Instruction::I32Const(max_key_len as i32), // out_len_ptr + Instruction::Call(0), + Instruction::Drop, + Instruction::End, ])), .. Default::default() }); let instance = Contract::::new(code, vec![])?; let info = instance.info()?; - for key in keys { - info.write( - &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, - Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len - None, - false, - ) - .map_err(|_| "Failed to write to storage during setup.")?; - } + info.write( + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + Some(vec![42u8; n as usize]), + None, + false, + ) + .map_err(|_| "Failed to write to storage during setup.")?; >::insert(&instance.account_id, info); let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -1556,7 +1533,7 @@ benchmarks! { #[pov_mode = Measured] seal_transfer { let r in 0 .. API_BENCHMARK_BATCHES; - let accounts = (0..r * API_BENCHMARK_BATCH_SIZE) + let accounts = (0..r) .map(|i| account::("receiver", i, 0)) .collect::>(); let account_len = accounts.get(0).map(|i| i.encode().len()).unwrap_or(0); @@ -1583,7 +1560,7 @@ benchmarks! { value: account_bytes, }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, 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 @@ -1594,7 +1571,7 @@ benchmarks! { .. Default::default() }); let instance = Contract::::new(code, vec![])?; - instance.set_balance(value * (r * API_BENCHMARK_BATCH_SIZE + 1).into()); + instance.set_balance(value * (r + 1).into()); let origin = RawOrigin::Signed(instance.caller.clone()); for account in &accounts { assert_eq!(T::Currency::total_balance(account), 0u32.into()); @@ -1611,16 +1588,16 @@ benchmarks! { seal_call { let r in 0 .. API_BENCHMARK_BATCHES; let dummy_code = WasmModule::::dummy_with_bytes(0); - let callees = (0..r * API_BENCHMARK_BATCH_SIZE) + let callees = (0..r) .map(|i| Contract::with_index(i + 1, dummy_code.clone(), vec![])) .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 = 0u32.into(); let value_bytes = value.encode(); - let value_len = BalanceOf::::max_encoded_len() as u32; + let value_len = value_bytes.len(); // Set an own limit every 2nd call - let own_limit = T::DefaultDepositLimit::get() - 100u32.into(); + let own_limit = (u32::MAX - 100).into(); let deposits = (0..r * API_BENCHMARK_BATCH_SIZE) .enumerate() .map(|(i,x)| if i % 2 == 0 { 0u32.into() } else { own_limit } ) @@ -1628,14 +1605,12 @@ benchmarks! { let deposits_bytes: Vec = deposits.iter().flat_map(|i| i.encode()).collect(); let deposits_len = deposits_bytes.len() as u32; let deposit_len = value_len.clone() as u32; - let callee_offset = value_len + deposits_len; - let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { module: "seal2", - name: "call", + name: "seal_call", params: vec![ ValueType::I32, ValueType::I32, @@ -1664,7 +1639,7 @@ benchmarks! { value: callee_bytes, }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Regular(Instruction::I32Const(0)), // flags Counter(callee_offset, callee_len as u32), // callee_ptr Regular(Instruction::I64Const(0)), // ref_time weight @@ -1682,12 +1657,12 @@ benchmarks! { }); let instance = Contract::::new(code, vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, Some(BalanceOf::::from(u32::MAX).into()), vec![]) #[pov_mode = Measured] seal_delegate_call { let r in 0 .. API_BENCHMARK_BATCHES; - let hashes = (0..r * API_BENCHMARK_BATCH_SIZE) + let hashes = (0..r) .map(|i| { let code = WasmModule::::dummy_with_bytes(i); Contracts::::store_code_raw(code.code, whitelisted_caller())?; @@ -1720,7 +1695,7 @@ benchmarks! { value: hashes_bytes, }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Regular(Instruction::I32Const(0)), // flags Counter(hashes_offset as u32, hash_len as u32), // code_hash_ptr Regular(Instruction::I32Const(0)), // input_data_ptr @@ -1738,14 +1713,10 @@ benchmarks! { }: call(origin, callee, 0u32.into(), Weight::MAX, None, vec![]) #[pov_mode = Measured] - seal_call_per_transfer_clone_kb { + seal_call_per_transfer_clone_byte { let t in 0 .. 1; - let c in 0 .. code::max_pages::() * 64; - let callees = (0..API_BENCHMARK_BATCH_SIZE) - .map(|i| Contract::with_index(i + 1, >::dummy(), vec![])) - .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 c in 0 .. code::max_pages::() * 64 * 1024; + let callee = Contract::with_index(5, >::dummy(), vec![])?; let value: BalanceOf = t.into(); let value_bytes = value.encode(); let value_len = value_bytes.len(); @@ -1773,33 +1744,34 @@ benchmarks! { }, DataSegment { offset: value_len as u32, - value: callee_bytes, + value: callee.account_id.encode(), }, ], - call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ - Regular(Instruction::I32Const(CallFlags::CLONE_INPUT.bits() as i32)), // flags - Counter(value_len as u32, callee_len as u32), // callee_ptr - Regular(Instruction::I64Const(0)), // gas - Regular(Instruction::I32Const(0)), // value_ptr - Regular(Instruction::I32Const(0)), // input_data_ptr - Regular(Instruction::I32Const(0)), // input_data_len - Regular(Instruction::I32Const(SENTINEL as i32)), // output_ptr - Regular(Instruction::I32Const(0)), // output_len_ptr - Regular(Instruction::Call(0)), - Regular(Instruction::Drop), + call_body: Some(body::plain(vec![ + Instruction::I32Const(CallFlags::CLONE_INPUT.bits() as i32), // flags + Instruction::I32Const(value_len as i32), // callee_ptr + Instruction::I64Const(0), // gas + Instruction::I32Const(0), // value_ptr + Instruction::I32Const(0), // input_data_ptr + Instruction::I32Const(0), // input_data_len + Instruction::I32Const(SENTINEL as i32), // output_ptr + Instruction::I32Const(0), // output_len_ptr + Instruction::Call(0), + Instruction::Drop, + Instruction::End, ])), .. Default::default() }); let instance = Contract::::new(code, vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - let bytes = vec![42; (c * 1024) as usize]; + let bytes = vec![42; c as usize]; }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, bytes) // We assume that every instantiate sends at least the minimum balance. #[pov_mode = Measured] seal_instantiate { let r in 0 .. API_BENCHMARK_BATCHES; - let hashes = (0..r * API_BENCHMARK_BATCH_SIZE) + let hashes = (0..r) .map(|i| { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), @@ -1824,7 +1796,7 @@ benchmarks! { let value_bytes = value.encode(); let value_len = BalanceOf::::max_encoded_len(); // Set an own deposit limit every 2nd call - let own_limit = T::DefaultDepositLimit::get() - 100u32.into(); + let own_limit = (u32::MAX - 100).into(); let deposits = (0..r * API_BENCHMARK_BATCH_SIZE) .enumerate() .map(|(i,x)| if i % 2 == 0 { 0u32.into() } else { own_limit } ) @@ -1833,19 +1805,17 @@ benchmarks! { let deposits_len = deposits_bytes.len(); let deposit_len = value_len.clone(); let addr_len = T::AccountId::max_encoded_len(); - // 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 deposits_offset = addr_len_offset + addr_len; let addr_offset = deposits_offset + deposits_len; - let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { module: "seal2", - name: "instantiate", + name: "seal_instantiate", params: vec![ ValueType::I32, ValueType::I64, @@ -1881,7 +1851,7 @@ benchmarks! { value: deposits_bytes, }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Counter(hashes_offset as u32, hash_len as u32), // code_hash_ptr Regular(Instruction::I64Const(0)), // ref_time weight Regular(Instruction::I64Const(0)), // proof_size weight @@ -1901,7 +1871,7 @@ benchmarks! { .. Default::default() }); let instance = Contract::::new(code, vec![])?; - instance.set_balance((value + Pallet::::min_balance()) * (r * API_BENCHMARK_BATCH_SIZE + 1).into()); + instance.set_balance((value + Pallet::::min_balance()) * (r + 1).into()); let origin = RawOrigin::Signed(instance.caller.clone()); let callee = instance.addr.clone(); let addresses = hashes @@ -1916,7 +1886,7 @@ benchmarks! { return Err("Expected that contract does not exist at this point.".into()); } } - }: call(origin, callee, 0u32.into(), Weight::MAX, None, vec![]) + }: call(origin, callee, 0u32.into(), Weight::MAX, Some(BalanceOf::::from(u32::MAX).into()), vec![]) verify { for addr in &addresses { ContractInfoOf::::get(&addr) @@ -1925,37 +1895,24 @@ benchmarks! { } #[pov_mode = Measured] - seal_instantiate_per_transfer_input_salt_kb { + seal_instantiate_per_transfer_input_salt_byte { let t in 0 .. 1; - let i in 0 .. (code::max_pages::() - 1) * 64; - let s in 0 .. (code::max_pages::() - 1) * 64; + let i in 0 .. (code::max_pages::() - 1) * 64 * 1024; + let s in 0 .. (code::max_pages::() - 1) * 64 * 1024; let callee_code = WasmModule::::dummy(); let hash = callee_code.hash; let hash_bytes = callee_code.hash.encode(); let hash_len = hash_bytes.len(); Contracts::::store_code_raw(callee_code.code, whitelisted_caller())?; - let salts = (0..API_BENCHMARK_BATCH_SIZE).map(|x| x.encode()).collect::>(); - let salt_len = salts.get(0).map(|x| x.len()).unwrap_or(0); - let salt_bytes = salts.iter().cloned().flatten().collect::>(); - let salts_len = salt_bytes.len(); - let value: BalanceOf = t.into(); + let value: BalanceOf = t.into(); let value_bytes = value.encode(); - let value_len = value_bytes.len(); - let addr_len = T::AccountId::max_encoded_len(); - - // offsets where to place static data in contract memory - let salt_offset = 0; - let value_offset = salts_len; - let hash_offset = value_offset + value_len; - let addr_len_offset = hash_offset + hash_len; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "seal0", + module: "seal1", name: "seal_instantiate", params: vec![ - ValueType::I32, ValueType::I32, ValueType::I64, ValueType::I32, @@ -1967,73 +1924,63 @@ benchmarks! { ValueType::I32, ValueType::I32, ValueType::I32, - ValueType::I32, ], return_type: Some(ValueType::I32), }], data_segments: vec![ DataSegment { - offset: salt_offset as u32, - value: salt_bytes, - }, - DataSegment { - offset: value_offset as u32, - value: value_bytes, - }, - DataSegment { - offset: hash_offset as u32, + offset: 0, value: hash_bytes, }, DataSegment { - offset: addr_len_offset as u32, - value: (addr_len as u32).to_le_bytes().into(), + offset: hash_len as u32, + value: value_bytes, }, ], - call_body: Some(body::repeated_dyn(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(salt_offset as u32, salt_len as u32), // input_data_ptr - Regular(Instruction::I32Const((i * 1024) 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(SENTINEL as i32)), // output_ptr - Regular(Instruction::I32Const(0)), // output_len_ptr - Counter(salt_offset as u32, salt_len as u32), // salt_ptr - Regular(Instruction::I32Const((s * 1024) as i32)), // salt_len - Regular(Instruction::Call(0)), - Regular(Instruction::I32Eqz), - Regular(Instruction::If(BlockType::NoResult)), - Regular(Instruction::Nop), - Regular(Instruction::Else), - Regular(Instruction::Unreachable), - Regular(Instruction::End), + call_body: Some(body::plain(vec![ + Instruction::I32Const(0 as i32), // code_hash_ptr + Instruction::I64Const(0), // gas + Instruction::I32Const(hash_len as i32), // value_ptr + Instruction::I32Const(0 as i32), // input_data_ptr + Instruction::I32Const(i as i32), // input_data_len + Instruction::I32Const(SENTINEL as i32), // address_ptr + Instruction::I32Const(0), // address_len_ptr + Instruction::I32Const(SENTINEL as i32), // output_ptr + Instruction::I32Const(0), // output_len_ptr + Instruction::I32Const(0 as i32), // salt_ptr + Instruction::I32Const(s as i32), // salt_len + Instruction::Call(0), + Instruction::I32Eqz, + Instruction::If(BlockType::NoResult), + Instruction::Nop, + Instruction::Else, + Instruction::Unreachable, + Instruction::End, + Instruction::End, ])), .. Default::default() }); let instance = Contract::::new(code, vec![])?; - instance.set_balance((value + Pallet::::min_balance()) * (API_BENCHMARK_BATCH_SIZE + 1).into()); + instance.set_balance(value + (Pallet::::min_balance() * 2u32.into())); let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // Only the overhead of calling the function itself with minimal arguments. #[pov_mode = Measured] seal_hash_sha2_256 { - let r in 0 .. 1; + let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::hasher( - "seal_hash_sha2_256", r * API_BENCHMARK_BATCH_SIZE, 0, + "seal_hash_sha2_256", r, 0, ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - // `n`: Input to hash in kilobytes + // `n`: Input to hash in bytes #[pov_mode = Measured] - seal_hash_sha2_256_per_kb { - let n in 0 .. code::max_pages::() * 64; + seal_hash_sha2_256_per_byte { + let n in 0 .. code::max_pages::() * 64 * 1024; let instance = Contract::::new(WasmModule::hasher( - "seal_hash_sha2_256", API_BENCHMARK_BATCH_SIZE, n * 1024, + "seal_hash_sha2_256", 1, n, ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -2041,19 +1988,19 @@ benchmarks! { // Only the overhead of calling the function itself with minimal arguments. #[pov_mode = Measured] seal_hash_keccak_256 { - let r in 0 .. 1; + let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::hasher( - "seal_hash_keccak_256", r * API_BENCHMARK_BATCH_SIZE, 0, + "seal_hash_keccak_256", r, 0, ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - // `n`: Input to hash in kilobytes + // `n`: Input to hash in bytes #[pov_mode = Measured] - seal_hash_keccak_256_per_kb { - let n in 0 .. code::max_pages::() * 64; + seal_hash_keccak_256_per_byte { + let n in 0 .. code::max_pages::() * 64 * 1024; let instance = Contract::::new(WasmModule::hasher( - "seal_hash_keccak_256", API_BENCHMARK_BATCH_SIZE, n * 1024, + "seal_hash_keccak_256", 1, n, ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -2061,19 +2008,19 @@ benchmarks! { // Only the overhead of calling the function itself with minimal arguments. #[pov_mode = Measured] seal_hash_blake2_256 { - let r in 0 .. 1; + let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::hasher( - "seal_hash_blake2_256", r * API_BENCHMARK_BATCH_SIZE, 0, + "seal_hash_blake2_256", r, 0, ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - // `n`: Input to hash in kilobytes + // `n`: Input to hash in bytes #[pov_mode = Measured] - seal_hash_blake2_256_per_kb { - let n in 0 .. code::max_pages::() * 64; + seal_hash_blake2_256_per_byte { + let n in 0 .. code::max_pages::() * 64 * 1024; let instance = Contract::::new(WasmModule::hasher( - "seal_hash_blake2_256", API_BENCHMARK_BATCH_SIZE, n * 1024, + "seal_hash_blake2_256", 1, n, ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -2081,19 +2028,19 @@ benchmarks! { // Only the overhead of calling the function itself with minimal arguments. #[pov_mode = Measured] seal_hash_blake2_128 { - let r in 0 .. 1; + let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::hasher( - "seal_hash_blake2_128", r * API_BENCHMARK_BATCH_SIZE, 0, + "seal_hash_blake2_128", r, 0, ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - // `n`: Input to hash in kilobytes + // `n`: Input to hash in bytes #[pov_mode = Measured] - seal_hash_blake2_128_per_kb { - let n in 0 .. code::max_pages::() * 64; + seal_hash_blake2_128_per_byte { + let n in 0 .. code::max_pages::() * 64 * 1024; let instance = Contract::::new(WasmModule::hasher( - "seal_hash_blake2_128", API_BENCHMARK_BATCH_SIZE, n * 1024, + "seal_hash_blake2_128", 1, n, ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -2102,11 +2049,11 @@ benchmarks! { // It generates different private keys and signatures for the message "Hello world". #[pov_mode = Measured] seal_ecdsa_recover { - let r in 0 .. 1; + let r in 0 .. API_BENCHMARK_BATCHES; let message_hash = sp_io::hashing::blake2_256("Hello world".as_bytes()); let key_type = sp_core::crypto::KeyTypeId(*b"code"); - let signatures = (0..r * API_BENCHMARK_BATCH_SIZE) + let signatures = (0..r) .map(|i| { let pub_key = sp_io::crypto::ecdsa_generate(key_type, None); let sig = sp_io::crypto::ecdsa_sign_prehashed(key_type, &pub_key, &message_hash).expect("Generates signature"); @@ -2134,7 +2081,7 @@ benchmarks! { value: signatures, }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Counter(32, 65), // signature_ptr Regular(Instruction::I32Const(0)), // message_hash_ptr Regular(Instruction::I32Const(signatures_bytes_len + 32)), // output_len_ptr @@ -2151,9 +2098,9 @@ benchmarks! { // generated different ECDSA keys. #[pov_mode = Measured] seal_ecdsa_to_eth_address { - let r in 0 .. 1; + let r in 0 .. API_BENCHMARK_BATCHES; let key_type = sp_core::crypto::KeyTypeId(*b"code"); - let pub_keys_bytes = (0..r * API_BENCHMARK_BATCH_SIZE) + let pub_keys_bytes = (0..r) .flat_map(|_| { sp_io::crypto::ecdsa_generate(key_type, None).0 }) @@ -2173,7 +2120,7 @@ benchmarks! { value: pub_keys_bytes, }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Counter(0, 33), // pub_key_ptr Regular(Instruction::I32Const(pub_keys_bytes_len)), // out_ptr Regular(Instruction::Call(0)), @@ -2188,7 +2135,7 @@ benchmarks! { #[pov_mode = Measured] seal_set_code_hash { let r in 0 .. API_BENCHMARK_BATCHES; - let code_hashes = (0..r * API_BENCHMARK_BATCH_SIZE) + let code_hashes = (0..r) .map(|i| { let new_code = WasmModule::::dummy_with_bytes(i); Contracts::::store_code_raw(new_code.code, whitelisted_caller())?; @@ -2215,7 +2162,7 @@ benchmarks! { value: code_hashes_bytes, }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Counter(0, code_hash_len as u32), // code_hash_ptr Regular(Instruction::Call(0)), Regular(Instruction::Drop), @@ -2237,7 +2184,7 @@ benchmarks! { params: vec![], return_type: Some(ValueType::I32), }], - call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::repeated(r, &[ Instruction::Call(0), Instruction::Drop, ])), @@ -2251,7 +2198,7 @@ benchmarks! { seal_account_reentrance_count { let r in 0 .. API_BENCHMARK_BATCHES; let dummy_code = WasmModule::::dummy_with_bytes(0); - let accounts = (0..r * API_BENCHMARK_BATCH_SIZE) + let accounts = (0..r) .map(|i| Contract::with_index(i + 1, dummy_code.clone(), vec![])) .collect::, _>>()?; let account_id_len = accounts.get(0).map(|i| i.account_id.encode().len()).unwrap_or(0); @@ -2270,7 +2217,7 @@ benchmarks! { value: account_id_bytes, }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Counter(0, account_id_len as u32), // account_ptr Regular(Instruction::Call(0)), Regular(Instruction::Drop), @@ -2292,7 +2239,7 @@ benchmarks! { params: vec![], return_type: Some(ValueType::I64), }], - call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::repeated(r, &[ Instruction::Call(0), Instruction::Drop, ])), @@ -2316,7 +2263,7 @@ benchmarks! { instr_i64const { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ RandomI64Repeated(1), Regular(Instruction::Drop), ])), @@ -2332,7 +2279,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ RandomUnaligned(0, code::max_pages::() * 64 * 1024 - 8), Regular(Instruction::I64Load(3, 0)), Regular(Instruction::Drop), @@ -2349,7 +2296,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ RandomUnaligned(0, code::max_pages::() * 64 * 1024 - 8), RandomI64Repeated(1), Regular(Instruction::I64Store(3, 0)), @@ -2365,7 +2312,7 @@ benchmarks! { instr_select { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ RandomI64Repeated(1), RandomI64Repeated(1), RandomI32(0, 2), @@ -2383,7 +2330,7 @@ benchmarks! { instr_if { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ RandomI32(0, 2), Regular(Instruction::If(BlockType::Value(ValueType::I64))), RandomI64Repeated(1), @@ -2404,7 +2351,7 @@ benchmarks! { instr_br { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Regular(Instruction::Block(BlockType::NoResult)), Regular(Instruction::Block(BlockType::NoResult)), Regular(Instruction::Block(BlockType::NoResult)), @@ -2431,7 +2378,7 @@ benchmarks! { instr_br_if { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Regular(Instruction::Block(BlockType::NoResult)), Regular(Instruction::Block(BlockType::NoResult)), Regular(Instruction::Block(BlockType::NoResult)), @@ -2463,7 +2410,7 @@ benchmarks! { default: 1, }); let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Regular(Instruction::Block(BlockType::NoResult)), Regular(Instruction::Block(BlockType::NoResult)), Regular(Instruction::Block(BlockType::NoResult)), @@ -2498,21 +2445,22 @@ benchmarks! { default: 0, }); let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(body::repeated_dyn(INSTR_BENCHMARK_BATCH_SIZE, vec![ - Regular(Instruction::Block(BlockType::NoResult)), - Regular(Instruction::Block(BlockType::NoResult)), - Regular(Instruction::Block(BlockType::NoResult)), - RandomI32(0, (e + 1) as i32), // Make sure the default entry is also used - Regular(Instruction::BrTable(table)), - RandomI64Repeated(1), - Regular(Instruction::Drop), - Regular(Instruction::End), - RandomI64Repeated(1), - Regular(Instruction::Drop), - Regular(Instruction::End), - RandomI64Repeated(1), - Regular(Instruction::Drop), - Regular(Instruction::End), + call_body: Some(body::plain(vec![ + Instruction::Block(BlockType::NoResult), + Instruction::Block(BlockType::NoResult), + Instruction::Block(BlockType::NoResult), + Instruction::I32Const((e / 2) as i32), + Instruction::BrTable(table), + Instruction::I64Const(42), + Instruction::Drop, + Instruction::End, + Instruction::I64Const(42), + Instruction::Drop, + Instruction::End, + Instruction::I64Const(42), + Instruction::Drop, + Instruction::End, + Instruction::End, ])), .. Default::default() })); @@ -2532,7 +2480,7 @@ benchmarks! { Instruction::Drop, Instruction::End, ])), - call_body: Some(body::repeated(r * INSTR_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::repeated(r, &[ Instruction::Call(2), // call aux ])), .. Default::default() @@ -2555,7 +2503,7 @@ benchmarks! { Instruction::Drop, Instruction::End, ])), - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ RandomI32(0, num_elements as i32), Regular(Instruction::CallIndirect(0, 0)), // we only have one sig: 0 ])), @@ -2569,39 +2517,6 @@ benchmarks! { sbox.invoke(); } - // w_instr_call_indirect_per_param = w_bench - 1 * w_param - // Calling a function indirectly causes it to go through a thunk function whose runtime - // linearly depend on the amount of parameters to this function. - // Please note that this is not necessary with a direct call. - #[pov_mode = Ignored] - instr_call_indirect_per_param { - let p in 0 .. T::Schedule::get().limits.parameters; - let num_elements = T::Schedule::get().limits.table_size; - use self::code::TableSegment; - let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - // We need to make use of the stack here in order to trigger stack height - // instrumentation. - aux_body: Some(body::plain(vec![ - Instruction::I64Const(42), - Instruction::Drop, - Instruction::End, - ])), - aux_arg_num: p, - call_body: Some(body::repeated_dyn(INSTR_BENCHMARK_BATCH_SIZE, vec![ - RandomI64Repeated(p as usize), - RandomI32(0, num_elements as i32), - Regular(Instruction::CallIndirect(p.min(1), 0)), // aux signature: 1 or 0 - ])), - table: Some(TableSegment { - num_elements, - function_index: 2, // aux - }), - .. Default::default() - })); - }: { - sbox.invoke(); - } - // w_per_local = w_bench #[pov_mode = Ignored] instr_call_per_local { @@ -2612,8 +2527,9 @@ benchmarks! { body::inject_locals(&mut aux_body, l); let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { aux_body: Some(aux_body), - call_body: Some(body::repeated(INSTR_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::plain(vec![ Instruction::Call(2), // call aux + Instruction::End, ])), .. Default::default() })); @@ -2626,7 +2542,7 @@ benchmarks! { instr_local_get { let r in 0 .. INSTR_BENCHMARK_BATCHES; let max_locals = T::Schedule::get().limits.locals; - let mut call_body = body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ + let mut call_body = body::repeated_dyn(r, vec![ RandomGetLocal(0, max_locals), Regular(Instruction::Drop), ]); @@ -2644,7 +2560,7 @@ benchmarks! { instr_local_set { let r in 0 .. INSTR_BENCHMARK_BATCHES; let max_locals = T::Schedule::get().limits.locals; - let mut call_body = body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ + let mut call_body = body::repeated_dyn(r, vec![ RandomI64Repeated(1), RandomSetLocal(0, max_locals), ]); @@ -2662,7 +2578,7 @@ benchmarks! { instr_local_tee { let r in 0 .. INSTR_BENCHMARK_BATCHES; let max_locals = T::Schedule::get().limits.locals; - let mut call_body = body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ + let mut call_body = body::repeated_dyn(r, vec![ RandomI64Repeated(1), RandomTeeLocal(0, max_locals), Regular(Instruction::Drop), @@ -2682,7 +2598,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let max_globals = T::Schedule::get().limits.globals; let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ RandomGetGlobal(0, max_globals), Regular(Instruction::Drop), ])), @@ -2699,7 +2615,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let max_globals = T::Schedule::get().limits.globals; let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ RandomI64Repeated(1), RandomSetGlobal(0, max_globals), ])), @@ -2716,7 +2632,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), - call_body: Some(body::repeated(r * INSTR_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::repeated(r, &[ Instruction::CurrentMemory(0), Instruction::Drop ])), @@ -2733,14 +2649,13 @@ benchmarks! { // depends on how much memory is already allocated. #[pov_mode = Ignored] instr_memory_grow { - let r in 0 .. 1; - let max_pages = ImportedMemory::max::().max_pages; + let r in 0 .. ImportedMemory::max::().max_pages; let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory { min_pages: 0, - max_pages, + max_pages: ImportedMemory::max::().max_pages, }), - call_body: Some(body::repeated(r * max_pages, &[ + call_body: Some(body::repeated(r, &[ Instruction::I32Const(1), Instruction::GrowMemory(0), Instruction::Drop, @@ -2759,7 +2674,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::unary_instr( Instruction::I64Clz, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -2770,7 +2685,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::unary_instr( Instruction::I64Ctz, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -2781,7 +2696,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::unary_instr( Instruction::I64Popcnt, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -2792,7 +2707,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::unary_instr( Instruction::I64Eqz, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -2802,7 +2717,7 @@ benchmarks! { instr_i64extendsi32 { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ RandomI32Repeated(1), Regular(Instruction::I64ExtendSI32), Regular(Instruction::Drop), @@ -2817,7 +2732,7 @@ benchmarks! { instr_i64extendui32 { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ RandomI32Repeated(1), Regular(Instruction::I64ExtendUI32), Regular(Instruction::Drop), @@ -2833,7 +2748,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::unary_instr( Instruction::I32WrapI64, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -2847,7 +2762,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64Eq, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -2858,7 +2773,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64Ne, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -2869,7 +2784,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64LtS, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -2880,7 +2795,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64LtU, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -2891,7 +2806,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64GtS, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -2902,7 +2817,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64GtU, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -2913,7 +2828,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64LeS, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -2924,7 +2839,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64LeU, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -2935,7 +2850,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64GeS, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -2946,7 +2861,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64GeU, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -2957,7 +2872,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64Add, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -2968,7 +2883,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64Sub, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -2979,7 +2894,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64Mul, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -2990,7 +2905,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64DivS, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -3001,7 +2916,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64DivU, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -3012,7 +2927,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64RemS, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -3023,7 +2938,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64RemU, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -3034,7 +2949,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64And, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -3045,7 +2960,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64Or, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -3056,7 +2971,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64Xor, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -3067,7 +2982,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64Shl, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -3078,7 +2993,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64ShrS, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -3089,7 +3004,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64ShrU, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -3100,7 +3015,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64Rotl, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); @@ -3111,7 +3026,7 @@ benchmarks! { let r in 0 .. INSTR_BENCHMARK_BATCHES; let mut sbox = Sandbox::from(&WasmModule::::binary_instr( Instruction::I64Rotr, - r * INSTR_BENCHMARK_BATCH_SIZE, + r, )); }: { sbox.invoke(); From 04ead1baf8ecc22e4e9eb98efad4409eca9f484e Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Thu, 16 Mar 2023 23:02:07 +0200 Subject: [PATCH 18/29] moved to simplified benchmarks --- frame/contracts/src/benchmarking/mod.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index 87aaaef403a07..4332788f7d4c7 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -559,11 +559,7 @@ benchmarks! { seal_gas_left { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( -<<<<<<< HEAD - "seal1", "seal_gas_left", r -======= - "seal0", "seal_gas_left", r ->>>>>>> master + "seal1", "gas_left", r ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -1599,10 +1595,10 @@ benchmarks! { let callee_bytes = callees.iter().flat_map(|x| x.account_id.encode()).collect(); let value: BalanceOf = 0u32.into(); let value_bytes = value.encode(); - let value_len = value_bytes.len(); + let value_len = BalanceOf::::max_encoded_len() as u32; // Set an own limit every 2nd call let own_limit = (u32::MAX - 100).into(); - let deposits = (0..r * API_BENCHMARK_BATCH_SIZE) + let deposits = (0..r) .enumerate() .map(|(i,x)| if i % 2 == 0 { 0u32.into() } else { own_limit } ) .collect::>>(); @@ -1614,7 +1610,7 @@ benchmarks! { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { module: "seal2", - name: "seal_call", + name: "call", params: vec![ ValueType::I32, ValueType::I32, @@ -1801,7 +1797,7 @@ benchmarks! { let value_len = BalanceOf::::max_encoded_len(); // Set an own deposit limit every 2nd call let own_limit = (u32::MAX - 100).into(); - let deposits = (0..r * API_BENCHMARK_BATCH_SIZE) + let deposits = (0..r) .enumerate() .map(|(i,x)| if i % 2 == 0 { 0u32.into() } else { own_limit } ) .collect::>>(); @@ -1819,7 +1815,7 @@ benchmarks! { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { module: "seal2", - name: "seal_instantiate", + name: "instantiate", params: vec![ ValueType::I32, ValueType::I64, From 7d1023d49e1135552c4b2e227334b3210b958f79 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Thu, 16 Mar 2023 23:09:16 +0200 Subject: [PATCH 19/29] fix seal_weight_to_fee bench --- frame/contracts/src/benchmarking/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index 4332788f7d4c7..869205485be6f 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -617,7 +617,7 @@ benchmarks! { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { module: "seal1", - name: "seal_weight_to_fee", + name: "weight_to_fee", params: vec![ValueType::I64, ValueType::I64, ValueType::I32, ValueType::I32], return_type: None, }], From 8e6c6d2c94f02d1a8ead7ad579b73627792b0fd2 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Thu, 16 Mar 2023 23:34:54 +0200 Subject: [PATCH 20/29] fix seal_instantiate benchmark --- frame/contracts/src/benchmarking/mod.rs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index 869205485be6f..e078a98dc250e 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -1795,22 +1795,16 @@ benchmarks! { assert!(value > 0u32.into()); let value_bytes = value.encode(); let value_len = BalanceOf::::max_encoded_len(); - // Set an own deposit limit every 2nd call - let own_limit = (u32::MAX - 100).into(); - let deposits = (0..r) - .enumerate() - .map(|(i,x)| if i % 2 == 0 { 0u32.into() } else { own_limit } ) - .collect::>>(); - let deposits_bytes: Vec = deposits.iter().flat_map(|i| i.encode()).collect(); - let deposits_len = deposits_bytes.len(); - let deposit_len = value_len.clone(); + // Set an own deposit limit for every call + let deposit_limit: u32 = (u32::MAX - 100).into(); + let deposit_bytes = deposit_limit.to_le_bytes().to_vec(); let addr_len = T::AccountId::max_encoded_len(); // 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 deposits_offset = addr_len_offset + addr_len; - let addr_offset = deposits_offset + deposits_len; + let deposit_offset = addr_len_offset + addr_len; + let addr_offset = deposit_offset + 4; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -1848,14 +1842,14 @@ benchmarks! { }, DataSegment { offset: addr_offset as u32, - value: deposits_bytes, + value: deposit_bytes, }, ], call_body: Some(body::repeated_dyn(r, vec![ Counter(hashes_offset as u32, hash_len as u32), // code_hash_ptr Regular(Instruction::I64Const(0)), // ref_time weight Regular(Instruction::I64Const(0)), // proof_size weight - Counter(deposits_offset as u32, deposit_len as u32), // deposit limit ptr + Regular(Instruction::I32Const(deposit_offset as i32)), // deposit limit ptr Regular(Instruction::I32Const(value_offset as i32)), // value_ptr Regular(Instruction::I32Const(0)), // input_data_ptr Regular(Instruction::I32Const(0)), // input_data_len From 16f0994326415d06374c2d68ea904ffe79e62071 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Fri, 31 Mar 2023 08:33:01 +0300 Subject: [PATCH 21/29] doc typo fix --- frame/contracts/README.md | 4 ++-- frame/contracts/src/lib.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frame/contracts/README.md b/frame/contracts/README.md index d9b0d5342aa51..b3766f44e6a0a 100644 --- a/frame/contracts/README.md +++ b/frame/contracts/README.md @@ -1,6 +1,6 @@ -# Contract Module +# Contracts Module -The Contract module provides functionality for the runtime to deploy and execute WebAssembly smart-contracts. +The Contracts module provides functionality for the runtime to deploy and execute WebAssembly smart-contracts. - [`Call`](https://paritytech.github.io/substrate/master/pallet_contracts/pallet/enum.Call.html) - [`Config`](https://paritytech.github.io/substrate/master/pallet_contracts/pallet/trait.Config.html) diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index e1eae3affcf6d..1459c0011579d 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -15,9 +15,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! # Contract Pallet +//! # Contracts Pallet //! -//! The Contract module provides functionality for the runtime to deploy and execute WebAssembly +//! The Contracts module provides functionality for the runtime to deploy and execute WebAssembly //! smart-contracts. //! //! - [`Config`] @@ -73,7 +73,7 @@ //! //! ## Usage //! -//! The Contract module is a work in progress. The following examples show how this Contract module +//! The Contracts module is a work in progress. The following examples show how this module //! can be used to instantiate and call contracts. //! //! * [`ink`](https://github.com/paritytech/ink) is From 24dcd0e2e66b7f93e4b7f6e1505420233a60960b Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Fri, 31 Mar 2023 09:03:25 +0300 Subject: [PATCH 22/29] default dl for benchmarking more dl for tests dl for tests to max deposit_limit fix in instantiate bench fix instantiate bench fix instantiate benchmark fix instantiate bench again remove dbg fix seal bench again fixing it still seal_instantiate zero deposit less runs to check if deposit enough try try 2 try 3 try 4 --- frame/contracts/src/benchmarking/mod.rs | 33 +++++++++---------------- frame/contracts/src/tests.rs | 4 +-- frame/contracts/src/wasm/runtime.rs | 5 ++-- 3 files changed, 16 insertions(+), 26 deletions(-) diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index b16b8f88ec59f..4e4261b0a5395 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -1771,17 +1771,17 @@ benchmarks! { }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, bytes) // We assume that every instantiate sends at least the minimum balance. - // This is a slow call: We redeuce the number of runs. + // This is a slow call: we reduce the number of runs. #[pov_mode = Measured] seal_instantiate { - let r in 0 .. API_BENCHMARK_RUNS / 2; + let r in 1 .. API_BENCHMARK_RUNS / 2; let hashes = (0..r) .map(|i| { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), call_body: Some(body::plain(vec![ - // we need to add this in order to make contracts unique - // so that they can be deployed from the same sender + // We need to add this in order to make contracts unique, + // so that they can be deployed from the same sender. Instruction::I32Const(i as i32), Instruction::Drop, Instruction::End, @@ -1794,21 +1794,16 @@ benchmarks! { .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 hashes_len = &hashes_bytes.len(); let value = Pallet::::min_balance(); assert!(value > 0u32.into()); let value_bytes = value.encode(); let value_len = BalanceOf::::max_encoded_len(); - // Set an own deposit limit for every call - let deposit_limit: u32 = (u32::MAX - 100).into(); - let deposit_bytes = deposit_limit.to_le_bytes().to_vec(); let addr_len = T::AccountId::max_encoded_len(); - // offsets where to place static data in contract memory - let value_offset = 0; - let hashes_offset = value_offset + value_len; + // Offsets where to place static data in contract memory. + let hashes_offset = value_len; let addr_len_offset = hashes_offset + hashes_len; - let deposit_offset = addr_len_offset + addr_len; - let addr_offset = deposit_offset + 4; + let addr_offset = addr_len_offset + addr_len; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -1833,7 +1828,7 @@ benchmarks! { }], data_segments: vec![ DataSegment { - offset: value_offset as u32, + offset: 0, value: value_bytes, }, DataSegment { @@ -1844,17 +1839,13 @@ benchmarks! { offset: addr_len_offset as u32, value: addr_len.to_le_bytes().into(), }, - DataSegment { - offset: addr_offset as u32, - value: deposit_bytes, - }, ], call_body: Some(body::repeated_dyn(r, vec![ Counter(hashes_offset as u32, hash_len as u32), // code_hash_ptr Regular(Instruction::I64Const(0)), // ref_time weight Regular(Instruction::I64Const(0)), // proof_size weight - Regular(Instruction::I32Const(deposit_offset as i32)), // deposit limit ptr - Regular(Instruction::I32Const(value_offset as i32)), // value_ptr + Regular(Instruction::I32Const(SENTINEL as i32)), // deposit limit ptr: use parent's limit + Regular(Instruction::I32Const(0)), // value_ptr Regular(Instruction::I32Const(0)), // input_data_ptr Regular(Instruction::I32Const(0)), // input_data_len Regular(Instruction::I32Const(addr_offset as i32)), // address_ptr @@ -1884,7 +1875,7 @@ benchmarks! { return Err("Expected that contract does not exist at this point.".into()); } } - }: call(origin, callee, 0u32.into(), Weight::MAX, Some(BalanceOf::::from(u32::MAX).into()), vec![]) + }: call(origin, callee, 0u32.into(), Weight::MAX, None, vec![]) verify { for addr in &addresses { ContractInfoOf::::get(&addr) diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index 9990af865443e..7a93f183af159 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -344,8 +344,8 @@ parameter_types! { }; pub static DepositPerByte: BalanceOf = 1; pub const DepositPerItem: BalanceOf = 2; - // We need this one set high enough for running benchmarks. - pub const DefaultDepositLimit: BalanceOf = 10_000_000; + // We set it to maximum for running benchmarks + pub const DefaultDepositLimit: BalanceOf = Balance::max_value(); } impl Convert> for Test { diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 54c69af4fc3e5..ec085d0c992c6 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -1579,9 +1579,8 @@ pub mod env { /// - `input_data_len`: length of the input data buffer. /// - `address_ptr`: a pointer where the new account's address is copied to. `SENTINEL` means /// not to copy. - /// - `address_len_ptr`: in-out pointer to where the length of the buffer is read from and the - /// actual length is written to. - /// - `output_ptr`: a pointer where the output buffer is copied to. `SENTINEL` means not to + /// - `address_len_ptr`: pointer to where put the length of the address. + /// - `output_ptr`: a pointer where the output buffer is copied to. `SENTINEL` means not to /// copy. /// - `output_len_ptr`: in-out pointer to where the length of the buffer is read from and the /// actual length is written to. From d7eab4a8470eaaf67163b4ceeca327305ba896e1 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Mon, 3 Apr 2023 13:06:23 +0300 Subject: [PATCH 23/29] max_runtime_mem to Schedule limits --- frame/contracts/src/lib.rs | 8 ++++---- frame/contracts/src/schedule.rs | 5 +++++ frame/contracts/src/tests.rs | 6 +++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 1459c0011579d..a745c31572f7b 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -353,8 +353,8 @@ pub mod pallet { } fn integrity_test() { - // Total runtime memory is expected to have 128Mb upper limit - const MAX_RUNTIME_MEM: u32 = 1024 * 1024 * 128; + // Total runtime memory limit + let max_runtime_mem: u32 = T::Schedule::get().limits.runtime_memory; // Memory limits for a single contract: // Value stack size: 1Mb per contract, default defined in wasmi const MAX_STACK_SIZE: u32 = 1024 * 1024; @@ -388,10 +388,10 @@ pub mod pallet { // This gives us the following formula: // // `(MaxCodeLen * 18 * 4 + MAX_STACK_SIZE + max_heap_size) * max_call_depth < - // MAX_RUNTIME_MEM/2` + // max_runtime_mem/2` // // Hence the upper limit for the `MaxCodeLen` can be defined as follows: - let code_len_limit = MAX_RUNTIME_MEM + let code_len_limit = max_runtime_mem .saturating_div(2) .saturating_div(max_call_depth) .saturating_sub(max_heap_size) diff --git a/frame/contracts/src/schedule.rs b/frame/contracts/src/schedule.rs index ccf1e98bd2382..fe7382eb8ff07 100644 --- a/frame/contracts/src/schedule.rs +++ b/frame/contracts/src/schedule.rs @@ -127,6 +127,10 @@ pub struct Limits { /// The maximum size of a storage value and event payload in bytes. pub payload_len: u32, + + /// The maximum node runtime memory. This is for integrity checks only and does not affect the + /// real setting. + pub runtime_memory: u32, } impl Limits { @@ -473,6 +477,7 @@ impl Default for Limits { br_table_size: 256, subject_len: 32, payload_len: 16 * 1024, + runtime_memory: 1024 * 1024 * 128, } } } diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index 7a93f183af159..6cad112d579d8 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -344,8 +344,8 @@ parameter_types! { }; pub static DepositPerByte: BalanceOf = 1; pub const DepositPerItem: BalanceOf = 2; - // We set it to maximum for running benchmarks - pub const DefaultDepositLimit: BalanceOf = Balance::max_value(); + // We need this one set high enough for running benchmarks. + pub const DefaultDepositLimit: BalanceOf = 10_000_000; } impl Convert> for Test { @@ -4190,7 +4190,7 @@ fn deposit_limit_in_nested_instantiate() { let callee_info_len = ContractInfoOf::::get(&addr).unwrap().encoded_size() as u64; - // We don't set a special deposit limit for the netsted instantiation. + // We don't set a special deposit limit for the nested instantiation. // // The deposit limit set for the parent is insufficient for the instantiation, which // requires: From af1b353cd640fda8bbb5fdcf85a5bf0986adca69 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Wed, 12 Apr 2023 18:38:12 +0300 Subject: [PATCH 24/29] add default deposit limit fallback check to test --- frame/contracts/src/tests.rs | 43 ++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index 668aa6574a073..351af1a9ec26e 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -346,7 +346,7 @@ parameter_types! { pub static DepositPerByte: BalanceOf = 1; pub const DepositPerItem: BalanceOf = 2; // We need this one set high enough for running benchmarks. - pub const DefaultDepositLimit: BalanceOf = 10_000_000; + pub static DefaultDepositLimit: BalanceOf = 10_000_000; } impl Convert> for Test { @@ -3943,15 +3943,50 @@ fn storage_deposit_limit_is_enforced() { assert_eq!(get_contract(&addr).total_deposit(), min_balance); assert_eq!(::Currency::total_balance(&addr), min_balance); - // Create 100 bytes of storage with a price of per byte + // Create 1 byte of storage with a price of per byte, + // setting insufficient deposit limit, as it requires 3 Balance: + // 2 for the item added + 1 for the new storage item. assert_err_ignore_postinfo!( Contracts::call( RuntimeOrigin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, - Some(codec::Compact(1)), - 100u32.to_le_bytes().to_vec() + Some(codec::Compact(2)), + 1u32.to_le_bytes().to_vec() + ), + >::StorageDepositLimitExhausted, + ); + + + // To check that deposit limit fallbacks to DefaultDepositLimit, + // we customize it here. + DEFAULT_DEPOSIT_LIMIT.with(|c| *c.borrow_mut() = 3); + + // Create 1 byte of storage, should cost 3 Balance: + // 2 for the item added + 1 for the new storage item. + // Should pass as it fallbacks to DefaultDepositLimit. + assert_ok!( + Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + 1u32.to_le_bytes().to_vec() + ) + ); + + // Use 4 more bytes of the storage for the same item, which requires 4 Balance. + // Should fail as DefaultDepositLimit is 3 and hence isn't enough. + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + 5u32.to_le_bytes().to_vec() ), >::StorageDepositLimitExhausted, ); From 04bea785f3e789478c78280fe1047a2d9538ce52 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Wed, 12 Apr 2023 20:03:59 +0300 Subject: [PATCH 25/29] weight params renaming --- frame/contracts/src/benchmarking/mod.rs | 2 +- frame/contracts/src/storage/meter.rs | 2 +- frame/contracts/src/wasm/runtime.rs | 28 ++++++++++++------------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index e5b4dc4d2d29c..39530c45d23ce 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -1594,7 +1594,7 @@ benchmarks! { .collect::>>(); let deposits_bytes: Vec = deposits.iter().flat_map(|i| i.encode()).collect(); let deposits_len = deposits_bytes.len() as u32; - let deposit_len = value_len.clone() as u32; + let deposit_len = value_len.clone(); let callee_offset = value_len + deposits_len; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), diff --git a/frame/contracts/src/storage/meter.rs b/frame/contracts/src/storage/meter.rs index 01f7f3e854de2..8bab0a6d262be 100644 --- a/frame/contracts/src/storage/meter.rs +++ b/frame/contracts/src/storage/meter.rs @@ -133,7 +133,7 @@ pub struct RawMeter { charges: Vec>, /// We store the nested state to determine if it has a special limit for sub-call. nested: S, - /// Type parameters are only used in impls. + /// Type parameter only used in impls. _phantom: PhantomData, } diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index ec085d0c992c6..97c8911f5b72b 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -1366,8 +1366,8 @@ pub mod env { /// - `flags`: See `crate::wasm::runtime::CallFlags` for a documentation of the supported flags. /// - `callee_ptr`: a pointer to the address of the callee contract. Should be decodable as an /// `T::AccountId`. Traps otherwise. - /// - `ref_time`: how much *ref_time* Weight to devote to the execution. - /// - `proof_limit`: how much *proof_limit* Weight to devote to the execution. + /// - `ref_time_limit`: how much *ref_time* Weight to devote to the execution. + /// - `proof_size_limit`: how much *proof_size* Weight to devote to the execution. /// - `deposit_ptr`: a pointer to the buffer with value of the storage deposit limit for the /// call. Should be decodable as a `T::Balance`. Traps otherwise. Passing `SENTINEL` means /// setting no specific limit for the call, which implies storage usage up to the limit of the @@ -1396,8 +1396,8 @@ pub mod env { memory: _, flags: u32, callee_ptr: u32, - ref_time: u64, - proof_limit: u64, + ref_time_limit: u64, + proof_size_limit: u64, deposit_ptr: u32, value_ptr: u32, input_data_ptr: u32, @@ -1412,7 +1412,7 @@ pub mod env { callee_ptr, value_ptr, deposit_ptr, - weight: Weight::from_parts(ref_time, proof_limit), + weight: Weight::from_parts(ref_time_limit, proof_size_limit), }, input_data_ptr, input_data_len, @@ -1567,8 +1567,8 @@ pub mod env { /// # Parameters /// /// - `code_hash_ptr`: a pointer to the buffer that contains the initializer code. - /// - `ref_time`: how much *ref_time* Weight to devote to the execution. - /// - `proof_limit`: how much *proof_limit* Weight to devote to the execution. + /// - `ref_time_limit`: how much *ref_time* Weight to devote to the execution. + /// - `proof_size_limit`: how much *proof_size* Weight to devote to the execution. /// - `deposit_ptr`: a pointer to the buffer with value of the storage deposit limit for /// instantiation. Should be decodable as a `T::Balance`. Traps otherwise. Passing `SENTINEL` /// means setting no specific limit for the call, which implies storage usage up to the limit @@ -1605,8 +1605,8 @@ pub mod env { ctx: _, memory: _, code_hash_ptr: u32, - ref_time: u64, - proof_limit: u64, + ref_time_limit: u64, + proof_size_limit: u64, deposit_ptr: u32, value_ptr: u32, input_data_ptr: u32, @@ -1621,7 +1621,7 @@ pub mod env { ctx.instantiate( memory, code_hash_ptr, - Weight::from_parts(ref_time, proof_limit), + Weight::from_parts(ref_time_limit, proof_size_limit), deposit_ptr, value_ptr, input_data_ptr, @@ -1906,19 +1906,19 @@ pub mod env { /// /// # Note /// - /// It is recommended to avoid specifying very small values for `ref_time` and `proof_limit` as + /// It is recommended to avoid specifying very small values for `ref_time_limit` and `proof_size_limit` as /// the prices for a single gas can be smaller than the basic balance unit. #[version(1)] #[unstable] fn weight_to_fee( ctx: _, memory: _, - ref_time: u64, - proof_limit: u64, + ref_time_limit: u64, + proof_size_limit: u64, out_ptr: u32, out_len_ptr: u32, ) -> Result<(), TrapReason> { - let weight = Weight::from_parts(ref_time, proof_limit); + let weight = Weight::from_parts(ref_time_limit, proof_size_limit); ctx.charge_gas(RuntimeCosts::WeightToFee)?; Ok(ctx.write_sandbox_output( memory, From 50c96da32f4290f8288c04540490ebf274361545 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Wed, 12 Apr 2023 20:28:55 +0300 Subject: [PATCH 26/29] fmt --- frame/contracts/src/tests.rs | 19 ++++++++----------- frame/contracts/src/wasm/runtime.rs | 5 +++-- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index 5572e2a656d27..b00d08acccbd1 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -4024,7 +4024,6 @@ fn storage_deposit_limit_is_enforced() { >::StorageDepositLimitExhausted, ); - // To check that deposit limit fallbacks to DefaultDepositLimit, // we customize it here. DEFAULT_DEPOSIT_LIMIT.with(|c| *c.borrow_mut() = 3); @@ -4032,16 +4031,14 @@ fn storage_deposit_limit_is_enforced() { // Create 1 byte of storage, should cost 3 Balance: // 2 for the item added + 1 for the new storage item. // Should pass as it fallbacks to DefaultDepositLimit. - assert_ok!( - Contracts::call( - RuntimeOrigin::signed(ALICE), - addr.clone(), - 0, - GAS_LIMIT, - None, - 1u32.to_le_bytes().to_vec() - ) - ); + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + 1u32.to_le_bytes().to_vec() + )); // Use 4 more bytes of the storage for the same item, which requires 4 Balance. // Should fail as DefaultDepositLimit is 3 and hence isn't enough. diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 96f1bdc2b49bc..219d90425d81a 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -1913,8 +1913,9 @@ pub mod env { /// /// # Note /// - /// It is recommended to avoid specifying very small values for `ref_time_limit` and `proof_size_limit` as - /// the prices for a single gas can be smaller than the basic balance unit. + /// It is recommended to avoid specifying very small values for `ref_time_limit` and + /// `proof_size_limit` as the prices for a single gas can be smaller than the basic balance + /// unit. #[version(1)] #[unstable] fn weight_to_fee( From 95c83eae396d7e03f59c4fd619839ad73000b671 Mon Sep 17 00:00:00 2001 From: Sasha Gryaznov Date: Tue, 25 Apr 2023 13:23:17 +0300 Subject: [PATCH 27/29] Update frame/contracts/src/benchmarking/mod.rs Co-authored-by: PG Herveou --- frame/contracts/src/benchmarking/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index bfaddc19fd902..6ebfb18509b1c 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -1589,8 +1589,7 @@ benchmarks! { // Set an own limit every 2nd call let own_limit = (u32::MAX - 100).into(); let deposits = (0..r) - .enumerate() - .map(|(i,x)| if i % 2 == 0 { 0u32.into() } else { own_limit } ) + .map(|i| if i % 2 == 0 { 0u32.into() } else { own_limit } ) .collect::>>(); let deposits_bytes: Vec = deposits.iter().flat_map(|i| i.encode()).collect(); let deposits_len = deposits_bytes.len() as u32; From 22c579458cc80adedd27303e65e57cf0113a39a1 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Tue, 25 Apr 2023 15:34:07 +0300 Subject: [PATCH 28/29] prettify inputs in tests --- frame/contracts/src/tests.rs | 95 +++++------------------------------- 1 file changed, 11 insertions(+), 84 deletions(-) diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index b00d08acccbd1..6fe5530e37ff1 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -4115,18 +4115,10 @@ fn deposit_limit_in_nested_calls() { 0, GAS_LIMIT, Some(codec::Compact(13)), - 100u32 - .to_le_bytes() - .as_ref() - .iter() - .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) - .chain(0u64.to_le_bytes().as_ref()) - .cloned() - .collect(), + (100u32, &addr_callee, 0u64).encode(), ), >::StorageDepositLimitExhausted, ); - // Now we specify the parent's limit high enough to cover the caller's storage additions. // However, we use a single byte more in the callee, hence the storage deposit should be 15 // Balance. @@ -4140,14 +4132,7 @@ fn deposit_limit_in_nested_calls() { 0, GAS_LIMIT, Some(codec::Compact(14)), - 101u32 - .to_le_bytes() - .as_ref() - .iter() - .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) - .chain(0u64.to_le_bytes().as_ref()) - .cloned() - .collect(), + (101u32, &addr_callee, 0u64).encode(), ), >::StorageDepositLimitExhausted, ); @@ -4164,14 +4149,7 @@ fn deposit_limit_in_nested_calls() { 0, GAS_LIMIT, Some(codec::Compact(16)), - 102u32 - .to_le_bytes() - .as_ref() - .iter() - .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) - .chain(&[1u8]) - .cloned() - .collect(), + (102u32, &addr_callee, 1u64).encode(), ), >::StorageDepositLimitExhausted, ); @@ -4186,14 +4164,7 @@ fn deposit_limit_in_nested_calls() { 0, GAS_LIMIT, Some(codec::Compact(0)), - 87u32 - .to_le_bytes() - .as_ref() - .iter() - .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) - .chain(0u64.to_le_bytes().as_ref()) - .cloned() - .collect(), + (87u32, &addr_callee, 0u64).encode(), ), >::StorageDepositLimitExhausted, ); @@ -4209,14 +4180,7 @@ fn deposit_limit_in_nested_calls() { 0, GAS_LIMIT, None, - 1_200u32 - .to_le_bytes() - .as_ref() - .iter() - .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) - .chain(0u64.to_le_bytes().as_ref()) - .cloned() - .collect(), + (1200u32, &addr_callee, 1u64).encode(), ), >::StorageDepositLimitExhausted, ); @@ -4230,14 +4194,7 @@ fn deposit_limit_in_nested_calls() { 0, GAS_LIMIT, Some(codec::Compact(1)), - 87u32 - .to_le_bytes() - .as_ref() - .iter() - .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) - .chain(&[1u8]) - .cloned() - .collect(), + (87u32, &addr_callee, 1u64).encode(), )); }); } @@ -4300,13 +4257,7 @@ fn deposit_limit_in_nested_instantiate() { 0, GAS_LIMIT, Some(codec::Compact(callee_info_len + 2 + ED + 1)), - 0u32.to_le_bytes() - .as_ref() - .iter() - .chain(code_hash_callee.as_ref()) - .chain(0u64.to_le_bytes().as_ref()) - .cloned() - .collect(), + (0u32, &code_hash_callee, 0u64).encode(), ), >::StorageDepositLimitExhausted, ); @@ -4323,13 +4274,7 @@ fn deposit_limit_in_nested_instantiate() { 0, GAS_LIMIT, Some(codec::Compact(callee_info_len + 2 + ED + 2)), - 1u32.to_le_bytes() - .as_ref() - .iter() - .chain(code_hash_callee.as_ref()) - .chain(0u64.to_le_bytes().as_ref()) - .cloned() - .collect(), + (1u32, &code_hash_callee, 0u64).encode(), ), >::StorageDepositLimitExhausted, ); @@ -4346,13 +4291,7 @@ fn deposit_limit_in_nested_instantiate() { 0, GAS_LIMIT, Some(codec::Compact(callee_info_len + 2 + ED + 2)), - 0u32.to_le_bytes() - .as_ref() - .iter() - .chain(code_hash_callee.as_ref()) - .chain((callee_info_len + 2 + ED + 1).to_le_bytes().as_ref()) - .cloned() - .collect(), + (0u32, &code_hash_callee, callee_info_len + 2 + ED + 1).encode(), ), >::StorageDepositLimitExhausted, ); @@ -4370,13 +4309,7 @@ fn deposit_limit_in_nested_instantiate() { 0, GAS_LIMIT, Some(codec::Compact(callee_info_len + 2 + ED + 3)), // enough parent limit - 1u32.to_le_bytes() - .as_ref() - .iter() - .chain(code_hash_callee.as_ref()) - .chain((callee_info_len + 2 + ED + 2).to_le_bytes().as_ref()) // not enough child limit - .cloned() - .collect(), + (1u32, &code_hash_callee, callee_info_len + 2 + ED + 2).encode(), ), >::StorageDepositLimitExhausted, ); @@ -4390,13 +4323,7 @@ fn deposit_limit_in_nested_instantiate() { 0, GAS_LIMIT, Some(codec::Compact(callee_info_len + 2 + ED + 4).into()), - 1u32.to_le_bytes() - .as_ref() - .iter() - .chain(code_hash_callee.as_ref()) - .chain((callee_info_len + 2 + ED + 3).to_le_bytes().as_ref()) // child limit is tougher but is still enough - .cloned() - .collect(), + (1u32, &code_hash_callee, callee_info_len + 2 + ED + 3).encode(), false, Determinism::Enforced, ); From 0dac6f6e1652a203dbce23bea1fb6a6c3ebc713b Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Tue, 25 Apr 2023 20:11:40 +0300 Subject: [PATCH 29/29] typestate param refactored --- frame/contracts/src/storage/meter.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/frame/contracts/src/storage/meter.rs b/frame/contracts/src/storage/meter.rs index 8bab0a6d262be..083f7adf366be 100644 --- a/frame/contracts/src/storage/meter.rs +++ b/frame/contracts/src/storage/meter.rs @@ -100,10 +100,7 @@ pub trait State: private::Sealed {} /// State parameter that constitutes a meter that is in its root state. #[derive(Default, Debug)] -pub enum Root { - #[default] - Unit, -} +pub struct Root; /// State parameter that constitutes a meter that is in its nested state. /// Its value indicates whether the nested meter has its own limit.