[contracts] Port host functions to Weight V2 and storage deposit limit (#13565)

* added [unstable][seal2] call()

* updated test to cover new seal_call proof_limit

* docs updated

* add [seal2][unstable] instantiate() and test

* add [seal2][unstable] weight_to_fee() + docs and test

* add [seal2][unstable] gas_left() + docs and test

* update benchmarks

* add DefaultDepositLimit to pallet Config

* specify deposit limit for nested call

add test for nested call deposit limit

save: separate deposit limit for nested calls

* specify deposit limit for nested instantiate

save: works with test

cleaned up debugging outputs

* update benchmarks

* added missing fixtures

* fix benches

* pass explicit deposit limit to storage bench

* explicit deposit limit for another set_storage bench

* add more deposit limit for storage benches

* moving to simplified benchmarks

* moved to simplified benchmarks

* fix seal_weight_to_fee bench

* fix seal_instantiate benchmark

* doc typo fix

* 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

* max_runtime_mem to Schedule limits

* add default deposit limit fallback check to test

* weight params renaming

* fmt

* Update frame/contracts/src/benchmarking/mod.rs

Co-authored-by: PG Herveou <pgherveou@gmail.com>

* prettify inputs in tests

* typestate param refactored

---------

Co-authored-by: PG Herveou <pgherveou@gmail.com>
This commit is contained in:
Sasha Gryaznov
2023-04-26 14:27:13 +03:00
committed by GitHub
parent 01c66da036
commit 60310de7d6
16 changed files with 949 additions and 275 deletions
+2
View File
@@ -1215,6 +1215,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 Schedule: pallet_contracts::Schedule<Runtime> = Default::default();
}
@@ -1233,6 +1234,7 @@ impl pallet_contracts::Config for Runtime {
type CallFilter = Nothing;
type DepositPerItem = DepositPerItem;
type DepositPerByte = DepositPerByte;
type DefaultDepositLimit = DefaultDepositLimit;
type CallStack = [pallet_contracts::Frame<Self>; 5];
type WeightPrice = pallet_transaction_payment::Pallet<Self>;
type WeightInfo = pallet_contracts::weights::SubstrateWeight<Self>;
+4 -4
View File
@@ -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)
@@ -63,9 +63,9 @@ directly. This makes sure that by default `pallet-contracts` does not expose any
When setting up the `Schedule` for your runtime make sure to set `InstructionWeights::fallback`
to a non zero value. The default is `0` and prevents the upload of any non deterministic code.
An indeterministic code can be deployed on-chain by passing `Determinism::AllowIndeterministic`
An indeterministic code can be deployed on-chain by passing `Determinism::Relaxed`
to `upload_code`. A deterministic contract can then delegate call into it if and only if it
is ran by using `bare_call` and passing `Determinism::AllowIndeterministic` to it. **Never use
is ran by using `bare_call` and passing `Determinism::Relaxed` to it. **Never use
this argument when the contract is called from an on-chain transaction.**
## Interface
@@ -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 i32) (result i32)))
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
(import "env" "memory" (memory 1 1))
@@ -13,24 +13,25 @@
(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 0) ;; Set no flag.
(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 0xffffffff) ;; u32 max sentinel value: pass no deposit limit.
(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))
)
@@ -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 i32) (result i32)))
(import "seal2" "instantiate" (func $seal_instantiate
(param i32 i64 i64 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)
))
(import "env" "memory" (memory 1 1))
@@ -43,18 +43,18 @@
(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 0xffffffff) ;; u32 max sentinel value: pass no deposit limit.
(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 +63,47 @@
(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 0xffffffff) ;; u32 max sentinel value: pass no deposit limit.
(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 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
(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 +123,18 @@
(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 0xffffffff) ;; u32 max sentinel value: pass no deposit limit.
(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 +164,16 @@
;; 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 0xffffffff) ;; u32 max sentinel value: pass no deposit limit.
(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 +193,40 @@
)
)
;; 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 0xffffffff) ;; u32 max sentinel value: pass no deposit limit.
(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 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
(i32.const 4294967295) ;; u32 max sentinel value: do not copy output
(i32.const 0) ;; Length is ignored in this cas
)
)
@@ -202,15 +250,16 @@
;; 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 0xffffffff) ;; u32 max sentinel value: pass no deposit limit.
(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
)
)
@@ -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 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))
;; 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 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
@@ -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))
)
)
@@ -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"))
)
@@ -547,7 +547,7 @@ benchmarks! {
seal_gas_left {
let r in 0 .. API_BENCHMARK_RUNS;
let instance = Contract::<T>::new(WasmModule::getter(
"seal0", "seal_gas_left", r
"seal1", "gas_left", r
), vec![])?;
let origin = RawOrigin::Signed(instance.caller.clone());
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
@@ -604,9 +604,9 @@ benchmarks! {
let code = WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
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 {
@@ -615,6 +615,7 @@ benchmarks! {
}],
call_body: Some(body::repeated(r, &[
Instruction::I64Const(500_000),
Instruction::I64Const(300_000),
Instruction::I32Const(4),
Instruction::I32Const(0),
Instruction::Call(0),
@@ -1584,16 +1585,26 @@ benchmarks! {
let callee_bytes = callees.iter().flat_map(|x| x.account_id.encode()).collect();
let value: BalanceOf<T> = 0u32.into();
let value_bytes = value.encode();
let value_len = value_bytes.len();
let value_len = BalanceOf::<T>::max_encoded_len() as u32;
// Set an own limit every 2nd call
let own_limit = (u32::MAX - 100).into();
let deposits = (0..r)
.map(|i| if i % 2 == 0 { 0u32.into() } else { own_limit } )
.collect::<Vec<BalanceOf<T>>>();
let deposits_bytes: Vec<u8> = deposits.iter().flat_map(|i| i.encode()).collect();
let deposits_len = deposits_bytes.len() as u32;
let deposit_len = value_len.clone();
let callee_offset = value_len + deposits_len;
let code = WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
imported_functions: vec![ImportedFunction {
module: "seal0",
name: "seal_call",
module: "seal2",
name: "call",
params: vec![
ValueType::I32,
ValueType::I32,
ValueType::I64,
ValueType::I64,
ValueType::I32,
ValueType::I32,
ValueType::I32,
@@ -1609,16 +1620,21 @@ benchmarks! {
value: value_bytes,
},
DataSegment {
offset: value_len as u32,
offset: value_len,
value: deposits_bytes,
},
DataSegment {
offset: callee_offset,
value: callee_bytes,
},
],
call_body: Some(body::repeated_dyn(r, 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(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(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
@@ -1630,7 +1646,7 @@ benchmarks! {
});
let instance = Contract::<T>::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::<T>::from(u32::MAX).into()), vec![])
// This is a slow call: We redeuce the number of runs.
#[pov_mode = Measured]
@@ -1742,17 +1758,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::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
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,
@@ -1765,27 +1781,24 @@ benchmarks! {
.collect::<Result<Vec<_>, &'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::<Vec<_>>();
let hashes_len = hashes_bytes.len();
let hashes_len = &hashes_bytes.len();
let value = Pallet::<T>::min_balance();
assert!(value > 0u32.into());
let value_bytes = value.encode();
let value_len = value_bytes.len();
let value_len = BalanceOf::<T>::max_encoded_len();
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 addr_offset = addr_len_offset + addr_len;
let code = WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
imported_functions: vec![ImportedFunction {
module: "seal0",
name: "seal_instantiate",
module: "seal2",
name: "instantiate",
params: vec![
ValueType::I32,
ValueType::I32,
ValueType::I64,
ValueType::I64,
ValueType::I32,
ValueType::I32,
@@ -1802,7 +1815,7 @@ benchmarks! {
}],
data_segments: vec![
DataSegment {
offset: value_offset as u32,
offset: 0,
value: value_bytes,
},
DataSegment {
@@ -1816,10 +1829,10 @@ benchmarks! {
],
call_body: Some(body::repeated_dyn(r, vec![
Counter(hashes_offset as u32, hash_len as u32), // code_hash_ptr
Regular(Instruction::I32Const(hash_len as i32)), // code_hash_len
Regular(Instruction::I64Const(0)), // gas
Regular(Instruction::I32Const(value_offset as i32)), // value_ptr
Regular(Instruction::I32Const(value_len as i32)), // value_len
Regular(Instruction::I64Const(0)), // ref_time weight
Regular(Instruction::I64Const(0)), // proof_size weight
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
@@ -1827,7 +1840,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),
])),
+99 -25
View File
@@ -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::{
tokens::{Fortitude::Polite, Preservation::Expendable},
@@ -40,7 +42,7 @@ use sp_core::{
sr25519::{Public as SR25519Public, Signature as SR25519Signature},
};
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::*, vec::Vec};
pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
@@ -137,6 +139,7 @@ pub trait Ext: sealing::Sealed {
fn call(
&mut self,
gas_limit: Weight,
deposit_limit: BalanceOf<Self::T>,
to: AccountIdOf<Self::T>,
value: BalanceOf<Self::T>,
input_data: Vec<u8>,
@@ -160,6 +163,7 @@ pub trait Ext: sealing::Sealed {
fn instantiate(
&mut self,
gas_limit: Weight,
deposit_limit: BalanceOf<Self::T>,
code: CodeHash<Self::T>,
value: BalanceOf<Self::T>,
input_data: Vec<u8>,
@@ -692,8 +696,9 @@ where
args,
value,
gas_meter,
storage_meter,
Weight::zero(),
storage_meter,
BalanceOf::<T>::zero(),
schedule,
determinism,
)?;
@@ -719,12 +724,13 @@ where
///
/// This does not take `self` because when constructing the first frame `self` is
/// not initialized, yet.
fn new_frame<S: storage::meter::State>(
fn new_frame<S: storage::meter::State + Default + Debug>(
frame_args: FrameArgs<T, E>,
value_transferred: BalanceOf<T>,
gas_meter: &mut GasMeter<T>,
storage_meter: &mut storage::meter::GenericMeter<T, S>,
gas_limit: Weight,
storage_meter: &mut storage::meter::GenericMeter<T, S>,
deposit_limit: BalanceOf<T>,
schedule: &Schedule<T>,
determinism: Determinism,
) -> Result<(Frame<T>, E, Option<u64>), ExecError> {
@@ -781,7 +787,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,
};
@@ -794,6 +800,7 @@ where
frame_args: FrameArgs<T, E>,
value_transferred: BalanceOf<T>,
gas_limit: Weight,
deposit_limit: BalanceOf<T>,
) -> Result<E, ExecError> {
if self.frames.len() == T::CallStack::size() {
return Err(Error::<T>::MaxCallDepthReached.into())
@@ -817,8 +824,9 @@ where
frame_args,
value_transferred,
nested_gas,
nested_storage,
gas_limit,
nested_storage,
deposit_limit,
self.schedule,
self.determinism,
)?;
@@ -859,8 +867,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);
@@ -869,7 +879,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.
@@ -877,6 +887,13 @@ where
return Err(Error::<T>::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::<T>::deposit_event(
vec![T::Hashing::hash_of(self.caller()), T::Hashing::hash_of(account_id)],
@@ -893,9 +910,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::<T>::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() },
);
},
@@ -1107,6 +1130,7 @@ where
fn call(
&mut self,
gas_limit: Weight,
deposit_limit: BalanceOf<T>,
to: T::AccountId,
value: BalanceOf<T>,
input_data: Vec<u8>,
@@ -1135,6 +1159,7 @@ where
FrameArgs::Call { dest: to, cached_info, delegated_call: None },
value,
gas_limit,
deposit_limit,
)?;
self.run(executable, input_data)
};
@@ -1166,6 +1191,7 @@ where
},
value,
Weight::zero(),
BalanceOf::<T>::zero(),
)?;
self.run(executable, input_data)
}
@@ -1173,6 +1199,7 @@ where
fn instantiate(
&mut self,
gas_limit: Weight,
deposit_limit: BalanceOf<Self::T>,
code_hash: CodeHash<T>,
value: BalanceOf<T>,
input_data: Vec<u8>,
@@ -1190,6 +1217,7 @@ where
},
value,
gas_limit,
deposit_limit,
)?;
let account_id = self.top_frame().account_id.clone();
self.run(executable, input_data).map(|ret| (account_id, ret))
@@ -1917,7 +1945,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::<Test>::zero(), BOB, 0, vec![], true);
ReachedBottom::mutate(|reached_bottom| {
if !*reached_bottom {
@@ -1971,7 +1999,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::<Test>::zero(), CHARLIE, 0, vec![], true),
Ok(_)
);
exec_success()
});
let charlie_ch = MockLoader::insert(Call, |ctx, _| {
@@ -2105,7 +2137,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::<Test>::zero(), CHARLIE, 0, vec![], true)
});
ExtBuilder::default().build().execute_with(|| {
@@ -2136,7 +2169,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::<Test>::zero(), CHARLIE, 0, vec![], true),
Ok(_)
);
exec_success()
});
let charlie_ch = MockLoader::insert(Call, |ctx, _| {
@@ -2287,6 +2324,7 @@ mod tests {
.ext
.instantiate(
Weight::zero(),
BalanceOf::<Test>::zero(),
dummy_ch,
<Test as Config>::Currency::minimum_balance(),
vec![],
@@ -2351,6 +2389,7 @@ mod tests {
assert_matches!(
ctx.ext.instantiate(
Weight::zero(),
BalanceOf::<Test>::zero(),
dummy_ch,
<Test as Config>::Currency::minimum_balance(),
vec![],
@@ -2443,13 +2482,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::<Test>::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::<Test>::zero(), BOB, 0, vec![99], true)
.is_ok());
exec_trapped()
});
@@ -2479,7 +2531,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::<Test>::zero(), ctx.ext.address().clone(), 0, vec![], true),
Err(ExecError{error, ..}) if error == <Error<Test>>::ContractNotFound.into()
);
exec_success()
@@ -2618,7 +2670,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::<Test>::zero(), dest, 0, vec![], false)
});
let code_charlie = MockLoader::insert(Call, |_, _| exec_success());
@@ -2665,15 +2717,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::<Test>::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::<Test>::zero(), BOB, 0, vec![1], true)
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
@@ -2862,6 +2916,7 @@ mod tests {
ctx.ext
.instantiate(
Weight::zero(),
BalanceOf::<Test>::zero(),
fail_code,
ctx.ext.minimum_balance() * 100,
vec![],
@@ -2875,6 +2930,7 @@ mod tests {
.ext
.instantiate(
Weight::zero(),
BalanceOf::<Test>::zero(),
success_code,
ctx.ext.minimum_balance() * 100,
vec![],
@@ -2883,7 +2939,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::<Test>::zero(), account_id, 0, vec![], false)
.unwrap();
exec_success()
});
@@ -3387,7 +3445,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::<Test>::zero(),
fail_code,
0,
vec![],
&[],
),
ExecError {
error: <Error<Test>>::ContractTrapped.into(),
origin: ErrorOrigin::Callee
@@ -3395,7 +3460,16 @@ mod tests {
);
assert_eq!(ctx.ext.nonce(), 1);
// Successful instantiation increments
ctx.ext.instantiate(Weight::zero(), success_code, 0, vec![], &[]).unwrap();
ctx.ext
.instantiate(
Weight::zero(),
BalanceOf::<Test>::zero(),
success_code,
0,
vec![],
&[],
)
.unwrap();
assert_eq!(ctx.ext.nonce(), 2);
exec_success()
});
+11 -7
View File
@@ -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://use.ink) is
@@ -265,6 +265,10 @@ pub mod pallet {
#[pallet::constant]
type DepositPerByte: Get<BalanceOf<Self>>;
/// Fallback value to limit the storage deposit if it's not being set by the caller.
#[pallet::constant]
type DefaultDepositLimit: Get<BalanceOf<Self>>;
/// The amount of balance a caller has to pay for each storage item.
///
/// # Note
@@ -315,8 +319,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;
@@ -350,10 +354,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)
@@ -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 {
@@ -479,6 +483,7 @@ impl Default for Limits {
br_table_size: 256,
subject_len: 32,
payload_len: 16 * 1024,
runtime_memory: 1024 * 1024 * 128,
}
}
}
+59 -21
View File
@@ -23,7 +23,7 @@ use crate::{
};
use codec::Encode;
use frame_support::{
dispatch::DispatchError,
dispatch::{fmt::Debug, DispatchError},
ensure,
traits::{
tokens::{Fortitude::Polite, Preservation::Protect, WithdrawConsequence},
@@ -32,7 +32,10 @@ use frame_support::{
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.
@@ -96,17 +99,24 @@ 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 struct Root;
/// 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<T: Config, E, S: State> {
pub struct RawMeter<T: Config, E, S: State + Default + Debug> {
/// The limit of how much balance this meter is allowed to consume.
limit: BalanceOf<T>,
/// The amount of balance that was used in this meter and all of its already absorbed children.
@@ -118,8 +128,10 @@ pub struct RawMeter<T: Config, E, S: State> {
/// We only have one charge per contract hence the size of this vector is
/// limited by the maximum call depth.
charges: Vec<Charge<T>>,
/// Type parameters are only used in impls.
_phantom: PhantomData<(E, S)>,
/// We store the nested state to determine if it has a special limit for sub-call.
nested: S,
/// Type parameter only used in impls.
_phantom: PhantomData<E>,
}
/// This type is used to describe a storage change when charging from the meter.
@@ -214,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<T: Config> {
deposit_account: DepositAccount<T>,
@@ -255,16 +270,24 @@ impl<T, E, S> RawMeter<T, E, S>
where
T: Config,
E: Ext<T>,
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<T, E, Nested> {
pub fn nested(&self, limit: BalanceOf<T>) -> RawMeter<T, E, Nested> {
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.
@@ -397,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(
@@ -429,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<T>>,
@@ -448,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<T>>,
) -> Result<(), DispatchError> {
match self.nested {
Nested::OwnLimit => self.enforce_limit(info),
Nested::DerivedLimit => Ok(()),
}
}
}
impl<T: Config> Ext<T> for ReservingExt {
@@ -462,7 +499,8 @@ impl<T: Config> Ext<T> for ReservingExt {
let max = T::Currency::reducible_balance(origin, Protect, Polite)
.saturating_sub(min_leftover)
.saturating_sub(Pallet::<T>::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),
@@ -673,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::<Test>::zero());
nested0.charge(&Default::default());
meter.absorb(nested0, DepositAccount(BOB), None);
@@ -695,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::<Test>::zero());
nested0.charge(&Diff {
bytes_added: 108,
bytes_removed: 5,
@@ -706,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::<Test>::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::<Test>::zero());
nested2.charge(&Diff { items_removed: 7, ..Default::default() });
nested0.absorb(nested2, DepositAccount(CHARLIE), Some(&mut nested2_info));
@@ -760,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::<Test>::zero());
nested0.charge(&Diff {
bytes_added: 5,
bytes_removed: 1,
@@ -771,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::<Test>::zero());
nested1.charge(&Diff { items_removed: 5, ..Default::default() });
nested1.charge(&Diff { bytes_added: 20, ..Default::default() });
nested1.terminate(&nested1_info);
+264 -63
View File
@@ -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,
@@ -44,6 +45,7 @@ use frame_support::{
};
use frame_system::{EventRecord, Phase};
use pretty_assertions::{assert_eq, assert_ne};
use sp_core::ByteArray;
use sp_io::hashing::blake2_256;
use sp_keystore::{testing::MemoryKeystore, KeystoreExt};
use sp_runtime::{
@@ -53,8 +55,6 @@ use sp_runtime::{
};
use std::ops::Deref;
use crate as pallet_contracts;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;
@@ -345,6 +345,8 @@ parameter_types! {
};
pub static DepositPerByte: BalanceOf<Test> = 1;
pub const DepositPerItem: BalanceOf<Test> = 2;
// We need this one set high enough for running benchmarks.
pub static DefaultDepositLimit: BalanceOf<Test> = 10_000_000;
}
impl Convert<Weight, BalanceOf<Self>> for Test {
@@ -402,6 +404,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>;
@@ -1110,7 +1113,7 @@ fn cannot_self_destruct_through_draning() {
#[test]
fn cannot_self_destruct_through_storage_refund_after_price_change() {
let (wasm, _code_hash) = compile_module::<Test>("store").unwrap();
let (wasm, _code_hash) = compile_module::<Test>("store_call").unwrap();
ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
let min_balance = <Test as Config>::Currency::minimum_balance();
@@ -2628,7 +2631,6 @@ fn gas_estimation_nested_call_fixed_limit() {
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let min_balance = <Test as Config>::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,
@@ -2662,6 +2664,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
@@ -2678,22 +2681,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::Enforced,
)
.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::Enforced,
)
.result;
assert_err!(result, <Error<Test>>::OutOfGas);
});
}
@@ -2774,7 +2791,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::<Test>("call_runtime").unwrap();
let (callee_code, _callee_hash) = compile_module::<Test>("dummy").unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
@@ -3968,7 +3985,7 @@ fn set_code_hash() {
#[test]
fn storage_deposit_limit_is_enforced() {
let (wasm, _code_hash) = compile_module::<Test>("store").unwrap();
let (wasm, _code_hash) = compile_module::<Test>("store_call").unwrap();
ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
let min_balance = <Test as Config>::Currency::minimum_balance();
@@ -3992,15 +4009,47 @@ fn storage_deposit_limit_is_enforced() {
assert_eq!(get_contract(&addr).total_deposit(), min_balance);
assert_eq!(<Test as Config>::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()
),
<Error<Test>>::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()
),
<Error<Test>>::StorageDepositLimitExhausted,
);
@@ -4008,10 +4057,10 @@ fn storage_deposit_limit_is_enforced() {
}
#[test]
fn storage_deposit_limit_is_enforced_late() {
fn deposit_limit_in_nested_calls() {
let (wasm_caller, _code_hash_caller) =
compile_module::<Test>("create_storage_and_call").unwrap();
let (wasm_callee, _code_hash_callee) = compile_module::<Test>("store").unwrap();
let (wasm_callee, _code_hash_callee) = compile_module::<Test>("store_call").unwrap();
ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
@@ -4054,27 +4103,28 @@ 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 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(
RuntimeOrigin::signed(ALICE),
addr_caller.clone(),
0,
GAS_LIMIT,
Some(codec::Compact(5)),
100u32
.to_le_bytes()
.as_ref()
.iter()
.chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee))
.cloned()
.collect(),
Some(codec::Compact(13)),
(100u32, &addr_callee, 0u64).encode(),
),
<Error<Test>>::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 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(
RuntimeOrigin::signed(ALICE),
@@ -4082,19 +4132,31 @@ fn storage_deposit_limit_is_enforced_late() {
0,
GAS_LIMIT,
Some(codec::Compact(14)),
101u32
.to_le_bytes()
.as_ref()
.iter()
.chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee))
.cloned()
.collect(),
(101u32, &addr_callee, 0u64).encode(),
),
<Error<Test>>::StorageDepositLimitExhausted,
);
// Refund in the callee contract but not enough to cover the 14 balance required by the
// caller.
// 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, &addr_callee, 1u64).encode(),
),
<Error<Test>>::StorageDepositLimitExhausted,
);
// 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 set a special limit for the nested call here.
assert_err_ignore_postinfo!(
Contracts::call(
RuntimeOrigin::signed(ALICE),
@@ -4102,59 +4164,198 @@ fn storage_deposit_limit_is_enforced_late() {
0,
GAS_LIMIT,
Some(codec::Compact(0)),
87u32
.to_le_bytes()
.as_ref()
.iter()
.chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee))
.cloned()
.collect(),
(87u32, &addr_callee, 0u64).encode(),
),
<Error<Test>>::StorageDepositLimitExhausted,
);
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 set a 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)),
1_200u32
.to_le_bytes()
.as_ref()
.iter()
.chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee))
.cloned()
.collect(),
None,
(1200u32, &addr_callee, 1u64).encode(),
),
<Error<Test>>::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(),
0,
GAS_LIMIT,
Some(codec::Compact(1)),
87u32
.to_le_bytes()
.as_ref()
.iter()
.chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee))
.cloned()
.collect(),
(87u32, &addr_callee, 1u64).encode(),
));
});
}
#[test]
fn deposit_limit_in_nested_instantiate() {
let (wasm_caller, _code_hash_caller) =
compile_module::<Test>("create_storage_and_instantiate").unwrap();
let (wasm_callee, code_hash_callee) = compile_module::<Test>("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::<Test>::get(&addr).unwrap().encoded_size() as u64;
// 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:
// - 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, &code_hash_callee, 0u64).encode(),
),
<Error<Test>>::StorageDepositLimitExhausted,
);
// The charges made on instantiation should be rolled back.
assert_eq!(<Test as Config>::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, &code_hash_callee, 0u64).encode(),
),
<Error<Test>>::StorageDepositLimitExhausted,
);
// The charges made on the instantiation should be rolled back.
assert_eq!(<Test as Config>::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, &code_hash_callee, callee_info_len + 2 + ED + 1).encode(),
),
<Error<Test>>::StorageDepositLimitExhausted,
);
// The charges made on the instantiation should be rolled back.
assert_eq!(<Test as Config>::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, &code_hash_callee, callee_info_len + 2 + ED + 2).encode(),
),
<Error<Test>>::StorageDepositLimitExhausted,
);
// The charges made on the instantiation should be rolled back.
assert_eq!(<Test as Config>::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, &code_hash_callee, callee_info_len + 2 + ED + 3).encode(),
false,
Determinism::Enforced,
);
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!(<Test as Config>::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!(<Test as Config>::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!(
<Test as Config>::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::<Test>("store").unwrap();
let (wasm, _code_hash) = compile_module::<Test>("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);
@@ -4198,7 +4399,7 @@ fn deposit_limit_honors_liquidity_restrictions() {
#[test]
fn deposit_limit_honors_existential_deposit() {
let (wasm, _code_hash) = compile_module::<Test>("store").unwrap();
let (wasm, _code_hash) = compile_module::<Test>("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);
@@ -4241,7 +4442,7 @@ fn deposit_limit_honors_existential_deposit() {
#[test]
fn deposit_limit_honors_min_leftover() {
let (wasm, _code_hash) = compile_module::<Test>("store").unwrap();
let (wasm, _code_hash) = compile_module::<Test>("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);
@@ -4266,7 +4467,7 @@ fn deposit_limit_honors_min_leftover() {
assert_eq!(get_contract(&addr).total_deposit(), min_balance);
assert_eq!(<Test as Config>::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),
+27 -21
View File
@@ -370,7 +370,7 @@ mod tests {
gas::GasMeter,
storage::WriteOutcome,
tests::{RuntimeCall, Test, ALICE, BOB},
BalanceOf, CodeHash, Error, OldWeight, Pallet as Contracts,
BalanceOf, CodeHash, Error, Pallet as Contracts,
};
use assert_matches::assert_matches;
use frame_support::{
@@ -470,6 +470,7 @@ mod tests {
fn call(
&mut self,
_gas_limit: Weight,
_deposit_limit: BalanceOf<Self::T>,
to: AccountIdOf<Self::T>,
value: u64,
data: Vec<u8>,
@@ -489,6 +490,7 @@ mod tests {
fn instantiate(
&mut self,
gas_limit: Weight,
_deposit_limit: BalanceOf<Self::T>,
code_hash: CodeHash<Test>,
value: u64,
data: Vec<u8>,
@@ -587,7 +589,11 @@ mod tests {
16_384
}
fn get_weight_price(&self, weight: Weight) -> BalanceOf<Self::T> {
BalanceOf::<Self::T>::from(1312_u32).saturating_mul(weight.ref_time().into())
BalanceOf::<Self::T>::from(1312_u32)
.saturating_mul(weight.ref_time().into())
.saturating_add(
BalanceOf::<Self::T>::from(103_u32).saturating_mul(weight.proof_size()),
)
}
fn schedule(&self) -> &Schedule<Self::T> {
&self.schedule
@@ -1589,7 +1595,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
@@ -1606,7 +1612,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
@@ -1616,11 +1622,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)
)
)
)
@@ -1635,12 +1641,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
@@ -1652,19 +1658,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)
)
@@ -1679,11 +1685,11 @@ mod tests {
let output = execute(CODE_GAS_LEFT, vec![], &mut ext).unwrap();
let 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");
}
/// Test that [`frame_support::weights::OldWeight`] en/decodes the same as our
+208 -43
View File
@@ -433,7 +433,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, deposit_ptr: u32, weight: Weight },
/// Execute deployed code in the context (storage, account ID, value) of the caller contract
DelegateCall { code_hash_ptr: u32 },
}
@@ -873,16 +873,22 @@ 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, deposit_ptr, weight } => {
let callee: <<E as Ext>::T as frame_system::Config>::AccountId =
self.read_sandbox_memory_as(memory, callee_ptr)?;
let deposit_limit: BalanceOf<<E as Ext>::T> = if deposit_ptr == SENTINEL {
BalanceOf::<<E as Ext>::T>::zero()
} else {
self.read_sandbox_memory_as(memory, deposit_ptr)?
};
let value: BalanceOf<<E as Ext>::T> =
self.read_sandbox_memory_as(memory, value_ptr)?;
if value > 0u32.into() {
self.charge_gas(RuntimeCosts::CallSurchargeTransfer)?;
}
self.ext.call(
Weight::from_parts(gas, 0),
weight,
deposit_limit,
callee,
value,
input_data,
@@ -926,7 +932,8 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> {
&mut self,
memory: &mut [u8],
code_hash_ptr: u32,
gas: u64,
weight: Weight,
deposit_ptr: u32,
value_ptr: u32,
input_data_ptr: u32,
input_data_len: u32,
@@ -937,8 +944,12 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> {
salt_ptr: u32,
salt_len: u32,
) -> Result<ReturnCode, TrapReason> {
let gas = Weight::from_parts(gas, 0);
self.charge_gas(RuntimeCosts::InstantiateBase { input_data_len, salt_len })?;
let deposit_limit: BalanceOf<<E as Ext>::T> = if deposit_ptr == SENTINEL {
BalanceOf::<<E as Ext>::T>::zero()
} else {
self.read_sandbox_memory_as(memory, deposit_ptr)?
};
let value: BalanceOf<<E as Ext>::T> = self.read_sandbox_memory_as(memory, value_ptr)?;
if value > 0u32.into() {
self.charge_gas(RuntimeCosts::InstantiateSurchargeTransfer)?;
@@ -947,7 +958,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(gas, 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(
@@ -993,6 +1005,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.
/// It deals only with the *ref_time* Weight.
///
/// - `amount`: How much gas is used.
fn gas(ctx: _, _memory: _, amount: u64) -> Result<(), TrapReason> {
@@ -1002,8 +1015,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: _,
@@ -1076,8 +1090,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(|_| ())
@@ -1299,7 +1314,47 @@ pub mod env {
ctx.call(
memory,
CallFlags::ALLOW_REENTRY,
CallType::Call { callee_ptr, value_ptr, gas },
CallType::Call {
callee_ptr,
value_ptr,
deposit_ptr: SENTINEL,
weight: Weight::from_parts(gas, 0),
},
input_data_ptr,
input_data_len,
output_ptr,
output_len_ptr,
)
}
/// 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<ReturnCode, TrapReason> {
ctx.call(
memory,
CallFlags::from_bits(flags).ok_or(Error::<E::T>::InvalidCallFlags)?,
CallType::Call {
callee_ptr,
value_ptr,
deposit_ptr: SENTINEL,
weight: Weight::from_parts(gas, 0),
},
input_data_ptr,
input_data_len,
output_ptr,
@@ -1318,7 +1373,12 @@ 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.
/// - `gas`: how much gas 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
/// 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.
@@ -1336,14 +1396,16 @@ pub mod env {
/// - `ReturnCode::CalleeTrapped`
/// - `ReturnCode::TransferFailed`
/// - `ReturnCode::NotCallable`
#[version(1)]
#[prefixed_alias]
#[version(2)]
#[unstable]
fn call(
ctx: _,
memory: _,
flags: u32,
callee_ptr: u32,
gas: u64,
ref_time_limit: u64,
proof_size_limit: u64,
deposit_ptr: u32,
value_ptr: u32,
input_data_ptr: u32,
input_data_len: u32,
@@ -1353,7 +1415,12 @@ pub mod env {
ctx.call(
memory,
CallFlags::from_bits(flags).ok_or(Error::<E::T>::InvalidCallFlags)?,
CallType::Call { callee_ptr, value_ptr, gas },
CallType::Call {
callee_ptr,
value_ptr,
deposit_ptr,
weight: Weight::from_parts(ref_time_limit, proof_size_limit),
},
input_data_ptr,
input_data_len,
output_ptr,
@@ -1417,8 +1484,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(
@@ -1441,7 +1507,47 @@ pub mod env {
ctx.instantiate(
memory,
code_hash_ptr,
gas,
Weight::from_parts(gas, 0),
SENTINEL,
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.
///
/// 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<ReturnCode, TrapReason> {
ctx.instantiate(
memory,
code_hash_ptr,
Weight::from_parts(gas, 0),
SENTINEL,
value_ptr,
input_data_ptr,
input_data_len,
@@ -1468,15 +1574,21 @@ 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_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
/// 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_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.
/// - `address_ptr`: a pointer where the new account's address is copied to. `SENTINEL` means
/// not to copy.
/// - `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.
/// - `salt_ptr`: Pointer to raw bytes used for address derivation. See `fn contract_address`.
@@ -1494,13 +1606,15 @@ 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_limit: u64,
proof_size_limit: u64,
deposit_ptr: u32,
value_ptr: u32,
input_data_ptr: u32,
input_data_len: u32,
@@ -1514,7 +1628,8 @@ pub mod env {
ctx.instantiate(
memory,
code_hash_ptr,
gas,
Weight::from_parts(ref_time_limit, proof_size_limit),
deposit_ptr,
value_ptr,
input_data_ptr,
input_data_len,
@@ -1762,17 +1877,9 @@ 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.
///
/// 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.
/// 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: _,
@@ -1793,18 +1900,76 @@ pub mod env {
)?)
}
/// Stores the amount of gas left into the supplied buffer.
/// 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 `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_limit: u64,
proof_size_limit: u64,
out_ptr: u32,
out_len_ptr: u32,
) -> Result<(), TrapReason> {
let weight = Weight::from_parts(ref_time_limit, proof_size_limit);
ctx.charge_gas(RuntimeCosts::WeightToFee)?;
Ok(ctx.write_sandbox_output(
memory,
out_ptr,
out_len_ptr,
&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 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,