contracts: switch to wasmi gas metering (#14084)

* upgrade to wasmi 0.29

* prepare cleanup

* sync ref_time w engine from the stack frame

* proc_macro: sync gas in host funcs

save: compiles, only gas pushing left to macro

WIP proc macro

proc macro: done

* clean benchmarks & schedule: w_base = w_i64const

* scale gas values btw engine and gas meter

* (re)instrumentation & code_cache removed

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

save

* address review comments

* move from CodeStorage&PrefabWasmModule to PristineCode&WasmBlob

* refactor: no reftime_limit&schedule passes, no CodeStorage

* bugs fixing

* fix tests: expected deposit amount

* fix prepare::tests

* update tests and fix bugs

tests::run_out_of_gas_engine, need 2 more

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

gas_syncs_no_overcharge bug fixed, test passes!

cleaned out debug prints

second bug is not a bug

disabled_chain_extension test fix (err msg)

tests run_out_of_fuel_host, chain_extension pass

all tests pass

* update docs

* bump wasmi 0.30.0

* benchmarks updated, tests pass

* refactoring

* s/OwnerInfo/CodeInfo/g;

* migration: draft, compiles

* migration: draft, runs

* migration: draft, runs (fixing)

* deposits repaid non pro rata

* deposits repaid pro rata

* better try-runtime output

* even better try-runtime output

* benchmark migration

* fix merge leftover

* add forgotten fixtures, fix docs

* address review comments

* ci fixes

* cleanup

* benchmarks::prepare to return DispatchError

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

* store memory limits to CodeInfo

* ci: roll back weights

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

* drive-by: update Readme and pallet rustdoc

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

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

* use wasmi 0.29

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

* use wasmi 0.30 again

* query memory limits from wasmi

* better migration types

* ci: pull weights from master

* refactoring

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

* addressing review comments

* refactor

* address review comments

* optimize migration

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

* another review round comments addressed

* ci fix one

* clippy fix

* ci fix two

---------

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