Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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`
Comment thread
xermicus marked this conversation as resolved.
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,
Comment thread
xermicus marked this conversation as resolved.
};
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