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
+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))