[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
+99 -25
View File
@@ -23,7 +23,9 @@ use crate::{
};
use frame_support::{
crypto::ecdsa::ECDSAExt,
dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo, Dispatchable},
dispatch::{
fmt::Debug, DispatchError, DispatchResult, DispatchResultWithPostInfo, Dispatchable,
},
storage::{with_transaction, TransactionOutcome},
traits::{
tokens::{Fortitude::Polite, Preservation::Expendable},
@@ -40,7 +42,7 @@ use sp_core::{
sr25519::{Public as SR25519Public, Signature as SR25519Signature},
};
use sp_io::{crypto::secp256k1_ecdsa_recover_compressed, hashing::blake2_256};
use sp_runtime::traits::{Convert, Hash};
use sp_runtime::traits::{Convert, Hash, Zero};
use sp_std::{marker::PhantomData, mem, prelude::*, vec::Vec};
pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
@@ -137,6 +139,7 @@ pub trait Ext: sealing::Sealed {
fn call(
&mut self,
gas_limit: Weight,
deposit_limit: BalanceOf<Self::T>,
to: AccountIdOf<Self::T>,
value: BalanceOf<Self::T>,
input_data: Vec<u8>,
@@ -160,6 +163,7 @@ pub trait Ext: sealing::Sealed {
fn instantiate(
&mut self,
gas_limit: Weight,
deposit_limit: BalanceOf<Self::T>,
code: CodeHash<Self::T>,
value: BalanceOf<Self::T>,
input_data: Vec<u8>,
@@ -692,8 +696,9 @@ where
args,
value,
gas_meter,
storage_meter,
Weight::zero(),
storage_meter,
BalanceOf::<T>::zero(),
schedule,
determinism,
)?;
@@ -719,12 +724,13 @@ where
///
/// This does not take `self` because when constructing the first frame `self` is
/// not initialized, yet.
fn new_frame<S: storage::meter::State>(
fn new_frame<S: storage::meter::State + Default + Debug>(
frame_args: FrameArgs<T, E>,
value_transferred: BalanceOf<T>,
gas_meter: &mut GasMeter<T>,
storage_meter: &mut storage::meter::GenericMeter<T, S>,
gas_limit: Weight,
storage_meter: &mut storage::meter::GenericMeter<T, S>,
deposit_limit: BalanceOf<T>,
schedule: &Schedule<T>,
determinism: Determinism,
) -> Result<(Frame<T>, E, Option<u64>), ExecError> {
@@ -781,7 +787,7 @@ where
account_id,
entry_point,
nested_gas: gas_meter.nested(gas_limit)?,
nested_storage: storage_meter.nested(),
nested_storage: storage_meter.nested(deposit_limit),
allows_reentry: true,
};
@@ -794,6 +800,7 @@ where
frame_args: FrameArgs<T, E>,
value_transferred: BalanceOf<T>,
gas_limit: Weight,
deposit_limit: BalanceOf<T>,
) -> Result<E, ExecError> {
if self.frames.len() == T::CallStack::size() {
return Err(Error::<T>::MaxCallDepthReached.into())
@@ -817,8 +824,9 @@ where
frame_args,
value_transferred,
nested_gas,
nested_storage,
gas_limit,
nested_storage,
deposit_limit,
self.schedule,
self.determinism,
)?;
@@ -859,8 +867,10 @@ where
return Ok(output)
}
// Storage limit is enforced as late as possible (when the last frame returns) so that
// the ordering of storage accesses does not matter.
// Storage limit is normally enforced as late as possible (when the last frame returns)
// so that the ordering of storage accesses does not matter.
// (However, if a special limit was set for a sub-call, it should be enforced right
// after the sub-call returned. See below for this case of enforcement).
if self.frames.is_empty() {
let frame = &mut self.first_frame;
frame.contract_info.load(&frame.account_id);
@@ -869,7 +879,7 @@ where
}
let frame = self.top_frame();
let account_id = &frame.account_id;
let account_id = &frame.account_id.clone();
match (entry_point, delegated_code_hash) {
(ExportedFunction::Constructor, _) => {
// It is not allowed to terminate a contract inside its constructor.
@@ -877,6 +887,13 @@ where
return Err(Error::<T>::TerminatedInConstructor.into())
}
// If a special limit was set for the sub-call, we enforce it here.
// This is needed because contract constructor might write to storage.
// The sub-call will be rolled back in case the limit is exhausted.
let frame = self.top_frame_mut();
let contract = frame.contract_info.as_contract();
frame.nested_storage.enforce_subcall_limit(contract)?;
// Deposit an instantiation event.
Contracts::<T>::deposit_event(
vec![T::Hashing::hash_of(self.caller()), T::Hashing::hash_of(account_id)],
@@ -893,9 +910,15 @@ where
);
},
(ExportedFunction::Call, None) => {
// If a special limit was set for the sub-call, we enforce it here.
// The sub-call will be rolled back in case the limit is exhausted.
let frame = self.top_frame_mut();
let contract = frame.contract_info.as_contract();
frame.nested_storage.enforce_subcall_limit(contract)?;
let caller = self.caller();
Contracts::<T>::deposit_event(
vec![T::Hashing::hash_of(caller), T::Hashing::hash_of(account_id)],
vec![T::Hashing::hash_of(caller), T::Hashing::hash_of(&account_id)],
Event::Called { caller: caller.clone(), contract: account_id.clone() },
);
},
@@ -1107,6 +1130,7 @@ where
fn call(
&mut self,
gas_limit: Weight,
deposit_limit: BalanceOf<T>,
to: T::AccountId,
value: BalanceOf<T>,
input_data: Vec<u8>,
@@ -1135,6 +1159,7 @@ where
FrameArgs::Call { dest: to, cached_info, delegated_call: None },
value,
gas_limit,
deposit_limit,
)?;
self.run(executable, input_data)
};
@@ -1166,6 +1191,7 @@ where
},
value,
Weight::zero(),
BalanceOf::<T>::zero(),
)?;
self.run(executable, input_data)
}
@@ -1173,6 +1199,7 @@ where
fn instantiate(
&mut self,
gas_limit: Weight,
deposit_limit: BalanceOf<Self::T>,
code_hash: CodeHash<T>,
value: BalanceOf<T>,
input_data: Vec<u8>,
@@ -1190,6 +1217,7 @@ where
},
value,
gas_limit,
deposit_limit,
)?;
let account_id = self.top_frame().account_id.clone();
self.run(executable, input_data).map(|ret| (account_id, ret))
@@ -1917,7 +1945,7 @@ mod tests {
let value = Default::default();
let recurse_ch = MockLoader::insert(Call, |ctx, _| {
// Try to call into yourself.
let r = ctx.ext.call(Weight::zero(), BOB, 0, vec![], true);
let r = ctx.ext.call(Weight::zero(), BalanceOf::<Test>::zero(), BOB, 0, vec![], true);
ReachedBottom::mutate(|reached_bottom| {
if !*reached_bottom {
@@ -1971,7 +1999,11 @@ mod tests {
WitnessedCallerBob::mutate(|caller| *caller = Some(ctx.ext.caller().clone()));
// Call into CHARLIE contract.
assert_matches!(ctx.ext.call(Weight::zero(), CHARLIE, 0, vec![], true), Ok(_));
assert_matches!(
ctx.ext
.call(Weight::zero(), BalanceOf::<Test>::zero(), CHARLIE, 0, vec![], true),
Ok(_)
);
exec_success()
});
let charlie_ch = MockLoader::insert(Call, |ctx, _| {
@@ -2105,7 +2137,8 @@ mod tests {
// ALICE is the origin of the call stack
assert!(ctx.ext.caller_is_origin());
// BOB calls CHARLIE
ctx.ext.call(Weight::zero(), CHARLIE, 0, vec![], true)
ctx.ext
.call(Weight::zero(), BalanceOf::<Test>::zero(), CHARLIE, 0, vec![], true)
});
ExtBuilder::default().build().execute_with(|| {
@@ -2136,7 +2169,11 @@ mod tests {
assert_eq!(*ctx.ext.address(), BOB);
// Call into charlie contract.
assert_matches!(ctx.ext.call(Weight::zero(), CHARLIE, 0, vec![], true), Ok(_));
assert_matches!(
ctx.ext
.call(Weight::zero(), BalanceOf::<Test>::zero(), CHARLIE, 0, vec![], true),
Ok(_)
);
exec_success()
});
let charlie_ch = MockLoader::insert(Call, |ctx, _| {
@@ -2287,6 +2324,7 @@ mod tests {
.ext
.instantiate(
Weight::zero(),
BalanceOf::<Test>::zero(),
dummy_ch,
<Test as Config>::Currency::minimum_balance(),
vec![],
@@ -2351,6 +2389,7 @@ mod tests {
assert_matches!(
ctx.ext.instantiate(
Weight::zero(),
BalanceOf::<Test>::zero(),
dummy_ch,
<Test as Config>::Currency::minimum_balance(),
vec![],
@@ -2443,13 +2482,26 @@ mod tests {
let info = ctx.ext.contract_info();
assert_eq!(info.storage_byte_deposit, 0);
info.storage_byte_deposit = 42;
assert_eq!(ctx.ext.call(Weight::zero(), CHARLIE, 0, vec![], true), exec_trapped());
assert_eq!(
ctx.ext.call(
Weight::zero(),
BalanceOf::<Test>::zero(),
CHARLIE,
0,
vec![],
true
),
exec_trapped()
);
assert_eq!(ctx.ext.contract_info().storage_byte_deposit, 42);
}
exec_success()
});
let code_charlie = MockLoader::insert(Call, |ctx, _| {
assert!(ctx.ext.call(Weight::zero(), BOB, 0, vec![99], true).is_ok());
assert!(ctx
.ext
.call(Weight::zero(), BalanceOf::<Test>::zero(), BOB, 0, vec![99], true)
.is_ok());
exec_trapped()
});
@@ -2479,7 +2531,7 @@ mod tests {
fn recursive_call_during_constructor_fails() {
let code = MockLoader::insert(Constructor, |ctx, _| {
assert_matches!(
ctx.ext.call(Weight::zero(), ctx.ext.address().clone(), 0, vec![], true),
ctx.ext.call(Weight::zero(), BalanceOf::<Test>::zero(), ctx.ext.address().clone(), 0, vec![], true),
Err(ExecError{error, ..}) if error == <Error<Test>>::ContractNotFound.into()
);
exec_success()
@@ -2618,7 +2670,7 @@ mod tests {
// call the contract passed as input with disabled reentry
let code_bob = MockLoader::insert(Call, |ctx, _| {
let dest = Decode::decode(&mut ctx.input_data.as_ref()).unwrap();
ctx.ext.call(Weight::zero(), dest, 0, vec![], false)
ctx.ext.call(Weight::zero(), BalanceOf::<Test>::zero(), dest, 0, vec![], false)
});
let code_charlie = MockLoader::insert(Call, |_, _| exec_success());
@@ -2665,15 +2717,17 @@ mod tests {
fn call_deny_reentry() {
let code_bob = MockLoader::insert(Call, |ctx, _| {
if ctx.input_data[0] == 0 {
ctx.ext.call(Weight::zero(), CHARLIE, 0, vec![], false)
ctx.ext
.call(Weight::zero(), BalanceOf::<Test>::zero(), CHARLIE, 0, vec![], false)
} else {
exec_success()
}
});
// call BOB with input set to '1'
let code_charlie =
MockLoader::insert(Call, |ctx, _| ctx.ext.call(Weight::zero(), BOB, 0, vec![1], true));
let code_charlie = MockLoader::insert(Call, |ctx, _| {
ctx.ext.call(Weight::zero(), BalanceOf::<Test>::zero(), BOB, 0, vec![1], true)
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
@@ -2862,6 +2916,7 @@ mod tests {
ctx.ext
.instantiate(
Weight::zero(),
BalanceOf::<Test>::zero(),
fail_code,
ctx.ext.minimum_balance() * 100,
vec![],
@@ -2875,6 +2930,7 @@ mod tests {
.ext
.instantiate(
Weight::zero(),
BalanceOf::<Test>::zero(),
success_code,
ctx.ext.minimum_balance() * 100,
vec![],
@@ -2883,7 +2939,9 @@ mod tests {
.unwrap();
// a plain call should not influence the account counter
ctx.ext.call(Weight::zero(), account_id, 0, vec![], false).unwrap();
ctx.ext
.call(Weight::zero(), BalanceOf::<Test>::zero(), account_id, 0, vec![], false)
.unwrap();
exec_success()
});
@@ -3387,7 +3445,14 @@ mod tests {
assert_eq!(ctx.ext.nonce(), 1);
// Should not change with a failed instantiation
assert_err!(
ctx.ext.instantiate(Weight::zero(), fail_code, 0, vec![], &[],),
ctx.ext.instantiate(
Weight::zero(),
BalanceOf::<Test>::zero(),
fail_code,
0,
vec![],
&[],
),
ExecError {
error: <Error<Test>>::ContractTrapped.into(),
origin: ErrorOrigin::Callee
@@ -3395,7 +3460,16 @@ mod tests {
);
assert_eq!(ctx.ext.nonce(), 1);
// Successful instantiation increments
ctx.ext.instantiate(Weight::zero(), success_code, 0, vec![], &[]).unwrap();
ctx.ext
.instantiate(
Weight::zero(),
BalanceOf::<Test>::zero(),
success_code,
0,
vec![],
&[],
)
.unwrap();
assert_eq!(ctx.ext.nonce(), 2);
exec_success()
});