mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-16 17:51:10 +00:00
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:
committed by
GitHub
parent
e69c3649b5
commit
08657f14b7
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user