contracts: Replace sp-sandbox and wasmi-validation by newest wasmi (#12501)

* Replace sp-sandbox and wasmi-validation by just wasmi

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

* Re-check original code on re-instrumentation

* Fix clippy

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

* Apply suggestions from code review

Co-authored-by: Robin Freyler <robin.freyler@gmail.com>

* Replace wasmi by ::wasmi

* Bump wasmi to 0.20

* Add explanation for `unreachable`

* Change proof

* Fixup master merge

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

* Fixup naming inconsistencies introduced by reentrancy PR

* Fix `scan_imports` docs

* Apply suggestions from code review

Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>

* Fixup suggestions

* Remove unnecessary &mut

* Fix test

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

* Fix benchmark merge fail

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

* Fix docs as suggested by code review

* Improve docs for `CodeRejected`

* Apply suggestions from code review

Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>

* Fix logic bug when setting `deterministic_only`

* Don't panic when module fails to compile

* Apply suggestions from code review

Co-authored-by: Robin Freyler <robin.freyler@gmail.com>

Co-authored-by: command-bot <>
Co-authored-by: Robin Freyler <robin.freyler@gmail.com>
Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>
This commit is contained in:
Alexander Theißen
2022-11-24 23:51:36 +01:00
committed by GitHub
parent e69c3649b5
commit 08657f14b7
23 changed files with 1909 additions and 1634 deletions
@@ -28,10 +28,6 @@ use crate::{Config, Determinism};
use frame_support::traits::Get;
use sp_core::crypto::UncheckedFrom;
use sp_runtime::traits::Hash;
use sp_sandbox::{
default_executor::{EnvironmentDefinitionBuilder, Memory},
SandboxEnvironmentBuilder, SandboxMemory,
};
use sp_std::{borrow::ToOwned, prelude::*};
use wasm_instrument::parity_wasm::{
builder,
@@ -128,7 +124,7 @@ pub struct ImportedFunction {
pub struct WasmModule<T: Config> {
pub code: Vec<u8>,
pub hash: <T::Hashing as Hash>::Output,
memory: Option<ImportedMemory>,
pub memory: Option<ImportedMemory>,
}
impl<T: Config> From<ModuleDefinition> for WasmModule<T>
@@ -395,16 +391,6 @@ where
.into()
}
/// Creates a memory instance for use in a sandbox with dimensions declared in this module
/// and adds it to `env`. A reference to that memory is returned so that it can be used to
/// access the memory contents from the supervisor.
pub fn add_memory<S>(&self, env: &mut EnvironmentDefinitionBuilder<S>) -> Option<Memory> {
let memory = if let Some(memory) = &self.memory { memory } else { return None };
let memory = Memory::new(memory.min_pages, Some(memory.max_pages)).unwrap();
env.add_memory("env", "memory", memory.clone());
Some(memory)
}
pub fn unary_instr(instr: Instruction, repeat: u32) -> Self {
use body::DynInstr::{RandomI64Repeated, Regular};
ModuleDefinition {
@@ -425,7 +425,7 @@ benchmarks! {
.map(|n| account::<T::AccountId>("account", n, 0))
.collect::<Vec<_>>();
let account_len = accounts.get(0).map(|i| i.encode().len()).unwrap_or(0);
let accounts_bytes = accounts.iter().map(|a| a.encode()).flatten().collect::<Vec<_>>();
let accounts_bytes = accounts.iter().flat_map(|a| a.encode()).collect::<Vec<_>>();
let code = WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
imported_functions: vec![ImportedFunction {
@@ -462,7 +462,7 @@ benchmarks! {
.map(|n| account::<T::AccountId>("account", n, 0))
.collect::<Vec<_>>();
let account_len = accounts.get(0).map(|i| i.encode().len()).unwrap_or(0);
let accounts_bytes = accounts.iter().map(|a| a.encode()).flatten().collect::<Vec<_>>();
let accounts_bytes = accounts.iter().flat_map(|a| a.encode()).collect::<Vec<_>>();
let accounts_len = accounts_bytes.len();
let pages = code::max_pages::<T>();
let code = WasmModule::<T>::from(ModuleDefinition {
@@ -2014,10 +2014,9 @@ benchmarks! {
let r in 0 .. 1;
let key_type = sp_core::crypto::KeyTypeId(*b"code");
let pub_keys_bytes = (0..r * API_BENCHMARK_BATCH_SIZE)
.map(|_| {
.flat_map(|_| {
sp_io::crypto::ecdsa_generate(key_type, None).0
})
.flatten()
.collect::<Vec<_>>();
let pub_keys_bytes_len = pub_keys_bytes.len() as i32;
let code = WasmModule::<T>::from(ModuleDefinition {
@@ -2086,13 +2085,13 @@ benchmarks! {
let origin = RawOrigin::Signed(instance.caller.clone());
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
reentrant_count {
seal_reentrance_count {
let r in 0 .. API_BENCHMARK_BATCHES;
let code = WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
imported_functions: vec![ImportedFunction {
module: "__unstable__",
name: "reentrant_count",
name: "reentrance_count",
params: vec![],
return_type: Some(ValueType::I32),
}],
@@ -2106,7 +2105,7 @@ benchmarks! {
let origin = RawOrigin::Signed(instance.caller.clone());
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
account_reentrance_count {
seal_account_reentrance_count {
let r in 0 .. API_BENCHMARK_BATCHES;
let dummy_code = WasmModule::<T>::dummy_with_bytes(0);
let accounts = (0..r * API_BENCHMARK_BATCH_SIZE)
@@ -2921,7 +2920,7 @@ benchmarks! {
#[extra]
ink_erc20_transfer {
let g in 0 .. 1;
let gas_metering = if g == 0 { false } else { true };
let gas_metering = g != 0;
let code = load_benchmark!("ink_erc20");
let data = {
let new: ([u8; 4], BalanceOf<T>) = ([0x9b, 0xae, 0x9d, 0x5e], 1000u32.into());
@@ -2959,7 +2958,7 @@ benchmarks! {
#[extra]
solang_erc20_transfer {
let g in 0 .. 1;
let gas_metering = if g == 0 { false } else { true };
let gas_metering = g != 0;
let code = include_bytes!("../../benchmarks/solang_erc20.wasm");
let caller = account::<T::AccountId>("instantiator", 0, 0);
let mut balance = [0u8; 32];
@@ -19,22 +19,20 @@
/// ! sandbox to execute the wasm code. This is because we do not need the full
/// ! environment that provides the seal interface as imported functions.
use super::{code::WasmModule, Config};
use crate::wasm::{Environment, PrefabWasmModule};
use sp_core::crypto::UncheckedFrom;
use sp_sandbox::{
default_executor::{EnvironmentDefinitionBuilder, Instance, Memory},
SandboxEnvironmentBuilder, SandboxInstance,
};
use wasmi::{errors::LinkerError, Func, Linker, StackLimits, Store};
/// Minimal execution environment without any exported functions.
/// Minimal execution environment without any imported functions.
pub struct Sandbox {
instance: Instance<()>,
_memory: Option<Memory>,
entry_point: Func,
store: Store<()>,
}
impl Sandbox {
/// Invoke the `call` function of a contract code and panic on any execution error.
pub fn invoke(&mut self) {
self.instance.invoke("call", &[], &mut ()).unwrap();
self.entry_point.call(&mut self.store, &[], &mut []).unwrap();
}
}
@@ -46,10 +44,27 @@ where
/// Creates an instance from the supplied module and supplies as much memory
/// to the instance as the module declares as imported.
fn from(module: &WasmModule<T>) -> Self {
let mut env_builder = EnvironmentDefinitionBuilder::new();
let memory = module.add_memory(&mut env_builder);
let instance = Instance::new(&module.code, &env_builder, &mut ())
.expect("Failed to create benchmarking Sandbox instance");
Self { instance, _memory: memory }
let memory = module
.memory
.as_ref()
.map(|mem| (mem.min_pages, mem.max_pages))
.unwrap_or((0, 0));
let (store, _memory, instance) = PrefabWasmModule::<T>::instantiate::<EmptyEnv, _>(
&module.code,
(),
memory,
StackLimits::default(),
)
.expect("Failed to create benchmarking Sandbox instance");
let entry_point = instance.get_export(&store, "call").unwrap().into_func().unwrap();
Self { entry_point, store }
}
}
struct EmptyEnv;
impl Environment<()> for EmptyEnv {
fn define(_store: &mut Store<()>, _linker: &mut Linker<()>) -> Result<(), LinkerError> {
Ok(())
}
}
@@ -270,6 +270,7 @@ impl<'a, 'b, E: Ext> Environment<'a, 'b, E, InitState> {
/// ever create this type. Chain extensions merely consume it.
pub(crate) fn new(
runtime: &'a mut Runtime<'b, E>,
memory: &'a mut [u8],
id: u32,
input_ptr: u32,
input_len: u32,
@@ -277,7 +278,7 @@ impl<'a, 'b, E: Ext> Environment<'a, 'b, E, InitState> {
output_len_ptr: u32,
) -> Self {
Environment {
inner: Inner { runtime, id, input_ptr, input_len, output_ptr, output_len_ptr },
inner: Inner { runtime, memory, id, input_ptr, input_len, output_ptr, output_len_ptr },
phantom: PhantomData,
}
}
@@ -338,9 +339,11 @@ where
/// charge the overall costs either using `max_len` (worst case approximation) or using
/// [`in_len()`](Self::in_len).
pub fn read(&self, max_len: u32) -> Result<Vec<u8>> {
self.inner
.runtime
.read_sandbox_memory(self.inner.input_ptr, self.inner.input_len.min(max_len))
self.inner.runtime.read_sandbox_memory(
self.inner.memory,
self.inner.input_ptr,
self.inner.input_len.min(max_len),
)
}
/// Reads `min(buffer.len(), in_len) from contract memory.
@@ -354,7 +357,11 @@ where
let buffer = core::mem::take(buffer);
&mut buffer[..len.min(self.inner.input_len as usize)]
};
self.inner.runtime.read_sandbox_memory_into_buf(self.inner.input_ptr, sliced)?;
self.inner.runtime.read_sandbox_memory_into_buf(
self.inner.memory,
self.inner.input_ptr,
sliced,
)?;
*buffer = sliced;
Ok(())
}
@@ -366,14 +373,20 @@ where
/// weight of the chain extension. This should usually be the case when fixed input types
/// are used.
pub fn read_as<T: Decode + MaxEncodedLen>(&mut self) -> Result<T> {
self.inner.runtime.read_sandbox_memory_as(self.inner.input_ptr)
self.inner
.runtime
.read_sandbox_memory_as(self.inner.memory, self.inner.input_ptr)
}
/// Reads and decodes a type with a dynamic size from contract memory.
///
/// Make sure to include `len` in your weight calculations.
pub fn read_as_unbounded<T: Decode>(&mut self, len: u32) -> Result<T> {
self.inner.runtime.read_sandbox_memory_as_unbounded(self.inner.input_ptr, len)
self.inner.runtime.read_sandbox_memory_as_unbounded(
self.inner.memory,
self.inner.input_ptr,
len,
)
}
/// The length of the input as passed in as `input_len`.
@@ -406,6 +419,7 @@ where
weight_per_byte: Option<Weight>,
) -> Result<()> {
self.inner.runtime.write_sandbox_output(
self.inner.memory,
self.inner.output_ptr,
self.inner.output_len_ptr,
buffer,
@@ -426,6 +440,8 @@ where
struct Inner<'a, 'b, E: Ext> {
/// The runtime contains all necessary functions to interact with the running contract.
runtime: &'a mut Runtime<'b, E>,
/// Reference to the contracts memory.
memory: &'a mut [u8],
/// Verbatim argument passed to `seal_call_chain_extension`.
id: u32,
/// Verbatim argument passed to `seal_call_chain_extension`.
+2 -2
View File
@@ -299,7 +299,7 @@ pub trait Ext: sealing::Sealed {
/// Returns the number of times the currently executing contract exists on the call stack in
/// addition to the calling instance. A value of 0 means no reentrancy.
fn reentrant_count(&self) -> u32;
fn reentrance_count(&self) -> u32;
/// Returns the number of times the specified contract exists on the call stack. Delegated calls
/// are not calculated as separate entrance.
@@ -1384,7 +1384,7 @@ where
Ok(())
}
fn reentrant_count(&self) -> u32 {
fn reentrance_count(&self) -> u32 {
let id: &AccountIdOf<Self::T> = &self.top_frame().account_id;
self.account_reentrance_count(id).saturating_sub(1)
}
+16 -4
View File
@@ -102,7 +102,7 @@ use crate::{
exec::{AccountIdOf, ExecError, Executable, Stack as ExecStack},
gas::GasMeter,
storage::{meter::Meter as StorageMeter, ContractInfo, DeletedContract, Storage},
wasm::{OwnerInfo, PrefabWasmModule},
wasm::{OwnerInfo, PrefabWasmModule, TryInstantiate},
weights::WeightInfo,
};
use codec::{Codec, Encode, HasCompact};
@@ -830,8 +830,13 @@ pub mod pallet {
/// to determine whether a reversion has taken place.
ContractReverted,
/// The contract's code was found to be invalid during validation or instrumentation.
///
/// The most likely cause of this is that an API was used which is not supported by the
/// node. This hapens if an older node is used with a new version of ink!. Try updating
/// your node to the newest available version.
///
/// A more detailed error can be found on the node console if debug messages are enabled
/// or in the debug buffer which is returned to RPC clients.
/// by supplying `-lruntime::contracts=debug`.
CodeRejected,
/// An indetermistic code was used in a context where this is not permitted.
Indeterministic,
@@ -1009,8 +1014,14 @@ where
determinism: Determinism,
) -> CodeUploadResult<CodeHash<T>, BalanceOf<T>> {
let schedule = T::Schedule::get();
let module = PrefabWasmModule::from_code(code, &schedule, origin, determinism)
.map_err(|(err, _)| err)?;
let module = PrefabWasmModule::from_code(
code,
&schedule,
origin,
determinism,
TryInstantiate::Instantiate,
)
.map_err(|(err, _)| err)?;
let deposit = module.open_deposit();
if let Some(storage_deposit_limit) = storage_deposit_limit {
ensure!(storage_deposit_limit >= deposit, <Error<T>>::StorageDepositLimitExhausted);
@@ -1135,6 +1146,7 @@ where
&schedule,
origin.clone(),
Determinism::Deterministic,
TryInstantiate::Skip,
)
.map_err(|(err, msg)| {
debug_message.as_mut().map(|buffer| buffer.extend(msg.as_bytes()));
+4 -4
View File
@@ -423,8 +423,8 @@ pub struct HostFnWeights<T: Config> {
/// Weight of calling `seal_ecdsa_to_eth_address`.
pub ecdsa_to_eth_address: u64,
/// Weight of calling `seal_reentrant_count`.
pub reentrant_count: u64,
/// Weight of calling `seal_reentrance_count`.
pub reentrance_count: u64,
/// Weight of calling `seal_account_reentrance_count`.
pub account_reentrance_count: u64,
@@ -538,7 +538,7 @@ impl<T: Config> Default for InstructionWeights<T> {
fn default() -> Self {
let max_pages = Limits::default().memory_pages;
Self {
version: 3,
version: 4,
fallback: 0,
i64const: cost_instr!(instr_i64const, 1),
i64load: cost_instr!(instr_i64load, 2),
@@ -665,7 +665,7 @@ impl<T: Config> Default for HostFnWeights<T> {
hash_blake2_128_per_byte: cost_byte_batched!(seal_hash_blake2_128_per_kb),
ecdsa_recover: cost_batched!(seal_ecdsa_recover),
ecdsa_to_eth_address: cost_batched!(seal_ecdsa_to_eth_address),
reentrant_count: cost_batched!(seal_reentrant_count),
reentrance_count: cost_batched!(seal_reentrance_count),
account_reentrance_count: cost_batched!(seal_account_reentrance_count),
_phantom: PhantomData,
}
+37 -11
View File
@@ -514,7 +514,7 @@ fn calling_plain_account_fails() {
#[test]
fn instantiate_and_call_and_deposit_event() {
let (wasm, code_hash) = compile_module::<Test>("return_from_start_fn").unwrap();
let (wasm, code_hash) = compile_module::<Test>("event_and_return_on_deploy").unwrap();
ExtBuilder::default().existential_deposit(500).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
@@ -3720,10 +3720,36 @@ fn contract_reverted() {
#[test]
fn code_rejected_error_works() {
let (wasm, _) = compile_module::<Test>("invalid_import").unwrap();
ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
let (wasm, _) = compile_module::<Test>("invalid_module").unwrap();
assert_noop!(
Contracts::upload_code(
RuntimeOrigin::signed(ALICE),
wasm.clone(),
None,
Determinism::Deterministic
),
<Error<Test>>::CodeRejected,
);
let result = Contracts::bare_instantiate(
ALICE,
0,
GAS_LIMIT,
None,
Code::Upload(wasm),
vec![],
vec![],
true,
);
assert_err!(result.result, <Error<Test>>::CodeRejected);
assert_eq!(
std::str::from_utf8(&result.debug_message).unwrap(),
"validation of new code failed"
);
let (wasm, _) = compile_module::<Test>("invalid_contract").unwrap();
assert_noop!(
Contracts::upload_code(
RuntimeOrigin::signed(ALICE),
@@ -3747,7 +3773,7 @@ fn code_rejected_error_works() {
assert_err!(result.result, <Error<Test>>::CodeRejected);
assert_eq!(
std::str::from_utf8(&result.debug_message).unwrap(),
"module imports a non-existent function"
"call function isn't exported"
);
});
}
@@ -4386,8 +4412,8 @@ fn delegate_call_indeterministic_code() {
#[test]
#[cfg(feature = "unstable-interface")]
fn reentrant_count_works_with_call() {
let (wasm, code_hash) = compile_module::<Test>("reentrant_count_call").unwrap();
fn reentrance_count_works_with_call() {
let (wasm, code_hash) = compile_module::<Test>("reentrance_count_call").unwrap();
let contract_addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
@@ -4423,8 +4449,8 @@ fn reentrant_count_works_with_call() {
#[test]
#[cfg(feature = "unstable-interface")]
fn reentrant_count_works_with_delegated_call() {
let (wasm, code_hash) = compile_module::<Test>("reentrant_count_delegated_call").unwrap();
fn reentrance_count_works_with_delegated_call() {
let (wasm, code_hash) = compile_module::<Test>("reentrance_count_delegated_call").unwrap();
let contract_addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
@@ -4462,8 +4488,8 @@ fn reentrant_count_works_with_delegated_call() {
#[cfg(feature = "unstable-interface")]
fn account_reentrance_count_works() {
let (wasm, code_hash) = compile_module::<Test>("account_reentrance_count_call").unwrap();
let (wasm_reentrant_count, code_hash_reentrant_count) =
compile_module::<Test>("reentrant_count_call").unwrap();
let (wasm_reentrance_count, code_hash_reentrance_count) =
compile_module::<Test>("reentrance_count_call").unwrap();
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
@@ -4483,14 +4509,14 @@ fn account_reentrance_count_works() {
300_000,
GAS_LIMIT,
None,
wasm_reentrant_count,
wasm_reentrance_count,
vec![],
vec![]
));
let contract_addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
let another_contract_addr =
Contracts::contract_address(&ALICE, &code_hash_reentrant_count, &[]);
Contracts::contract_address(&ALICE, &code_hash_reentrance_count, &[]);
let result1 = Contracts::bare_call(
ALICE,
@@ -192,7 +192,10 @@ where
pub fn reinstrument<T: Config>(
prefab_module: &mut PrefabWasmModule<T>,
schedule: &Schedule<T>,
) -> Result<u32, DispatchError> {
) -> Result<u32, DispatchError>
where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
let original_code =
<PristineCode<T>>::get(&prefab_module.code_hash).ok_or(Error::<T>::CodeNotFound)?;
let original_code_len = original_code.len();
@@ -201,9 +204,12 @@ pub fn reinstrument<T: Config>(
// 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_contract::<T>(&original_code, schedule, prefab_module.determinism)
.map_err(|_| <Error<T>>::CodeRejected)?,
Some("Contract exceeds limit after re-instrumentation."),
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);
@@ -1,83 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 2018-2022 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.
use super::Runtime;
use crate::exec::Ext;
use sp_sandbox::Value;
use wasm_instrument::parity_wasm::elements::{FunctionType, ValueType};
pub trait ConvertibleToWasm: Sized {
const VALUE_TYPE: ValueType;
type NativeType;
fn to_typed_value(self) -> Value;
fn from_typed_value(_: Value) -> Option<Self>;
}
impl ConvertibleToWasm for i32 {
const VALUE_TYPE: ValueType = ValueType::I32;
type NativeType = i32;
fn to_typed_value(self) -> Value {
Value::I32(self)
}
fn from_typed_value(v: Value) -> Option<Self> {
v.as_i32()
}
}
impl ConvertibleToWasm for u32 {
const VALUE_TYPE: ValueType = ValueType::I32;
type NativeType = u32;
fn to_typed_value(self) -> Value {
Value::I32(self as i32)
}
fn from_typed_value(v: Value) -> Option<Self> {
match v {
Value::I32(v) => Some(v as u32),
_ => None,
}
}
}
impl ConvertibleToWasm for u64 {
const VALUE_TYPE: ValueType = ValueType::I64;
type NativeType = u64;
fn to_typed_value(self) -> Value {
Value::I64(self as i64)
}
fn from_typed_value(v: Value) -> Option<Self> {
match v {
Value::I64(v) => Some(v as u64),
_ => None,
}
}
}
pub type HostFunc<E> = fn(
&mut Runtime<E>,
&[sp_sandbox::Value],
) -> Result<sp_sandbox::ReturnValue, sp_sandbox::HostError>;
pub trait FunctionImplProvider<E: Ext> {
fn impls<F: FnMut(&[u8], &[u8], HostFunc<E>)>(f: &mut F);
}
/// This trait can be used to check whether the host environment can satisfy
/// a requested function import.
pub trait ImportSatisfyCheck {
/// Returns `true` if the host environment contains a function with
/// the specified name and its type matches to the given type, or `false`
/// otherwise.
fn can_satisfy(module: &[u8], name: &[u8], func_type: &FunctionType) -> bool;
}
+86 -50
View File
@@ -18,19 +18,19 @@
//! This module provides a means for executing contracts
//! represented in wasm.
#[macro_use]
mod env_def;
mod code_cache;
mod prepare;
mod runtime;
#[cfg(feature = "runtime-benchmarks")]
pub use crate::wasm::code_cache::reinstrument;
pub use crate::wasm::runtime::{CallFlags, ReturnCode, Runtime, RuntimeCosts};
pub use crate::wasm::{
prepare::TryInstantiate,
runtime::{CallFlags, Environment, ReturnCode, Runtime, RuntimeCosts},
};
use crate::{
exec::{ExecResult, Executable, ExportedFunction, Ext},
gas::GasMeter,
wasm::env_def::FunctionImplProvider,
AccountIdOf, BalanceOf, CodeHash, CodeStorage, CodeVec, Config, Error, RelaxedCodeVec,
Schedule,
};
@@ -38,10 +38,12 @@ use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::dispatch::{DispatchError, DispatchResult};
use sp_core::crypto::UncheckedFrom;
use sp_runtime::RuntimeDebug;
use sp_sandbox::{SandboxEnvironmentBuilder, SandboxInstance, SandboxMemory};
use sp_std::prelude::*;
#[cfg(test)]
pub use tests::MockExt;
use wasmi::{
Config as WasmiConfig, Engine, Instance, Linker, Memory, MemoryType, Module, StackLimits, Store,
};
/// A prepared wasm module ready for execution.
///
@@ -151,12 +153,14 @@ where
schedule: &Schedule<T>,
owner: AccountIdOf<T>,
determinism: Determinism,
try_instantiate: TryInstantiate,
) -> Result<Self, (DispatchError, &'static str)> {
let module = prepare::prepare_contract(
let module = prepare::prepare::<runtime::Env, T>(
original_code.try_into().map_err(|_| (<Error<T>>::CodeTooLarge.into(), ""))?,
schedule,
owner,
determinism,
try_instantiate,
)?;
Ok(module)
}
@@ -189,6 +193,44 @@ where
}
}
/// Creates and returns an instance of the supplied code.
///
/// 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.
pub fn instantiate<E, H>(
code: &[u8],
host_state: H,
memory: (u32, u32),
stack_limits: StackLimits,
) -> Result<(Store<H>, Memory, Instance), wasmi::Error>
where
E: Environment<H>,
{
let mut config = WasmiConfig::default();
config
.set_stack_limits(stack_limits)
.wasm_multi_value(false)
.wasm_mutable_global(false)
.wasm_sign_extension(false)
.wasm_saturating_float_to_int(false);
let engine = Engine::new(&config);
let module = Module::new(&engine, code)?;
let mut store = Store::new(&engine, host_state);
let mut linker = Linker::new();
E::define(&mut store, &mut linker)?;
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",
);
linker
.define("env", "memory", memory)
.expect("We just created the linker. It has no define with this name attached; qed");
let instance = linker.instantiate(&mut store, &module)?.ensure_no_start(&mut store)?;
Ok((store, memory, instance))
}
/// Create and store the module without checking nor instrumenting the passed code.
///
/// # Note
@@ -201,7 +243,7 @@ where
schedule: &Schedule<T>,
owner: T::AccountId,
) -> DispatchResult {
let executable = prepare::benchmarking::prepare_contract(original_code, schedule, owner)
let executable = prepare::benchmarking::prepare(original_code, schedule, owner)
.map_err::<DispatchError, _>(Into::into)?;
code_cache::store(executable, false)
}
@@ -247,36 +289,35 @@ where
function: &ExportedFunction,
input_data: Vec<u8>,
) -> ExecResult {
let memory = sp_sandbox::default_executor::Memory::new(self.initial, Some(self.maximum))
.unwrap_or_else(|_| {
// unlike `.expect`, explicit panic preserves the source location.
// Needed as we can't use `RUST_BACKTRACE` in here.
panic!(
"exec.prefab_module.initial can't be greater than exec.prefab_module.maximum;
thus Memory::new must not fail;
qed"
)
});
let runtime = Runtime::new(ext, input_data);
let (mut store, memory, instance) = Self::instantiate::<crate::wasm::runtime::Env, _>(
self.code.as_slice(),
runtime,
(self.initial, self.maximum),
StackLimits::default(),
)
.map_err(|msg| {
log::debug!(target: "runtime::contracts", "failed to instantiate code: {}", msg);
Error::<T>::CodeRejected
})?;
store.state_mut().set_memory(memory);
let mut imports = sp_sandbox::default_executor::EnvironmentDefinitionBuilder::new();
imports.add_memory(self::prepare::IMPORT_MODULE_MEMORY, "memory", memory.clone());
runtime::Env::impls(&mut |module, name, func_ptr| {
imports.add_host_func(module, name, func_ptr);
});
let exported_func = instance
.get_export(&store, function.identifier())
.and_then(|export| export.into_func())
.ok_or_else(|| {
log::error!(target: "runtime::contracts", "failed to find entry point");
Error::<T>::CodeRejected
})?;
// We store before executing so that the code hash is available in the constructor.
let code = self.code.clone();
if let &ExportedFunction::Constructor = function {
code_cache::store(self, true)?;
}
// Instantiate the instance from the instrumented module code and invoke the contract
// entrypoint.
let mut runtime = Runtime::new(ext, input_data, memory);
let result = sp_sandbox::default_executor::Instance::new(&code, &imports, &mut runtime)
.and_then(|mut instance| instance.invoke(function.identifier(), &[], &mut runtime));
let result = exported_func.call(&mut store, &[], &mut []);
runtime.to_execution_result(result)
store.into_state().to_execution_result(result)
}
fn code_hash(&self) -> &CodeHash<T> {
@@ -307,7 +348,7 @@ mod tests {
};
use assert_matches::assert_matches;
use frame_support::{
assert_ok,
assert_err, assert_ok,
dispatch::DispatchResultWithPostInfo,
weights::{OldWeight, Weight},
};
@@ -578,7 +619,7 @@ mod tests {
fn ecdsa_to_eth_address(&self, _pk: &[u8; 33]) -> Result<[u8; 20], ()> {
Ok([2u8; 20])
}
fn reentrant_count(&self) -> u32 {
fn reentrance_count(&self) -> u32 {
12
}
fn account_reentrance_count(&self, _account_id: &AccountIdOf<Self::T>) -> u32 {
@@ -594,8 +635,9 @@ mod tests {
&schedule,
ALICE,
Determinism::Deterministic,
TryInstantiate::Skip,
)
.unwrap();
.map_err(|err| err.0)?;
executable.execute(ext.borrow_mut(), &ExportedFunction::Call, input_data)
}
@@ -788,10 +830,7 @@ mod tests {
"#;
let mut mock_ext = MockExt::default();
let input = vec![0xff, 0x2a, 0x99, 0x88];
frame_support::assert_err!(
execute(CODE, input.clone(), &mut mock_ext),
<Error<Test>>::InputForwarded,
);
assert_err!(execute(CODE, input.clone(), &mut mock_ext), <Error<Test>>::InputForwarded,);
assert_eq!(
&mock_ext.calls,
@@ -1584,35 +1623,32 @@ mod tests {
assert_ok!(execute(CODE_VALUE_TRANSFERRED, vec![], MockExt::default()));
}
const CODE_RETURN_FROM_START_FN: &str = r#"
const START_FN_ILLEGAL: &str = r#"
(module
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
(import "env" "memory" (memory 1 1))
(start $start)
(func $start
(call $seal_return
(i32.const 0)
(i32.const 8)
(i32.const 4)
)
(unreachable)
)
(func (export "call")
(unreachable)
)
(func (export "deploy"))
(func (export "deploy")
(unreachable)
)
(data (i32.const 8) "\01\02\03\04")
)
"#;
#[test]
fn return_from_start_fn() {
let output = execute(CODE_RETURN_FROM_START_FN, vec![], MockExt::default()).unwrap();
assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: vec![1, 2, 3, 4] });
fn start_fn_illegal() {
let output = execute(START_FN_ILLEGAL, vec![], MockExt::default());
assert_err!(output, <Error<Test>>::CodeRejected,);
}
const CODE_TIMESTAMP_NOW: &str = r#"
@@ -2859,10 +2895,10 @@ mod tests {
#[test]
#[cfg(feature = "unstable-interface")]
fn reentrant_count_works() {
fn reentrance_count_works() {
const CODE: &str = r#"
(module
(import "__unstable__" "reentrant_count" (func $reentrant_count (result i32)))
(import "__unstable__" "reentrance_count" (func $reentrance_count (result i32)))
(import "env" "memory" (memory 1 1))
(func $assert (param i32)
(block $ok
@@ -2875,7 +2911,7 @@ mod tests {
(func (export "call")
(local $return_val i32)
(set_local $return_val
(call $reentrant_count)
(call $reentrance_count)
)
(call $assert
(i32.eq (get_local $return_val) (i32.const 12))
+161 -92
View File
@@ -22,20 +22,38 @@
use crate::{
chain_extension::ChainExtension,
storage::meter::Diff,
wasm::{env_def::ImportSatisfyCheck, Determinism, OwnerInfo, PrefabWasmModule},
wasm::{Determinism, Environment, OwnerInfo, PrefabWasmModule},
AccountIdOf, CodeVec, Config, Error, Schedule,
};
use codec::{Encode, MaxEncodedLen};
use sp_core::crypto::UncheckedFrom;
use sp_runtime::{traits::Hash, DispatchError};
use sp_std::prelude::*;
use wasm_instrument::parity_wasm::elements::{
self, External, Internal, MemoryType, Type, ValueType,
};
use wasmi::StackLimits;
use wasmparser::{Validator, WasmFeatures};
/// Imported memory must be located inside this module. The reason for hardcoding is that current
/// compiler toolchains might not support specifying other modules than "env" for memory imports.
pub const IMPORT_MODULE_MEMORY: &str = "env";
/// Determines whether a module should be instantiated during preparation.
pub enum TryInstantiate {
/// Do the instantiation to make sure that the module is valid.
///
/// This should be used if a module is only uploaded but not executed. We need
/// to make sure that it can be actually instantiated.
Instantiate,
/// Skip the instantiation during preparation.
///
/// This makes sense when the preparation takes place as part of an instantation. Then
/// this instantiation would fail the whole transaction and an extra check is not
/// necessary.
Skip,
}
struct ContractModule<'a, T: Config> {
/// A deserialized module. The module is valid (this is Guaranteed by `new` method).
module: elements::Module,
@@ -48,14 +66,9 @@ impl<'a, T: Config> ContractModule<'a, T> {
/// Returns `Err` if the `original_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> {
use wasmi_validation::{validate_module, PlainValidator};
let module =
elements::deserialize_buffer(original_code).map_err(|_| "Can't decode wasm code")?;
// Make sure that the module is valid.
validate_module::<PlainValidator>(&module, ()).map_err(|_| "Module is not valid")?;
// Return a `ContractModule` instance with
// __valid__ module.
Ok(ContractModule { module, schedule })
@@ -279,27 +292,33 @@ impl<'a, T: Config> ContractModule<'a, T> {
/// Scan an import section if any.
///
/// This accomplishes two tasks:
/// 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.
///
/// - checks any imported function against defined host functions set, incl. their signatures.
/// - if there is a memory import, returns it's descriptor
/// `import_fn_banlist`: list of function names that are disallowed to be imported
fn scan_imports<C: ImportSatisfyCheck>(
fn scan_imports(
&self,
import_fn_banlist: &[&[u8]],
) -> Result<Option<&MemoryType>, &'static str> {
let module = &self.module;
let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]);
let import_entries = module.import_section().map(|is| is.entries()).unwrap_or(&[]);
let mut imported_mem_type = None;
for import in import_entries {
let type_idx = match *import.external() {
match *import.external() {
External::Table(_) => return Err("Cannot import tables"),
External::Global(_) => return Err("Cannot import globals"),
External::Function(ref type_idx) => type_idx,
External::Function(_) => {
if !T::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 {
return Err("Invalid module for imported memory")
@@ -313,22 +332,6 @@ impl<'a, T: Config> ContractModule<'a, T> {
imported_mem_type = Some(memory_type);
continue
},
};
let Type::Function(ref func_ty) = types
.get(*type_idx as usize)
.ok_or("validation: import entry points to a non-existent type")?;
if !T::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) ||
!C::can_satisfy(import.module().as_bytes(), import.field().as_bytes(), func_ty)
{
return Err("module imports a non-existent function")
}
}
Ok(imported_mem_type)
@@ -366,12 +369,54 @@ fn get_memory_limits<T: Config>(
}
}
fn check_and_instrument<C: ImportSatisfyCheck, T: Config>(
/// Check and instrument the given `original_code`.
///
/// 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],
schedule: &Schedule<T>,
determinism: Determinism,
) -> Result<(Vec<u8>, (u32, u32)), &'static str> {
let result = (|| {
try_instantiate: TryInstantiate,
) -> Result<(Vec<u8>, (u32, u32)), (DispatchError, &'static str)>
where
E: Environment<()>,
T: Config,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
// Do not enable any features here. Any additional feature needs to be carefully
// checked for potential security issues. For example, enabling multi value could lead
// to a DoS vector: It breaks our assumption that branch instructions are of constant time.
// Depending on the implementation they can linearly depend on the amount of values returned
// from a block.
Validator::new_with_features(WasmFeatures {
relaxed_simd: false,
threads: false,
tail_call: false,
multi_memory: false,
exceptions: false,
memory64: false,
extended_const: false,
component_model: false,
// This is not our only defense: We check for float types later in the preparation
// process. Additionally, all instructions explictily need to have weights assigned
// or the deployment will fail. We have none assigned for float instructions.
deterministic_only: matches!(determinism, Determinism::Deterministic),
mutable_global: false,
saturating_float_to_int: false,
sign_extension: false,
bulk_memory: false,
multi_value: false,
reference_types: false,
simd: false,
})
.validate_all(original_code)
.map_err(|err| {
log::debug!(target: "runtime::contracts", "{}", err);
(Error::<T>::CodeRejected.into(), "validation of new code failed")
})?;
let (code, (initial, maximum)) = (|| {
let contract_module = ContractModule::new(original_code, schedule)?;
contract_module.scan_exports()?;
contract_module.ensure_no_internal_memory()?;
@@ -387,7 +432,7 @@ fn check_and_instrument<C: ImportSatisfyCheck, T: Config>(
// 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::<C>(&disallowed_imports)?, schedule)?;
get_memory_limits(contract_module.scan_imports(&disallowed_imports)?, schedule)?;
let code = contract_module
.inject_gas_metering(determinism)?
@@ -395,24 +440,56 @@ fn check_and_instrument<C: ImportSatisfyCheck, T: Config>(
.into_wasm_code()?;
Ok((code, memory_limits))
})();
})()
.map_err(|msg: &str| {
log::debug!(target: "runtime::contracts", "new code rejected: {}", msg);
(Error::<T>::CodeRejected.into(), msg)
})?;
if let Err(msg) = &result {
log::debug!(target: "runtime::contracts", "CodeRejected: {}", 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.
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, _>(&code, (), (initial, maximum), stack_limits)
.map_err(|err| {
log::debug!(target: "runtime::contracts", "{}", err);
(Error::<T>::CodeRejected.into(), "new code rejected after instrumentation")
})?;
}
result
Ok((code, (initial, maximum)))
}
fn do_preparation<C: ImportSatisfyCheck, T: Config>(
/// Loads the given module given in `original_code`, performs some checks on it and
/// does some preprocessing.
///
/// The checks are:
///
/// - 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.
pub fn prepare<E, T>(
original_code: CodeVec<T>,
schedule: &Schedule<T>,
owner: AccountIdOf<T>,
determinism: Determinism,
) -> Result<PrefabWasmModule<T>, (DispatchError, &'static str)> {
try_instantiate: TryInstantiate,
) -> Result<PrefabWasmModule<T>, (DispatchError, &'static str)>
where
E: Environment<()>,
T: Config,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
let (code, (initial, maximum)) =
check_and_instrument::<C, T>(original_code.as_ref(), schedule, determinism)
.map_err(|msg| (<Error<T>>::CodeRejected.into(), msg))?;
instrument::<E, T>(original_code.as_ref(), schedule, determinism, try_instantiate)?;
let original_code_len = original_code.len();
let mut module = PrefabWasmModule {
@@ -420,10 +497,10 @@ fn do_preparation<C: ImportSatisfyCheck, T: Config>(
initial,
maximum,
code: code.try_into().map_err(|_| (<Error<T>>::CodeTooLarge.into(), ""))?,
determinism,
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
@@ -441,37 +518,28 @@ fn do_preparation<C: ImportSatisfyCheck, T: Config>(
Ok(module)
}
/// Loads the given module given in `original_code`, performs some checks on it and
/// does some preprocessing.
/// Same as [`prepare`] but without constructing a new module.
///
/// The checks are:
///
/// - 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.
pub fn prepare_contract<T: Config>(
original_code: CodeVec<T>,
schedule: &Schedule<T>,
owner: AccountIdOf<T>,
determinism: Determinism,
) -> Result<PrefabWasmModule<T>, (DispatchError, &'static str)> {
do_preparation::<super::runtime::Env, T>(original_code, schedule, owner, determinism)
}
/// The same as [`prepare_contract`] but without constructing a new [`PrefabWasmModule`]
///
/// # Note
///
/// Use this when an existing contract should be re-instrumented with a newer schedule version.
pub fn reinstrument_contract<T: Config>(
/// 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>, &'static str> {
Ok(check_and_instrument::<super::runtime::Env, T>(original_code, schedule, determinism)?.0)
) -> Result<Vec<u8>, DispatchError>
where
E: Environment<()>,
T: Config,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
instrument::<E, T>(original_code, schedule, determinism, TryInstantiate::Skip)
.map_err(|(err, msg)| {
log::error!(target: "runtime::contracts", "CodeRejected during reinstrument: {}", msg);
err
})
.map(|(code, _)| code)
}
/// Alternate (possibly unsafe) preparation functions used only for benchmarking.
@@ -482,29 +550,22 @@ pub fn reinstrument_contract<T: Config>(
/// in production code.
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking {
use super::{elements::FunctionType, *};
impl ImportSatisfyCheck for () {
fn can_satisfy(_module: &[u8], _name: &[u8], _func_type: &FunctionType) -> bool {
true
}
}
use super::*;
/// Prepare function that neither checks nor instruments the passed in code.
pub fn prepare_contract<T: Config>(
pub fn prepare<T: Config>(
original_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)?;
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")?),
determinism: Determinism::Deterministic,
code: contract_module
.into_wasm_code()?
.try_into()
@@ -515,6 +576,7 @@ pub mod benchmarking {
deposit: Default::default(),
refcount: 0,
}),
determinism: Determinism::Deterministic,
})
}
}
@@ -540,27 +602,28 @@ mod tests {
#[allow(unreachable_code)]
mod env {
use super::*;
use crate::wasm::runtime::TrapReason;
// Define test environment for tests. We need ImportSatisfyCheck
// implementation from it. So actual implementations doesn't matter.
#[define_env]
pub mod test_env {
fn panic(_ctx: crate::wasm::Runtime<E>) -> Result<(), TrapReason> {
fn panic(_ctx: _, _memory: _) -> Result<(), TrapReason> {
Ok(())
}
// gas is an implementation defined function and a contract can't import it.
fn gas(_ctx: crate::wasm::Runtime<E>, _amount: u32) -> Result<(), TrapReason> {
fn gas(_ctx: _, _memory: _, _amount: u64) -> Result<(), TrapReason> {
Ok(())
}
fn nop(_ctx: crate::wasm::Runtime<E>, _unused: u64) -> Result<(), TrapReason> {
fn nop(_ctx: _, _memory: _, _unused: u64) -> Result<(), TrapReason> {
Ok(())
}
// new version of nop with other data type for argumebt
// new version of nop with other data type for argument
#[version(1)]
fn nop(_ctx: crate::wasm::Runtime<E>, _unused: i32) -> Result<(), TrapReason> {
fn nop(_ctx: _, _memory: _, _unused: i32) -> Result<(), TrapReason> {
Ok(())
}
}
@@ -582,7 +645,13 @@ mod tests {
},
.. Default::default()
};
let r = do_preparation::<env::Env, Test>(wasm, &schedule, ALICE, Determinism::Deterministic);
let r = prepare::<env::Env, Test>(
wasm,
&schedule,
ALICE,
Determinism::Deterministic,
TryInstantiate::Instantiate,
);
assert_matches::assert_matches!(r.map_err(|(_, msg)| msg), $($expected)*);
}
};
@@ -718,7 +787,7 @@ mod tests {
(func (export "deploy"))
)
"#,
Err("Module is not valid")
Err("validation of new code failed")
);
prepare_test!(
@@ -784,7 +853,7 @@ mod tests {
(func (export "deploy"))
)
"#,
Err("Module is not valid")
Err("validation of new code failed")
);
prepare_test!(
@@ -910,7 +979,7 @@ mod tests {
(func (export "deploy"))
)
"#,
Err("module imports a non-existent function")
Err("module imports a banned function")
);
// memory is in "env" and not in "seal0"
@@ -965,7 +1034,7 @@ mod tests {
(func (export "deploy"))
)
"#,
Err("module imports a non-existent function")
Err("module imports a banned function")
);
prepare_test!(
@@ -978,7 +1047,7 @@ mod tests {
(func (export "deploy"))
)
"#,
Err("module imports a non-existent function")
Err("new code rejected after instrumentation")
);
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff