mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-18 00:11:01 +00:00
contracts: switch to wasmi gas metering (#14084)
* upgrade to wasmi 0.29 * prepare cleanup * sync ref_time w engine from the stack frame * proc_macro: sync gas in host funcs save: compiles, only gas pushing left to macro WIP proc macro proc macro: done * clean benchmarks & schedule: w_base = w_i64const * scale gas values btw engine and gas meter * (re)instrumentation & code_cache removed * remove gas() host fn, continue clean-up save * address review comments * move from CodeStorage&PrefabWasmModule to PristineCode&WasmBlob * refactor: no reftime_limit&schedule passes, no CodeStorage * bugs fixing * fix tests: expected deposit amount * fix prepare::tests * update tests and fix bugs tests::run_out_of_gas_engine, need 2 more save: 2 bugs with gas syncs: 1 of 2 tests done gas_syncs_no_overcharge bug fixed, test passes! cleaned out debug prints second bug is not a bug disabled_chain_extension test fix (err msg) tests run_out_of_fuel_host, chain_extension pass all tests pass * update docs * bump wasmi 0.30.0 * benchmarks updated, tests pass * refactoring * s/OwnerInfo/CodeInfo/g; * migration: draft, compiles * migration: draft, runs * migration: draft, runs (fixing) * deposits repaid non pro rata * deposits repaid pro rata * better try-runtime output * even better try-runtime output * benchmark migration * fix merge leftover * add forgotten fixtures, fix docs * address review comments * ci fixes * cleanup * benchmarks::prepare to return DispatchError * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts * store memory limits to CodeInfo * ci: roll back weights * ".git/.scripts/commands/bench-vm/bench-vm.sh" pallet dev pallet_contracts * drive-by: update Readme and pallet rustdoc * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts * use wasmi 0.29 * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts * use wasmi 0.30 again * query memory limits from wasmi * better migration types * ci: pull weights from master * refactoring * ".git/.scripts/commands/bench-vm/bench-vm.sh" pallet dev pallet_contracts * addressing review comments * refactor * address review comments * optimize migration * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts * another review round comments addressed * ci fix one * clippy fix * ci fix two --------- Co-authored-by: command-bot <>
This commit is contained in:
@@ -1,239 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! A module that implements instrumented code cache.
|
||||
//!
|
||||
//! - In order to run contract code we need to instrument it with gas metering.
|
||||
//! To do that we need to provide the schedule which will supply exact gas costs values.
|
||||
//! We cache this code in the storage saving the schedule version.
|
||||
//! - Before running contract code we check if the cached code has the schedule version that
|
||||
//! is equal to the current saved schedule.
|
||||
//! If it is equal then run the code, if it isn't reinstrument with the current schedule.
|
||||
//! - When we update the schedule we want it to have strictly greater version than the current saved
|
||||
//! one:
|
||||
//! this guarantees that every instrumented contract code in cache cannot have the version equal to
|
||||
//! the current one. Thus, before executing a contract it should be reinstrument with new schedule.
|
||||
|
||||
use crate::{
|
||||
gas::{GasMeter, Token},
|
||||
wasm::{prepare, PrefabWasmModule},
|
||||
weights::WeightInfo,
|
||||
CodeHash, CodeStorage, Config, Error, Event, OwnerInfoOf, Pallet, PristineCode, Schedule,
|
||||
Weight,
|
||||
};
|
||||
use frame_support::{
|
||||
dispatch::{DispatchError, DispatchResult},
|
||||
ensure,
|
||||
traits::{Get, ReservableCurrency},
|
||||
WeakBoundedVec,
|
||||
};
|
||||
use sp_runtime::traits::BadOrigin;
|
||||
use sp_std::vec;
|
||||
|
||||
/// Put the instrumented module in storage.
|
||||
///
|
||||
/// Increments the refcount of the in-storage `prefab_module` if it already exists in storage
|
||||
/// under the specified `code_hash`.
|
||||
pub fn store<T: Config>(mut module: PrefabWasmModule<T>, instantiated: bool) -> DispatchResult {
|
||||
let code_hash = sp_std::mem::take(&mut module.code_hash);
|
||||
<OwnerInfoOf<T>>::mutate(&code_hash, |owner_info| {
|
||||
match owner_info {
|
||||
// Instantiate existing contract.
|
||||
//
|
||||
// No need to update the `CodeStorage` as any re-instrumentation eagerly saves
|
||||
// the re-instrumented code.
|
||||
Some(owner_info) if instantiated => {
|
||||
owner_info.refcount = owner_info.refcount.checked_add(1).expect(
|
||||
"
|
||||
refcount is 64bit. Generating this overflow would require to store
|
||||
_at least_ 18 exabyte of data assuming that a contract consumes only
|
||||
one byte of data. Any node would run out of storage space before hitting
|
||||
this overflow.
|
||||
qed
|
||||
",
|
||||
);
|
||||
Ok(())
|
||||
},
|
||||
// Re-upload existing contract without executing it.
|
||||
//
|
||||
// We are careful here to just overwrite the code to not include it into the PoV.
|
||||
// We do this because the uploaded code was instrumented with the latest schedule
|
||||
// and hence we persist those changes. Otherwise the next execution will pay again
|
||||
// for the instrumentation.
|
||||
Some(_) => {
|
||||
<CodeStorage<T>>::insert(&code_hash, module);
|
||||
Ok(())
|
||||
},
|
||||
// Upload a new contract.
|
||||
//
|
||||
// We need to write all data structures and collect the deposit.
|
||||
None => {
|
||||
let orig_code = module.original_code.take().expect(
|
||||
"
|
||||
If an executable isn't in storage it was uploaded.
|
||||
If it was uploaded the original code must exist. qed
|
||||
",
|
||||
);
|
||||
let mut new_owner_info = module.owner_info.take().expect(
|
||||
"If an executable isn't in storage it was uploaded.
|
||||
If it was uploaded the owner info was generated and attached. qed
|
||||
",
|
||||
);
|
||||
// This `None` case happens only in freshly uploaded modules. This means that
|
||||
// the `owner` is always the origin of the current transaction.
|
||||
T::Currency::reserve(&new_owner_info.owner, new_owner_info.deposit)
|
||||
.map_err(|_| <Error<T>>::StorageDepositNotEnoughFunds)?;
|
||||
new_owner_info.refcount = if instantiated { 1 } else { 0 };
|
||||
<PristineCode<T>>::insert(&code_hash, orig_code);
|
||||
<CodeStorage<T>>::insert(&code_hash, module);
|
||||
*owner_info = Some(new_owner_info);
|
||||
<Pallet<T>>::deposit_event(vec![code_hash], Event::CodeStored { code_hash });
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Decrement the refcount of a code in-storage by one.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// A contract whose refcount dropped to zero isn't automatically removed. A `remove_code`
|
||||
/// transaction must be submitted by the original uploader to do so.
|
||||
pub fn decrement_refcount<T: Config>(code_hash: CodeHash<T>) {
|
||||
<OwnerInfoOf<T>>::mutate(code_hash, |existing| {
|
||||
if let Some(info) = existing {
|
||||
info.refcount = info.refcount.saturating_sub(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Increment the refcount of a code in-storage by one.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// [`Error::CodeNotFound`] is returned if the specified `code_hash` does not exist.
|
||||
pub fn increment_refcount<T: Config>(code_hash: CodeHash<T>) -> Result<(), DispatchError> {
|
||||
<OwnerInfoOf<T>>::mutate(code_hash, |existing| -> Result<(), DispatchError> {
|
||||
if let Some(info) = existing {
|
||||
info.refcount = info.refcount.saturating_add(1);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::<T>::CodeNotFound.into())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Try to remove code together with all associated information.
|
||||
pub fn try_remove<T: Config>(origin: &T::AccountId, code_hash: CodeHash<T>) -> DispatchResult {
|
||||
<OwnerInfoOf<T>>::try_mutate_exists(&code_hash, |existing| {
|
||||
if let Some(owner_info) = existing {
|
||||
ensure!(owner_info.refcount == 0, <Error<T>>::CodeInUse);
|
||||
ensure!(&owner_info.owner == origin, BadOrigin);
|
||||
T::Currency::unreserve(&owner_info.owner, owner_info.deposit);
|
||||
*existing = None;
|
||||
<PristineCode<T>>::remove(&code_hash);
|
||||
<CodeStorage<T>>::remove(&code_hash);
|
||||
<Pallet<T>>::deposit_event(vec![code_hash], Event::CodeRemoved { code_hash });
|
||||
Ok(())
|
||||
} else {
|
||||
Err(<Error<T>>::CodeNotFound.into())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Load code with the given code hash.
|
||||
///
|
||||
/// If the module was instrumented with a lower version of schedule than
|
||||
/// the current one given as an argument, then this function will perform
|
||||
/// re-instrumentation and update the cache in the storage.
|
||||
pub fn load<T: Config>(
|
||||
code_hash: CodeHash<T>,
|
||||
schedule: &Schedule<T>,
|
||||
gas_meter: &mut GasMeter<T>,
|
||||
) -> Result<PrefabWasmModule<T>, DispatchError> {
|
||||
let max_code_len = T::MaxCodeLen::get();
|
||||
let charged = gas_meter.charge(CodeToken::Load(max_code_len))?;
|
||||
|
||||
let mut prefab_module = <CodeStorage<T>>::get(code_hash).ok_or(Error::<T>::CodeNotFound)?;
|
||||
let instrumented_code_len = prefab_module.code.len() as u32;
|
||||
gas_meter.adjust_gas(charged, CodeToken::Load(instrumented_code_len));
|
||||
prefab_module.code_hash = code_hash;
|
||||
|
||||
if prefab_module.instruction_weights_version < schedule.instruction_weights.version {
|
||||
// The instruction weights have changed.
|
||||
// We need to re-instrument the code with the new instruction weights.
|
||||
let charged = gas_meter.charge(CodeToken::Reinstrument(instrumented_code_len))?;
|
||||
let orig_code_len = reinstrument(&mut prefab_module, schedule)?;
|
||||
gas_meter.adjust_gas(charged, CodeToken::Reinstrument(orig_code_len));
|
||||
}
|
||||
|
||||
Ok(prefab_module)
|
||||
}
|
||||
|
||||
/// Instruments the passed prefab wasm module with the supplied schedule.
|
||||
///
|
||||
/// Returns the size in bytes of the uninstrumented code.
|
||||
pub fn reinstrument<T: Config>(
|
||||
prefab_module: &mut PrefabWasmModule<T>,
|
||||
schedule: &Schedule<T>,
|
||||
) -> Result<u32, DispatchError> {
|
||||
let original_code =
|
||||
<PristineCode<T>>::get(&prefab_module.code_hash).ok_or(Error::<T>::CodeNotFound)?;
|
||||
let original_code_len = original_code.len();
|
||||
// We need to allow contracts growing too big after re-instrumentation. Otherwise
|
||||
// the contract can become inaccessible. The user has no influence over this size
|
||||
// as the contract is already deployed and every change in size would be the result
|
||||
// of changes in the instrumentation algorithm controlled by the chain authors.
|
||||
prefab_module.code = WeakBoundedVec::force_from(
|
||||
prepare::reinstrument::<super::runtime::Env, T>(
|
||||
&original_code,
|
||||
schedule,
|
||||
prefab_module.determinism,
|
||||
)?,
|
||||
Some("Contract exceeds size limit after re-instrumentation."),
|
||||
);
|
||||
prefab_module.instruction_weights_version = schedule.instruction_weights.version;
|
||||
<CodeStorage<T>>::insert(&prefab_module.code_hash, &*prefab_module);
|
||||
Ok(original_code_len as u32)
|
||||
}
|
||||
|
||||
/// Costs for operations that are related to code handling.
|
||||
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
|
||||
#[derive(Clone, Copy)]
|
||||
enum CodeToken {
|
||||
/// Weight for reinstrumenting a contract contract of the supplied size in bytes.
|
||||
Reinstrument(u32),
|
||||
/// Weight for loading a contract per byte.
|
||||
Load(u32),
|
||||
}
|
||||
|
||||
impl<T: Config> Token<T> for CodeToken {
|
||||
fn weight(&self) -> Weight {
|
||||
use self::CodeToken::*;
|
||||
// In case of `Load` we already covered the general costs of
|
||||
// calling the storage but still need to account for the actual size of the
|
||||
// contract code. This is why we subtract `T::*::(0)`. We need to do this at this
|
||||
// point because when charging the general weight for calling the contract we not know the
|
||||
// size of the contract.
|
||||
match *self {
|
||||
Reinstrument(len) => T::WeightInfo::reinstrument(len),
|
||||
Load(len) => T::WeightInfo::call_with_code_per_byte(len)
|
||||
.saturating_sub(T::WeightInfo::call_with_code_per_byte(0)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,13 +18,9 @@
|
||||
//! This module provides a means for executing contracts
|
||||
//! represented in wasm.
|
||||
|
||||
mod code_cache;
|
||||
mod prepare;
|
||||
mod runtime;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub use crate::wasm::code_cache::reinstrument;
|
||||
|
||||
#[cfg(doc)]
|
||||
pub use crate::wasm::runtime::api_doc;
|
||||
|
||||
@@ -41,81 +37,66 @@ pub use crate::wasm::{
|
||||
|
||||
use crate::{
|
||||
exec::{ExecResult, Executable, ExportedFunction, Ext},
|
||||
gas::GasMeter,
|
||||
AccountIdOf, BalanceOf, CodeHash, CodeVec, Config, Error, OwnerInfoOf, RelaxedCodeVec,
|
||||
Schedule, LOG_TARGET,
|
||||
gas::{GasMeter, Token},
|
||||
wasm::prepare::IMPORT_MODULE_MEMORY,
|
||||
weights::WeightInfo,
|
||||
AccountIdOf, BadOrigin, BalanceOf, CodeHash, CodeInfoOf, CodeVec, Config, Error, Event, Pallet,
|
||||
PristineCode, Schedule, Weight, LOG_TARGET,
|
||||
};
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use frame_support::dispatch::{DispatchError, DispatchResult};
|
||||
use frame_support::{
|
||||
dispatch::{DispatchError, DispatchResult},
|
||||
ensure,
|
||||
traits::ReservableCurrency,
|
||||
};
|
||||
use sp_core::Get;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_std::prelude::*;
|
||||
use wasmi::{
|
||||
Config as WasmiConfig, Engine, Instance, Linker, Memory, MemoryType, Module, StackLimits, Store,
|
||||
Config as WasmiConfig, Engine, ExternType, FuelConsumptionMode, Instance, Linker, Memory,
|
||||
MemoryType, Module, StackLimits, Store,
|
||||
};
|
||||
const BYTES_PER_PAGE: usize = 64 * 1024;
|
||||
|
||||
/// A prepared wasm module ready for execution.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This data structure is mostly immutable once created and stored. The exceptions that
|
||||
/// can be changed by calling a contract are `instruction_weights_version` and `code`.
|
||||
/// `instruction_weights_version` and `code` change when a contract with an outdated instrumentation
|
||||
/// is called. Therefore one must be careful when holding any in-memory representation of this
|
||||
/// type while calling into a contract as those fields can get out of date.
|
||||
#[derive(Clone, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
|
||||
/// Validated Wasm module ready for execution.
|
||||
/// This data structure is immutable once created and stored.
|
||||
#[derive(Encode, Decode, scale_info::TypeInfo)]
|
||||
#[codec(mel_bound())]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct PrefabWasmModule<T: Config> {
|
||||
/// Version of the instruction weights with which the code was instrumented.
|
||||
#[codec(compact)]
|
||||
instruction_weights_version: u32,
|
||||
/// Initial memory size of a contract's sandbox.
|
||||
#[codec(compact)]
|
||||
initial: u32,
|
||||
/// The maximum memory size of a contract's sandbox.
|
||||
#[codec(compact)]
|
||||
maximum: u32,
|
||||
/// Code instrumented with the latest schedule.
|
||||
code: RelaxedCodeVec<T>,
|
||||
/// A code that might contain non deterministic features and is therefore never allowed
|
||||
/// to be run on chain. Specifically this code can never be instantiated into a contract
|
||||
/// and can just be used through a delegate call.
|
||||
determinism: Determinism,
|
||||
/// The uninstrumented, pristine version of the code.
|
||||
///
|
||||
/// It is not stored because the pristine code has its own storage item. The value
|
||||
/// is only `Some` when this module was created from an `original_code` and `None` if
|
||||
/// it was loaded from storage.
|
||||
pub struct WasmBlob<T: Config> {
|
||||
code: CodeVec<T>,
|
||||
// This isn't needed for contract execution and is not stored alongside it.
|
||||
#[codec(skip)]
|
||||
original_code: Option<CodeVec<T>>,
|
||||
/// The code hash of the stored code which is defined as the hash over the `original_code`.
|
||||
///
|
||||
/// As the map key there is no need to store the hash in the value, too. It is set manually
|
||||
/// when loading the module from storage.
|
||||
code_info: CodeInfo<T>,
|
||||
// This is for not calculating the hash every time we need it.
|
||||
#[codec(skip)]
|
||||
code_hash: CodeHash<T>,
|
||||
// This isn't needed for contract execution and does not get loaded from storage by default.
|
||||
// It is `Some` if and only if this struct was generated from code.
|
||||
#[codec(skip)]
|
||||
owner_info: Option<OwnerInfo<T>>,
|
||||
}
|
||||
|
||||
/// Information that belongs to a [`PrefabWasmModule`] but is stored separately.
|
||||
/// Contract code related data, such as:
|
||||
///
|
||||
/// - owner of the contract, i.e. account uploaded its code,
|
||||
/// - storage deposit amount,
|
||||
/// - reference count,
|
||||
/// - determinism marker.
|
||||
///
|
||||
/// It is stored in a separate storage entry to avoid loading the code when not necessary.
|
||||
#[derive(Clone, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
|
||||
#[codec(mel_bound())]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct OwnerInfo<T: Config> {
|
||||
/// The account that has deployed the contract and hence is allowed to remove it.
|
||||
pub struct CodeInfo<T: Config> {
|
||||
/// The account that has uploaded the contract code and hence is allowed to remove it.
|
||||
owner: AccountIdOf<T>,
|
||||
/// The amount of balance that was deposited by the owner in order to deploy it.
|
||||
/// The amount of balance that was deposited by the owner in order to store it on-chain.
|
||||
#[codec(compact)]
|
||||
deposit: BalanceOf<T>,
|
||||
/// The number of contracts that use this as their code.
|
||||
/// The number of instantiated contracts that use this as their code.
|
||||
#[codec(compact)]
|
||||
refcount: u64,
|
||||
/// Marks if the code might contain non-deterministic features and is therefore never allowed
|
||||
/// to be run on-chain. Specifically, such a code can never be instantiated into a contract
|
||||
/// and can just be used through a delegate call.
|
||||
determinism: Determinism,
|
||||
}
|
||||
|
||||
/// Defines the required determinism level of a wasm blob when either running or uploading code.
|
||||
@@ -149,26 +130,42 @@ impl ExportedFunction {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> PrefabWasmModule<T> {
|
||||
/// Create the module by checking and instrumenting `original_code`.
|
||||
/// Cost of code loading from storage.
|
||||
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
|
||||
#[derive(Clone, Copy)]
|
||||
struct CodeLoadToken(u32);
|
||||
|
||||
impl<T: Config> Token<T> for CodeLoadToken {
|
||||
fn weight(&self) -> Weight {
|
||||
// When loading the contract, we already covered the general costs of
|
||||
// calling the storage but still need to account for the actual size of the
|
||||
// contract code. This is why we subtract `T::*::(0)`. We need to do this at this
|
||||
// point because when charging the general weight for calling the contract we don't know the
|
||||
// size of the contract.
|
||||
T::WeightInfo::call_with_code_per_byte(self.0)
|
||||
.saturating_sub(T::WeightInfo::call_with_code_per_byte(0))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> WasmBlob<T> {
|
||||
/// Create the module by checking the `code`.
|
||||
///
|
||||
/// This does **not** store the module. For this one need to either call [`Self::store`]
|
||||
/// or [`<Self as Executable>::execute`][`Executable::execute`].
|
||||
pub fn from_code(
|
||||
original_code: Vec<u8>,
|
||||
code: Vec<u8>,
|
||||
schedule: &Schedule<T>,
|
||||
owner: AccountIdOf<T>,
|
||||
determinism: Determinism,
|
||||
try_instantiate: TryInstantiate,
|
||||
) -> Result<Self, (DispatchError, &'static str)> {
|
||||
let module = prepare::prepare::<runtime::Env, T>(
|
||||
original_code.try_into().map_err(|_| (<Error<T>>::CodeTooLarge.into(), ""))?,
|
||||
prepare::prepare::<runtime::Env, T>(
|
||||
code.try_into().map_err(|_| (<Error<T>>::CodeTooLarge.into(), ""))?,
|
||||
schedule,
|
||||
owner,
|
||||
determinism,
|
||||
try_instantiate,
|
||||
)?;
|
||||
Ok(module)
|
||||
)
|
||||
}
|
||||
|
||||
/// Store the code without instantiating it.
|
||||
@@ -176,27 +173,25 @@ impl<T: Config> PrefabWasmModule<T> {
|
||||
/// Otherwise the code is stored when [`<Self as Executable>::execute`][`Executable::execute`]
|
||||
/// is called.
|
||||
pub fn store(self) -> DispatchResult {
|
||||
code_cache::store(self, false)
|
||||
Self::store_code(self, false)
|
||||
}
|
||||
|
||||
/// Remove the code from storage and refund the deposit to its owner.
|
||||
///
|
||||
/// Applies all necessary checks before removing the code.
|
||||
pub fn remove(origin: &T::AccountId, code_hash: CodeHash<T>) -> DispatchResult {
|
||||
code_cache::try_remove::<T>(origin, code_hash)
|
||||
Self::try_remove_code(origin, code_hash)
|
||||
}
|
||||
|
||||
/// Returns whether there is a deposit to be paid for this module.
|
||||
///
|
||||
/// Returns `0` if the module is already in storage and hence no deposit will
|
||||
/// be charged when storing it.
|
||||
pub fn open_deposit(&self) -> BalanceOf<T> {
|
||||
if <OwnerInfoOf<T>>::contains_key(&self.code_hash) {
|
||||
/// be charged for storing it.
|
||||
pub fn open_deposit(&self, code_info: &CodeInfo<T>) -> BalanceOf<T> {
|
||||
if <CodeInfoOf<T>>::contains_key(self.code_hash()) {
|
||||
0u32.into()
|
||||
} else {
|
||||
// Only already in-storage contracts have their `owner_info` set to `None`.
|
||||
// Therefore it is correct to return `0` in this case.
|
||||
self.owner_info.as_ref().map(|i| i.deposit).unwrap_or_default()
|
||||
code_info.deposit
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,14 +199,14 @@ impl<T: Config> PrefabWasmModule<T> {
|
||||
///
|
||||
/// This is either used for later executing a contract or for validation of a contract.
|
||||
/// When validating we pass `()` as `host_state`. Please note that such a dummy instance must
|
||||
/// **never** be called/executed since it will panic the executor.
|
||||
/// **never** be called/executed, since it will panic the executor.
|
||||
pub fn instantiate<E, H>(
|
||||
code: &[u8],
|
||||
host_state: H,
|
||||
memory: (u32, u32),
|
||||
schedule: &Schedule<T>,
|
||||
stack_limits: StackLimits,
|
||||
allow_deprecated: AllowDeprecatedInterface,
|
||||
) -> Result<(Store<H>, Memory, Instance), wasmi::Error>
|
||||
) -> Result<(Store<H>, Memory, Instance), &'static str>
|
||||
where
|
||||
E: Environment<H>,
|
||||
{
|
||||
@@ -221,9 +216,12 @@ impl<T: Config> PrefabWasmModule<T> {
|
||||
.wasm_multi_value(false)
|
||||
.wasm_mutable_global(false)
|
||||
.wasm_sign_extension(false)
|
||||
.wasm_saturating_float_to_int(false);
|
||||
.wasm_saturating_float_to_int(false)
|
||||
.consume_fuel(true)
|
||||
.fuel_consumption_mode(FuelConsumptionMode::Eager);
|
||||
|
||||
let engine = Engine::new(&config);
|
||||
let module = Module::new(&engine, code)?;
|
||||
let module = Module::new(&engine, code.clone()).map_err(|_| "can't decode Wasm module")?;
|
||||
let mut store = Store::new(&engine, host_state);
|
||||
let mut linker = Linker::new(&engine);
|
||||
E::define(
|
||||
@@ -235,55 +233,187 @@ impl<T: Config> PrefabWasmModule<T> {
|
||||
AllowUnstableInterface::No
|
||||
},
|
||||
allow_deprecated,
|
||||
)?;
|
||||
let memory = Memory::new(&mut store, MemoryType::new(memory.0, Some(memory.1))?).expect(
|
||||
"The limits defined in our `Schedule` limit the amount of memory well below u32::MAX; qed",
|
||||
);
|
||||
)
|
||||
.map_err(|_| "can't define host functions to Linker")?;
|
||||
// Query wasmi for memory limits specified in the module's import entry.
|
||||
let memory_limits = Self::get_memory_limits(module.imports(), schedule)?;
|
||||
// Here we allocate this memory in the _store_. It allocates _inital_ value, but allows it
|
||||
// to grow up to maximum number of memory pages, if neccesary.
|
||||
let qed = "We checked the limits versus our Schedule,
|
||||
which specifies the max amount of memory pages
|
||||
well below u16::MAX; qed";
|
||||
let memory = Memory::new(
|
||||
&mut store,
|
||||
MemoryType::new(memory_limits.0, Some(memory_limits.1)).expect(qed),
|
||||
)
|
||||
.expect(qed);
|
||||
linker
|
||||
.define("env", "memory", memory)
|
||||
.expect("We just created the linker. It has no define with this name attached; qed");
|
||||
.expect("We just created the Linker. It has no definitions with this name; qed");
|
||||
|
||||
let instance = linker.instantiate(&mut store, &module)?.ensure_no_start(&mut store)?;
|
||||
let instance = linker
|
||||
.instantiate(&mut store, &module)
|
||||
.map_err(|_| "can't instantiate module with provided definitions")?
|
||||
.ensure_no_start(&mut store)
|
||||
.map_err(|_| "start function is forbidden but found in the module")?;
|
||||
|
||||
Ok((store, memory, instance))
|
||||
}
|
||||
|
||||
/// Query wasmi for memory limits specified for the import in Wasm module.
|
||||
fn get_memory_limits(
|
||||
imports: wasmi::ModuleImportsIter,
|
||||
schedule: &Schedule<T>,
|
||||
) -> Result<(u32, u32), &'static str> {
|
||||
let mut mem_type = None;
|
||||
for import in imports {
|
||||
match *import.ty() {
|
||||
ExternType::Memory(mt) => {
|
||||
if import.module() != IMPORT_MODULE_MEMORY {
|
||||
return Err("Invalid module for imported memory")
|
||||
}
|
||||
if import.name() != "memory" {
|
||||
return Err("Memory import must have the field name 'memory'")
|
||||
}
|
||||
mem_type = Some(mt);
|
||||
break
|
||||
},
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
// We don't need to check here if module memory limits satisfy the schedule,
|
||||
// as this was already done during the code uploading.
|
||||
// If none memory imported then set its limits to (0,0).
|
||||
// Any access to it will then lead to out of bounds trap.
|
||||
let (initial, maximum) = mem_type.map_or(Default::default(), |mt| {
|
||||
(
|
||||
mt.initial_pages().to_bytes().unwrap_or(0).saturating_div(BYTES_PER_PAGE) as u32,
|
||||
mt.maximum_pages().map_or(schedule.limits.memory_pages, |p| {
|
||||
p.to_bytes().unwrap_or(0).saturating_div(BYTES_PER_PAGE) as u32
|
||||
}),
|
||||
)
|
||||
});
|
||||
if initial > maximum {
|
||||
return Err(
|
||||
"Requested initial number of memory pages should not exceed the requested maximum",
|
||||
)
|
||||
}
|
||||
if maximum > schedule.limits.memory_pages {
|
||||
return Err("Maximum number of memory pages should not exceed the maximum configured in the Schedule.")
|
||||
}
|
||||
Ok((initial, maximum))
|
||||
}
|
||||
|
||||
/// Getter method for the code_info.
|
||||
pub fn code_info(&self) -> &CodeInfo<T> {
|
||||
&self.code_info
|
||||
}
|
||||
|
||||
/// Put the module blob into storage.
|
||||
///
|
||||
/// Increments the reference count of the in-storage `WasmBlob`, if it already exists in
|
||||
/// storage.
|
||||
fn store_code(mut module: Self, instantiated: bool) -> DispatchResult {
|
||||
let code_hash = &module.code_hash().clone();
|
||||
<CodeInfoOf<T>>::mutate(code_hash, |stored_code_info| {
|
||||
match stored_code_info {
|
||||
// Instantiate existing contract.
|
||||
Some(stored_code_info) if instantiated => {
|
||||
stored_code_info.refcount = stored_code_info.refcount.checked_add(1).expect(
|
||||
"
|
||||
refcount is 64bit. Generating this overflow would require to store
|
||||
_at least_ 18 exabyte of data assuming that a contract consumes only
|
||||
one byte of data. Any node would run out of storage space before hitting
|
||||
this overflow;
|
||||
qed
|
||||
",
|
||||
);
|
||||
Ok(())
|
||||
},
|
||||
// Contract code is already stored in storage. Nothing to be done here.
|
||||
Some(_) => Ok(()),
|
||||
// Upload a new contract code.
|
||||
//
|
||||
// We need to store the code and its code_info, and collect the deposit.
|
||||
None => {
|
||||
// This `None` case happens only in freshly uploaded modules. This means that
|
||||
// the `owner` is always the origin of the current transaction.
|
||||
T::Currency::reserve(&module.code_info.owner, module.code_info.deposit)
|
||||
.map_err(|_| <Error<T>>::StorageDepositNotEnoughFunds)?;
|
||||
module.code_info.refcount = if instantiated { 1 } else { 0 };
|
||||
<PristineCode<T>>::insert(code_hash, module.code);
|
||||
*stored_code_info = Some(module.code_info);
|
||||
<Pallet<T>>::deposit_event(
|
||||
vec![*code_hash],
|
||||
Event::CodeStored { code_hash: *code_hash },
|
||||
);
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Try to remove code together with all associated information.
|
||||
fn try_remove_code(origin: &T::AccountId, code_hash: CodeHash<T>) -> DispatchResult {
|
||||
<CodeInfoOf<T>>::try_mutate_exists(&code_hash, |existing| {
|
||||
if let Some(code_info) = existing {
|
||||
ensure!(code_info.refcount == 0, <Error<T>>::CodeInUse);
|
||||
ensure!(&code_info.owner == origin, BadOrigin);
|
||||
T::Currency::unreserve(&code_info.owner, code_info.deposit);
|
||||
*existing = None;
|
||||
<PristineCode<T>>::remove(&code_hash);
|
||||
<Pallet<T>>::deposit_event(vec![code_hash], Event::CodeRemoved { code_hash });
|
||||
Ok(())
|
||||
} else {
|
||||
Err(<Error<T>>::CodeNotFound.into())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Load code with the given code hash.
|
||||
fn load_code(
|
||||
code_hash: CodeHash<T>,
|
||||
gas_meter: &mut GasMeter<T>,
|
||||
) -> Result<CodeVec<T>, DispatchError> {
|
||||
let max_code_len = T::MaxCodeLen::get();
|
||||
let charged = gas_meter.charge(CodeLoadToken(max_code_len))?;
|
||||
|
||||
let code = <PristineCode<T>>::get(code_hash).ok_or(Error::<T>::CodeNotFound)?;
|
||||
let code_len = code.len() as u32;
|
||||
gas_meter.adjust_gas(charged, CodeLoadToken(code_len));
|
||||
|
||||
Ok(code)
|
||||
}
|
||||
|
||||
/// See [`Self::from_code_unchecked`].
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub fn store_code_unchecked(
|
||||
original_code: Vec<u8>,
|
||||
code: Vec<u8>,
|
||||
schedule: &Schedule<T>,
|
||||
owner: T::AccountId,
|
||||
) -> DispatchResult {
|
||||
let executable = Self::from_code_unchecked(original_code, schedule, owner)?;
|
||||
code_cache::store(executable, false)
|
||||
let executable = Self::from_code_unchecked(code, schedule, owner)?;
|
||||
Self::store_code(executable, false)
|
||||
}
|
||||
|
||||
/// Decrement instruction_weights_version by 1. Panics if it is already 0.
|
||||
#[cfg(test)]
|
||||
pub fn decrement_version(&mut self) {
|
||||
self.instruction_weights_version = self.instruction_weights_version.checked_sub(1).unwrap();
|
||||
}
|
||||
|
||||
/// Create the module without checking nor instrumenting the passed code.
|
||||
/// Create the module without checking the passed code.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This is useful for benchmarking where we don't want instrumentation to skew
|
||||
/// This is useful for benchmarking where we don't want validation of the module to skew
|
||||
/// our results. This also does not collect any deposit from the `owner`. Also useful
|
||||
/// during testing when we want to deploy codes that do not pass the instantiation checks.
|
||||
#[cfg(any(test, feature = "runtime-benchmarks"))]
|
||||
fn from_code_unchecked(
|
||||
original_code: Vec<u8>,
|
||||
code: Vec<u8>,
|
||||
schedule: &Schedule<T>,
|
||||
owner: T::AccountId,
|
||||
) -> Result<Self, DispatchError> {
|
||||
prepare::benchmarking::prepare(original_code, schedule, owner)
|
||||
.map_err::<DispatchError, _>(Into::into)
|
||||
prepare::benchmarking::prepare(code, schedule, owner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> OwnerInfo<T> {
|
||||
impl<T: Config> CodeInfo<T> {
|
||||
/// Return the refcount of the module.
|
||||
#[cfg(test)]
|
||||
pub fn refcount(&self) -> u64 {
|
||||
@@ -291,21 +421,38 @@ impl<T: Config> OwnerInfo<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Executable<T> for PrefabWasmModule<T> {
|
||||
impl<T: Config> Executable<T> for WasmBlob<T> {
|
||||
fn from_storage(
|
||||
code_hash: CodeHash<T>,
|
||||
schedule: &Schedule<T>,
|
||||
gas_meter: &mut GasMeter<T>,
|
||||
) -> Result<Self, DispatchError> {
|
||||
code_cache::load(code_hash, schedule, gas_meter)
|
||||
let code = Self::load_code(code_hash, gas_meter)?;
|
||||
// We store `code_info` at the same time as contract code,
|
||||
// therefore this query shouldn't really fail.
|
||||
// We consider its failure equal to `CodeNotFound`, as contract code without
|
||||
// `code_info` is unusable in this pallet.
|
||||
let code_info = <CodeInfoOf<T>>::get(code_hash).ok_or(Error::<T>::CodeNotFound)?;
|
||||
|
||||
Ok(Self { code, code_info, code_hash })
|
||||
}
|
||||
|
||||
fn add_user(code_hash: CodeHash<T>) -> Result<(), DispatchError> {
|
||||
code_cache::increment_refcount::<T>(code_hash)
|
||||
fn increment_refcount(code_hash: CodeHash<T>) -> Result<(), DispatchError> {
|
||||
<CodeInfoOf<T>>::mutate(code_hash, |existing| -> Result<(), DispatchError> {
|
||||
if let Some(info) = existing {
|
||||
info.refcount = info.refcount.saturating_add(1);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::<T>::CodeNotFound.into())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_user(code_hash: CodeHash<T>) {
|
||||
code_cache::decrement_refcount::<T>(code_hash)
|
||||
fn decrement_refcount(code_hash: CodeHash<T>) {
|
||||
<CodeInfoOf<T>>::mutate(code_hash, |existing| {
|
||||
if let Some(info) = existing {
|
||||
info.refcount = info.refcount.saturating_sub(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn execute<E: Ext<T = T>>(
|
||||
@@ -314,23 +461,40 @@ impl<T: Config> Executable<T> for PrefabWasmModule<T> {
|
||||
function: &ExportedFunction,
|
||||
input_data: Vec<u8>,
|
||||
) -> ExecResult {
|
||||
let code = self.code.as_slice();
|
||||
// Instantiate the Wasm module to the engine.
|
||||
let runtime = Runtime::new(ext, input_data);
|
||||
let schedule = <T>::Schedule::get();
|
||||
let (mut store, memory, instance) = Self::instantiate::<crate::wasm::runtime::Env, _>(
|
||||
self.code.as_slice(),
|
||||
code,
|
||||
runtime,
|
||||
(self.initial, self.maximum),
|
||||
&schedule,
|
||||
StackLimits::default(),
|
||||
match function {
|
||||
ExportedFunction::Constructor => AllowDeprecatedInterface::No,
|
||||
ExportedFunction::Call => AllowDeprecatedInterface::Yes,
|
||||
ExportedFunction::Constructor => AllowDeprecatedInterface::No,
|
||||
},
|
||||
)
|
||||
.map_err(|msg| {
|
||||
log::debug!(target: LOG_TARGET, "failed to instantiate code: {}", msg);
|
||||
log::debug!(target: LOG_TARGET, "failed to instantiate code to wasmi: {}", msg);
|
||||
Error::<T>::CodeRejected
|
||||
})?;
|
||||
store.data_mut().set_memory(memory);
|
||||
|
||||
// Set fuel limit for the wasmi execution.
|
||||
// We normalize it by the base instruction weight, as its cost in wasmi engine is `1`.
|
||||
let fuel_limit = store
|
||||
.data_mut()
|
||||
.ext()
|
||||
.gas_meter_mut()
|
||||
.gas_left()
|
||||
.ref_time()
|
||||
.checked_div(T::Schedule::get().instruction_weights.base as u64)
|
||||
.ok_or(Error::<T>::InvalidSchedule)?;
|
||||
store
|
||||
.add_fuel(fuel_limit)
|
||||
.expect("We've set up engine to fuel consuming mode; qed");
|
||||
|
||||
let exported_func = instance
|
||||
.get_export(&store, function.identifier())
|
||||
.and_then(|export| export.into_func())
|
||||
@@ -341,10 +505,14 @@ impl<T: Config> Executable<T> for PrefabWasmModule<T> {
|
||||
|
||||
// We store before executing so that the code hash is available in the constructor.
|
||||
if let &ExportedFunction::Constructor = function {
|
||||
code_cache::store(self, true)?;
|
||||
Self::store_code(self, true)?;
|
||||
}
|
||||
|
||||
let result = exported_func.call(&mut store, &[], &mut []);
|
||||
let engine_consumed_total = store.fuel_consumed().expect("Fuel metering is enabled; qed");
|
||||
// Sync this frame's gas meter with the engine's one.
|
||||
let gas_meter = store.data_mut().ext().gas_meter_mut();
|
||||
gas_meter.charge_fuel(engine_consumed_total)?;
|
||||
|
||||
store.into_data().to_execution_result(result)
|
||||
}
|
||||
@@ -358,7 +526,7 @@ impl<T: Config> Executable<T> for PrefabWasmModule<T> {
|
||||
}
|
||||
|
||||
fn is_deterministic(&self) -> bool {
|
||||
matches!(self.determinism, Determinism::Enforced)
|
||||
matches!(self.code_info.determinism, Determinism::Enforced)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -603,7 +771,10 @@ mod tests {
|
||||
fn schedule(&self) -> &Schedule<Self::T> {
|
||||
&self.schedule
|
||||
}
|
||||
fn gas_meter(&mut self) -> &mut GasMeter<Self::T> {
|
||||
fn gas_meter(&self) -> &GasMeter<Self::T> {
|
||||
&self.gas_meter
|
||||
}
|
||||
fn gas_meter_mut(&mut self) -> &mut GasMeter<Self::T> {
|
||||
&mut self.gas_meter
|
||||
}
|
||||
fn append_debug_buffer(&mut self, msg: &str) -> bool {
|
||||
@@ -660,13 +831,16 @@ mod tests {
|
||||
type RuntimeConfig = <MockExt as Ext>::T;
|
||||
RuntimeConfig::set_unstable_interface(unstable_interface);
|
||||
let wasm = wat::parse_str(wat).unwrap();
|
||||
let schedule = crate::Schedule::default();
|
||||
let executable = if skip_checks {
|
||||
PrefabWasmModule::<RuntimeConfig>::from_code_unchecked(wasm, &schedule, ALICE)?
|
||||
} else {
|
||||
PrefabWasmModule::<RuntimeConfig>::from_code(
|
||||
WasmBlob::<RuntimeConfig>::from_code_unchecked(
|
||||
wasm,
|
||||
&schedule,
|
||||
ext.borrow_mut().schedule(),
|
||||
ALICE,
|
||||
)?
|
||||
} else {
|
||||
WasmBlob::<RuntimeConfig>::from_code(
|
||||
wasm,
|
||||
ext.borrow_mut().schedule(),
|
||||
ALICE,
|
||||
Determinism::Enforced,
|
||||
TryInstantiate::Instantiate,
|
||||
@@ -3161,25 +3335,6 @@ mod tests {
|
||||
execute(CODE, vec![], &mut mock_ext).unwrap();
|
||||
}
|
||||
|
||||
/// Code with deprecated functions cannot be uploaded or instantiated. However, we
|
||||
/// need to make sure that it still can be re-instrumented.
|
||||
#[test]
|
||||
fn can_reinstrument_deprecated() {
|
||||
const CODE_RANDOM: &str = r#"
|
||||
(module
|
||||
(import "seal0" "random" (func $seal_random (param i32 i32 i32 i32)))
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#;
|
||||
let wasm = wat::parse_str(CODE_RANDOM).unwrap();
|
||||
let schedule = crate::Schedule::<Test>::default();
|
||||
#[cfg(not(feature = "runtime-benchmarks"))]
|
||||
assert_err!(execute(CODE_RANDOM, vec![], MockExt::default()), <Error<Test>>::CodeRejected);
|
||||
self::prepare::reinstrument::<runtime::Env, Test>(&wasm, &schedule, Determinism::Enforced)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// This test check that an unstable interface cannot be deployed. In case of runtime
|
||||
/// benchmarks we always allow unstable interfaces. This is why this test does not
|
||||
/// work when this feature is enabled.
|
||||
|
||||
@@ -22,17 +22,15 @@
|
||||
use crate::{
|
||||
chain_extension::ChainExtension,
|
||||
storage::meter::Diff,
|
||||
wasm::{
|
||||
runtime::AllowDeprecatedInterface, Determinism, Environment, OwnerInfo, PrefabWasmModule,
|
||||
},
|
||||
wasm::{runtime::AllowDeprecatedInterface, CodeInfo, Determinism, Environment, WasmBlob},
|
||||
AccountIdOf, CodeVec, Config, Error, Schedule, LOG_TARGET,
|
||||
};
|
||||
use codec::{Encode, MaxEncodedLen};
|
||||
use codec::MaxEncodedLen;
|
||||
use sp_runtime::{traits::Hash, DispatchError};
|
||||
use sp_std::prelude::*;
|
||||
use wasm_instrument::{
|
||||
gas_metering,
|
||||
parity_wasm::elements::{self, External, Internal, MemoryType, Type, ValueType},
|
||||
#[cfg(any(test, feature = "runtime-benchmarks"))]
|
||||
use sp_std::prelude::Vec;
|
||||
use wasm_instrument::parity_wasm::elements::{
|
||||
self, External, Internal, MemoryType, Type, ValueType,
|
||||
};
|
||||
use wasmi::StackLimits;
|
||||
use wasmparser::{Validator, WasmFeatures};
|
||||
@@ -56,32 +54,20 @@ pub enum TryInstantiate {
|
||||
Skip,
|
||||
}
|
||||
|
||||
/// The reason why a contract is instrumented.
|
||||
enum InstrumentReason {
|
||||
/// A new code is uploaded.
|
||||
New,
|
||||
/// Existing code is re-instrumented.
|
||||
Reinstrument,
|
||||
}
|
||||
/// The inner deserialized module is valid (this is guaranteed by `new` method).
|
||||
pub struct ContractModule(elements::Module);
|
||||
|
||||
struct ContractModule<'a, T: Config> {
|
||||
/// A deserialized module. The module is valid (this is Guaranteed by `new` method).
|
||||
module: elements::Module,
|
||||
schedule: &'a Schedule<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: Config> ContractModule<'a, T> {
|
||||
impl ContractModule {
|
||||
/// Creates a new instance of `ContractModule`.
|
||||
///
|
||||
/// Returns `Err` if the `original_code` couldn't be decoded or
|
||||
/// Returns `Err` if the `code` couldn't be decoded or
|
||||
/// if it contains an invalid module.
|
||||
fn new(original_code: &[u8], schedule: &'a Schedule<T>) -> Result<Self, &'static str> {
|
||||
let module =
|
||||
elements::deserialize_buffer(original_code).map_err(|_| "Can't decode wasm code")?;
|
||||
pub fn new(code: &[u8]) -> Result<Self, &'static str> {
|
||||
let module = elements::deserialize_buffer(code).map_err(|_| "Can't decode Wasm code")?;
|
||||
|
||||
// Return a `ContractModule` instance with
|
||||
// __valid__ module.
|
||||
Ok(ContractModule { module, schedule })
|
||||
Ok(ContractModule(module))
|
||||
}
|
||||
|
||||
/// Ensures that module doesn't declare internal memories.
|
||||
@@ -90,7 +76,7 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
/// Memory section contains declarations of internal linear memories, so if we find one
|
||||
/// we reject such a module.
|
||||
fn ensure_no_internal_memory(&self) -> Result<(), &'static str> {
|
||||
if self.module.memory_section().map_or(false, |ms| ms.entries().len() > 0) {
|
||||
if self.0.memory_section().map_or(false, |ms| ms.entries().len() > 0) {
|
||||
return Err("module declares internal memory")
|
||||
}
|
||||
Ok(())
|
||||
@@ -98,7 +84,7 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
|
||||
/// Ensures that tables declared in the module are not too big.
|
||||
fn ensure_table_size_limit(&self, limit: u32) -> Result<(), &'static str> {
|
||||
if let Some(table_section) = self.module.table_section() {
|
||||
if let Some(table_section) = self.0.table_section() {
|
||||
// In Wasm MVP spec, there may be at most one table declared. Double check this
|
||||
// explicitly just in case the Wasm version changes.
|
||||
if table_section.entries().len() > 1 {
|
||||
@@ -117,7 +103,7 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
|
||||
/// Ensure that any `br_table` instruction adheres to its immediate value limit.
|
||||
fn ensure_br_table_size_limit(&self, limit: u32) -> Result<(), &'static str> {
|
||||
let code_section = if let Some(type_section) = self.module.code_section() {
|
||||
let code_section = if let Some(type_section) = self.0.code_section() {
|
||||
type_section
|
||||
} else {
|
||||
return Ok(())
|
||||
@@ -134,7 +120,7 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
}
|
||||
|
||||
fn ensure_global_variable_limit(&self, limit: u32) -> Result<(), &'static str> {
|
||||
if let Some(global_section) = self.module.global_section() {
|
||||
if let Some(global_section) = self.0.global_section() {
|
||||
if global_section.entries().len() > limit as usize {
|
||||
return Err("module declares too many globals")
|
||||
}
|
||||
@@ -143,7 +129,7 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
}
|
||||
|
||||
fn ensure_local_variable_limit(&self, limit: u32) -> Result<(), &'static str> {
|
||||
if let Some(code_section) = self.module.code_section() {
|
||||
if let Some(code_section) = self.0.code_section() {
|
||||
for func_body in code_section.bodies() {
|
||||
let locals_count: u32 =
|
||||
func_body.locals().iter().map(|val_type| val_type.count()).sum();
|
||||
@@ -157,7 +143,7 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
|
||||
/// Ensure that no function exists that has more parameters than allowed.
|
||||
fn ensure_parameter_limit(&self, limit: u32) -> Result<(), &'static str> {
|
||||
let type_section = if let Some(type_section) = self.module.type_section() {
|
||||
let type_section = if let Some(type_section) = self.0.type_section() {
|
||||
type_section
|
||||
} else {
|
||||
return Ok(())
|
||||
@@ -172,14 +158,6 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn inject_gas_metering(self, determinism: Determinism) -> Result<Self, &'static str> {
|
||||
let gas_rules = self.schedule.rules(determinism);
|
||||
let backend = gas_metering::host_function::Injector::new("seal0", "gas");
|
||||
let contract_module = gas_metering::inject(self.module, backend, &gas_rules)
|
||||
.map_err(|_| "gas instrumentation failed")?;
|
||||
Ok(ContractModule { module: contract_module, schedule: self.schedule })
|
||||
}
|
||||
|
||||
/// Check that the module has required exported functions. For now
|
||||
/// these are just entrypoints:
|
||||
///
|
||||
@@ -191,7 +169,7 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
let mut deploy_found = false;
|
||||
let mut call_found = false;
|
||||
|
||||
let module = &self.module;
|
||||
let module = &self.0;
|
||||
|
||||
let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]);
|
||||
let export_entries = module.export_section().map(|is| is.entries()).unwrap_or(&[]);
|
||||
@@ -261,13 +239,8 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
///
|
||||
/// This makes sure that the import section looks as we expect it from a contract
|
||||
/// and enforces and returns the memory type declared by the contract if any.
|
||||
///
|
||||
/// `import_fn_banlist`: list of function names that are disallowed to be imported
|
||||
fn scan_imports(
|
||||
&self,
|
||||
import_fn_banlist: &[&[u8]],
|
||||
) -> Result<Option<&MemoryType>, &'static str> {
|
||||
let module = &self.module;
|
||||
pub fn scan_imports<T: Config>(&self) -> Result<Option<&MemoryType>, &'static str> {
|
||||
let module = &self.0;
|
||||
let import_entries = module.import_section().map(|is| is.entries()).unwrap_or(&[]);
|
||||
let mut imported_mem_type = None;
|
||||
|
||||
@@ -276,15 +249,11 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
External::Table(_) => return Err("Cannot import tables"),
|
||||
External::Global(_) => return Err("Cannot import globals"),
|
||||
External::Function(_) => {
|
||||
if !T::ChainExtension::enabled() &&
|
||||
if !<T as Config>::ChainExtension::enabled() &&
|
||||
import.field().as_bytes() == b"seal_call_chain_extension"
|
||||
{
|
||||
return Err("module uses chain extensions but chain extensions are disabled")
|
||||
}
|
||||
|
||||
if import_fn_banlist.iter().any(|f| import.field().as_bytes() == *f) {
|
||||
return Err("module imports a banned function")
|
||||
}
|
||||
},
|
||||
External::Memory(ref memory_type) => {
|
||||
if import.module() != IMPORT_MODULE_MEMORY {
|
||||
@@ -301,14 +270,11 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(imported_mem_type)
|
||||
}
|
||||
|
||||
fn into_wasm_code(self) -> Result<Vec<u8>, &'static str> {
|
||||
elements::serialize(self.module).map_err(|_| "error serializing instrumented module")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "runtime-benchmarks"))]
|
||||
fn get_memory_limits<T: Config>(
|
||||
module: Option<&MemoryType>,
|
||||
schedule: &Schedule<T>,
|
||||
@@ -318,35 +284,30 @@ fn get_memory_limits<T: Config>(
|
||||
let limits = memory_type.limits();
|
||||
match (limits.initial(), limits.maximum()) {
|
||||
(initial, Some(maximum)) if initial > maximum =>
|
||||
Err("Requested initial number of pages should not exceed the requested maximum"),
|
||||
Err("Requested initial number of memory pages should not exceed the requested maximum"),
|
||||
(_, Some(maximum)) if maximum > schedule.limits.memory_pages =>
|
||||
Err("Maximum number of pages should not exceed the configured maximum."),
|
||||
Err("Maximum number of memory pages should not exceed the maximum configured in the Schedule."),
|
||||
(initial, Some(maximum)) => Ok((initial, maximum)),
|
||||
(_, None) => {
|
||||
// Maximum number of pages should be always declared.
|
||||
// This isn't a hard requirement and can be treated as a maximum set
|
||||
// to configured maximum.
|
||||
Err("Maximum number of pages should be always declared.")
|
||||
(initial, None) => {
|
||||
Ok((initial, schedule.limits.memory_pages))
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// If none memory imported then just create an empty placeholder.
|
||||
// Any access to it will lead to out of bounds trap.
|
||||
// None memory imported in the Wasm module,
|
||||
// any access to it will lead to out of bounds trap.
|
||||
Ok((0, 0))
|
||||
}
|
||||
}
|
||||
|
||||
/// Check and instrument the given `original_code`.
|
||||
/// Check that given `code` satisfies constraints required for the contract Wasm module.
|
||||
///
|
||||
/// On success it returns the instrumented versions together with its `(initial, maximum)`
|
||||
/// error requirement. The memory requirement was also validated against the `schedule`.
|
||||
fn instrument<E, T>(
|
||||
original_code: &[u8],
|
||||
/// On success it returns back the code.
|
||||
fn validate<E, T>(
|
||||
code: &[u8],
|
||||
schedule: &Schedule<T>,
|
||||
determinism: Determinism,
|
||||
try_instantiate: TryInstantiate,
|
||||
reason: InstrumentReason,
|
||||
) -> Result<(Vec<u8>, (u32, u32)), (DispatchError, &'static str)>
|
||||
) -> Result<(), (DispatchError, &'static str)>
|
||||
where
|
||||
E: Environment<()>,
|
||||
T: Config,
|
||||
@@ -377,189 +338,114 @@ where
|
||||
simd: false,
|
||||
memory_control: false,
|
||||
})
|
||||
.validate_all(original_code)
|
||||
.validate_all(code)
|
||||
.map_err(|err| {
|
||||
log::debug!(target: LOG_TARGET, "{}", err);
|
||||
(Error::<T>::CodeRejected.into(), "validation of new code failed")
|
||||
(Error::<T>::CodeRejected.into(), "Validation of new code failed!")
|
||||
})?;
|
||||
|
||||
let (code, (initial, maximum)) = (|| {
|
||||
let contract_module = ContractModule::new(original_code, schedule)?;
|
||||
(|| {
|
||||
let contract_module = ContractModule::new(code)?;
|
||||
contract_module.scan_exports()?;
|
||||
contract_module.scan_imports::<T>()?;
|
||||
contract_module.ensure_no_internal_memory()?;
|
||||
contract_module.ensure_table_size_limit(schedule.limits.table_size)?;
|
||||
contract_module.ensure_global_variable_limit(schedule.limits.globals)?;
|
||||
contract_module.ensure_local_variable_limit(schedule.limits.locals)?;
|
||||
contract_module.ensure_parameter_limit(schedule.limits.parameters)?;
|
||||
contract_module.ensure_br_table_size_limit(schedule.limits.br_table_size)?;
|
||||
|
||||
// We disallow importing `gas` function here since it is treated as implementation detail.
|
||||
let disallowed_imports = [b"gas".as_ref()];
|
||||
let memory_limits =
|
||||
get_memory_limits(contract_module.scan_imports(&disallowed_imports)?, schedule)?;
|
||||
|
||||
let code = contract_module.inject_gas_metering(determinism)?.into_wasm_code()?;
|
||||
|
||||
Ok((code, memory_limits))
|
||||
// Extract memory limits from the module.
|
||||
// This also checks that module's memory import satisfies the schedule.
|
||||
Ok(())
|
||||
})()
|
||||
.map_err(|msg: &str| {
|
||||
log::debug!(target: LOG_TARGET, "new code rejected: {}", msg);
|
||||
log::debug!(target: LOG_TARGET, "New code rejected: {}", msg);
|
||||
(Error::<T>::CodeRejected.into(), msg)
|
||||
})?;
|
||||
|
||||
// This will make sure that the module can be actually run within wasmi:
|
||||
//
|
||||
// - Doesn't use any unknown imports.
|
||||
// - Doesn't explode the wasmi bytecode generation.
|
||||
// - It doesn't use any unknown imports.
|
||||
// - It doesn't explode the wasmi bytecode generation.
|
||||
if matches!(try_instantiate, TryInstantiate::Instantiate) {
|
||||
// We don't actually ever run any code so we can get away with a minimal stack which
|
||||
// reduces the amount of memory that needs to be zeroed.
|
||||
let stack_limits = StackLimits::new(1, 1, 0).expect("initial <= max; qed");
|
||||
PrefabWasmModule::<T>::instantiate::<E, _>(
|
||||
WasmBlob::<T>::instantiate::<E, _>(
|
||||
&code,
|
||||
(),
|
||||
(initial, maximum),
|
||||
schedule,
|
||||
stack_limits,
|
||||
match reason {
|
||||
InstrumentReason::New => AllowDeprecatedInterface::No,
|
||||
InstrumentReason::Reinstrument => AllowDeprecatedInterface::Yes,
|
||||
},
|
||||
AllowDeprecatedInterface::No,
|
||||
)
|
||||
.map_err(|err| {
|
||||
log::debug!(target: LOG_TARGET, "{}", err);
|
||||
(Error::<T>::CodeRejected.into(), "new code rejected after instrumentation")
|
||||
(Error::<T>::CodeRejected.into(), "New code rejected on wasmi instantiation!")
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok((code, (initial, maximum)))
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Loads the given module given in `original_code`, performs some checks on it and
|
||||
/// does some preprocessing.
|
||||
/// Validates the given binary `code` is a valid Wasm module satisfying following constraints:
|
||||
///
|
||||
/// The checks are:
|
||||
/// - The module doesn't define an internal memory instance.
|
||||
/// - Imported memory (if any) doesn't reserve more memory than permitted by the `schedule`.
|
||||
/// - All imported functions from the external environment match defined by `env` module.
|
||||
///
|
||||
/// - the provided code is a valid wasm module
|
||||
/// - the module doesn't define an internal memory instance
|
||||
/// - imported memory (if any) doesn't reserve more memory than permitted by the `schedule`
|
||||
/// - all imported functions from the external environment matches defined by `env` module
|
||||
///
|
||||
/// The preprocessing includes injecting code for gas metering and metering the height of stack.
|
||||
/// Also constructs contract `code_info` by calculating the storage deposit.
|
||||
pub fn prepare<E, T>(
|
||||
original_code: CodeVec<T>,
|
||||
code: CodeVec<T>,
|
||||
schedule: &Schedule<T>,
|
||||
owner: AccountIdOf<T>,
|
||||
determinism: Determinism,
|
||||
try_instantiate: TryInstantiate,
|
||||
) -> Result<PrefabWasmModule<T>, (DispatchError, &'static str)>
|
||||
) -> Result<WasmBlob<T>, (DispatchError, &'static str)>
|
||||
where
|
||||
E: Environment<()>,
|
||||
T: Config,
|
||||
{
|
||||
let (code, (initial, maximum)) = instrument::<E, T>(
|
||||
original_code.as_ref(),
|
||||
schedule,
|
||||
determinism,
|
||||
try_instantiate,
|
||||
InstrumentReason::New,
|
||||
)?;
|
||||
validate::<E, T>(code.as_ref(), schedule, determinism, try_instantiate)?;
|
||||
|
||||
let original_code_len = original_code.len();
|
||||
|
||||
let mut module = PrefabWasmModule {
|
||||
instruction_weights_version: schedule.instruction_weights.version,
|
||||
initial,
|
||||
maximum,
|
||||
code: code.try_into().map_err(|_| (<Error<T>>::CodeTooLarge.into(), ""))?,
|
||||
code_hash: T::Hashing::hash(&original_code),
|
||||
original_code: Some(original_code),
|
||||
owner_info: None,
|
||||
determinism,
|
||||
};
|
||||
|
||||
// We need to add the sizes of the `#[codec(skip)]` fields which are stored in different
|
||||
// storage items. This is also why we have `3` items added and not only one.
|
||||
let bytes_added = module
|
||||
.encoded_size()
|
||||
.saturating_add(original_code_len)
|
||||
.saturating_add(<OwnerInfo<T>>::max_encoded_len()) as u32;
|
||||
let deposit = Diff { bytes_added, items_added: 3, ..Default::default() }
|
||||
// Calculate deposit for storing contract code and `code_info` in two different storage items.
|
||||
let bytes_added = code.len().saturating_add(<CodeInfo<T>>::max_encoded_len()) as u32;
|
||||
let deposit = Diff { bytes_added, items_added: 2, ..Default::default() }
|
||||
.update_contract::<T>(None)
|
||||
.charge_or_zero();
|
||||
|
||||
module.owner_info = Some(OwnerInfo { owner, deposit, refcount: 0 });
|
||||
let code_info = CodeInfo { owner, deposit, determinism, refcount: 0 };
|
||||
let code_hash = T::Hashing::hash(&code);
|
||||
|
||||
Ok(module)
|
||||
}
|
||||
|
||||
/// Same as [`prepare`] but without constructing a new module.
|
||||
///
|
||||
/// Used to update the code of an existing module to the newest [`Schedule`] version.
|
||||
/// Stictly speaking is not necessary to check the existing code before reinstrumenting because
|
||||
/// it can't change in the meantime. However, since we recently switched the validation library
|
||||
/// we want to re-validate to weed out any bugs that were lurking in the old version.
|
||||
pub fn reinstrument<E, T>(
|
||||
original_code: &[u8],
|
||||
schedule: &Schedule<T>,
|
||||
determinism: Determinism,
|
||||
) -> Result<Vec<u8>, DispatchError>
|
||||
where
|
||||
E: Environment<()>,
|
||||
T: Config,
|
||||
{
|
||||
instrument::<E, T>(
|
||||
original_code,
|
||||
schedule,
|
||||
determinism,
|
||||
// This function was triggered by an interaction with an existing contract code
|
||||
// that will try to instantiate anyways. Failing here would not help
|
||||
// as the contract is already on chain.
|
||||
TryInstantiate::Skip,
|
||||
InstrumentReason::Reinstrument,
|
||||
)
|
||||
.map_err(|(err, msg)| {
|
||||
log::error!(target: LOG_TARGET, "CodeRejected during reinstrument: {}", msg);
|
||||
err
|
||||
})
|
||||
.map(|(code, _)| code)
|
||||
Ok(WasmBlob { code, code_info, code_hash })
|
||||
}
|
||||
|
||||
/// Alternate (possibly unsafe) preparation functions used only for benchmarking and testing.
|
||||
///
|
||||
/// For benchmarking we need to construct special contracts that might not pass our
|
||||
/// sanity checks or need to skip instrumentation for correct results. We hide functions
|
||||
/// allowing this behind a feature that is only set during benchmarking or testing to
|
||||
/// prevent usage in production code.
|
||||
/// sanity checks. We hide functions allowing this behind a feature that is only set during
|
||||
/// benchmarking or testing to prevent usage in production code.
|
||||
#[cfg(any(test, feature = "runtime-benchmarks"))]
|
||||
pub mod benchmarking {
|
||||
use super::*;
|
||||
|
||||
/// Prepare function that neither checks nor instruments the passed in code.
|
||||
/// Prepare function that does not perform most checks on the passed in code.
|
||||
pub fn prepare<T: Config>(
|
||||
original_code: Vec<u8>,
|
||||
code: Vec<u8>,
|
||||
schedule: &Schedule<T>,
|
||||
owner: AccountIdOf<T>,
|
||||
) -> Result<PrefabWasmModule<T>, &'static str> {
|
||||
let contract_module = ContractModule::new(&original_code, schedule)?;
|
||||
let memory_limits = get_memory_limits(contract_module.scan_imports(&[])?, schedule)?;
|
||||
Ok(PrefabWasmModule {
|
||||
instruction_weights_version: schedule.instruction_weights.version,
|
||||
initial: memory_limits.0,
|
||||
maximum: memory_limits.1,
|
||||
code_hash: T::Hashing::hash(&original_code),
|
||||
original_code: Some(original_code.try_into().map_err(|_| "Original code too large")?),
|
||||
code: contract_module
|
||||
.into_wasm_code()?
|
||||
.try_into()
|
||||
.map_err(|_| "Instrumented code too large")?,
|
||||
owner_info: Some(OwnerInfo {
|
||||
owner,
|
||||
// this is a helper function for benchmarking which skips deposit collection
|
||||
deposit: Default::default(),
|
||||
refcount: 0,
|
||||
}),
|
||||
) -> Result<WasmBlob<T>, DispatchError> {
|
||||
let contract_module = ContractModule::new(&code)?;
|
||||
let _ = get_memory_limits(contract_module.scan_imports::<T>()?, schedule)?;
|
||||
let code_hash = T::Hashing::hash(&code);
|
||||
let code = code.try_into().map_err(|_| <Error<T>>::CodeTooLarge)?;
|
||||
let code_info = CodeInfo {
|
||||
owner,
|
||||
// this is a helper function for benchmarking which skips deposit collection
|
||||
deposit: Default::default(),
|
||||
refcount: 0,
|
||||
determinism: Determinism::Enforced,
|
||||
})
|
||||
};
|
||||
|
||||
Ok(WasmBlob { code, code_info, code_hash })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -574,9 +460,9 @@ mod tests {
|
||||
use pallet_contracts_proc_macro::define_env;
|
||||
use std::fmt;
|
||||
|
||||
impl fmt::Debug for PrefabWasmModule<Test> {
|
||||
impl fmt::Debug for WasmBlob<Test> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "PreparedContract {{ .. }}")
|
||||
write!(f, "ContractCode {{ .. }}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -654,7 +540,7 @@ mod tests {
|
||||
)
|
||||
(func (export "deploy"))
|
||||
)"#,
|
||||
Err("validation of new code failed")
|
||||
Err("Validation of new code failed!")
|
||||
);
|
||||
|
||||
mod functions {
|
||||
@@ -807,20 +693,7 @@ mod tests {
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("validation of new code failed")
|
||||
);
|
||||
|
||||
prepare_test!(
|
||||
no_maximum,
|
||||
r#"
|
||||
(module
|
||||
(import "env" "memory" (memory 1))
|
||||
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("Maximum number of pages should be always declared.")
|
||||
Err("Validation of new code failed!")
|
||||
);
|
||||
|
||||
prepare_test!(
|
||||
@@ -846,7 +719,7 @@ mod tests {
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("Maximum number of pages should not exceed the configured maximum.")
|
||||
Err("New code rejected on wasmi instantiation!")
|
||||
);
|
||||
|
||||
prepare_test!(
|
||||
@@ -873,7 +746,7 @@ mod tests {
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("validation of new code failed")
|
||||
Err("Validation of new code failed!")
|
||||
);
|
||||
|
||||
prepare_test!(
|
||||
@@ -987,21 +860,6 @@ mod tests {
|
||||
Ok(_)
|
||||
);
|
||||
|
||||
// even though gas is defined the contract can't import it since
|
||||
// it is an implementation defined.
|
||||
prepare_test!(
|
||||
can_not_import_gas_function,
|
||||
r#"
|
||||
(module
|
||||
(import "seal0" "gas" (func (param i32)))
|
||||
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("module imports a banned function")
|
||||
);
|
||||
|
||||
// memory is in "env" and not in "seal0"
|
||||
prepare_test!(
|
||||
memory_not_in_seal0,
|
||||
@@ -1043,18 +901,17 @@ mod tests {
|
||||
Ok(_)
|
||||
);
|
||||
|
||||
// wrong signature
|
||||
prepare_test!(
|
||||
wrong_signature,
|
||||
r#"
|
||||
(module
|
||||
(import "seal0" "gas" (func (param i64)))
|
||||
(import "seal0" "input" (func (param i64)))
|
||||
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("module imports a banned function")
|
||||
Err("New code rejected on wasmi instantiation!")
|
||||
);
|
||||
|
||||
prepare_test!(
|
||||
@@ -1067,7 +924,7 @@ mod tests {
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("new code rejected after instrumentation")
|
||||
Err("New code rejected on wasmi instantiation!")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1164,7 +1021,7 @@ mod tests {
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("validation of new code failed")
|
||||
Err("Validation of new code failed!")
|
||||
);
|
||||
|
||||
prepare_test!(
|
||||
@@ -1176,7 +1033,7 @@ mod tests {
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("validation of new code failed")
|
||||
Err("Validation of new code failed!")
|
||||
);
|
||||
|
||||
prepare_test!(
|
||||
@@ -1188,7 +1045,7 @@ mod tests {
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("validation of new code failed")
|
||||
Err("Validation of new code failed!")
|
||||
);
|
||||
|
||||
prepare_test!(
|
||||
@@ -1200,7 +1057,7 @@ mod tests {
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("validation of new code failed")
|
||||
Err("Validation of new code failed!")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,6 @@ pub trait Environment<HostState> {
|
||||
}
|
||||
|
||||
/// Type of a storage key.
|
||||
#[allow(dead_code)]
|
||||
enum KeyType {
|
||||
/// Legacy fix sized key `[u8;32]`.
|
||||
Fix,
|
||||
@@ -174,9 +173,6 @@ impl HostError for TrapReason {}
|
||||
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum RuntimeCosts {
|
||||
/// Charge the gas meter with the cost of a metering block. The charged costs are
|
||||
/// the supplied cost of the block plus the overhead of the metering itself.
|
||||
MeteringBlock(u64),
|
||||
/// Weight charged for copying data from the sandbox.
|
||||
CopyFromContract(u32),
|
||||
/// Weight charged for copying data to the sandbox.
|
||||
@@ -277,7 +273,6 @@ impl RuntimeCosts {
|
||||
fn token<T: Config>(&self, s: &HostFnWeights<T>) -> RuntimeToken {
|
||||
use self::RuntimeCosts::*;
|
||||
let weight = match *self {
|
||||
MeteringBlock(amount) => s.gas.saturating_add(Weight::from_parts(amount, 0)),
|
||||
CopyFromContract(len) => s.return_per_byte.saturating_mul(len.into()),
|
||||
CopyToContract(len) => s.input_per_byte.saturating_mul(len.into()),
|
||||
Caller => s.caller,
|
||||
@@ -369,7 +364,7 @@ impl RuntimeCosts {
|
||||
macro_rules! charge_gas {
|
||||
($runtime:expr, $costs:expr) => {{
|
||||
let token = $costs.token(&$runtime.ext.schedule().host_fn_weights);
|
||||
$runtime.ext.gas_meter().charge(token)
|
||||
$runtime.ext.gas_meter_mut().charge(token)
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -485,25 +480,40 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> {
|
||||
|
||||
/// Converts the sandbox result and the runtime state into the execution outcome.
|
||||
pub fn to_execution_result(self, sandbox_result: Result<(), wasmi::Error>) -> ExecResult {
|
||||
use wasmi::core::TrapCode::OutOfFuel;
|
||||
use TrapReason::*;
|
||||
|
||||
match sandbox_result {
|
||||
// Contract returned from main function -> no data was returned.
|
||||
Ok(_) => Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }),
|
||||
// `OutOfGas` when host asks engine to consume more than left in the _store_.
|
||||
// We should never get this case, as gas meter is being charged (and hence raises error)
|
||||
// first.
|
||||
Err(wasmi::Error::Store(_)) => Err(Error::<E::T>::OutOfGas.into()),
|
||||
// Contract either trapped or some host function aborted the execution.
|
||||
Err(wasmi::Error::Trap(trap)) => {
|
||||
// If we encoded a reason then it is some abort generated by a host function.
|
||||
// Otherwise the trap came from the contract.
|
||||
let reason: TrapReason = trap.downcast().ok_or(Error::<E::T>::ContractTrapped)?;
|
||||
match reason {
|
||||
Return(ReturnData { flags, data }) => {
|
||||
let flags =
|
||||
ReturnFlags::from_bits(flags).ok_or(Error::<E::T>::InvalidCallFlags)?;
|
||||
Ok(ExecReturnValue { flags, data })
|
||||
},
|
||||
Termination =>
|
||||
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }),
|
||||
SupervisorError(error) => return Err(error.into()),
|
||||
if let Some(OutOfFuel) = trap.trap_code() {
|
||||
// `OutOfGas` during engine execution.
|
||||
return Err(Error::<E::T>::OutOfGas.into())
|
||||
}
|
||||
// If we encoded a reason then it is some abort generated by a host function.
|
||||
if let Some(reason) = &trap.downcast_ref::<TrapReason>() {
|
||||
match &reason {
|
||||
Return(ReturnData { flags, data }) => {
|
||||
let flags = ReturnFlags::from_bits(*flags)
|
||||
.ok_or(Error::<E::T>::InvalidCallFlags)?;
|
||||
return Ok(ExecReturnValue { flags, data: data.to_vec() })
|
||||
},
|
||||
Termination =>
|
||||
return Ok(ExecReturnValue {
|
||||
flags: ReturnFlags::empty(),
|
||||
data: Vec::new(),
|
||||
}),
|
||||
SupervisorError(error) => return Err((*error).into()),
|
||||
}
|
||||
}
|
||||
// Otherwise the trap came from the contract itself.
|
||||
Err(Error::<E::T>::ContractTrapped.into())
|
||||
},
|
||||
// Any other error is returned only if instantiation or linking failed (i.e.
|
||||
// wasm binary tried to import a function that is not provided by the host).
|
||||
@@ -536,7 +546,7 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> {
|
||||
/// refunded to match the actual amount.
|
||||
pub fn adjust_gas(&mut self, charged: ChargedAmount, actual_costs: RuntimeCosts) {
|
||||
let token = actual_costs.token(&self.ext.schedule().host_fn_weights);
|
||||
self.ext.gas_meter().adjust_gas(charged, token);
|
||||
self.ext.gas_meter_mut().adjust_gas(charged, token);
|
||||
}
|
||||
|
||||
/// Read designated chunk from the sandbox memory.
|
||||
@@ -1004,18 +1014,6 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> {
|
||||
// for every function.
|
||||
#[define_env(doc)]
|
||||
pub mod env {
|
||||
/// Account for used gas. Traps if gas used is greater than gas limit.
|
||||
///
|
||||
/// NOTE: This is a implementation defined call and is NOT a part of the public API.
|
||||
/// This call is supposed to be called only by instrumentation injected code.
|
||||
/// It deals only with the *ref_time* Weight.
|
||||
///
|
||||
/// - `amount`: How much gas is used.
|
||||
fn gas(ctx: _, _memory: _, amount: u64) -> Result<(), TrapReason> {
|
||||
ctx.charge_gas(RuntimeCosts::MeteringBlock(amount))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the value at the given key in the contract storage.
|
||||
///
|
||||
/// Equivalent to the newer [`seal1`][`super::api_doc::Version1::set_storage`] version with the
|
||||
|
||||
Reference in New Issue
Block a user