mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 22:11:06 +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
Generated
+60
-10
@@ -3099,6 +3099,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap-nostd"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590"
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
@@ -5317,10 +5323,10 @@ dependencies = [
|
||||
"sp-io",
|
||||
"sp-keystore",
|
||||
"sp-runtime",
|
||||
"sp-sandbox",
|
||||
"sp-std",
|
||||
"wasm-instrument",
|
||||
"wasmi-validation",
|
||||
"wasmi 0.20.0",
|
||||
"wasmparser-nostd",
|
||||
"wat",
|
||||
]
|
||||
|
||||
@@ -7320,7 +7326,7 @@ dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"spin",
|
||||
"spin 0.5.2",
|
||||
"untrusted",
|
||||
"web-sys",
|
||||
"winapi",
|
||||
@@ -8008,7 +8014,7 @@ dependencies = [
|
||||
"tempfile",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"wasmi",
|
||||
"wasmi 0.13.0",
|
||||
"wat",
|
||||
]
|
||||
|
||||
@@ -8025,7 +8031,7 @@ dependencies = [
|
||||
"thiserror",
|
||||
"wasm-instrument",
|
||||
"wasmer",
|
||||
"wasmi",
|
||||
"wasmi 0.13.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8039,7 +8045,7 @@ dependencies = [
|
||||
"sp-runtime-interface",
|
||||
"sp-sandbox",
|
||||
"sp-wasm-interface",
|
||||
"wasmi",
|
||||
"wasmi 0.13.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9564,7 +9570,7 @@ dependencies = [
|
||||
"substrate-bip39",
|
||||
"thiserror",
|
||||
"tiny-bip39",
|
||||
"wasmi",
|
||||
"wasmi 0.13.0",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -9895,7 +9901,7 @@ dependencies = [
|
||||
"sp-io",
|
||||
"sp-std",
|
||||
"sp-wasm-interface",
|
||||
"wasmi",
|
||||
"wasmi 0.13.0",
|
||||
"wat",
|
||||
]
|
||||
|
||||
@@ -10095,7 +10101,7 @@ dependencies = [
|
||||
"log",
|
||||
"parity-scale-codec",
|
||||
"sp-std",
|
||||
"wasmi",
|
||||
"wasmi 0.13.0",
|
||||
"wasmtime",
|
||||
]
|
||||
|
||||
@@ -10120,6 +10126,12 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09"
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.6.0"
|
||||
@@ -11624,7 +11636,19 @@ checksum = "fc13b3c219ca9aafeec59150d80d89851df02e0061bc357b4d66fc55a8d38787"
|
||||
dependencies = [
|
||||
"parity-wasm",
|
||||
"wasmi-validation",
|
||||
"wasmi_core",
|
||||
"wasmi_core 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmi"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01bf50edb2ea9d922aa75a7bf3c15e26a6c9e2d18c56e862b49737a582901729"
|
||||
dependencies = [
|
||||
"spin 0.9.4",
|
||||
"wasmi_arena",
|
||||
"wasmi_core 0.5.0",
|
||||
"wasmparser-nostd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11636,6 +11660,12 @@ dependencies = [
|
||||
"parity-wasm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmi_arena"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1ea379cbb0b41f3a9f0bf7b47036d036aae7f43383d8cc487d4deccf40dee0a"
|
||||
|
||||
[[package]]
|
||||
name = "wasmi_core"
|
||||
version = "0.2.0"
|
||||
@@ -11649,6 +11679,17 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmi_core"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5bf998ab792be85e20e771fe14182b4295571ad1d4f89d3da521c1bef5f597a"
|
||||
dependencies = [
|
||||
"downcast-rs",
|
||||
"libm",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.78.2"
|
||||
@@ -11664,6 +11705,15 @@ dependencies = [
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser-nostd"
|
||||
version = "0.91.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c37f310b5a62bfd5ae7c0f1d8e6f98af16a5d6d84ba764e9c36439ec14e318b"
|
||||
dependencies = [
|
||||
"indexmap-nostd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime"
|
||||
version = "1.0.0"
|
||||
|
||||
@@ -25,7 +25,8 @@ serde = { version = "1", optional = true, features = ["derive"] }
|
||||
smallvec = { version = "1", default-features = false, features = [
|
||||
"const_generics",
|
||||
] }
|
||||
wasmi-validation = { version = "0.5", default-features = false }
|
||||
wasmi = { version = "0.20", default-features = false }
|
||||
wasmparser = { package = "wasmparser-nostd", version = "0.91", default-features = false }
|
||||
impl-trait-for-tuples = "0.2"
|
||||
|
||||
# Only used in benchmarking to generate random contract code
|
||||
@@ -42,7 +43,6 @@ sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primit
|
||||
sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" }
|
||||
sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" }
|
||||
sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" }
|
||||
sp-sandbox = { version = "0.10.0-dev", default-features = false, path = "../../primitives/sandbox" }
|
||||
sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" }
|
||||
|
||||
[dev-dependencies]
|
||||
@@ -69,16 +69,16 @@ std = [
|
||||
"sp-runtime/std",
|
||||
"sp-io/std",
|
||||
"sp-std/std",
|
||||
"sp-sandbox/std",
|
||||
"frame-benchmarking?/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"wasm-instrument/std",
|
||||
"wasmi-validation/std",
|
||||
"wasmi/std",
|
||||
"pallet-contracts-primitives/std",
|
||||
"pallet-contracts-proc-macro/full",
|
||||
"log/std",
|
||||
"rand/std",
|
||||
"wasmparser/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"frame-benchmarking/runtime-benchmarks",
|
||||
|
||||
+1
-3
@@ -3,8 +3,7 @@
|
||||
(import "seal0" "seal_deposit_event" (func $seal_deposit_event (param i32 i32 i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(start $start)
|
||||
(func $start
|
||||
(func (export "deploy")
|
||||
(call $seal_deposit_event
|
||||
(i32.const 0) ;; The topics buffer
|
||||
(i32.const 0) ;; The topics buffer's length
|
||||
@@ -22,7 +21,6 @@
|
||||
(func (export "call")
|
||||
(unreachable)
|
||||
)
|
||||
(func (export "deploy"))
|
||||
|
||||
(data (i32.const 8) "\01\02\03\04")
|
||||
)
|
||||
@@ -0,0 +1,4 @@
|
||||
;; Valid module but missing the call function
|
||||
(module
|
||||
(func (export "deploy"))
|
||||
)
|
||||
@@ -1,6 +0,0 @@
|
||||
;; A valid contract which does nothing at all but imports an invalid function
|
||||
(module
|
||||
(import "invalid" "invalid_88_99" (func (param i32 i32 i32)))
|
||||
(func (export "deploy"))
|
||||
(func (export "call"))
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
;; An invalid module
|
||||
(module
|
||||
(func (export "deploy"))
|
||||
(func (export "call")
|
||||
;; imbalanced stack
|
||||
(i32.const 7)
|
||||
)
|
||||
)
|
||||
+7
-7
@@ -1,10 +1,10 @@
|
||||
;; This fixture recursively tests if reentrant_count returns correct reentrant count value when
|
||||
;; This fixture recursively tests if reentrance_count returns correct reentrant count value when
|
||||
;; using seal_call to make caller contract call to itself
|
||||
(module
|
||||
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
|
||||
(import "seal0" "seal_address" (func $seal_address (param i32 i32)))
|
||||
(import "seal1" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32) (result i32)))
|
||||
(import "__unstable__" "reentrant_count" (func $reentrant_count (result i32)))
|
||||
(import "__unstable__" "reentrance_count" (func $reentrance_count (result i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
;; [0, 32) reserved for $seal_address output
|
||||
@@ -26,7 +26,7 @@
|
||||
)
|
||||
)
|
||||
(func (export "call")
|
||||
(local $expected_reentrant_count i32)
|
||||
(local $expected_reentrance_count i32)
|
||||
(local $seal_call_exit_code i32)
|
||||
|
||||
;; reading current contract address
|
||||
@@ -36,19 +36,19 @@
|
||||
(call $seal_input (i32.const 32) (i32.const 36))
|
||||
|
||||
;; reading manually passed reentrant count
|
||||
(set_local $expected_reentrant_count (i32.load (i32.const 32)))
|
||||
(set_local $expected_reentrance_count (i32.load (i32.const 32)))
|
||||
|
||||
;; reentrance count is calculated correctly
|
||||
(call $assert
|
||||
(i32.eq (call $reentrant_count) (get_local $expected_reentrant_count))
|
||||
(i32.eq (call $reentrance_count) (get_local $expected_reentrance_count))
|
||||
)
|
||||
|
||||
;; re-enter 5 times in a row and assert that the reentrant counter works as expected
|
||||
(i32.eq (call $reentrant_count) (i32.const 5))
|
||||
(i32.eq (call $reentrance_count) (i32.const 5))
|
||||
(if
|
||||
(then) ;; recursion exit case
|
||||
(else
|
||||
;; incrementing $expected_reentrant_count passed to the contract
|
||||
;; incrementing $expected_reentrance_count passed to the contract
|
||||
(i32.store (i32.const 32) (i32.add (i32.load (i32.const 32)) (i32.const 1)))
|
||||
|
||||
;; Call to itself
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
;; This fixture recursively tests if reentrant_count returns correct reentrant count value when
|
||||
;; This fixture recursively tests if reentrance_count returns correct reentrant count value when
|
||||
;; using seal_delegate_call to make caller contract delegate call to itself
|
||||
(module
|
||||
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
|
||||
(import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32)))
|
||||
(import "seal0" "seal_delegate_call" (func $seal_delegate_call (param i32 i32 i32 i32 i32 i32) (result i32)))
|
||||
(import "__unstable__" "reentrant_count" (func $reentrant_count (result i32)))
|
||||
(import "__unstable__" "reentrance_count" (func $reentrance_count (result i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
;; [0, 32) buffer where code hash is copied
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
;; reentrance count stays 0
|
||||
(call $assert
|
||||
(i32.eq (call $reentrant_count) (i32.const 0))
|
||||
(i32.eq (call $reentrance_count) (i32.const 0))
|
||||
)
|
||||
|
||||
(i32.eq (get_local $callstack_height) (i32.const 5))
|
||||
@@ -29,29 +29,27 @@ use alloc::{
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
use proc_macro2::TokenStream;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use syn::{parse_macro_input, spanned::Spanned, Data, DeriveInput, Ident};
|
||||
use syn::{parse_macro_input, spanned::Spanned, Data, DeriveInput, FnArg, Ident};
|
||||
|
||||
/// This derives `Debug` for a struct where each field must be of some numeric type.
|
||||
/// It interprets each field as its represents some weight and formats it as times so that
|
||||
/// it is readable by humans.
|
||||
#[proc_macro_derive(WeightDebug)]
|
||||
pub fn derive_weight_debug(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
pub fn derive_weight_debug(input: TokenStream) -> TokenStream {
|
||||
derive_debug(input, format_weight)
|
||||
}
|
||||
|
||||
/// This is basically identical to the std libs Debug derive but without adding any
|
||||
/// bounds to existing generics.
|
||||
#[proc_macro_derive(ScheduleDebug)]
|
||||
pub fn derive_schedule_debug(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
pub fn derive_schedule_debug(input: TokenStream) -> TokenStream {
|
||||
derive_debug(input, format_default)
|
||||
}
|
||||
|
||||
fn derive_debug(
|
||||
input: proc_macro::TokenStream,
|
||||
fmt: impl Fn(&Ident) -> TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
fn derive_debug(input: TokenStream, fmt: impl Fn(&Ident) -> TokenStream2) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let name = &input.ident;
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
@@ -72,7 +70,7 @@ fn derive_debug(
|
||||
let fields = {
|
||||
drop(fmt);
|
||||
drop(data);
|
||||
TokenStream::new()
|
||||
TokenStream2::new()
|
||||
};
|
||||
|
||||
let tokens = quote! {
|
||||
@@ -91,7 +89,7 @@ fn derive_debug(
|
||||
|
||||
/// This is only used then the `full` feature is activated.
|
||||
#[cfg(feature = "full")]
|
||||
fn iterate_fields(data: &syn::DataStruct, fmt: impl Fn(&Ident) -> TokenStream) -> TokenStream {
|
||||
fn iterate_fields(data: &syn::DataStruct, fmt: impl Fn(&Ident) -> TokenStream2) -> TokenStream2 {
|
||||
use syn::Fields;
|
||||
|
||||
match &data.fields {
|
||||
@@ -119,7 +117,7 @@ fn iterate_fields(data: &syn::DataStruct, fmt: impl Fn(&Ident) -> TokenStream) -
|
||||
}
|
||||
}
|
||||
|
||||
fn format_weight(field: &Ident) -> TokenStream {
|
||||
fn format_weight(field: &Ident) -> TokenStream2 {
|
||||
quote_spanned! { field.span() =>
|
||||
&if self.#field > 1_000_000_000 {
|
||||
format!(
|
||||
@@ -142,7 +140,7 @@ fn format_weight(field: &Ident) -> TokenStream {
|
||||
}
|
||||
}
|
||||
|
||||
fn format_default(field: &Ident) -> TokenStream {
|
||||
fn format_default(field: &Ident) -> TokenStream2 {
|
||||
quote_spanned! { field.span() =>
|
||||
&self.#field
|
||||
}
|
||||
@@ -167,8 +165,20 @@ enum HostFnReturn {
|
||||
ReturnCode,
|
||||
}
|
||||
|
||||
impl HostFnReturn {
|
||||
fn to_wasm_sig(&self) -> TokenStream2 {
|
||||
let ok = match self {
|
||||
Self::Unit => quote! { () },
|
||||
Self::U32 | Self::ReturnCode => quote! { ::core::primitive::u32 },
|
||||
};
|
||||
quote! {
|
||||
::core::result::Result<#ok, ::wasmi::core::Trap>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for HostFn {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
self.item.to_tokens(tokens);
|
||||
}
|
||||
}
|
||||
@@ -179,6 +189,8 @@ impl HostFn {
|
||||
let msg = format!("Invalid host function definition. {}", msg);
|
||||
syn::Error::new(span, msg)
|
||||
};
|
||||
|
||||
// process attributes
|
||||
let msg = "only #[version(<u8>)] or #[unstable] attribute is allowed.";
|
||||
let span = item.span();
|
||||
let mut attrs = item.attrs.clone();
|
||||
@@ -201,16 +213,31 @@ impl HostFn {
|
||||
_ => Err(err(span, msg)),
|
||||
}?;
|
||||
|
||||
// process arguments: The first and second arg are treated differently (ctx, memory)
|
||||
// they must exist and be `ctx: _` and `memory: _`.
|
||||
let msg = "Every function must start with two inferred parameters: ctx: _ and memory: _";
|
||||
let special_args = item
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.take(2)
|
||||
.enumerate()
|
||||
.map(|(i, arg)| is_valid_special_arg(i, arg))
|
||||
.fold(0u32, |acc, valid| if valid { acc + 1 } else { acc });
|
||||
|
||||
if special_args != 2 {
|
||||
return Err(err(span, msg))
|
||||
}
|
||||
|
||||
// process return type
|
||||
let msg = r#"Should return one of the following:
|
||||
- Result<(), TrapReason>,
|
||||
- Result<ReturnCode, TrapReason>,
|
||||
- Result<u32, TrapReason>"#;
|
||||
|
||||
let ret_ty = match item.clone().sig.output {
|
||||
syn::ReturnType::Type(_, ty) => Ok(ty.clone()),
|
||||
_ => Err(err(span, &msg)),
|
||||
}?;
|
||||
|
||||
match *ret_ty {
|
||||
syn::Type::Path(tp) => {
|
||||
let result = &tp.path.segments.last().ok_or(err(span, &msg))?;
|
||||
@@ -265,13 +292,13 @@ impl HostFn {
|
||||
},
|
||||
_ => Err(err(ok_ty.span(), &msg)),
|
||||
}?;
|
||||
|
||||
let returns = match ok_ty_str.as_str() {
|
||||
"()" => Ok(HostFnReturn::Unit),
|
||||
"u32" => Ok(HostFnReturn::U32),
|
||||
"ReturnCode" => Ok(HostFnReturn::ReturnCode),
|
||||
_ => Err(err(arg1.span(), &msg)),
|
||||
}?;
|
||||
|
||||
Ok(Self { item, module, name, returns })
|
||||
},
|
||||
_ => Err(err(span, &msg)),
|
||||
@@ -280,25 +307,6 @@ impl HostFn {
|
||||
_ => Err(err(span, &msg)),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_wasm_sig(&self) -> TokenStream {
|
||||
let args = self.item.sig.inputs.iter().skip(1).filter_map(|a| match a {
|
||||
syn::FnArg::Typed(pt) => Some(&pt.ty),
|
||||
_ => None,
|
||||
});
|
||||
let returns = match &self.returns {
|
||||
HostFnReturn::U32 => quote! { vec![ <u32>::VALUE_TYPE ] },
|
||||
HostFnReturn::ReturnCode => quote! { vec![ <ReturnCode>::VALUE_TYPE ] },
|
||||
HostFnReturn::Unit => quote! { vec![] },
|
||||
};
|
||||
|
||||
quote! {
|
||||
wasm_instrument::parity_wasm::elements::FunctionType::new(
|
||||
vec! [ #(<#args>::VALUE_TYPE),* ],
|
||||
#returns,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EnvDef {
|
||||
@@ -343,149 +351,135 @@ impl EnvDef {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid_special_arg(idx: usize, arg: &FnArg) -> bool {
|
||||
let pat = if let FnArg::Typed(pat) = arg { pat } else { return false };
|
||||
let ident = if let syn::Pat::Ident(ref ident) = *pat.pat { &ident.ident } else { return false };
|
||||
let name_ok = match idx {
|
||||
0 => ident == "ctx" || ident == "_ctx",
|
||||
1 => ident == "memory" || ident == "_memory",
|
||||
_ => false,
|
||||
};
|
||||
if !name_ok {
|
||||
return false
|
||||
}
|
||||
matches!(*pat.ty, syn::Type::Infer(_))
|
||||
}
|
||||
|
||||
/// Expands environment definiton.
|
||||
/// Should generate source code for:
|
||||
/// - wasm import satisfy checks (see `expand_can_satisfy()`);
|
||||
/// - implementations of the host functions to be added to the wasm runtime environment (see
|
||||
/// `expand_impls()`).
|
||||
fn expand_env(def: &mut EnvDef) -> proc_macro2::TokenStream {
|
||||
let can_satisfy = expand_can_satisfy(def);
|
||||
fn expand_env(def: &mut EnvDef) -> TokenStream2 {
|
||||
let impls = expand_impls(def);
|
||||
|
||||
quote! {
|
||||
pub struct Env;
|
||||
#can_satisfy
|
||||
#impls
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates `can_satisfy()` method for every host function, to be used to check
|
||||
/// these functions versus expected module, name and signatures when imporing them from a wasm
|
||||
/// module.
|
||||
fn expand_can_satisfy(def: &mut EnvDef) -> proc_macro2::TokenStream {
|
||||
let checks = def.host_funcs.iter().map(|f| {
|
||||
let (module, name, signature) = (&f.module, &f.name, &f.to_wasm_sig());
|
||||
quote! {
|
||||
if module == #module.as_bytes()
|
||||
&& name == #name.as_bytes()
|
||||
&& signature == &#signature
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
let satisfy_checks = quote! {
|
||||
#( #checks )*
|
||||
};
|
||||
/// Generates for every host function:
|
||||
/// - real implementation, to register it in the contract execution environment;
|
||||
/// - dummy implementation, to be used as mocks for contract validation step.
|
||||
fn expand_impls(def: &mut EnvDef) -> TokenStream2 {
|
||||
let impls = expand_functions(def, true, quote! { crate::wasm::Runtime<E> });
|
||||
let dummy_impls = expand_functions(def, false, quote! { () });
|
||||
|
||||
quote! {
|
||||
impl crate::wasm::env_def::ImportSatisfyCheck for Env {
|
||||
fn can_satisfy(
|
||||
module: &[u8],
|
||||
name: &[u8],
|
||||
signature: &wasm_instrument::parity_wasm::elements::FunctionType,
|
||||
) -> bool {
|
||||
use crate::wasm::env_def::ConvertibleToWasm;
|
||||
#[cfg(not(feature = "unstable-interface"))]
|
||||
if module == b"__unstable__" {
|
||||
return false;
|
||||
}
|
||||
#satisfy_checks
|
||||
return false;
|
||||
}
|
||||
impl<'a, E> crate::wasm::Environment<crate::wasm::runtime::Runtime<'a, E>> for Env
|
||||
where
|
||||
E: Ext,
|
||||
<E::T as ::frame_system::Config>::AccountId:
|
||||
::sp_core::crypto::UncheckedFrom<<E::T as ::frame_system::Config>::Hash> + ::core::convert::AsRef<[::core::primitive::u8]>,
|
||||
{
|
||||
fn define(store: &mut ::wasmi::Store<crate::wasm::Runtime<E>>, linker: &mut ::wasmi::Linker<crate::wasm::Runtime<E>>) -> Result<(), ::wasmi::errors::LinkerError> {
|
||||
#impls
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::wasm::Environment<()> for Env
|
||||
{
|
||||
fn define(store: &mut ::wasmi::Store<()>, linker: &mut ::wasmi::Linker<()>) -> Result<(), ::wasmi::errors::LinkerError> {
|
||||
#dummy_impls
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates implementation for every host function, to register it in the contract execution
|
||||
/// environment.
|
||||
fn expand_impls(def: &mut EnvDef) -> proc_macro2::TokenStream {
|
||||
let impls = def.host_funcs.iter().map(|f| {
|
||||
let params = &f.item.sig.inputs.iter().skip(1).map(|arg| {
|
||||
match arg {
|
||||
syn::FnArg::Typed(pt) => {
|
||||
if let syn::Pat::Ident(ident) = &*pt.pat {
|
||||
let p_type = &pt.ty;
|
||||
let p_name = ident.ident.clone();
|
||||
quote! {
|
||||
let #p_name : <#p_type as crate::wasm::env_def::ConvertibleToWasm>::NativeType =
|
||||
args.next()
|
||||
.and_then(|v| <#p_type as crate::wasm::env_def::ConvertibleToWasm>::from_typed_value(v.clone()))
|
||||
.expect(
|
||||
"precondition: all imports should be checked against the signatures of corresponding
|
||||
functions defined by `#[define_env]` proc macro by the user of the macro;
|
||||
thus this can never be `None`;
|
||||
qed;"
|
||||
);
|
||||
}
|
||||
} else { quote! { } }
|
||||
},
|
||||
_ => quote! { },
|
||||
}
|
||||
});
|
||||
fn expand_functions(
|
||||
def: &mut EnvDef,
|
||||
expand_blocks: bool,
|
||||
host_state: TokenStream2,
|
||||
) -> TokenStream2 {
|
||||
let impls = def.host_funcs.iter().map(|f| {
|
||||
// skip the context and memory argument
|
||||
let params = f.item.sig.inputs.iter().skip(2);
|
||||
let (module, name, body, wasm_output, output) = (
|
||||
&f.module,
|
||||
&f.name,
|
||||
&f.item.block,
|
||||
f.returns.to_wasm_sig(),
|
||||
&f.item.sig.output
|
||||
);
|
||||
let unstable_feat = match module.as_str() {
|
||||
"__unstable__" => quote! { #[cfg(feature = "unstable-interface")] },
|
||||
_ => quote! {},
|
||||
};
|
||||
|
||||
let outline = match &f.returns {
|
||||
HostFnReturn::Unit => quote! {
|
||||
body().map_err(|reason| {
|
||||
ctx.set_trap_reason(reason);
|
||||
sp_sandbox::HostError
|
||||
})?;
|
||||
return Ok(sp_sandbox::ReturnValue::Unit);
|
||||
},
|
||||
_ => quote! {
|
||||
let r = body().map_err(|reason| {
|
||||
ctx.set_trap_reason(reason);
|
||||
sp_sandbox::HostError
|
||||
})?;
|
||||
return Ok(sp_sandbox::ReturnValue::Value({
|
||||
r.to_typed_value()
|
||||
}));
|
||||
},
|
||||
};
|
||||
let params = params.clone();
|
||||
let (module, name, ident, body) = (&f.module, &f.name, &f.item.sig.ident, &f.item.block);
|
||||
let unstable_feat = match module.as_str() {
|
||||
"__unstable__" => quote! { #[cfg(feature = "unstable-interface")] },
|
||||
_ => quote! { },
|
||||
};
|
||||
quote! {
|
||||
#unstable_feat
|
||||
f(#module.as_bytes(), #name.as_bytes(), {
|
||||
fn #ident<E: Ext>(
|
||||
ctx: &mut crate::wasm::Runtime<E>,
|
||||
args: &[sp_sandbox::Value],
|
||||
) -> Result<sp_sandbox::ReturnValue, sp_sandbox::HostError>
|
||||
where
|
||||
<E::T as frame_system::Config>::AccountId: sp_core::crypto::UncheckedFrom<<E::T as frame_system::Config>::Hash>
|
||||
+ AsRef<[u8]>,
|
||||
{
|
||||
#[allow(unused)]
|
||||
let mut args = args.iter();
|
||||
let mut body = || {
|
||||
#( #params )*
|
||||
#body
|
||||
};
|
||||
#outline
|
||||
// If we don't expand blocks (implementing for `()`) we change a few things:
|
||||
// - We replace any code by unreachable!
|
||||
// - Allow unused variables as the code that uses is not expanded
|
||||
// - We don't need to map the error as we simply panic if they code would ever be executed
|
||||
let inner = if expand_blocks {
|
||||
quote! { || #output {
|
||||
let (memory, ctx) = __caller__
|
||||
.host_data()
|
||||
.memory()
|
||||
.expect("Memory must be set when setting up host data; qed")
|
||||
.data_and_store_mut(&mut __caller__);
|
||||
#body
|
||||
} }
|
||||
} else {
|
||||
quote! { || -> #wasm_output {
|
||||
// This is part of the implementation for `Environment<()>` which is not
|
||||
// meant to be actually executed. It is only for validation which will
|
||||
// never call host functions.
|
||||
::core::unreachable!()
|
||||
} }
|
||||
};
|
||||
let map_err = if expand_blocks {
|
||||
quote! {
|
||||
|reason| {
|
||||
::wasmi::core::Trap::host(reason)
|
||||
}
|
||||
}
|
||||
#ident::<E>
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let packed_impls = quote! {
|
||||
#( #impls )*
|
||||
};
|
||||
|
||||
quote! {
|
||||
impl<E: Ext> crate::wasm::env_def::FunctionImplProvider<E> for Env
|
||||
where
|
||||
<E::T as frame_system::Config>::AccountId:
|
||||
sp_core::crypto::UncheckedFrom<<E::T as frame_system::Config>::Hash> + AsRef<[u8]>,
|
||||
{
|
||||
fn impls<F: FnMut(&[u8], &[u8], crate::wasm::env_def::HostFunc<E>)>(f: &mut F) {
|
||||
#packed_impls
|
||||
} else {
|
||||
quote! {
|
||||
|reason| { reason }
|
||||
}
|
||||
};
|
||||
let allow_unused = if expand_blocks {
|
||||
quote! { }
|
||||
} else {
|
||||
quote! { #[allow(unused_variables)] }
|
||||
};
|
||||
|
||||
|
||||
quote! {
|
||||
#unstable_feat
|
||||
#allow_unused
|
||||
linker.define(#module, #name, ::wasmi::Func::wrap(&mut*store, |mut __caller__: ::wasmi::Caller<#host_state>, #( #params, )*| -> #wasm_output {
|
||||
let mut func = #inner;
|
||||
func()
|
||||
.map_err(#map_err)
|
||||
.map(::core::convert::Into::into)
|
||||
}))?;
|
||||
}
|
||||
});
|
||||
quote! {
|
||||
#( #impls )*
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,7 +496,7 @@ fn expand_impls(def: &mut EnvDef) -> proc_macro2::TokenStream {
|
||||
/// ```nocompile
|
||||
/// #[define_env]
|
||||
/// pub mod some_env {
|
||||
/// fn some_host_fn(ctx: Runtime<E>, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<(), TrapReason> {
|
||||
/// fn some_host_fn(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<(), TrapReason> {
|
||||
/// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ())
|
||||
/// }
|
||||
/// }
|
||||
@@ -517,12 +511,12 @@ fn expand_impls(def: &mut EnvDef) -> proc_macro2::TokenStream {
|
||||
/// #[define_env]
|
||||
/// pub mod some_env {
|
||||
/// #[version(1)]
|
||||
/// fn some_host_fn(ctx: Runtime<E>, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<ReturnCode, TrapReason> {
|
||||
/// fn some_host_fn(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<ReturnCode, TrapReason> {
|
||||
/// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ())
|
||||
/// }
|
||||
///
|
||||
/// #[unstable]
|
||||
/// fn some_host_fn(ctx: Runtime<E>, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<u32, TrapReason> {
|
||||
/// fn some_host_fn(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<u32, TrapReason> {
|
||||
/// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ())
|
||||
/// }
|
||||
/// }
|
||||
@@ -540,12 +534,12 @@ fn expand_impls(def: &mut EnvDef) -> proc_macro2::TokenStream {
|
||||
/// pub mod some_env {
|
||||
/// #[version(1)]
|
||||
/// #[prefixed_alias]
|
||||
/// fn some_host_fn(ctx: Runtime<E>, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<ReturnCode, TrapReason> {
|
||||
/// fn some_host_fn(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<ReturnCode, TrapReason> {
|
||||
/// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ())
|
||||
/// }
|
||||
///
|
||||
/// #[unstable]
|
||||
/// fn some_host_fn(ctx: Runtime<E>, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<u32, TrapReason> {
|
||||
/// fn some_host_fn(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<u32, TrapReason> {
|
||||
/// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ())
|
||||
/// }
|
||||
/// }
|
||||
@@ -562,16 +556,16 @@ fn expand_impls(def: &mut EnvDef) -> proc_macro2::TokenStream {
|
||||
/// - `Result<u32, TrapReason>`.
|
||||
///
|
||||
/// The macro expands to `pub struct Env` declaration, with the following traits implementations:
|
||||
/// - `pallet_contracts::wasm::env_def::ImportSatisfyCheck`
|
||||
/// - `pallet_contracts::wasm::env_def::FunctionImplProvider`
|
||||
/// - `pallet_contracts::wasm::Environment<Runtime<E>> where E: Ext`
|
||||
/// - `pallet_contracts::wasm::Environment<()>`
|
||||
///
|
||||
/// The implementation on `()` can be used in places where no `Ext` exists, yet. This is useful
|
||||
/// when only checking whether a code can be instantiated without actually executing any code.
|
||||
#[proc_macro_attribute]
|
||||
pub fn define_env(
|
||||
attr: proc_macro::TokenStream,
|
||||
item: proc_macro::TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
pub fn define_env(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
if !attr.is_empty() {
|
||||
let msg = "Invalid `define_env` attribute macro: expected no attributes: `#[define_env]`.";
|
||||
let span = proc_macro2::TokenStream::from(attr).span();
|
||||
let span = TokenStream2::from(attr).span();
|
||||
return syn::Error::new(span, msg).to_compile_error().into()
|
||||
}
|
||||
|
||||
|
||||
@@ -28,10 +28,6 @@ use crate::{Config, Determinism};
|
||||
use frame_support::traits::Get;
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
use sp_runtime::traits::Hash;
|
||||
use sp_sandbox::{
|
||||
default_executor::{EnvironmentDefinitionBuilder, Memory},
|
||||
SandboxEnvironmentBuilder, SandboxMemory,
|
||||
};
|
||||
use sp_std::{borrow::ToOwned, prelude::*};
|
||||
use wasm_instrument::parity_wasm::{
|
||||
builder,
|
||||
@@ -128,7 +124,7 @@ pub struct ImportedFunction {
|
||||
pub struct WasmModule<T: Config> {
|
||||
pub code: Vec<u8>,
|
||||
pub hash: <T::Hashing as Hash>::Output,
|
||||
memory: Option<ImportedMemory>,
|
||||
pub memory: Option<ImportedMemory>,
|
||||
}
|
||||
|
||||
impl<T: Config> From<ModuleDefinition> for WasmModule<T>
|
||||
@@ -395,16 +391,6 @@ where
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Creates a memory instance for use in a sandbox with dimensions declared in this module
|
||||
/// and adds it to `env`. A reference to that memory is returned so that it can be used to
|
||||
/// access the memory contents from the supervisor.
|
||||
pub fn add_memory<S>(&self, env: &mut EnvironmentDefinitionBuilder<S>) -> Option<Memory> {
|
||||
let memory = if let Some(memory) = &self.memory { memory } else { return None };
|
||||
let memory = Memory::new(memory.min_pages, Some(memory.max_pages)).unwrap();
|
||||
env.add_memory("env", "memory", memory.clone());
|
||||
Some(memory)
|
||||
}
|
||||
|
||||
pub fn unary_instr(instr: Instruction, repeat: u32) -> Self {
|
||||
use body::DynInstr::{RandomI64Repeated, Regular};
|
||||
ModuleDefinition {
|
||||
|
||||
@@ -425,7 +425,7 @@ benchmarks! {
|
||||
.map(|n| account::<T::AccountId>("account", n, 0))
|
||||
.collect::<Vec<_>>();
|
||||
let account_len = accounts.get(0).map(|i| i.encode().len()).unwrap_or(0);
|
||||
let accounts_bytes = accounts.iter().map(|a| a.encode()).flatten().collect::<Vec<_>>();
|
||||
let accounts_bytes = accounts.iter().flat_map(|a| a.encode()).collect::<Vec<_>>();
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
@@ -462,7 +462,7 @@ benchmarks! {
|
||||
.map(|n| account::<T::AccountId>("account", n, 0))
|
||||
.collect::<Vec<_>>();
|
||||
let account_len = accounts.get(0).map(|i| i.encode().len()).unwrap_or(0);
|
||||
let accounts_bytes = accounts.iter().map(|a| a.encode()).flatten().collect::<Vec<_>>();
|
||||
let accounts_bytes = accounts.iter().flat_map(|a| a.encode()).collect::<Vec<_>>();
|
||||
let accounts_len = accounts_bytes.len();
|
||||
let pages = code::max_pages::<T>();
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
@@ -2014,10 +2014,9 @@ benchmarks! {
|
||||
let r in 0 .. 1;
|
||||
let key_type = sp_core::crypto::KeyTypeId(*b"code");
|
||||
let pub_keys_bytes = (0..r * API_BENCHMARK_BATCH_SIZE)
|
||||
.map(|_| {
|
||||
.flat_map(|_| {
|
||||
sp_io::crypto::ecdsa_generate(key_type, None).0
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
let pub_keys_bytes_len = pub_keys_bytes.len() as i32;
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
@@ -2086,13 +2085,13 @@ benchmarks! {
|
||||
let origin = RawOrigin::Signed(instance.caller.clone());
|
||||
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
|
||||
|
||||
reentrant_count {
|
||||
seal_reentrance_count {
|
||||
let r in 0 .. API_BENCHMARK_BATCHES;
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "__unstable__",
|
||||
name: "reentrant_count",
|
||||
name: "reentrance_count",
|
||||
params: vec![],
|
||||
return_type: Some(ValueType::I32),
|
||||
}],
|
||||
@@ -2106,7 +2105,7 @@ benchmarks! {
|
||||
let origin = RawOrigin::Signed(instance.caller.clone());
|
||||
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
|
||||
|
||||
account_reentrance_count {
|
||||
seal_account_reentrance_count {
|
||||
let r in 0 .. API_BENCHMARK_BATCHES;
|
||||
let dummy_code = WasmModule::<T>::dummy_with_bytes(0);
|
||||
let accounts = (0..r * API_BENCHMARK_BATCH_SIZE)
|
||||
@@ -2921,7 +2920,7 @@ benchmarks! {
|
||||
#[extra]
|
||||
ink_erc20_transfer {
|
||||
let g in 0 .. 1;
|
||||
let gas_metering = if g == 0 { false } else { true };
|
||||
let gas_metering = g != 0;
|
||||
let code = load_benchmark!("ink_erc20");
|
||||
let data = {
|
||||
let new: ([u8; 4], BalanceOf<T>) = ([0x9b, 0xae, 0x9d, 0x5e], 1000u32.into());
|
||||
@@ -2959,7 +2958,7 @@ benchmarks! {
|
||||
#[extra]
|
||||
solang_erc20_transfer {
|
||||
let g in 0 .. 1;
|
||||
let gas_metering = if g == 0 { false } else { true };
|
||||
let gas_metering = g != 0;
|
||||
let code = include_bytes!("../../benchmarks/solang_erc20.wasm");
|
||||
let caller = account::<T::AccountId>("instantiator", 0, 0);
|
||||
let mut balance = [0u8; 32];
|
||||
|
||||
@@ -19,22 +19,20 @@
|
||||
/// ! sandbox to execute the wasm code. This is because we do not need the full
|
||||
/// ! environment that provides the seal interface as imported functions.
|
||||
use super::{code::WasmModule, Config};
|
||||
use crate::wasm::{Environment, PrefabWasmModule};
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
use sp_sandbox::{
|
||||
default_executor::{EnvironmentDefinitionBuilder, Instance, Memory},
|
||||
SandboxEnvironmentBuilder, SandboxInstance,
|
||||
};
|
||||
use wasmi::{errors::LinkerError, Func, Linker, StackLimits, Store};
|
||||
|
||||
/// Minimal execution environment without any exported functions.
|
||||
/// Minimal execution environment without any imported functions.
|
||||
pub struct Sandbox {
|
||||
instance: Instance<()>,
|
||||
_memory: Option<Memory>,
|
||||
entry_point: Func,
|
||||
store: Store<()>,
|
||||
}
|
||||
|
||||
impl Sandbox {
|
||||
/// Invoke the `call` function of a contract code and panic on any execution error.
|
||||
pub fn invoke(&mut self) {
|
||||
self.instance.invoke("call", &[], &mut ()).unwrap();
|
||||
self.entry_point.call(&mut self.store, &[], &mut []).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,10 +44,27 @@ where
|
||||
/// Creates an instance from the supplied module and supplies as much memory
|
||||
/// to the instance as the module declares as imported.
|
||||
fn from(module: &WasmModule<T>) -> Self {
|
||||
let mut env_builder = EnvironmentDefinitionBuilder::new();
|
||||
let memory = module.add_memory(&mut env_builder);
|
||||
let instance = Instance::new(&module.code, &env_builder, &mut ())
|
||||
.expect("Failed to create benchmarking Sandbox instance");
|
||||
Self { instance, _memory: memory }
|
||||
let memory = module
|
||||
.memory
|
||||
.as_ref()
|
||||
.map(|mem| (mem.min_pages, mem.max_pages))
|
||||
.unwrap_or((0, 0));
|
||||
let (store, _memory, instance) = PrefabWasmModule::<T>::instantiate::<EmptyEnv, _>(
|
||||
&module.code,
|
||||
(),
|
||||
memory,
|
||||
StackLimits::default(),
|
||||
)
|
||||
.expect("Failed to create benchmarking Sandbox instance");
|
||||
let entry_point = instance.get_export(&store, "call").unwrap().into_func().unwrap();
|
||||
Self { entry_point, store }
|
||||
}
|
||||
}
|
||||
|
||||
struct EmptyEnv;
|
||||
|
||||
impl Environment<()> for EmptyEnv {
|
||||
fn define(_store: &mut Store<()>, _linker: &mut Linker<()>) -> Result<(), LinkerError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,6 +270,7 @@ impl<'a, 'b, E: Ext> Environment<'a, 'b, E, InitState> {
|
||||
/// ever create this type. Chain extensions merely consume it.
|
||||
pub(crate) fn new(
|
||||
runtime: &'a mut Runtime<'b, E>,
|
||||
memory: &'a mut [u8],
|
||||
id: u32,
|
||||
input_ptr: u32,
|
||||
input_len: u32,
|
||||
@@ -277,7 +278,7 @@ impl<'a, 'b, E: Ext> Environment<'a, 'b, E, InitState> {
|
||||
output_len_ptr: u32,
|
||||
) -> Self {
|
||||
Environment {
|
||||
inner: Inner { runtime, id, input_ptr, input_len, output_ptr, output_len_ptr },
|
||||
inner: Inner { runtime, memory, id, input_ptr, input_len, output_ptr, output_len_ptr },
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
@@ -338,9 +339,11 @@ where
|
||||
/// charge the overall costs either using `max_len` (worst case approximation) or using
|
||||
/// [`in_len()`](Self::in_len).
|
||||
pub fn read(&self, max_len: u32) -> Result<Vec<u8>> {
|
||||
self.inner
|
||||
.runtime
|
||||
.read_sandbox_memory(self.inner.input_ptr, self.inner.input_len.min(max_len))
|
||||
self.inner.runtime.read_sandbox_memory(
|
||||
self.inner.memory,
|
||||
self.inner.input_ptr,
|
||||
self.inner.input_len.min(max_len),
|
||||
)
|
||||
}
|
||||
|
||||
/// Reads `min(buffer.len(), in_len) from contract memory.
|
||||
@@ -354,7 +357,11 @@ where
|
||||
let buffer = core::mem::take(buffer);
|
||||
&mut buffer[..len.min(self.inner.input_len as usize)]
|
||||
};
|
||||
self.inner.runtime.read_sandbox_memory_into_buf(self.inner.input_ptr, sliced)?;
|
||||
self.inner.runtime.read_sandbox_memory_into_buf(
|
||||
self.inner.memory,
|
||||
self.inner.input_ptr,
|
||||
sliced,
|
||||
)?;
|
||||
*buffer = sliced;
|
||||
Ok(())
|
||||
}
|
||||
@@ -366,14 +373,20 @@ where
|
||||
/// weight of the chain extension. This should usually be the case when fixed input types
|
||||
/// are used.
|
||||
pub fn read_as<T: Decode + MaxEncodedLen>(&mut self) -> Result<T> {
|
||||
self.inner.runtime.read_sandbox_memory_as(self.inner.input_ptr)
|
||||
self.inner
|
||||
.runtime
|
||||
.read_sandbox_memory_as(self.inner.memory, self.inner.input_ptr)
|
||||
}
|
||||
|
||||
/// Reads and decodes a type with a dynamic size from contract memory.
|
||||
///
|
||||
/// Make sure to include `len` in your weight calculations.
|
||||
pub fn read_as_unbounded<T: Decode>(&mut self, len: u32) -> Result<T> {
|
||||
self.inner.runtime.read_sandbox_memory_as_unbounded(self.inner.input_ptr, len)
|
||||
self.inner.runtime.read_sandbox_memory_as_unbounded(
|
||||
self.inner.memory,
|
||||
self.inner.input_ptr,
|
||||
len,
|
||||
)
|
||||
}
|
||||
|
||||
/// The length of the input as passed in as `input_len`.
|
||||
@@ -406,6 +419,7 @@ where
|
||||
weight_per_byte: Option<Weight>,
|
||||
) -> Result<()> {
|
||||
self.inner.runtime.write_sandbox_output(
|
||||
self.inner.memory,
|
||||
self.inner.output_ptr,
|
||||
self.inner.output_len_ptr,
|
||||
buffer,
|
||||
@@ -426,6 +440,8 @@ where
|
||||
struct Inner<'a, 'b, E: Ext> {
|
||||
/// The runtime contains all necessary functions to interact with the running contract.
|
||||
runtime: &'a mut Runtime<'b, E>,
|
||||
/// Reference to the contracts memory.
|
||||
memory: &'a mut [u8],
|
||||
/// Verbatim argument passed to `seal_call_chain_extension`.
|
||||
id: u32,
|
||||
/// Verbatim argument passed to `seal_call_chain_extension`.
|
||||
|
||||
@@ -299,7 +299,7 @@ pub trait Ext: sealing::Sealed {
|
||||
|
||||
/// Returns the number of times the currently executing contract exists on the call stack in
|
||||
/// addition to the calling instance. A value of 0 means no reentrancy.
|
||||
fn reentrant_count(&self) -> u32;
|
||||
fn reentrance_count(&self) -> u32;
|
||||
|
||||
/// Returns the number of times the specified contract exists on the call stack. Delegated calls
|
||||
/// are not calculated as separate entrance.
|
||||
@@ -1384,7 +1384,7 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reentrant_count(&self) -> u32 {
|
||||
fn reentrance_count(&self) -> u32 {
|
||||
let id: &AccountIdOf<Self::T> = &self.top_frame().account_id;
|
||||
self.account_reentrance_count(id).saturating_sub(1)
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ use crate::{
|
||||
exec::{AccountIdOf, ExecError, Executable, Stack as ExecStack},
|
||||
gas::GasMeter,
|
||||
storage::{meter::Meter as StorageMeter, ContractInfo, DeletedContract, Storage},
|
||||
wasm::{OwnerInfo, PrefabWasmModule},
|
||||
wasm::{OwnerInfo, PrefabWasmModule, TryInstantiate},
|
||||
weights::WeightInfo,
|
||||
};
|
||||
use codec::{Codec, Encode, HasCompact};
|
||||
@@ -830,8 +830,13 @@ pub mod pallet {
|
||||
/// to determine whether a reversion has taken place.
|
||||
ContractReverted,
|
||||
/// The contract's code was found to be invalid during validation or instrumentation.
|
||||
///
|
||||
/// The most likely cause of this is that an API was used which is not supported by the
|
||||
/// node. This hapens if an older node is used with a new version of ink!. Try updating
|
||||
/// your node to the newest available version.
|
||||
///
|
||||
/// A more detailed error can be found on the node console if debug messages are enabled
|
||||
/// or in the debug buffer which is returned to RPC clients.
|
||||
/// by supplying `-lruntime::contracts=debug`.
|
||||
CodeRejected,
|
||||
/// An indetermistic code was used in a context where this is not permitted.
|
||||
Indeterministic,
|
||||
@@ -1009,8 +1014,14 @@ where
|
||||
determinism: Determinism,
|
||||
) -> CodeUploadResult<CodeHash<T>, BalanceOf<T>> {
|
||||
let schedule = T::Schedule::get();
|
||||
let module = PrefabWasmModule::from_code(code, &schedule, origin, determinism)
|
||||
.map_err(|(err, _)| err)?;
|
||||
let module = PrefabWasmModule::from_code(
|
||||
code,
|
||||
&schedule,
|
||||
origin,
|
||||
determinism,
|
||||
TryInstantiate::Instantiate,
|
||||
)
|
||||
.map_err(|(err, _)| err)?;
|
||||
let deposit = module.open_deposit();
|
||||
if let Some(storage_deposit_limit) = storage_deposit_limit {
|
||||
ensure!(storage_deposit_limit >= deposit, <Error<T>>::StorageDepositLimitExhausted);
|
||||
@@ -1135,6 +1146,7 @@ where
|
||||
&schedule,
|
||||
origin.clone(),
|
||||
Determinism::Deterministic,
|
||||
TryInstantiate::Skip,
|
||||
)
|
||||
.map_err(|(err, msg)| {
|
||||
debug_message.as_mut().map(|buffer| buffer.extend(msg.as_bytes()));
|
||||
|
||||
@@ -423,8 +423,8 @@ pub struct HostFnWeights<T: Config> {
|
||||
/// Weight of calling `seal_ecdsa_to_eth_address`.
|
||||
pub ecdsa_to_eth_address: u64,
|
||||
|
||||
/// Weight of calling `seal_reentrant_count`.
|
||||
pub reentrant_count: u64,
|
||||
/// Weight of calling `seal_reentrance_count`.
|
||||
pub reentrance_count: u64,
|
||||
|
||||
/// Weight of calling `seal_account_reentrance_count`.
|
||||
pub account_reentrance_count: u64,
|
||||
@@ -538,7 +538,7 @@ impl<T: Config> Default for InstructionWeights<T> {
|
||||
fn default() -> Self {
|
||||
let max_pages = Limits::default().memory_pages;
|
||||
Self {
|
||||
version: 3,
|
||||
version: 4,
|
||||
fallback: 0,
|
||||
i64const: cost_instr!(instr_i64const, 1),
|
||||
i64load: cost_instr!(instr_i64load, 2),
|
||||
@@ -665,7 +665,7 @@ impl<T: Config> Default for HostFnWeights<T> {
|
||||
hash_blake2_128_per_byte: cost_byte_batched!(seal_hash_blake2_128_per_kb),
|
||||
ecdsa_recover: cost_batched!(seal_ecdsa_recover),
|
||||
ecdsa_to_eth_address: cost_batched!(seal_ecdsa_to_eth_address),
|
||||
reentrant_count: cost_batched!(seal_reentrant_count),
|
||||
reentrance_count: cost_batched!(seal_reentrance_count),
|
||||
account_reentrance_count: cost_batched!(seal_account_reentrance_count),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
|
||||
@@ -514,7 +514,7 @@ fn calling_plain_account_fails() {
|
||||
|
||||
#[test]
|
||||
fn instantiate_and_call_and_deposit_event() {
|
||||
let (wasm, code_hash) = compile_module::<Test>("return_from_start_fn").unwrap();
|
||||
let (wasm, code_hash) = compile_module::<Test>("event_and_return_on_deploy").unwrap();
|
||||
|
||||
ExtBuilder::default().existential_deposit(500).build().execute_with(|| {
|
||||
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
|
||||
@@ -3720,10 +3720,36 @@ fn contract_reverted() {
|
||||
|
||||
#[test]
|
||||
fn code_rejected_error_works() {
|
||||
let (wasm, _) = compile_module::<Test>("invalid_import").unwrap();
|
||||
ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
|
||||
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
|
||||
|
||||
let (wasm, _) = compile_module::<Test>("invalid_module").unwrap();
|
||||
assert_noop!(
|
||||
Contracts::upload_code(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
wasm.clone(),
|
||||
None,
|
||||
Determinism::Deterministic
|
||||
),
|
||||
<Error<Test>>::CodeRejected,
|
||||
);
|
||||
let result = Contracts::bare_instantiate(
|
||||
ALICE,
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
Code::Upload(wasm),
|
||||
vec![],
|
||||
vec![],
|
||||
true,
|
||||
);
|
||||
assert_err!(result.result, <Error<Test>>::CodeRejected);
|
||||
assert_eq!(
|
||||
std::str::from_utf8(&result.debug_message).unwrap(),
|
||||
"validation of new code failed"
|
||||
);
|
||||
|
||||
let (wasm, _) = compile_module::<Test>("invalid_contract").unwrap();
|
||||
assert_noop!(
|
||||
Contracts::upload_code(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
@@ -3747,7 +3773,7 @@ fn code_rejected_error_works() {
|
||||
assert_err!(result.result, <Error<Test>>::CodeRejected);
|
||||
assert_eq!(
|
||||
std::str::from_utf8(&result.debug_message).unwrap(),
|
||||
"module imports a non-existent function"
|
||||
"call function isn't exported"
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -4386,8 +4412,8 @@ fn delegate_call_indeterministic_code() {
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
fn reentrant_count_works_with_call() {
|
||||
let (wasm, code_hash) = compile_module::<Test>("reentrant_count_call").unwrap();
|
||||
fn reentrance_count_works_with_call() {
|
||||
let (wasm, code_hash) = compile_module::<Test>("reentrance_count_call").unwrap();
|
||||
let contract_addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
|
||||
|
||||
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
|
||||
@@ -4423,8 +4449,8 @@ fn reentrant_count_works_with_call() {
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
fn reentrant_count_works_with_delegated_call() {
|
||||
let (wasm, code_hash) = compile_module::<Test>("reentrant_count_delegated_call").unwrap();
|
||||
fn reentrance_count_works_with_delegated_call() {
|
||||
let (wasm, code_hash) = compile_module::<Test>("reentrance_count_delegated_call").unwrap();
|
||||
let contract_addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
|
||||
|
||||
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
|
||||
@@ -4462,8 +4488,8 @@ fn reentrant_count_works_with_delegated_call() {
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
fn account_reentrance_count_works() {
|
||||
let (wasm, code_hash) = compile_module::<Test>("account_reentrance_count_call").unwrap();
|
||||
let (wasm_reentrant_count, code_hash_reentrant_count) =
|
||||
compile_module::<Test>("reentrant_count_call").unwrap();
|
||||
let (wasm_reentrance_count, code_hash_reentrance_count) =
|
||||
compile_module::<Test>("reentrance_count_call").unwrap();
|
||||
|
||||
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
|
||||
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
|
||||
@@ -4483,14 +4509,14 @@ fn account_reentrance_count_works() {
|
||||
300_000,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
wasm_reentrant_count,
|
||||
wasm_reentrance_count,
|
||||
vec![],
|
||||
vec![]
|
||||
));
|
||||
|
||||
let contract_addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
|
||||
let another_contract_addr =
|
||||
Contracts::contract_address(&ALICE, &code_hash_reentrant_count, &[]);
|
||||
Contracts::contract_address(&ALICE, &code_hash_reentrance_count, &[]);
|
||||
|
||||
let result1 = Contracts::bare_call(
|
||||
ALICE,
|
||||
|
||||
@@ -192,7 +192,10 @@ where
|
||||
pub fn reinstrument<T: Config>(
|
||||
prefab_module: &mut PrefabWasmModule<T>,
|
||||
schedule: &Schedule<T>,
|
||||
) -> Result<u32, DispatchError> {
|
||||
) -> Result<u32, DispatchError>
|
||||
where
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
|
||||
{
|
||||
let original_code =
|
||||
<PristineCode<T>>::get(&prefab_module.code_hash).ok_or(Error::<T>::CodeNotFound)?;
|
||||
let original_code_len = original_code.len();
|
||||
@@ -201,9 +204,12 @@ pub fn reinstrument<T: Config>(
|
||||
// as the contract is already deployed and every change in size would be the result
|
||||
// of changes in the instrumentation algorithm controlled by the chain authors.
|
||||
prefab_module.code = WeakBoundedVec::force_from(
|
||||
prepare::reinstrument_contract::<T>(&original_code, schedule, prefab_module.determinism)
|
||||
.map_err(|_| <Error<T>>::CodeRejected)?,
|
||||
Some("Contract exceeds limit after re-instrumentation."),
|
||||
prepare::reinstrument::<super::runtime::Env, T>(
|
||||
&original_code,
|
||||
schedule,
|
||||
prefab_module.determinism,
|
||||
)?,
|
||||
Some("Contract exceeds size limit after re-instrumentation."),
|
||||
);
|
||||
prefab_module.instruction_weights_version = schedule.instruction_weights.version;
|
||||
<CodeStorage<T>>::insert(&prefab_module.code_hash, &*prefab_module);
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::Runtime;
|
||||
use crate::exec::Ext;
|
||||
|
||||
use sp_sandbox::Value;
|
||||
use wasm_instrument::parity_wasm::elements::{FunctionType, ValueType};
|
||||
|
||||
pub trait ConvertibleToWasm: Sized {
|
||||
const VALUE_TYPE: ValueType;
|
||||
type NativeType;
|
||||
fn to_typed_value(self) -> Value;
|
||||
fn from_typed_value(_: Value) -> Option<Self>;
|
||||
}
|
||||
impl ConvertibleToWasm for i32 {
|
||||
const VALUE_TYPE: ValueType = ValueType::I32;
|
||||
type NativeType = i32;
|
||||
fn to_typed_value(self) -> Value {
|
||||
Value::I32(self)
|
||||
}
|
||||
fn from_typed_value(v: Value) -> Option<Self> {
|
||||
v.as_i32()
|
||||
}
|
||||
}
|
||||
impl ConvertibleToWasm for u32 {
|
||||
const VALUE_TYPE: ValueType = ValueType::I32;
|
||||
type NativeType = u32;
|
||||
fn to_typed_value(self) -> Value {
|
||||
Value::I32(self as i32)
|
||||
}
|
||||
fn from_typed_value(v: Value) -> Option<Self> {
|
||||
match v {
|
||||
Value::I32(v) => Some(v as u32),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ConvertibleToWasm for u64 {
|
||||
const VALUE_TYPE: ValueType = ValueType::I64;
|
||||
type NativeType = u64;
|
||||
fn to_typed_value(self) -> Value {
|
||||
Value::I64(self as i64)
|
||||
}
|
||||
fn from_typed_value(v: Value) -> Option<Self> {
|
||||
match v {
|
||||
Value::I64(v) => Some(v as u64),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type HostFunc<E> = fn(
|
||||
&mut Runtime<E>,
|
||||
&[sp_sandbox::Value],
|
||||
) -> Result<sp_sandbox::ReturnValue, sp_sandbox::HostError>;
|
||||
|
||||
pub trait FunctionImplProvider<E: Ext> {
|
||||
fn impls<F: FnMut(&[u8], &[u8], HostFunc<E>)>(f: &mut F);
|
||||
}
|
||||
|
||||
/// This trait can be used to check whether the host environment can satisfy
|
||||
/// a requested function import.
|
||||
pub trait ImportSatisfyCheck {
|
||||
/// Returns `true` if the host environment contains a function with
|
||||
/// the specified name and its type matches to the given type, or `false`
|
||||
/// otherwise.
|
||||
fn can_satisfy(module: &[u8], name: &[u8], func_type: &FunctionType) -> bool;
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
@@ -22,20 +22,38 @@
|
||||
use crate::{
|
||||
chain_extension::ChainExtension,
|
||||
storage::meter::Diff,
|
||||
wasm::{env_def::ImportSatisfyCheck, Determinism, OwnerInfo, PrefabWasmModule},
|
||||
wasm::{Determinism, Environment, OwnerInfo, PrefabWasmModule},
|
||||
AccountIdOf, CodeVec, Config, Error, Schedule,
|
||||
};
|
||||
use codec::{Encode, MaxEncodedLen};
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
use sp_runtime::{traits::Hash, DispatchError};
|
||||
use sp_std::prelude::*;
|
||||
use wasm_instrument::parity_wasm::elements::{
|
||||
self, External, Internal, MemoryType, Type, ValueType,
|
||||
};
|
||||
use wasmi::StackLimits;
|
||||
use wasmparser::{Validator, WasmFeatures};
|
||||
|
||||
/// Imported memory must be located inside this module. The reason for hardcoding is that current
|
||||
/// compiler toolchains might not support specifying other modules than "env" for memory imports.
|
||||
pub const IMPORT_MODULE_MEMORY: &str = "env";
|
||||
|
||||
/// Determines whether a module should be instantiated during preparation.
|
||||
pub enum TryInstantiate {
|
||||
/// Do the instantiation to make sure that the module is valid.
|
||||
///
|
||||
/// This should be used if a module is only uploaded but not executed. We need
|
||||
/// to make sure that it can be actually instantiated.
|
||||
Instantiate,
|
||||
/// Skip the instantiation during preparation.
|
||||
///
|
||||
/// This makes sense when the preparation takes place as part of an instantation. Then
|
||||
/// this instantiation would fail the whole transaction and an extra check is not
|
||||
/// necessary.
|
||||
Skip,
|
||||
}
|
||||
|
||||
struct ContractModule<'a, T: Config> {
|
||||
/// A deserialized module. The module is valid (this is Guaranteed by `new` method).
|
||||
module: elements::Module,
|
||||
@@ -48,14 +66,9 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
/// Returns `Err` if the `original_code` couldn't be decoded or
|
||||
/// if it contains an invalid module.
|
||||
fn new(original_code: &[u8], schedule: &'a Schedule<T>) -> Result<Self, &'static str> {
|
||||
use wasmi_validation::{validate_module, PlainValidator};
|
||||
|
||||
let module =
|
||||
elements::deserialize_buffer(original_code).map_err(|_| "Can't decode wasm code")?;
|
||||
|
||||
// Make sure that the module is valid.
|
||||
validate_module::<PlainValidator>(&module, ()).map_err(|_| "Module is not valid")?;
|
||||
|
||||
// Return a `ContractModule` instance with
|
||||
// __valid__ module.
|
||||
Ok(ContractModule { module, schedule })
|
||||
@@ -279,27 +292,33 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
|
||||
/// Scan an import section if any.
|
||||
///
|
||||
/// This accomplishes two tasks:
|
||||
/// This makes sure that the import section looks as we expect it from a contract
|
||||
/// and enforces and returns the memory type declared by the contract if any.
|
||||
///
|
||||
/// - checks any imported function against defined host functions set, incl. their signatures.
|
||||
/// - if there is a memory import, returns it's descriptor
|
||||
/// `import_fn_banlist`: list of function names that are disallowed to be imported
|
||||
fn scan_imports<C: ImportSatisfyCheck>(
|
||||
fn scan_imports(
|
||||
&self,
|
||||
import_fn_banlist: &[&[u8]],
|
||||
) -> Result<Option<&MemoryType>, &'static str> {
|
||||
let module = &self.module;
|
||||
|
||||
let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]);
|
||||
let import_entries = module.import_section().map(|is| is.entries()).unwrap_or(&[]);
|
||||
|
||||
let mut imported_mem_type = None;
|
||||
|
||||
for import in import_entries {
|
||||
let type_idx = match *import.external() {
|
||||
match *import.external() {
|
||||
External::Table(_) => return Err("Cannot import tables"),
|
||||
External::Global(_) => return Err("Cannot import globals"),
|
||||
External::Function(ref type_idx) => type_idx,
|
||||
External::Function(_) => {
|
||||
if !T::ChainExtension::enabled() &&
|
||||
import.field().as_bytes() == b"seal_call_chain_extension"
|
||||
{
|
||||
return Err("module uses chain extensions but chain extensions are disabled")
|
||||
}
|
||||
|
||||
if import_fn_banlist.iter().any(|f| import.field().as_bytes() == *f) {
|
||||
return Err("module imports a banned function")
|
||||
}
|
||||
},
|
||||
External::Memory(ref memory_type) => {
|
||||
if import.module() != IMPORT_MODULE_MEMORY {
|
||||
return Err("Invalid module for imported memory")
|
||||
@@ -313,22 +332,6 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
imported_mem_type = Some(memory_type);
|
||||
continue
|
||||
},
|
||||
};
|
||||
|
||||
let Type::Function(ref func_ty) = types
|
||||
.get(*type_idx as usize)
|
||||
.ok_or("validation: import entry points to a non-existent type")?;
|
||||
|
||||
if !T::ChainExtension::enabled() &&
|
||||
import.field().as_bytes() == b"seal_call_chain_extension"
|
||||
{
|
||||
return Err("module uses chain extensions but chain extensions are disabled")
|
||||
}
|
||||
|
||||
if import_fn_banlist.iter().any(|f| import.field().as_bytes() == *f) ||
|
||||
!C::can_satisfy(import.module().as_bytes(), import.field().as_bytes(), func_ty)
|
||||
{
|
||||
return Err("module imports a non-existent function")
|
||||
}
|
||||
}
|
||||
Ok(imported_mem_type)
|
||||
@@ -366,12 +369,54 @@ fn get_memory_limits<T: Config>(
|
||||
}
|
||||
}
|
||||
|
||||
fn check_and_instrument<C: ImportSatisfyCheck, T: Config>(
|
||||
/// Check and instrument the given `original_code`.
|
||||
///
|
||||
/// On success it returns the instrumented versions together with its `(initial, maximum)`
|
||||
/// error requirement. The memory requirement was also validated against the `schedule`.
|
||||
fn instrument<E, T>(
|
||||
original_code: &[u8],
|
||||
schedule: &Schedule<T>,
|
||||
determinism: Determinism,
|
||||
) -> Result<(Vec<u8>, (u32, u32)), &'static str> {
|
||||
let result = (|| {
|
||||
try_instantiate: TryInstantiate,
|
||||
) -> Result<(Vec<u8>, (u32, u32)), (DispatchError, &'static str)>
|
||||
where
|
||||
E: Environment<()>,
|
||||
T: Config,
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
|
||||
{
|
||||
// Do not enable any features here. Any additional feature needs to be carefully
|
||||
// checked for potential security issues. For example, enabling multi value could lead
|
||||
// to a DoS vector: It breaks our assumption that branch instructions are of constant time.
|
||||
// Depending on the implementation they can linearly depend on the amount of values returned
|
||||
// from a block.
|
||||
Validator::new_with_features(WasmFeatures {
|
||||
relaxed_simd: false,
|
||||
threads: false,
|
||||
tail_call: false,
|
||||
multi_memory: false,
|
||||
exceptions: false,
|
||||
memory64: false,
|
||||
extended_const: false,
|
||||
component_model: false,
|
||||
// This is not our only defense: We check for float types later in the preparation
|
||||
// process. Additionally, all instructions explictily need to have weights assigned
|
||||
// or the deployment will fail. We have none assigned for float instructions.
|
||||
deterministic_only: matches!(determinism, Determinism::Deterministic),
|
||||
mutable_global: false,
|
||||
saturating_float_to_int: false,
|
||||
sign_extension: false,
|
||||
bulk_memory: false,
|
||||
multi_value: false,
|
||||
reference_types: false,
|
||||
simd: false,
|
||||
})
|
||||
.validate_all(original_code)
|
||||
.map_err(|err| {
|
||||
log::debug!(target: "runtime::contracts", "{}", err);
|
||||
(Error::<T>::CodeRejected.into(), "validation of new code failed")
|
||||
})?;
|
||||
|
||||
let (code, (initial, maximum)) = (|| {
|
||||
let contract_module = ContractModule::new(original_code, schedule)?;
|
||||
contract_module.scan_exports()?;
|
||||
contract_module.ensure_no_internal_memory()?;
|
||||
@@ -387,7 +432,7 @@ fn check_and_instrument<C: ImportSatisfyCheck, T: Config>(
|
||||
// We disallow importing `gas` function here since it is treated as implementation detail.
|
||||
let disallowed_imports = [b"gas".as_ref()];
|
||||
let memory_limits =
|
||||
get_memory_limits(contract_module.scan_imports::<C>(&disallowed_imports)?, schedule)?;
|
||||
get_memory_limits(contract_module.scan_imports(&disallowed_imports)?, schedule)?;
|
||||
|
||||
let code = contract_module
|
||||
.inject_gas_metering(determinism)?
|
||||
@@ -395,24 +440,56 @@ fn check_and_instrument<C: ImportSatisfyCheck, T: Config>(
|
||||
.into_wasm_code()?;
|
||||
|
||||
Ok((code, memory_limits))
|
||||
})();
|
||||
})()
|
||||
.map_err(|msg: &str| {
|
||||
log::debug!(target: "runtime::contracts", "new code rejected: {}", msg);
|
||||
(Error::<T>::CodeRejected.into(), msg)
|
||||
})?;
|
||||
|
||||
if let Err(msg) = &result {
|
||||
log::debug!(target: "runtime::contracts", "CodeRejected: {}", msg);
|
||||
// This will make sure that the module can be actually run within wasmi:
|
||||
//
|
||||
// - Doesn't use any unknown imports.
|
||||
// - Doesn't explode the wasmi bytecode generation.
|
||||
if matches!(try_instantiate, TryInstantiate::Instantiate) {
|
||||
// We don't actually ever run any code so we can get away with a minimal stack which
|
||||
// reduces the amount of memory that needs to be zeroed.
|
||||
let stack_limits = StackLimits::new(1, 1, 0).expect("initial <= max; qed");
|
||||
PrefabWasmModule::<T>::instantiate::<E, _>(&code, (), (initial, maximum), stack_limits)
|
||||
.map_err(|err| {
|
||||
log::debug!(target: "runtime::contracts", "{}", err);
|
||||
(Error::<T>::CodeRejected.into(), "new code rejected after instrumentation")
|
||||
})?;
|
||||
}
|
||||
|
||||
result
|
||||
Ok((code, (initial, maximum)))
|
||||
}
|
||||
|
||||
fn do_preparation<C: ImportSatisfyCheck, T: Config>(
|
||||
/// Loads the given module given in `original_code`, performs some checks on it and
|
||||
/// does some preprocessing.
|
||||
///
|
||||
/// The checks are:
|
||||
///
|
||||
/// - the provided code is a valid wasm module
|
||||
/// - the module doesn't define an internal memory instance
|
||||
/// - imported memory (if any) doesn't reserve more memory than permitted by the `schedule`
|
||||
/// - all imported functions from the external environment matches defined by `env` module
|
||||
///
|
||||
/// The preprocessing includes injecting code for gas metering and metering the height of stack.
|
||||
pub fn prepare<E, T>(
|
||||
original_code: CodeVec<T>,
|
||||
schedule: &Schedule<T>,
|
||||
owner: AccountIdOf<T>,
|
||||
determinism: Determinism,
|
||||
) -> Result<PrefabWasmModule<T>, (DispatchError, &'static str)> {
|
||||
try_instantiate: TryInstantiate,
|
||||
) -> Result<PrefabWasmModule<T>, (DispatchError, &'static str)>
|
||||
where
|
||||
E: Environment<()>,
|
||||
T: Config,
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
|
||||
{
|
||||
let (code, (initial, maximum)) =
|
||||
check_and_instrument::<C, T>(original_code.as_ref(), schedule, determinism)
|
||||
.map_err(|msg| (<Error<T>>::CodeRejected.into(), msg))?;
|
||||
instrument::<E, T>(original_code.as_ref(), schedule, determinism, try_instantiate)?;
|
||||
|
||||
let original_code_len = original_code.len();
|
||||
|
||||
let mut module = PrefabWasmModule {
|
||||
@@ -420,10 +497,10 @@ fn do_preparation<C: ImportSatisfyCheck, T: Config>(
|
||||
initial,
|
||||
maximum,
|
||||
code: code.try_into().map_err(|_| (<Error<T>>::CodeTooLarge.into(), ""))?,
|
||||
determinism,
|
||||
code_hash: T::Hashing::hash(&original_code),
|
||||
original_code: Some(original_code),
|
||||
owner_info: None,
|
||||
determinism,
|
||||
};
|
||||
|
||||
// We need to add the sizes of the `#[codec(skip)]` fields which are stored in different
|
||||
@@ -441,37 +518,28 @@ fn do_preparation<C: ImportSatisfyCheck, T: Config>(
|
||||
Ok(module)
|
||||
}
|
||||
|
||||
/// Loads the given module given in `original_code`, performs some checks on it and
|
||||
/// does some preprocessing.
|
||||
/// Same as [`prepare`] but without constructing a new module.
|
||||
///
|
||||
/// The checks are:
|
||||
///
|
||||
/// - provided code is a valid wasm module.
|
||||
/// - the module doesn't define an internal memory instance,
|
||||
/// - imported memory (if any) doesn't reserve more memory than permitted by the `schedule`,
|
||||
/// - all imported functions from the external environment matches defined by `env` module,
|
||||
///
|
||||
/// The preprocessing includes injecting code for gas metering and metering the height of stack.
|
||||
pub fn prepare_contract<T: Config>(
|
||||
original_code: CodeVec<T>,
|
||||
schedule: &Schedule<T>,
|
||||
owner: AccountIdOf<T>,
|
||||
determinism: Determinism,
|
||||
) -> Result<PrefabWasmModule<T>, (DispatchError, &'static str)> {
|
||||
do_preparation::<super::runtime::Env, T>(original_code, schedule, owner, determinism)
|
||||
}
|
||||
|
||||
/// The same as [`prepare_contract`] but without constructing a new [`PrefabWasmModule`]
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Use this when an existing contract should be re-instrumented with a newer schedule version.
|
||||
pub fn reinstrument_contract<T: Config>(
|
||||
/// Used to update the code of an existing module to the newest [`Schedule`] version.
|
||||
/// Stictly speaking is not necessary to check the existing code before reinstrumenting because
|
||||
/// it can't change in the meantime. However, since we recently switched the validation library
|
||||
/// we want to re-validate to weed out any bugs that were lurking in the old version.
|
||||
pub fn reinstrument<E, T>(
|
||||
original_code: &[u8],
|
||||
schedule: &Schedule<T>,
|
||||
determinism: Determinism,
|
||||
) -> Result<Vec<u8>, &'static str> {
|
||||
Ok(check_and_instrument::<super::runtime::Env, T>(original_code, schedule, determinism)?.0)
|
||||
) -> Result<Vec<u8>, DispatchError>
|
||||
where
|
||||
E: Environment<()>,
|
||||
T: Config,
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
|
||||
{
|
||||
instrument::<E, T>(original_code, schedule, determinism, TryInstantiate::Skip)
|
||||
.map_err(|(err, msg)| {
|
||||
log::error!(target: "runtime::contracts", "CodeRejected during reinstrument: {}", msg);
|
||||
err
|
||||
})
|
||||
.map(|(code, _)| code)
|
||||
}
|
||||
|
||||
/// Alternate (possibly unsafe) preparation functions used only for benchmarking.
|
||||
@@ -482,29 +550,22 @@ pub fn reinstrument_contract<T: Config>(
|
||||
/// in production code.
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub mod benchmarking {
|
||||
use super::{elements::FunctionType, *};
|
||||
|
||||
impl ImportSatisfyCheck for () {
|
||||
fn can_satisfy(_module: &[u8], _name: &[u8], _func_type: &FunctionType) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
use super::*;
|
||||
|
||||
/// Prepare function that neither checks nor instruments the passed in code.
|
||||
pub fn prepare_contract<T: Config>(
|
||||
pub fn prepare<T: Config>(
|
||||
original_code: Vec<u8>,
|
||||
schedule: &Schedule<T>,
|
||||
owner: AccountIdOf<T>,
|
||||
) -> Result<PrefabWasmModule<T>, &'static str> {
|
||||
let contract_module = ContractModule::new(&original_code, schedule)?;
|
||||
let memory_limits = get_memory_limits(contract_module.scan_imports::<()>(&[])?, schedule)?;
|
||||
let memory_limits = get_memory_limits(contract_module.scan_imports(&[])?, schedule)?;
|
||||
Ok(PrefabWasmModule {
|
||||
instruction_weights_version: schedule.instruction_weights.version,
|
||||
initial: memory_limits.0,
|
||||
maximum: memory_limits.1,
|
||||
code_hash: T::Hashing::hash(&original_code),
|
||||
original_code: Some(original_code.try_into().map_err(|_| "Original code too large")?),
|
||||
determinism: Determinism::Deterministic,
|
||||
code: contract_module
|
||||
.into_wasm_code()?
|
||||
.try_into()
|
||||
@@ -515,6 +576,7 @@ pub mod benchmarking {
|
||||
deposit: Default::default(),
|
||||
refcount: 0,
|
||||
}),
|
||||
determinism: Determinism::Deterministic,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -540,27 +602,28 @@ mod tests {
|
||||
#[allow(unreachable_code)]
|
||||
mod env {
|
||||
use super::*;
|
||||
use crate::wasm::runtime::TrapReason;
|
||||
|
||||
// Define test environment for tests. We need ImportSatisfyCheck
|
||||
// implementation from it. So actual implementations doesn't matter.
|
||||
#[define_env]
|
||||
pub mod test_env {
|
||||
fn panic(_ctx: crate::wasm::Runtime<E>) -> Result<(), TrapReason> {
|
||||
fn panic(_ctx: _, _memory: _) -> Result<(), TrapReason> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// gas is an implementation defined function and a contract can't import it.
|
||||
fn gas(_ctx: crate::wasm::Runtime<E>, _amount: u32) -> Result<(), TrapReason> {
|
||||
fn gas(_ctx: _, _memory: _, _amount: u64) -> Result<(), TrapReason> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn nop(_ctx: crate::wasm::Runtime<E>, _unused: u64) -> Result<(), TrapReason> {
|
||||
fn nop(_ctx: _, _memory: _, _unused: u64) -> Result<(), TrapReason> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// new version of nop with other data type for argumebt
|
||||
// new version of nop with other data type for argument
|
||||
#[version(1)]
|
||||
fn nop(_ctx: crate::wasm::Runtime<E>, _unused: i32) -> Result<(), TrapReason> {
|
||||
fn nop(_ctx: _, _memory: _, _unused: i32) -> Result<(), TrapReason> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -582,7 +645,13 @@ mod tests {
|
||||
},
|
||||
.. Default::default()
|
||||
};
|
||||
let r = do_preparation::<env::Env, Test>(wasm, &schedule, ALICE, Determinism::Deterministic);
|
||||
let r = prepare::<env::Env, Test>(
|
||||
wasm,
|
||||
&schedule,
|
||||
ALICE,
|
||||
Determinism::Deterministic,
|
||||
TryInstantiate::Instantiate,
|
||||
);
|
||||
assert_matches::assert_matches!(r.map_err(|(_, msg)| msg), $($expected)*);
|
||||
}
|
||||
};
|
||||
@@ -718,7 +787,7 @@ mod tests {
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("Module is not valid")
|
||||
Err("validation of new code failed")
|
||||
);
|
||||
|
||||
prepare_test!(
|
||||
@@ -784,7 +853,7 @@ mod tests {
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("Module is not valid")
|
||||
Err("validation of new code failed")
|
||||
);
|
||||
|
||||
prepare_test!(
|
||||
@@ -910,7 +979,7 @@ mod tests {
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("module imports a non-existent function")
|
||||
Err("module imports a banned function")
|
||||
);
|
||||
|
||||
// memory is in "env" and not in "seal0"
|
||||
@@ -965,7 +1034,7 @@ mod tests {
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("module imports a non-existent function")
|
||||
Err("module imports a banned function")
|
||||
);
|
||||
|
||||
prepare_test!(
|
||||
@@ -978,7 +1047,7 @@ mod tests {
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("module imports a non-existent function")
|
||||
Err("new code rejected after instrumentation")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user