mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 13:01:07 +00:00
contracts: Add instantiation_nonce API (#12800)
* Add `instantiation_nonce` API * Fixes for tests * Update frame/contracts/src/schedule.rs Co-authored-by: Sasha Gryaznov <hi@agryaznov.com> * ".git/.scripts/bench-bot.sh" pallet dev pallet_contracts Co-authored-by: Sasha Gryaznov <hi@agryaznov.com> Co-authored-by: command-bot <>
This commit is contained in:
committed by
GitHub
parent
c336eae64a
commit
770afb9ee3
@@ -163,6 +163,7 @@ struct HostFn {
|
||||
enum HostFnReturn {
|
||||
Unit,
|
||||
U32,
|
||||
U64,
|
||||
ReturnCode,
|
||||
}
|
||||
|
||||
@@ -171,6 +172,7 @@ impl HostFnReturn {
|
||||
let ok = match self {
|
||||
Self::Unit => quote! { () },
|
||||
Self::U32 | Self::ReturnCode => quote! { ::core::primitive::u32 },
|
||||
Self::U64 => quote! { ::core::primitive::u64 },
|
||||
};
|
||||
quote! {
|
||||
::core::result::Result<#ok, ::wasmi::core::Trap>
|
||||
@@ -241,6 +243,7 @@ impl HostFn {
|
||||
let msg = r#"Should return one of the following:
|
||||
- Result<(), TrapReason>,
|
||||
- Result<ReturnCode, TrapReason>,
|
||||
- Result<u64, TrapReason>,
|
||||
- Result<u32, TrapReason>"#;
|
||||
let ret_ty = match item.clone().sig.output {
|
||||
syn::ReturnType::Type(_, ty) => Ok(ty.clone()),
|
||||
@@ -303,6 +306,7 @@ impl HostFn {
|
||||
let returns = match ok_ty_str.as_str() {
|
||||
"()" => Ok(HostFnReturn::Unit),
|
||||
"u32" => Ok(HostFnReturn::U32),
|
||||
"u64" => Ok(HostFnReturn::U64),
|
||||
"ReturnCode" => Ok(HostFnReturn::ReturnCode),
|
||||
_ => Err(err(arg1.span(), &msg)),
|
||||
}?;
|
||||
|
||||
@@ -2138,6 +2138,26 @@ benchmarks! {
|
||||
let origin = RawOrigin::Signed(instance.caller.clone());
|
||||
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
|
||||
|
||||
seal_instantiation_nonce {
|
||||
let r in 0 .. API_BENCHMARK_BATCHES;
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "instantiation_nonce",
|
||||
params: vec![],
|
||||
return_type: Some(ValueType::I64),
|
||||
}],
|
||||
call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[
|
||||
Instruction::Call(0),
|
||||
Instruction::Drop,
|
||||
])),
|
||||
.. Default::default()
|
||||
});
|
||||
let instance = Contract::<T>::new(code, vec![])?;
|
||||
let origin = RawOrigin::Signed(instance.caller.clone());
|
||||
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
|
||||
|
||||
// We make the assumption that pushing a constant and dropping a value takes roughly
|
||||
// the same amount of time. We follow that `t.load` and `drop` both have the weight
|
||||
// of this benchmark / 2. We need to make this assumption because there is no way
|
||||
|
||||
@@ -305,6 +305,9 @@ pub trait Ext: sealing::Sealed {
|
||||
/// are not calculated as separate entrance.
|
||||
/// A value of 0 means it does not exist on the call stack.
|
||||
fn account_reentrance_count(&self, account_id: &AccountIdOf<Self::T>) -> u32;
|
||||
|
||||
/// Returns a nonce that is incremented for every instantiated contract.
|
||||
fn nonce(&mut self) -> u64;
|
||||
}
|
||||
|
||||
/// Describes the different functions that can be exported by an [`Executable`].
|
||||
@@ -654,7 +657,7 @@ where
|
||||
let (mut stack, executable) = Self::new(
|
||||
FrameArgs::Instantiate {
|
||||
sender: origin.clone(),
|
||||
nonce: Self::initial_nonce(),
|
||||
nonce: <Nonce<T>>::get().wrapping_add(1),
|
||||
executable,
|
||||
salt,
|
||||
},
|
||||
@@ -1068,19 +1071,10 @@ where
|
||||
|
||||
/// Increments and returns the next nonce. Pulls it from storage if it isn't in cache.
|
||||
fn next_nonce(&mut self) -> u64 {
|
||||
let next = if let Some(current) = self.nonce {
|
||||
current.wrapping_add(1)
|
||||
} else {
|
||||
Self::initial_nonce()
|
||||
};
|
||||
let next = self.nonce().wrapping_add(1);
|
||||
self.nonce = Some(next);
|
||||
next
|
||||
}
|
||||
|
||||
/// Pull the current nonce from storage.
|
||||
fn initial_nonce() -> u64 {
|
||||
<Nonce<T>>::get().wrapping_add(1)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, E> Ext for Stack<'a, T, E>
|
||||
@@ -1394,6 +1388,16 @@ where
|
||||
.filter(|f| f.delegate_caller.is_none() && &f.account_id == account_id)
|
||||
.count() as u32
|
||||
}
|
||||
|
||||
fn nonce(&mut self) -> u64 {
|
||||
if let Some(current) = self.nonce {
|
||||
current
|
||||
} else {
|
||||
let current = <Nonce<T>>::get();
|
||||
self.nonce = Some(current);
|
||||
current
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod sealing {
|
||||
@@ -3325,4 +3329,49 @@ mod tests {
|
||||
assert_matches!(result, Ok(_));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonce_api_works() {
|
||||
let fail_code = MockLoader::insert(Constructor, |_, _| exec_trapped());
|
||||
let success_code = MockLoader::insert(Constructor, |_, _| exec_success());
|
||||
let code_hash = MockLoader::insert(Call, move |ctx, _| {
|
||||
// It is set to one when this contract was instantiated by `place_contract`
|
||||
assert_eq!(ctx.ext.nonce(), 1);
|
||||
// Should not change without any instantation in-between
|
||||
assert_eq!(ctx.ext.nonce(), 1);
|
||||
// Should not change with a failed instantiation
|
||||
assert_err!(
|
||||
ctx.ext.instantiate(Weight::zero(), fail_code, 0, vec![], &[],),
|
||||
ExecError {
|
||||
error: <Error<Test>>::ContractTrapped.into(),
|
||||
origin: ErrorOrigin::Callee
|
||||
}
|
||||
);
|
||||
assert_eq!(ctx.ext.nonce(), 1);
|
||||
// Successful instantation increments
|
||||
ctx.ext.instantiate(Weight::zero(), success_code, 0, vec![], &[]).unwrap();
|
||||
assert_eq!(ctx.ext.nonce(), 2);
|
||||
exec_success()
|
||||
});
|
||||
|
||||
ExtBuilder::default().build().execute_with(|| {
|
||||
let min_balance = <Test as Config>::Currency::minimum_balance();
|
||||
let schedule = <Test as Config>::Schedule::get();
|
||||
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
|
||||
set_balance(&ALICE, min_balance * 1000);
|
||||
place_contract(&BOB, code_hash);
|
||||
let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap();
|
||||
assert_ok!(MockStack::run_call(
|
||||
ALICE,
|
||||
BOB,
|
||||
&mut gas_meter,
|
||||
&mut storage_meter,
|
||||
&schedule,
|
||||
0,
|
||||
vec![],
|
||||
None,
|
||||
Determinism::Deterministic
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,12 +430,15 @@ pub struct HostFnWeights<T: Config> {
|
||||
/// Weight of calling `seal_ecdsa_to_eth_address`.
|
||||
pub ecdsa_to_eth_address: u64,
|
||||
|
||||
/// Weight of calling `seal_reentrance_count`.
|
||||
/// Weight of calling `reentrance_count`.
|
||||
pub reentrance_count: u64,
|
||||
|
||||
/// Weight of calling `seal_account_reentrance_count`.
|
||||
/// Weight of calling `account_reentrance_count`.
|
||||
pub account_reentrance_count: u64,
|
||||
|
||||
/// Weight of calling `instantiation_nonce`.
|
||||
pub instantiation_nonce: u64,
|
||||
|
||||
/// The type parameter is used in the default implementation.
|
||||
#[codec(skip)]
|
||||
pub _phantom: PhantomData<T>,
|
||||
@@ -676,6 +679,7 @@ impl<T: Config> Default for HostFnWeights<T> {
|
||||
ecdsa_to_eth_address: cost_batched!(seal_ecdsa_to_eth_address),
|
||||
reentrance_count: cost_batched!(seal_reentrance_count),
|
||||
account_reentrance_count: cost_batched!(seal_account_reentrance_count),
|
||||
instantiation_nonce: cost_batched!(seal_instantiation_nonce),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -625,6 +625,9 @@ mod tests {
|
||||
fn account_reentrance_count(&self, _account_id: &AccountIdOf<Self::T>) -> u32 {
|
||||
12
|
||||
}
|
||||
fn nonce(&mut self) -> u64 {
|
||||
995
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_internal<E: BorrowMut<MockExt>>(
|
||||
@@ -649,16 +652,16 @@ mod tests {
|
||||
}
|
||||
|
||||
fn execute<E: BorrowMut<MockExt>>(wat: &str, input_data: Vec<u8>, ext: E) -> ExecResult {
|
||||
execute_internal(wat, input_data, ext, false)
|
||||
execute_internal(wat, input_data, ext, true)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "runtime-benchmarks"))]
|
||||
fn execute_with_unstable<E: BorrowMut<MockExt>>(
|
||||
fn execute_no_unstable<E: BorrowMut<MockExt>>(
|
||||
wat: &str,
|
||||
input_data: Vec<u8>,
|
||||
ext: E,
|
||||
) -> ExecResult {
|
||||
execute_internal(wat, input_data, ext, true)
|
||||
execute_internal(wat, input_data, ext, false)
|
||||
}
|
||||
|
||||
const CODE_TRANSFER: &str = r#"
|
||||
@@ -2971,13 +2974,39 @@ mod tests {
|
||||
execute(CODE, vec![], &mut mock_ext).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn instantiation_nonce_works() {
|
||||
const CODE: &str = r#"
|
||||
(module
|
||||
(import "seal0" "instantiation_nonce" (func $nonce (result i64)))
|
||||
(func $assert (param i32)
|
||||
(block $ok
|
||||
(br_if $ok
|
||||
(get_local 0)
|
||||
)
|
||||
(unreachable)
|
||||
)
|
||||
)
|
||||
(func (export "call")
|
||||
(call $assert
|
||||
(i64.eq (call $nonce) (i64.const 995))
|
||||
)
|
||||
)
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#;
|
||||
|
||||
let mut mock_ext = MockExt::default();
|
||||
execute(CODE, vec![], &mut mock_ext).unwrap();
|
||||
}
|
||||
|
||||
/// This test check that an unstable interface cannot be deployed. In case of runtime
|
||||
/// benchmarks we always allow unstable interfaces. This is why this test does not
|
||||
/// work when this feature is enabled.
|
||||
#[cfg(not(feature = "runtime-benchmarks"))]
|
||||
#[test]
|
||||
fn cannot_deploy_unstable() {
|
||||
const CANNT_DEPLOY_UNSTABLE: &str = r#"
|
||||
const CANNOT_DEPLOY_UNSTABLE: &str = r#"
|
||||
(module
|
||||
(import "seal0" "reentrance_count" (func $reentrance_count (result i32)))
|
||||
(func (export "call"))
|
||||
@@ -2985,9 +3014,9 @@ mod tests {
|
||||
)
|
||||
"#;
|
||||
assert_err!(
|
||||
execute(CANNT_DEPLOY_UNSTABLE, vec![], MockExt::default()),
|
||||
execute_no_unstable(CANNOT_DEPLOY_UNSTABLE, vec![], MockExt::default()),
|
||||
<Error<Test>>::CodeRejected,
|
||||
);
|
||||
assert_ok!(execute_with_unstable(CANNT_DEPLOY_UNSTABLE, vec![], MockExt::default()));
|
||||
assert_ok!(execute(CANNOT_DEPLOY_UNSTABLE, vec![], MockExt::default()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,6 +265,8 @@ pub enum RuntimeCosts {
|
||||
ReentrantCount,
|
||||
/// Weight of calling `account_reentrance_count`
|
||||
AccountEntranceCount,
|
||||
/// Weight of calling `instantiation_nonce`
|
||||
InstantationNonce,
|
||||
}
|
||||
|
||||
impl RuntimeCosts {
|
||||
@@ -344,6 +346,7 @@ impl RuntimeCosts {
|
||||
EcdsaToEthAddress => s.ecdsa_to_eth_address,
|
||||
ReentrantCount => s.reentrance_count,
|
||||
AccountEntranceCount => s.account_reentrance_count,
|
||||
InstantationNonce => s.instantiation_nonce,
|
||||
};
|
||||
RuntimeToken {
|
||||
#[cfg(test)]
|
||||
@@ -2614,4 +2617,14 @@ pub mod env {
|
||||
ctx.read_sandbox_memory_as(memory, account_ptr)?;
|
||||
Ok(ctx.ext.account_reentrance_count(&account_id))
|
||||
}
|
||||
|
||||
/// Returns a nonce that is unique per contract instantiation.
|
||||
///
|
||||
/// The nonce is incremented for each succesful contract instantiation. This is a
|
||||
/// sensible default salt for contract instantiations.
|
||||
#[unstable]
|
||||
fn instantiation_nonce(ctx: _, _memory: _) -> Result<u64, TrapReason> {
|
||||
ctx.charge_gas(RuntimeCosts::InstantationNonce)?;
|
||||
Ok(ctx.ext.nonce())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,7 @@ pub trait WeightInfo {
|
||||
fn seal_set_code_hash(r: u32, ) -> Weight;
|
||||
fn seal_reentrance_count(r: u32, ) -> Weight;
|
||||
fn seal_account_reentrance_count(r: u32, ) -> Weight;
|
||||
fn seal_instantiation_nonce(r: u32, ) -> Weight;
|
||||
fn instr_i64const(r: u32, ) -> Weight;
|
||||
fn instr_i64load(r: u32, ) -> Weight;
|
||||
fn instr_i64store(r: u32, ) -> Weight;
|
||||
@@ -1054,6 +1055,21 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
.saturating_add(T::DbWeight::get().reads(6))
|
||||
.saturating_add(T::DbWeight::get().writes(3))
|
||||
}
|
||||
// Storage: System Account (r:1 w:0)
|
||||
// Storage: Contracts ContractInfoOf (r:1 w:1)
|
||||
// Storage: Contracts CodeStorage (r:1 w:0)
|
||||
// Storage: Timestamp Now (r:1 w:0)
|
||||
// Storage: System EventTopics (r:2 w:2)
|
||||
// Storage: Contracts Nonce (r:1 w:1)
|
||||
/// The range of component `r` is `[0, 20]`.
|
||||
fn seal_instantiation_nonce(r: u32, ) -> Weight {
|
||||
// Minimum execution time: 293_987 nanoseconds.
|
||||
Weight::from_ref_time(307_154_849)
|
||||
// Standard Error: 27_486
|
||||
.saturating_add(Weight::from_ref_time(8_759_333).saturating_mul(r.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(7))
|
||||
.saturating_add(T::DbWeight::get().writes(4))
|
||||
}
|
||||
/// The range of component `r` is `[0, 50]`.
|
||||
fn instr_i64const(r: u32, ) -> Weight {
|
||||
// Minimum execution time: 688 nanoseconds.
|
||||
@@ -2307,6 +2323,21 @@ impl WeightInfo for () {
|
||||
.saturating_add(RocksDbWeight::get().reads(6))
|
||||
.saturating_add(RocksDbWeight::get().writes(3))
|
||||
}
|
||||
// Storage: System Account (r:1 w:0)
|
||||
// Storage: Contracts ContractInfoOf (r:1 w:1)
|
||||
// Storage: Contracts CodeStorage (r:1 w:0)
|
||||
// Storage: Timestamp Now (r:1 w:0)
|
||||
// Storage: System EventTopics (r:2 w:2)
|
||||
// Storage: Contracts Nonce (r:1 w:1)
|
||||
/// The range of component `r` is `[0, 20]`.
|
||||
fn seal_instantiation_nonce(r: u32, ) -> Weight {
|
||||
// Minimum execution time: 293_987 nanoseconds.
|
||||
Weight::from_ref_time(307_154_849)
|
||||
// Standard Error: 27_486
|
||||
.saturating_add(Weight::from_ref_time(8_759_333).saturating_mul(r.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(7))
|
||||
.saturating_add(RocksDbWeight::get().writes(4))
|
||||
}
|
||||
/// The range of component `r` is `[0, 50]`.
|
||||
fn instr_i64const(r: u32, ) -> Weight {
|
||||
// Minimum execution time: 688 nanoseconds.
|
||||
|
||||
Reference in New Issue
Block a user