mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 21:11:07 +00:00
[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:
@@ -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>;
|
||||
|
||||
@@ -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),
|
||||
])),
|
||||
|
||||
@@ -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()
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user