mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-07 07:18:03 +00:00
[contracts] define_env! re-write as a proc macro (#11888)
* define_env proc macro basics + can_satisfy part ready * expand_impls part done * fix of the &FunctionType bug * pallet is compiled * updated host fn definition syntax * docs comments allowed to host fn definitions * all 53 host funcs re-defined by the new macro * unstable feat fix * cleanup * legacy mbe macros cleaned up * Added Env ident to macro attribute; all tests pass! * \#[v(..)] -> \#[version(..)] * some tiny corrections * save * builds with non-magic rt; tests fail * tests pass * refactored errors + added docs * merge err fixed * fixes on @ascjones review, all except moving away from `pub mod env` syntax * debug printing cleared * clippy fix
This commit is contained in:
@@ -15,16 +15,23 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Proc macros used in the contracts module.
|
||||
//! Procedural macroses used in the contracts module.
|
||||
//!
|
||||
//! Most likely you should use the [`#[define_env]`][`macro@define_env`] attribute macro which hides
|
||||
//! boilerplate of defining external environment for a wasm module.
|
||||
|
||||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::string::ToString;
|
||||
use alloc::{
|
||||
format,
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::{parse_macro_input, Data, DeriveInput, Ident};
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use syn::{parse_macro_input, spanned::Spanned, Data, DeriveInput, 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
|
||||
@@ -85,7 +92,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 {
|
||||
use syn::{spanned::Spanned, Fields};
|
||||
use syn::Fields;
|
||||
|
||||
match &data.fields {
|
||||
Fields::Named(fields) => {
|
||||
@@ -140,3 +147,392 @@ fn format_default(field: &Ident) -> TokenStream {
|
||||
&self.#field
|
||||
}
|
||||
}
|
||||
|
||||
/// Parsed environment definition.
|
||||
struct EnvDef {
|
||||
host_funcs: Vec<HostFn>,
|
||||
}
|
||||
|
||||
/// Parsed host function definition.
|
||||
struct HostFn {
|
||||
item: syn::ItemFn,
|
||||
module: String,
|
||||
name: String,
|
||||
returns: HostFnReturn,
|
||||
}
|
||||
|
||||
enum HostFnReturn {
|
||||
Unit,
|
||||
U32,
|
||||
ReturnCode,
|
||||
}
|
||||
|
||||
impl ToTokens for HostFn {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
self.item.to_tokens(tokens);
|
||||
}
|
||||
}
|
||||
|
||||
impl HostFn {
|
||||
pub fn try_from(item: syn::Item) -> syn::Result<Self> {
|
||||
let err = |span, msg| {
|
||||
let msg = format!("Invalid host function definition. {}", msg);
|
||||
syn::Error::new(span, msg)
|
||||
};
|
||||
let msg = "only #[version(<u8>)] or #[unstable] attribute is allowed.";
|
||||
let span = item.span();
|
||||
let item = match item {
|
||||
syn::Item::Fn(i_fn) => Ok(i_fn),
|
||||
_ => Err(err(span, msg)),
|
||||
}?;
|
||||
|
||||
let name = item.sig.ident.to_string();
|
||||
let attrs: Vec<&syn::Attribute> =
|
||||
item.attrs.iter().filter(|m| !m.path.is_ident("doc")).collect();
|
||||
|
||||
let module = match attrs.len() {
|
||||
0 => Ok("seal0".to_string()),
|
||||
1 => {
|
||||
let attr = &attrs[0];
|
||||
let ident = attr.path.get_ident().ok_or(err(span, msg))?.to_string();
|
||||
match ident.as_str() {
|
||||
"version" => {
|
||||
let ver: syn::LitInt = attr.parse_args()?;
|
||||
Ok(format!("seal{}", ver.base10_parse::<u8>().map_err(|_| err(span, msg))?))
|
||||
},
|
||||
"unstable" => Ok("__unstable__".to_string()),
|
||||
_ => Err(err(span, msg)),
|
||||
}
|
||||
},
|
||||
_ => Err(err(span, msg)),
|
||||
}?;
|
||||
|
||||
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))?;
|
||||
let (id, span) = (result.ident.to_string(), result.ident.span());
|
||||
id.eq(&"Result".to_string()).then_some(()).ok_or(err(span, &msg))?;
|
||||
|
||||
match &result.arguments {
|
||||
syn::PathArguments::AngleBracketed(group) => {
|
||||
if group.args.len() != 2 {
|
||||
return Err(err(span, &msg))
|
||||
};
|
||||
|
||||
let arg2 = group.args.last().ok_or(err(span, &msg))?;
|
||||
|
||||
let err_ty = match arg2 {
|
||||
syn::GenericArgument::Type(ty) => Ok(ty.clone()),
|
||||
_ => Err(err(arg2.span(), &msg)),
|
||||
}?;
|
||||
|
||||
match err_ty {
|
||||
syn::Type::Path(tp) => Ok(tp
|
||||
.path
|
||||
.segments
|
||||
.first()
|
||||
.ok_or(err(arg2.span(), &msg))?
|
||||
.ident
|
||||
.to_string()),
|
||||
_ => Err(err(tp.span(), &msg)),
|
||||
}?
|
||||
.eq("TrapReason")
|
||||
.then_some(())
|
||||
.ok_or(err(span, &msg))?;
|
||||
|
||||
let arg1 = group.args.first().ok_or(err(span, &msg))?;
|
||||
let ok_ty = match arg1 {
|
||||
syn::GenericArgument::Type(ty) => Ok(ty.clone()),
|
||||
_ => Err(err(arg1.span(), &msg)),
|
||||
}?;
|
||||
let ok_ty_str = match ok_ty {
|
||||
syn::Type::Path(tp) => Ok(tp
|
||||
.path
|
||||
.segments
|
||||
.first()
|
||||
.ok_or(err(arg1.span(), &msg))?
|
||||
.ident
|
||||
.to_string()),
|
||||
syn::Type::Tuple(tt) => {
|
||||
if !tt.elems.is_empty() {
|
||||
return Err(err(arg1.span(), &msg))
|
||||
};
|
||||
Ok("()".to_string())
|
||||
},
|
||||
_ => 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)),
|
||||
}
|
||||
},
|
||||
_ => 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 {
|
||||
pub fn try_from(item: syn::ItemMod) -> syn::Result<Self> {
|
||||
let span = item.span();
|
||||
let err = |msg| syn::Error::new(span, msg);
|
||||
let items = &item
|
||||
.content
|
||||
.as_ref()
|
||||
.ok_or(err("Invalid environment definition, expected `mod` to be inlined."))?
|
||||
.1;
|
||||
|
||||
let host_funcs = items
|
||||
.iter()
|
||||
.map(|i| HostFn::try_from(i.clone()))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(Self { host_funcs })
|
||||
}
|
||||
}
|
||||
|
||||
/// 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);
|
||||
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 )*
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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! { },
|
||||
}
|
||||
});
|
||||
|
||||
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
|
||||
}
|
||||
#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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines a host functions set that can be imported by contract wasm code.
|
||||
///
|
||||
/// **NB**: Be advised that all functions defined by this macro
|
||||
/// will panic if called with unexpected arguments.
|
||||
///
|
||||
/// It's up to you as the user of this macro to check signatures of wasm code to be executed
|
||||
/// and reject the code if any imported function has a mismatched signature.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```nocompile
|
||||
/// #[define_env]
|
||||
/// pub mod some_env {
|
||||
/// fn some_host_fn(ctx: Runtime<E: Ext>, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<(), TrapReason> {
|
||||
/// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ())
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// This example will expand to the `some_host_fn()` defined in the wasm module named `seal0`.
|
||||
/// To define a host function in `seal1` and `__unstable__` modules, it should be annotated with the
|
||||
/// appropriate attribute as follows:
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```nocompile
|
||||
/// #[define_env]
|
||||
/// pub mod some_env {
|
||||
/// #[version(1)]
|
||||
/// fn some_host_fn(ctx: Runtime<E: Ext>, 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: Ext>, 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(|_| ())
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Only following return types are allowed for the host functions defined with the macro:
|
||||
/// - `Result<(), TrapReason>`,
|
||||
/// - `Result<ReturnCode, TrapReason>`,
|
||||
/// - `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`
|
||||
#[proc_macro_attribute]
|
||||
pub fn define_env(
|
||||
attr: proc_macro::TokenStream,
|
||||
item: proc_macro::TokenStream,
|
||||
) -> proc_macro::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();
|
||||
return syn::Error::new(span, msg).to_compile_error().into()
|
||||
}
|
||||
|
||||
let item = syn::parse_macro_input!(item as syn::ItemMod);
|
||||
|
||||
match EnvDef::try_from(item) {
|
||||
Ok(mut def) => expand_env(&mut def).into(),
|
||||
Err(e) => e.to_compile_error().into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,396 +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.
|
||||
|
||||
//! Definition of macros that hides boilerplate of defining external environment
|
||||
//! for a wasm module.
|
||||
//!
|
||||
//! Most likely you should use `define_env` macro.
|
||||
|
||||
macro_rules! convert_args {
|
||||
() => (vec![]);
|
||||
( $( $t:ty ),* ) => ( vec![ $( { use $crate::wasm::env_def::ConvertibleToWasm; <$t>::VALUE_TYPE }, )* ] );
|
||||
}
|
||||
|
||||
macro_rules! gen_signature {
|
||||
( ( $( $params: ty ),* ) ) => (
|
||||
{
|
||||
wasm_instrument::parity_wasm::elements::FunctionType::new(
|
||||
convert_args!($($params),*), vec![],
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
( ( $( $params: ty ),* ) -> $returns: ty ) => (
|
||||
{
|
||||
wasm_instrument::parity_wasm::elements::FunctionType::new(
|
||||
convert_args!($($params),*),
|
||||
vec![{use $crate::wasm::env_def::ConvertibleToWasm; <$returns>::VALUE_TYPE}],
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! gen_signature_dispatch {
|
||||
(
|
||||
$needle_module:ident,
|
||||
$needle_name:ident,
|
||||
$needle_sig:ident ;
|
||||
$module:ident,
|
||||
$name:ident
|
||||
( $ctx:ident $( , $names:ident : $params:ty )* ) $( -> $returns:ty )* , $($rest:tt)*
|
||||
) => {
|
||||
let module = stringify!($module).as_bytes();
|
||||
if module == $needle_module && stringify!($name).as_bytes() == $needle_name {
|
||||
let signature = gen_signature!( ( $( $params ),* ) $( -> $returns )* );
|
||||
if $needle_sig == &signature {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
gen_signature_dispatch!($needle_module, $needle_name, $needle_sig ; $($rest)*);
|
||||
}
|
||||
};
|
||||
( $needle_module:ident, $needle_name:ident, $needle_sig:ident ; ) => {};
|
||||
}
|
||||
|
||||
/// Unmarshall arguments and then execute `body` expression and return its result.
|
||||
macro_rules! unmarshall_then_body {
|
||||
( $body:tt, $ctx:ident, $args_iter:ident, $( $names:ident : $params:ty ),* ) => ({
|
||||
$(
|
||||
let $names : <$params as $crate::wasm::env_def::ConvertibleToWasm>::NativeType =
|
||||
$args_iter.next()
|
||||
.and_then(|v| <$params 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!` macro by the user of the macro;
|
||||
signatures of these functions defined by `$params`;
|
||||
calls always made with arguments types of which are defined by the corresponding imports;
|
||||
thus types of arguments should be equal to type list in `$params` and
|
||||
length of argument list and $params should be equal;
|
||||
thus this can never be `None`;
|
||||
qed;
|
||||
"
|
||||
);
|
||||
)*
|
||||
$body
|
||||
})
|
||||
}
|
||||
|
||||
/// Since we can't specify the type of closure directly at binding site:
|
||||
///
|
||||
/// ```nocompile
|
||||
/// let f: FnOnce() -> Result<<u32 as ConvertibleToWasm>::NativeType, _> = || { /* ... */ };
|
||||
/// ```
|
||||
///
|
||||
/// we use this function to constrain the type of the closure.
|
||||
#[inline(always)]
|
||||
pub fn constrain_closure<R, F>(f: F) -> F
|
||||
where
|
||||
F: FnOnce() -> Result<R, crate::wasm::runtime::TrapReason>,
|
||||
{
|
||||
f
|
||||
}
|
||||
|
||||
macro_rules! unmarshall_then_body_then_marshall {
|
||||
( $args_iter:ident, $ctx:ident, ( $( $names:ident : $params:ty ),* ) -> $returns:ty => $body:tt ) => ({
|
||||
let body = $crate::wasm::env_def::macros::constrain_closure::<
|
||||
<$returns as $crate::wasm::env_def::ConvertibleToWasm>::NativeType, _
|
||||
>(|| {
|
||||
unmarshall_then_body!($body, $ctx, $args_iter, $( $names : $params ),*)
|
||||
});
|
||||
let r = body().map_err(|reason| {
|
||||
$ctx.set_trap_reason(reason);
|
||||
sp_sandbox::HostError
|
||||
})?;
|
||||
return Ok(sp_sandbox::ReturnValue::Value({ use $crate::wasm::env_def::ConvertibleToWasm; r.to_typed_value() }))
|
||||
});
|
||||
( $args_iter:ident, $ctx:ident, ( $( $names:ident : $params:ty ),* ) => $body:tt ) => ({
|
||||
let body = $crate::wasm::env_def::macros::constrain_closure::<(), _>(|| {
|
||||
unmarshall_then_body!($body, $ctx, $args_iter, $( $names : $params ),*)
|
||||
});
|
||||
body().map_err(|reason| {
|
||||
$ctx.set_trap_reason(reason);
|
||||
sp_sandbox::HostError
|
||||
})?;
|
||||
return Ok(sp_sandbox::ReturnValue::Unit)
|
||||
})
|
||||
}
|
||||
|
||||
macro_rules! define_func {
|
||||
( $trait:tt $name:ident ( $ctx: ident $(, $names:ident : $params:ty)*) $(-> $returns:ty)* => $body:tt ) => {
|
||||
fn $name< E: $trait >(
|
||||
$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();
|
||||
|
||||
unmarshall_then_body_then_marshall!(
|
||||
args,
|
||||
$ctx,
|
||||
( $( $names : $params ),* ) $( -> $returns )* => $body
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! register_body {
|
||||
( $reg_cb:ident, $trait:tt;
|
||||
$module:ident $name:ident ( $ctx:ident $( , $names:ident : $params:ty )* )
|
||||
$( -> $returns:ty )* => $body:tt
|
||||
) => {
|
||||
$reg_cb(
|
||||
stringify!($module).as_bytes(),
|
||||
stringify!($name).as_bytes(),
|
||||
{
|
||||
define_func!(
|
||||
$trait $name ( $ctx $(, $names : $params )* ) $( -> $returns )* => $body
|
||||
);
|
||||
$name::<E>
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! register_func {
|
||||
( $reg_cb:ident, $trait:tt; ) => {};
|
||||
|
||||
( $reg_cb:ident, $trait:tt;
|
||||
__unstable__ $name:ident ( $ctx:ident $( , $names:ident : $params:ty )* )
|
||||
$( -> $returns:ty )* => $body:tt $($rest:tt)*
|
||||
) => {
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
register_body!(
|
||||
$reg_cb, $trait;
|
||||
__unstable__ $name
|
||||
( $ctx $( , $names : $params )* )
|
||||
$( -> $returns )* => $body
|
||||
);
|
||||
register_func!( $reg_cb, $trait; $($rest)* );
|
||||
};
|
||||
|
||||
( $reg_cb:ident, $trait:tt;
|
||||
$module:ident $name:ident ( $ctx:ident $( , $names:ident : $params:ty )* )
|
||||
$( -> $returns:ty )* => $body:tt $($rest:tt)*
|
||||
) => {
|
||||
register_body!(
|
||||
$reg_cb, $trait;
|
||||
$module $name
|
||||
( $ctx $( , $names : $params )* )
|
||||
$( -> $returns )* => $body
|
||||
);
|
||||
register_func!( $reg_cb, $trait; $($rest)* );
|
||||
};
|
||||
}
|
||||
|
||||
/// Define a function set that can be imported by executing wasm code.
|
||||
///
|
||||
/// **NB**: Be advised that all functions defined by this macro
|
||||
/// will panic if called with unexpected arguments.
|
||||
///
|
||||
/// It's up to the user of this macro to check signatures of wasm code to be executed
|
||||
/// and reject the code if any imported function has a mismatched signature.
|
||||
macro_rules! define_env {
|
||||
( $init_name:ident , < E: $trait:tt > ,
|
||||
$( [$module:ident] $name:ident ( $ctx:ident $( , $names:ident : $params:ty )* )
|
||||
$( -> $returns:ty )* => $body:tt , )*
|
||||
) => {
|
||||
pub struct $init_name;
|
||||
|
||||
impl $crate::wasm::env_def::ImportSatisfyCheck for $init_name {
|
||||
fn can_satisfy(
|
||||
module: &[u8],
|
||||
name: &[u8],
|
||||
func_type: &wasm_instrument::parity_wasm::elements::FunctionType,
|
||||
) -> bool
|
||||
{
|
||||
#[cfg(not(feature = "unstable-interface"))]
|
||||
if module == b"__unstable__" {
|
||||
return false;
|
||||
}
|
||||
gen_signature_dispatch!(
|
||||
module, name, func_type ;
|
||||
$( $module, $name ( $ctx $(, $names : $params )* ) $( -> $returns )* , )*
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Ext> $crate::wasm::env_def::FunctionImplProvider<E> for $init_name
|
||||
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) {
|
||||
register_func!(
|
||||
f,
|
||||
$trait;
|
||||
$( $module $name ( $ctx $( , $names : $params )* ) $( -> $returns)* => $body )*
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
exec::Ext,
|
||||
wasm::{runtime::TrapReason, tests::MockExt, Runtime},
|
||||
Weight,
|
||||
};
|
||||
use sp_runtime::traits::Zero;
|
||||
use sp_sandbox::{ReturnValue, Value};
|
||||
use wasm_instrument::parity_wasm::elements::{FunctionType, ValueType};
|
||||
|
||||
struct TestRuntime {
|
||||
value: u32,
|
||||
}
|
||||
|
||||
impl TestRuntime {
|
||||
fn set_trap_reason(&mut self, _reason: TrapReason) {}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_unmarshall_then_body_then_marshall_value_or_trap() {
|
||||
fn test_value(
|
||||
_ctx: &mut TestRuntime,
|
||||
args: &[sp_sandbox::Value],
|
||||
) -> Result<ReturnValue, sp_sandbox::HostError> {
|
||||
let mut args = args.iter();
|
||||
unmarshall_then_body_then_marshall!(
|
||||
args,
|
||||
_ctx,
|
||||
(a: u32, b: u32) -> u32 => {
|
||||
if b == 0 {
|
||||
Err(crate::wasm::runtime::TrapReason::Termination)
|
||||
} else {
|
||||
Ok(a / b)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
let ctx = &mut TestRuntime { value: 0 };
|
||||
assert_eq!(
|
||||
test_value(ctx, &[Value::I32(15), Value::I32(3)]).unwrap(),
|
||||
ReturnValue::Value(Value::I32(5)),
|
||||
);
|
||||
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 TestRuntime,
|
||||
args: &[sp_sandbox::Value],
|
||||
) -> Result<ReturnValue, sp_sandbox::HostError> {
|
||||
let mut args = args.iter();
|
||||
unmarshall_then_body_then_marshall!(
|
||||
args,
|
||||
ctx,
|
||||
(a: u32, b: u32) => {
|
||||
ctx.value = a + b;
|
||||
Ok(())
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
let ctx = &mut TestRuntime { value: 0 };
|
||||
let result = test_unit(ctx, &[Value::I32(2), Value::I32(3)]).unwrap();
|
||||
assert_eq!(result, ReturnValue::Unit);
|
||||
assert_eq!(ctx.value, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_define_func() {
|
||||
define_func!( Ext seal_gas (_ctx, amount: u32) => {
|
||||
let amount = Weight::from(amount);
|
||||
if !amount.is_zero() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(TrapReason::Termination)
|
||||
}
|
||||
});
|
||||
let _f: fn(
|
||||
&mut Runtime<MockExt>,
|
||||
&[sp_sandbox::Value],
|
||||
) -> Result<sp_sandbox::ReturnValue, sp_sandbox::HostError> = seal_gas::<MockExt>;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_gen_signature() {
|
||||
assert_eq!(gen_signature!((i32)), FunctionType::new(vec![ValueType::I32], vec![]));
|
||||
|
||||
assert_eq!(
|
||||
gen_signature!( (i32, u32) -> u32 ),
|
||||
FunctionType::new(vec![ValueType::I32, ValueType::I32], vec![ValueType::I32]),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_unmarshall_then_body() {
|
||||
let args = vec![Value::I32(5), Value::I32(3)];
|
||||
let mut args = args.iter();
|
||||
|
||||
let ctx: &mut u32 = &mut 0;
|
||||
|
||||
let r = unmarshall_then_body!(
|
||||
{
|
||||
*ctx = a + b;
|
||||
a * b
|
||||
},
|
||||
ctx,
|
||||
args,
|
||||
a: u32,
|
||||
b: u32
|
||||
);
|
||||
|
||||
assert_eq!(*ctx, 8);
|
||||
assert_eq!(r, 15);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_define_env() {
|
||||
use crate::wasm::env_def::ImportSatisfyCheck;
|
||||
|
||||
define_env!(Env, <E: Ext>,
|
||||
[seal0] seal_gas( _ctx, amount: u32 ) => {
|
||||
let amount = Weight::from(amount);
|
||||
if !amount.is_zero() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(crate::wasm::runtime::TrapReason::Termination)
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
assert!(Env::can_satisfy(
|
||||
b"seal0",
|
||||
b"seal_gas",
|
||||
&FunctionType::new(vec![ValueType::I32], vec![])
|
||||
));
|
||||
assert!(!Env::can_satisfy(b"seal0", b"not_exists", &FunctionType::new(vec![], vec![])));
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,6 @@ use crate::exec::Ext;
|
||||
use sp_sandbox::Value;
|
||||
use wasm_instrument::parity_wasm::elements::{FunctionType, ValueType};
|
||||
|
||||
#[macro_use]
|
||||
pub mod macros;
|
||||
|
||||
pub trait ConvertibleToWasm: Sized {
|
||||
const VALUE_TYPE: ValueType;
|
||||
type NativeType;
|
||||
|
||||
@@ -517,6 +517,7 @@ mod tests {
|
||||
schedule::Limits,
|
||||
tests::{Test, ALICE},
|
||||
};
|
||||
use pallet_contracts_proc_macro::define_env;
|
||||
use std::fmt;
|
||||
|
||||
impl fmt::Debug for PrefabWasmModule<Test> {
|
||||
@@ -532,17 +533,27 @@ mod tests {
|
||||
|
||||
// Define test environment for tests. We need ImportSatisfyCheck
|
||||
// implementation from it. So actual implementations doesn't matter.
|
||||
define_env!(Test, <E: Ext>,
|
||||
[seal0] panic(_ctx) => { unreachable!(); },
|
||||
#[define_env]
|
||||
pub mod test_env {
|
||||
fn panic(_ctx: crate::wasm::Runtime<E>) -> Result<(), TrapReason> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// gas is an implementation defined function and a contract can't import it.
|
||||
[seal0] gas(_ctx, _amount: u32) => { unreachable!(); },
|
||||
fn gas(_ctx: crate::wasm::Runtime<E>, _amount: u32) -> Result<(), TrapReason> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
[seal0] nop(_ctx, _unused: u64) => { unreachable!(); },
|
||||
fn nop(_ctx: crate::wasm::Runtime<E>, _unused: u64) -> Result<(), TrapReason> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// new version of nop with other data type for argument
|
||||
[seal1] nop(_ctx, _unused: i32) => { unreachable!(); },
|
||||
);
|
||||
// new version of nop with other data type for argumebt
|
||||
#[version(1)]
|
||||
fn nop(_ctx: crate::wasm::Runtime<E>, _unused: i32) -> Result<(), TrapReason> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! prepare_test {
|
||||
@@ -561,7 +572,7 @@ mod tests {
|
||||
},
|
||||
.. Default::default()
|
||||
};
|
||||
let r = do_preparation::<env::Test, Test>(wasm, &schedule, ALICE);
|
||||
let r = do_preparation::<env::Env, Test>(wasm, &schedule, ALICE);
|
||||
assert_matches::assert_matches!(r.map_err(|(_, msg)| msg), $($expected)*);
|
||||
}
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user