mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 07:01:05 +00:00
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:
Generated
+10
-2
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"))
|
||||
)
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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!")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Generated
+1500
-2427
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user