mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-28 08:37:56 +00:00
contracts: switch from parity-wasm-based to wasmi-based module validation (#14449)
* 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 * save: scan_exports ported, compiles * save (wip, not compiles) * 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 * scan_imports ported * scan_export ported, other checks removed * tests fixed tests fixed * drop wasmparser and parity-wasm dependencies * typo fix * 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 * allow stored modules to have no memory imports * rollback: allow stored modules to have no memory imports * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts * address review comments --------- Co-authored-by: command-bot <>
This commit is contained in:
Generated
-1
@@ -6360,7 +6360,6 @@ dependencies = [
|
|||||||
"sp-std",
|
"sp-std",
|
||||||
"wasm-instrument 0.4.0",
|
"wasm-instrument 0.4.0",
|
||||||
"wasmi",
|
"wasmi",
|
||||||
"wasmparser-nostd",
|
|
||||||
"wat",
|
"wat",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -21,16 +21,15 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features =
|
|||||||
] }
|
] }
|
||||||
scale-info = { version = "2.5.0", default-features = false, features = ["derive"] }
|
scale-info = { version = "2.5.0", default-features = false, features = ["derive"] }
|
||||||
log = { version = "0.4", default-features = false }
|
log = { version = "0.4", default-features = false }
|
||||||
wasm-instrument = { version = "0.4", default-features = false }
|
|
||||||
serde = { version = "1", optional = true, features = ["derive"] }
|
serde = { version = "1", optional = true, features = ["derive"] }
|
||||||
smallvec = { version = "1", default-features = false, features = [
|
smallvec = { version = "1", default-features = false, features = [
|
||||||
"const_generics",
|
"const_generics",
|
||||||
] }
|
] }
|
||||||
wasmi = { version = "0.30", default-features = false }
|
wasmi = { version = "0.30", default-features = false }
|
||||||
wasmparser = { package = "wasmparser-nostd", version = "0.100", default-features = false }
|
|
||||||
impl-trait-for-tuples = "0.2"
|
impl-trait-for-tuples = "0.2"
|
||||||
|
|
||||||
# Only used in benchmarking to generate random contract code
|
# Only used in benchmarking to generate contract code
|
||||||
|
wasm-instrument = { version = "0.4", optional = true, default-features = false }
|
||||||
rand = { version = "0.8", optional = true, default-features = false }
|
rand = { version = "0.8", optional = true, default-features = false }
|
||||||
rand_pcg = { version = "0.3", optional = true }
|
rand_pcg = { version = "0.3", optional = true }
|
||||||
|
|
||||||
@@ -81,12 +80,12 @@ std = [
|
|||||||
"pallet-contracts-proc-macro/full",
|
"pallet-contracts-proc-macro/full",
|
||||||
"log/std",
|
"log/std",
|
||||||
"rand/std",
|
"rand/std",
|
||||||
"wasmparser/std",
|
|
||||||
"environmental/std",
|
"environmental/std",
|
||||||
]
|
]
|
||||||
runtime-benchmarks = [
|
runtime-benchmarks = [
|
||||||
"frame-benchmarking/runtime-benchmarks",
|
"frame-benchmarking/runtime-benchmarks",
|
||||||
"rand",
|
"rand",
|
||||||
"rand_pcg",
|
"rand_pcg",
|
||||||
|
"wasm-instrument",
|
||||||
]
|
]
|
||||||
try-runtime = ["frame-support/try-runtime"]
|
try-runtime = ["frame-support/try-runtime"]
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
;; A valid contract which does nothing at all
|
;; A valid contract which does nothing at all
|
||||||
(module
|
(module
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
(func (export "call"))
|
(func (export "call"))
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
;; Module that contains a float instruction which is illegal in deterministic mode
|
;; Module that contains a float instruction which is illegal in deterministic mode
|
||||||
(module
|
(module
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
(func (export "call")
|
(func (export "call")
|
||||||
f32.const 1
|
f32.const 1
|
||||||
drop
|
drop
|
||||||
|
|||||||
+1
@@ -1,4 +1,5 @@
|
|||||||
;; Valid module but missing the call function
|
;; Valid module but missing the call function
|
||||||
(module
|
(module
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
;; A valid contract which does nothing at all
|
||||||
|
(module
|
||||||
|
(func (export "deploy"))
|
||||||
|
(func (export "call"))
|
||||||
|
)
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
(module
|
(module
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
(func (export "call")
|
(func (export "call")
|
||||||
(loop $inf (br $inf)) ;; just run out of gas
|
(loop $inf (br $inf)) ;; just run out of gas
|
||||||
(unreachable)
|
(unreachable)
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ impl<T: Config> From<ModuleDefinition> for WasmModule<T> {
|
|||||||
// internal functions start at that offset.
|
// internal functions start at that offset.
|
||||||
let func_offset = u32::try_from(def.imported_functions.len()).unwrap();
|
let func_offset = u32::try_from(def.imported_functions.len()).unwrap();
|
||||||
|
|
||||||
// Every contract must export "deploy" and "call" functions
|
// Every contract must export "deploy" and "call" functions.
|
||||||
let mut contract = builder::module()
|
let mut contract = builder::module()
|
||||||
// deploy function (first internal function)
|
// deploy function (first internal function)
|
||||||
.function()
|
.function()
|
||||||
@@ -163,15 +163,16 @@ impl<T: Config> From<ModuleDefinition> for WasmModule<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Grant access to linear memory.
|
// Grant access to linear memory.
|
||||||
if let Some(memory) = &def.memory {
|
// Every contract module is required to have an imported memory.
|
||||||
contract = contract
|
// If no memory is specified in the passed ModuleDefenition, then
|
||||||
.import()
|
// default to (1, 1).
|
||||||
.module("env")
|
let (init, max) = if let Some(memory) = &def.memory {
|
||||||
.field("memory")
|
(memory.min_pages, Some(memory.max_pages))
|
||||||
.external()
|
} else {
|
||||||
.memory(memory.min_pages, Some(memory.max_pages))
|
(1, Some(1))
|
||||||
.build();
|
};
|
||||||
}
|
|
||||||
|
contract = contract.import().path("env", "memory").external().memory(init, max).build();
|
||||||
|
|
||||||
// Import supervisor functions. They start with idx 0.
|
// Import supervisor functions. They start with idx 0.
|
||||||
for func in def.imported_functions {
|
for func in def.imported_functions {
|
||||||
|
|||||||
@@ -19,7 +19,9 @@
|
|||||||
/// ! sandbox to execute the Wasm code. This is because we do not need the full
|
/// ! sandbox to execute the Wasm code. This is because we do not need the full
|
||||||
/// ! environment that provides the seal interface as imported functions.
|
/// ! environment that provides the seal interface as imported functions.
|
||||||
use super::{code::WasmModule, Config};
|
use super::{code::WasmModule, Config};
|
||||||
use crate::wasm::{AllowDeprecatedInterface, AllowUnstableInterface, Environment, WasmBlob};
|
use crate::wasm::{
|
||||||
|
AllowDeprecatedInterface, AllowUnstableInterface, Determinism, Environment, WasmBlob,
|
||||||
|
};
|
||||||
use sp_core::Get;
|
use sp_core::Get;
|
||||||
use wasmi::{errors::LinkerError, Func, Linker, StackLimits, Store};
|
use wasmi::{errors::LinkerError, Func, Linker, StackLimits, Store};
|
||||||
|
|
||||||
@@ -44,6 +46,7 @@ impl<T: Config> From<&WasmModule<T>> for Sandbox {
|
|||||||
&module.code,
|
&module.code,
|
||||||
(),
|
(),
|
||||||
&<T>::Schedule::get(),
|
&<T>::Schedule::get(),
|
||||||
|
Determinism::Relaxed,
|
||||||
StackLimits::default(),
|
StackLimits::default(),
|
||||||
// We are testing with an empty environment anyways
|
// We are testing with an empty environment anyways
|
||||||
AllowDeprecatedInterface::No,
|
AllowDeprecatedInterface::No,
|
||||||
|
|||||||
@@ -885,6 +885,8 @@ pub mod pallet {
|
|||||||
CodeTooLarge,
|
CodeTooLarge,
|
||||||
/// No code could be found at the supplied code hash.
|
/// No code could be found at the supplied code hash.
|
||||||
CodeNotFound,
|
CodeNotFound,
|
||||||
|
/// No code info could be found at the supplied code hash.
|
||||||
|
CodeInfoNotFound,
|
||||||
/// A buffer outside of sandbox memory was passed to a contract API function.
|
/// A buffer outside of sandbox memory was passed to a contract API function.
|
||||||
OutOfBounds,
|
OutOfBounds,
|
||||||
/// Input passed to a contract API function failed to decode as expected type.
|
/// Input passed to a contract API function failed to decode as expected type.
|
||||||
|
|||||||
@@ -2055,7 +2055,7 @@ fn disabled_chain_extension_errors_on_call() {
|
|||||||
TestExtension::disable();
|
TestExtension::disable();
|
||||||
assert_err_ignore_postinfo!(
|
assert_err_ignore_postinfo!(
|
||||||
Contracts::call(RuntimeOrigin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, None, vec![],),
|
Contracts::call(RuntimeOrigin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, None, vec![],),
|
||||||
Error::<Test>::NoChainExtension,
|
Error::<Test>::CodeRejected,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -4419,10 +4419,10 @@ fn code_rejected_error_works() {
|
|||||||
assert_err!(result.result, <Error<Test>>::CodeRejected);
|
assert_err!(result.result, <Error<Test>>::CodeRejected);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
std::str::from_utf8(&result.debug_message).unwrap(),
|
std::str::from_utf8(&result.debug_message).unwrap(),
|
||||||
"Validation of new code failed!"
|
"Can't load the module into wasmi!"
|
||||||
);
|
);
|
||||||
|
|
||||||
let (wasm, _) = compile_module::<Test>("invalid_contract").unwrap();
|
let (wasm, _) = compile_module::<Test>("invalid_contract_no_call").unwrap();
|
||||||
assert_noop!(
|
assert_noop!(
|
||||||
Contracts::upload_code(
|
Contracts::upload_code(
|
||||||
RuntimeOrigin::signed(ALICE),
|
RuntimeOrigin::signed(ALICE),
|
||||||
@@ -4449,6 +4449,34 @@ fn code_rejected_error_works() {
|
|||||||
std::str::from_utf8(&result.debug_message).unwrap(),
|
std::str::from_utf8(&result.debug_message).unwrap(),
|
||||||
"call function isn't exported"
|
"call function isn't exported"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let (wasm, _) = compile_module::<Test>("invalid_contract_no_memory").unwrap();
|
||||||
|
assert_noop!(
|
||||||
|
Contracts::upload_code(
|
||||||
|
RuntimeOrigin::signed(ALICE),
|
||||||
|
wasm.clone(),
|
||||||
|
None,
|
||||||
|
Determinism::Enforced
|
||||||
|
),
|
||||||
|
<Error<Test>>::CodeRejected,
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = Contracts::bare_instantiate(
|
||||||
|
ALICE,
|
||||||
|
0,
|
||||||
|
GAS_LIMIT,
|
||||||
|
None,
|
||||||
|
Code::Upload(wasm),
|
||||||
|
vec![],
|
||||||
|
vec![],
|
||||||
|
DebugInfo::UnsafeDebug,
|
||||||
|
CollectEvents::Skip,
|
||||||
|
);
|
||||||
|
assert_err!(result.result, <Error<Test>>::CodeRejected);
|
||||||
|
assert_eq!(
|
||||||
|
std::str::from_utf8(&result.debug_message).unwrap(),
|
||||||
|
"No memory import found in the module"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5117,6 +5145,7 @@ fn cannot_instantiate_indeterministic_code() {
|
|||||||
None,
|
None,
|
||||||
Determinism::Relaxed,
|
Determinism::Relaxed,
|
||||||
));
|
));
|
||||||
|
|
||||||
assert_err_ignore_postinfo!(
|
assert_err_ignore_postinfo!(
|
||||||
Contracts::instantiate(
|
Contracts::instantiate(
|
||||||
RuntimeOrigin::signed(ALICE),
|
RuntimeOrigin::signed(ALICE),
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ pub use crate::wasm::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
exec::{ExecResult, Executable, ExportedFunction, Ext},
|
exec::{ExecResult, Executable, ExportedFunction, Ext},
|
||||||
gas::{GasMeter, Token},
|
gas::{GasMeter, Token},
|
||||||
wasm::prepare::IMPORT_MODULE_MEMORY,
|
wasm::prepare::LoadedModule,
|
||||||
weights::WeightInfo,
|
weights::WeightInfo,
|
||||||
AccountIdOf, BadOrigin, BalanceOf, CodeHash, CodeInfoOf, CodeVec, Config, Error, Event, Pallet,
|
AccountIdOf, BadOrigin, BalanceOf, CodeHash, CodeInfoOf, CodeVec, Config, Error, Event, Pallet,
|
||||||
PristineCode, Schedule, Weight, LOG_TARGET,
|
PristineCode, Schedule, Weight, LOG_TARGET,
|
||||||
@@ -52,10 +52,8 @@ use frame_support::{
|
|||||||
use sp_core::Get;
|
use sp_core::Get;
|
||||||
use sp_runtime::RuntimeDebug;
|
use sp_runtime::RuntimeDebug;
|
||||||
use sp_std::prelude::*;
|
use sp_std::prelude::*;
|
||||||
use wasmi::{
|
use wasmi::{Instance, Linker, Memory, MemoryType, StackLimits, Store};
|
||||||
Config as WasmiConfig, Engine, ExternType, FuelConsumptionMode, Instance, Linker, Memory,
|
|
||||||
MemoryType, Module, StackLimits, Store,
|
|
||||||
};
|
|
||||||
const BYTES_PER_PAGE: usize = 64 * 1024;
|
const BYTES_PER_PAGE: usize = 64 * 1024;
|
||||||
|
|
||||||
/// Validated Wasm module ready for execution.
|
/// Validated Wasm module ready for execution.
|
||||||
@@ -204,26 +202,16 @@ impl<T: Config> WasmBlob<T> {
|
|||||||
code: &[u8],
|
code: &[u8],
|
||||||
host_state: H,
|
host_state: H,
|
||||||
schedule: &Schedule<T>,
|
schedule: &Schedule<T>,
|
||||||
|
determinism: Determinism,
|
||||||
stack_limits: StackLimits,
|
stack_limits: StackLimits,
|
||||||
allow_deprecated: AllowDeprecatedInterface,
|
allow_deprecated: AllowDeprecatedInterface,
|
||||||
) -> Result<(Store<H>, Memory, Instance), &'static str>
|
) -> Result<(Store<H>, Memory, Instance), &'static str>
|
||||||
where
|
where
|
||||||
E: Environment<H>,
|
E: Environment<H>,
|
||||||
{
|
{
|
||||||
let mut config = WasmiConfig::default();
|
let contract = LoadedModule::new::<T>(&code, determinism, Some(stack_limits))?;
|
||||||
config
|
let mut store = Store::new(&contract.engine, host_state);
|
||||||
.set_stack_limits(stack_limits)
|
let mut linker = Linker::new(&contract.engine);
|
||||||
.wasm_multi_value(false)
|
|
||||||
.wasm_mutable_global(false)
|
|
||||||
.wasm_sign_extension(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.clone()).map_err(|_| "can't decode Wasm module")?;
|
|
||||||
let mut store = Store::new(&engine, host_state);
|
|
||||||
let mut linker = Linker::new(&engine);
|
|
||||||
E::define(
|
E::define(
|
||||||
&mut store,
|
&mut store,
|
||||||
&mut linker,
|
&mut linker,
|
||||||
@@ -235,8 +223,9 @@ impl<T: Config> WasmBlob<T> {
|
|||||||
allow_deprecated,
|
allow_deprecated,
|
||||||
)
|
)
|
||||||
.map_err(|_| "can't define host functions to Linker")?;
|
.map_err(|_| "can't define host functions to Linker")?;
|
||||||
|
|
||||||
// Query wasmi for memory limits specified in the module's import entry.
|
// Query wasmi for memory limits specified in the module's import entry.
|
||||||
let memory_limits = Self::get_memory_limits(module.imports(), schedule)?;
|
let memory_limits = contract.scan_imports::<T>(schedule)?;
|
||||||
// Here we allocate this memory in the _store_. It allocates _inital_ value, but allows it
|
// 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.
|
// to grow up to maximum number of memory pages, if neccesary.
|
||||||
let qed = "We checked the limits versus our Schedule,
|
let qed = "We checked the limits versus our Schedule,
|
||||||
@@ -247,12 +236,13 @@ impl<T: Config> WasmBlob<T> {
|
|||||||
MemoryType::new(memory_limits.0, Some(memory_limits.1)).expect(qed),
|
MemoryType::new(memory_limits.0, Some(memory_limits.1)).expect(qed),
|
||||||
)
|
)
|
||||||
.expect(qed);
|
.expect(qed);
|
||||||
|
|
||||||
linker
|
linker
|
||||||
.define("env", "memory", memory)
|
.define("env", "memory", memory)
|
||||||
.expect("We just created the Linker. It has no definitions with this name; qed");
|
.expect("We just created the Linker. It has no definitions with this name; qed");
|
||||||
|
|
||||||
let instance = linker
|
let instance = linker
|
||||||
.instantiate(&mut store, &module)
|
.instantiate(&mut store, &contract.module)
|
||||||
.map_err(|_| "can't instantiate module with provided definitions")?
|
.map_err(|_| "can't instantiate module with provided definitions")?
|
||||||
.ensure_no_start(&mut store)
|
.ensure_no_start(&mut store)
|
||||||
.map_err(|_| "start function is forbidden but found in the module")?;
|
.map_err(|_| "start function is forbidden but found in the module")?;
|
||||||
@@ -260,50 +250,6 @@ impl<T: Config> WasmBlob<T> {
|
|||||||
Ok((store, memory, instance))
|
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.
|
/// Getter method for the code_info.
|
||||||
pub fn code_info(&self) -> &CodeInfo<T> {
|
pub fn code_info(&self) -> &CodeInfo<T> {
|
||||||
&self.code_info
|
&self.code_info
|
||||||
@@ -469,6 +415,7 @@ impl<T: Config> Executable<T> for WasmBlob<T> {
|
|||||||
code,
|
code,
|
||||||
runtime,
|
runtime,
|
||||||
&schedule,
|
&schedule,
|
||||||
|
self.code_info.determinism,
|
||||||
StackLimits::default(),
|
StackLimits::default(),
|
||||||
match function {
|
match function {
|
||||||
ExportedFunction::Call => AllowDeprecatedInterface::Yes,
|
ExportedFunction::Call => AllowDeprecatedInterface::Yes,
|
||||||
@@ -3314,6 +3261,8 @@ mod tests {
|
|||||||
const CODE: &str = r#"
|
const CODE: &str = r#"
|
||||||
(module
|
(module
|
||||||
(import "seal0" "instantiation_nonce" (func $nonce (result i64)))
|
(import "seal0" "instantiation_nonce" (func $nonce (result i64)))
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
(func $assert (param i32)
|
(func $assert (param i32)
|
||||||
(block $ok
|
(block $ok
|
||||||
(br_if $ok
|
(br_if $ok
|
||||||
@@ -3344,6 +3293,8 @@ mod tests {
|
|||||||
const CANNOT_DEPLOY_UNSTABLE: &str = r#"
|
const CANNOT_DEPLOY_UNSTABLE: &str = r#"
|
||||||
(module
|
(module
|
||||||
(import "seal0" "reentrance_count" (func $reentrance_count (result i32)))
|
(import "seal0" "reentrance_count" (func $reentrance_count (result i32)))
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
(func (export "call"))
|
(func (export "call"))
|
||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
)
|
)
|
||||||
@@ -3364,6 +3315,8 @@ mod tests {
|
|||||||
const CODE_RANDOM_0: &str = r#"
|
const CODE_RANDOM_0: &str = r#"
|
||||||
(module
|
(module
|
||||||
(import "seal0" "seal_random" (func $seal_random (param i32 i32 i32 i32)))
|
(import "seal0" "seal_random" (func $seal_random (param i32 i32 i32 i32)))
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
(func (export "call"))
|
(func (export "call"))
|
||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
)
|
)
|
||||||
@@ -3371,6 +3324,8 @@ mod tests {
|
|||||||
const CODE_RANDOM_1: &str = r#"
|
const CODE_RANDOM_1: &str = r#"
|
||||||
(module
|
(module
|
||||||
(import "seal1" "seal_random" (func $seal_random (param i32 i32 i32 i32)))
|
(import "seal1" "seal_random" (func $seal_random (param i32 i32 i32 i32)))
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
(func (export "call"))
|
(func (export "call"))
|
||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
)
|
)
|
||||||
@@ -3378,6 +3333,8 @@ mod tests {
|
|||||||
const CODE_RANDOM_2: &str = r#"
|
const CODE_RANDOM_2: &str = r#"
|
||||||
(module
|
(module
|
||||||
(import "seal0" "random" (func $seal_random (param i32 i32 i32 i32)))
|
(import "seal0" "random" (func $seal_random (param i32 i32 i32 i32)))
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
(func (export "call"))
|
(func (export "call"))
|
||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
)
|
)
|
||||||
@@ -3385,6 +3342,8 @@ mod tests {
|
|||||||
const CODE_RANDOM_3: &str = r#"
|
const CODE_RANDOM_3: &str = r#"
|
||||||
(module
|
(module
|
||||||
(import "seal1" "random" (func $seal_random (param i32 i32 i32 i32)))
|
(import "seal1" "random" (func $seal_random (param i32 i32 i32 i32)))
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
(func (export "call"))
|
(func (export "call"))
|
||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,18 +22,20 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
chain_extension::ChainExtension,
|
chain_extension::ChainExtension,
|
||||||
storage::meter::Diff,
|
storage::meter::Diff,
|
||||||
wasm::{runtime::AllowDeprecatedInterface, CodeInfo, Determinism, Environment, WasmBlob},
|
wasm::{
|
||||||
|
runtime::AllowDeprecatedInterface, CodeInfo, Determinism, Environment, WasmBlob,
|
||||||
|
BYTES_PER_PAGE,
|
||||||
|
},
|
||||||
AccountIdOf, CodeVec, Config, Error, Schedule, LOG_TARGET,
|
AccountIdOf, CodeVec, Config, Error, Schedule, LOG_TARGET,
|
||||||
};
|
};
|
||||||
use codec::MaxEncodedLen;
|
use codec::MaxEncodedLen;
|
||||||
use sp_runtime::{traits::Hash, DispatchError};
|
use sp_runtime::{traits::Hash, DispatchError};
|
||||||
#[cfg(any(test, feature = "runtime-benchmarks"))]
|
#[cfg(any(test, feature = "runtime-benchmarks"))]
|
||||||
use sp_std::prelude::Vec;
|
use sp_std::prelude::Vec;
|
||||||
use wasm_instrument::parity_wasm::elements::{
|
use wasmi::{
|
||||||
self, External, Internal, MemoryType, Type, ValueType,
|
core::ValueType as WasmiValueType, Config as WasmiConfig, Engine, ExternType,
|
||||||
|
FuelConsumptionMode, Module, StackLimits,
|
||||||
};
|
};
|
||||||
use wasmi::StackLimits;
|
|
||||||
use wasmparser::{Validator, WasmFeatures};
|
|
||||||
|
|
||||||
/// Imported memory must be located inside this module. The reason for hardcoding is that current
|
/// 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.
|
/// compiler toolchains might not support specifying other modules than "env" for memory imports.
|
||||||
@@ -54,108 +56,50 @@ pub enum TryInstantiate {
|
|||||||
Skip,
|
Skip,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The inner deserialized module is valid (this is guaranteed by `new` method).
|
/// The inner deserialized module is valid and contains only allowed WebAssembly features.
|
||||||
pub struct ContractModule(elements::Module);
|
/// This is checked by loading it into wasmi interpreter `engine`.
|
||||||
|
pub struct LoadedModule {
|
||||||
|
pub module: Module,
|
||||||
|
pub engine: Engine,
|
||||||
|
}
|
||||||
|
|
||||||
impl ContractModule {
|
impl LoadedModule {
|
||||||
/// Creates a new instance of `ContractModule`.
|
/// Creates a new instance of `LoadedModule`.
|
||||||
///
|
///
|
||||||
/// Returns `Err` if the `code` couldn't be decoded or
|
/// The inner Wasm module is checked not to have restricted WebAssembly proposals.
|
||||||
/// if it contains an invalid module.
|
/// Returns `Err` if the `code` cannot be deserialized or if it contains an invalid module.
|
||||||
pub fn new(code: &[u8]) -> Result<Self, &'static str> {
|
pub fn new<T>(
|
||||||
let module = elements::deserialize_buffer(code).map_err(|_| "Can't decode Wasm code")?;
|
code: &[u8],
|
||||||
|
determinism: Determinism,
|
||||||
|
stack_limits: Option<StackLimits>,
|
||||||
|
) -> Result<Self, &'static str> {
|
||||||
|
// NOTE: wasmi does not support unstable WebAssembly features. The module is implicitly
|
||||||
|
// checked for not having those ones when creating `wasmi::Module` below.
|
||||||
|
let mut config = WasmiConfig::default();
|
||||||
|
config
|
||||||
|
.wasm_multi_value(false)
|
||||||
|
.wasm_mutable_global(false)
|
||||||
|
.wasm_sign_extension(false)
|
||||||
|
.wasm_bulk_memory(false)
|
||||||
|
.wasm_reference_types(false)
|
||||||
|
.wasm_tail_call(false)
|
||||||
|
.wasm_extended_const(false)
|
||||||
|
.wasm_saturating_float_to_int(false)
|
||||||
|
.floats(matches!(determinism, Determinism::Relaxed))
|
||||||
|
.consume_fuel(true)
|
||||||
|
.fuel_consumption_mode(FuelConsumptionMode::Eager);
|
||||||
|
|
||||||
// Return a `ContractModule` instance with
|
if let Some(stack_limits) = stack_limits {
|
||||||
|
config.set_stack_limits(stack_limits);
|
||||||
|
}
|
||||||
|
|
||||||
|
let engine = Engine::new(&config);
|
||||||
|
let module =
|
||||||
|
Module::new(&engine, code.clone()).map_err(|_| "Can't load the module into wasmi!")?;
|
||||||
|
|
||||||
|
// Return a `LoadedModule` instance with
|
||||||
// __valid__ module.
|
// __valid__ module.
|
||||||
Ok(ContractModule(module))
|
Ok(LoadedModule { module, engine })
|
||||||
}
|
|
||||||
|
|
||||||
/// Ensures that module doesn't declare internal memories.
|
|
||||||
///
|
|
||||||
/// In this runtime we only allow wasm module to import memory from the environment.
|
|
||||||
/// Memory section contains declarations of internal linear memories, so if we find one
|
|
||||||
/// we reject such a module.
|
|
||||||
fn ensure_no_internal_memory(&self) -> Result<(), &'static str> {
|
|
||||||
if self.0.memory_section().map_or(false, |ms| ms.entries().len() > 0) {
|
|
||||||
return Err("module declares internal memory")
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ensures that tables declared in the module are not too big.
|
|
||||||
fn ensure_table_size_limit(&self, limit: u32) -> Result<(), &'static str> {
|
|
||||||
if let Some(table_section) = self.0.table_section() {
|
|
||||||
// In Wasm MVP spec, there may be at most one table declared. Double check this
|
|
||||||
// explicitly just in case the Wasm version changes.
|
|
||||||
if table_section.entries().len() > 1 {
|
|
||||||
return Err("multiple tables declared")
|
|
||||||
}
|
|
||||||
if let Some(table_type) = table_section.entries().first() {
|
|
||||||
// Check the table's initial size as there is no instruction or environment function
|
|
||||||
// capable of growing the table.
|
|
||||||
if table_type.limits().initial() > limit {
|
|
||||||
return Err("table exceeds maximum size allowed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ensure that any `br_table` instruction adheres to its immediate value limit.
|
|
||||||
fn ensure_br_table_size_limit(&self, limit: u32) -> Result<(), &'static str> {
|
|
||||||
let code_section = if let Some(type_section) = self.0.code_section() {
|
|
||||||
type_section
|
|
||||||
} else {
|
|
||||||
return Ok(())
|
|
||||||
};
|
|
||||||
for instr in code_section.bodies().iter().flat_map(|body| body.code().elements()) {
|
|
||||||
use self::elements::Instruction::BrTable;
|
|
||||||
if let BrTable(table) = instr {
|
|
||||||
if table.table.len() > limit as usize {
|
|
||||||
return Err("BrTable's immediate value is too big.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensure_global_variable_limit(&self, limit: u32) -> Result<(), &'static str> {
|
|
||||||
if let Some(global_section) = self.0.global_section() {
|
|
||||||
if global_section.entries().len() > limit as usize {
|
|
||||||
return Err("module declares too many globals")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensure_local_variable_limit(&self, limit: u32) -> Result<(), &'static str> {
|
|
||||||
if let Some(code_section) = self.0.code_section() {
|
|
||||||
for func_body in code_section.bodies() {
|
|
||||||
let locals_count: u32 =
|
|
||||||
func_body.locals().iter().map(|val_type| val_type.count()).sum();
|
|
||||||
if locals_count > limit {
|
|
||||||
return Err("single function declares too many locals")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ensure that no function exists that has more parameters than allowed.
|
|
||||||
fn ensure_parameter_limit(&self, limit: u32) -> Result<(), &'static str> {
|
|
||||||
let type_section = if let Some(type_section) = self.0.type_section() {
|
|
||||||
type_section
|
|
||||||
} else {
|
|
||||||
return Ok(())
|
|
||||||
};
|
|
||||||
|
|
||||||
for Type::Function(func) in type_section.types() {
|
|
||||||
if func.params().len() > limit as usize {
|
|
||||||
return Err("Use of a function type with too many parameters.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check that the module has required exported functions. For now
|
/// Check that the module has required exported functions. For now
|
||||||
@@ -168,61 +112,33 @@ impl ContractModule {
|
|||||||
fn scan_exports(&self) -> Result<(), &'static str> {
|
fn scan_exports(&self) -> Result<(), &'static str> {
|
||||||
let mut deploy_found = false;
|
let mut deploy_found = false;
|
||||||
let mut call_found = false;
|
let mut call_found = false;
|
||||||
|
let module = &self.module;
|
||||||
|
let exports = module.exports();
|
||||||
|
|
||||||
let module = &self.0;
|
for export in exports {
|
||||||
|
match export.ty() {
|
||||||
let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]);
|
ExternType::Func(ft) => {
|
||||||
let export_entries = module.export_section().map(|is| is.entries()).unwrap_or(&[]);
|
match export.name() {
|
||||||
let func_entries = module.function_section().map(|fs| fs.entries()).unwrap_or(&[]);
|
|
||||||
|
|
||||||
// Function index space consists of imported function following by
|
|
||||||
// declared functions. Calculate the total number of imported functions so
|
|
||||||
// we can use it to convert indexes from function space to declared function space.
|
|
||||||
let fn_space_offset = module
|
|
||||||
.import_section()
|
|
||||||
.map(|is| is.entries())
|
|
||||||
.unwrap_or(&[])
|
|
||||||
.iter()
|
|
||||||
.filter(|entry| matches!(*entry.external(), External::Function(_)))
|
|
||||||
.count();
|
|
||||||
|
|
||||||
for export in export_entries {
|
|
||||||
match export.field() {
|
|
||||||
"call" => call_found = true,
|
"call" => call_found = true,
|
||||||
"deploy" => deploy_found = true,
|
"deploy" => deploy_found = true,
|
||||||
_ => return Err("unknown export: expecting only deploy and call functions"),
|
_ =>
|
||||||
|
return Err(
|
||||||
|
"unknown function export: expecting only deploy and call functions",
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
// Check the signature.
|
||||||
// Then check the export kind. "call" and "deploy" are
|
// Both "call" and "deploy" have the () -> () function type.
|
||||||
// functions.
|
|
||||||
let fn_idx = match export.internal() {
|
|
||||||
Internal::Function(ref fn_idx) => *fn_idx,
|
|
||||||
_ => return Err("expected a function"),
|
|
||||||
};
|
|
||||||
|
|
||||||
// convert index from function index space to declared index space.
|
|
||||||
let fn_idx = match fn_idx.checked_sub(fn_space_offset as u32) {
|
|
||||||
Some(fn_idx) => fn_idx,
|
|
||||||
None => {
|
|
||||||
// Underflow here means fn_idx points to imported function which we don't allow!
|
|
||||||
return Err("entry point points to an imported function")
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Then check the signature.
|
|
||||||
// Both "call" and "deploy" has a () -> () function type.
|
|
||||||
// We still support () -> (i32) for backwards compatibility.
|
// We still support () -> (i32) for backwards compatibility.
|
||||||
let func_ty_idx = func_entries
|
if !(ft.params().is_empty() &&
|
||||||
.get(fn_idx as usize)
|
(ft.results().is_empty() || ft.results() == [WasmiValueType::I32]))
|
||||||
.ok_or("export refers to non-existent function")?
|
|
||||||
.type_ref();
|
|
||||||
let Type::Function(ref func_ty) =
|
|
||||||
types.get(func_ty_idx as usize).ok_or("function has a non-existent type")?;
|
|
||||||
if !(func_ty.params().is_empty() &&
|
|
||||||
(func_ty.results().is_empty() || func_ty.results() == [ValueType::I32]))
|
|
||||||
{
|
{
|
||||||
return Err("entry point has wrong signature")
|
return Err("entry point has wrong signature")
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
ExternType::Memory(_) => return Err("memory export is forbidden"),
|
||||||
|
ExternType::Global(_) => return Err("global export is forbidden"),
|
||||||
|
ExternType::Table(_) => return Err("table export is forbidden"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !deploy_found {
|
if !deploy_found {
|
||||||
@@ -237,71 +153,86 @@ impl ContractModule {
|
|||||||
|
|
||||||
/// Scan an import section if any.
|
/// Scan an import section if any.
|
||||||
///
|
///
|
||||||
/// This makes sure that the import section looks as we expect it from a contract
|
/// This makes sure that:
|
||||||
/// and enforces and returns the memory type declared by the contract if any.
|
/// - The import section looks as we expect it from a contract.
|
||||||
pub fn scan_imports<T: Config>(&self) -> Result<Option<&MemoryType>, &'static str> {
|
/// - The limits of the memory type declared by the contract comply with the Schedule.
|
||||||
let module = &self.0;
|
///
|
||||||
let import_entries = module.import_section().map(|is| is.entries()).unwrap_or(&[]);
|
/// Returns the checked memory limits back to caller.
|
||||||
let mut imported_mem_type = None;
|
///
|
||||||
|
/// This method fails if:
|
||||||
|
///
|
||||||
|
/// - Memory import not found in the module.
|
||||||
|
/// - Tables or globals found among imports.
|
||||||
|
/// - `call_chain_extension` host function is imported, while chain extensions are disabled.
|
||||||
|
///
|
||||||
|
/// NOTE that only single memory instance is allowed for contract modules, which is enforced by
|
||||||
|
/// this check combined with multi_memory proposal gets disabled in the engine.
|
||||||
|
pub fn scan_imports<T: Config>(
|
||||||
|
&self,
|
||||||
|
schedule: &Schedule<T>,
|
||||||
|
) -> Result<(u32, u32), &'static str> {
|
||||||
|
let module = &self.module;
|
||||||
|
let imports = module.imports();
|
||||||
|
let mut memory_limits = None;
|
||||||
|
|
||||||
|
for import in imports {
|
||||||
|
match *import.ty() {
|
||||||
|
ExternType::Table(_) => return Err("Cannot import tables"),
|
||||||
|
ExternType::Global(_) => return Err("Cannot import globals"),
|
||||||
|
ExternType::Func(_) => {
|
||||||
|
let _ = import.ty().func().ok_or("expected a function")?;
|
||||||
|
|
||||||
for import in import_entries {
|
|
||||||
match *import.external() {
|
|
||||||
External::Table(_) => return Err("Cannot import tables"),
|
|
||||||
External::Global(_) => return Err("Cannot import globals"),
|
|
||||||
External::Function(_) => {
|
|
||||||
if !<T as Config>::ChainExtension::enabled() &&
|
if !<T as Config>::ChainExtension::enabled() &&
|
||||||
import.field().as_bytes() == b"seal_call_chain_extension"
|
import.name().as_bytes() == b"seal_call_chain_extension" ||
|
||||||
|
import.name().as_bytes() == b"call_chain_extension"
|
||||||
{
|
{
|
||||||
return Err("module uses chain extensions but chain extensions are disabled")
|
return Err("Module uses chain extensions but chain extensions are disabled")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
External::Memory(ref memory_type) => {
|
ExternType::Memory(mt) => {
|
||||||
if import.module() != IMPORT_MODULE_MEMORY {
|
if import.module().as_bytes() != IMPORT_MODULE_MEMORY.as_bytes() {
|
||||||
return Err("Invalid module for imported memory")
|
return Err("Invalid module for imported memory")
|
||||||
}
|
}
|
||||||
if import.field() != "memory" {
|
if import.name().as_bytes() != b"memory" {
|
||||||
return Err("Memory import must have the field name 'memory'")
|
return Err("Memory import must have the field name 'memory'")
|
||||||
}
|
}
|
||||||
if imported_mem_type.is_some() {
|
if memory_limits.is_some() {
|
||||||
return Err("Multiple memory imports defined")
|
return Err("Multiple memory imports defined")
|
||||||
}
|
}
|
||||||
imported_mem_type = Some(memory_type);
|
// Parse memory limits defaulting it to (0,0).
|
||||||
|
// Any access to it will then lead to out of bounds trap.
|
||||||
|
let (initial, maximum) = (
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
memory_limits = Some((initial, maximum));
|
||||||
continue
|
continue
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(imported_mem_type)
|
memory_limits.ok_or("No memory import found in the module")
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(any(test, feature = "runtime-benchmarks"))]
|
|
||||||
fn get_memory_limits<T: Config>(
|
|
||||||
module: Option<&MemoryType>,
|
|
||||||
schedule: &Schedule<T>,
|
|
||||||
) -> Result<(u32, u32), &'static str> {
|
|
||||||
if let Some(memory_type) = module {
|
|
||||||
// Inspect the module to extract the initial and maximum page count.
|
|
||||||
let limits = memory_type.limits();
|
|
||||||
match (limits.initial(), limits.maximum()) {
|
|
||||||
(initial, Some(maximum)) if initial > maximum =>
|
|
||||||
Err("Requested initial number of memory pages should not exceed the requested maximum"),
|
|
||||||
(_, Some(maximum)) if maximum > schedule.limits.memory_pages =>
|
|
||||||
Err("Maximum number of memory pages should not exceed the maximum configured in the Schedule."),
|
|
||||||
(initial, Some(maximum)) => Ok((initial, maximum)),
|
|
||||||
(initial, None) => {
|
|
||||||
Ok((initial, schedule.limits.memory_pages))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// None memory imported in the Wasm module,
|
|
||||||
// any access to it will lead to out of bounds trap.
|
|
||||||
Ok((0, 0))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check that given `code` satisfies constraints required for the contract Wasm module.
|
/// Check that given `code` satisfies constraints required for the contract Wasm module.
|
||||||
|
/// This includes two groups of checks:
|
||||||
///
|
///
|
||||||
/// On success it returns back the code.
|
/// 1. General engine-side validation makes sure the module is consistent and does not contain
|
||||||
|
/// forbidden WebAssembly features.
|
||||||
|
/// 2. Additional checks which are specific to smart contracts eligible for this pallet.
|
||||||
fn validate<E, T>(
|
fn validate<E, T>(
|
||||||
code: &[u8],
|
code: &[u8],
|
||||||
schedule: &Schedule<T>,
|
schedule: &Schedule<T>,
|
||||||
@@ -312,54 +243,17 @@ where
|
|||||||
E: Environment<()>,
|
E: Environment<()>,
|
||||||
T: Config,
|
T: Config,
|
||||||
{
|
{
|
||||||
// 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: All instructions explicitly need to have weights assigned
|
|
||||||
// or the deployment will fail. We have none assigned for float instructions.
|
|
||||||
floats: matches!(determinism, Determinism::Relaxed),
|
|
||||||
mutable_global: false,
|
|
||||||
saturating_float_to_int: false,
|
|
||||||
sign_extension: false,
|
|
||||||
bulk_memory: false,
|
|
||||||
multi_value: false,
|
|
||||||
reference_types: false,
|
|
||||||
simd: false,
|
|
||||||
memory_control: false,
|
|
||||||
})
|
|
||||||
.validate_all(code)
|
|
||||||
.map_err(|err| {
|
|
||||||
log::debug!(target: LOG_TARGET, "{}", err);
|
|
||||||
(Error::<T>::CodeRejected.into(), "Validation of new code failed!")
|
|
||||||
})?;
|
|
||||||
|
|
||||||
(|| {
|
(|| {
|
||||||
let contract_module = ContractModule::new(code)?;
|
// We check that the module is generally valid,
|
||||||
|
// and does not have restricted WebAssembly features, here.
|
||||||
|
let contract_module = LoadedModule::new::<T>(code, determinism, None)?;
|
||||||
|
// The we check that module satisfies constraints the pallet puts on contracts.
|
||||||
contract_module.scan_exports()?;
|
contract_module.scan_exports()?;
|
||||||
contract_module.scan_imports::<T>()?;
|
contract_module.scan_imports::<T>(schedule)?;
|
||||||
contract_module.ensure_no_internal_memory()?;
|
|
||||||
contract_module.ensure_table_size_limit(schedule.limits.table_size)?;
|
|
||||||
contract_module.ensure_global_variable_limit(schedule.limits.globals)?;
|
|
||||||
contract_module.ensure_local_variable_limit(schedule.limits.locals)?;
|
|
||||||
contract_module.ensure_parameter_limit(schedule.limits.parameters)?;
|
|
||||||
contract_module.ensure_br_table_size_limit(schedule.limits.br_table_size)?;
|
|
||||||
// Extract memory limits from the module.
|
|
||||||
// This also checks that module's memory import satisfies the schedule.
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})()
|
})()
|
||||||
.map_err(|msg: &str| {
|
.map_err(|msg: &str| {
|
||||||
log::debug!(target: LOG_TARGET, "New code rejected: {}", msg);
|
log::debug!(target: LOG_TARGET, "New code rejected on validation: {}", msg);
|
||||||
(Error::<T>::CodeRejected.into(), msg)
|
(Error::<T>::CodeRejected.into(), msg)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@@ -375,6 +269,7 @@ where
|
|||||||
&code,
|
&code,
|
||||||
(),
|
(),
|
||||||
schedule,
|
schedule,
|
||||||
|
determinism,
|
||||||
stack_limits,
|
stack_limits,
|
||||||
AllowDeprecatedInterface::No,
|
AllowDeprecatedInterface::No,
|
||||||
)
|
)
|
||||||
@@ -383,13 +278,15 @@ where
|
|||||||
(Error::<T>::CodeRejected.into(), "New code rejected on wasmi instantiation!")
|
(Error::<T>::CodeRejected.into(), "New code rejected on wasmi instantiation!")
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validates the given binary `code` is a valid Wasm module satisfying following constraints:
|
/// Validates the given binary `code` is a valid Wasm module satisfying following constraints:
|
||||||
///
|
///
|
||||||
/// - The module doesn't define an internal memory instance.
|
/// - The module doesn't export any memory.
|
||||||
/// - Imported memory (if any) doesn't reserve more memory than permitted by the `schedule`.
|
/// - The module does imports memory, which limits lay within the limits permitted by the
|
||||||
|
/// `schedule`.
|
||||||
/// - All imported functions from the external environment match defined by `env` module.
|
/// - All imported functions from the external environment match defined by `env` module.
|
||||||
///
|
///
|
||||||
/// Also constructs contract `code_info` by calculating the storage deposit.
|
/// Also constructs contract `code_info` by calculating the storage deposit.
|
||||||
@@ -411,7 +308,6 @@ where
|
|||||||
let deposit = Diff { bytes_added, items_added: 2, ..Default::default() }
|
let deposit = Diff { bytes_added, items_added: 2, ..Default::default() }
|
||||||
.update_contract::<T>(None)
|
.update_contract::<T>(None)
|
||||||
.charge_or_zero();
|
.charge_or_zero();
|
||||||
|
|
||||||
let code_info = CodeInfo { owner, deposit, determinism, refcount: 0 };
|
let code_info = CodeInfo { owner, deposit, determinism, refcount: 0 };
|
||||||
let code_hash = T::Hashing::hash(&code);
|
let code_hash = T::Hashing::hash(&code);
|
||||||
|
|
||||||
@@ -427,23 +323,24 @@ where
|
|||||||
pub mod benchmarking {
|
pub mod benchmarking {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Prepare function that does not perform most checks on the passed in code.
|
/// Prepare function that does not perform export section checks on the passed in code.
|
||||||
pub fn prepare<T: Config>(
|
pub fn prepare<T: Config>(
|
||||||
code: Vec<u8>,
|
code: Vec<u8>,
|
||||||
schedule: &Schedule<T>,
|
schedule: &Schedule<T>,
|
||||||
owner: AccountIdOf<T>,
|
owner: AccountIdOf<T>,
|
||||||
) -> Result<WasmBlob<T>, DispatchError> {
|
) -> Result<WasmBlob<T>, DispatchError> {
|
||||||
let contract_module = ContractModule::new(&code)?;
|
let determinism = Determinism::Enforced;
|
||||||
let _ = get_memory_limits(contract_module.scan_imports::<T>()?, schedule)?;
|
let contract_module = LoadedModule::new::<T>(&code, determinism, None)?;
|
||||||
let code_hash = T::Hashing::hash(&code);
|
let _ = contract_module.scan_imports::<T>(schedule)?;
|
||||||
let code = code.try_into().map_err(|_| <Error<T>>::CodeTooLarge)?;
|
let code: CodeVec<T> = code.try_into().map_err(|_| <Error<T>>::CodeTooLarge)?;
|
||||||
let code_info = CodeInfo {
|
let code_info = CodeInfo {
|
||||||
owner,
|
owner,
|
||||||
// this is a helper function for benchmarking which skips deposit collection
|
// this is a helper function for benchmarking which skips deposit collection
|
||||||
deposit: Default::default(),
|
deposit: Default::default(),
|
||||||
refcount: 0,
|
refcount: 0,
|
||||||
determinism: Determinism::Enforced,
|
determinism,
|
||||||
};
|
};
|
||||||
|
let code_hash = T::Hashing::hash(&code);
|
||||||
|
|
||||||
Ok(WasmBlob { code, code_info, code_hash })
|
Ok(WasmBlob { code, code_info, code_hash })
|
||||||
}
|
}
|
||||||
@@ -540,108 +437,9 @@ mod tests {
|
|||||||
)
|
)
|
||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
)"#,
|
)"#,
|
||||||
Err("Validation of new code failed!")
|
Err("Can't load the module into wasmi!")
|
||||||
);
|
);
|
||||||
|
|
||||||
mod functions {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
prepare_test!(
|
|
||||||
param_number_valid,
|
|
||||||
r#"
|
|
||||||
(module
|
|
||||||
(func (export "call"))
|
|
||||||
(func (export "deploy"))
|
|
||||||
(func (param i32 i32 i32))
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
Ok(_)
|
|
||||||
);
|
|
||||||
|
|
||||||
prepare_test!(
|
|
||||||
param_number_invalid,
|
|
||||||
r#"
|
|
||||||
(module
|
|
||||||
(func (export "call"))
|
|
||||||
(func (export "deploy"))
|
|
||||||
(func (param i32 i32 i32 i32))
|
|
||||||
(func (param i32))
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
Err("Use of a function type with too many parameters.")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
mod globals {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
prepare_test!(
|
|
||||||
global_number_valid,
|
|
||||||
r#"
|
|
||||||
(module
|
|
||||||
(global i64 (i64.const 0))
|
|
||||||
(global i64 (i64.const 0))
|
|
||||||
(global i64 (i64.const 0))
|
|
||||||
(func (export "call"))
|
|
||||||
(func (export "deploy"))
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
Ok(_)
|
|
||||||
);
|
|
||||||
|
|
||||||
prepare_test!(
|
|
||||||
global_number_too_high,
|
|
||||||
r#"
|
|
||||||
(module
|
|
||||||
(global i64 (i64.const 0))
|
|
||||||
(global i64 (i64.const 0))
|
|
||||||
(global i64 (i64.const 0))
|
|
||||||
(global i64 (i64.const 0))
|
|
||||||
(func (export "call"))
|
|
||||||
(func (export "deploy"))
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
Err("module declares too many globals")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
mod locals {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
prepare_test!(
|
|
||||||
local_number_valid,
|
|
||||||
r#"
|
|
||||||
(module
|
|
||||||
(func
|
|
||||||
(local i32)
|
|
||||||
(local i32)
|
|
||||||
(local i32)
|
|
||||||
)
|
|
||||||
(func (export "call"))
|
|
||||||
(func (export "deploy"))
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
Ok(_)
|
|
||||||
);
|
|
||||||
|
|
||||||
prepare_test!(
|
|
||||||
local_number_too_high,
|
|
||||||
r#"
|
|
||||||
(module
|
|
||||||
(func
|
|
||||||
(local i32)
|
|
||||||
(local i32)
|
|
||||||
(local i32)
|
|
||||||
(local i32)
|
|
||||||
)
|
|
||||||
(func (export "call"))
|
|
||||||
(func (export "deploy"))
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
Err("single function declares too many locals")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
mod memories {
|
mod memories {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@@ -668,7 +466,7 @@ mod tests {
|
|||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
)
|
)
|
||||||
"#,
|
"#,
|
||||||
Err("module declares internal memory")
|
Err("No memory import found in the module")
|
||||||
);
|
);
|
||||||
|
|
||||||
prepare_test!(
|
prepare_test!(
|
||||||
@@ -680,7 +478,7 @@ mod tests {
|
|||||||
(func (export "call"))
|
(func (export "call"))
|
||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
)"#,
|
)"#,
|
||||||
Ok(_)
|
Err("No memory import found in the module")
|
||||||
);
|
);
|
||||||
|
|
||||||
prepare_test!(
|
prepare_test!(
|
||||||
@@ -693,7 +491,7 @@ mod tests {
|
|||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
)
|
)
|
||||||
"#,
|
"#,
|
||||||
Err("Validation of new code failed!")
|
Err("Can't load the module into wasmi!")
|
||||||
);
|
);
|
||||||
|
|
||||||
prepare_test!(
|
prepare_test!(
|
||||||
@@ -719,7 +517,7 @@ mod tests {
|
|||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
)
|
)
|
||||||
"#,
|
"#,
|
||||||
Err("New code rejected on wasmi instantiation!")
|
Err("Maximum number of memory pages should not exceed the maximum configured in the Schedule")
|
||||||
);
|
);
|
||||||
|
|
||||||
prepare_test!(
|
prepare_test!(
|
||||||
@@ -746,7 +544,7 @@ mod tests {
|
|||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
)
|
)
|
||||||
"#,
|
"#,
|
||||||
Err("Validation of new code failed!")
|
Err("Can't load the module into wasmi!")
|
||||||
);
|
);
|
||||||
|
|
||||||
prepare_test!(
|
prepare_test!(
|
||||||
@@ -775,75 +573,6 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
mod tables {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
prepare_test!(
|
|
||||||
no_tables,
|
|
||||||
r#"
|
|
||||||
(module
|
|
||||||
(func (export "call"))
|
|
||||||
(func (export "deploy"))
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
Ok(_)
|
|
||||||
);
|
|
||||||
|
|
||||||
prepare_test!(
|
|
||||||
table_valid_size,
|
|
||||||
r#"
|
|
||||||
(module
|
|
||||||
(table 3 funcref)
|
|
||||||
|
|
||||||
(func (export "call"))
|
|
||||||
(func (export "deploy"))
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
Ok(_)
|
|
||||||
);
|
|
||||||
|
|
||||||
prepare_test!(
|
|
||||||
table_too_big,
|
|
||||||
r#"
|
|
||||||
(module
|
|
||||||
(table 4 funcref)
|
|
||||||
|
|
||||||
(func (export "call"))
|
|
||||||
(func (export "deploy"))
|
|
||||||
)"#,
|
|
||||||
Err("table exceeds maximum size allowed")
|
|
||||||
);
|
|
||||||
|
|
||||||
prepare_test!(
|
|
||||||
br_table_valid_size,
|
|
||||||
r#"
|
|
||||||
(module
|
|
||||||
(func (export "call"))
|
|
||||||
(func (export "deploy"))
|
|
||||||
(func
|
|
||||||
i32.const 0
|
|
||||||
br_table 0 0 0 0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
Ok(_)
|
|
||||||
);
|
|
||||||
|
|
||||||
prepare_test!(
|
|
||||||
br_table_too_big,
|
|
||||||
r#"
|
|
||||||
(module
|
|
||||||
(func (export "call"))
|
|
||||||
(func (export "deploy"))
|
|
||||||
(func
|
|
||||||
i32.const 0
|
|
||||||
br_table 0 0 0 0 0
|
|
||||||
)
|
|
||||||
)"#,
|
|
||||||
Err("BrTable's immediate value is too big.")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
mod imports {
|
mod imports {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@@ -852,6 +581,7 @@ mod tests {
|
|||||||
r#"
|
r#"
|
||||||
(module
|
(module
|
||||||
(import "seal0" "nop" (func (param i64)))
|
(import "seal0" "nop" (func (param i64)))
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
(func (export "call"))
|
(func (export "call"))
|
||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
@@ -874,7 +604,7 @@ mod tests {
|
|||||||
Err("Invalid module for imported memory")
|
Err("Invalid module for imported memory")
|
||||||
);
|
);
|
||||||
|
|
||||||
// memory is in "env" and not in some arbitrary module
|
// Memory is in "env" and not in some arbitrary module
|
||||||
prepare_test!(
|
prepare_test!(
|
||||||
memory_not_in_arbitrary_module,
|
memory_not_in_arbitrary_module,
|
||||||
r#"
|
r#"
|
||||||
@@ -893,6 +623,8 @@ mod tests {
|
|||||||
r#"
|
r#"
|
||||||
(module
|
(module
|
||||||
(import "seal1" "nop" (func (param i32)))
|
(import "seal1" "nop" (func (param i32)))
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
|
|
||||||
(func (export "call"))
|
(func (export "call"))
|
||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
@@ -906,6 +638,7 @@ mod tests {
|
|||||||
r#"
|
r#"
|
||||||
(module
|
(module
|
||||||
(import "seal0" "input" (func (param i64)))
|
(import "seal0" "input" (func (param i64)))
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
(func (export "call"))
|
(func (export "call"))
|
||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
@@ -924,6 +657,21 @@ mod tests {
|
|||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
)
|
)
|
||||||
"#,
|
"#,
|
||||||
|
Err("No memory import found in the module")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try to import function from not a "seal*" module.
|
||||||
|
prepare_test!(
|
||||||
|
try_import_from_wrong_module,
|
||||||
|
r#"
|
||||||
|
(module
|
||||||
|
(import "env" "panic" (func))
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
|
(func (export "call"))
|
||||||
|
(func (export "deploy"))
|
||||||
|
)
|
||||||
|
"#,
|
||||||
Err("New code rejected on wasmi instantiation!")
|
Err("New code rejected on wasmi instantiation!")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -935,6 +683,7 @@ mod tests {
|
|||||||
it_works,
|
it_works,
|
||||||
r#"
|
r#"
|
||||||
(module
|
(module
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
(func (export "call"))
|
(func (export "call"))
|
||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
)
|
)
|
||||||
@@ -942,6 +691,17 @@ mod tests {
|
|||||||
Ok(_)
|
Ok(_)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
prepare_test!(
|
||||||
|
omit_memory,
|
||||||
|
r#"
|
||||||
|
(module
|
||||||
|
(func (export "call"))
|
||||||
|
(func (export "deploy"))
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
Err("No memory import found in the module")
|
||||||
|
);
|
||||||
|
|
||||||
prepare_test!(
|
prepare_test!(
|
||||||
omit_deploy,
|
omit_deploy,
|
||||||
r#"
|
r#"
|
||||||
@@ -963,21 +723,23 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Try to use imported function as an entry point.
|
// Try to use imported function as an entry point.
|
||||||
|
// This is allowed.
|
||||||
prepare_test!(
|
prepare_test!(
|
||||||
try_sneak_export_as_entrypoint,
|
try_sneak_export_as_entrypoint,
|
||||||
r#"
|
r#"
|
||||||
(module
|
(module
|
||||||
(import "seal0" "panic" (func))
|
(import "seal0" "panic" (func))
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
|
|
||||||
(export "call" (func 0))
|
(export "call" (func 0))
|
||||||
)
|
)
|
||||||
"#,
|
"#,
|
||||||
Err("entry point points to an imported function")
|
Ok(_)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Try to use imported function as an entry point.
|
// Try to use global as an entry point.
|
||||||
prepare_test!(
|
prepare_test!(
|
||||||
try_sneak_export_as_global,
|
try_sneak_export_as_global,
|
||||||
r#"
|
r#"
|
||||||
@@ -986,7 +748,7 @@ mod tests {
|
|||||||
(global (export "call") i32 (i32.const 0))
|
(global (export "call") i32 (i32.const 0))
|
||||||
)
|
)
|
||||||
"#,
|
"#,
|
||||||
Err("expected a function")
|
Err("global export is forbidden")
|
||||||
);
|
);
|
||||||
|
|
||||||
prepare_test!(
|
prepare_test!(
|
||||||
@@ -1009,7 +771,7 @@ mod tests {
|
|||||||
(func (export "whatevs"))
|
(func (export "whatevs"))
|
||||||
)
|
)
|
||||||
"#,
|
"#,
|
||||||
Err("unknown export: expecting only deploy and call functions")
|
Err("unknown function export: expecting only deploy and call functions")
|
||||||
);
|
);
|
||||||
|
|
||||||
prepare_test!(
|
prepare_test!(
|
||||||
@@ -1021,7 +783,7 @@ mod tests {
|
|||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
)
|
)
|
||||||
"#,
|
"#,
|
||||||
Err("Validation of new code failed!")
|
Err("Can't load the module into wasmi!")
|
||||||
);
|
);
|
||||||
|
|
||||||
prepare_test!(
|
prepare_test!(
|
||||||
@@ -1033,7 +795,7 @@ mod tests {
|
|||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
)
|
)
|
||||||
"#,
|
"#,
|
||||||
Err("Validation of new code failed!")
|
Err("Can't load the module into wasmi!")
|
||||||
);
|
);
|
||||||
|
|
||||||
prepare_test!(
|
prepare_test!(
|
||||||
@@ -1045,7 +807,7 @@ mod tests {
|
|||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
)
|
)
|
||||||
"#,
|
"#,
|
||||||
Err("Validation of new code failed!")
|
Err("Can't load the module into wasmi!")
|
||||||
);
|
);
|
||||||
|
|
||||||
prepare_test!(
|
prepare_test!(
|
||||||
@@ -1057,7 +819,7 @@ mod tests {
|
|||||||
(func (export "deploy"))
|
(func (export "deploy"))
|
||||||
)
|
)
|
||||||
"#,
|
"#,
|
||||||
Err("Validation of new code failed!")
|
Err("Can't load the module into wasmi!")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+619
-623
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user