From 4c3614337536868b094f0acac7930fe0e059f55c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= <bkchr@users.noreply.github.com> Date: Wed, 29 Jan 2020 16:24:40 +0100 Subject: [PATCH] Add `get_global` for `Sandbox` (#4756) * Add `get_global` for `Sandbox` This pr adds `get_global` to retrieve a `global` variable from an instantiated sandbox wasm blob. * Bump `spec_version` * Update primitives/wasm-interface/src/lib.rs Co-Authored-By: Sergei Pepyakin <sergei@parity.io> * `get_global` -> `get_global_val` Co-authored-by: Sergei Pepyakin <s.pepyakin@gmail.com> Co-authored-by: Gavin Wood <github@gavwood.com> --- substrate/Cargo.lock | 9 +- substrate/bin/node/runtime/src/lib.rs | 2 +- .../client/executor/common/src/sandbox.rs | 17 +++- .../client/executor/runtime-test/src/lib.rs | 34 +++++-- .../executor/src/integration_tests/mod.rs | 4 +- .../executor/src/integration_tests/sandbox.rs | 23 +++++ substrate/client/executor/wasmi/src/lib.rs | 15 ++- .../wasmtime/src/function_executor.rs | 15 ++- .../contracts/src/wasm/env_def/macros.rs | 20 ++-- .../frame/contracts/src/wasm/env_def/mod.rs | 30 +++--- substrate/frame/contracts/src/wasm/runtime.rs | 6 +- substrate/primitives/core/src/sandbox.rs | 93 ------------------- substrate/primitives/io/Cargo.toml | 2 + substrate/primitives/io/src/lib.rs | 8 ++ .../primitives/runtime-interface/Cargo.toml | 4 +- .../primitives/runtime-interface/src/impls.rs | 10 +- substrate/primitives/runtime/Cargo.toml | 2 +- substrate/primitives/sandbox/Cargo.toml | 2 + substrate/primitives/sandbox/src/lib.rs | 14 ++- substrate/primitives/sandbox/with_std.rs | 67 ++++++------- substrate/primitives/sandbox/without_std.rs | 14 ++- .../primitives/wasm-interface/Cargo.toml | 3 +- .../primitives/wasm-interface/src/lib.rs | 76 ++++++++++++++- 23 files changed, 275 insertions(+), 195 deletions(-) diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index 88b73133f9c..0ad431f9d39 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -4206,13 +4206,13 @@ dependencies = [ "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "bitvec 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", "byte-slice-cast 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-scale-codec-derive 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-scale-codec-derive 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "parity-scale-codec-derive" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro-crate 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -6594,6 +6594,7 @@ dependencies = [ "sp-state-machine 0.8.0", "sp-std 2.0.0", "sp-trie 2.0.0", + "sp-wasm-interface 2.0.0", ] [[package]] @@ -6724,6 +6725,7 @@ dependencies = [ "sp-core 2.0.0", "sp-io 2.0.0", "sp-std 2.0.0", + "sp-wasm-interface 2.0.0", "wabt 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", "wasmi 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -6857,6 +6859,7 @@ name = "sp-wasm-interface" version = "2.0.0" dependencies = [ "impl-trait-for-tuples 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-scale-codec 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "sp-std 2.0.0", "wasmi 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -8700,7 +8703,7 @@ dependencies = [ "checksum parity-multihash 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "df3a17dc27848fd99e4f87eb0f8c9baba6ede0a6d555400c850ca45254ef4ce3" "checksum parity-multihash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70a4d7b05e51bff5ae2c29c7b8c3d889985bbd8f4e15b3542fcc1f6f9666d292" "checksum parity-scale-codec 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f747c06d9f3b2ad387ac881b9667298c81b1243aa9833f086e05996937c35507" -"checksum parity-scale-codec-derive 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "492ac3aa93d6caa5d20e4e3e0b75d08e2dcd9dd8a50d19529548b6fe11b3f295" +"checksum parity-scale-codec-derive 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "34e513ff3e406f3ede6796dcdc83d0b32ffb86668cea1ccf7363118abeb00476" "checksum parity-send-wrapper 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aa9777aa91b8ad9dd5aaa04a9b6bcb02c7f1deb952fca5a66034d5e63afc5c6f" "checksum parity-util-mem 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8174d85e62c4d615fddd1ef67966bdc5757528891d0742f15b131ad04667b3f9" "checksum parity-util-mem 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "900dd84654b048e5bad420bb341658fc2c4d7fea628c22bcf4621733e54859b4" diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index bad1ea41749..0d4b094c79b 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -81,7 +81,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. spec_version: 208, - impl_version: 0, + impl_version: 1, apis: RUNTIME_API_VERSIONS, }; diff --git a/substrate/client/executor/common/src/sandbox.rs b/substrate/client/executor/common/src/sandbox.rs index e48afd48917..89285a75fc9 100644 --- a/substrate/client/executor/common/src/sandbox.rs +++ b/substrate/client/executor/common/src/sandbox.rs @@ -224,7 +224,8 @@ fn trap(msg: &'static str) -> Trap { } fn deserialize_result(serialized_result: &[u8]) -> std::result::Result<Option<RuntimeValue>, Trap> { - use self::sandbox_primitives::{HostError, ReturnValue}; + use self::sandbox_primitives::HostError; + use sp_wasm_interface::ReturnValue; let result_val = std::result::Result::<ReturnValue, HostError>::decode(&mut &serialized_result[..]) .map_err(|_| trap("Decoding Result<ReturnValue, HostError> failed!"))?; @@ -260,7 +261,7 @@ impl<'a, FE: SandboxCapabilities + 'a> Externals for GuestExternals<'a, FE> { let invoke_args_data: Vec<u8> = args.as_ref() .iter() .cloned() - .map(sandbox_primitives::TypedValue::from) + .map(sp_wasm_interface::Value::from) .collect::<Vec<_>>() .encode(); @@ -362,6 +363,18 @@ impl<FR> SandboxInstance<FR> { }, ) } + + /// Get the value from a global with the given `name`. + /// + /// Returns `Some(_)` if the global could be found. + pub fn get_global_val(&self, name: &str) -> Option<sp_wasm_interface::Value> { + let global = self.instance + .export_by_name(name)? + .as_global()? + .get(); + + Some(global.into()) + } } /// Error occurred during instantiation of a sandboxed module. diff --git a/substrate/client/executor/runtime-test/src/lib.rs b/substrate/client/executor/runtime-test/src/lib.rs index f6d37abf942..b183398c02f 100644 --- a/substrate/client/executor/runtime-test/src/lib.rs +++ b/substrate/client/executor/runtime-test/src/lib.rs @@ -17,6 +17,8 @@ use sp_io::{ use sp_runtime::{print, traits::{BlakeTwo256, Hash}}; #[cfg(not(feature = "std"))] use sp_core::{ed25519, sr25519}; +#[cfg(not(feature = "std"))] +use sp_sandbox::Value; extern "C" { #[allow(dead_code)] @@ -133,8 +135,8 @@ sp_core::wasm_export_functions! { execute_sandboxed( &code, &[ - sp_sandbox::TypedValue::I32(0x12345678), - sp_sandbox::TypedValue::I64(0x1234567887654321), + Value::I32(0x12345678), + Value::I64(0x1234567887654321), ], ).is_ok() } @@ -143,10 +145,10 @@ sp_core::wasm_export_functions! { let ok = match execute_sandboxed( &code, &[ - sp_sandbox::TypedValue::I32(0x1336), + Value::I32(0x1336), ] ) { - Ok(sp_sandbox::ReturnValue::Value(sp_sandbox::TypedValue::I32(0x1337))) => true, + Ok(sp_sandbox::ReturnValue::Value(Value::I32(0x1337))) => true, _ => false, }; @@ -165,6 +167,22 @@ sp_core::wasm_export_functions! { code } + + fn test_sandbox_get_global_val(code: Vec<u8>) -> i64 { + let env_builder = sp_sandbox::EnvironmentDefinitionBuilder::new(); + let instance = if let Ok(i) = sp_sandbox::Instance::new(&code, &env_builder, &mut ()) { + i + } else { + return 20; + }; + + match instance.get_global_val("test_global") { + Some(sp_sandbox::Value::I64(val)) => val, + None => 30, + val => 40, + } + } + fn test_offchain_local_storage() -> bool { let kind = sp_core::offchain::StorageKind::PERSISTENT; assert_eq!(sp_io::offchain::local_storage_get(kind, b"test"), None); @@ -262,7 +280,7 @@ sp_core::wasm_export_functions! { #[cfg(not(feature = "std"))] fn execute_sandboxed( code: &[u8], - args: &[sp_sandbox::TypedValue], + args: &[Value], ) -> Result<sp_sandbox::ReturnValue, sp_sandbox::HostError> { struct State { counter: u32, @@ -270,7 +288,7 @@ fn execute_sandboxed( fn env_assert( _e: &mut State, - args: &[sp_sandbox::TypedValue], + args: &[Value], ) -> Result<sp_sandbox::ReturnValue, sp_sandbox::HostError> { if args.len() != 1 { return Err(sp_sandbox::HostError); @@ -284,14 +302,14 @@ fn execute_sandboxed( } fn env_inc_counter( e: &mut State, - args: &[sp_sandbox::TypedValue], + args: &[Value], ) -> Result<sp_sandbox::ReturnValue, sp_sandbox::HostError> { if args.len() != 1 { return Err(sp_sandbox::HostError); } let inc_by = args[0].as_i32().ok_or_else(|| sp_sandbox::HostError)?; e.counter += inc_by as u32; - Ok(sp_sandbox::ReturnValue::Value(sp_sandbox::TypedValue::I32(e.counter as i32))) + Ok(sp_sandbox::ReturnValue::Value(Value::I32(e.counter as i32))) } let mut state = State { counter: 0 }; diff --git a/substrate/client/executor/src/integration_tests/mod.rs b/substrate/client/executor/src/integration_tests/mod.rs index 02a5897ea42..3f3d9f69e13 100644 --- a/substrate/client/executor/src/integration_tests/mod.rs +++ b/substrate/client/executor/src/integration_tests/mod.rs @@ -88,7 +88,7 @@ fn call_not_existing_function(wasm_method: WasmExecutionMethod) { #[cfg(feature = "wasmtime")] WasmExecutionMethod::Compiled => assert_eq!( &format!("{:?}", e), - "Other(\"call to undefined external function with index 67\")" + "Other(\"call to undefined external function with index 68\")" ), } } @@ -117,7 +117,7 @@ fn call_yet_another_not_existing_function(wasm_method: WasmExecutionMethod) { #[cfg(feature = "wasmtime")] WasmExecutionMethod::Compiled => assert_eq!( &format!("{:?}", e), - "Other(\"call to undefined external function with index 68\")" + "Other(\"call to undefined external function with index 69\")" ), } } diff --git a/substrate/client/executor/src/integration_tests/sandbox.rs b/substrate/client/executor/src/integration_tests/sandbox.rs index 9458542e3a1..8e8b7896cf9 100644 --- a/substrate/client/executor/src/integration_tests/sandbox.rs +++ b/substrate/client/executor/src/integration_tests/sandbox.rs @@ -302,3 +302,26 @@ fn start_fn_traps(wasm_method: WasmExecutionMethod) { 2u8.encode(), ); } + +#[test_case(WasmExecutionMethod::Interpreted)] +#[cfg_attr(feature = "wasmtime", test_case(WasmExecutionMethod::Compiled))] +fn get_global_val_works(wasm_method: WasmExecutionMethod) { + let mut ext = TestExternalities::default(); + let mut ext = ext.ext(); + + let code = wabt::wat2wasm(r#" + (module + (global (export "test_global") i64 (i64.const 500)) + ) + "#).unwrap().encode(); + + assert_eq!( + call_in_wasm( + "test_sandbox_get_global_val", + &code, + wasm_method, + &mut ext, + ).unwrap(), + 500i64.encode(), + ); +} diff --git a/substrate/client/executor/wasmi/src/lib.rs b/substrate/client/executor/wasmi/src/lib.rs index c4701fd8275..1bcb1aab8af 100644 --- a/substrate/client/executor/wasmi/src/lib.rs +++ b/substrate/client/executor/wasmi/src/lib.rs @@ -213,7 +213,7 @@ impl<'a> Sandbox for FunctionExecutor<'a> { trace!(target: "sp-sandbox", "invoke, instance_idx={}", instance_id); // Deserialize arguments and convert them into wasmi types. - let args = Vec::<sandbox_primitives::TypedValue>::decode(&mut &args[..]) + let args = Vec::<sp_wasm_interface::Value>::decode(&mut &args[..]) .map_err(|_| "Can't decode serialized arguments for the invocation")? .into_iter() .map(Into::into) @@ -226,7 +226,7 @@ impl<'a> Sandbox for FunctionExecutor<'a> { Ok(None) => Ok(sandbox_primitives::ERR_OK), Ok(Some(val)) => { // Serialize return value and write it back into the memory. - sandbox_primitives::ReturnValue::Value(val.into()).using_encoded(|val| { + sp_wasm_interface::ReturnValue::Value(val.into()).using_encoded(|val| { if val.len() > return_val_len as usize { Err("Return value buffer is too small")?; } @@ -269,6 +269,17 @@ impl<'a> Sandbox for FunctionExecutor<'a> { Ok(instance_idx_or_err_code as u32) } + + fn get_global_val( + &self, + instance_idx: u32, + name: &str, + ) -> WResult<Option<sp_wasm_interface::Value>> { + self.sandbox_store + .instance(instance_idx) + .map(|i| i.get_global_val(name)) + .map_err(|e| e.to_string()) + } } /// Will be used on initialization of a module to resolve function and memory imports. diff --git a/substrate/client/executor/wasmtime/src/function_executor.rs b/substrate/client/executor/wasmtime/src/function_executor.rs index bc665f18e47..7bbf5a456bf 100644 --- a/substrate/client/executor/wasmtime/src/function_executor.rs +++ b/substrate/client/executor/wasmtime/src/function_executor.rs @@ -286,7 +286,7 @@ impl<'a> Sandbox for FunctionExecutor<'a> { trace!(target: "sp-sandbox", "invoke, instance_idx={}", instance_id); // Deserialize arguments and convert them into wasmi types. - let args = Vec::<sandbox_primitives::TypedValue>::decode(&mut &args[..]) + let args = Vec::<sp_wasm_interface::Value>::decode(&mut &args[..]) .map_err(|_| "Can't decode serialized arguments for the invocation")? .into_iter() .map(Into::into) @@ -299,7 +299,7 @@ impl<'a> Sandbox for FunctionExecutor<'a> { Ok(None) => Ok(sandbox_primitives::ERR_OK), Ok(Some(val)) => { // Serialize return value and write it back into the memory. - sandbox_primitives::ReturnValue::Value(val.into()).using_encoded(|val| { + sp_wasm_interface::ReturnValue::Value(val.into()).using_encoded(|val| { if val.len() > return_val_len as usize { Err("Return value buffer is too small")?; } @@ -337,6 +337,17 @@ impl<'a> Sandbox for FunctionExecutor<'a> { Ok(instance_idx_or_err_code as u32) } + + fn get_global_val( + &self, + instance_idx: u32, + name: &str, + ) -> WResult<Option<sp_wasm_interface::Value>> { + self.sandbox_store + .instance(instance_idx) + .map(|i| i.get_global_val(name)) + .map_err(|e| e.to_string()) + } } // The storage for a Wasmtime invocation argument. diff --git a/substrate/frame/contracts/src/wasm/env_def/macros.rs b/substrate/frame/contracts/src/wasm/env_def/macros.rs index 89a147b2c6f..335d35f1e78 100644 --- a/substrate/frame/contracts/src/wasm/env_def/macros.rs +++ b/substrate/frame/contracts/src/wasm/env_def/macros.rs @@ -126,7 +126,7 @@ macro_rules! define_func { ( < E: $ext_ty:tt > $name:ident ( $ctx: ident $(, $names:ident : $params:ty)*) $(-> $returns:ty)* => $body:tt ) => { fn $name< E: $ext_ty >( $ctx: &mut $crate::wasm::Runtime<E>, - args: &[sp_sandbox::TypedValue], + args: &[sp_sandbox::Value], ) -> Result<sp_sandbox::ReturnValue, sp_sandbox::HostError> { #[allow(unused)] let mut args = args.iter(); @@ -196,7 +196,7 @@ mod tests { use parity_wasm::elements::FunctionType; use parity_wasm::elements::ValueType; use sp_runtime::traits::Zero; - use sp_sandbox::{self, ReturnValue, TypedValue}; + use sp_sandbox::{ReturnValue, Value}; use crate::wasm::tests::MockExt; use crate::wasm::Runtime; use crate::exec::Ext; @@ -206,7 +206,7 @@ mod tests { fn macro_unmarshall_then_body_then_marshall_value_or_trap() { fn test_value( _ctx: &mut u32, - args: &[sp_sandbox::TypedValue], + args: &[sp_sandbox::Value], ) -> Result<ReturnValue, sp_sandbox::HostError> { let mut args = args.iter(); unmarshall_then_body_then_marshall!( @@ -224,17 +224,17 @@ mod tests { let ctx = &mut 0; assert_eq!( - test_value(ctx, &[TypedValue::I32(15), TypedValue::I32(3)]).unwrap(), - ReturnValue::Value(TypedValue::I32(5)), + test_value(ctx, &[Value::I32(15), Value::I32(3)]).unwrap(), + ReturnValue::Value(Value::I32(5)), ); - assert!(test_value(ctx, &[TypedValue::I32(15), TypedValue::I32(0)]).is_err()); + assert!(test_value(ctx, &[Value::I32(15), Value::I32(0)]).is_err()); } #[test] fn macro_unmarshall_then_body_then_marshall_unit() { fn test_unit( ctx: &mut u32, - args: &[sp_sandbox::TypedValue], + args: &[sp_sandbox::Value], ) -> Result<ReturnValue, sp_sandbox::HostError> { let mut args = args.iter(); unmarshall_then_body_then_marshall!( @@ -248,7 +248,7 @@ mod tests { } let ctx = &mut 0; - let result = test_unit(ctx, &[TypedValue::I32(2), TypedValue::I32(3)]).unwrap(); + let result = test_unit(ctx, &[Value::I32(2), Value::I32(3)]).unwrap(); assert_eq!(result, ReturnValue::Unit); assert_eq!(*ctx, 5); } @@ -263,7 +263,7 @@ mod tests { Err(sp_sandbox::HostError) } }); - let _f: fn(&mut Runtime<MockExt>, &[sp_sandbox::TypedValue]) + let _f: fn(&mut Runtime<MockExt>, &[sp_sandbox::Value]) -> Result<sp_sandbox::ReturnValue, sp_sandbox::HostError> = ext_gas::<MockExt>; } @@ -282,7 +282,7 @@ mod tests { #[test] fn macro_unmarshall_then_body() { - let args = vec![TypedValue::I32(5), TypedValue::I32(3)]; + let args = vec![Value::I32(5), Value::I32(3)]; let mut args = args.iter(); let ctx: &mut u32 = &mut 0; diff --git a/substrate/frame/contracts/src/wasm/env_def/mod.rs b/substrate/frame/contracts/src/wasm/env_def/mod.rs index 004308da422..7b67f74ec95 100644 --- a/substrate/frame/contracts/src/wasm/env_def/mod.rs +++ b/substrate/frame/contracts/src/wasm/env_def/mod.rs @@ -17,7 +17,7 @@ use super::Runtime; use crate::exec::Ext; -use sp_sandbox::{self, TypedValue}; +use sp_sandbox::Value; use parity_wasm::elements::{FunctionType, ValueType}; #[macro_use] @@ -26,28 +26,28 @@ pub(crate) mod macros; pub trait ConvertibleToWasm: Sized { const VALUE_TYPE: ValueType; type NativeType; - fn to_typed_value(self) -> TypedValue; - fn from_typed_value(_: TypedValue) -> Option<Self>; + fn to_typed_value(self) -> Value; + fn from_typed_value(_: Value) -> Option<Self>; } impl ConvertibleToWasm for i32 { type NativeType = i32; const VALUE_TYPE: ValueType = ValueType::I32; - fn to_typed_value(self) -> TypedValue { - TypedValue::I32(self) + fn to_typed_value(self) -> Value { + Value::I32(self) } - fn from_typed_value(v: TypedValue) -> Option<Self> { + fn from_typed_value(v: Value) -> Option<Self> { v.as_i32() } } impl ConvertibleToWasm for u32 { type NativeType = u32; const VALUE_TYPE: ValueType = ValueType::I32; - fn to_typed_value(self) -> TypedValue { - TypedValue::I32(self as i32) + fn to_typed_value(self) -> Value { + Value::I32(self as i32) } - fn from_typed_value(v: TypedValue) -> Option<Self> { + fn from_typed_value(v: Value) -> Option<Self> { match v { - TypedValue::I32(v) => Some(v as u32), + Value::I32(v) => Some(v as u32), _ => None, } } @@ -55,12 +55,12 @@ impl ConvertibleToWasm for u32 { impl ConvertibleToWasm for u64 { type NativeType = u64; const VALUE_TYPE: ValueType = ValueType::I64; - fn to_typed_value(self) -> TypedValue { - TypedValue::I64(self as i64) + fn to_typed_value(self) -> Value { + Value::I64(self as i64) } - fn from_typed_value(v: TypedValue) -> Option<Self> { + fn from_typed_value(v: Value) -> Option<Self> { match v { - TypedValue::I64(v) => Some(v as u64), + Value::I64(v) => Some(v as u64), _ => None, } } @@ -69,7 +69,7 @@ impl ConvertibleToWasm for u64 { pub(crate) type HostFunc<E> = fn( &mut Runtime<E>, - &[sp_sandbox::TypedValue] + &[sp_sandbox::Value] ) -> Result<sp_sandbox::ReturnValue, sp_sandbox::HostError>; pub(crate) trait FunctionImplProvider<E: Ext> { diff --git a/substrate/frame/contracts/src/wasm/runtime.rs b/substrate/frame/contracts/src/wasm/runtime.rs index 07cb8cb524e..66ae8a4996c 100644 --- a/substrate/frame/contracts/src/wasm/runtime.rs +++ b/substrate/frame/contracts/src/wasm/runtime.rs @@ -23,9 +23,7 @@ use crate::exec::{ use crate::gas::{Gas, GasMeter, Token, GasMeterResult, approx_gas_for_balance}; use sp_sandbox; use frame_system; -use sp_std::prelude::*; -use sp_std::convert::TryInto; -use sp_std::mem; +use sp_std::{prelude::*, mem, convert::TryInto}; use codec::{Decode, Encode}; use sp_runtime::traits::{Bounded, SaturatedConversion}; @@ -89,7 +87,7 @@ pub(crate) fn to_execution_result<E: Ext>( buffer.clear(); Ok(ExecReturnValue { status: STATUS_SUCCESS, data: buffer }) } - Ok(sp_sandbox::ReturnValue::Value(sp_sandbox::TypedValue::I32(exit_code))) => { + Ok(sp_sandbox::ReturnValue::Value(sp_sandbox::Value::I32(exit_code))) => { let status = (exit_code & 0xFF).try_into() .expect("exit_code is masked into the range of a u8; qed"); Ok(ExecReturnValue { status, data: runtime.scratch_buf }) diff --git a/substrate/primitives/core/src/sandbox.rs b/substrate/primitives/core/src/sandbox.rs index a0673c49db1..73fbcfb572e 100644 --- a/substrate/primitives/core/src/sandbox.rs +++ b/substrate/primitives/core/src/sandbox.rs @@ -24,99 +24,6 @@ use sp_std::vec::Vec; #[derive(crate::RuntimeDebug)] pub struct HostError; -/// Representation of a typed wasm value. -#[derive(Clone, Copy, PartialEq, Encode, Decode)] -#[derive(crate::RuntimeDebug)] -pub enum TypedValue { - /// Value of 32-bit signed or unsigned integer. - #[codec(index = "1")] - I32(i32), - - /// Value of 64-bit signed or unsigned integer. - #[codec(index = "2")] - I64(i64), - - /// Value of 32-bit IEEE 754-2008 floating point number represented as a bit pattern. - #[codec(index = "3")] - F32(i32), - - /// Value of 64-bit IEEE 754-2008 floating point number represented as a bit pattern. - #[codec(index = "4")] - F64(i64), -} - -impl TypedValue { - /// Returns `Some` if this value of type `I32`. - pub fn as_i32(&self) -> Option<i32> { - match *self { - TypedValue::I32(v) => Some(v), - _ => None, - } - } -} - -#[cfg(feature = "std")] -impl From<::wasmi::RuntimeValue> for TypedValue { - fn from(val: ::wasmi::RuntimeValue) -> TypedValue { - use ::wasmi::RuntimeValue; - match val { - RuntimeValue::I32(v) => TypedValue::I32(v), - RuntimeValue::I64(v) => TypedValue::I64(v), - RuntimeValue::F32(v) => TypedValue::F32(v.to_bits() as i32), - RuntimeValue::F64(v) => TypedValue::F64(v.to_bits() as i64), - } - } -} - -#[cfg(feature = "std")] -impl From<TypedValue> for ::wasmi::RuntimeValue { - fn from(val: TypedValue) -> ::wasmi::RuntimeValue { - use ::wasmi::RuntimeValue; - use ::wasmi::nan_preserving_float::{F32, F64}; - match val { - TypedValue::I32(v) => RuntimeValue::I32(v), - TypedValue::I64(v) => RuntimeValue::I64(v), - TypedValue::F32(v_bits) => RuntimeValue::F32(F32::from_bits(v_bits as u32)), - TypedValue::F64(v_bits) => RuntimeValue::F64(F64::from_bits(v_bits as u64)), - } - } -} - -/// Typed value that can be returned from a function. -/// -/// Basically a `TypedValue` plus `Unit`, for functions which return nothing. -#[derive(Clone, Copy, PartialEq, Encode, Decode)] -#[derive(crate::RuntimeDebug)] -pub enum ReturnValue { - /// For returning nothing. - Unit, - /// For returning some concrete value. - Value(TypedValue), -} - -impl From<TypedValue> for ReturnValue { - fn from(v: TypedValue) -> ReturnValue { - ReturnValue::Value(v) - } -} - -impl ReturnValue { - /// Maximum number of bytes `ReturnValue` might occupy when serialized with - /// `Codec`. - /// - /// Breakdown: - /// 1 byte for encoding unit/value variant - /// 1 byte for encoding value type - /// 8 bytes for encoding the biggest value types available in wasm: f64, i64. - pub const ENCODED_MAX_SIZE: usize = 10; -} - -#[test] -fn return_value_encoded_max_size() { - let encoded = ReturnValue::Value(TypedValue::I64(-1)).encode(); - assert_eq!(encoded.len(), ReturnValue::ENCODED_MAX_SIZE); -} - /// Describes an entity to define or import into the environment. #[derive(Clone, PartialEq, Eq, Encode, Decode)] #[derive(crate::RuntimeDebug)] diff --git a/substrate/primitives/io/Cargo.toml b/substrate/primitives/io/Cargo.toml index 7d831cdfeb9..f494697b4b5 100644 --- a/substrate/primitives/io/Cargo.toml +++ b/substrate/primitives/io/Cargo.toml @@ -12,6 +12,7 @@ sp-core = { version = "2.0.0", default-features = false, path = "../core" } sp-std = { version = "2.0.0", default-features = false, path = "../std" } libsecp256k1 = { version = "0.3.4", optional = true } sp-state-machine = { version = "0.8", optional = true, path = "../../primitives/state-machine" } +sp-wasm-interface = { version = "2.0.0", path = "../../primitives/wasm-interface", default-features = false } sp-runtime-interface = { version = "2.0.0", default-features = false, path = "../runtime-interface" } sp-trie = { version = "2.0.0", optional = true, path = "../../primitives/trie" } sp-externalities = { version = "0.8.0", optional = true, path = "../externalities" } @@ -29,6 +30,7 @@ std = [ "libsecp256k1", "sp-runtime-interface/std", "sp-externalities", + "sp-wasm-interface/std", "log", ] diff --git a/substrate/primitives/io/src/lib.rs b/substrate/primitives/io/src/lib.rs index 704477b1fb2..dce67133d39 100644 --- a/substrate/primitives/io/src/lib.rs +++ b/substrate/primitives/io/src/lib.rs @@ -850,6 +850,14 @@ pub trait Sandbox { fn instance_teardown(&mut self, instance_idx: u32) { self.sandbox().instance_teardown(instance_idx).expect("Failed to teardown sandbox instance") } + + /// Get the value from a global with the given `name`. The sandbox is determined by the given + /// `instance_idx`. + /// + /// Returns `Some(_)` when the requested global variable could be found. + fn get_global_val(&mut self, instance_idx: u32, name: &str) -> Option<sp_wasm_interface::Value> { + self.sandbox().get_global_val(instance_idx, name).expect("Failed to get global from sandbox") + } } /// Allocator used by Substrate when executing the Wasm runtime. diff --git a/substrate/primitives/runtime-interface/Cargo.toml b/substrate/primitives/runtime-interface/Cargo.toml index 30e9ae6bd6b..3f20de1288d 100644 --- a/substrate/primitives/runtime-interface/Cargo.toml +++ b/substrate/primitives/runtime-interface/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" license = "GPL-3.0" [dependencies] -sp-wasm-interface = { version = "2.0.0", optional = true, path = "../wasm-interface" } +sp-wasm-interface = { version = "2.0.0", path = "../wasm-interface", default-features = false } sp-std = { version = "2.0.0", default-features = false, path = "../std" } sp-runtime-interface-proc-macro = { version = "2.0.0", path = "proc-macro" } sp-externalities = { version = "0.8.0", optional = true, path = "../externalities" } @@ -25,7 +25,7 @@ trybuild = "1.0.17" [features] default = [ "std" ] std = [ - "sp-wasm-interface", + "sp-wasm-interface/std", "sp-std/std", "codec/std", "sp-externalities", diff --git a/substrate/primitives/runtime-interface/src/impls.rs b/substrate/primitives/runtime-interface/src/impls.rs index cde8e60eea3..35bd96bd05e 100644 --- a/substrate/primitives/runtime-interface/src/impls.rs +++ b/substrate/primitives/runtime-interface/src/impls.rs @@ -17,7 +17,7 @@ //! Provides implementations for the runtime interface traits. use crate::{ - RIType, Pointer, pass_by::{PassBy, Codec, Inner, PassByInner}, + RIType, Pointer, pass_by::{PassBy, Codec, Inner, PassByInner, Enum}, util::{unpack_ptr_and_len, pack_ptr_and_len}, }; #[cfg(feature = "std")] @@ -523,3 +523,11 @@ macro_rules! for_u128_i128 { for_u128_i128!(u128); for_u128_i128!(i128); + +impl PassBy for sp_wasm_interface::ValueType { + type PassBy = Enum<sp_wasm_interface::ValueType>; +} + +impl PassBy for sp_wasm_interface::Value { + type PassBy = Codec<sp_wasm_interface::Value>; +} diff --git a/substrate/primitives/runtime/Cargo.toml b/substrate/primitives/runtime/Cargo.toml index 70469754166..6f50a1a4d9a 100644 --- a/substrate/primitives/runtime/Cargo.toml +++ b/substrate/primitives/runtime/Cargo.toml @@ -7,7 +7,7 @@ license = "GPL-3.0" [dependencies] serde = { version = "1.0.101", optional = true, features = ["derive"] } -codec = { package = "parity-scale-codec", version = "1.1.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "1.1.2", default-features = false, features = ["derive"] } sp-core = { version = "2.0.0", default-features = false, path = "../core" } sp-application-crypto = { version = "2.0.0", default-features = false, path = "../application-crypto" } sp-arithmetic = { version = "2.0.0", default-features = false, path = "../arithmetic" } diff --git a/substrate/primitives/sandbox/Cargo.toml b/substrate/primitives/sandbox/Cargo.toml index 071ac2ba7ba..c942e019fe6 100755 --- a/substrate/primitives/sandbox/Cargo.toml +++ b/substrate/primitives/sandbox/Cargo.toml @@ -10,6 +10,7 @@ wasmi = { version = "0.6.2", optional = true } sp-core = { version = "2.0.0", default-features = false, path = "../core" } sp-std = { version = "2.0.0", default-features = false, path = "../std" } sp-io = { version = "2.0.0", default-features = false, path = "../io" } +sp-wasm-interface = { version = "2.0.0", default-features = false, path = "../wasm-interface" } codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false } [dev-dependencies] @@ -24,5 +25,6 @@ std = [ "sp-std/std", "codec/std", "sp-io/std", + "sp-wasm-interface/std", ] strict = [] diff --git a/substrate/primitives/sandbox/src/lib.rs b/substrate/primitives/sandbox/src/lib.rs index 2760a46b48d..17712ad3655 100755 --- a/substrate/primitives/sandbox/src/lib.rs +++ b/substrate/primitives/sandbox/src/lib.rs @@ -40,7 +40,8 @@ use sp_std::prelude::*; -pub use sp_core::sandbox::{TypedValue, ReturnValue, HostError}; +pub use sp_core::sandbox::HostError; +pub use sp_wasm_interface::{Value, ReturnValue}; mod imp { #[cfg(feature = "std")] @@ -75,7 +76,7 @@ impl From<Error> for HostError { /// supervisor in [`EnvironmentDefinitionBuilder`]. /// /// [`EnvironmentDefinitionBuilder`]: struct.EnvironmentDefinitionBuilder.html -pub type HostFuncType<T> = fn(&mut T, &[TypedValue]) -> Result<ReturnValue, HostError>; +pub type HostFuncType<T> = fn(&mut T, &[Value]) -> Result<ReturnValue, HostError>; /// Reference to a sandboxed linear memory, that /// will be used by the guest module. @@ -197,9 +198,16 @@ impl<T> Instance<T> { pub fn invoke( &mut self, name: &str, - args: &[TypedValue], + args: &[Value], state: &mut T, ) -> Result<ReturnValue, Error> { self.inner.invoke(name, args, state) } + + /// Get the value from a global with the given `name`. + /// + /// Returns `Some(_)` if the global could be found. + pub fn get_global_val(&self, name: &str) -> Option<Value> { + self.inner.get_global_val(name) + } } diff --git a/substrate/primitives/sandbox/with_std.rs b/substrate/primitives/sandbox/with_std.rs index 5f20342a5a9..cc3119003d1 100755 --- a/substrate/primitives/sandbox/with_std.rs +++ b/substrate/primitives/sandbox/with_std.rs @@ -23,7 +23,7 @@ use wasmi::{ RuntimeArgs, RuntimeValue, Signature, TableDescriptor, TableRef, Trap, TrapKind }; use wasmi::memory_units::Pages; -use super::{Error, TypedValue, ReturnValue, HostFuncType, HostError}; +use super::{Error, Value, ReturnValue, HostFuncType, HostError}; #[derive(Clone)] pub struct Memory { @@ -88,27 +88,7 @@ impl fmt::Display for DummyHostError { } } -impl wasmi::HostError for DummyHostError { -} - -fn from_runtime_value(v: RuntimeValue) -> TypedValue { - match v { - RuntimeValue::I32(v) => TypedValue::I32(v), - RuntimeValue::I64(v) => TypedValue::I64(v), - RuntimeValue::F32(v) => TypedValue::F32(v.to_bits() as i32), - RuntimeValue::F64(v) => TypedValue::F64(v.to_bits() as i64), - } -} - -fn to_runtime_value(v: TypedValue) -> RuntimeValue { - use wasmi::nan_preserving_float::{F32, F64}; - match v { - TypedValue::I32(v) => RuntimeValue::I32(v as i32), - TypedValue::I64(v) => RuntimeValue::I64(v as i64), - TypedValue::F32(v_bits) => RuntimeValue::F32(F32::from_bits(v_bits as u32)), - TypedValue::F64(v_bits) => RuntimeValue::F64(F64::from_bits(v_bits as u64)), - } -} +impl wasmi::HostError for DummyHostError {} struct GuestExternals<'a, T: 'a> { state: &'a mut T, @@ -124,13 +104,13 @@ impl<'a, T> Externals for GuestExternals<'a, T> { let args = args.as_ref() .iter() .cloned() - .map(from_runtime_value) + .map(Into::into) .collect::<Vec<_>>(); let result = (self.defined_host_functions.funcs[index])(self.state, &args); match result { Ok(value) => Ok(match value { - ReturnValue::Value(v) => Some(to_runtime_value(v)), + ReturnValue::Value(v) => Some(v.into()), ReturnValue::Unit => None, }), Err(HostError) => Err(TrapKind::Host(Box::new(DummyHostError)).into()), @@ -253,7 +233,7 @@ impl<T> ImportResolver for EnvironmentDefinitionBuilder<T> { pub struct Instance<T> { instance: ModuleRef, defined_host_functions: DefinedHostFunctions<T>, - _marker: ::std::marker::PhantomData<T>, + _marker: std::marker::PhantomData<T>, } impl<T> Instance<T> { @@ -281,14 +261,14 @@ impl<T> Instance<T> { Ok(Instance { instance, defined_host_functions, - _marker: ::std::marker::PhantomData::<T>, + _marker: std::marker::PhantomData::<T>, }) } pub fn invoke( &mut self, name: &str, - args: &[TypedValue], + args: &[Value], state: &mut T, ) -> Result<ReturnValue, Error> { let args = args.iter().cloned().map(Into::into).collect::<Vec<_>>(); @@ -306,20 +286,29 @@ impl<T> Instance<T> { Err(_err) => Err(Error::Execution), } } + + pub fn get_global_val(&self, name: &str) -> Option<Value> { + let global = self.instance + .export_by_name(name)? + .as_global()? + .get(); + + Some(global.into()) + } } #[cfg(test)] mod tests { use wabt; - use crate::{Error, TypedValue, ReturnValue, HostError, EnvironmentDefinitionBuilder, Instance}; + use crate::{Error, Value, ReturnValue, HostError, EnvironmentDefinitionBuilder, Instance}; use assert_matches::assert_matches; - fn execute_sandboxed(code: &[u8], args: &[TypedValue]) -> Result<ReturnValue, HostError> { + fn execute_sandboxed(code: &[u8], args: &[Value]) -> Result<ReturnValue, HostError> { struct State { counter: u32, } - fn env_assert(_e: &mut State, args: &[TypedValue]) -> Result<ReturnValue, HostError> { + fn env_assert(_e: &mut State, args: &[Value]) -> Result<ReturnValue, HostError> { if args.len() != 1 { return Err(HostError); } @@ -330,16 +319,16 @@ mod tests { Err(HostError) } } - fn env_inc_counter(e: &mut State, args: &[TypedValue]) -> Result<ReturnValue, HostError> { + fn env_inc_counter(e: &mut State, args: &[Value]) -> Result<ReturnValue, HostError> { if args.len() != 1 { return Err(HostError); } let inc_by = args[0].as_i32().ok_or_else(|| HostError)?; e.counter += inc_by as u32; - Ok(ReturnValue::Value(TypedValue::I32(e.counter as i32))) + Ok(ReturnValue::Value(Value::I32(e.counter as i32))) } /// Function that takes one argument of any type and returns that value. - fn env_polymorphic_id(_e: &mut State, args: &[TypedValue]) -> Result<ReturnValue, HostError> { + fn env_polymorphic_id(_e: &mut State, args: &[Value]) -> Result<ReturnValue, HostError> { if args.len() != 1 { return Err(HostError); } @@ -387,8 +376,8 @@ mod tests { let result = execute_sandboxed( &code, &[ - TypedValue::I32(0x12345678), - TypedValue::I64(0x1234567887654321), + Value::I32(0x12345678), + Value::I64(0x1234567887654321), ] ); assert!(result.is_ok()); @@ -410,10 +399,10 @@ mod tests { let return_val = execute_sandboxed( &code, &[ - TypedValue::I32(0x1336), + Value::I32(0x1336), ] ).unwrap(); - assert_eq!(return_val, ReturnValue::Value(TypedValue::I32(0x1337))); + assert_eq!(return_val, ReturnValue::Value(Value::I32(0x1337))); } #[test] @@ -453,8 +442,8 @@ mod tests { #[test] fn cant_return_unmatching_type() { - fn env_returns_i32(_e: &mut (), _args: &[TypedValue]) -> Result<ReturnValue, HostError> { - Ok(ReturnValue::Value(TypedValue::I32(42))) + fn env_returns_i32(_e: &mut (), _args: &[Value]) -> Result<ReturnValue, HostError> { + Ok(ReturnValue::Value(Value::I32(42))) } let mut env_builder = EnvironmentDefinitionBuilder::new(); diff --git a/substrate/primitives/sandbox/without_std.rs b/substrate/primitives/sandbox/without_std.rs index 901354aee34..c31c6fe3494 100755 --- a/substrate/primitives/sandbox/without_std.rs +++ b/substrate/primitives/sandbox/without_std.rs @@ -18,7 +18,7 @@ use codec::{Decode, Encode}; use sp_core::sandbox as sandbox_primitives; use sp_io::sandbox; use sp_std::{prelude::*, slice, marker, mem, vec, rc::Rc}; -use super::{Error, TypedValue, ReturnValue, HostFuncType}; +use super::{Error, Value, ReturnValue, HostFuncType}; mod ffi { use sp_std::mem; @@ -183,7 +183,7 @@ extern "C" fn dispatch_thunk<T>( slice::from_raw_parts(serialized_args_ptr, serialized_args_len) } }; - let args = Vec::<TypedValue>::decode(&mut &serialized_args[..]).expect( + let args = Vec::<Value>::decode(&mut &serialized_args[..]).expect( "serialized args should be provided by the runtime; correctly serialized data should be deserializable; qed", @@ -244,11 +244,11 @@ impl<T> Instance<T> { pub fn invoke( &mut self, name: &str, - args: &[TypedValue], + args: &[Value], state: &mut T, ) -> Result<ReturnValue, Error> { let serialized_args = args.to_vec().encode(); - let mut return_val = vec![0u8; sandbox_primitives::ReturnValue::ENCODED_MAX_SIZE]; + let mut return_val = vec![0u8; ReturnValue::ENCODED_MAX_SIZE]; let result = sandbox::invoke( self.instance_idx, @@ -261,7 +261,7 @@ impl<T> Instance<T> { match result { sandbox_primitives::ERR_OK => { - let return_val = sandbox_primitives::ReturnValue::decode(&mut &return_val[..]) + let return_val = ReturnValue::decode(&mut &return_val[..]) .map_err(|_| Error::Execution)?; Ok(return_val) } @@ -269,6 +269,10 @@ impl<T> Instance<T> { _ => unreachable!(), } } + + pub fn get_global_val(&self, name: &str) -> Option<Value> { + sandbox::get_global_val(self.instance_idx, name) + } } impl<T> Drop for Instance<T> { diff --git a/substrate/primitives/wasm-interface/Cargo.toml b/substrate/primitives/wasm-interface/Cargo.toml index b97e3040aa4..bb9ea85872a 100644 --- a/substrate/primitives/wasm-interface/Cargo.toml +++ b/substrate/primitives/wasm-interface/Cargo.toml @@ -9,7 +9,8 @@ license = "GPL-3.0" wasmi = { version = "0.6.2", optional = true } impl-trait-for-tuples = "0.1.2" sp-std = { version = "2.0.0", path = "../std", default-features = false } +codec = { package = "parity-scale-codec", version = "1.1.2", default-features = false, features = ["derive"] } [features] default = [ "std" ] -std = [ "wasmi", "sp-std/std" ] +std = [ "wasmi", "sp-std/std", "codec/std" ] diff --git a/substrate/primitives/wasm-interface/src/lib.rs b/substrate/primitives/wasm-interface/src/lib.rs index d6f9bbfbf09..7bb4469c771 100644 --- a/substrate/primitives/wasm-interface/src/lib.rs +++ b/substrate/primitives/wasm-interface/src/lib.rs @@ -44,8 +44,33 @@ pub enum ValueType { F64, } +impl From<ValueType> for u8 { + fn from(val: ValueType) -> u8 { + match val { + ValueType::I32 => 0, + ValueType::I64 => 1, + ValueType::F32 => 2, + ValueType::F64 => 3, + } + } +} + +impl sp_std::convert::TryFrom<u8> for ValueType { + type Error = (); + + fn try_from(val: u8) -> sp_std::result::Result<ValueType, ()> { + match val { + 0 => Ok(Self::I32), + 1 => Ok(Self::I64), + 2 => Ok(Self::F32), + 3 => Ok(Self::F64), + _ => Err(()), + } + } +} + /// Values supported by Substrate on the boundary between host/Wasm. -#[derive(PartialEq, Debug, Clone, Copy)] +#[derive(PartialEq, Debug, Clone, Copy, codec::Encode, codec::Decode)] pub enum Value { /// A 32-bit integer. I32(i32), @@ -71,6 +96,14 @@ impl Value { Value::F64(_) => ValueType::F64, } } + + /// Return `Self` as `i32`. + pub fn as_i32(&self) -> Option<i32> { + match self { + Self::I32(val) => Some(*val), + _ => None, + } + } } /// Provides `Sealed` trait to prevent implementing trait `PointerType` outside of this crate. @@ -298,6 +331,12 @@ pub trait Sandbox { raw_env_def: &[u8], state: u32, ) -> Result<u32>; + + /// Get the value from a global with the given `name`. The sandbox is determined by the + /// given `instance_idx` instance. + /// + /// Returns `Some(_)` when the requested global variable could be found. + fn get_global_val(&self, instance_idx: u32, name: &str) -> Result<Option<Value>>; } /// Something that provides implementations for host functions. @@ -409,9 +448,37 @@ impl ReadPrimitive<u64> for &mut dyn FunctionContext { } } +/// Typed value that can be returned from a function. +/// +/// Basically a `TypedValue` plus `Unit`, for functions which return nothing. +#[derive(Clone, Copy, PartialEq, codec::Encode, codec::Decode, Debug)] +pub enum ReturnValue { + /// For returning nothing. + Unit, + /// For returning some concrete value. + Value(Value), +} + +impl From<Value> for ReturnValue { + fn from(v: Value) -> ReturnValue { + ReturnValue::Value(v) + } +} + +impl ReturnValue { + /// Maximum number of bytes `ReturnValue` might occupy when serialized with `SCALE`. + /// + /// Breakdown: + /// 1 byte for encoding unit/value variant + /// 1 byte for encoding value type + /// 8 bytes for encoding the biggest value types available in wasm: f64, i64. + pub const ENCODED_MAX_SIZE: usize = 10; +} + #[cfg(test)] mod tests { use super::*; + use codec::Encode; #[test] fn pointer_offset_works() { @@ -425,4 +492,11 @@ mod tests { assert_eq!(ptr.offset(10).unwrap(), Pointer::new(80)); assert_eq!(ptr.offset(32).unwrap(), Pointer::new(256)); } + + + #[test] + fn return_value_encoded_max_size() { + let encoded = ReturnValue::Value(Value::I64(-1)).encode(); + assert_eq!(encoded.len(), ReturnValue::ENCODED_MAX_SIZE); + } } -- GitLab