contracts: switch to wasmi gas metering (#14084)

* upgrade to wasmi 0.29

* prepare cleanup

* sync ref_time w engine from the stack frame

* proc_macro: sync gas in host funcs

save: compiles, only gas pushing left to macro

WIP proc macro

proc macro: done

* clean benchmarks & schedule: w_base = w_i64const

* scale gas values btw engine and gas meter

* (re)instrumentation & code_cache removed

* remove gas() host fn, continue clean-up

save

* address review comments

* move from CodeStorage&PrefabWasmModule to PristineCode&WasmBlob

* refactor: no reftime_limit&schedule passes, no CodeStorage

* bugs fixing

* fix tests: expected deposit amount

* fix prepare::tests

* update tests and fix bugs

tests::run_out_of_gas_engine, need 2 more

save: 2 bugs with gas syncs: 1 of 2 tests done

gas_syncs_no_overcharge bug fixed, test passes!

cleaned out debug prints

second bug is not a bug

disabled_chain_extension test fix (err msg)

tests run_out_of_fuel_host, chain_extension pass

all tests pass

* update docs

* bump wasmi 0.30.0

* benchmarks updated, tests pass

* refactoring

* s/OwnerInfo/CodeInfo/g;

* migration: draft, compiles

* migration: draft, runs

* migration: draft, runs (fixing)

* deposits repaid non pro rata

* deposits repaid pro rata

* better try-runtime output

* even better try-runtime output

* benchmark migration

* fix merge leftover

* add forgotten fixtures, fix docs

* address review comments

* ci fixes

* cleanup

* benchmarks::prepare to return DispatchError

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

* store memory limits to CodeInfo

* ci: roll back weights

* ".git/.scripts/commands/bench-vm/bench-vm.sh" pallet dev pallet_contracts

* drive-by: update Readme and pallet rustdoc

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

* use wasmi 0.29

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

* use wasmi 0.30 again

* query memory limits from wasmi

* better migration types

* ci: pull weights from master

* refactoring

* ".git/.scripts/commands/bench-vm/bench-vm.sh" pallet dev pallet_contracts

* addressing review comments

* refactor

* address review comments

* optimize migration

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

* another review round comments addressed

* ci fix one

* clippy fix

* ci fix two

---------

Co-authored-by: command-bot <>
This commit is contained in:
Sasha Gryaznov
2023-07-03 14:04:10 +03:00
committed by GitHub
parent e42768ea34
commit fda86dd501
23 changed files with 2786 additions and 4588 deletions
+10 -2
View File
@@ -3691,6 +3691,12 @@ dependencies = [
"webrtc-util",
]
[[package]]
name = "intx"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f38a50a899dc47a6d0ed5508e7f601a2e34c3a85303514b5d137f3c10a0c75"
[[package]]
name = "io-lifetimes"
version = "1.0.11"
@@ -13139,10 +13145,12 @@ dependencies = [
[[package]]
name = "wasmi"
version = "0.28.0"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e61a7006b0fdf24f6bbe8dcfdad5ca1b350de80061fb2827f31c82fbbb9565a"
checksum = "e51fb5c61993e71158abf5bb863df2674ca3ec39ed6471c64f07aeaf751d67b4"
dependencies = [
"intx",
"smallvec",
"spin 0.9.8",
"wasmi_arena",
"wasmi_core",
+1 -1
View File
@@ -26,7 +26,7 @@ serde = { version = "1", optional = true, features = ["derive"] }
smallvec = { version = "1", default-features = false, features = [
"const_generics",
] }
wasmi = { version = "0.28", default-features = false }
wasmi = { version = "0.30", default-features = false }
wasmparser = { package = "wasmparser-nostd", version = "0.100", default-features = false }
impl-trait-for-tuples = "0.2"
+23 -29
View File
@@ -13,8 +13,8 @@ This module extends accounts based on the `Currency` trait to have smart-contrac
be used with other modules that implement accounts based on `Currency`. These "smart-contract accounts"
have the ability to instantiate smart-contracts and make calls to other contract and non-contract accounts.
The smart-contract code is stored once in a `code_cache`, and later retrievable via its `code_hash`.
This means that multiple smart-contracts can be instantiated from the same `code_cache`, without replicating
The smart-contract code is stored once, and later retrievable via its `code_hash`.
This means that multiple smart-contracts can be instantiated from the same `code`, without replicating
the code each time.
When a smart-contract is called, its associated code is retrieved via the code hash and gets executed.
@@ -24,18 +24,17 @@ or call other smart-contracts.
Finally, when an account is reaped, its associated code and storage of the smart-contract account
will also be deleted.
### Gas
### Weight
Senders must specify a gas limit with every call, as all instructions invoked by the smart-contract require gas.
Unused gas is refunded after the call, regardless of the execution outcome.
Senders must specify a [`Weight`](https://paritytech.github.io/substrate/master/sp_weights/struct.Weight.html) limit with every call, as all instructions invoked by the smart-contract require weight.
Unused weight is refunded after the call, regardless of the execution outcome.
If the gas limit is reached, then all calls and state changes (including balance transfers) are only
reverted at the current call's contract level. For example, if contract A calls B and B runs out of gas mid-call,
If the weight limit is reached, then all calls and state changes (including balance transfers) are only
reverted at the current call's contract level. For example, if contract A calls B and B runs out of weight mid-call,
then all of B's calls are reverted. Assuming correct error handling by contract A, A's other calls and state
changes still persist.
One gas is equivalent to one [weight](https://docs.substrate.io/v3/runtime/weights-and-fees)
which is defined as one picosecond of execution time on the runtime's reference machine.
One `ref_time` `Weight` is defined as one picosecond of execution time on the runtime's reference machine.
### Revert Behaviour
@@ -43,29 +42,26 @@ Contract call failures are not cascading. When failures occur in a sub-call, the
and the call will only revert at the specific contract level. For example, if contract A calls contract B, and B
fails, A can decide how to handle that failure, either proceeding or reverting A's changes.
### Offchain Execution
### Off-chain Execution
In general, a contract execution needs to be deterministic so that all nodes come to the same
conclusion when executing it. To that end we disallow any instructions that could cause
indeterminism. Most notable are any floating point arithmetic. That said, sometimes contracts
are executed off-chain and hence are not subject to consensus. If code is only executed by a
single node and implicitly trusted by other actors is such a case. Trusted execution environments
come to mind. To that end we allow the execution of indeterminstic code for offchain usages
come to mind. To that end we allow the execution of indeterminstic code for off-chain usages
with the following constraints:
1. No contract can ever be instantiated from an indeterministic code. The only way to execute
the code is to use a delegate call from a deterministic contract.
2. The code that wants to use this feature needs to depend on `pallet-contracts` and use `bare_call`
2. The code that wants to use this feature needs to depend on `pallet-contracts` and use [`bare_call()`](https://paritytech.github.io/substrate/master/pallet_contracts/pallet/struct.Pallet.html#method.bare_call)
directly. This makes sure that by default `pallet-contracts` does not expose any indeterminism.
## How to use
When setting up the `Schedule` for your runtime make sure to set `InstructionWeights::fallback`
to a non zero value. The default is `0` and prevents the upload of any non deterministic code.
#### How to use
An indeterministic code can be deployed on-chain by passing `Determinism::Relaxed`
to `upload_code`. A deterministic contract can then delegate call into it if and only if it
is ran by using `bare_call` and passing `Determinism::Relaxed` to it. **Never use
to [`upload_code()`](https://paritytech.github.io/substrate/master/pallet_contracts/pallet/struct.Pallet.html#method.upload_code). A deterministic contract can then delegate call into it if and only if it
is ran by using [`bare_call()`](https://paritytech.github.io/substrate/master/pallet_contracts/pallet/struct.Pallet.html#method.bare_call) and passing [`Determinism::Relaxed`](https://paritytech.github.io/substrate/master/pallet_contracts/enum.Determinism.html#variant.Relaxed) to it. **Never use
this argument when the contract is called from an on-chain transaction.**
## Interface
@@ -99,24 +95,22 @@ Each contract is one WebAssembly module that looks like this:
```
The documentation of all importable functions can be found
[here](https://github.com/paritytech/substrate/blob/master/frame/contracts/src/wasm/runtime.rs).
Look for the `define_env!` macro invocation.
[here](https://paritytech.github.io/substrate/master/pallet_contracts/api_doc/trait.Current.html).
## Usage
This module executes WebAssembly smart contracts. These can potentially be written in any language
that compiles to web assembly. However, using a language that specifically targets this module
will make things a lot easier. One such language is [`ink!`](https://use.ink)
which is an [`eDSL`](https://wiki.haskell.org/Embedded_domain_specific_language) that enables
writing WebAssembly based smart contracts in the Rust programming language.
that compiles to Wasm. However, using a language that specifically targets this module
will make things a lot easier. One such language is [`ink!`](https://use.ink). It enables
writing WebAssembly-based smart-contracts in the Rust programming language.
## Debugging
Contracts can emit messages to the client when called as RPC through the `seal_debug_message`
Contracts can emit messages to the client when called as RPC through the [`debug_message`](https://paritytech.github.io/substrate/master/pallet_contracts/api_doc/trait.Current.html#tymethod.debug_message)
API. This is exposed in [ink!](https://use.ink) via
[`ink_env::debug_message()`](https://paritytech.github.io/ink/ink_env/fn.debug_message.html).
Those messages are gathered into an internal buffer and send to the RPC client.
Those messages are gathered into an internal buffer and sent to the RPC client.
It is up the the individual client if and how those messages are presented to the user.
This buffer is also printed as a debug message. In order to see these messages on the node
@@ -154,11 +148,11 @@ this pallet contains the concept of an unstable interface. Akin to the rust nigh
it allows us to add new interfaces but mark them as unstable so that contract languages can
experiment with them and give feedback before we stabilize those.
In order to access interfaces marked as `#[unstable]` in `runtime.rs` one need to set
`pallet_contracts::Config::UnsafeUnstableInterface` to `ConstU32<true>`. It should be obvious
In order to access interfaces marked as `#[unstable]` in [`runtime.rs`](src/wasm/runtime.rs) one need to set
`pallet_contracts::Config::UnsafeUnstableInterface` to `ConstU32<true>`. **It should be obvious
that any production runtime should never be compiled with this feature: In addition to be
subject to change or removal those interfaces might not have proper weights associated with
them and are therefore considered unsafe.
them and are therefore considered unsafe**.
New interfaces are generally added as unstable and might go through several iterations
before they are promoted to a stable interface.
@@ -0,0 +1,14 @@
;; Everything prepared for the host function call, but no call is performed.
(module
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
(import "env" "memory" (memory 1 1))
;; [0, 8) buffer to write input
;; [8, 12) size of the input buffer
(data (i32.const 8) "\04")
(func (export "call"))
(func (export "deploy"))
)
@@ -0,0 +1,22 @@
;; Stores a value of the passed size. The host function is called once.
(module
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
(import "env" "memory" (memory 1 1))
;; [0, 8) buffer to write input
;; [8, 12) size of the input buffer
(data (i32.const 8) "\04")
(func (export "call")
;; instructions to consume engine fuel
(drop
(i32.const 42)
)
(call $seal_input (i32.const 0) (i32.const 8))
)
(func (export "deploy"))
)
@@ -0,0 +1,28 @@
;; Stores a value of the passed size. The host function is called twice.
(module
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
(import "env" "memory" (memory 1 1))
;; [0, 8) buffer to write input
;; [8, 12) size of the input buffer
(data (i32.const 8) "\04")
(func (export "call")
;; instructions to consume engine fuel
(drop
(i32.const 42)
)
(call $seal_input (i32.const 0) (i32.const 8))
;; instructions to consume engine fuel
(drop
(i32.const 42)
)
(call $seal_input (i32.const 0) (i32.const 8))
)
(func (export "deploy"))
)
+44 -10
View File
@@ -624,19 +624,15 @@ fn expand_functions(def: &EnvDef, expand_blocks: bool, host_state: TokenStream2)
let trace_fmt_str = format!("{}::{}({}) = {{:?}}\n", module, name, params_fmt_str);
quote! {
let result = #body;
if ::log::log_enabled!(target: "runtime::contracts::strace", ::log::Level::Trace) {
let result = #body;
{
use sp_std::fmt::Write;
let mut w = sp_std::Writer::default();
let _ = core::write!(&mut w, #trace_fmt_str, #( #trace_fmt_args, )* result);
let msg = core::str::from_utf8(&w.inner()).unwrap_or_default();
ctx.ext().append_debug_buffer(msg);
}
result
} else {
#body
}
result
}
};
@@ -661,7 +657,7 @@ fn expand_functions(def: &EnvDef, expand_blocks: bool, host_state: TokenStream2)
::core::unreachable!()
} }
};
let map_err = if expand_blocks {
let into_host = if expand_blocks {
quote! {
|reason| {
::wasmi::core::Trap::from(reason)
@@ -677,6 +673,43 @@ fn expand_functions(def: &EnvDef, expand_blocks: bool, host_state: TokenStream2)
} else {
quote! { #[allow(unused_variables)] }
};
let sync_gas_before = if expand_blocks {
quote! {
// Gas left in the gas meter right before switching to engine execution.
let __gas_before__ = {
let engine_consumed_total =
__caller__.fuel_consumed().expect("Fuel metering is enabled; qed");
let gas_meter = __caller__.data_mut().ext().gas_meter_mut();
gas_meter
.charge_fuel(engine_consumed_total)
.map_err(TrapReason::from)
.map_err(#into_host)?
.ref_time()
};
}
} else {
quote! { }
};
// Gas left in the gas meter right after returning from engine execution.
let sync_gas_after = if expand_blocks {
quote! {
let mut gas_after = __caller__.data_mut().ext().gas_meter().gas_left().ref_time();
let mut host_consumed = __gas_before__.saturating_sub(gas_after);
// Possible undercharge of at max 1 fuel here, if host consumed less than `instruction_weights.base`
// Not a problem though, as soon as host accounts its spent gas properly.
let fuel_consumed = host_consumed
.checked_div(__caller__.data_mut().ext().schedule().instruction_weights.base as u64)
.ok_or(Error::<E::T>::InvalidSchedule)
.map_err(TrapReason::from)
.map_err(#into_host)?;
__caller__
.consume_fuel(fuel_consumed)
.map_err(|_| TrapReason::from(Error::<E::T>::OutOfGas))
.map_err(#into_host)?;
}
} else {
quote! { }
};
quote! {
// We need to allow all interfaces when runtime benchmarks are performed because
@@ -688,10 +721,11 @@ fn expand_functions(def: &EnvDef, expand_blocks: bool, host_state: TokenStream2)
{
#allow_unused
linker.define(#module, #name, ::wasmi::Func::wrap(&mut*store, |mut __caller__: ::wasmi::Caller<#host_state>, #( #params, )*| -> #wasm_output {
#sync_gas_before
let mut func = #inner;
func()
.map_err(#map_err)
.map(::core::convert::Into::into)
let result = func().map_err(#into_host).map(::core::convert::Into::into);
#sync_gas_after
result
}))?;
}
}
@@ -24,18 +24,15 @@
//! we define this simple definition of a contract that can be passed to `create_code` that
//! compiles it down into a `WasmModule` that can be used as a contract's code.
use crate::{Config, Determinism};
use crate::Config;
use frame_support::traits::Get;
use sp_runtime::traits::Hash;
use sp_std::{borrow::ToOwned, prelude::*};
use wasm_instrument::{
gas_metering,
parity_wasm::{
builder,
elements::{
self, BlockType, CustomSection, External, FuncBody, Instruction, Instructions, Module,
Section, ValueType,
},
use wasm_instrument::parity_wasm::{
builder,
elements::{
self, BlockType, CustomSection, External, FuncBody, Instruction, Instructions, Module,
Section, ValueType,
},
};
@@ -240,15 +237,9 @@ impl<T: Config> From<ModuleDefinition> for WasmModule<T> {
}
impl<T: Config> WasmModule<T> {
/// Uses the supplied wasm module and instruments it when requested.
pub fn instrumented(code: &[u8], inject_gas: bool) -> Self {
let module = {
let mut module = Module::from_bytes(code).unwrap();
if inject_gas {
module = inject_gas_metering::<T>(module);
}
module
};
/// Uses the supplied wasm module.
pub fn from_code(code: &[u8]) -> Self {
let module = Module::from_bytes(code).unwrap();
let limits = *module
.import_section()
.unwrap()
@@ -366,37 +357,13 @@ impl<T: Config> WasmModule<T> {
}
.into()
}
pub fn unary_instr(instr: Instruction, repeat: u32) -> Self {
use body::DynInstr::{RandomI64Repeated, Regular};
ModuleDefinition {
call_body: Some(body::repeated_dyn(
repeat,
vec![RandomI64Repeated(1), Regular(instr), Regular(Instruction::Drop)],
)),
..Default::default()
}
.into()
}
pub fn binary_instr(instr: Instruction, repeat: u32) -> Self {
use body::DynInstr::{RandomI64Repeated, Regular};
ModuleDefinition {
call_body: Some(body::repeated_dyn(
repeat,
vec![RandomI64Repeated(2), Regular(instr), Regular(Instruction::Drop)],
)),
..Default::default()
}
.into()
}
}
/// Mechanisms to generate a function body that can be used inside a `ModuleDefinition`.
pub mod body {
use super::*;
/// When generating contract code by repeating a wasm sequence, it's sometimes necessary
/// When generating contract code by repeating a Wasm sequence, it's sometimes necessary
/// to change those instructions on each repetition. The variants of this enum describe
/// various ways in which this can happen.
pub enum DynInstr {
@@ -405,31 +372,8 @@ pub mod body {
/// Insert a I32Const with incrementing value for each insertion.
/// (start_at, increment_by)
Counter(u32, u32),
/// Insert a I32Const with a random value in [low, high) not divisible by two.
/// (low, high)
RandomUnaligned(u32, u32),
/// Insert a I32Const with a random value in [low, high).
/// (low, high)
RandomI32(i32, i32),
/// Insert the specified amount of I32Const with a random value.
RandomI32Repeated(usize),
/// Insert the specified amount of I64Const with a random value.
RandomI64Repeated(usize),
/// Insert a GetLocal with a random offset in [low, high).
/// (low, high)
RandomGetLocal(u32, u32),
/// Insert a SetLocal with a random offset in [low, high).
/// (low, high)
RandomSetLocal(u32, u32),
/// Insert a TeeLocal with a random offset in [low, high).
/// (low, high)
RandomTeeLocal(u32, u32),
/// Insert a GetGlobal with a random offset in [low, high).
/// (low, high)
RandomGetGlobal(u32, u32),
/// Insert a SetGlobal with a random offset in [low, high).
/// (low, high)
RandomSetGlobal(u32, u32),
}
pub fn plain(instructions: Vec<Instruction>) -> FuncBody {
@@ -466,53 +410,16 @@ pub mod body {
*offset += *increment_by;
vec![Instruction::I32Const(current as i32)]
},
DynInstr::RandomUnaligned(low, high) => {
let unaligned = rng.gen_range(*low..*high) | 1;
vec![Instruction::I32Const(unaligned as i32)]
},
DynInstr::RandomI32(low, high) => {
vec![Instruction::I32Const(rng.gen_range(*low..*high))]
},
DynInstr::RandomI32Repeated(num) =>
(&mut rng).sample_iter(Standard).take(*num).map(Instruction::I32Const).collect(),
DynInstr::RandomI64Repeated(num) =>
(&mut rng).sample_iter(Standard).take(*num).map(Instruction::I64Const).collect(),
DynInstr::RandomGetLocal(low, high) => {
vec![Instruction::GetLocal(rng.gen_range(*low..*high))]
},
DynInstr::RandomSetLocal(low, high) => {
vec![Instruction::SetLocal(rng.gen_range(*low..*high))]
},
DynInstr::RandomTeeLocal(low, high) => {
vec![Instruction::TeeLocal(rng.gen_range(*low..*high))]
},
DynInstr::RandomGetGlobal(low, high) => {
vec![Instruction::GetGlobal(rng.gen_range(*low..*high))]
},
DynInstr::RandomSetGlobal(low, high) => {
vec![Instruction::SetGlobal(rng.gen_range(*low..*high))]
},
})
.chain(sp_std::iter::once(Instruction::End))
.collect();
FuncBody::new(Vec::new(), Instructions::new(body))
}
/// Replace the locals of the supplied `body` with `num` i64 locals.
pub fn inject_locals(body: &mut FuncBody, num: u32) {
use self::elements::Local;
*body.locals_mut() = vec![Local::new(num, ValueType::I64)];
}
}
/// The maximum amount of pages any contract is allowed to have according to the current `Schedule`.
pub fn max_pages<T: Config>() -> u32 {
T::Schedule::get().limits.memory_pages
}
fn inject_gas_metering<T: Config>(module: Module) -> Module {
let schedule = T::Schedule::get();
let gas_rules = schedule.rules(Determinism::Enforced);
let backend = gas_metering::host_function::Injector::new("seal0", "gas");
gas_metering::inject(module, backend, &gas_rules).unwrap()
}
+28 -843
View File
@@ -30,7 +30,7 @@ use self::{
};
use crate::{
exec::{AccountIdOf, Key},
migration::{v10, v11, v9, MigrationStep},
migration::{v10, v11, v12, v9, MigrationStep},
wasm::CallFlags,
Pallet as Contracts, *,
};
@@ -38,12 +38,9 @@ use codec::{Encode, MaxEncodedLen};
use frame_benchmarking::v1::{account, benchmarks, whitelisted_caller};
use frame_support::{pallet_prelude::StorageVersion, weights::Weight};
use frame_system::RawOrigin;
use sp_runtime::{
traits::{Bounded, Hash},
Perbill,
};
use sp_runtime::traits::{Bounded, Hash};
use sp_std::prelude::*;
use wasm_instrument::parity_wasm::elements::{BlockType, BrTableData, Instruction, ValueType};
use wasm_instrument::parity_wasm::elements::{BlockType, Instruction, ValueType};
/// How many runs we do per API benchmark.
///
@@ -162,16 +159,12 @@ where
/// Returns `true` iff all storage entries related to code storage exist.
fn code_exists(hash: &CodeHash<T>) -> bool {
<PristineCode<T>>::contains_key(hash) &&
<CodeStorage<T>>::contains_key(&hash) &&
<OwnerInfoOf<T>>::contains_key(&hash)
<PristineCode<T>>::contains_key(hash) && <CodeInfoOf<T>>::contains_key(&hash)
}
/// Returns `true` iff no storage entry related to code storage exist.
fn code_removed(hash: &CodeHash<T>) -> bool {
!<PristineCode<T>>::contains_key(hash) &&
!<CodeStorage<T>>::contains_key(&hash) &&
!<OwnerInfoOf<T>>::contains_key(&hash)
!<PristineCode<T>>::contains_key(hash) && !<CodeInfoOf<T>>::contains_key(&hash)
}
}
@@ -219,22 +212,7 @@ benchmarks! {
ContractInfo::<T>::process_deletion_queue_batch(Weight::MAX)
}
// This benchmarks the additional weight that is charged when a contract is executed the
// first time after a new schedule was deployed: For every new schedule a contract needs
// to re-run the instrumentation once.
#[pov_mode = Measured]
reinstrument {
let c in 0 .. Perbill::from_percent(49).mul_ceil(T::MaxCodeLen::get());
let WasmModule { code, hash, .. } = WasmModule::<T>::sized(c, Location::Call);
Contracts::<T>::store_code_raw(code, whitelisted_caller())?;
let schedule = T::Schedule::get();
let mut gas_meter = GasMeter::new(Weight::MAX);
let mut module = PrefabWasmModule::from_storage(hash, &schedule, &mut gas_meter)?;
}: {
Contracts::<T>::reinstrument_module(&mut module, &schedule)?;
}
// This benchmarks the v9 migration step. (update codeStorage)
// This benchmarks the v9 migration step (update codeStorage).
#[pov_mode = Measured]
v9_migration_step {
let c in 0 .. T::MaxCodeLen::get();
@@ -244,7 +222,7 @@ benchmarks! {
m.step();
}
// This benchmarks the v10 migration step. (use dedicated deposit_account)
// This benchmarks the v10 migration step (use dedicated deposit_account).
#[pov_mode = Measured]
v10_migration_step {
let contract = <Contract<T>>::with_caller(
@@ -257,7 +235,7 @@ benchmarks! {
m.step();
}
// This benchmarks the v11 migration step.
// This benchmarks the v11 migration step (Don't rely on reserved balances keeping an account alive).
#[pov_mode = Measured]
v11_migration_step {
let k in 0 .. 1024;
@@ -267,6 +245,18 @@ benchmarks! {
m.step();
}
// This benchmarks the v12 migration step (Move `OwnerInfo` to `CodeInfo`,
// add `determinism` field to the latter, clear `CodeStorage`
// and repay deposits).
#[pov_mode = Measured]
v12_migration_step {
let c in 0 .. T::MaxCodeLen::get();
v12::store_old_dummy_code::<T>(c as usize, account::<T::AccountId>("account", 0, 0));
let mut m = v12::Migration::<T>::default();
}: {
m.step();
}
// This benchmarks the weight of executing Migration::migrate to execute a noop migration.
#[pov_mode = Measured]
migration_noop {
@@ -348,14 +338,9 @@ benchmarks! {
// `c`: Size of the code in bytes.
// `i`: Size of the input in bytes.
// `s`: Size of the salt in bytes.
//
// # Note
//
// We cannot let `c` grow to the maximum code size because the code is not allowed
// to be larger than the maximum size **after instrumentation**.
#[pov_mode = Measured]
instantiate_with_code {
let c in 0 .. Perbill::from_percent(49).mul_ceil(T::MaxCodeLen::get());
let c in 0 .. T::MaxCodeLen::get();
let i in 0 .. code::max_pages::<T>() * 64 * 1024;
let s in 0 .. code::max_pages::<T>() * 64 * 1024;
let input = vec![42u8; i as usize];
@@ -445,14 +430,9 @@ benchmarks! {
// This constructs a contract that is maximal expensive to instrument.
// It creates a maximum number of metering blocks per byte.
// `c`: Size of the code in bytes.
//
// # Note
//
// We cannot let `c` grow to the maximum code size because the code is not allowed
// to be larger than the maximum size **after instrumentation**.
#[pov_mode = Measured]
upload_code {
let c in 0 .. Perbill::from_percent(49).mul_ceil(T::MaxCodeLen::get());
let c in 0 .. T::MaxCodeLen::get();
let caller = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, caller_funding::<T>());
let WasmModule { code, hash, .. } = WasmModule::<T>::sized(c, Location::Call);
@@ -466,7 +446,7 @@ benchmarks! {
// Removing code does not depend on the size of the contract because all the information
// needed to verify the removal claim (refcount, owner) is stored in a separate storage
// item (`OwnerInfoOf`).
// item (`CodeInfoOf`).
#[pov_mode = Measured]
remove_code {
let caller = whitelisted_caller();
@@ -736,27 +716,6 @@ benchmarks! {
let origin = RawOrigin::Signed(instance.caller.clone());
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
#[pov_mode = Measured]
seal_gas {
let r in 0 .. API_BENCHMARK_RUNS;
let code = WasmModule::<T>::from(ModuleDefinition {
imported_functions: vec![ImportedFunction {
module: "seal0",
name: "gas",
params: vec![ValueType::I64],
return_type: None,
}],
call_body: Some(body::repeated(r, &[
Instruction::I64Const(42),
Instruction::Call(0),
])),
.. 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![])
#[pov_mode = Measured]
seal_input {
let r in 0 .. API_BENCHMARK_RUNS;
@@ -1051,7 +1010,7 @@ benchmarks! {
// or maximum allowed debug buffer size, whichever is less.
let i in 0 .. (T::Schedule::get().limits.memory_pages * 64 * 1024).min(T::MaxDebugBufferLen::get());
// We benchmark versus messages containing printable ASCII codes.
// About 1Kb goes to the instrumented contract code instructions,
// About 1Kb goes to the contract code instructions,
// whereas all the space left we use for the initialization of the debug messages data.
let message = (0 .. T::MaxCodeLen::get() - 1024).zip((32..127).cycle()).map(|i| i.1).collect::<Vec<_>>();
let code = WasmModule::<T>::from(ModuleDefinition {
@@ -2445,15 +2404,10 @@ benchmarks! {
}: 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
// to measure them on their own using a valid wasm module. We need their individual
// values to derive the weight of individual instructions (by subtraction) from
// benchmarks that include those for parameter pushing and return type dropping.
// We call the weight of `t.load` and `drop`: `w_param`.
// the same amount of time. We call this weight `w_base`.
// The weight that would result from the respective benchmark we call: `w_bench`.
//
// w_i{32,64}const = w_drop = w_bench / 2
// w_base = w_i{32,64}const = w_drop = w_bench / 2
#[pov_mode = Ignored]
instr_i64const {
let r in 0 .. INSTR_BENCHMARK_RUNS;
@@ -2468,765 +2422,6 @@ benchmarks! {
sbox.invoke();
}
// w_i{32,64}load = w_bench - 2 * w_param
#[pov_mode = Ignored]
instr_i64load {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
call_body: Some(body::repeated_dyn(r, vec![
RandomUnaligned(0, code::max_pages::<T>() * 64 * 1024 - 8),
Regular(Instruction::I64Load(3, 0)),
Regular(Instruction::Drop),
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_i{32,64}store{...} = w_bench - 2 * w_param
#[pov_mode = Ignored]
instr_i64store {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
call_body: Some(body::repeated_dyn(r, vec![
RandomUnaligned(0, code::max_pages::<T>() * 64 * 1024 - 8),
RandomI64Repeated(1),
Regular(Instruction::I64Store(3, 0)),
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_select = w_bench - 4 * w_param
#[pov_mode = Ignored]
instr_select {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(body::repeated_dyn(r, vec![
RandomI64Repeated(1),
RandomI64Repeated(1),
RandomI32(0, 2),
Regular(Instruction::Select),
Regular(Instruction::Drop),
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_if = w_bench - 3 * w_param
#[pov_mode = Ignored]
instr_if {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(body::repeated_dyn(r, vec![
RandomI32(0, 2),
Regular(Instruction::If(BlockType::Value(ValueType::I64))),
RandomI64Repeated(1),
Regular(Instruction::Else),
RandomI64Repeated(1),
Regular(Instruction::End),
Regular(Instruction::Drop),
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_br = w_bench - 2 * w_param
// Block instructions are not counted.
#[pov_mode = Ignored]
instr_br {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(body::repeated_dyn(r, vec![
Regular(Instruction::Block(BlockType::NoResult)),
Regular(Instruction::Block(BlockType::NoResult)),
Regular(Instruction::Block(BlockType::NoResult)),
Regular(Instruction::Br(1)),
RandomI64Repeated(1),
Regular(Instruction::Drop),
Regular(Instruction::End),
RandomI64Repeated(1),
Regular(Instruction::Drop),
Regular(Instruction::End),
RandomI64Repeated(1),
Regular(Instruction::Drop),
Regular(Instruction::End),
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_br_if = w_bench - 3 * w_param
// Block instructions are not counted.
#[pov_mode = Ignored]
instr_br_if {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(body::repeated_dyn(r, vec![
Regular(Instruction::Block(BlockType::NoResult)),
Regular(Instruction::Block(BlockType::NoResult)),
Regular(Instruction::Block(BlockType::NoResult)),
Regular(Instruction::I32Const(1)),
Regular(Instruction::BrIf(1)),
RandomI64Repeated(1),
Regular(Instruction::Drop),
Regular(Instruction::End),
RandomI64Repeated(1),
Regular(Instruction::Drop),
Regular(Instruction::End),
RandomI64Repeated(1),
Regular(Instruction::Drop),
Regular(Instruction::End),
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_br_table = w_bench - 3 * w_param
// Block instructions are not counted.
#[pov_mode = Ignored]
instr_br_table {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let table = Box::new(BrTableData {
table: Box::new([1, 1, 1]),
default: 1,
});
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(body::repeated_dyn(r, vec![
Regular(Instruction::Block(BlockType::NoResult)),
Regular(Instruction::Block(BlockType::NoResult)),
Regular(Instruction::Block(BlockType::NoResult)),
RandomI32(0, 4),
Regular(Instruction::BrTable(table)),
RandomI64Repeated(1),
Regular(Instruction::Drop),
Regular(Instruction::End),
RandomI64Repeated(1),
Regular(Instruction::Drop),
Regular(Instruction::End),
RandomI64Repeated(1),
Regular(Instruction::Drop),
Regular(Instruction::End),
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_br_table_per_entry = w_bench
#[pov_mode = Ignored]
instr_br_table_per_entry {
let e in 1 .. T::Schedule::get().limits.br_table_size;
let entry: Vec<u32> = [0, 1].iter()
.cloned()
.cycle()
.take((e / 2) as usize).collect();
let table = Box::new(BrTableData {
table: entry.into_boxed_slice(),
default: 0,
});
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(body::plain(vec![
Instruction::Block(BlockType::NoResult),
Instruction::Block(BlockType::NoResult),
Instruction::Block(BlockType::NoResult),
Instruction::I32Const((e / 2) as i32),
Instruction::BrTable(table),
Instruction::I64Const(42),
Instruction::Drop,
Instruction::End,
Instruction::I64Const(42),
Instruction::Drop,
Instruction::End,
Instruction::I64Const(42),
Instruction::Drop,
Instruction::End,
Instruction::End,
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_call = w_bench - 2 * w_param
#[pov_mode = Ignored]
instr_call {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
// We need to make use of the stack here in order to trigger stack height
// instrumentation.
aux_body: Some(body::plain(vec![
Instruction::I64Const(42),
Instruction::Drop,
Instruction::End,
])),
call_body: Some(body::repeated(r, &[
Instruction::Call(2), // call aux
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_call_indrect = w_bench - 3 * w_param
#[pov_mode = Ignored]
instr_call_indirect {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let num_elements = T::Schedule::get().limits.table_size;
use self::code::TableSegment;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
// We need to make use of the stack here in order to trigger stack height
// instrumentation.
aux_body: Some(body::plain(vec![
Instruction::I64Const(42),
Instruction::Drop,
Instruction::End,
])),
call_body: Some(body::repeated_dyn(r, vec![
RandomI32(0, num_elements as i32),
Regular(Instruction::CallIndirect(0, 0)), // we only have one sig: 0
])),
table: Some(TableSegment {
num_elements,
function_index: 2, // aux
}),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_per_local = w_bench
#[pov_mode = Ignored]
instr_call_per_local {
let l in 0 .. T::Schedule::get().limits.locals;
let mut aux_body = body::plain(vec![
Instruction::End,
]);
body::inject_locals(&mut aux_body, l);
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
aux_body: Some(aux_body),
call_body: Some(body::plain(vec![
Instruction::Call(2), // call aux
Instruction::End,
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_local_get = w_bench - 1 * w_param
#[pov_mode = Ignored]
instr_local_get {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let max_locals = T::Schedule::get().limits.locals;
let mut call_body = body::repeated_dyn(r, vec![
RandomGetLocal(0, max_locals),
Regular(Instruction::Drop),
]);
body::inject_locals(&mut call_body, max_locals);
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(call_body),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_local_set = w_bench - 1 * w_param
#[pov_mode = Ignored]
instr_local_set {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let max_locals = T::Schedule::get().limits.locals;
let mut call_body = body::repeated_dyn(r, vec![
RandomI64Repeated(1),
RandomSetLocal(0, max_locals),
]);
body::inject_locals(&mut call_body, max_locals);
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(call_body),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_local_tee = w_bench - 2 * w_param
#[pov_mode = Ignored]
instr_local_tee {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let max_locals = T::Schedule::get().limits.locals;
let mut call_body = body::repeated_dyn(r, vec![
RandomI64Repeated(1),
RandomTeeLocal(0, max_locals),
Regular(Instruction::Drop),
]);
body::inject_locals(&mut call_body, max_locals);
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(call_body),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_global_get = w_bench - 1 * w_param
#[pov_mode = Ignored]
instr_global_get {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let max_globals = T::Schedule::get().limits.globals;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(body::repeated_dyn(r, vec![
RandomGetGlobal(0, max_globals),
Regular(Instruction::Drop),
])),
num_globals: max_globals,
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_global_set = w_bench - 1 * w_param
#[pov_mode = Ignored]
instr_global_set {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let max_globals = T::Schedule::get().limits.globals;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(body::repeated_dyn(r, vec![
RandomI64Repeated(1),
RandomSetGlobal(0, max_globals),
])),
num_globals: max_globals,
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_memory_get = w_bench - 1 * w_param
#[pov_mode = Ignored]
instr_memory_current {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
call_body: Some(body::repeated(r, &[
Instruction::CurrentMemory(0),
Instruction::Drop
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_memory_grow = w_bench - 2 * w_param
// We can only allow allocate as much memory as it is allowed in a contract.
// Therefore the repeat count is limited by the maximum memory any contract can have.
// Using a contract with more memory will skew the benchmark because the runtime of grow
// depends on how much memory is already allocated.
#[pov_mode = Ignored]
instr_memory_grow {
let r in 0 .. ImportedMemory::max::<T>().max_pages;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory {
min_pages: 0,
max_pages: ImportedMemory::max::<T>().max_pages,
}),
call_body: Some(body::repeated(r, &[
Instruction::I32Const(1),
Instruction::GrowMemory(0),
Instruction::Drop,
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
// Unary numeric instructions.
// All use w = w_bench - 2 * w_param.
#[pov_mode = Ignored]
instr_i64clz {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::unary_instr(
Instruction::I64Clz,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64ctz {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::unary_instr(
Instruction::I64Ctz,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64popcnt {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::unary_instr(
Instruction::I64Popcnt,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64eqz {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::unary_instr(
Instruction::I64Eqz,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64extendsi32 {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(body::repeated_dyn(r, vec![
RandomI32Repeated(1),
Regular(Instruction::I64ExtendSI32),
Regular(Instruction::Drop),
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64extendui32 {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(body::repeated_dyn(r, vec![
RandomI32Repeated(1),
Regular(Instruction::I64ExtendUI32),
Regular(Instruction::Drop),
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i32wrapi64 {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::unary_instr(
Instruction::I32WrapI64,
r,
));
}: {
sbox.invoke();
}
// Binary numeric instructions.
// All use w = w_bench - 3 * w_param.
#[pov_mode = Ignored]
instr_i64eq {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64Eq,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64ne {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64Ne,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64lts {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64LtS,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64ltu {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64LtU,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64gts {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64GtS,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64gtu {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64GtU,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64les {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64LeS,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64leu {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64LeU,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64ges {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64GeS,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64geu {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64GeU,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64add {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64Add,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64sub {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64Sub,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64mul {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64Mul,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64divs {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64DivS,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64divu {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64DivU,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64rems {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64RemS,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64remu {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64RemU,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64and {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64And,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64or {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64Or,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64xor {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64Xor,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64shl {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64Shl,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64shrs {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64ShrS,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64shru {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64ShrU,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64rotl {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64Rotl,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64rotr {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64Rotr,
r,
));
}: {
sbox.invoke();
}
// This is no benchmark. It merely exist to have an easy way to pretty print the currently
// configured `Schedule` during benchmark development.
// It can be outputted using the following command:
@@ -3250,21 +2445,16 @@ benchmarks! {
}: {}
// Execute one erc20 transfer using the ink! erc20 example contract.
//
// `g` is used to enable gas instrumentation to compare the performance impact of
// that instrumentation at runtime.
#[extra]
#[pov_mode = Measured]
ink_erc20_transfer {
let g in 0 .. 1;
let gas_metering = g != 0;
let code = load_benchmark!("ink_erc20");
let data = {
let new: ([u8; 4], BalanceOf<T>) = ([0x9b, 0xae, 0x9d, 0x5e], 1000u32.into());
new.encode()
};
let instance = Contract::<T>::new(
WasmModule::instrumented(code, gas_metering), data,
WasmModule::from_code(code), data,
)?;
let data = {
let transfer: ([u8; 4], AccountIdOf<T>, BalanceOf<T>) = (
@@ -3290,14 +2480,9 @@ benchmarks! {
}
// Execute one erc20 transfer using the open zeppelin erc20 contract compiled with solang.
//
// `g` is used to enable gas instrumentation to compare the performance impact of
// that instrumentation at runtime.
#[extra]
#[pov_mode = Measured]
solang_erc20_transfer {
let g in 0 .. 1;
let gas_metering = g != 0;
let code = include_bytes!("../../benchmarks/solang_erc20.wasm");
let caller = account::<T::AccountId>("instantiator", 0, 0);
let mut balance = [0u8; 32];
@@ -3313,7 +2498,7 @@ benchmarks! {
new.encode()
};
let instance = Contract::<T>::with_caller(
caller, WasmModule::instrumented(code, gas_metering), data,
caller, WasmModule::from_code(code), data,
)?;
balance[0] = 1;
let data = {
@@ -15,13 +15,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
/// ! For instruction benchmarking we do no instantiate a full contract but merely the
/// ! sandbox to execute the wasm code. This is because we do not need the full
/// ! For instruction benchmarking we do not instantiate a full contract but merely the
/// ! sandbox to execute the Wasm code. This is because we do not need the full
/// ! environment that provides the seal interface as imported functions.
use super::{code::WasmModule, Config};
use crate::wasm::{
AllowDeprecatedInterface, AllowUnstableInterface, Environment, PrefabWasmModule,
};
use crate::wasm::{AllowDeprecatedInterface, AllowUnstableInterface, Environment, WasmBlob};
use sp_core::Get;
use wasmi::{errors::LinkerError, Func, Linker, StackLimits, Store};
/// Minimal execution environment without any imported functions.
@@ -38,23 +37,24 @@ impl Sandbox {
}
impl<T: Config> From<&WasmModule<T>> for Sandbox {
/// Creates an instance from the supplied module and supplies as much memory
/// to the instance as the module declares as imported.
/// Creates an instance from the supplied module.
/// Sets the execution engine fuel level to `u64::MAX`.
fn from(module: &WasmModule<T>) -> Self {
let memory = module
.memory
.as_ref()
.map(|mem| (mem.min_pages, mem.max_pages))
.unwrap_or((0, 0));
let (store, _memory, instance) = PrefabWasmModule::<T>::instantiate::<EmptyEnv, _>(
let (mut store, _memory, instance) = WasmBlob::<T>::instantiate::<EmptyEnv, _>(
&module.code,
(),
memory,
&<T>::Schedule::get(),
StackLimits::default(),
// We are testing with an empty environment anyways
AllowDeprecatedInterface::No,
)
.expect("Failed to create benchmarking Sandbox instance");
// Set fuel for wasmi execution.
store
.add_fuel(u64::MAX)
.expect("We've set up engine to fuel consuming mode; qed");
let entry_point = instance.get_export(&store, "call").unwrap().into_func().unwrap();
Self { entry_point, store }
}
+48 -47
View File
@@ -135,7 +135,7 @@ pub trait Ext: sealing::Sealed {
/// Call (possibly transferring some amount of funds) into the specified account.
///
/// Returns the original code size of the called contract.
/// Returns the code size of the called contract.
fn call(
&mut self,
gas_limit: Weight,
@@ -148,7 +148,7 @@ pub trait Ext: sealing::Sealed {
/// Execute code in the current frame.
///
/// Returns the original code size of the called contract.
/// Returns the code size of the called contract.
fn delegate_call(
&mut self,
code: CodeHash<Self::T>,
@@ -159,7 +159,7 @@ pub trait Ext: sealing::Sealed {
///
/// Returns the original code size of the called contract.
/// The newly created account will be associated with `code`. `value` specifies the amount of
/// value transferred from this to the newly created account.
/// value transferred from the caller to the newly created account.
fn instantiate(
&mut self,
gas_limit: Weight,
@@ -263,8 +263,11 @@ pub trait Ext: sealing::Sealed {
/// Get a reference to the schedule used by the current call.
fn schedule(&self) -> &Schedule<Self::T>;
/// Get an immutable reference to the nested gas meter.
fn gas_meter(&self) -> &GasMeter<Self::T>;
/// Get a mutable reference to the nested gas meter.
fn gas_meter(&mut self) -> &mut GasMeter<Self::T>;
fn gas_meter_mut(&mut self) -> &mut GasMeter<Self::T>;
/// Append a string to the debug buffer.
///
@@ -325,24 +328,27 @@ pub trait Executable<T: Config>: Sized {
/// Load the executable from storage.
///
/// # Note
/// Charges size base load and instrumentation weight from the gas meter.
/// Charges size base load weight from the gas meter.
fn from_storage(
code_hash: CodeHash<T>,
schedule: &Schedule<T>,
gas_meter: &mut GasMeter<T>,
) -> Result<Self, DispatchError>;
/// Increment the refcount of a code in-storage by one.
///
/// This is needed when the code is not set via instantiate but `seal_set_code_hash`.
/// Increment the reference count of a of a stored code by one.
///
/// # Errors
///
/// [`Error::CodeNotFound`] is returned if the specified `code_hash` does not exist.
fn add_user(code_hash: CodeHash<T>) -> Result<(), DispatchError>;
/// [`Error::CodeNotFound`] is returned if no stored code found having the specified
/// `code_hash`.
fn increment_refcount(code_hash: CodeHash<T>) -> Result<(), DispatchError>;
/// Decrement the refcount by one if the code exists.
fn remove_user(code_hash: CodeHash<T>);
/// Decrement the reference count of a stored code by one.
///
/// # Note
///
/// A contract whose reference count dropped to zero isn't automatically removed. A
/// `remove_code` transaction must be submitted by the original uploader to do so.
fn decrement_refcount(code_hash: CodeHash<T>);
/// Execute the specified exported function and return the result.
///
@@ -363,7 +369,7 @@ pub trait Executable<T: Config>: Sized {
/// The code hash of the executable.
fn code_hash(&self) -> &CodeHash<T>;
/// Size of the instrumented code in bytes.
/// Size of the contract code in bytes.
fn code_len(&self) -> u32;
/// The code does not contain any instructions which could lead to indeterminism.
@@ -703,9 +709,9 @@ where
Weight::zero(),
storage_meter,
BalanceOf::<T>::zero(),
schedule,
determinism,
)?;
let stack = Self {
origin,
schedule,
@@ -735,7 +741,6 @@ where
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> {
let (account_id, contract_info, executable, delegate_caller, entry_point, nonce) =
@@ -751,7 +756,7 @@ where
if let Some(DelegatedCall { executable, caller }) = delegated_call {
(executable, Some(caller))
} else {
(E::from_storage(contract.code_hash, schedule, gas_meter)?, None)
(E::from_storage(contract.code_hash, gas_meter)?, None)
};
(dest, contract, executable, delegate_caller, ExportedFunction::Call, None)
@@ -759,7 +764,7 @@ where
FrameArgs::Instantiate { sender, nonce, executable, salt, input_data } => {
let account_id = Contracts::<T>::contract_address(
&sender,
executable.code_hash(),
&executable.code_hash(),
input_data,
salt,
);
@@ -831,7 +836,6 @@ where
gas_limit,
nested_storage,
deposit_limit,
self.schedule,
self.determinism,
)?;
self.frames.push(frame);
@@ -864,7 +868,7 @@ where
// Every non delegate call or instantiate also optionally transfers the balance.
self.initial_transfer()?;
// Call into the wasm blob.
// Call into the Wasm blob.
let output = executable
.execute(self, &entry_point, input_data)
.map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?;
@@ -1193,7 +1197,7 @@ where
code_hash: CodeHash<Self::T>,
input_data: Vec<u8>,
) -> Result<ExecReturnValue, ExecError> {
let executable = E::from_storage(code_hash, self.schedule, self.gas_meter())?;
let executable = E::from_storage(code_hash, self.gas_meter_mut())?;
let top_frame = self.top_frame_mut();
let contract_info = top_frame.contract_info().clone();
let account_id = top_frame.account_id.clone();
@@ -1220,7 +1224,7 @@ where
input_data: Vec<u8>,
salt: &[u8],
) -> Result<(AccountIdOf<T>, ExecReturnValue), ExecError> {
let executable = E::from_storage(code_hash, self.schedule, self.gas_meter())?;
let executable = E::from_storage(code_hash, self.gas_meter_mut())?;
let nonce = self.next_nonce();
let executable = self.push_frame(
FrameArgs::Instantiate {
@@ -1255,7 +1259,7 @@ where
)?;
info.queue_trie_for_deletion();
ContractInfoOf::<T>::remove(&frame.account_id);
E::remove_user(info.code_hash);
E::decrement_refcount(info.code_hash);
Contracts::<T>::deposit_event(
vec![T::Hashing::hash_of(&frame.account_id), T::Hashing::hash_of(&beneficiary)],
Event::Terminated {
@@ -1372,7 +1376,11 @@ where
self.schedule
}
fn gas_meter(&mut self) -> &mut GasMeter<Self::T> {
fn gas_meter(&self) -> &GasMeter<Self::T> {
&self.top_frame().nested_gas
}
fn gas_meter_mut(&mut self) -> &mut GasMeter<Self::T> {
&mut self.top_frame_mut().nested_gas
}
@@ -1423,12 +1431,12 @@ where
fn set_code_hash(&mut self, hash: CodeHash<Self::T>) -> Result<(), DispatchError> {
let frame = top_frame_mut!(self);
if !E::from_storage(hash, self.schedule, &mut frame.nested_gas)?.is_deterministic() {
if !E::from_storage(hash, &mut frame.nested_gas)?.is_deterministic() {
return Err(<Error<T>>::Indeterministic.into())
}
E::add_user(hash)?;
E::increment_refcount(hash)?;
let prev_hash = frame.contract_info().code_hash;
E::remove_user(prev_hash);
E::decrement_refcount(prev_hash);
frame.contract_info().code_hash = hash;
Contracts::<Self::T>::deposit_event(
vec![T::Hashing::hash_of(&frame.account_id), hash, prev_hash],
@@ -1591,7 +1599,6 @@ mod tests {
impl Executable<Test> for MockExecutable {
fn from_storage(
code_hash: CodeHash<Test>,
_schedule: &Schedule<Test>,
_gas_meter: &mut GasMeter<Test>,
) -> Result<Self, DispatchError> {
Loader::mutate(|loader| {
@@ -1599,11 +1606,11 @@ mod tests {
})
}
fn add_user(code_hash: CodeHash<Test>) -> Result<(), DispatchError> {
fn increment_refcount(code_hash: CodeHash<Test>) -> Result<(), DispatchError> {
MockLoader::increment_refcount(code_hash)
}
fn remove_user(code_hash: CodeHash<Test>) {
fn decrement_refcount(code_hash: CodeHash<Test>) {
MockLoader::decrement_refcount(code_hash);
}
@@ -1614,7 +1621,7 @@ mod tests {
input_data: Vec<u8>,
) -> ExecResult {
if let &Constructor = function {
Self::add_user(self.code_hash).unwrap();
Self::increment_refcount(self.code_hash).unwrap();
}
if function == &self.func_type {
(self.func)(MockCtx { ext, input_data }, &self)
@@ -1952,8 +1959,7 @@ mod tests {
let schedule = <Test as Config>::Schedule::get();
let min_balance = <Test as Config>::Currency::minimum_balance();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable =
MockExecutable::from_storage(input_data_ch, &schedule, &mut gas_meter).unwrap();
let executable = MockExecutable::from_storage(input_data_ch, &mut gas_meter).unwrap();
set_balance(&ALICE, min_balance * 10_000);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
@@ -2366,8 +2372,7 @@ mod tests {
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable =
MockExecutable::from_storage(dummy_ch, &schedule, &mut gas_meter).unwrap();
let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
@@ -2399,8 +2404,7 @@ mod tests {
let schedule = <Test as Config>::Schedule::get();
let min_balance = <Test as Config>::Currency::minimum_balance();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable =
MockExecutable::from_storage(dummy_ch, &schedule, &mut gas_meter).unwrap();
let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap();
set_balance(&ALICE, min_balance * 1000);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
@@ -2445,8 +2449,7 @@ mod tests {
let schedule = <Test as Config>::Schedule::get();
let min_balance = <Test as Config>::Currency::minimum_balance();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable =
MockExecutable::from_storage(dummy_ch, &schedule, &mut gas_meter).unwrap();
let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap();
set_balance(&ALICE, min_balance * 1000);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
@@ -2614,8 +2617,7 @@ mod tests {
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable =
MockExecutable::from_storage(terminate_ch, &schedule, &mut gas_meter).unwrap();
let executable = MockExecutable::from_storage(terminate_ch, &mut gas_meter).unwrap();
set_balance(&ALICE, 10_000);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
@@ -2717,7 +2719,7 @@ mod tests {
let schedule = <Test as Config>::Schedule::get();
let min_balance = <Test as Config>::Currency::minimum_balance();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(code, &schedule, &mut gas_meter).unwrap();
let executable = MockExecutable::from_storage(code, &mut gas_meter).unwrap();
set_balance(&ALICE, min_balance * 10_000);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
@@ -3141,14 +3143,13 @@ mod tests {
let schedule = <Test as Config>::Schedule::get();
let min_balance = <Test as Config>::Currency::minimum_balance();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let fail_executable =
MockExecutable::from_storage(fail_code, &schedule, &mut gas_meter).unwrap();
let fail_executable = MockExecutable::from_storage(fail_code, &mut gas_meter).unwrap();
let success_executable =
MockExecutable::from_storage(success_code, &schedule, &mut gas_meter).unwrap();
MockExecutable::from_storage(success_code, &mut gas_meter).unwrap();
let succ_fail_executable =
MockExecutable::from_storage(succ_fail_code, &schedule, &mut gas_meter).unwrap();
MockExecutable::from_storage(succ_fail_code, &mut gas_meter).unwrap();
let succ_succ_executable =
MockExecutable::from_storage(succ_succ_code, &schedule, &mut gas_meter).unwrap();
MockExecutable::from_storage(succ_succ_code, &mut gas_meter).unwrap();
set_balance(&ALICE, min_balance * 10_000);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
+35 -3
View File
@@ -23,6 +23,7 @@ use frame_support::{
weights::Weight,
DefaultNoBound,
};
use sp_core::Get;
use sp_runtime::traits::Zero;
use sp_std::marker::PhantomData;
@@ -80,6 +81,8 @@ pub struct GasMeter<T: Config> {
gas_left: Weight,
/// Due to `adjust_gas` and `nested` the `gas_left` can temporarily dip below its final value.
gas_left_lowest: Weight,
/// Amount of fuel consumed by the engine from the last host function call.
engine_consumed: u64,
_phantom: PhantomData<T>,
#[cfg(test)]
tokens: Vec<ErasedToken>,
@@ -91,6 +94,7 @@ impl<T: Config> GasMeter<T> {
gas_limit,
gas_left: gas_limit,
gas_left_lowest: gas_limit,
engine_consumed: Default::default(),
_phantom: PhantomData,
#[cfg(test)]
tokens: Vec::new(),
@@ -151,7 +155,7 @@ impl<T: Config> GasMeter<T> {
/// Amount is calculated by the given `token`.
///
/// Returns `OutOfGas` if there is not enough gas or addition of the specified
/// amount of gas has lead to overflow. On success returns `Proceed`.
/// amount of gas has lead to overflow.
///
/// NOTE that amount isn't consumed if there is not enough gas. This is considered
/// safe because we always charge gas before performing any resource-spending action.
@@ -181,17 +185,45 @@ impl<T: Config> GasMeter<T> {
self.gas_left = self.gas_left.saturating_add(adjustment).min(self.gas_limit);
}
/// This method is used for gas syncs with the engine.
///
/// Updates internal `engine_comsumed` tracker of engine fuel consumption.
///
/// Charges self with the `ref_time` Weight corresponding to wasmi fuel consumed on the engine
/// side since last sync. Passed value is scaled by multiplying it by the weight of a basic
/// operation, as such an operation in wasmi engine costs 1.
///
/// Returns the updated `gas_left` `Weight` value from the meter.
/// Normally this would never fail, as engine should fail first when out of gas.
pub fn charge_fuel(&mut self, wasmi_fuel_total: u64) -> Result<Weight, DispatchError> {
// Take the part consumed since the last update.
let wasmi_fuel = wasmi_fuel_total.saturating_sub(self.engine_consumed);
if !wasmi_fuel.is_zero() {
self.engine_consumed = wasmi_fuel_total;
let reftime_consumed =
wasmi_fuel.saturating_mul(T::Schedule::get().instruction_weights.base as u64);
let ref_time_left = self
.gas_left
.ref_time()
.checked_sub(reftime_consumed)
.ok_or_else(|| Error::<T>::OutOfGas)?;
*(self.gas_left.ref_time_mut()) = ref_time_left;
}
Ok(self.gas_left)
}
/// Returns the amount of gas that is required to run the same call.
///
/// This can be different from `gas_spent` because due to `adjust_gas` the amount of
/// spent gas can temporarily drop and be refunded later.
pub fn gas_required(&self) -> Weight {
self.gas_limit - self.gas_left_lowest()
self.gas_limit.saturating_sub(self.gas_left_lowest())
}
/// Returns how much gas was spent
pub fn gas_consumed(&self) -> Weight {
self.gas_limit - self.gas_left
self.gas_limit.saturating_sub(self.gas_left)
}
/// Returns how much gas left from the initial budget.
+51 -73
View File
@@ -30,7 +30,7 @@
//! These "smart-contract accounts" have the ability to instantiate smart-contracts and make calls
//! to other contract and non-contract accounts.
//!
//! The smart-contract code is stored once in a code cache, and later retrievable via its hash.
//! The smart-contract code is stored once, and later retrievable via its hash.
//! This means that multiple smart-contracts can be instantiated from the same hash, without
//! replicating the code each time.
//!
@@ -41,14 +41,14 @@
//! Finally, when an account is reaped, its associated code and storage of the smart-contract
//! account will also be deleted.
//!
//! ### Gas
//! ### Weight
//!
//! Senders must specify a gas limit with every call, as all instructions invoked by the
//! smart-contract require gas. Unused gas is refunded after the call, regardless of the execution
//! outcome.
//! Senders must specify a [`Weight`] limit with every call, as all instructions invoked by the
//! smart-contract require weight. Unused weight is refunded after the call, regardless of the
//! execution outcome.
//!
//! If the gas limit is reached, then all calls and state changes (including balance transfers) are
//! only reverted at the current call's contract level. For example, if contract A calls B and B
//! If the weight limit is reached, then all calls and state changes (including balance transfers)
//! are only reverted at the current call's contract level. For example, if contract A calls B and B
//! runs out of gas mid-call, then all of B's calls are reverted. Assuming correct error handling by
//! contract A, A's other calls and state changes still persist.
//!
@@ -63,22 +63,25 @@
//!
//! ### Dispatchable functions
//!
//! * [`Pallet::instantiate_with_code`] - Deploys a new contract from the supplied wasm binary,
//! * [`Pallet::instantiate_with_code`] - Deploys a new contract from the supplied Wasm binary,
//! optionally transferring
//! some balance. This instantiates a new smart contract account with the supplied code and
//! calls its constructor to initialize the contract.
//! * [`Pallet::instantiate`] - The same as `instantiate_with_code` but instead of uploading new
//! code an existing `code_hash` is supplied.
//! * [`Pallet::call`] - Makes a call to an account, optionally transferring some balance.
//! * [`Pallet::upload_code`] - Uploads new code without instantiating a contract from it.
//! * [`Pallet::remove_code`] - Removes the stored code and refunds the deposit to its owner. Only
//! allowed to code owner.
//! * [`Pallet::set_code`] - Changes the code of an existing contract. Only allowed to `Root`
//! origin.
//! * [`Pallet::migrate`] - Runs migration steps of curent multi-block migration in priority, before
//! [`Hooks::on_idle`][frame_support::traits::Hooks::on_idle] activates.
//!
//! ## Usage
//!
//! The Contracts module is a work in progress. The following examples show how this module
//! can be used to instantiate and call contracts.
//!
//! * [`ink!`](https://use.ink) is
//! an [`eDSL`](https://wiki.haskell.org/Embedded_domain_specific_language) that enables writing
//! WebAssembly based smart contracts in the Rust programming language.
//! * [`ink!`](https://use.ink) is language that enables writing Wasm-based smart contracts in plain
//! Rust.
#![allow(rustdoc::private_intra_doc_links)]
#![cfg_attr(not(feature = "std"), no_std)]
@@ -102,7 +105,7 @@ use crate::{
exec::{AccountIdOf, ErrorOrigin, ExecError, Executable, Key, Stack as ExecStack},
gas::GasMeter,
storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager},
wasm::{OwnerInfo, PrefabWasmModule, TryInstantiate},
wasm::{CodeInfo, TryInstantiate, WasmBlob},
};
use codec::{Codec, Decode, Encode, HasCompact};
use environmental::*;
@@ -118,7 +121,7 @@ use frame_support::{
ReservableCurrency, Time,
},
weights::Weight,
BoundedVec, RuntimeDebugNoBound, WeakBoundedVec,
BoundedVec, RuntimeDebugNoBound,
};
use frame_system::{ensure_signed, pallet_prelude::OriginFor, EventRecord, Pallet as System};
use pallet_contracts_primitives::{
@@ -149,7 +152,6 @@ type TrieId = BoundedVec<u8, ConstU32<128>>;
type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
type CodeVec<T> = BoundedVec<u8, <T as Config>::MaxCodeLen>;
type RelaxedCodeVec<T> = WeakBoundedVec<u8, <T as Config>::MaxCodeLen>;
type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
type DebugBufferVec<T> = BoundedVec<u8, <T as Config>::MaxDebugBufferLen>;
type EventRecordOf<T> =
@@ -184,7 +186,7 @@ pub mod pallet {
/// The current storage version.
#[cfg(not(any(test, feature = "runtime-benchmarks")))]
const STORAGE_VERSION: StorageVersion = StorageVersion::new(11);
const STORAGE_VERSION: StorageVersion = StorageVersion::new(12);
/// Hard coded storage version for running tests that depend on the current storage version.
#[cfg(any(test, feature = "runtime-benchmarks"))]
@@ -291,9 +293,7 @@ pub mod pallet {
/// The address generator used to generate the addresses of contracts.
type AddressGenerator: AddressGenerator<Self>;
/// The maximum length of a contract code in bytes. This limit applies to the instrumented
/// version of the code. Therefore `instantiate_with_code` can fail even when supplying
/// a wasm binary below this maximum size.
/// The maximum length of a contract code in bytes.
///
/// The value should be chosen carefully taking into the account the overall memory limit
/// your runtime has, as well as the [maximum allowed callstack
@@ -380,19 +380,19 @@ pub mod pallet {
// Check that given configured `MaxCodeLen`, runtime heap memory limit can't be broken.
//
// In worst case, the decoded wasm contract code would be `x16` times larger than the
// In worst case, the decoded Wasm contract code would be `x16` times larger than the
// encoded one. This is because even a single-byte wasm instruction has 16-byte size in
// wasmi. This gives us `MaxCodeLen*16` safety margin.
//
// Next, the pallet keeps both the original and instrumented wasm blobs for each
// contract, hence we add up `MaxCodeLen*2` more to the safety margin.
// Next, the pallet keeps the Wasm blob for each
// contract, hence we add up `MaxCodeLen` to the safety margin.
//
// Finally, the inefficiencies of the freeing-bump allocator
// being used in the client for the runtime memory allocations, could lead to possible
// memory allocations for contract code grow up to `x4` times in some extreme cases,
// which gives us total multiplier of `18*4` for `MaxCodeLen`.
// which gives us total multiplier of `17*4` for `MaxCodeLen`.
//
// That being said, for every contract executed in runtime, at least `MaxCodeLen*18*4`
// That being said, for every contract executed in runtime, at least `MaxCodeLen*17*4`
// memory should be available. Note that maximum allowed heap memory and stack size per
// each contract (stack frame) should also be counted.
//
@@ -401,7 +401,7 @@ pub mod pallet {
//
// This gives us the following formula:
//
// `(MaxCodeLen * 18 * 4 + MAX_STACK_SIZE + max_heap_size) * max_call_depth <
// `(MaxCodeLen * 17 * 4 + MAX_STACK_SIZE + max_heap_size) * max_call_depth <
// max_runtime_mem/2`
//
// Hence the upper limit for the `MaxCodeLen` can be defined as follows:
@@ -410,7 +410,7 @@ pub mod pallet {
.saturating_div(max_call_depth)
.saturating_sub(max_heap_size)
.saturating_sub(MAX_STACK_SIZE)
.saturating_div(18 * 4);
.saturating_div(17 * 4);
assert!(
T::MaxCodeLen::get() < code_len_limit,
@@ -521,7 +521,7 @@ pub mod pallet {
///
/// If the code does not already exist a deposit is reserved from the caller
/// and unreserved only when [`Self::remove_code`] is called. The size of the reserve
/// depends on the instrumented size of the the supplied `code`.
/// depends on the size of the supplied `code`.
///
/// If the code already exists in storage it will still return `Ok` and upgrades
/// the in storage version to the current
@@ -563,7 +563,7 @@ pub mod pallet {
) -> DispatchResultWithPostInfo {
Migration::<T>::ensure_migrated()?;
let origin = ensure_signed(origin)?;
<PrefabWasmModule<T>>::remove(&origin, code_hash)?;
<WasmBlob<T>>::remove(&origin, code_hash)?;
// we waive the fee because removing unused code is beneficial
Ok(Pays::No.into())
}
@@ -594,8 +594,8 @@ pub mod pallet {
} else {
return Err(<Error<T>>::ContractNotFound.into())
};
<PrefabWasmModule<T>>::add_user(code_hash)?;
<PrefabWasmModule<T>>::remove_user(contract.code_hash);
<WasmBlob<T>>::increment_refcount(code_hash)?;
<WasmBlob<T>>::decrement_refcount(contract.code_hash);
Self::deposit_event(
vec![T::Hashing::hash_of(&dest), code_hash, contract.code_hash],
Event::ContractCodeUpdated {
@@ -674,8 +674,7 @@ pub mod pallet {
///
/// Instantiation is executed as follows:
///
/// - The supplied `code` is instrumented, deployed, and a `code_hash` is created for that
/// code.
/// - The supplied `code` is deployed, and a `code_hash` is created for that code.
/// - If the `code_hash` already exists on the chain the underlying `code` will be shared.
/// - The destination address is computed based on the sender, code_hash and the salt.
/// - The smart-contract account is created at the computed address.
@@ -865,8 +864,8 @@ pub mod pallet {
#[pallet::error]
pub enum Error<T> {
/// A new schedule must have a greater version than the current one.
InvalidScheduleVersion,
/// Invalid schedule supplied, e.g. with zero weight of a basic operation.
InvalidSchedule,
/// Invalid combination of flags supplied to `seal_call` or `seal_delegate_call`.
InvalidCallFlags,
/// The executed contract exhausted its gas limit.
@@ -929,7 +928,7 @@ pub mod pallet {
/// or via RPC an `Ok` will be returned. In this case the caller needs to inspect the flags
/// to determine whether a reversion has taken place.
ContractReverted,
/// The contract's code was found to be invalid during validation or instrumentation.
/// The contract's code was found to be invalid during validation.
///
/// The most likely cause of this is that an API was used which is not supported by the
/// node. This happens if an older node is used with a new version of ink!. Try updating
@@ -946,18 +945,13 @@ pub mod pallet {
NoMigrationPerformed,
}
/// A mapping from an original code hash to the original code, untouched by instrumentation.
/// A mapping from a contract's code hash to its code.
#[pallet::storage]
pub(crate) type PristineCode<T: Config> = StorageMap<_, Identity, CodeHash<T>, CodeVec<T>>;
/// A mapping between an original code hash and instrumented wasm code, ready for execution.
/// A mapping from a contract's code hash to its code info.
#[pallet::storage]
pub(crate) type CodeStorage<T: Config> =
StorageMap<_, Identity, CodeHash<T>, PrefabWasmModule<T>>;
/// A mapping between an original code hash and its owner information.
#[pallet::storage]
pub(crate) type OwnerInfoOf<T: Config> = StorageMap<_, Identity, CodeHash<T>, OwnerInfo<T>>;
pub(crate) type CodeInfoOf<T: Config> = StorageMap<_, Identity, CodeHash<T>, CodeInfo<T>>;
/// This is a **monotonic** counter incremented on contract instantiation.
///
@@ -1196,7 +1190,7 @@ impl<T: Config> Invokable<T> for CallInput<T> {
},
};
let schedule = T::Schedule::get();
let result = ExecStack::<T, PrefabWasmModule<T>>::run_call(
let result = ExecStack::<T, WasmBlob<T>>::run_call(
origin.clone(),
dest.clone(),
&mut gas_meter,
@@ -1239,7 +1233,7 @@ impl<T: Config> Invokable<T> for InstantiateInput<T> {
let origin = contract_origin.account_id()?;
let (extra_deposit, executable) = match &self.code {
Code::Upload(binary) => {
let executable = PrefabWasmModule::from_code(
let executable = WasmBlob::from_code(
binary.clone(),
&schedule,
origin.clone(),
@@ -1257,12 +1251,10 @@ impl<T: Config> Invokable<T> for InstantiateInput<T> {
// uploaded module does not already exist. This deposit is not part of the
// storage meter because it is not transferred to the contract but
// reserved on the uploading account.
(executable.open_deposit(), executable)
(executable.open_deposit(&executable.code_info()), executable)
},
Code::Existing(hash) => (
Default::default(),
PrefabWasmModule::from_storage(*hash, &schedule, &mut gas_meter)?,
),
Code::Existing(hash) =>
(Default::default(), WasmBlob::from_storage(*hash, &mut gas_meter)?),
};
let contract_origin = Origin::from_account_id(origin.clone());
let mut storage_meter = StorageMeter::new(
@@ -1271,7 +1263,7 @@ impl<T: Config> Invokable<T> for InstantiateInput<T> {
common.value.saturating_add(extra_deposit),
)?;
let CommonInput { value, data, debug_message, .. } = common;
let result = ExecStack::<T, PrefabWasmModule<T>>::run_instantiate(
let result = ExecStack::<T, WasmBlob<T>>::run_instantiate(
origin.clone(),
executable,
&mut gas_meter,
@@ -1443,15 +1435,10 @@ impl<T: Config> Pallet<T> {
) -> CodeUploadResult<CodeHash<T>, BalanceOf<T>> {
Migration::<T>::ensure_migrated()?;
let schedule = T::Schedule::get();
let module = PrefabWasmModule::from_code(
code,
&schedule,
origin,
determinism,
TryInstantiate::Instantiate,
)
.map_err(|(err, _)| err)?;
let deposit = module.open_deposit();
let module =
WasmBlob::from_code(code, &schedule, origin, determinism, TryInstantiate::Instantiate)
.map_err(|(err, _)| err)?;
let deposit = module.open_deposit(&module.code_info());
if let Some(storage_deposit_limit) = storage_deposit_limit {
ensure!(storage_deposit_limit >= deposit, <Error<T>>::StorageDepositLimitExhausted);
}
@@ -1494,26 +1481,17 @@ impl<T: Config> Pallet<T> {
ContractInfo::<T>::load_code_hash(account)
}
/// Store code for benchmarks which does not check nor instrument the code.
/// Store code for benchmarks which does not validate the code.
#[cfg(feature = "runtime-benchmarks")]
fn store_code_raw(
code: Vec<u8>,
owner: T::AccountId,
) -> frame_support::dispatch::DispatchResult {
let schedule = T::Schedule::get();
PrefabWasmModule::store_code_unchecked(code, &schedule, owner)?;
WasmBlob::store_code_unchecked(code, &schedule, owner)?;
Ok(())
}
/// This exists so that benchmarks can determine the weight of running an instrumentation.
#[cfg(feature = "runtime-benchmarks")]
fn reinstrument_module(
module: &mut PrefabWasmModule<T>,
schedule: &Schedule<T>,
) -> frame_support::dispatch::DispatchResult {
self::wasm::reinstrument(module, schedule).map(|_| ())
}
/// Deposit a pallet contracts event. Handles the conversion to the overarching event type.
fn deposit_event(topics: Vec<T::Hash>, event: Event<T>) {
<frame_system::Pallet<T>>::deposit_event_indexed(
+2 -1
View File
@@ -58,6 +58,7 @@
pub mod v10;
pub mod v11;
pub mod v12;
pub mod v9;
use crate::{weights::WeightInfo, Config, Error, MigrationInProgress, Pallet, Weight, LOG_TARGET};
@@ -173,7 +174,7 @@ mod private {
/// Defines a sequence of migrations.
///
/// The sequence must be defined by a tuple of migrations, each of which must implement the
/// `Migrate` trait. Migrations must be ordered by their versions with no gaps.
/// `MigrationStep` trait. Migrations must be ordered by their versions with no gaps.
pub trait MigrateSequence: private::Sealed {
/// Returns the range of versions that this migrations sequence can handle.
/// Migrations must be ordered by their versions with no gaps.
@@ -179,7 +179,11 @@ impl<T: Config> MigrationStep for Migration<T> {
})
// If it fails we fallback to minting the ED.
.unwrap_or_else(|err| {
log::error!(target: LOG_TARGET, "Failed to transfer the base deposit, reason: {:?}", err);
log::error!(
target: LOG_TARGET,
"Failed to transfer the base deposit, reason: {:?}",
err
);
T::Currency::deposit_creating(&deposit_account, min_balance);
min_balance
});
@@ -0,0 +1,315 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Move `OwnerInfo` to `CodeInfo`, add `determinism` field to the latter, clear `CodeStorage` and
//! repay deposits.
use crate::{
migration::{IsFinished, MigrationStep},
weights::WeightInfo,
AccountIdOf, BalanceOf, CodeHash, Config, Determinism, Pallet, Weight, LOG_TARGET,
};
use codec::{Decode, Encode};
use frame_support::{
codec, pallet_prelude::*, storage_alias, traits::ReservableCurrency, DefaultNoBound, Identity,
};
use scale_info::prelude::format;
use sp_core::hexdisplay::HexDisplay;
#[cfg(feature = "try-runtime")]
use sp_runtime::TryRuntimeError;
use sp_runtime::{traits::Zero, FixedPointNumber, FixedU128, Saturating};
use sp_std::prelude::*;
mod old {
use super::*;
#[derive(Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
#[codec(mel_bound())]
#[scale_info(skip_type_params(T))]
pub struct OwnerInfo<T: Config> {
pub owner: AccountIdOf<T>,
#[codec(compact)]
pub deposit: BalanceOf<T>,
#[codec(compact)]
pub refcount: u64,
}
#[derive(Encode, Decode, scale_info::TypeInfo)]
#[codec(mel_bound())]
#[scale_info(skip_type_params(T))]
pub struct PrefabWasmModule {
#[codec(compact)]
pub instruction_weights_version: u32,
#[codec(compact)]
pub initial: u32,
#[codec(compact)]
pub maximum: u32,
pub code: Vec<u8>,
pub determinism: Determinism,
}
#[storage_alias]
pub type OwnerInfoOf<T: Config> = StorageMap<Pallet<T>, Identity, CodeHash<T>, OwnerInfo<T>>;
#[storage_alias]
pub type CodeStorage<T: Config> =
StorageMap<Pallet<T>, Identity, CodeHash<T>, PrefabWasmModule>;
}
#[derive(Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
#[codec(mel_bound())]
#[scale_info(skip_type_params(T))]
pub struct CodeInfo<T: Config> {
owner: AccountIdOf<T>,
#[codec(compact)]
deposit: BalanceOf<T>,
#[codec(compact)]
refcount: u64,
determinism: Determinism,
}
#[storage_alias]
pub type CodeInfoOf<T: Config> = StorageMap<Pallet<T>, Twox64Concat, CodeHash<T>, CodeInfo<T>>;
#[storage_alias]
pub type PristineCode<T: Config> = StorageMap<Pallet<T>, Identity, CodeHash<T>, Vec<u8>>;
#[cfg(feature = "runtime-benchmarks")]
pub fn store_old_dummy_code<T: Config>(len: usize, account: T::AccountId) {
use sp_runtime::traits::Hash;
let code = vec![42u8; len];
let hash = T::Hashing::hash(&code);
PristineCode::<T>::insert(hash, code.clone());
let module = old::PrefabWasmModule {
instruction_weights_version: Default::default(),
initial: Default::default(),
maximum: Default::default(),
code,
determinism: Determinism::Enforced,
};
old::CodeStorage::<T>::insert(hash, module);
let info = old::OwnerInfo { owner: account, deposit: u32::MAX.into(), refcount: u64::MAX };
old::OwnerInfoOf::<T>::insert(hash, info);
}
#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)]
pub struct Migration<T: Config> {
last_code_hash: Option<CodeHash<T>>,
}
impl<T: Config> MigrationStep for Migration<T> {
const VERSION: u16 = 12;
fn max_step_weight() -> Weight {
T::WeightInfo::v12_migration_step(T::MaxCodeLen::get())
}
fn step(&mut self) -> (IsFinished, Weight) {
let mut iter = if let Some(last_key) = self.last_code_hash.take() {
old::OwnerInfoOf::<T>::iter_from(old::OwnerInfoOf::<T>::hashed_key_for(last_key))
} else {
old::OwnerInfoOf::<T>::iter()
};
if let Some((hash, old_info)) = iter.next() {
log::debug!(target: LOG_TARGET, "Migrating OwnerInfo for code_hash {:?}", hash);
let module = old::CodeStorage::<T>::take(hash)
.expect(format!("No PrefabWasmModule found for code_hash: {:?}", hash).as_str());
let code_len = module.code.len();
// We print this to measure the impact of the migration.
// Storage removed: deleted PrefabWasmModule's encoded len.
// Storage added: determinism field encoded len (as all other CodeInfo fields are the
// same as in the deleted OwnerInfo).
log::debug!(target: LOG_TARGET, "Storage removed: 1 item, {} bytes", &code_len,);
// Storage usage prices could change over time, and accounts who uploaded their
// contracts code before the storage deposits where introduced, had not been ever
// charged with any deposit for that (see migration v6).
//
// This is why deposit to be refunded here is calculated as follows:
//
// 1. Calculate the deposit amount for storage before the migration, given current
// prices.
// 2. Given current reserved deposit amount, calculate the correction factor.
// 3. Calculate the deposit amount for storage after the migration, given current
// prices.
// 4. Calculate real deposit amount to be reserved after the migration.
let price_per_byte = T::DepositPerByte::get();
let price_per_item = T::DepositPerItem::get();
let bytes_before = module
.encoded_size()
.saturating_add(code_len)
.saturating_add(old::OwnerInfo::<T>::max_encoded_len()) as u32;
let items_before = 3u32;
let deposit_expected_before = price_per_byte
.saturating_mul(bytes_before.into())
.saturating_add(price_per_item.saturating_mul(items_before.into()));
let ratio = FixedU128::checked_from_rational(old_info.deposit, deposit_expected_before)
.unwrap_or_default()
.min(FixedU128::from_u32(1));
let bytes_after = code_len.saturating_add(CodeInfo::<T>::max_encoded_len()) as u32;
let items_after = 2u32;
let deposit_expected_after = price_per_byte
.saturating_mul(bytes_after.into())
.saturating_add(price_per_item.saturating_mul(items_after.into()));
let deposit = ratio.saturating_mul_int(deposit_expected_after);
let info = CodeInfo::<T> {
determinism: module.determinism,
owner: old_info.owner,
deposit,
refcount: old_info.refcount,
};
let amount = old_info.deposit.saturating_sub(info.deposit);
if !amount.is_zero() {
T::Currency::unreserve(&info.owner, amount);
log::debug!(
target: LOG_TARGET,
"Deposit refunded: {:?} Balance, to: {:?}",
&amount,
HexDisplay::from(&info.owner.encode())
);
} else {
log::warn!(
target: LOG_TARGET,
"new deposit: {:?} >= old deposit: {:?}",
&info.deposit,
&old_info.deposit
);
}
CodeInfoOf::<T>::insert(hash, info);
self.last_code_hash = Some(hash);
(IsFinished::No, T::WeightInfo::v12_migration_step(code_len as u32))
} else {
log::debug!(target: LOG_TARGET, "No more OwnerInfo to migrate");
(IsFinished::Yes, T::WeightInfo::v12_migration_step(0))
}
}
#[cfg(feature = "try-runtime")]
fn pre_upgrade_step() -> Result<Vec<u8>, TryRuntimeError> {
let len = 100;
log::debug!(target: LOG_TARGET, "Taking sample of {} OwnerInfo(s)", len);
let sample: Vec<_> = old::OwnerInfoOf::<T>::iter()
.take(len)
.map(|(k, v)| {
let module = old::CodeStorage::<T>::get(k)
.expect("No PrefabWasmModule found for code_hash: {:?}");
let info: CodeInfo<T> = CodeInfo {
determinism: module.determinism,
deposit: v.deposit,
refcount: v.refcount,
owner: v.owner,
};
(k, info)
})
.collect();
let storage: u32 =
old::CodeStorage::<T>::iter().map(|(_k, v)| v.encoded_size() as u32).sum();
let mut deposit: BalanceOf<T> = Default::default();
old::OwnerInfoOf::<T>::iter().for_each(|(_k, v)| deposit += v.deposit);
Ok((sample, deposit, storage).encode())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade_step(state: Vec<u8>) -> Result<(), TryRuntimeError> {
let state = <(Vec<(CodeHash<T>, CodeInfo<T>)>, BalanceOf<T>, u32) as Decode>::decode(
&mut &state[..],
)
.unwrap();
log::debug!(target: LOG_TARGET, "Validating state of {} Codeinfo(s)", state.0.len());
for (hash, old) in state.0 {
let info = CodeInfoOf::<T>::get(&hash)
.expect(format!("CodeInfo for code_hash {:?} not found!", hash).as_str());
ensure!(info.determinism == old.determinism, "invalid determinism");
ensure!(info.owner == old.owner, "invalid owner");
ensure!(info.refcount == old.refcount, "invalid refcount");
}
if let Some((k, _)) = old::CodeStorage::<T>::iter().next() {
log::warn!(
target: LOG_TARGET,
"CodeStorage is still NOT empty, found code_hash: {:?}",
k
);
} else {
log::debug!(target: LOG_TARGET, "CodeStorage is empty.");
}
if let Some((k, _)) = old::OwnerInfoOf::<T>::iter().next() {
log::warn!(
target: LOG_TARGET,
"OwnerInfoOf is still NOT empty, found code_hash: {:?}",
k
);
} else {
log::debug!(target: LOG_TARGET, "OwnerInfoOf is empty.");
}
let mut deposit: BalanceOf<T> = Default::default();
let mut items = 0u32;
let mut storage_info = 0u32;
CodeInfoOf::<T>::iter().for_each(|(_k, v)| {
deposit += v.deposit;
items += 1;
storage_info += v.encoded_size() as u32;
});
let mut storage_code = 0u32;
PristineCode::<T>::iter().for_each(|(_k, v)| {
storage_code += v.len() as u32;
});
let (_, old_deposit, storage_module) = state;
// CodeInfoOf::max_encoded_len == OwnerInfoOf::max_encoded_len + 1
// I.e. code info adds up 1 byte per record.
let info_bytes_added = items.clone();
// We removed 1 PrefabWasmModule, and added 1 byte of determinism flag, per contract code.
let storage_removed = storage_module.saturating_sub(info_bytes_added);
// module+code+info - bytes
let storage_was = storage_module
.saturating_add(storage_code)
.saturating_add(storage_info)
.saturating_sub(info_bytes_added);
// We removed 1 storage item (PrefabWasmMod) for every stored contract code (was stored 3
// items per code).
let items_removed = items;
log::info!(
target: LOG_TARGET,
"Storage freed, bytes: {} (of {}), items: {} (of {})",
storage_removed,
storage_was,
items_removed,
items_removed * 3,
);
log::info!(
target: LOG_TARGET,
"Deposits returned, total: {:?} Balance (of {:?} Balance)",
old_deposit.saturating_sub(deposit),
old_deposit,
);
Ok(())
}
}
+9 -276
View File
@@ -18,7 +18,7 @@
//! This module contains the cost schedule and supporting code that constructs a
//! sane default schedule from a `WeightInfo` implementation.
use crate::{wasm::Determinism, weights::WeightInfo, Config};
use crate::{weights::WeightInfo, Config};
use codec::{Decode, Encode};
use frame_support::{weights::Weight, DefaultNoBound};
@@ -28,7 +28,6 @@ use scale_info::TypeInfo;
use serde::{Deserialize, Serialize};
use sp_runtime::RuntimeDebug;
use sp_std::marker::PhantomData;
use wasm_instrument::{gas_metering, parity_wasm::elements};
/// Definition of the cost schedule and other parameterizations for the wasm vm.
///
@@ -50,18 +49,12 @@ use wasm_instrument::{gas_metering, parity_wasm::elements};
/// .. Default::default()
/// },
/// instruction_weights: InstructionWeights {
/// version: 5,
/// .. Default::default()
/// },
/// .. Default::default()
/// }
/// }
/// ```
///
/// # Note
///
/// Please make sure to bump the [`InstructionWeights::version`] whenever substantial
/// changes are made to its values.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "std", serde(bound(serialize = "", deserialize = "")))]
#[derive(Clone, Encode, Decode, PartialEq, Eq, ScheduleDebug, DefaultNoBound, TypeInfo)]
@@ -78,12 +71,6 @@ pub struct Schedule<T: Config> {
}
/// Describes the upper limits on various metrics.
///
/// # Note
///
/// The values in this struct should never be decreased. The reason is that decreasing those
/// values will break existing contracts which are above the new limits when a
/// re-instrumentation is triggered.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct Limits {
@@ -140,100 +127,15 @@ impl Limits {
}
}
/// Describes the weight for all categories of supported wasm instructions.
///
/// There there is one field for each wasm instruction that describes the weight to
/// execute one instruction of that name. There are a few exceptions:
///
/// 1. If there is a i64 and a i32 variant of an instruction we use the weight of the former for
/// both.
/// 2. The following instructions are free of charge because they merely structure the wasm module
/// and cannot be spammed without making the module invalid (and rejected): End, Unreachable,
/// Return, Else
/// 3. The following instructions cannot be benchmarked because they are removed by any real world
/// execution engine as a preprocessing step and therefore don't yield a meaningful benchmark
/// result. However, in contrast to the instructions mentioned in 2. they can be spammed. We
/// price them with the same weight as the "default" instruction (i64.const): Block, Loop, Nop
/// 4. We price both i64.const and drop as InstructionWeights.i64const / 2. The reason for that is
/// that we cannot benchmark either of them on its own but we need their individual values to
/// derive (by subtraction) the weight of all other instructions that use them as supporting
/// instructions. Supporting means mainly pushing arguments and dropping return values in order
/// to maintain a valid module.
/// Gas metering of Wasm executed instructions is being done on the engine side.
/// This struct holds a reference value used to gas units scaling between host and engine.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Clone, Encode, Decode, PartialEq, Eq, ScheduleDebug, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct InstructionWeights<T: Config> {
/// Version of the instruction weights.
///
/// # Note
///
/// Should be incremented whenever any instruction weight is changed. The
/// reason is that changes to instruction weights require a re-instrumentation
/// in order to apply the changes to an already deployed code. The re-instrumentation
/// is triggered by comparing the version of the current schedule with the version the code was
/// instrumented with. Changes usually happen when pallet_contracts is re-benchmarked.
///
/// Changes to other parts of the schedule should not increment the version in
/// order to avoid unnecessary re-instrumentations.
pub version: u32,
/// Weight to be used for instructions which don't have benchmarks assigned.
///
/// This weight is used whenever a code is uploaded with [`Determinism::Relaxed`]
/// and an instruction (usually a float instruction) is encountered. This weight is **not**
/// used if a contract is uploaded with [`Determinism::Enforced`]. If this field is set to
/// `0` (the default) only deterministic codes are allowed to be uploaded.
pub fallback: u32,
pub i64const: u32,
pub i64load: u32,
pub i64store: u32,
pub select: u32,
pub r#if: u32,
pub br: u32,
pub br_if: u32,
pub br_table: u32,
pub br_table_per_entry: u32,
pub call: u32,
pub call_indirect: u32,
pub call_per_local: u32,
pub local_get: u32,
pub local_set: u32,
pub local_tee: u32,
pub global_get: u32,
pub global_set: u32,
pub memory_current: u32,
pub memory_grow: u32,
pub i64clz: u32,
pub i64ctz: u32,
pub i64popcnt: u32,
pub i64eqz: u32,
pub i64extendsi32: u32,
pub i64extendui32: u32,
pub i32wrapi64: u32,
pub i64eq: u32,
pub i64ne: u32,
pub i64lts: u32,
pub i64ltu: u32,
pub i64gts: u32,
pub i64gtu: u32,
pub i64les: u32,
pub i64leu: u32,
pub i64ges: u32,
pub i64geu: u32,
pub i64add: u32,
pub i64sub: u32,
pub i64mul: u32,
pub i64divs: u32,
pub i64divu: u32,
pub i64rems: u32,
pub i64remu: u32,
pub i64and: u32,
pub i64or: u32,
pub i64xor: u32,
pub i64shl: u32,
pub i64shrs: u32,
pub i64shru: u32,
pub i64rotl: u32,
pub i64rotr: u32,
/// Base instruction `ref_time` Weight.
/// Should match to wasmi's `1` fuel (see <https://github.com/paritytech/wasmi/issues/701>).
pub base: u32,
/// The type parameter is used in the default implementation.
#[codec(skip)]
pub _phantom: PhantomData<T>,
@@ -286,9 +188,6 @@ pub struct HostFnWeights<T: Config> {
/// Weight of calling `seal_weight_to_fee`.
pub weight_to_fee: Weight,
/// Weight of calling `gas`.
pub gas: Weight,
/// Weight of calling `seal_input`.
pub input: Weight,
@@ -491,63 +390,10 @@ impl Default for Limits {
}
impl<T: Config> Default for InstructionWeights<T> {
/// We price both `i64.const` and `drop` as `instr_i64const / 2`. The reason
/// for that is that we cannot benchmark either of them on its own.
fn default() -> Self {
Self {
version: 4,
fallback: 0,
i64const: cost_instr!(instr_i64const, 1),
i64load: cost_instr!(instr_i64load, 2),
i64store: cost_instr!(instr_i64store, 2),
select: cost_instr!(instr_select, 4),
r#if: cost_instr!(instr_if, 3),
br: cost_instr!(instr_br, 2),
br_if: cost_instr!(instr_br_if, 3),
br_table: cost_instr!(instr_br_table, 3),
br_table_per_entry: cost_instr!(instr_br_table_per_entry, 0),
call: cost_instr!(instr_call, 2),
call_indirect: cost_instr!(instr_call_indirect, 3),
call_per_local: cost_instr!(instr_call_per_local, 0),
local_get: cost_instr!(instr_local_get, 1),
local_set: cost_instr!(instr_local_set, 1),
local_tee: cost_instr!(instr_local_tee, 2),
global_get: cost_instr!(instr_global_get, 1),
global_set: cost_instr!(instr_global_set, 1),
memory_current: cost_instr!(instr_memory_current, 1),
memory_grow: cost_instr!(instr_memory_grow, 1),
i64clz: cost_instr!(instr_i64clz, 2),
i64ctz: cost_instr!(instr_i64ctz, 2),
i64popcnt: cost_instr!(instr_i64popcnt, 2),
i64eqz: cost_instr!(instr_i64eqz, 2),
i64extendsi32: cost_instr!(instr_i64extendsi32, 2),
i64extendui32: cost_instr!(instr_i64extendui32, 2),
i32wrapi64: cost_instr!(instr_i32wrapi64, 2),
i64eq: cost_instr!(instr_i64eq, 3),
i64ne: cost_instr!(instr_i64ne, 3),
i64lts: cost_instr!(instr_i64lts, 3),
i64ltu: cost_instr!(instr_i64ltu, 3),
i64gts: cost_instr!(instr_i64gts, 3),
i64gtu: cost_instr!(instr_i64gtu, 3),
i64les: cost_instr!(instr_i64les, 3),
i64leu: cost_instr!(instr_i64leu, 3),
i64ges: cost_instr!(instr_i64ges, 3),
i64geu: cost_instr!(instr_i64geu, 3),
i64add: cost_instr!(instr_i64add, 3),
i64sub: cost_instr!(instr_i64sub, 3),
i64mul: cost_instr!(instr_i64mul, 3),
i64divs: cost_instr!(instr_i64divs, 3),
i64divu: cost_instr!(instr_i64divu, 3),
i64rems: cost_instr!(instr_i64rems, 3),
i64remu: cost_instr!(instr_i64remu, 3),
i64and: cost_instr!(instr_i64and, 3),
i64or: cost_instr!(instr_i64or, 3),
i64xor: cost_instr!(instr_i64xor, 3),
i64shl: cost_instr!(instr_i64shl, 3),
i64shrs: cost_instr!(instr_i64shrs, 3),
i64shru: cost_instr!(instr_i64shru, 3),
i64rotl: cost_instr!(instr_i64rotl, 3),
i64rotr: cost_instr!(instr_i64rotr, 3),
_phantom: PhantomData,
}
Self { base: cost_instr!(instr_i64const, 1), _phantom: PhantomData }
}
}
@@ -568,12 +414,6 @@ impl<T: Config> Default for HostFnWeights<T> {
block_number: cost!(seal_block_number),
now: cost!(seal_now),
weight_to_fee: cost!(seal_weight_to_fee),
// Manually remove proof size from basic block cost.
//
// Due to imperfect benchmarking some host functions incur a small
// amount of proof size. Usually this is ok. However, charging a basic block is such
// a frequent operation that this would be a vast overestimation.
gas: cost!(seal_gas).set_proof_size(0),
input: cost!(seal_input),
input_per_byte: cost!(seal_input_per_byte),
r#return: cost!(seal_return),
@@ -641,113 +481,6 @@ impl<T: Config> Default for HostFnWeights<T> {
}
}
struct ScheduleRules<'a, T: Config> {
schedule: &'a Schedule<T>,
determinism: Determinism,
}
impl<T: Config> Schedule<T> {
pub(crate) fn rules(&self, determinism: Determinism) -> impl gas_metering::Rules + '_ {
ScheduleRules { schedule: self, determinism }
}
}
impl<'a, T: Config> gas_metering::Rules for ScheduleRules<'a, T> {
fn instruction_cost(&self, instruction: &elements::Instruction) -> Option<u32> {
use self::elements::Instruction::*;
let w = &self.schedule.instruction_weights;
let weight = match *instruction {
End | Unreachable | Return | Else => 0,
I32Const(_) | I64Const(_) | Block(_) | Loop(_) | Nop | Drop => w.i64const,
I32Load(_, _) |
I32Load8S(_, _) |
I32Load8U(_, _) |
I32Load16S(_, _) |
I32Load16U(_, _) |
I64Load(_, _) |
I64Load8S(_, _) |
I64Load8U(_, _) |
I64Load16S(_, _) |
I64Load16U(_, _) |
I64Load32S(_, _) |
I64Load32U(_, _) => w.i64load,
I32Store(_, _) |
I32Store8(_, _) |
I32Store16(_, _) |
I64Store(_, _) |
I64Store8(_, _) |
I64Store16(_, _) |
I64Store32(_, _) => w.i64store,
Select => w.select,
If(_) => w.r#if,
Br(_) => w.br,
BrIf(_) => w.br_if,
Call(_) => w.call,
GetLocal(_) => w.local_get,
SetLocal(_) => w.local_set,
TeeLocal(_) => w.local_tee,
GetGlobal(_) => w.global_get,
SetGlobal(_) => w.global_set,
CurrentMemory(_) => w.memory_current,
GrowMemory(_) => w.memory_grow,
CallIndirect(_, _) => w.call_indirect,
BrTable(ref data) => w
.br_table
.saturating_add(w.br_table_per_entry.saturating_mul(data.table.len() as u32)),
I32Clz | I64Clz => w.i64clz,
I32Ctz | I64Ctz => w.i64ctz,
I32Popcnt | I64Popcnt => w.i64popcnt,
I32Eqz | I64Eqz => w.i64eqz,
I64ExtendSI32 => w.i64extendsi32,
I64ExtendUI32 => w.i64extendui32,
I32WrapI64 => w.i32wrapi64,
I32Eq | I64Eq => w.i64eq,
I32Ne | I64Ne => w.i64ne,
I32LtS | I64LtS => w.i64lts,
I32LtU | I64LtU => w.i64ltu,
I32GtS | I64GtS => w.i64gts,
I32GtU | I64GtU => w.i64gtu,
I32LeS | I64LeS => w.i64les,
I32LeU | I64LeU => w.i64leu,
I32GeS | I64GeS => w.i64ges,
I32GeU | I64GeU => w.i64geu,
I32Add | I64Add => w.i64add,
I32Sub | I64Sub => w.i64sub,
I32Mul | I64Mul => w.i64mul,
I32DivS | I64DivS => w.i64divs,
I32DivU | I64DivU => w.i64divu,
I32RemS | I64RemS => w.i64rems,
I32RemU | I64RemU => w.i64remu,
I32And | I64And => w.i64and,
I32Or | I64Or => w.i64or,
I32Xor | I64Xor => w.i64xor,
I32Shl | I64Shl => w.i64shl,
I32ShrS | I64ShrS => w.i64shrs,
I32ShrU | I64ShrU => w.i64shru,
I32Rotl | I64Rotl => w.i64rotl,
I32Rotr | I64Rotr => w.i64rotr,
// Returning None makes the gas instrumentation fail which we intend for
// unsupported or unknown instructions. Offchain we might allow indeterminism and hence
// use the fallback weight for those instructions.
_ if matches!(self.determinism, Determinism::Relaxed) && w.fallback > 0 => w.fallback,
_ => return None,
};
Some(weight)
}
fn memory_grow_cost(&self) -> gas_metering::MemoryGrowCost {
// We benchmarked the memory.grow instruction with the maximum allowed pages.
// The cost for growing is therefore already included in the instruction cost.
gas_metering::MemoryGrowCost::Free
}
fn call_per_local_cost(&self) -> u32 {
self.schedule.instruction_weights.call_per_local
}
}
#[cfg(test)]
mod test {
use super::*;
+214 -116
View File
@@ -15,7 +15,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use self::test_utils::hash;
use self::test_utils::{ensure_stored, expected_deposit, hash};
use crate as pallet_contracts;
use crate::{
chain_extension::{
@@ -25,11 +25,11 @@ use crate::{
exec::{Frame, Key},
storage::DeletionQueueManager,
tests::test_utils::{get_contract, get_contract_checked},
wasm::{Determinism, PrefabWasmModule, ReturnCode as RuntimeReturnCode},
wasm::{Determinism, ReturnCode as RuntimeReturnCode},
weights::WeightInfo,
BalanceOf, Code, CodeStorage, CollectEvents, Config, ContractInfo, ContractInfoOf, DebugInfo,
BalanceOf, Code, CollectEvents, Config, ContractInfo, ContractInfoOf, DebugInfo,
DefaultAddressGenerator, DeletionQueueCounter, Error, MigrationInProgress, NoopMigration,
Origin, Pallet, Schedule,
Origin, Pallet, PristineCode, Schedule,
};
use assert_matches::assert_matches;
use codec::Encode;
@@ -83,15 +83,18 @@ macro_rules! assert_return_code {
macro_rules! assert_refcount {
( $code_hash:expr , $should:expr $(,)? ) => {{
let is = crate::OwnerInfoOf::<Test>::get($code_hash).map(|m| m.refcount()).unwrap();
let is = crate::CodeInfoOf::<Test>::get($code_hash).map(|m| m.refcount()).unwrap();
assert_eq!(is, $should);
}};
}
pub mod test_utils {
use super::{Balances, Hash, SysConfig, Test};
use crate::{exec::AccountIdOf, CodeHash, Config, ContractInfo, ContractInfoOf, Nonce};
use codec::Encode;
use super::{Balances, DepositPerByte, DepositPerItem, Hash, SysConfig, Test};
use crate::{
exec::AccountIdOf, CodeHash, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf,
Nonce, PristineCode,
};
use codec::{Encode, MaxEncodedLen};
use frame_support::traits::Currency;
pub fn place_contract(address: &AccountIdOf<Test>, code_hash: CodeHash<Test>) {
@@ -119,6 +122,20 @@ pub mod test_utils {
pub fn hash<S: Encode>(s: &S) -> <<Test as SysConfig>::Hashing as Hash>::Output {
<<Test as SysConfig>::Hashing as Hash>::hash_of(s)
}
pub fn expected_deposit(code_len: usize) -> u64 {
// For code_info, the deposit for max_encoded_len is taken.
let code_info_len = CodeInfo::<Test>::max_encoded_len() as u64;
// Calculate deposit to be reserved.
// We add 2 storage items: one for code, other for code_info
DepositPerByte::get().saturating_mul(code_len as u64 + code_info_len) +
DepositPerItem::get().saturating_mul(2)
}
pub fn ensure_stored(code_hash: CodeHash<Test>) -> usize {
// Assert that code_info is stored
assert!(CodeInfoOf::<Test>::contains_key(&code_hash));
// Assert that contract code is stored, and get its size.
PristineCode::<Test>::try_get(&code_hash).unwrap().len()
}
}
impl Test {
@@ -174,6 +191,8 @@ impl ChainExtension<Test> for TestExtension {
where
E: Ext<T = Test>,
{
use codec::Decode;
let func_id = env.func_id();
let id = env.ext_id() as u32 | func_id as u32;
match func_id {
@@ -193,7 +212,11 @@ impl ChainExtension<Test> for TestExtension {
},
2 => {
let mut env = env.buf_in_buf_out();
let weight = Weight::from_parts(env.read(5)?[4].into(), 0);
let mut enc = &env.read(9)?[4..8];
let weight = Weight::from_parts(
u32::decode(&mut enc).map_err(|_| Error::<Test>::ContractTrapped)?.into(),
0,
);
env.charge_weight(weight)?;
Ok(RetVal::Converging(id))
},
@@ -357,8 +380,7 @@ impl pallet_proxy::Config for Test {
parameter_types! {
pub MySchedule: Schedule<Test> = {
let mut schedule = <Schedule<Test>>::default();
schedule.instruction_weights.fallback = 1;
let schedule = <Schedule<Test>>::default();
schedule
};
pub static DepositPerByte: BalanceOf<Test> = 1;
@@ -802,8 +824,9 @@ fn deposit_event_max_value_limit() {
});
}
// Fail out of fuel (ref_time weight) in the engine.
#[test]
fn run_out_of_gas() {
fn run_out_of_fuel_engine() {
let (wasm, _code_hash) = compile_module::<Test>("run_out_of_gas").unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let min_balance = <Test as Config>::Currency::minimum_balance();
@@ -840,6 +863,155 @@ fn run_out_of_gas() {
});
}
// Fail out of fuel (ref_time weight) in the host.
#[test]
fn run_out_of_fuel_host() {
let (code, _hash) = compile_module::<Test>("chain_extension").unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let min_balance = <Test as Config>::Currency::minimum_balance();
let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance);
let addr = Contracts::bare_instantiate(
ALICE,
min_balance * 100,
GAS_LIMIT,
None,
Code::Upload(code),
vec![],
vec![],
DebugInfo::Skip,
CollectEvents::Skip,
)
.result
.unwrap()
.account_id;
let gas_limit = Weight::from_parts(u32::MAX as u64, GAS_LIMIT.proof_size());
// Use chain extension to charge more ref_time than it is available.
let result = Contracts::bare_call(
ALICE,
addr.clone(),
0,
gas_limit,
None,
ExtensionInput { extension_id: 0, func_id: 2, extra: &u32::MAX.encode() }.into(),
DebugInfo::Skip,
CollectEvents::Skip,
Determinism::Enforced,
)
.result;
assert_err!(result, <Error<Test>>::OutOfGas);
});
}
#[test]
fn gas_syncs_work() {
let (wasm0, _code_hash) = compile_module::<Test>("seal_input_noop").unwrap();
let (wasm1, _code_hash) = compile_module::<Test>("seal_input_once").unwrap();
let (wasm2, _code_hash) = compile_module::<Test>("seal_input_twice").unwrap();
ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
// Instantiate noop contract.
let addr0 = Contracts::bare_instantiate(
ALICE,
0,
GAS_LIMIT,
None,
Code::Upload(wasm0),
vec![],
vec![],
DebugInfo::Skip,
CollectEvents::Skip,
)
.result
.unwrap()
.account_id;
// Instantiate 1st contract.
let addr1 = Contracts::bare_instantiate(
ALICE,
0,
GAS_LIMIT,
None,
Code::Upload(wasm1),
vec![],
vec![],
DebugInfo::Skip,
CollectEvents::Skip,
)
.result
.unwrap()
.account_id;
// Instantiate 2nd contract.
let addr2 = Contracts::bare_instantiate(
ALICE,
0,
GAS_LIMIT,
None,
Code::Upload(wasm2),
vec![],
vec![],
DebugInfo::Skip,
CollectEvents::Skip,
)
.result
.unwrap()
.account_id;
let result = Contracts::bare_call(
ALICE,
addr0,
0,
GAS_LIMIT,
None,
1u8.to_le_bytes().to_vec(),
DebugInfo::Skip,
CollectEvents::Skip,
Determinism::Enforced,
);
assert_ok!(result.result);
let engine_consumed_noop = result.gas_consumed.ref_time();
let result = Contracts::bare_call(
ALICE,
addr1,
0,
GAS_LIMIT,
None,
1u8.to_le_bytes().to_vec(),
DebugInfo::Skip,
CollectEvents::Skip,
Determinism::Enforced,
);
assert_ok!(result.result);
let gas_consumed_once = result.gas_consumed.ref_time();
let host_consumed_once = <Test as Config>::Schedule::get().host_fn_weights.input.ref_time();
let engine_consumed_once = gas_consumed_once - host_consumed_once - engine_consumed_noop;
let result = Contracts::bare_call(
ALICE,
addr2,
0,
GAS_LIMIT,
None,
1u8.to_le_bytes().to_vec(),
DebugInfo::Skip,
CollectEvents::Skip,
Determinism::Enforced,
);
assert_ok!(result.result);
let gas_consumed_twice = result.gas_consumed.ref_time();
let host_consumed_twice = host_consumed_once * 2;
let engine_consumed_twice = gas_consumed_twice - host_consumed_twice - engine_consumed_noop;
// Second contract just repeats first contract's instructions twice.
// If runtime syncs gas with the engine properly, this should pass.
assert_eq!(engine_consumed_twice, engine_consumed_once * 2);
});
}
/// Check that contracts with the same account id have different trie ids.
/// Check the `Nonce` storage item for more information.
#[test]
@@ -1949,7 +2121,7 @@ fn chain_extension_works() {
0,
GAS_LIMIT,
None,
ExtensionInput { extension_id: 0, func_id: 2, extra: &[0] }.into(),
ExtensionInput { extension_id: 0, func_id: 2, extra: &0u32.encode() }.into(),
DebugInfo::Skip,
CollectEvents::Skip,
Determinism::Enforced,
@@ -1962,7 +2134,7 @@ fn chain_extension_works() {
0,
GAS_LIMIT,
None,
ExtensionInput { extension_id: 0, func_id: 2, extra: &[42] }.into(),
ExtensionInput { extension_id: 0, func_id: 2, extra: &42u32.encode() }.into(),
DebugInfo::Skip,
CollectEvents::Skip,
Determinism::Enforced,
@@ -1975,7 +2147,7 @@ fn chain_extension_works() {
0,
GAS_LIMIT,
None,
ExtensionInput { extension_id: 0, func_id: 2, extra: &[95] }.into(),
ExtensionInput { extension_id: 0, func_id: 2, extra: &95u32.encode() }.into(),
DebugInfo::Skip,
CollectEvents::Skip,
Determinism::Enforced,
@@ -2582,7 +2754,7 @@ fn refcounter() {
assert_refcount!(code_hash, 1);
// Pristine code should still be there
crate::PristineCode::<Test>::get(code_hash).unwrap();
PristineCode::<Test>::get(code_hash).unwrap();
// remove the last contract
assert_ok!(Contracts::call(
@@ -2597,90 +2769,6 @@ fn refcounter() {
// refcount is `0` but code should still exists because it needs to be removed manually
assert!(crate::PristineCode::<Test>::contains_key(&code_hash));
assert!(crate::CodeStorage::<Test>::contains_key(&code_hash));
});
}
#[test]
fn reinstrument_does_charge() {
let (wasm, code_hash) = compile_module::<Test>("return_with_data").unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
let min_balance = <Test as Config>::Currency::minimum_balance();
let zero = 0u32.to_le_bytes().encode();
let code_len = wasm.len() as u32;
let addr = Contracts::bare_instantiate(
ALICE,
min_balance * 100,
GAS_LIMIT,
None,
Code::Upload(wasm),
zero.clone(),
vec![],
DebugInfo::Skip,
CollectEvents::Skip,
)
.result
.unwrap()
.account_id;
// Call the contract two times without reinstrument
let result0 = Contracts::bare_call(
ALICE,
addr.clone(),
0,
GAS_LIMIT,
None,
zero.clone(),
DebugInfo::Skip,
CollectEvents::Skip,
Determinism::Enforced,
);
assert!(!result0.result.unwrap().did_revert());
let result1 = Contracts::bare_call(
ALICE,
addr.clone(),
0,
GAS_LIMIT,
None,
zero.clone(),
DebugInfo::Skip,
CollectEvents::Skip,
Determinism::Enforced,
);
assert!(!result1.result.unwrap().did_revert());
// They should match because both where called with the same schedule.
assert_eq!(result0.gas_consumed, result1.gas_consumed);
// We cannot change the schedule. Instead, we decrease the version of the deployed
// contract below the current schedule's version.
crate::CodeStorage::mutate(&code_hash, |code: &mut Option<PrefabWasmModule<Test>>| {
code.as_mut().unwrap().decrement_version();
});
// This call should trigger reinstrumentation
let result2 = Contracts::bare_call(
ALICE,
addr.clone(),
0,
GAS_LIMIT,
None,
zero.clone(),
DebugInfo::Skip,
CollectEvents::Skip,
Determinism::Enforced,
);
assert!(!result2.result.unwrap().did_revert());
assert!(result2.gas_consumed.ref_time() > result1.gas_consumed.ref_time());
assert_eq!(
result2.gas_consumed.ref_time(),
result1.gas_consumed.ref_time() +
<Test as Config>::WeightInfo::reinstrument(code_len).ref_time(),
);
});
}
@@ -2874,7 +2962,7 @@ fn gas_estimation_nested_call_fixed_limit() {
.result
);
// Make the same call using proof_size a but less than estimated. Should fail with OutOfGas.
// Make the same call using proof_size but less than estimated. Should fail with OutOfGas.
let result = Contracts::bare_call(
ALICE,
addr_caller,
@@ -3395,14 +3483,16 @@ fn upload_code_works() {
// Drop previous events
initialize_block(2);
assert!(!<CodeStorage<Test>>::contains_key(code_hash));
assert!(!PristineCode::<Test>::contains_key(&code_hash));
assert_ok!(Contracts::upload_code(
RuntimeOrigin::signed(ALICE),
wasm,
Some(codec::Compact(1_000)),
Determinism::Enforced,
));
assert!(<CodeStorage<Test>>::contains_key(code_hash));
// Ensure the contract was stored and get expected deposit amount to be reserved.
let deposit_expected = expected_deposit(ensure_stored(code_hash));
assert_eq!(
System::events(),
@@ -3411,7 +3501,7 @@ fn upload_code_works() {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
who: ALICE,
amount: 173,
amount: deposit_expected,
}),
topics: vec![],
},
@@ -3428,6 +3518,8 @@ fn upload_code_works() {
#[test]
fn upload_code_limit_too_low() {
let (wasm, _code_hash) = compile_module::<Test>("dummy").unwrap();
let deposit_expected = expected_deposit(wasm.len());
let deposit_insufficient = deposit_expected.saturating_sub(1);
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
@@ -3439,7 +3531,7 @@ fn upload_code_limit_too_low() {
Contracts::upload_code(
RuntimeOrigin::signed(ALICE),
wasm,
Some(codec::Compact(100)),
Some(codec::Compact(deposit_insufficient)),
Determinism::Enforced
),
<Error<Test>>::StorageDepositLimitExhausted,
@@ -3452,9 +3544,11 @@ fn upload_code_limit_too_low() {
#[test]
fn upload_code_not_enough_balance() {
let (wasm, _code_hash) = compile_module::<Test>("dummy").unwrap();
let deposit_expected = expected_deposit(wasm.len());
let deposit_insufficient = deposit_expected.saturating_sub(1);
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 150);
let _ = Balances::deposit_creating(&ALICE, deposit_insufficient);
// Drop previous events
initialize_block(2);
@@ -3489,11 +3583,10 @@ fn remove_code_works() {
Some(codec::Compact(1_000)),
Determinism::Enforced,
));
// Ensure the contract was stored and get expected deposit amount to be reserved.
let deposit_expected = expected_deposit(ensure_stored(code_hash));
assert!(<CodeStorage<Test>>::contains_key(code_hash));
assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash));
assert!(!<CodeStorage<Test>>::contains_key(code_hash));
assert_eq!(
System::events(),
vec![
@@ -3501,7 +3594,7 @@ fn remove_code_works() {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
who: ALICE,
amount: 173,
amount: deposit_expected,
}),
topics: vec![],
},
@@ -3514,7 +3607,7 @@ fn remove_code_works() {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Unreserved {
who: ALICE,
amount: 173,
amount: deposit_expected,
}),
topics: vec![],
},
@@ -3544,6 +3637,8 @@ fn remove_code_wrong_origin() {
Some(codec::Compact(1_000)),
Determinism::Enforced,
));
// Ensure the contract was stored and get expected deposit amount to be reserved.
let deposit_expected = expected_deposit(ensure_stored(code_hash));
assert_noop!(
Contracts::remove_code(RuntimeOrigin::signed(BOB), code_hash),
@@ -3557,7 +3652,7 @@ fn remove_code_wrong_origin() {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
who: ALICE,
amount: 173,
amount: deposit_expected,
}),
topics: vec![],
},
@@ -3648,6 +3743,8 @@ fn instantiate_with_zero_balance_works() {
// Check that the BOB contract has been instantiated.
let contract = get_contract(&addr);
let deposit_account = contract.deposit_account().deref();
// Ensure the contract was stored and get expected deposit amount to be reserved.
let deposit_expected = expected_deposit(ensure_stored(code_hash));
// Make sure the account exists even though no free balance was send
assert_eq!(<Test as Config>::Currency::free_balance(&addr), min_balance);
@@ -3708,7 +3805,7 @@ fn instantiate_with_zero_balance_works() {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
who: ALICE,
amount: 173,
amount: deposit_expected,
}),
topics: vec![],
},
@@ -3759,7 +3856,8 @@ fn instantiate_with_below_existential_deposit_works() {
// Check that the BOB contract has been instantiated.
let contract = get_contract(&addr);
let deposit_account = contract.deposit_account().deref();
// Ensure the contract was stored and get expected deposit amount to be reserved.
let deposit_expected = expected_deposit(ensure_stored(code_hash));
// Make sure the account exists even though not enough free balance was send
assert_eq!(<Test as Config>::Currency::free_balance(&addr), min_balance + 50);
assert_eq!(<Test as Config>::Currency::total_balance(&addr), min_balance + 50);
@@ -3828,7 +3926,7 @@ fn instantiate_with_below_existential_deposit_works() {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
who: ALICE,
amount: 173,
amount: deposit_expected,
}),
topics: vec![],
},
@@ -4321,7 +4419,7 @@ fn code_rejected_error_works() {
assert_err!(result.result, <Error<Test>>::CodeRejected);
assert_eq!(
std::str::from_utf8(&result.debug_message).unwrap(),
"validation of new code failed"
"Validation of new code failed!"
);
let (wasm, _) = compile_module::<Test>("invalid_contract").unwrap();
@@ -1,239 +0,0 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! A module that implements instrumented code cache.
//!
//! - In order to run contract code we need to instrument it with gas metering.
//! To do that we need to provide the schedule which will supply exact gas costs values.
//! We cache this code in the storage saving the schedule version.
//! - Before running contract code we check if the cached code has the schedule version that
//! is equal to the current saved schedule.
//! If it is equal then run the code, if it isn't reinstrument with the current schedule.
//! - When we update the schedule we want it to have strictly greater version than the current saved
//! one:
//! this guarantees that every instrumented contract code in cache cannot have the version equal to
//! the current one. Thus, before executing a contract it should be reinstrument with new schedule.
use crate::{
gas::{GasMeter, Token},
wasm::{prepare, PrefabWasmModule},
weights::WeightInfo,
CodeHash, CodeStorage, Config, Error, Event, OwnerInfoOf, Pallet, PristineCode, Schedule,
Weight,
};
use frame_support::{
dispatch::{DispatchError, DispatchResult},
ensure,
traits::{Get, ReservableCurrency},
WeakBoundedVec,
};
use sp_runtime::traits::BadOrigin;
use sp_std::vec;
/// Put the instrumented module in storage.
///
/// Increments the refcount of the in-storage `prefab_module` if it already exists in storage
/// under the specified `code_hash`.
pub fn store<T: Config>(mut module: PrefabWasmModule<T>, instantiated: bool) -> DispatchResult {
let code_hash = sp_std::mem::take(&mut module.code_hash);
<OwnerInfoOf<T>>::mutate(&code_hash, |owner_info| {
match owner_info {
// Instantiate existing contract.
//
// No need to update the `CodeStorage` as any re-instrumentation eagerly saves
// the re-instrumented code.
Some(owner_info) if instantiated => {
owner_info.refcount = owner_info.refcount.checked_add(1).expect(
"
refcount is 64bit. Generating this overflow would require to store
_at least_ 18 exabyte of data assuming that a contract consumes only
one byte of data. Any node would run out of storage space before hitting
this overflow.
qed
",
);
Ok(())
},
// Re-upload existing contract without executing it.
//
// We are careful here to just overwrite the code to not include it into the PoV.
// We do this because the uploaded code was instrumented with the latest schedule
// and hence we persist those changes. Otherwise the next execution will pay again
// for the instrumentation.
Some(_) => {
<CodeStorage<T>>::insert(&code_hash, module);
Ok(())
},
// Upload a new contract.
//
// We need to write all data structures and collect the deposit.
None => {
let orig_code = module.original_code.take().expect(
"
If an executable isn't in storage it was uploaded.
If it was uploaded the original code must exist. qed
",
);
let mut new_owner_info = module.owner_info.take().expect(
"If an executable isn't in storage it was uploaded.
If it was uploaded the owner info was generated and attached. qed
",
);
// This `None` case happens only in freshly uploaded modules. This means that
// the `owner` is always the origin of the current transaction.
T::Currency::reserve(&new_owner_info.owner, new_owner_info.deposit)
.map_err(|_| <Error<T>>::StorageDepositNotEnoughFunds)?;
new_owner_info.refcount = if instantiated { 1 } else { 0 };
<PristineCode<T>>::insert(&code_hash, orig_code);
<CodeStorage<T>>::insert(&code_hash, module);
*owner_info = Some(new_owner_info);
<Pallet<T>>::deposit_event(vec![code_hash], Event::CodeStored { code_hash });
Ok(())
},
}
})
}
/// Decrement the refcount of a code in-storage by one.
///
/// # Note
///
/// A contract whose refcount dropped to zero isn't automatically removed. A `remove_code`
/// transaction must be submitted by the original uploader to do so.
pub fn decrement_refcount<T: Config>(code_hash: CodeHash<T>) {
<OwnerInfoOf<T>>::mutate(code_hash, |existing| {
if let Some(info) = existing {
info.refcount = info.refcount.saturating_sub(1);
}
});
}
/// Increment the refcount of a code in-storage by one.
///
/// # Errors
///
/// [`Error::CodeNotFound`] is returned if the specified `code_hash` does not exist.
pub fn increment_refcount<T: Config>(code_hash: CodeHash<T>) -> Result<(), DispatchError> {
<OwnerInfoOf<T>>::mutate(code_hash, |existing| -> Result<(), DispatchError> {
if let Some(info) = existing {
info.refcount = info.refcount.saturating_add(1);
Ok(())
} else {
Err(Error::<T>::CodeNotFound.into())
}
})
}
/// Try to remove code together with all associated information.
pub fn try_remove<T: Config>(origin: &T::AccountId, code_hash: CodeHash<T>) -> DispatchResult {
<OwnerInfoOf<T>>::try_mutate_exists(&code_hash, |existing| {
if let Some(owner_info) = existing {
ensure!(owner_info.refcount == 0, <Error<T>>::CodeInUse);
ensure!(&owner_info.owner == origin, BadOrigin);
T::Currency::unreserve(&owner_info.owner, owner_info.deposit);
*existing = None;
<PristineCode<T>>::remove(&code_hash);
<CodeStorage<T>>::remove(&code_hash);
<Pallet<T>>::deposit_event(vec![code_hash], Event::CodeRemoved { code_hash });
Ok(())
} else {
Err(<Error<T>>::CodeNotFound.into())
}
})
}
/// Load code with the given code hash.
///
/// If the module was instrumented with a lower version of schedule than
/// the current one given as an argument, then this function will perform
/// re-instrumentation and update the cache in the storage.
pub fn load<T: Config>(
code_hash: CodeHash<T>,
schedule: &Schedule<T>,
gas_meter: &mut GasMeter<T>,
) -> Result<PrefabWasmModule<T>, DispatchError> {
let max_code_len = T::MaxCodeLen::get();
let charged = gas_meter.charge(CodeToken::Load(max_code_len))?;
let mut prefab_module = <CodeStorage<T>>::get(code_hash).ok_or(Error::<T>::CodeNotFound)?;
let instrumented_code_len = prefab_module.code.len() as u32;
gas_meter.adjust_gas(charged, CodeToken::Load(instrumented_code_len));
prefab_module.code_hash = code_hash;
if prefab_module.instruction_weights_version < schedule.instruction_weights.version {
// The instruction weights have changed.
// We need to re-instrument the code with the new instruction weights.
let charged = gas_meter.charge(CodeToken::Reinstrument(instrumented_code_len))?;
let orig_code_len = reinstrument(&mut prefab_module, schedule)?;
gas_meter.adjust_gas(charged, CodeToken::Reinstrument(orig_code_len));
}
Ok(prefab_module)
}
/// Instruments the passed prefab wasm module with the supplied schedule.
///
/// Returns the size in bytes of the uninstrumented code.
pub fn reinstrument<T: Config>(
prefab_module: &mut PrefabWasmModule<T>,
schedule: &Schedule<T>,
) -> Result<u32, DispatchError> {
let original_code =
<PristineCode<T>>::get(&prefab_module.code_hash).ok_or(Error::<T>::CodeNotFound)?;
let original_code_len = original_code.len();
// We need to allow contracts growing too big after re-instrumentation. Otherwise
// the contract can become inaccessible. The user has no influence over this size
// as the contract is already deployed and every change in size would be the result
// of changes in the instrumentation algorithm controlled by the chain authors.
prefab_module.code = WeakBoundedVec::force_from(
prepare::reinstrument::<super::runtime::Env, T>(
&original_code,
schedule,
prefab_module.determinism,
)?,
Some("Contract exceeds size limit after re-instrumentation."),
);
prefab_module.instruction_weights_version = schedule.instruction_weights.version;
<CodeStorage<T>>::insert(&prefab_module.code_hash, &*prefab_module);
Ok(original_code_len as u32)
}
/// Costs for operations that are related to code handling.
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
#[derive(Clone, Copy)]
enum CodeToken {
/// Weight for reinstrumenting a contract contract of the supplied size in bytes.
Reinstrument(u32),
/// Weight for loading a contract per byte.
Load(u32),
}
impl<T: Config> Token<T> for CodeToken {
fn weight(&self) -> Weight {
use self::CodeToken::*;
// In case of `Load` we already covered the general costs of
// calling the storage but still need to account for the actual size of the
// contract code. This is why we subtract `T::*::(0)`. We need to do this at this
// point because when charging the general weight for calling the contract we not know the
// size of the contract.
match *self {
Reinstrument(len) => T::WeightInfo::reinstrument(len),
Load(len) => T::WeightInfo::call_with_code_per_byte(len)
.saturating_sub(T::WeightInfo::call_with_code_per_byte(0)),
}
}
}
+288 -133
View File
@@ -18,13 +18,9 @@
//! This module provides a means for executing contracts
//! represented in wasm.
mod code_cache;
mod prepare;
mod runtime;
#[cfg(feature = "runtime-benchmarks")]
pub use crate::wasm::code_cache::reinstrument;
#[cfg(doc)]
pub use crate::wasm::runtime::api_doc;
@@ -41,81 +37,66 @@ pub use crate::wasm::{
use crate::{
exec::{ExecResult, Executable, ExportedFunction, Ext},
gas::GasMeter,
AccountIdOf, BalanceOf, CodeHash, CodeVec, Config, Error, OwnerInfoOf, RelaxedCodeVec,
Schedule, LOG_TARGET,
gas::{GasMeter, Token},
wasm::prepare::IMPORT_MODULE_MEMORY,
weights::WeightInfo,
AccountIdOf, BadOrigin, BalanceOf, CodeHash, CodeInfoOf, CodeVec, Config, Error, Event, Pallet,
PristineCode, Schedule, Weight, LOG_TARGET,
};
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::dispatch::{DispatchError, DispatchResult};
use frame_support::{
dispatch::{DispatchError, DispatchResult},
ensure,
traits::ReservableCurrency,
};
use sp_core::Get;
use sp_runtime::RuntimeDebug;
use sp_std::prelude::*;
use wasmi::{
Config as WasmiConfig, Engine, Instance, Linker, Memory, MemoryType, Module, StackLimits, Store,
Config as WasmiConfig, Engine, ExternType, FuelConsumptionMode, Instance, Linker, Memory,
MemoryType, Module, StackLimits, Store,
};
const BYTES_PER_PAGE: usize = 64 * 1024;
/// A prepared wasm module ready for execution.
///
/// # Note
///
/// This data structure is mostly immutable once created and stored. The exceptions that
/// can be changed by calling a contract are `instruction_weights_version` and `code`.
/// `instruction_weights_version` and `code` change when a contract with an outdated instrumentation
/// is called. Therefore one must be careful when holding any in-memory representation of this
/// type while calling into a contract as those fields can get out of date.
#[derive(Clone, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
/// Validated Wasm module ready for execution.
/// This data structure is immutable once created and stored.
#[derive(Encode, Decode, scale_info::TypeInfo)]
#[codec(mel_bound())]
#[scale_info(skip_type_params(T))]
pub struct PrefabWasmModule<T: Config> {
/// Version of the instruction weights with which the code was instrumented.
#[codec(compact)]
instruction_weights_version: u32,
/// Initial memory size of a contract's sandbox.
#[codec(compact)]
initial: u32,
/// The maximum memory size of a contract's sandbox.
#[codec(compact)]
maximum: u32,
/// Code instrumented with the latest schedule.
code: RelaxedCodeVec<T>,
/// A code that might contain non deterministic features and is therefore never allowed
/// to be run on chain. Specifically this code can never be instantiated into a contract
/// and can just be used through a delegate call.
determinism: Determinism,
/// The uninstrumented, pristine version of the code.
///
/// It is not stored because the pristine code has its own storage item. The value
/// is only `Some` when this module was created from an `original_code` and `None` if
/// it was loaded from storage.
pub struct WasmBlob<T: Config> {
code: CodeVec<T>,
// This isn't needed for contract execution and is not stored alongside it.
#[codec(skip)]
original_code: Option<CodeVec<T>>,
/// The code hash of the stored code which is defined as the hash over the `original_code`.
///
/// As the map key there is no need to store the hash in the value, too. It is set manually
/// when loading the module from storage.
code_info: CodeInfo<T>,
// This is for not calculating the hash every time we need it.
#[codec(skip)]
code_hash: CodeHash<T>,
// This isn't needed for contract execution and does not get loaded from storage by default.
// It is `Some` if and only if this struct was generated from code.
#[codec(skip)]
owner_info: Option<OwnerInfo<T>>,
}
/// Information that belongs to a [`PrefabWasmModule`] but is stored separately.
/// Contract code related data, such as:
///
/// - owner of the contract, i.e. account uploaded its code,
/// - storage deposit amount,
/// - reference count,
/// - determinism marker.
///
/// It is stored in a separate storage entry to avoid loading the code when not necessary.
#[derive(Clone, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
#[codec(mel_bound())]
#[scale_info(skip_type_params(T))]
pub struct OwnerInfo<T: Config> {
/// The account that has deployed the contract and hence is allowed to remove it.
pub struct CodeInfo<T: Config> {
/// The account that has uploaded the contract code and hence is allowed to remove it.
owner: AccountIdOf<T>,
/// The amount of balance that was deposited by the owner in order to deploy it.
/// The amount of balance that was deposited by the owner in order to store it on-chain.
#[codec(compact)]
deposit: BalanceOf<T>,
/// The number of contracts that use this as their code.
/// The number of instantiated contracts that use this as their code.
#[codec(compact)]
refcount: u64,
/// Marks if the code might contain non-deterministic features and is therefore never allowed
/// to be run on-chain. Specifically, such a code can never be instantiated into a contract
/// and can just be used through a delegate call.
determinism: Determinism,
}
/// Defines the required determinism level of a wasm blob when either running or uploading code.
@@ -149,26 +130,42 @@ impl ExportedFunction {
}
}
impl<T: Config> PrefabWasmModule<T> {
/// Create the module by checking and instrumenting `original_code`.
/// Cost of code loading from storage.
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
#[derive(Clone, Copy)]
struct CodeLoadToken(u32);
impl<T: Config> Token<T> for CodeLoadToken {
fn weight(&self) -> Weight {
// When loading the contract, we already covered the general costs of
// calling the storage but still need to account for the actual size of the
// contract code. This is why we subtract `T::*::(0)`. We need to do this at this
// point because when charging the general weight for calling the contract we don't know the
// size of the contract.
T::WeightInfo::call_with_code_per_byte(self.0)
.saturating_sub(T::WeightInfo::call_with_code_per_byte(0))
}
}
impl<T: Config> WasmBlob<T> {
/// Create the module by checking the `code`.
///
/// This does **not** store the module. For this one need to either call [`Self::store`]
/// or [`<Self as Executable>::execute`][`Executable::execute`].
pub fn from_code(
original_code: Vec<u8>,
code: Vec<u8>,
schedule: &Schedule<T>,
owner: AccountIdOf<T>,
determinism: Determinism,
try_instantiate: TryInstantiate,
) -> Result<Self, (DispatchError, &'static str)> {
let module = prepare::prepare::<runtime::Env, T>(
original_code.try_into().map_err(|_| (<Error<T>>::CodeTooLarge.into(), ""))?,
prepare::prepare::<runtime::Env, T>(
code.try_into().map_err(|_| (<Error<T>>::CodeTooLarge.into(), ""))?,
schedule,
owner,
determinism,
try_instantiate,
)?;
Ok(module)
)
}
/// Store the code without instantiating it.
@@ -176,27 +173,25 @@ impl<T: Config> PrefabWasmModule<T> {
/// Otherwise the code is stored when [`<Self as Executable>::execute`][`Executable::execute`]
/// is called.
pub fn store(self) -> DispatchResult {
code_cache::store(self, false)
Self::store_code(self, false)
}
/// Remove the code from storage and refund the deposit to its owner.
///
/// Applies all necessary checks before removing the code.
pub fn remove(origin: &T::AccountId, code_hash: CodeHash<T>) -> DispatchResult {
code_cache::try_remove::<T>(origin, code_hash)
Self::try_remove_code(origin, code_hash)
}
/// Returns whether there is a deposit to be paid for this module.
///
/// Returns `0` if the module is already in storage and hence no deposit will
/// be charged when storing it.
pub fn open_deposit(&self) -> BalanceOf<T> {
if <OwnerInfoOf<T>>::contains_key(&self.code_hash) {
/// be charged for storing it.
pub fn open_deposit(&self, code_info: &CodeInfo<T>) -> BalanceOf<T> {
if <CodeInfoOf<T>>::contains_key(self.code_hash()) {
0u32.into()
} else {
// Only already in-storage contracts have their `owner_info` set to `None`.
// Therefore it is correct to return `0` in this case.
self.owner_info.as_ref().map(|i| i.deposit).unwrap_or_default()
code_info.deposit
}
}
@@ -204,14 +199,14 @@ impl<T: Config> PrefabWasmModule<T> {
///
/// This is either used for later executing a contract or for validation of a contract.
/// When validating we pass `()` as `host_state`. Please note that such a dummy instance must
/// **never** be called/executed since it will panic the executor.
/// **never** be called/executed, since it will panic the executor.
pub fn instantiate<E, H>(
code: &[u8],
host_state: H,
memory: (u32, u32),
schedule: &Schedule<T>,
stack_limits: StackLimits,
allow_deprecated: AllowDeprecatedInterface,
) -> Result<(Store<H>, Memory, Instance), wasmi::Error>
) -> Result<(Store<H>, Memory, Instance), &'static str>
where
E: Environment<H>,
{
@@ -221,9 +216,12 @@ impl<T: Config> PrefabWasmModule<T> {
.wasm_multi_value(false)
.wasm_mutable_global(false)
.wasm_sign_extension(false)
.wasm_saturating_float_to_int(false);
.wasm_saturating_float_to_int(false)
.consume_fuel(true)
.fuel_consumption_mode(FuelConsumptionMode::Eager);
let engine = Engine::new(&config);
let module = Module::new(&engine, code)?;
let module = Module::new(&engine, code.clone()).map_err(|_| "can't decode Wasm module")?;
let mut store = Store::new(&engine, host_state);
let mut linker = Linker::new(&engine);
E::define(
@@ -235,55 +233,187 @@ impl<T: Config> PrefabWasmModule<T> {
AllowUnstableInterface::No
},
allow_deprecated,
)?;
let memory = Memory::new(&mut store, MemoryType::new(memory.0, Some(memory.1))?).expect(
"The limits defined in our `Schedule` limit the amount of memory well below u32::MAX; qed",
);
)
.map_err(|_| "can't define host functions to Linker")?;
// Query wasmi for memory limits specified in the module's import entry.
let memory_limits = Self::get_memory_limits(module.imports(), schedule)?;
// Here we allocate this memory in the _store_. It allocates _inital_ value, but allows it
// to grow up to maximum number of memory pages, if neccesary.
let qed = "We checked the limits versus our Schedule,
which specifies the max amount of memory pages
well below u16::MAX; qed";
let memory = Memory::new(
&mut store,
MemoryType::new(memory_limits.0, Some(memory_limits.1)).expect(qed),
)
.expect(qed);
linker
.define("env", "memory", memory)
.expect("We just created the linker. It has no define with this name attached; qed");
.expect("We just created the Linker. It has no definitions with this name; qed");
let instance = linker.instantiate(&mut store, &module)?.ensure_no_start(&mut store)?;
let instance = linker
.instantiate(&mut store, &module)
.map_err(|_| "can't instantiate module with provided definitions")?
.ensure_no_start(&mut store)
.map_err(|_| "start function is forbidden but found in the module")?;
Ok((store, memory, instance))
}
/// Query wasmi for memory limits specified for the import in Wasm module.
fn get_memory_limits(
imports: wasmi::ModuleImportsIter,
schedule: &Schedule<T>,
) -> Result<(u32, u32), &'static str> {
let mut mem_type = None;
for import in imports {
match *import.ty() {
ExternType::Memory(mt) => {
if import.module() != IMPORT_MODULE_MEMORY {
return Err("Invalid module for imported memory")
}
if import.name() != "memory" {
return Err("Memory import must have the field name 'memory'")
}
mem_type = Some(mt);
break
},
_ => continue,
}
}
// We don't need to check here if module memory limits satisfy the schedule,
// as this was already done during the code uploading.
// If none memory imported then set its limits to (0,0).
// Any access to it will then lead to out of bounds trap.
let (initial, maximum) = mem_type.map_or(Default::default(), |mt| {
(
mt.initial_pages().to_bytes().unwrap_or(0).saturating_div(BYTES_PER_PAGE) as u32,
mt.maximum_pages().map_or(schedule.limits.memory_pages, |p| {
p.to_bytes().unwrap_or(0).saturating_div(BYTES_PER_PAGE) as u32
}),
)
});
if initial > maximum {
return Err(
"Requested initial number of memory pages should not exceed the requested maximum",
)
}
if maximum > schedule.limits.memory_pages {
return Err("Maximum number of memory pages should not exceed the maximum configured in the Schedule.")
}
Ok((initial, maximum))
}
/// Getter method for the code_info.
pub fn code_info(&self) -> &CodeInfo<T> {
&self.code_info
}
/// Put the module blob into storage.
///
/// Increments the reference count of the in-storage `WasmBlob`, if it already exists in
/// storage.
fn store_code(mut module: Self, instantiated: bool) -> DispatchResult {
let code_hash = &module.code_hash().clone();
<CodeInfoOf<T>>::mutate(code_hash, |stored_code_info| {
match stored_code_info {
// Instantiate existing contract.
Some(stored_code_info) if instantiated => {
stored_code_info.refcount = stored_code_info.refcount.checked_add(1).expect(
"
refcount is 64bit. Generating this overflow would require to store
_at least_ 18 exabyte of data assuming that a contract consumes only
one byte of data. Any node would run out of storage space before hitting
this overflow;
qed
",
);
Ok(())
},
// Contract code is already stored in storage. Nothing to be done here.
Some(_) => Ok(()),
// Upload a new contract code.
//
// We need to store the code and its code_info, and collect the deposit.
None => {
// This `None` case happens only in freshly uploaded modules. This means that
// the `owner` is always the origin of the current transaction.
T::Currency::reserve(&module.code_info.owner, module.code_info.deposit)
.map_err(|_| <Error<T>>::StorageDepositNotEnoughFunds)?;
module.code_info.refcount = if instantiated { 1 } else { 0 };
<PristineCode<T>>::insert(code_hash, module.code);
*stored_code_info = Some(module.code_info);
<Pallet<T>>::deposit_event(
vec![*code_hash],
Event::CodeStored { code_hash: *code_hash },
);
Ok(())
},
}
})
}
/// Try to remove code together with all associated information.
fn try_remove_code(origin: &T::AccountId, code_hash: CodeHash<T>) -> DispatchResult {
<CodeInfoOf<T>>::try_mutate_exists(&code_hash, |existing| {
if let Some(code_info) = existing {
ensure!(code_info.refcount == 0, <Error<T>>::CodeInUse);
ensure!(&code_info.owner == origin, BadOrigin);
T::Currency::unreserve(&code_info.owner, code_info.deposit);
*existing = None;
<PristineCode<T>>::remove(&code_hash);
<Pallet<T>>::deposit_event(vec![code_hash], Event::CodeRemoved { code_hash });
Ok(())
} else {
Err(<Error<T>>::CodeNotFound.into())
}
})
}
/// Load code with the given code hash.
fn load_code(
code_hash: CodeHash<T>,
gas_meter: &mut GasMeter<T>,
) -> Result<CodeVec<T>, DispatchError> {
let max_code_len = T::MaxCodeLen::get();
let charged = gas_meter.charge(CodeLoadToken(max_code_len))?;
let code = <PristineCode<T>>::get(code_hash).ok_or(Error::<T>::CodeNotFound)?;
let code_len = code.len() as u32;
gas_meter.adjust_gas(charged, CodeLoadToken(code_len));
Ok(code)
}
/// See [`Self::from_code_unchecked`].
#[cfg(feature = "runtime-benchmarks")]
pub fn store_code_unchecked(
original_code: Vec<u8>,
code: Vec<u8>,
schedule: &Schedule<T>,
owner: T::AccountId,
) -> DispatchResult {
let executable = Self::from_code_unchecked(original_code, schedule, owner)?;
code_cache::store(executable, false)
let executable = Self::from_code_unchecked(code, schedule, owner)?;
Self::store_code(executable, false)
}
/// Decrement instruction_weights_version by 1. Panics if it is already 0.
#[cfg(test)]
pub fn decrement_version(&mut self) {
self.instruction_weights_version = self.instruction_weights_version.checked_sub(1).unwrap();
}
/// Create the module without checking nor instrumenting the passed code.
/// Create the module without checking the passed code.
///
/// # Note
///
/// This is useful for benchmarking where we don't want instrumentation to skew
/// This is useful for benchmarking where we don't want validation of the module to skew
/// our results. This also does not collect any deposit from the `owner`. Also useful
/// during testing when we want to deploy codes that do not pass the instantiation checks.
#[cfg(any(test, feature = "runtime-benchmarks"))]
fn from_code_unchecked(
original_code: Vec<u8>,
code: Vec<u8>,
schedule: &Schedule<T>,
owner: T::AccountId,
) -> Result<Self, DispatchError> {
prepare::benchmarking::prepare(original_code, schedule, owner)
.map_err::<DispatchError, _>(Into::into)
prepare::benchmarking::prepare(code, schedule, owner)
}
}
impl<T: Config> OwnerInfo<T> {
impl<T: Config> CodeInfo<T> {
/// Return the refcount of the module.
#[cfg(test)]
pub fn refcount(&self) -> u64 {
@@ -291,21 +421,38 @@ impl<T: Config> OwnerInfo<T> {
}
}
impl<T: Config> Executable<T> for PrefabWasmModule<T> {
impl<T: Config> Executable<T> for WasmBlob<T> {
fn from_storage(
code_hash: CodeHash<T>,
schedule: &Schedule<T>,
gas_meter: &mut GasMeter<T>,
) -> Result<Self, DispatchError> {
code_cache::load(code_hash, schedule, gas_meter)
let code = Self::load_code(code_hash, gas_meter)?;
// We store `code_info` at the same time as contract code,
// therefore this query shouldn't really fail.
// We consider its failure equal to `CodeNotFound`, as contract code without
// `code_info` is unusable in this pallet.
let code_info = <CodeInfoOf<T>>::get(code_hash).ok_or(Error::<T>::CodeNotFound)?;
Ok(Self { code, code_info, code_hash })
}
fn add_user(code_hash: CodeHash<T>) -> Result<(), DispatchError> {
code_cache::increment_refcount::<T>(code_hash)
fn increment_refcount(code_hash: CodeHash<T>) -> Result<(), DispatchError> {
<CodeInfoOf<T>>::mutate(code_hash, |existing| -> Result<(), DispatchError> {
if let Some(info) = existing {
info.refcount = info.refcount.saturating_add(1);
Ok(())
} else {
Err(Error::<T>::CodeNotFound.into())
}
})
}
fn remove_user(code_hash: CodeHash<T>) {
code_cache::decrement_refcount::<T>(code_hash)
fn decrement_refcount(code_hash: CodeHash<T>) {
<CodeInfoOf<T>>::mutate(code_hash, |existing| {
if let Some(info) = existing {
info.refcount = info.refcount.saturating_sub(1);
}
});
}
fn execute<E: Ext<T = T>>(
@@ -314,23 +461,40 @@ impl<T: Config> Executable<T> for PrefabWasmModule<T> {
function: &ExportedFunction,
input_data: Vec<u8>,
) -> ExecResult {
let code = self.code.as_slice();
// Instantiate the Wasm module to the engine.
let runtime = Runtime::new(ext, input_data);
let schedule = <T>::Schedule::get();
let (mut store, memory, instance) = Self::instantiate::<crate::wasm::runtime::Env, _>(
self.code.as_slice(),
code,
runtime,
(self.initial, self.maximum),
&schedule,
StackLimits::default(),
match function {
ExportedFunction::Constructor => AllowDeprecatedInterface::No,
ExportedFunction::Call => AllowDeprecatedInterface::Yes,
ExportedFunction::Constructor => AllowDeprecatedInterface::No,
},
)
.map_err(|msg| {
log::debug!(target: LOG_TARGET, "failed to instantiate code: {}", msg);
log::debug!(target: LOG_TARGET, "failed to instantiate code to wasmi: {}", msg);
Error::<T>::CodeRejected
})?;
store.data_mut().set_memory(memory);
// Set fuel limit for the wasmi execution.
// We normalize it by the base instruction weight, as its cost in wasmi engine is `1`.
let fuel_limit = store
.data_mut()
.ext()
.gas_meter_mut()
.gas_left()
.ref_time()
.checked_div(T::Schedule::get().instruction_weights.base as u64)
.ok_or(Error::<T>::InvalidSchedule)?;
store
.add_fuel(fuel_limit)
.expect("We've set up engine to fuel consuming mode; qed");
let exported_func = instance
.get_export(&store, function.identifier())
.and_then(|export| export.into_func())
@@ -341,10 +505,14 @@ impl<T: Config> Executable<T> for PrefabWasmModule<T> {
// We store before executing so that the code hash is available in the constructor.
if let &ExportedFunction::Constructor = function {
code_cache::store(self, true)?;
Self::store_code(self, true)?;
}
let result = exported_func.call(&mut store, &[], &mut []);
let engine_consumed_total = store.fuel_consumed().expect("Fuel metering is enabled; qed");
// Sync this frame's gas meter with the engine's one.
let gas_meter = store.data_mut().ext().gas_meter_mut();
gas_meter.charge_fuel(engine_consumed_total)?;
store.into_data().to_execution_result(result)
}
@@ -358,7 +526,7 @@ impl<T: Config> Executable<T> for PrefabWasmModule<T> {
}
fn is_deterministic(&self) -> bool {
matches!(self.determinism, Determinism::Enforced)
matches!(self.code_info.determinism, Determinism::Enforced)
}
}
@@ -603,7 +771,10 @@ mod tests {
fn schedule(&self) -> &Schedule<Self::T> {
&self.schedule
}
fn gas_meter(&mut self) -> &mut GasMeter<Self::T> {
fn gas_meter(&self) -> &GasMeter<Self::T> {
&self.gas_meter
}
fn gas_meter_mut(&mut self) -> &mut GasMeter<Self::T> {
&mut self.gas_meter
}
fn append_debug_buffer(&mut self, msg: &str) -> bool {
@@ -660,13 +831,16 @@ mod tests {
type RuntimeConfig = <MockExt as Ext>::T;
RuntimeConfig::set_unstable_interface(unstable_interface);
let wasm = wat::parse_str(wat).unwrap();
let schedule = crate::Schedule::default();
let executable = if skip_checks {
PrefabWasmModule::<RuntimeConfig>::from_code_unchecked(wasm, &schedule, ALICE)?
} else {
PrefabWasmModule::<RuntimeConfig>::from_code(
WasmBlob::<RuntimeConfig>::from_code_unchecked(
wasm,
&schedule,
ext.borrow_mut().schedule(),
ALICE,
)?
} else {
WasmBlob::<RuntimeConfig>::from_code(
wasm,
ext.borrow_mut().schedule(),
ALICE,
Determinism::Enforced,
TryInstantiate::Instantiate,
@@ -3161,25 +3335,6 @@ mod tests {
execute(CODE, vec![], &mut mock_ext).unwrap();
}
/// Code with deprecated functions cannot be uploaded or instantiated. However, we
/// need to make sure that it still can be re-instrumented.
#[test]
fn can_reinstrument_deprecated() {
const CODE_RANDOM: &str = r#"
(module
(import "seal0" "random" (func $seal_random (param i32 i32 i32 i32)))
(func (export "call"))
(func (export "deploy"))
)
"#;
let wasm = wat::parse_str(CODE_RANDOM).unwrap();
let schedule = crate::Schedule::<Test>::default();
#[cfg(not(feature = "runtime-benchmarks"))]
assert_err!(execute(CODE_RANDOM, vec![], MockExt::default()), <Error<Test>>::CodeRejected);
self::prepare::reinstrument::<runtime::Env, Test>(&wasm, &schedule, Determinism::Enforced)
.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.
+96 -239
View File
@@ -22,17 +22,15 @@
use crate::{
chain_extension::ChainExtension,
storage::meter::Diff,
wasm::{
runtime::AllowDeprecatedInterface, Determinism, Environment, OwnerInfo, PrefabWasmModule,
},
wasm::{runtime::AllowDeprecatedInterface, CodeInfo, Determinism, Environment, WasmBlob},
AccountIdOf, CodeVec, Config, Error, Schedule, LOG_TARGET,
};
use codec::{Encode, MaxEncodedLen};
use codec::MaxEncodedLen;
use sp_runtime::{traits::Hash, DispatchError};
use sp_std::prelude::*;
use wasm_instrument::{
gas_metering,
parity_wasm::elements::{self, External, Internal, MemoryType, Type, ValueType},
#[cfg(any(test, feature = "runtime-benchmarks"))]
use sp_std::prelude::Vec;
use wasm_instrument::parity_wasm::elements::{
self, External, Internal, MemoryType, Type, ValueType,
};
use wasmi::StackLimits;
use wasmparser::{Validator, WasmFeatures};
@@ -56,32 +54,20 @@ pub enum TryInstantiate {
Skip,
}
/// The reason why a contract is instrumented.
enum InstrumentReason {
/// A new code is uploaded.
New,
/// Existing code is re-instrumented.
Reinstrument,
}
/// The inner deserialized module is valid (this is guaranteed by `new` method).
pub struct ContractModule(elements::Module);
struct ContractModule<'a, T: Config> {
/// A deserialized module. The module is valid (this is Guaranteed by `new` method).
module: elements::Module,
schedule: &'a Schedule<T>,
}
impl<'a, T: Config> ContractModule<'a, T> {
impl ContractModule {
/// Creates a new instance of `ContractModule`.
///
/// Returns `Err` if the `original_code` couldn't be decoded or
/// Returns `Err` if the `code` couldn't be decoded or
/// if it contains an invalid module.
fn new(original_code: &[u8], schedule: &'a Schedule<T>) -> Result<Self, &'static str> {
let module =
elements::deserialize_buffer(original_code).map_err(|_| "Can't decode wasm code")?;
pub fn new(code: &[u8]) -> Result<Self, &'static str> {
let module = elements::deserialize_buffer(code).map_err(|_| "Can't decode Wasm code")?;
// Return a `ContractModule` instance with
// __valid__ module.
Ok(ContractModule { module, schedule })
Ok(ContractModule(module))
}
/// Ensures that module doesn't declare internal memories.
@@ -90,7 +76,7 @@ impl<'a, T: Config> ContractModule<'a, T> {
/// Memory section contains declarations of internal linear memories, so if we find one
/// we reject such a module.
fn ensure_no_internal_memory(&self) -> Result<(), &'static str> {
if self.module.memory_section().map_or(false, |ms| ms.entries().len() > 0) {
if self.0.memory_section().map_or(false, |ms| ms.entries().len() > 0) {
return Err("module declares internal memory")
}
Ok(())
@@ -98,7 +84,7 @@ impl<'a, T: Config> ContractModule<'a, T> {
/// Ensures that tables declared in the module are not too big.
fn ensure_table_size_limit(&self, limit: u32) -> Result<(), &'static str> {
if let Some(table_section) = self.module.table_section() {
if let Some(table_section) = self.0.table_section() {
// In Wasm MVP spec, there may be at most one table declared. Double check this
// explicitly just in case the Wasm version changes.
if table_section.entries().len() > 1 {
@@ -117,7 +103,7 @@ impl<'a, T: Config> ContractModule<'a, T> {
/// Ensure that any `br_table` instruction adheres to its immediate value limit.
fn ensure_br_table_size_limit(&self, limit: u32) -> Result<(), &'static str> {
let code_section = if let Some(type_section) = self.module.code_section() {
let code_section = if let Some(type_section) = self.0.code_section() {
type_section
} else {
return Ok(())
@@ -134,7 +120,7 @@ impl<'a, T: Config> ContractModule<'a, T> {
}
fn ensure_global_variable_limit(&self, limit: u32) -> Result<(), &'static str> {
if let Some(global_section) = self.module.global_section() {
if let Some(global_section) = self.0.global_section() {
if global_section.entries().len() > limit as usize {
return Err("module declares too many globals")
}
@@ -143,7 +129,7 @@ impl<'a, T: Config> ContractModule<'a, T> {
}
fn ensure_local_variable_limit(&self, limit: u32) -> Result<(), &'static str> {
if let Some(code_section) = self.module.code_section() {
if let Some(code_section) = self.0.code_section() {
for func_body in code_section.bodies() {
let locals_count: u32 =
func_body.locals().iter().map(|val_type| val_type.count()).sum();
@@ -157,7 +143,7 @@ impl<'a, T: Config> ContractModule<'a, T> {
/// Ensure that no function exists that has more parameters than allowed.
fn ensure_parameter_limit(&self, limit: u32) -> Result<(), &'static str> {
let type_section = if let Some(type_section) = self.module.type_section() {
let type_section = if let Some(type_section) = self.0.type_section() {
type_section
} else {
return Ok(())
@@ -172,14 +158,6 @@ impl<'a, T: Config> ContractModule<'a, T> {
Ok(())
}
fn inject_gas_metering(self, determinism: Determinism) -> Result<Self, &'static str> {
let gas_rules = self.schedule.rules(determinism);
let backend = gas_metering::host_function::Injector::new("seal0", "gas");
let contract_module = gas_metering::inject(self.module, backend, &gas_rules)
.map_err(|_| "gas instrumentation failed")?;
Ok(ContractModule { module: contract_module, schedule: self.schedule })
}
/// Check that the module has required exported functions. For now
/// these are just entrypoints:
///
@@ -191,7 +169,7 @@ impl<'a, T: Config> ContractModule<'a, T> {
let mut deploy_found = false;
let mut call_found = false;
let module = &self.module;
let module = &self.0;
let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]);
let export_entries = module.export_section().map(|is| is.entries()).unwrap_or(&[]);
@@ -261,13 +239,8 @@ impl<'a, T: Config> ContractModule<'a, T> {
///
/// This makes sure that the import section looks as we expect it from a contract
/// and enforces and returns the memory type declared by the contract if any.
///
/// `import_fn_banlist`: list of function names that are disallowed to be imported
fn scan_imports(
&self,
import_fn_banlist: &[&[u8]],
) -> Result<Option<&MemoryType>, &'static str> {
let module = &self.module;
pub fn scan_imports<T: Config>(&self) -> Result<Option<&MemoryType>, &'static str> {
let module = &self.0;
let import_entries = module.import_section().map(|is| is.entries()).unwrap_or(&[]);
let mut imported_mem_type = None;
@@ -276,15 +249,11 @@ impl<'a, T: Config> ContractModule<'a, T> {
External::Table(_) => return Err("Cannot import tables"),
External::Global(_) => return Err("Cannot import globals"),
External::Function(_) => {
if !T::ChainExtension::enabled() &&
if !<T as Config>::ChainExtension::enabled() &&
import.field().as_bytes() == b"seal_call_chain_extension"
{
return Err("module uses chain extensions but chain extensions are disabled")
}
if import_fn_banlist.iter().any(|f| import.field().as_bytes() == *f) {
return Err("module imports a banned function")
}
},
External::Memory(ref memory_type) => {
if import.module() != IMPORT_MODULE_MEMORY {
@@ -301,14 +270,11 @@ impl<'a, T: Config> ContractModule<'a, T> {
},
}
}
Ok(imported_mem_type)
}
fn into_wasm_code(self) -> Result<Vec<u8>, &'static str> {
elements::serialize(self.module).map_err(|_| "error serializing instrumented module")
}
}
#[cfg(any(test, feature = "runtime-benchmarks"))]
fn get_memory_limits<T: Config>(
module: Option<&MemoryType>,
schedule: &Schedule<T>,
@@ -318,35 +284,30 @@ fn get_memory_limits<T: Config>(
let limits = memory_type.limits();
match (limits.initial(), limits.maximum()) {
(initial, Some(maximum)) if initial > maximum =>
Err("Requested initial number of pages should not exceed the requested maximum"),
Err("Requested initial number of memory pages should not exceed the requested maximum"),
(_, Some(maximum)) if maximum > schedule.limits.memory_pages =>
Err("Maximum number of pages should not exceed the configured maximum."),
Err("Maximum number of memory pages should not exceed the maximum configured in the Schedule."),
(initial, Some(maximum)) => Ok((initial, maximum)),
(_, None) => {
// Maximum number of pages should be always declared.
// This isn't a hard requirement and can be treated as a maximum set
// to configured maximum.
Err("Maximum number of pages should be always declared.")
(initial, None) => {
Ok((initial, schedule.limits.memory_pages))
},
}
} else {
// If none memory imported then just create an empty placeholder.
// Any access to it will lead to out of bounds trap.
// None memory imported in the Wasm module,
// any access to it will lead to out of bounds trap.
Ok((0, 0))
}
}
/// Check and instrument the given `original_code`.
/// Check that given `code` satisfies constraints required for the contract Wasm module.
///
/// On success it returns the instrumented versions together with its `(initial, maximum)`
/// error requirement. The memory requirement was also validated against the `schedule`.
fn instrument<E, T>(
original_code: &[u8],
/// On success it returns back the code.
fn validate<E, T>(
code: &[u8],
schedule: &Schedule<T>,
determinism: Determinism,
try_instantiate: TryInstantiate,
reason: InstrumentReason,
) -> Result<(Vec<u8>, (u32, u32)), (DispatchError, &'static str)>
) -> Result<(), (DispatchError, &'static str)>
where
E: Environment<()>,
T: Config,
@@ -377,189 +338,114 @@ where
simd: false,
memory_control: false,
})
.validate_all(original_code)
.validate_all(code)
.map_err(|err| {
log::debug!(target: LOG_TARGET, "{}", err);
(Error::<T>::CodeRejected.into(), "validation of new code failed")
(Error::<T>::CodeRejected.into(), "Validation of new code failed!")
})?;
let (code, (initial, maximum)) = (|| {
let contract_module = ContractModule::new(original_code, schedule)?;
(|| {
let contract_module = ContractModule::new(code)?;
contract_module.scan_exports()?;
contract_module.scan_imports::<T>()?;
contract_module.ensure_no_internal_memory()?;
contract_module.ensure_table_size_limit(schedule.limits.table_size)?;
contract_module.ensure_global_variable_limit(schedule.limits.globals)?;
contract_module.ensure_local_variable_limit(schedule.limits.locals)?;
contract_module.ensure_parameter_limit(schedule.limits.parameters)?;
contract_module.ensure_br_table_size_limit(schedule.limits.br_table_size)?;
// We disallow importing `gas` function here since it is treated as implementation detail.
let disallowed_imports = [b"gas".as_ref()];
let memory_limits =
get_memory_limits(contract_module.scan_imports(&disallowed_imports)?, schedule)?;
let code = contract_module.inject_gas_metering(determinism)?.into_wasm_code()?;
Ok((code, memory_limits))
// Extract memory limits from the module.
// This also checks that module's memory import satisfies the schedule.
Ok(())
})()
.map_err(|msg: &str| {
log::debug!(target: LOG_TARGET, "new code rejected: {}", msg);
log::debug!(target: LOG_TARGET, "New code rejected: {}", msg);
(Error::<T>::CodeRejected.into(), msg)
})?;
// This will make sure that the module can be actually run within wasmi:
//
// - Doesn't use any unknown imports.
// - Doesn't explode the wasmi bytecode generation.
// - It doesn't use any unknown imports.
// - It doesn't explode the wasmi bytecode generation.
if matches!(try_instantiate, TryInstantiate::Instantiate) {
// We don't actually ever run any code so we can get away with a minimal stack which
// reduces the amount of memory that needs to be zeroed.
let stack_limits = StackLimits::new(1, 1, 0).expect("initial <= max; qed");
PrefabWasmModule::<T>::instantiate::<E, _>(
WasmBlob::<T>::instantiate::<E, _>(
&code,
(),
(initial, maximum),
schedule,
stack_limits,
match reason {
InstrumentReason::New => AllowDeprecatedInterface::No,
InstrumentReason::Reinstrument => AllowDeprecatedInterface::Yes,
},
AllowDeprecatedInterface::No,
)
.map_err(|err| {
log::debug!(target: LOG_TARGET, "{}", err);
(Error::<T>::CodeRejected.into(), "new code rejected after instrumentation")
(Error::<T>::CodeRejected.into(), "New code rejected on wasmi instantiation!")
})?;
}
Ok((code, (initial, maximum)))
Ok(())
}
/// Loads the given module given in `original_code`, performs some checks on it and
/// does some preprocessing.
/// Validates the given binary `code` is a valid Wasm module satisfying following constraints:
///
/// The checks are:
/// - The module doesn't define an internal memory instance.
/// - Imported memory (if any) doesn't reserve more memory than permitted by the `schedule`.
/// - All imported functions from the external environment match defined by `env` module.
///
/// - the provided code is a valid wasm module
/// - the module doesn't define an internal memory instance
/// - imported memory (if any) doesn't reserve more memory than permitted by the `schedule`
/// - all imported functions from the external environment matches defined by `env` module
///
/// The preprocessing includes injecting code for gas metering and metering the height of stack.
/// Also constructs contract `code_info` by calculating the storage deposit.
pub fn prepare<E, T>(
original_code: CodeVec<T>,
code: CodeVec<T>,
schedule: &Schedule<T>,
owner: AccountIdOf<T>,
determinism: Determinism,
try_instantiate: TryInstantiate,
) -> Result<PrefabWasmModule<T>, (DispatchError, &'static str)>
) -> Result<WasmBlob<T>, (DispatchError, &'static str)>
where
E: Environment<()>,
T: Config,
{
let (code, (initial, maximum)) = instrument::<E, T>(
original_code.as_ref(),
schedule,
determinism,
try_instantiate,
InstrumentReason::New,
)?;
validate::<E, T>(code.as_ref(), schedule, determinism, try_instantiate)?;
let original_code_len = original_code.len();
let mut module = PrefabWasmModule {
instruction_weights_version: schedule.instruction_weights.version,
initial,
maximum,
code: code.try_into().map_err(|_| (<Error<T>>::CodeTooLarge.into(), ""))?,
code_hash: T::Hashing::hash(&original_code),
original_code: Some(original_code),
owner_info: None,
determinism,
};
// We need to add the sizes of the `#[codec(skip)]` fields which are stored in different
// storage items. This is also why we have `3` items added and not only one.
let bytes_added = module
.encoded_size()
.saturating_add(original_code_len)
.saturating_add(<OwnerInfo<T>>::max_encoded_len()) as u32;
let deposit = Diff { bytes_added, items_added: 3, ..Default::default() }
// Calculate deposit for storing contract code and `code_info` in two different storage items.
let bytes_added = code.len().saturating_add(<CodeInfo<T>>::max_encoded_len()) as u32;
let deposit = Diff { bytes_added, items_added: 2, ..Default::default() }
.update_contract::<T>(None)
.charge_or_zero();
module.owner_info = Some(OwnerInfo { owner, deposit, refcount: 0 });
let code_info = CodeInfo { owner, deposit, determinism, refcount: 0 };
let code_hash = T::Hashing::hash(&code);
Ok(module)
}
/// Same as [`prepare`] but without constructing a new module.
///
/// Used to update the code of an existing module to the newest [`Schedule`] version.
/// Stictly speaking is not necessary to check the existing code before reinstrumenting because
/// it can't change in the meantime. However, since we recently switched the validation library
/// we want to re-validate to weed out any bugs that were lurking in the old version.
pub fn reinstrument<E, T>(
original_code: &[u8],
schedule: &Schedule<T>,
determinism: Determinism,
) -> Result<Vec<u8>, DispatchError>
where
E: Environment<()>,
T: Config,
{
instrument::<E, T>(
original_code,
schedule,
determinism,
// This function was triggered by an interaction with an existing contract code
// that will try to instantiate anyways. Failing here would not help
// as the contract is already on chain.
TryInstantiate::Skip,
InstrumentReason::Reinstrument,
)
.map_err(|(err, msg)| {
log::error!(target: LOG_TARGET, "CodeRejected during reinstrument: {}", msg);
err
})
.map(|(code, _)| code)
Ok(WasmBlob { code, code_info, code_hash })
}
/// Alternate (possibly unsafe) preparation functions used only for benchmarking and testing.
///
/// For benchmarking we need to construct special contracts that might not pass our
/// sanity checks or need to skip instrumentation for correct results. We hide functions
/// allowing this behind a feature that is only set during benchmarking or testing to
/// prevent usage in production code.
/// sanity checks. We hide functions allowing this behind a feature that is only set during
/// benchmarking or testing to prevent usage in production code.
#[cfg(any(test, feature = "runtime-benchmarks"))]
pub mod benchmarking {
use super::*;
/// Prepare function that neither checks nor instruments the passed in code.
/// Prepare function that does not perform most checks on the passed in code.
pub fn prepare<T: Config>(
original_code: Vec<u8>,
code: Vec<u8>,
schedule: &Schedule<T>,
owner: AccountIdOf<T>,
) -> Result<PrefabWasmModule<T>, &'static str> {
let contract_module = ContractModule::new(&original_code, schedule)?;
let memory_limits = get_memory_limits(contract_module.scan_imports(&[])?, schedule)?;
Ok(PrefabWasmModule {
instruction_weights_version: schedule.instruction_weights.version,
initial: memory_limits.0,
maximum: memory_limits.1,
code_hash: T::Hashing::hash(&original_code),
original_code: Some(original_code.try_into().map_err(|_| "Original code too large")?),
code: contract_module
.into_wasm_code()?
.try_into()
.map_err(|_| "Instrumented code too large")?,
owner_info: Some(OwnerInfo {
owner,
// this is a helper function for benchmarking which skips deposit collection
deposit: Default::default(),
refcount: 0,
}),
) -> Result<WasmBlob<T>, DispatchError> {
let contract_module = ContractModule::new(&code)?;
let _ = get_memory_limits(contract_module.scan_imports::<T>()?, schedule)?;
let code_hash = T::Hashing::hash(&code);
let code = code.try_into().map_err(|_| <Error<T>>::CodeTooLarge)?;
let code_info = CodeInfo {
owner,
// this is a helper function for benchmarking which skips deposit collection
deposit: Default::default(),
refcount: 0,
determinism: Determinism::Enforced,
})
};
Ok(WasmBlob { code, code_info, code_hash })
}
}
@@ -574,9 +460,9 @@ mod tests {
use pallet_contracts_proc_macro::define_env;
use std::fmt;
impl fmt::Debug for PrefabWasmModule<Test> {
impl fmt::Debug for WasmBlob<Test> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "PreparedContract {{ .. }}")
write!(f, "ContractCode {{ .. }}")
}
}
@@ -654,7 +540,7 @@ mod tests {
)
(func (export "deploy"))
)"#,
Err("validation of new code failed")
Err("Validation of new code failed!")
);
mod functions {
@@ -807,20 +693,7 @@ mod tests {
(func (export "deploy"))
)
"#,
Err("validation of new code failed")
);
prepare_test!(
no_maximum,
r#"
(module
(import "env" "memory" (memory 1))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("Maximum number of pages should be always declared.")
Err("Validation of new code failed!")
);
prepare_test!(
@@ -846,7 +719,7 @@ mod tests {
(func (export "deploy"))
)
"#,
Err("Maximum number of pages should not exceed the configured maximum.")
Err("New code rejected on wasmi instantiation!")
);
prepare_test!(
@@ -873,7 +746,7 @@ mod tests {
(func (export "deploy"))
)
"#,
Err("validation of new code failed")
Err("Validation of new code failed!")
);
prepare_test!(
@@ -987,21 +860,6 @@ mod tests {
Ok(_)
);
// even though gas is defined the contract can't import it since
// it is an implementation defined.
prepare_test!(
can_not_import_gas_function,
r#"
(module
(import "seal0" "gas" (func (param i32)))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("module imports a banned function")
);
// memory is in "env" and not in "seal0"
prepare_test!(
memory_not_in_seal0,
@@ -1043,18 +901,17 @@ mod tests {
Ok(_)
);
// wrong signature
prepare_test!(
wrong_signature,
r#"
(module
(import "seal0" "gas" (func (param i64)))
(import "seal0" "input" (func (param i64)))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("module imports a banned function")
Err("New code rejected on wasmi instantiation!")
);
prepare_test!(
@@ -1067,7 +924,7 @@ mod tests {
(func (export "deploy"))
)
"#,
Err("new code rejected after instrumentation")
Err("New code rejected on wasmi instantiation!")
);
}
@@ -1164,7 +1021,7 @@ mod tests {
(func (export "deploy"))
)
"#,
Err("validation of new code failed")
Err("Validation of new code failed!")
);
prepare_test!(
@@ -1176,7 +1033,7 @@ mod tests {
(func (export "deploy"))
)
"#,
Err("validation of new code failed")
Err("Validation of new code failed!")
);
prepare_test!(
@@ -1188,7 +1045,7 @@ mod tests {
(func (export "deploy"))
)
"#,
Err("validation of new code failed")
Err("Validation of new code failed!")
);
prepare_test!(
@@ -1200,7 +1057,7 @@ mod tests {
(func (export "deploy"))
)
"#,
Err("validation of new code failed")
Err("Validation of new code failed!")
);
}
}
+29 -31
View File
@@ -67,7 +67,6 @@ pub trait Environment<HostState> {
}
/// Type of a storage key.
#[allow(dead_code)]
enum KeyType {
/// Legacy fix sized key `[u8;32]`.
Fix,
@@ -174,9 +173,6 @@ impl HostError for TrapReason {}
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
#[derive(Copy, Clone)]
pub enum RuntimeCosts {
/// Charge the gas meter with the cost of a metering block. The charged costs are
/// the supplied cost of the block plus the overhead of the metering itself.
MeteringBlock(u64),
/// Weight charged for copying data from the sandbox.
CopyFromContract(u32),
/// Weight charged for copying data to the sandbox.
@@ -277,7 +273,6 @@ impl RuntimeCosts {
fn token<T: Config>(&self, s: &HostFnWeights<T>) -> RuntimeToken {
use self::RuntimeCosts::*;
let weight = match *self {
MeteringBlock(amount) => s.gas.saturating_add(Weight::from_parts(amount, 0)),
CopyFromContract(len) => s.return_per_byte.saturating_mul(len.into()),
CopyToContract(len) => s.input_per_byte.saturating_mul(len.into()),
Caller => s.caller,
@@ -369,7 +364,7 @@ impl RuntimeCosts {
macro_rules! charge_gas {
($runtime:expr, $costs:expr) => {{
let token = $costs.token(&$runtime.ext.schedule().host_fn_weights);
$runtime.ext.gas_meter().charge(token)
$runtime.ext.gas_meter_mut().charge(token)
}};
}
@@ -485,25 +480,40 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> {
/// Converts the sandbox result and the runtime state into the execution outcome.
pub fn to_execution_result(self, sandbox_result: Result<(), wasmi::Error>) -> ExecResult {
use wasmi::core::TrapCode::OutOfFuel;
use TrapReason::*;
match sandbox_result {
// Contract returned from main function -> no data was returned.
Ok(_) => Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }),
// `OutOfGas` when host asks engine to consume more than left in the _store_.
// We should never get this case, as gas meter is being charged (and hence raises error)
// first.
Err(wasmi::Error::Store(_)) => Err(Error::<E::T>::OutOfGas.into()),
// Contract either trapped or some host function aborted the execution.
Err(wasmi::Error::Trap(trap)) => {
// If we encoded a reason then it is some abort generated by a host function.
// Otherwise the trap came from the contract.
let reason: TrapReason = trap.downcast().ok_or(Error::<E::T>::ContractTrapped)?;
match reason {
Return(ReturnData { flags, data }) => {
let flags =
ReturnFlags::from_bits(flags).ok_or(Error::<E::T>::InvalidCallFlags)?;
Ok(ExecReturnValue { flags, data })
},
Termination =>
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }),
SupervisorError(error) => return Err(error.into()),
if let Some(OutOfFuel) = trap.trap_code() {
// `OutOfGas` during engine execution.
return Err(Error::<E::T>::OutOfGas.into())
}
// If we encoded a reason then it is some abort generated by a host function.
if let Some(reason) = &trap.downcast_ref::<TrapReason>() {
match &reason {
Return(ReturnData { flags, data }) => {
let flags = ReturnFlags::from_bits(*flags)
.ok_or(Error::<E::T>::InvalidCallFlags)?;
return Ok(ExecReturnValue { flags, data: data.to_vec() })
},
Termination =>
return Ok(ExecReturnValue {
flags: ReturnFlags::empty(),
data: Vec::new(),
}),
SupervisorError(error) => return Err((*error).into()),
}
}
// Otherwise the trap came from the contract itself.
Err(Error::<E::T>::ContractTrapped.into())
},
// Any other error is returned only if instantiation or linking failed (i.e.
// wasm binary tried to import a function that is not provided by the host).
@@ -536,7 +546,7 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> {
/// refunded to match the actual amount.
pub fn adjust_gas(&mut self, charged: ChargedAmount, actual_costs: RuntimeCosts) {
let token = actual_costs.token(&self.ext.schedule().host_fn_weights);
self.ext.gas_meter().adjust_gas(charged, token);
self.ext.gas_meter_mut().adjust_gas(charged, token);
}
/// Read designated chunk from the sandbox memory.
@@ -1004,18 +1014,6 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> {
// for every function.
#[define_env(doc)]
pub mod env {
/// Account for used gas. Traps if gas used is greater than gas limit.
///
/// 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> {
ctx.charge_gas(RuntimeCosts::MeteringBlock(amount))?;
Ok(())
}
/// Set the value at the given key in the contract storage.
///
/// Equivalent to the newer [`seal1`][`super::api_doc::Version1::set_storage`] version with the
+1500 -2427
View File
File diff suppressed because it is too large Load Diff