contracts: switch to wasmi gas metering (#14084)

* upgrade to wasmi 0.29

* prepare cleanup

* sync ref_time w engine from the stack frame

* proc_macro: sync gas in host funcs

save: compiles, only gas pushing left to macro

WIP proc macro

proc macro: done

* clean benchmarks & schedule: w_base = w_i64const

* scale gas values btw engine and gas meter

* (re)instrumentation & code_cache removed

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

save

* address review comments

* move from CodeStorage&PrefabWasmModule to PristineCode&WasmBlob

* refactor: no reftime_limit&schedule passes, no CodeStorage

* bugs fixing

* fix tests: expected deposit amount

* fix prepare::tests

* update tests and fix bugs

tests::run_out_of_gas_engine, need 2 more

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

gas_syncs_no_overcharge bug fixed, test passes!

cleaned out debug prints

second bug is not a bug

disabled_chain_extension test fix (err msg)

tests run_out_of_fuel_host, chain_extension pass

all tests pass

* update docs

* bump wasmi 0.30.0

* benchmarks updated, tests pass

* refactoring

* s/OwnerInfo/CodeInfo/g;

* migration: draft, compiles

* migration: draft, runs

* migration: draft, runs (fixing)

* deposits repaid non pro rata

* deposits repaid pro rata

* better try-runtime output

* even better try-runtime output

* benchmark migration

* fix merge leftover

* add forgotten fixtures, fix docs

* address review comments

* ci fixes

* cleanup

* benchmarks::prepare to return DispatchError

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

* store memory limits to CodeInfo

* ci: roll back weights

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

* drive-by: update Readme and pallet rustdoc

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

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

* use wasmi 0.29

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

* use wasmi 0.30 again

* query memory limits from wasmi

* better migration types

* ci: pull weights from master

* refactoring

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

* addressing review comments

* refactor

* address review comments

* optimize migration

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

* another review round comments addressed

* ci fix one

* clippy fix

* ci fix two

---------

Co-authored-by: command-bot <>
This commit is contained in:
Sasha Gryaznov
2023-07-03 14:04:10 +03:00
committed by GitHub
parent e42768ea34
commit fda86dd501
23 changed files with 2786 additions and 4588 deletions
+288 -133
View File
@@ -18,13 +18,9 @@
//! This module provides a means for executing contracts
//! represented in wasm.
mod code_cache;
mod prepare;
mod runtime;
#[cfg(feature = "runtime-benchmarks")]
pub use crate::wasm::code_cache::reinstrument;
#[cfg(doc)]
pub use crate::wasm::runtime::api_doc;
@@ -41,81 +37,66 @@ pub use crate::wasm::{
use crate::{
exec::{ExecResult, Executable, ExportedFunction, Ext},
gas::GasMeter,
AccountIdOf, BalanceOf, CodeHash, CodeVec, Config, Error, OwnerInfoOf, RelaxedCodeVec,
Schedule, LOG_TARGET,
gas::{GasMeter, Token},
wasm::prepare::IMPORT_MODULE_MEMORY,
weights::WeightInfo,
AccountIdOf, BadOrigin, BalanceOf, CodeHash, CodeInfoOf, CodeVec, Config, Error, Event, Pallet,
PristineCode, Schedule, Weight, LOG_TARGET,
};
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::dispatch::{DispatchError, DispatchResult};
use frame_support::{
dispatch::{DispatchError, DispatchResult},
ensure,
traits::ReservableCurrency,
};
use sp_core::Get;
use sp_runtime::RuntimeDebug;
use sp_std::prelude::*;
use wasmi::{
Config as WasmiConfig, Engine, Instance, Linker, Memory, MemoryType, Module, StackLimits, Store,
Config as WasmiConfig, Engine, ExternType, FuelConsumptionMode, Instance, Linker, Memory,
MemoryType, Module, StackLimits, Store,
};
const BYTES_PER_PAGE: usize = 64 * 1024;
/// A prepared wasm module ready for execution.
///
/// # Note
///
/// This data structure is mostly immutable once created and stored. The exceptions that
/// can be changed by calling a contract are `instruction_weights_version` and `code`.
/// `instruction_weights_version` and `code` change when a contract with an outdated instrumentation
/// is called. Therefore one must be careful when holding any in-memory representation of this
/// type while calling into a contract as those fields can get out of date.
#[derive(Clone, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
/// Validated Wasm module ready for execution.
/// This data structure is immutable once created and stored.
#[derive(Encode, Decode, scale_info::TypeInfo)]
#[codec(mel_bound())]
#[scale_info(skip_type_params(T))]
pub struct PrefabWasmModule<T: Config> {
/// Version of the instruction weights with which the code was instrumented.
#[codec(compact)]
instruction_weights_version: u32,
/// Initial memory size of a contract's sandbox.
#[codec(compact)]
initial: u32,
/// The maximum memory size of a contract's sandbox.
#[codec(compact)]
maximum: u32,
/// Code instrumented with the latest schedule.
code: RelaxedCodeVec<T>,
/// A code that might contain non deterministic features and is therefore never allowed
/// to be run on chain. Specifically this code can never be instantiated into a contract
/// and can just be used through a delegate call.
determinism: Determinism,
/// The uninstrumented, pristine version of the code.
///
/// It is not stored because the pristine code has its own storage item. The value
/// is only `Some` when this module was created from an `original_code` and `None` if
/// it was loaded from storage.
pub struct WasmBlob<T: Config> {
code: CodeVec<T>,
// This isn't needed for contract execution and is not stored alongside it.
#[codec(skip)]
original_code: Option<CodeVec<T>>,
/// The code hash of the stored code which is defined as the hash over the `original_code`.
///
/// As the map key there is no need to store the hash in the value, too. It is set manually
/// when loading the module from storage.
code_info: CodeInfo<T>,
// This is for not calculating the hash every time we need it.
#[codec(skip)]
code_hash: CodeHash<T>,
// This isn't needed for contract execution and does not get loaded from storage by default.
// It is `Some` if and only if this struct was generated from code.
#[codec(skip)]
owner_info: Option<OwnerInfo<T>>,
}
/// Information that belongs to a [`PrefabWasmModule`] but is stored separately.
/// Contract code related data, such as:
///
/// - owner of the contract, i.e. account uploaded its code,
/// - storage deposit amount,
/// - reference count,
/// - determinism marker.
///
/// It is stored in a separate storage entry to avoid loading the code when not necessary.
#[derive(Clone, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
#[codec(mel_bound())]
#[scale_info(skip_type_params(T))]
pub struct OwnerInfo<T: Config> {
/// The account that has deployed the contract and hence is allowed to remove it.
pub struct CodeInfo<T: Config> {
/// The account that has uploaded the contract code and hence is allowed to remove it.
owner: AccountIdOf<T>,
/// The amount of balance that was deposited by the owner in order to deploy it.
/// The amount of balance that was deposited by the owner in order to store it on-chain.
#[codec(compact)]
deposit: BalanceOf<T>,
/// The number of contracts that use this as their code.
/// The number of instantiated contracts that use this as their code.
#[codec(compact)]
refcount: u64,
/// Marks if the code might contain non-deterministic features and is therefore never allowed
/// to be run on-chain. Specifically, such a code can never be instantiated into a contract
/// and can just be used through a delegate call.
determinism: Determinism,
}
/// Defines the required determinism level of a wasm blob when either running or uploading code.
@@ -149,26 +130,42 @@ impl ExportedFunction {
}
}
impl<T: Config> PrefabWasmModule<T> {
/// Create the module by checking and instrumenting `original_code`.
/// Cost of code loading from storage.
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
#[derive(Clone, Copy)]
struct CodeLoadToken(u32);
impl<T: Config> Token<T> for CodeLoadToken {
fn weight(&self) -> Weight {
// When loading the contract, we already covered the general costs of
// calling the storage but still need to account for the actual size of the
// contract code. This is why we subtract `T::*::(0)`. We need to do this at this
// point because when charging the general weight for calling the contract we don't know the
// size of the contract.
T::WeightInfo::call_with_code_per_byte(self.0)
.saturating_sub(T::WeightInfo::call_with_code_per_byte(0))
}
}
impl<T: Config> WasmBlob<T> {
/// Create the module by checking the `code`.
///
/// This does **not** store the module. For this one need to either call [`Self::store`]
/// or [`<Self as Executable>::execute`][`Executable::execute`].
pub fn from_code(
original_code: Vec<u8>,
code: Vec<u8>,
schedule: &Schedule<T>,
owner: AccountIdOf<T>,
determinism: Determinism,
try_instantiate: TryInstantiate,
) -> Result<Self, (DispatchError, &'static str)> {
let module = prepare::prepare::<runtime::Env, T>(
original_code.try_into().map_err(|_| (<Error<T>>::CodeTooLarge.into(), ""))?,
prepare::prepare::<runtime::Env, T>(
code.try_into().map_err(|_| (<Error<T>>::CodeTooLarge.into(), ""))?,
schedule,
owner,
determinism,
try_instantiate,
)?;
Ok(module)
)
}
/// Store the code without instantiating it.
@@ -176,27 +173,25 @@ impl<T: Config> PrefabWasmModule<T> {
/// Otherwise the code is stored when [`<Self as Executable>::execute`][`Executable::execute`]
/// is called.
pub fn store(self) -> DispatchResult {
code_cache::store(self, false)
Self::store_code(self, false)
}
/// Remove the code from storage and refund the deposit to its owner.
///
/// Applies all necessary checks before removing the code.
pub fn remove(origin: &T::AccountId, code_hash: CodeHash<T>) -> DispatchResult {
code_cache::try_remove::<T>(origin, code_hash)
Self::try_remove_code(origin, code_hash)
}
/// Returns whether there is a deposit to be paid for this module.
///
/// Returns `0` if the module is already in storage and hence no deposit will
/// be charged when storing it.
pub fn open_deposit(&self) -> BalanceOf<T> {
if <OwnerInfoOf<T>>::contains_key(&self.code_hash) {
/// be charged for storing it.
pub fn open_deposit(&self, code_info: &CodeInfo<T>) -> BalanceOf<T> {
if <CodeInfoOf<T>>::contains_key(self.code_hash()) {
0u32.into()
} else {
// Only already in-storage contracts have their `owner_info` set to `None`.
// Therefore it is correct to return `0` in this case.
self.owner_info.as_ref().map(|i| i.deposit).unwrap_or_default()
code_info.deposit
}
}
@@ -204,14 +199,14 @@ impl<T: Config> PrefabWasmModule<T> {
///
/// This is either used for later executing a contract or for validation of a contract.
/// When validating we pass `()` as `host_state`. Please note that such a dummy instance must
/// **never** be called/executed since it will panic the executor.
/// **never** be called/executed, since it will panic the executor.
pub fn instantiate<E, H>(
code: &[u8],
host_state: H,
memory: (u32, u32),
schedule: &Schedule<T>,
stack_limits: StackLimits,
allow_deprecated: AllowDeprecatedInterface,
) -> Result<(Store<H>, Memory, Instance), wasmi::Error>
) -> Result<(Store<H>, Memory, Instance), &'static str>
where
E: Environment<H>,
{
@@ -221,9 +216,12 @@ impl<T: Config> PrefabWasmModule<T> {
.wasm_multi_value(false)
.wasm_mutable_global(false)
.wasm_sign_extension(false)
.wasm_saturating_float_to_int(false);
.wasm_saturating_float_to_int(false)
.consume_fuel(true)
.fuel_consumption_mode(FuelConsumptionMode::Eager);
let engine = Engine::new(&config);
let module = Module::new(&engine, code)?;
let module = Module::new(&engine, code.clone()).map_err(|_| "can't decode Wasm module")?;
let mut store = Store::new(&engine, host_state);
let mut linker = Linker::new(&engine);
E::define(
@@ -235,55 +233,187 @@ impl<T: Config> PrefabWasmModule<T> {
AllowUnstableInterface::No
},
allow_deprecated,
)?;
let memory = Memory::new(&mut store, MemoryType::new(memory.0, Some(memory.1))?).expect(
"The limits defined in our `Schedule` limit the amount of memory well below u32::MAX; qed",
);
)
.map_err(|_| "can't define host functions to Linker")?;
// Query wasmi for memory limits specified in the module's import entry.
let memory_limits = Self::get_memory_limits(module.imports(), schedule)?;
// Here we allocate this memory in the _store_. It allocates _inital_ value, but allows it
// to grow up to maximum number of memory pages, if neccesary.
let qed = "We checked the limits versus our Schedule,
which specifies the max amount of memory pages
well below u16::MAX; qed";
let memory = Memory::new(
&mut store,
MemoryType::new(memory_limits.0, Some(memory_limits.1)).expect(qed),
)
.expect(qed);
linker
.define("env", "memory", memory)
.expect("We just created the linker. It has no define with this name attached; qed");
.expect("We just created the Linker. It has no definitions with this name; qed");
let instance = linker.instantiate(&mut store, &module)?.ensure_no_start(&mut store)?;
let instance = linker
.instantiate(&mut store, &module)
.map_err(|_| "can't instantiate module with provided definitions")?
.ensure_no_start(&mut store)
.map_err(|_| "start function is forbidden but found in the module")?;
Ok((store, memory, instance))
}
/// Query wasmi for memory limits specified for the import in Wasm module.
fn get_memory_limits(
imports: wasmi::ModuleImportsIter,
schedule: &Schedule<T>,
) -> Result<(u32, u32), &'static str> {
let mut mem_type = None;
for import in imports {
match *import.ty() {
ExternType::Memory(mt) => {
if import.module() != IMPORT_MODULE_MEMORY {
return Err("Invalid module for imported memory")
}
if import.name() != "memory" {
return Err("Memory import must have the field name 'memory'")
}
mem_type = Some(mt);
break
},
_ => continue,
}
}
// We don't need to check here if module memory limits satisfy the schedule,
// as this was already done during the code uploading.
// If none memory imported then set its limits to (0,0).
// Any access to it will then lead to out of bounds trap.
let (initial, maximum) = mem_type.map_or(Default::default(), |mt| {
(
mt.initial_pages().to_bytes().unwrap_or(0).saturating_div(BYTES_PER_PAGE) as u32,
mt.maximum_pages().map_or(schedule.limits.memory_pages, |p| {
p.to_bytes().unwrap_or(0).saturating_div(BYTES_PER_PAGE) as u32
}),
)
});
if initial > maximum {
return Err(
"Requested initial number of memory pages should not exceed the requested maximum",
)
}
if maximum > schedule.limits.memory_pages {
return Err("Maximum number of memory pages should not exceed the maximum configured in the Schedule.")
}
Ok((initial, maximum))
}
/// Getter method for the code_info.
pub fn code_info(&self) -> &CodeInfo<T> {
&self.code_info
}
/// Put the module blob into storage.
///
/// Increments the reference count of the in-storage `WasmBlob`, if it already exists in
/// storage.
fn store_code(mut module: Self, instantiated: bool) -> DispatchResult {
let code_hash = &module.code_hash().clone();
<CodeInfoOf<T>>::mutate(code_hash, |stored_code_info| {
match stored_code_info {
// Instantiate existing contract.
Some(stored_code_info) if instantiated => {
stored_code_info.refcount = stored_code_info.refcount.checked_add(1).expect(
"
refcount is 64bit. Generating this overflow would require to store
_at least_ 18 exabyte of data assuming that a contract consumes only
one byte of data. Any node would run out of storage space before hitting
this overflow;
qed
",
);
Ok(())
},
// Contract code is already stored in storage. Nothing to be done here.
Some(_) => Ok(()),
// Upload a new contract code.
//
// We need to store the code and its code_info, and collect the deposit.
None => {
// This `None` case happens only in freshly uploaded modules. This means that
// the `owner` is always the origin of the current transaction.
T::Currency::reserve(&module.code_info.owner, module.code_info.deposit)
.map_err(|_| <Error<T>>::StorageDepositNotEnoughFunds)?;
module.code_info.refcount = if instantiated { 1 } else { 0 };
<PristineCode<T>>::insert(code_hash, module.code);
*stored_code_info = Some(module.code_info);
<Pallet<T>>::deposit_event(
vec![*code_hash],
Event::CodeStored { code_hash: *code_hash },
);
Ok(())
},
}
})
}
/// Try to remove code together with all associated information.
fn try_remove_code(origin: &T::AccountId, code_hash: CodeHash<T>) -> DispatchResult {
<CodeInfoOf<T>>::try_mutate_exists(&code_hash, |existing| {
if let Some(code_info) = existing {
ensure!(code_info.refcount == 0, <Error<T>>::CodeInUse);
ensure!(&code_info.owner == origin, BadOrigin);
T::Currency::unreserve(&code_info.owner, code_info.deposit);
*existing = None;
<PristineCode<T>>::remove(&code_hash);
<Pallet<T>>::deposit_event(vec![code_hash], Event::CodeRemoved { code_hash });
Ok(())
} else {
Err(<Error<T>>::CodeNotFound.into())
}
})
}
/// Load code with the given code hash.
fn load_code(
code_hash: CodeHash<T>,
gas_meter: &mut GasMeter<T>,
) -> Result<CodeVec<T>, DispatchError> {
let max_code_len = T::MaxCodeLen::get();
let charged = gas_meter.charge(CodeLoadToken(max_code_len))?;
let code = <PristineCode<T>>::get(code_hash).ok_or(Error::<T>::CodeNotFound)?;
let code_len = code.len() as u32;
gas_meter.adjust_gas(charged, CodeLoadToken(code_len));
Ok(code)
}
/// See [`Self::from_code_unchecked`].
#[cfg(feature = "runtime-benchmarks")]
pub fn store_code_unchecked(
original_code: Vec<u8>,
code: Vec<u8>,
schedule: &Schedule<T>,
owner: T::AccountId,
) -> DispatchResult {
let executable = Self::from_code_unchecked(original_code, schedule, owner)?;
code_cache::store(executable, false)
let executable = Self::from_code_unchecked(code, schedule, owner)?;
Self::store_code(executable, false)
}
/// Decrement instruction_weights_version by 1. Panics if it is already 0.
#[cfg(test)]
pub fn decrement_version(&mut self) {
self.instruction_weights_version = self.instruction_weights_version.checked_sub(1).unwrap();
}
/// Create the module without checking nor instrumenting the passed code.
/// Create the module without checking the passed code.
///
/// # Note
///
/// This is useful for benchmarking where we don't want instrumentation to skew
/// This is useful for benchmarking where we don't want validation of the module to skew
/// our results. This also does not collect any deposit from the `owner`. Also useful
/// during testing when we want to deploy codes that do not pass the instantiation checks.
#[cfg(any(test, feature = "runtime-benchmarks"))]
fn from_code_unchecked(
original_code: Vec<u8>,
code: Vec<u8>,
schedule: &Schedule<T>,
owner: T::AccountId,
) -> Result<Self, DispatchError> {
prepare::benchmarking::prepare(original_code, schedule, owner)
.map_err::<DispatchError, _>(Into::into)
prepare::benchmarking::prepare(code, schedule, owner)
}
}
impl<T: Config> OwnerInfo<T> {
impl<T: Config> CodeInfo<T> {
/// Return the refcount of the module.
#[cfg(test)]
pub fn refcount(&self) -> u64 {
@@ -291,21 +421,38 @@ impl<T: Config> OwnerInfo<T> {
}
}
impl<T: Config> Executable<T> for PrefabWasmModule<T> {
impl<T: Config> Executable<T> for WasmBlob<T> {
fn from_storage(
code_hash: CodeHash<T>,
schedule: &Schedule<T>,
gas_meter: &mut GasMeter<T>,
) -> Result<Self, DispatchError> {
code_cache::load(code_hash, schedule, gas_meter)
let code = Self::load_code(code_hash, gas_meter)?;
// We store `code_info` at the same time as contract code,
// therefore this query shouldn't really fail.
// We consider its failure equal to `CodeNotFound`, as contract code without
// `code_info` is unusable in this pallet.
let code_info = <CodeInfoOf<T>>::get(code_hash).ok_or(Error::<T>::CodeNotFound)?;
Ok(Self { code, code_info, code_hash })
}
fn add_user(code_hash: CodeHash<T>) -> Result<(), DispatchError> {
code_cache::increment_refcount::<T>(code_hash)
fn increment_refcount(code_hash: CodeHash<T>) -> Result<(), DispatchError> {
<CodeInfoOf<T>>::mutate(code_hash, |existing| -> Result<(), DispatchError> {
if let Some(info) = existing {
info.refcount = info.refcount.saturating_add(1);
Ok(())
} else {
Err(Error::<T>::CodeNotFound.into())
}
})
}
fn remove_user(code_hash: CodeHash<T>) {
code_cache::decrement_refcount::<T>(code_hash)
fn decrement_refcount(code_hash: CodeHash<T>) {
<CodeInfoOf<T>>::mutate(code_hash, |existing| {
if let Some(info) = existing {
info.refcount = info.refcount.saturating_sub(1);
}
});
}
fn execute<E: Ext<T = T>>(
@@ -314,23 +461,40 @@ impl<T: Config> Executable<T> for PrefabWasmModule<T> {
function: &ExportedFunction,
input_data: Vec<u8>,
) -> ExecResult {
let code = self.code.as_slice();
// Instantiate the Wasm module to the engine.
let runtime = Runtime::new(ext, input_data);
let schedule = <T>::Schedule::get();
let (mut store, memory, instance) = Self::instantiate::<crate::wasm::runtime::Env, _>(
self.code.as_slice(),
code,
runtime,
(self.initial, self.maximum),
&schedule,
StackLimits::default(),
match function {
ExportedFunction::Constructor => AllowDeprecatedInterface::No,
ExportedFunction::Call => AllowDeprecatedInterface::Yes,
ExportedFunction::Constructor => AllowDeprecatedInterface::No,
},
)
.map_err(|msg| {
log::debug!(target: LOG_TARGET, "failed to instantiate code: {}", msg);
log::debug!(target: LOG_TARGET, "failed to instantiate code to wasmi: {}", msg);
Error::<T>::CodeRejected
})?;
store.data_mut().set_memory(memory);
// Set fuel limit for the wasmi execution.
// We normalize it by the base instruction weight, as its cost in wasmi engine is `1`.
let fuel_limit = store
.data_mut()
.ext()
.gas_meter_mut()
.gas_left()
.ref_time()
.checked_div(T::Schedule::get().instruction_weights.base as u64)
.ok_or(Error::<T>::InvalidSchedule)?;
store
.add_fuel(fuel_limit)
.expect("We've set up engine to fuel consuming mode; qed");
let exported_func = instance
.get_export(&store, function.identifier())
.and_then(|export| export.into_func())
@@ -341,10 +505,14 @@ impl<T: Config> Executable<T> for PrefabWasmModule<T> {
// We store before executing so that the code hash is available in the constructor.
if let &ExportedFunction::Constructor = function {
code_cache::store(self, true)?;
Self::store_code(self, true)?;
}
let result = exported_func.call(&mut store, &[], &mut []);
let engine_consumed_total = store.fuel_consumed().expect("Fuel metering is enabled; qed");
// Sync this frame's gas meter with the engine's one.
let gas_meter = store.data_mut().ext().gas_meter_mut();
gas_meter.charge_fuel(engine_consumed_total)?;
store.into_data().to_execution_result(result)
}
@@ -358,7 +526,7 @@ impl<T: Config> Executable<T> for PrefabWasmModule<T> {
}
fn is_deterministic(&self) -> bool {
matches!(self.determinism, Determinism::Enforced)
matches!(self.code_info.determinism, Determinism::Enforced)
}
}
@@ -603,7 +771,10 @@ mod tests {
fn schedule(&self) -> &Schedule<Self::T> {
&self.schedule
}
fn gas_meter(&mut self) -> &mut GasMeter<Self::T> {
fn gas_meter(&self) -> &GasMeter<Self::T> {
&self.gas_meter
}
fn gas_meter_mut(&mut self) -> &mut GasMeter<Self::T> {
&mut self.gas_meter
}
fn append_debug_buffer(&mut self, msg: &str) -> bool {
@@ -660,13 +831,16 @@ mod tests {
type RuntimeConfig = <MockExt as Ext>::T;
RuntimeConfig::set_unstable_interface(unstable_interface);
let wasm = wat::parse_str(wat).unwrap();
let schedule = crate::Schedule::default();
let executable = if skip_checks {
PrefabWasmModule::<RuntimeConfig>::from_code_unchecked(wasm, &schedule, ALICE)?
} else {
PrefabWasmModule::<RuntimeConfig>::from_code(
WasmBlob::<RuntimeConfig>::from_code_unchecked(
wasm,
&schedule,
ext.borrow_mut().schedule(),
ALICE,
)?
} else {
WasmBlob::<RuntimeConfig>::from_code(
wasm,
ext.borrow_mut().schedule(),
ALICE,
Determinism::Enforced,
TryInstantiate::Instantiate,
@@ -3161,25 +3335,6 @@ mod tests {
execute(CODE, vec![], &mut mock_ext).unwrap();
}
/// Code with deprecated functions cannot be uploaded or instantiated. However, we
/// need to make sure that it still can be re-instrumented.
#[test]
fn can_reinstrument_deprecated() {
const CODE_RANDOM: &str = r#"
(module
(import "seal0" "random" (func $seal_random (param i32 i32 i32 i32)))
(func (export "call"))
(func (export "deploy"))
)
"#;
let wasm = wat::parse_str(CODE_RANDOM).unwrap();
let schedule = crate::Schedule::<Test>::default();
#[cfg(not(feature = "runtime-benchmarks"))]
assert_err!(execute(CODE_RANDOM, vec![], MockExt::default()), <Error<Test>>::CodeRejected);
self::prepare::reinstrument::<runtime::Env, Test>(&wasm, &schedule, Determinism::Enforced)
.unwrap();
}
/// This test check that an unstable interface cannot be deployed. In case of runtime
/// benchmarks we always allow unstable interfaces. This is why this test does not
/// work when this feature is enabled.