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
@@ -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