mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-17 14:45:41 +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:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user