Skip to content

Commit

Permalink
contracts: Add instantiation_nonce API (paritytech#12800)
Browse files Browse the repository at this point in the history
* 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 <>
  • Loading branch information
2 people authored and ltfschoen committed Feb 22, 2023
1 parent 37e27ac commit 79f4a49
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 19 deletions.
4 changes: 4 additions & 0 deletions frame/contracts/proc-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ struct HostFn {
enum HostFnReturn {
Unit,
U32,
U64,
ReturnCode,
}

Expand All @@ -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>
Expand Down Expand Up @@ -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()),
Expand Down Expand Up @@ -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)),
}?;
Expand Down
20 changes: 20 additions & 0 deletions frame/contracts/src/benchmarking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
71 changes: 60 additions & 11 deletions frame/contracts/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`].
Expand Down Expand Up @@ -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,
},
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
));
});
}
}
8 changes: 6 additions & 2 deletions frame/contracts/src/schedule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down Expand Up @@ -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,
}
}
Expand Down
41 changes: 35 additions & 6 deletions frame/contracts/src/wasm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>>(
Expand All @@ -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#"
Expand Down Expand Up @@ -2971,23 +2974,49 @@ 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"))
(func (export "deploy"))
)
"#;
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()));
}
}
13 changes: 13 additions & 0 deletions frame/contracts/src/wasm/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,8 @@ pub enum RuntimeCosts {
ReentrantCount,
/// Weight of calling `account_reentrance_count`
AccountEntranceCount,
/// Weight of calling `instantiation_nonce`
InstantationNonce,
}

impl RuntimeCosts {
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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())
}
}
31 changes: 31 additions & 0 deletions frame/contracts/src/weights.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit 79f4a49

Please sign in to comment.