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

* added [unstable][seal2] call()

* updated test to cover new seal_call proof_limit

* docs updated

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

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

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

* update benchmarks

* add DefaultDepositLimit to pallet Config

* specify deposit limit for nested call

add test for nested call deposit limit

save: separate deposit limit for nested calls

* specify deposit limit for nested instantiate

save: works with test

cleaned up debugging outputs

* update benchmarks

* added missing fixtures

* fix benches

* pass explicit deposit limit to storage bench

* explicit deposit limit for another set_storage bench

* add more deposit limit for storage benches

* moving to simplified benchmarks

* moved to simplified benchmarks

* fix seal_weight_to_fee bench

* fix seal_instantiate benchmark

* doc typo fix

* default dl for benchmarking

more dl for tests

dl for tests to max

deposit_limit fix in instantiate bench

fix instantiate bench

fix instantiate benchmark

fix instantiate bench again

remove dbg

fix seal bench again

fixing it still

seal_instantiate zero deposit

less runs to check if deposit enough

try

try 2

try 3

try 4

* max_runtime_mem to Schedule limits

* add default deposit limit fallback check to test

* weight params renaming

* fmt

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

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

* prettify inputs in tests

* typestate param refactored

---------

Co-authored-by: PG Herveou <pgherveou@gmail.com>
This commit is contained in:
Sasha Gryaznov
2023-04-26 14:27:13 +03:00
committed by GitHub
parent 01c66da036
commit 60310de7d6
16 changed files with 949 additions and 275 deletions
+27 -21
View File
@@ -370,7 +370,7 @@ mod tests {
gas::GasMeter,
storage::WriteOutcome,
tests::{RuntimeCall, Test, ALICE, BOB},
BalanceOf, CodeHash, Error, OldWeight, Pallet as Contracts,
BalanceOf, CodeHash, Error, Pallet as Contracts,
};
use assert_matches::assert_matches;
use frame_support::{
@@ -470,6 +470,7 @@ mod tests {
fn call(
&mut self,
_gas_limit: Weight,
_deposit_limit: BalanceOf<Self::T>,
to: AccountIdOf<Self::T>,
value: u64,
data: Vec<u8>,
@@ -489,6 +490,7 @@ mod tests {
fn instantiate(
&mut self,
gas_limit: Weight,
_deposit_limit: BalanceOf<Self::T>,
code_hash: CodeHash<Test>,
value: u64,
data: Vec<u8>,
@@ -587,7 +589,11 @@ mod tests {
16_384
}
fn get_weight_price(&self, weight: Weight) -> BalanceOf<Self::T> {
BalanceOf::<Self::T>::from(1312_u32).saturating_mul(weight.ref_time().into())
BalanceOf::<Self::T>::from(1312_u32)
.saturating_mul(weight.ref_time().into())
.saturating_add(
BalanceOf::<Self::T>::from(103_u32).saturating_mul(weight.proof_size()),
)
}
fn schedule(&self) -> &Schedule<Self::T> {
&self.schedule
@@ -1589,7 +1595,7 @@ mod tests {
const CODE_GAS_PRICE: &str = r#"
(module
(import "seal0" "seal_weight_to_fee" (func $seal_weight_to_fee (param i64 i32 i32)))
(import "seal1" "weight_to_fee" (func $seal_weight_to_fee (param i64 i64 i32 i32)))
(import "env" "memory" (memory 1 1))
;; size of our buffer is 32 bytes
@@ -1606,7 +1612,7 @@ mod tests {
(func (export "call")
;; This stores the gas price in the buffer
(call $seal_weight_to_fee (i64.const 2) (i32.const 0) (i32.const 32))
(call $seal_weight_to_fee (i64.const 2) (i64.const 1) (i32.const 0) (i32.const 32))
;; assert len == 8
(call $assert
@@ -1616,11 +1622,11 @@ mod tests {
)
)
;; assert that contents of the buffer is equal to the i64 value of 2 * 1312.
;; assert that contents of the buffer is equal to the i64 value of 2 * 1312 + 103 = 2727.
(call $assert
(i64.eq
(i64.load (i32.const 0))
(i64.const 2624)
(i64.const 2727)
)
)
)
@@ -1635,12 +1641,12 @@ mod tests {
const CODE_GAS_LEFT: &str = r#"
(module
(import "seal0" "seal_gas_left" (func $seal_gas_left (param i32 i32)))
(import "seal1" "gas_left" (func $seal_gas_left (param i32 i32)))
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
(import "env" "memory" (memory 1 1))
;; size of our buffer is 32 bytes
(data (i32.const 32) "\20")
;; Make output buffer size 20 bytes
(data (i32.const 20) "\14")
(func $assert (param i32)
(block $ok
@@ -1652,19 +1658,19 @@ mod tests {
)
(func (export "call")
;; This stores the gas left in the buffer
(call $seal_gas_left (i32.const 0) (i32.const 32))
;; This stores the weight left to the buffer
(call $seal_gas_left (i32.const 0) (i32.const 20))
;; assert len == 8
;; Assert len <= 16 (max encoded Weight len)
(call $assert
(i32.eq
(i32.load (i32.const 32))
(i32.const 8)
(i32.le_u
(i32.load (i32.const 20))
(i32.const 16)
)
)
;; return gas left
(call $seal_return (i32.const 0) (i32.const 0) (i32.const 8))
;; Return weight left and its encoded value len
(call $seal_return (i32.const 0) (i32.const 0) (i32.load (i32.const 20)))
(unreachable)
)
@@ -1679,11 +1685,11 @@ mod tests {
let output = execute(CODE_GAS_LEFT, vec![], &mut ext).unwrap();
let gas_left = OldWeight::decode(&mut &*output.data).unwrap();
let weight_left = Weight::decode(&mut &*output.data).unwrap();
let actual_left = ext.gas_meter.gas_left();
// TODO: account for proof size weight
assert!(gas_left < gas_limit.ref_time(), "gas_left must be less than initial");
assert!(gas_left > actual_left.ref_time(), "gas_left must be greater than final");
assert!(weight_left.all_lt(gas_limit), "gas_left must be less than initial");
assert!(weight_left.all_gt(actual_left), "gas_left must be greater than final");
}
/// Test that [`frame_support::weights::OldWeight`] en/decodes the same as our
+208 -43
View File
@@ -433,7 +433,7 @@ bitflags! {
/// The kind of call that should be performed.
enum CallType {
/// Execute another instantiated contract
Call { callee_ptr: u32, value_ptr: u32, gas: u64 },
Call { callee_ptr: u32, value_ptr: u32, deposit_ptr: u32, weight: Weight },
/// Execute deployed code in the context (storage, account ID, value) of the caller contract
DelegateCall { code_hash_ptr: u32 },
}
@@ -873,16 +873,22 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> {
};
let call_outcome = match call_type {
CallType::Call { callee_ptr, value_ptr, gas } => {
CallType::Call { callee_ptr, value_ptr, deposit_ptr, weight } => {
let callee: <<E as Ext>::T as frame_system::Config>::AccountId =
self.read_sandbox_memory_as(memory, callee_ptr)?;
let deposit_limit: BalanceOf<<E as Ext>::T> = if deposit_ptr == SENTINEL {
BalanceOf::<<E as Ext>::T>::zero()
} else {
self.read_sandbox_memory_as(memory, deposit_ptr)?
};
let value: BalanceOf<<E as Ext>::T> =
self.read_sandbox_memory_as(memory, value_ptr)?;
if value > 0u32.into() {
self.charge_gas(RuntimeCosts::CallSurchargeTransfer)?;
}
self.ext.call(
Weight::from_parts(gas, 0),
weight,
deposit_limit,
callee,
value,
input_data,
@@ -926,7 +932,8 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> {
&mut self,
memory: &mut [u8],
code_hash_ptr: u32,
gas: u64,
weight: Weight,
deposit_ptr: u32,
value_ptr: u32,
input_data_ptr: u32,
input_data_len: u32,
@@ -937,8 +944,12 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> {
salt_ptr: u32,
salt_len: u32,
) -> Result<ReturnCode, TrapReason> {
let gas = Weight::from_parts(gas, 0);
self.charge_gas(RuntimeCosts::InstantiateBase { input_data_len, salt_len })?;
let deposit_limit: BalanceOf<<E as Ext>::T> = if deposit_ptr == SENTINEL {
BalanceOf::<<E as Ext>::T>::zero()
} else {
self.read_sandbox_memory_as(memory, deposit_ptr)?
};
let value: BalanceOf<<E as Ext>::T> = self.read_sandbox_memory_as(memory, value_ptr)?;
if value > 0u32.into() {
self.charge_gas(RuntimeCosts::InstantiateSurchargeTransfer)?;
@@ -947,7 +958,8 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> {
self.read_sandbox_memory_as(memory, code_hash_ptr)?;
let input_data = self.read_sandbox_memory(memory, input_data_ptr, input_data_len)?;
let salt = self.read_sandbox_memory(memory, salt_ptr, salt_len)?;
let instantiate_outcome = self.ext.instantiate(gas, code_hash, value, input_data, &salt);
let instantiate_outcome =
self.ext.instantiate(weight, deposit_limit, code_hash, value, input_data, &salt);
if let Ok((address, output)) = &instantiate_outcome {
if !output.flags.contains(ReturnFlags::REVERT) {
self.write_sandbox_output(
@@ -993,6 +1005,7 @@ pub mod env {
///
/// NOTE: This is a implementation defined call and is NOT a part of the public API.
/// This call is supposed to be called only by instrumentation injected code.
/// It deals only with the *ref_time* Weight.
///
/// - `amount`: How much gas is used.
fn gas(ctx: _, _memory: _, amount: u64) -> Result<(), TrapReason> {
@@ -1002,8 +1015,9 @@ pub mod env {
/// Set the value at the given key in the contract storage.
///
/// Equivalent to the newer version [`super::seal1::Api::set_storage`] with the exception of the
/// return type. Still a valid thing to call when not interested in the return value.
/// Equivalent to the newer [`seal1`][`super::api_doc::Version1::set_storage`] version with the
/// exception of the return type. Still a valid thing to call when not interested in the return
/// value.
#[prefixed_alias]
fn set_storage(
ctx: _,
@@ -1076,8 +1090,9 @@ pub mod env {
/// Clear the value at the given key in the contract storage.
///
/// Equivalent to the newer version [`super::seal1::Api::clear_storage`] with the exception of
/// the return type. Still a valid thing to call when not interested in the return value.
/// Equivalent to the newer [`seal1`][`super::api_doc::Version1::clear_storage`] version with
/// the exception of the return type. Still a valid thing to call when not interested in the
/// return value.
#[prefixed_alias]
fn clear_storage(ctx: _, memory: _, key_ptr: u32) -> Result<(), TrapReason> {
ctx.clear_storage(memory, KeyType::Fix, key_ptr).map(|_| ())
@@ -1299,7 +1314,47 @@ pub mod env {
ctx.call(
memory,
CallFlags::ALLOW_REENTRY,
CallType::Call { callee_ptr, value_ptr, gas },
CallType::Call {
callee_ptr,
value_ptr,
deposit_ptr: SENTINEL,
weight: Weight::from_parts(gas, 0),
},
input_data_ptr,
input_data_len,
output_ptr,
output_len_ptr,
)
}
/// Make a call to another contract.
///
/// Equivalent to the newer [`seal2`][`super::api_doc::Version2::call`] version but works with
/// *ref_time* Weight only. It is recommended to switch to the latest version, once it's
/// stabilized.
#[version(1)]
#[prefixed_alias]
fn call(
ctx: _,
memory: _,
flags: u32,
callee_ptr: u32,
gas: u64,
value_ptr: u32,
input_data_ptr: u32,
input_data_len: u32,
output_ptr: u32,
output_len_ptr: u32,
) -> Result<ReturnCode, TrapReason> {
ctx.call(
memory,
CallFlags::from_bits(flags).ok_or(Error::<E::T>::InvalidCallFlags)?,
CallType::Call {
callee_ptr,
value_ptr,
deposit_ptr: SENTINEL,
weight: Weight::from_parts(gas, 0),
},
input_data_ptr,
input_data_len,
output_ptr,
@@ -1318,7 +1373,12 @@ pub mod env {
/// - `flags`: See `crate::wasm::runtime::CallFlags` for a documentation of the supported flags.
/// - `callee_ptr`: a pointer to the address of the callee contract. Should be decodable as an
/// `T::AccountId`. Traps otherwise.
/// - `gas`: how much gas to devote to the execution.
/// - `ref_time_limit`: how much *ref_time* Weight to devote to the execution.
/// - `proof_size_limit`: how much *proof_size* Weight to devote to the execution.
/// - `deposit_ptr`: a pointer to the buffer with value of the storage deposit limit for the
/// call. Should be decodable as a `T::Balance`. Traps otherwise. Passing `SENTINEL` means
/// setting no specific limit for the call, which implies storage usage up to the limit of the
/// parent call.
/// - `value_ptr`: a pointer to the buffer with value, how much value to send. Should be
/// decodable as a `T::Balance`. Traps otherwise.
/// - `input_data_ptr`: a pointer to a buffer to be used as input data to the callee.
@@ -1336,14 +1396,16 @@ pub mod env {
/// - `ReturnCode::CalleeTrapped`
/// - `ReturnCode::TransferFailed`
/// - `ReturnCode::NotCallable`
#[version(1)]
#[prefixed_alias]
#[version(2)]
#[unstable]
fn call(
ctx: _,
memory: _,
flags: u32,
callee_ptr: u32,
gas: u64,
ref_time_limit: u64,
proof_size_limit: u64,
deposit_ptr: u32,
value_ptr: u32,
input_data_ptr: u32,
input_data_len: u32,
@@ -1353,7 +1415,12 @@ pub mod env {
ctx.call(
memory,
CallFlags::from_bits(flags).ok_or(Error::<E::T>::InvalidCallFlags)?,
CallType::Call { callee_ptr, value_ptr, gas },
CallType::Call {
callee_ptr,
value_ptr,
deposit_ptr,
weight: Weight::from_parts(ref_time_limit, proof_size_limit),
},
input_data_ptr,
input_data_len,
output_ptr,
@@ -1417,8 +1484,7 @@ pub mod env {
/// # Note
///
/// The values `_code_hash_len` and `_value_len` are ignored because the encoded sizes
/// of those types are fixed through
/// [`codec::MaxEncodedLen`]. The fields exist
/// of those types are fixed through [`codec::MaxEncodedLen`]. The fields exist
/// for backwards compatibility. Consider switching to the newest version of this function.
#[prefixed_alias]
fn instantiate(
@@ -1441,7 +1507,47 @@ pub mod env {
ctx.instantiate(
memory,
code_hash_ptr,
gas,
Weight::from_parts(gas, 0),
SENTINEL,
value_ptr,
input_data_ptr,
input_data_len,
address_ptr,
address_len_ptr,
output_ptr,
output_len_ptr,
salt_ptr,
salt_len,
)
}
/// Instantiate a contract with the specified code hash.
///
/// Equivalent to the newer [`seal2`][`super::api_doc::Version2::instantiate`] version but works
/// with *ref_time* Weight only. It is recommended to switch to the latest version, once it's
/// stabilized.
#[version(1)]
#[prefixed_alias]
fn instantiate(
ctx: _,
memory: _,
code_hash_ptr: u32,
gas: u64,
value_ptr: u32,
input_data_ptr: u32,
input_data_len: u32,
address_ptr: u32,
address_len_ptr: u32,
output_ptr: u32,
output_len_ptr: u32,
salt_ptr: u32,
salt_len: u32,
) -> Result<ReturnCode, TrapReason> {
ctx.instantiate(
memory,
code_hash_ptr,
Weight::from_parts(gas, 0),
SENTINEL,
value_ptr,
input_data_ptr,
input_data_len,
@@ -1468,15 +1574,21 @@ pub mod env {
/// # Parameters
///
/// - `code_hash_ptr`: a pointer to the buffer that contains the initializer code.
/// - `gas`: how much gas to devote to the execution of the initializer code.
/// - `ref_time_limit`: how much *ref_time* Weight to devote to the execution.
/// - `proof_size_limit`: how much *proof_size* Weight to devote to the execution.
/// - `deposit_ptr`: a pointer to the buffer with value of the storage deposit limit for
/// instantiation. Should be decodable as a `T::Balance`. Traps otherwise. Passing `SENTINEL`
/// means setting no specific limit for the call, which implies storage usage up to the limit
/// of the parent call.
/// - `value_ptr`: a pointer to the buffer with value, how much value to send. Should be
/// decodable as a `T::Balance`. Traps otherwise.
/// - `input_data_ptr`: a pointer to a buffer to be used as input data to the initializer code.
/// - `input_data_len`: length of the input data buffer.
/// - `address_ptr`: a pointer where the new account's address is copied to.
/// - `address_len_ptr`: in-out pointer to where the length of the buffer is read from and the
/// actual length is written to.
/// - `output_ptr`: a pointer where the output buffer is copied to.
/// - `address_ptr`: a pointer where the new account's address is copied to. `SENTINEL` means
/// not to copy.
/// - `address_len_ptr`: pointer to where put the length of the address.
/// - `output_ptr`: a pointer where the output buffer is copied to. `SENTINEL` means not to
/// copy.
/// - `output_len_ptr`: in-out pointer to where the length of the buffer is read from and the
/// actual length is written to.
/// - `salt_ptr`: Pointer to raw bytes used for address derivation. See `fn contract_address`.
@@ -1494,13 +1606,15 @@ pub mod env {
/// - `ReturnCode::CalleeTrapped`
/// - `ReturnCode::TransferFailed`
/// - `ReturnCode::CodeNotFound`
#[version(1)]
#[prefixed_alias]
#[version(2)]
#[unstable]
fn instantiate(
ctx: _,
memory: _,
code_hash_ptr: u32,
gas: u64,
ref_time_limit: u64,
proof_size_limit: u64,
deposit_ptr: u32,
value_ptr: u32,
input_data_ptr: u32,
input_data_len: u32,
@@ -1514,7 +1628,8 @@ pub mod env {
ctx.instantiate(
memory,
code_hash_ptr,
gas,
Weight::from_parts(ref_time_limit, proof_size_limit),
deposit_ptr,
value_ptr,
input_data_ptr,
input_data_len,
@@ -1762,17 +1877,9 @@ pub mod env {
/// Stores the price for the specified amount of gas into the supplied buffer.
///
/// The value is stored to linear memory at the address pointed to by `out_ptr`.
/// `out_len_ptr` must point to a u32 value that describes the available space at
/// `out_ptr`. This call overwrites it with the size of the value. If the available
/// space at `out_ptr` is less than the size of the value a trap is triggered.
///
/// The data is encoded as `T::Balance`.
///
/// # Note
///
/// It is recommended to avoid specifying very small values for `gas` as the prices for a single
/// gas can be smaller than one.
/// Equivalent to the newer [`seal1`][`super::api_doc::Version2::weight_to_fee`] version but
/// works with *ref_time* Weight only. It is recommended to switch to the latest version, once
/// it's stabilized.
#[prefixed_alias]
fn weight_to_fee(
ctx: _,
@@ -1793,18 +1900,76 @@ pub mod env {
)?)
}
/// Stores the amount of gas left into the supplied buffer.
/// Stores the price for the specified amount of weight into the supplied buffer.
///
/// # Parameters
///
/// - `out_ptr`: pointer to the linear memory where the returning value is written to. If the
/// available space at `out_ptr` is less than the size of the value a trap is triggered.
/// - `out_len_ptr`: in-out pointer into linear memory where the buffer length is read from and
/// the value length is written to.
///
/// The data is encoded as `T::Balance`.
///
/// # Note
///
/// It is recommended to avoid specifying very small values for `ref_time_limit` and
/// `proof_size_limit` as the prices for a single gas can be smaller than the basic balance
/// unit.
#[version(1)]
#[unstable]
fn weight_to_fee(
ctx: _,
memory: _,
ref_time_limit: u64,
proof_size_limit: u64,
out_ptr: u32,
out_len_ptr: u32,
) -> Result<(), TrapReason> {
let weight = Weight::from_parts(ref_time_limit, proof_size_limit);
ctx.charge_gas(RuntimeCosts::WeightToFee)?;
Ok(ctx.write_sandbox_output(
memory,
out_ptr,
out_len_ptr,
&ctx.ext.get_weight_price(weight).encode(),
false,
already_charged,
)?)
}
/// Stores the weight left into the supplied buffer.
///
/// Equivalent to the newer [`seal1`][`super::api_doc::Version2::gas_left`] version but
/// works with *ref_time* Weight only. It is recommended to switch to the latest version, once
/// it's stabilized.
#[prefixed_alias]
fn gas_left(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> {
ctx.charge_gas(RuntimeCosts::GasLeft)?;
let gas_left = &ctx.ext.gas_meter().gas_left().ref_time().encode();
Ok(ctx.write_sandbox_output(
memory,
out_ptr,
out_len_ptr,
gas_left,
false,
already_charged,
)?)
}
/// Stores the amount of weight left into the supplied buffer.
///
/// The value is stored to linear memory at the address pointed to by `out_ptr`.
/// `out_len_ptr` must point to a u32 value that describes the available space at
/// `out_ptr`. This call overwrites it with the size of the value. If the available
/// space at `out_ptr` is less than the size of the value a trap is triggered.
///
/// The data is encoded as Gas.
#[prefixed_alias]
/// The data is encoded as Weight.
#[version(1)]
#[unstable]
fn gas_left(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> {
ctx.charge_gas(RuntimeCosts::GasLeft)?;
let gas_left = &ctx.ext.gas_meter().gas_left().ref_time().encode();
let gas_left = &ctx.ext.gas_meter().gas_left().encode();
Ok(ctx.write_sandbox_output(
memory,
out_ptr,