feat: initialize Kurdistan SDK - independent fork of Polkadot SDK
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
[package]
|
||||
name = "sp-runtime-interface"
|
||||
version = "24.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Substrate runtime interface"
|
||||
documentation = "https://docs.rs/sp-runtime-interface/"
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
bytes = { workspace = true }
|
||||
codec = { features = ["bytes"], workspace = true }
|
||||
impl-trait-for-tuples = { workspace = true }
|
||||
sp-externalities = { workspace = true }
|
||||
sp-runtime-interface-proc-macro = { workspace = true, default-features = true }
|
||||
sp-std = { workspace = true }
|
||||
sp-storage = { workspace = true }
|
||||
sp-tracing = { workspace = true }
|
||||
sp-wasm-interface = { workspace = true }
|
||||
static_assertions = { workspace = true, default-features = true }
|
||||
|
||||
[target.'cfg(all(any(target_arch = "riscv32", target_arch = "riscv64"), substrate_runtime))'.dependencies]
|
||||
polkavm-derive = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rustversion = { workspace = true }
|
||||
sp-io = { workspace = true, default-features = true }
|
||||
sp-runtime-interface-test-wasm = { workspace = true }
|
||||
sp-state-machine = { workspace = true, default-features = true }
|
||||
trybuild = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bytes/std",
|
||||
"codec/std",
|
||||
"sp-externalities/std",
|
||||
"sp-io/std",
|
||||
"sp-runtime-interface-test-wasm/std",
|
||||
"sp-state-machine/std",
|
||||
"sp-std/std",
|
||||
"sp-storage/std",
|
||||
"sp-tracing/std",
|
||||
"sp-wasm-interface/std",
|
||||
]
|
||||
|
||||
# ATTENTION
|
||||
#
|
||||
# Only use when you know what you are doing.
|
||||
#
|
||||
# Disables static assertions in `impls.rs` that checks the word size. To prevent any footgun, the
|
||||
# check is changed into a runtime check.
|
||||
disable_target_static_assertions = []
|
||||
runtime-benchmarks = [
|
||||
"sp-io/runtime-benchmarks",
|
||||
"sp-state-machine/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,89 @@
|
||||
Substrate runtime interface
|
||||
|
||||
This crate provides types, traits and macros around runtime interfaces. A runtime interface is a fixed interface between
|
||||
a Substrate runtime and a Substrate node. For a native runtime the interface maps to a direct function call of the
|
||||
implementation. For a wasm runtime the interface maps to an external function call. These external functions are
|
||||
exported by the wasm executor and they map to the same implementation as the native calls.
|
||||
|
||||
# Using a type in a runtime interface
|
||||
<!-- markdown-link-check-disable -->
|
||||
Any type that should be used in a runtime interface as argument or return value needs to implement [`RIType`]. The
|
||||
associated type
|
||||
[`FFIType`](https:/docs.rs/sp-runtime-interface/latest/sp_runtime_interface/trait.RIType.html#associatedtype.FFIType) is
|
||||
the type that is used in the FFI function to represent the actual type. For example `[T]` is represented by an `u64`.
|
||||
The slice pointer and the length will be mapped to an `u64` value. For more information see this
|
||||
[table](https:/docs.rs/sp-runtime-interface/latest/sp_runtime_interface/#ffi-type-and-conversion). The FFI function
|
||||
definition is used when calling from the wasm runtime into the node.
|
||||
|
||||
Traits are used to convert from a type to the corresponding
|
||||
[`RIType::FFIType`](https:/docs.rs/sp-runtime-interface/latest/sp_runtime_interface/trait.RIType.html#associatedtype.FFIType).
|
||||
Depending on where and how a type should be used in a function signature, a combination of the following traits need to
|
||||
be implemented:
|
||||
<!-- markdown-link-check-enable -->
|
||||
1. Pass as function argument: [`wasm::IntoFFIValue`] and [`host::FromFFIValue`]
|
||||
2. As function return value: [`wasm::FromFFIValue`] and [`host::IntoFFIValue`]
|
||||
3. Pass as mutable function argument: [`host::IntoPreallocatedFFIValue`]
|
||||
|
||||
The traits are implemented for most of the common types like `[T]`, `Vec<T>`, arrays and primitive types.
|
||||
|
||||
For custom types, we provide the
|
||||
[`PassBy`](https://docs.rs/sp-runtime-interface/latest/sp_runtime_interface/pass_by#PassBy) trait and strategies that
|
||||
define how a type is passed between the wasm runtime and the node. Each strategy also provides a derive macro to
|
||||
simplify the implementation.
|
||||
|
||||
# Performance
|
||||
|
||||
To not waste any more performance when calling into the node, not all types are SCALE encoded when being passed as
|
||||
arguments between the wasm runtime and the node. For most types that are raw bytes like `Vec<u8>`, `[u8]` or `[u8; N]`
|
||||
we pass them directly, without SCALE encoding them in front of. The implementation of [`RIType`] each type provides more
|
||||
information on how the data is passed.
|
||||
|
||||
# Declaring a runtime interface
|
||||
|
||||
Declaring a runtime interface is similar to declaring a trait in Rust:
|
||||
|
||||
```rust
|
||||
#[sp_runtime_interface::runtime_interface]
|
||||
trait RuntimeInterface {
|
||||
fn some_function(value: &[u8]) -> bool {
|
||||
value.iter().all(|v| *v > 125)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For more information on declaring a runtime interface, see
|
||||
[`#[runtime_interface]`](https://docs.rs/sp-runtime-interface/latest/sp_runtime_interface/attr.runtime_interface.html).
|
||||
|
||||
# FFI type and conversion
|
||||
|
||||
The following table documents how values of types are passed between the wasm and the host side and how they are
|
||||
converted into the corresponding type.
|
||||
|
||||
<!-- markdownlint-disable MD013 -->
|
||||
| Type | FFI type | Conversion |
|
||||
|----|----|----|
|
||||
| `u8` | `u8` | `Identity` |
|
||||
| `u16` | `u16` | `Identity` |
|
||||
| `u32` | `u32` | `Identity` |
|
||||
| `u64` | `u64` | `Identity` |
|
||||
| `i128` | `u32` | `v.as_ptr()` (pointer to a 16 byte array) |
|
||||
| `i8` | `i8` | `Identity` |
|
||||
| `i16` | `i16` | `Identity` |
|
||||
| `i32` | `i32` | `Identity` |
|
||||
| `i64` | `i64` | `Identity` |
|
||||
| `u128` | `u32` | `v.as_ptr()` (pointer to a 16 byte array) |
|
||||
| `bool` | `u8` | `if v { 1 } else { 0 }` |
|
||||
| `&str` | `u64` | <code>v.len() 32bit << 32 | v.as_ptr() 32bit</code> |
|
||||
| `&[u8]` | `u64` | <code>v.len() 32bit << 32 | v.as_ptr() 32bit</code> |
|
||||
| `Vec<u8>` | `u64` | <code>v.len() 32bit << 32 | v.as_ptr() 32bit</code> |
|
||||
| `Vec<T> where T: Encode` | `u64` | `let e = v.encode();`<br><br><code>e.len() 32bit << 32 | e.as_ptr() 32bit</code> |
|
||||
| `&[T] where T: Encode` | `u64` | `let e = v.encode();`<br><br><code>e.len() 32bit << 32 | e.as_ptr() 32bit</code> |
|
||||
| `[u8; N]` | `u32` | `v.as_ptr()` |
|
||||
| `*const T` | `u32` | `Identity` |
|
||||
| `Option<T>` | `u64` | `let e = v.encode();`<br><br><code>e.len() 32bit << 32 | e.as_ptr() 32bit</code> |
|
||||
| [`T where T: PassBy<PassBy=Inner>`](https://docs.rs/sp-runtime-interface/latest/sp_runtime_interface/pass_by#Inner) | Depends on inner | Depends on inner |
|
||||
| [`T where T: PassBy<PassBy=Codec>`](https://docs.rs/sp-runtime-interface/latest/sp_runtime_interface/pass_by#Codec) | `u64`| <code>v.len() 32bit << 32 | v.as_ptr() 32bit</code> |
|
||||
|
||||
`Identity` means that the value is converted directly into the corresponding FFI type.
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -0,0 +1,27 @@
|
||||
[package]
|
||||
name = "sp-runtime-interface-proc-macro"
|
||||
version = "17.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "This crate provides procedural macros for usage within the context of the Substrate runtime interface."
|
||||
documentation = "https://docs.rs/sp-runtime-interface-proc-macro"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
Inflector = { workspace = true }
|
||||
expander = { workspace = true }
|
||||
proc-macro-crate = { workspace = true }
|
||||
proc-macro2 = { workspace = true }
|
||||
quote = { workspace = true }
|
||||
syn = { features = ["extra-traits", "fold", "full", "visit"], workspace = true }
|
||||
@@ -0,0 +1,80 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! This crate provides procedural macros for usage within the context of the Substrate runtime
|
||||
//! interface.
|
||||
//!
|
||||
//! It provides the [`#[runtime_interface]`](attr.runtime_interface.html) attribute macro
|
||||
//! for generating the runtime interfaces.
|
||||
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
parse_macro_input, ItemTrait, Result, Token,
|
||||
};
|
||||
|
||||
mod runtime_interface;
|
||||
mod utils;
|
||||
|
||||
struct Options {
|
||||
wasm_only: bool,
|
||||
tracing: bool,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
fn unpack(self) -> (bool, bool) {
|
||||
(self.wasm_only, self.tracing)
|
||||
}
|
||||
}
|
||||
impl Default for Options {
|
||||
fn default() -> Self {
|
||||
Options { wasm_only: false, tracing: true }
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for Options {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let mut res = Self::default();
|
||||
while !input.is_empty() {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(runtime_interface::keywords::wasm_only) {
|
||||
let _ = input.parse::<runtime_interface::keywords::wasm_only>();
|
||||
res.wasm_only = true;
|
||||
} else if lookahead.peek(runtime_interface::keywords::no_tracing) {
|
||||
let _ = input.parse::<runtime_interface::keywords::no_tracing>();
|
||||
res.tracing = false;
|
||||
} else if lookahead.peek(Token![,]) {
|
||||
let _ = input.parse::<Token![,]>();
|
||||
} else {
|
||||
return Err(lookahead.error());
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn runtime_interface(
|
||||
attrs: proc_macro::TokenStream,
|
||||
input: proc_macro::TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
let trait_def = parse_macro_input!(input as ItemTrait);
|
||||
let (wasm_only, tracing) = parse_macro_input!(attrs as Options).unpack();
|
||||
|
||||
runtime_interface::runtime_interface_impl(trait_def, wasm_only, tracing)
|
||||
.unwrap_or_else(|e| e.to_compile_error())
|
||||
.into()
|
||||
}
|
||||
+269
@@ -0,0 +1,269 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! Generates the bare function interface for a given trait definition.
|
||||
//!
|
||||
//! The bare functions are the ones that will be called by the user. On the native/host side, these
|
||||
//! functions directly execute the provided implementation. On the wasm side, these
|
||||
//! functions will prepare the parameters for the FFI boundary, call the external host function
|
||||
//! exported into wasm and convert back the result.
|
||||
//!
|
||||
//! [`generate`] is the entry point for generating for each
|
||||
//! trait method one bare function.
|
||||
//!
|
||||
//! [`function_for_method`] generates the bare
|
||||
//! function per trait method. Each bare function contains both implementations. The implementations
|
||||
//! are feature-gated, so that one is compiled for the native and the other for the wasm side.
|
||||
|
||||
use crate::utils::{
|
||||
create_exchangeable_host_function_ident, create_function_ident_with_version,
|
||||
generate_crate_access, get_function_argument_names, get_function_arguments,
|
||||
get_runtime_interface, host_inner_return_ty, pat_ty_to_host_inner, RuntimeInterfaceFunction,
|
||||
};
|
||||
|
||||
use syn::{
|
||||
parse_quote, spanned::Spanned, FnArg, Ident, ItemTrait, Result, Signature, Token, TraitItemFn,
|
||||
};
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
|
||||
use quote::{quote, quote_spanned};
|
||||
|
||||
use std::iter;
|
||||
|
||||
/// Generate one bare function per trait method. The name of the bare function is equal to the name
|
||||
/// of the trait method.
|
||||
pub fn generate(trait_def: &ItemTrait, is_wasm_only: bool, tracing: bool) -> Result<TokenStream> {
|
||||
let trait_name = &trait_def.ident;
|
||||
let runtime_interface = get_runtime_interface(trait_def)?;
|
||||
|
||||
// latest version dispatch
|
||||
let token_stream: Result<TokenStream> = runtime_interface.latest_versions_to_call().try_fold(
|
||||
TokenStream::new(),
|
||||
|mut t, (latest_version, method)| {
|
||||
t.extend(function_for_method(method, latest_version, is_wasm_only)?);
|
||||
Ok(t)
|
||||
},
|
||||
);
|
||||
|
||||
// earlier versions compatibility dispatch (only std variant)
|
||||
let result: Result<TokenStream> =
|
||||
runtime_interface
|
||||
.all_versions()
|
||||
.try_fold(token_stream?, |mut t, (version, method)| {
|
||||
t.extend(function_std_impl(trait_name, method, version, is_wasm_only, tracing)?);
|
||||
Ok(t)
|
||||
});
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Generates the bare function implementation for the given method for the host and wasm side.
|
||||
fn function_for_method(
|
||||
method: &RuntimeInterfaceFunction,
|
||||
latest_version: u32,
|
||||
is_wasm_only: bool,
|
||||
) -> Result<TokenStream> {
|
||||
let std_impl =
|
||||
if !is_wasm_only { function_std_latest_impl(method, latest_version)? } else { quote!() };
|
||||
|
||||
let no_std_impl = function_no_std_impl(method, is_wasm_only)?;
|
||||
|
||||
Ok(quote! {
|
||||
#std_impl
|
||||
|
||||
#no_std_impl
|
||||
})
|
||||
}
|
||||
|
||||
/// Generates the bare function implementation for `cfg(substrate_runtime)`.
|
||||
fn function_no_std_impl(
|
||||
method: &RuntimeInterfaceFunction,
|
||||
is_wasm_only: bool,
|
||||
) -> Result<TokenStream> {
|
||||
let should_trap_on_return = method.should_trap_on_return();
|
||||
let mut method = (*method).clone();
|
||||
crate::utils::unpack_inner_types_in_signature(&mut method.sig);
|
||||
|
||||
let function_name = &method.sig.ident;
|
||||
let host_function_name = create_exchangeable_host_function_ident(&method.sig.ident);
|
||||
let args = get_function_arguments(&method.sig);
|
||||
let arg_names = get_function_argument_names(&method.sig);
|
||||
let return_value = if should_trap_on_return {
|
||||
syn::ReturnType::Type(
|
||||
<Token![->]>::default(),
|
||||
Box::new(syn::TypeNever { bang_token: <Token![!]>::default() }.into()),
|
||||
)
|
||||
} else {
|
||||
method.sig.output.clone()
|
||||
};
|
||||
let maybe_unreachable = if should_trap_on_return {
|
||||
quote! {
|
||||
;
|
||||
#[cfg(target_family = "wasm")]
|
||||
{ core::arch::wasm32::unreachable(); }
|
||||
|
||||
#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
|
||||
unsafe { core::arch::asm!("unimp", options(noreturn)); }
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
let attrs = method.attrs.iter().filter(|a| !a.path().is_ident("version"));
|
||||
|
||||
let cfg_wasm_only = if is_wasm_only {
|
||||
quote! { #[cfg(substrate_runtime)] }
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
#cfg_wasm_only
|
||||
#[cfg(substrate_runtime)]
|
||||
#( #attrs )*
|
||||
pub fn #function_name( #( #args, )* ) #return_value {
|
||||
// Call the host function
|
||||
#host_function_name.get()( #( #arg_names, )* )
|
||||
#maybe_unreachable
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate call to latest function version for `cfg(not(substrate_runtime))`
|
||||
///
|
||||
/// This should generate simple `fn func(..) { func_version_<latest_version>(..) }`.
|
||||
fn function_std_latest_impl(method: &TraitItemFn, latest_version: u32) -> Result<TokenStream> {
|
||||
let function_name = &method.sig.ident;
|
||||
let args = get_function_arguments(&method.sig).map(pat_ty_to_host_inner).map(FnArg::Typed);
|
||||
let arg_names = get_function_argument_names(&method.sig).collect::<Vec<_>>();
|
||||
let return_value = host_inner_return_ty(&method.sig.output);
|
||||
let attrs = method.attrs.iter().filter(|a| !a.path().is_ident("version"));
|
||||
let latest_function_name =
|
||||
create_function_ident_with_version(&method.sig.ident, latest_version);
|
||||
|
||||
Ok(quote_spanned! { method.span() =>
|
||||
#[cfg(not(substrate_runtime))]
|
||||
#( #attrs )*
|
||||
pub fn #function_name( #( #args, )* ) #return_value {
|
||||
#latest_function_name(
|
||||
#( #arg_names, )*
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Generates the bare function implementation for `cfg(not(substrate_runtime))`.
|
||||
fn function_std_impl(
|
||||
trait_name: &Ident,
|
||||
method: &TraitItemFn,
|
||||
version: u32,
|
||||
is_wasm_only: bool,
|
||||
tracing: bool,
|
||||
) -> Result<TokenStream> {
|
||||
let function_name = create_function_ident_with_version(&method.sig.ident, version);
|
||||
let function_name_str = function_name.to_string();
|
||||
|
||||
let crate_ = generate_crate_access();
|
||||
let args = get_function_arguments(&method.sig)
|
||||
.map(pat_ty_to_host_inner)
|
||||
.map(FnArg::Typed)
|
||||
.chain(
|
||||
// Add the function context as last parameter when this is a wasm only interface.
|
||||
iter::from_fn(|| {
|
||||
if is_wasm_only {
|
||||
Some(parse_quote!(
|
||||
mut __function_context__: &mut dyn #crate_::sp_wasm_interface::FunctionContext
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.take(1),
|
||||
);
|
||||
let return_value = host_inner_return_ty(&method.sig.output);
|
||||
let attrs = method.attrs.iter().filter(|a| !a.path().is_ident("version"));
|
||||
// Don't make the function public accessible when this is a wasm only interface.
|
||||
let call_to_trait = generate_call_to_trait(trait_name, method, version, is_wasm_only);
|
||||
let call_to_trait = if !tracing {
|
||||
call_to_trait
|
||||
} else {
|
||||
parse_quote!(
|
||||
#crate_::sp_tracing::within_span! { #crate_::sp_tracing::trace_span!(#function_name_str);
|
||||
#call_to_trait
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
Ok(quote_spanned! { method.span() =>
|
||||
#[cfg(not(substrate_runtime))]
|
||||
#( #attrs )*
|
||||
fn #function_name( #( #args, )* ) #return_value {
|
||||
#call_to_trait
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate the call to the interface trait.
|
||||
fn generate_call_to_trait(
|
||||
trait_name: &Ident,
|
||||
method: &TraitItemFn,
|
||||
version: u32,
|
||||
is_wasm_only: bool,
|
||||
) -> TokenStream {
|
||||
let crate_ = generate_crate_access();
|
||||
let method_name = create_function_ident_with_version(&method.sig.ident, version);
|
||||
let expect_msg =
|
||||
format!("`{}` called outside of an Externalities-provided environment.", method_name);
|
||||
let arg_names = get_function_argument_names(&method.sig);
|
||||
|
||||
if takes_self_argument(&method.sig) {
|
||||
let instance = if is_wasm_only {
|
||||
Ident::new("__function_context__", Span::call_site())
|
||||
} else {
|
||||
Ident::new("__externalities__", Span::call_site())
|
||||
};
|
||||
|
||||
let impl_ = quote!( #trait_name::#method_name(&mut #instance, #( #arg_names, )*) );
|
||||
|
||||
if is_wasm_only {
|
||||
quote_spanned! { method.span() => #impl_ }
|
||||
} else {
|
||||
quote_spanned! { method.span() =>
|
||||
#crate_::with_externalities(|mut #instance| #impl_).expect(#expect_msg)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The name of the trait the interface trait is implemented for
|
||||
let impl_trait_name = if is_wasm_only {
|
||||
quote!( #crate_::sp_wasm_interface::FunctionContext )
|
||||
} else {
|
||||
quote!( #crate_::Externalities )
|
||||
};
|
||||
|
||||
quote_spanned! { method.span() =>
|
||||
<&mut dyn #impl_trait_name as #trait_name>::#method_name(
|
||||
#( #arg_names, )*
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns if the given `Signature` takes a `self` argument.
|
||||
fn takes_self_argument(sig: &Signature) -> bool {
|
||||
matches!(sig.inputs.first(), Some(FnArg::Receiver(_)))
|
||||
}
|
||||
+480
@@ -0,0 +1,480 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! Generates the extern host functions and the implementation for these host functions.
|
||||
//!
|
||||
//! The extern host functions will be called by the bare function interface from the Wasm side.
|
||||
//! The implementation of these host functions will be called on the host side from the Wasm
|
||||
//! executor. These implementations call the bare function interface.
|
||||
|
||||
use crate::utils::{
|
||||
create_exchangeable_host_function_ident, create_function_ident_with_version,
|
||||
create_host_function_ident, generate_crate_access, get_function_argument_names,
|
||||
get_function_argument_names_and_types, get_function_argument_types, get_function_arguments,
|
||||
get_runtime_interface, RuntimeInterfaceFunction,
|
||||
};
|
||||
|
||||
use syn::{
|
||||
spanned::Spanned, Error, Ident, ItemTrait, Pat, Result, ReturnType, Signature, TraitItemFn,
|
||||
};
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
|
||||
use quote::quote;
|
||||
|
||||
use inflector::Inflector;
|
||||
|
||||
use std::iter::Iterator;
|
||||
|
||||
/// Generate the extern host functions for wasm and the `HostFunctions` struct that provides the
|
||||
/// implementations for the host functions on the host.
|
||||
pub fn generate(trait_def: &ItemTrait, is_wasm_only: bool) -> Result<TokenStream> {
|
||||
let trait_name = &trait_def.ident;
|
||||
let extern_host_function_impls = get_runtime_interface(trait_def)?
|
||||
.latest_versions_to_call()
|
||||
.try_fold(TokenStream::new(), |mut t, (version, method)| {
|
||||
t.extend(generate_extern_host_function(method, version, trait_name)?);
|
||||
Ok::<_, Error>(t)
|
||||
})?;
|
||||
let exchangeable_host_functions = get_runtime_interface(trait_def)?
|
||||
.latest_versions_to_call()
|
||||
.try_fold(TokenStream::new(), |mut t, (_, m)| {
|
||||
t.extend(generate_exchangeable_host_function(m)?);
|
||||
Ok::<_, Error>(t)
|
||||
})?;
|
||||
let host_functions_struct = generate_host_functions_struct(trait_def, is_wasm_only)?;
|
||||
|
||||
Ok(quote! {
|
||||
/// The implementations of the extern host functions. This special implementation module
|
||||
/// is required to change the extern host functions signature to
|
||||
/// `unsafe fn name(args) -> ret` to make the function implementations exchangeable.
|
||||
#[cfg(substrate_runtime)]
|
||||
mod extern_host_function_impls {
|
||||
use super::*;
|
||||
|
||||
#extern_host_function_impls
|
||||
}
|
||||
|
||||
#exchangeable_host_functions
|
||||
|
||||
#host_functions_struct
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate the extern host function for the given method.
|
||||
fn generate_extern_host_function(
|
||||
method: &TraitItemFn,
|
||||
version: u32,
|
||||
trait_name: &Ident,
|
||||
) -> Result<TokenStream> {
|
||||
let crate_ = generate_crate_access();
|
||||
|
||||
let mut unpacked_sig = method.sig.clone();
|
||||
crate::utils::unpack_inner_types_in_signature(&mut unpacked_sig);
|
||||
let unpacked_args = get_function_arguments(&unpacked_sig);
|
||||
let unpacked_return_value = &unpacked_sig.output;
|
||||
|
||||
let arg_types = get_function_argument_types(&method.sig);
|
||||
let arg_names = get_function_argument_names(&method.sig);
|
||||
let function = &method.sig.ident;
|
||||
let ext_function = create_host_function_ident(&method.sig.ident, version, trait_name);
|
||||
let doc_string = format!(
|
||||
" Default extern host function implementation for [`super::{}`].",
|
||||
method.sig.ident,
|
||||
);
|
||||
let return_value = &method.sig.output;
|
||||
let cfg_attrs = method.attrs.iter().filter(|a| a.path().is_ident("cfg"));
|
||||
|
||||
let ffi_return_value = match method.sig.output {
|
||||
ReturnType::Default => quote!(),
|
||||
ReturnType::Type(_, ref ty) => quote! {
|
||||
-> <#ty as #crate_::RIType>::FFIType
|
||||
},
|
||||
};
|
||||
|
||||
let convert_return_value = match return_value {
|
||||
ReturnType::Default => quote! { __runtime_interface_result_ },
|
||||
ReturnType::Type(_, ref ty) => quote! {
|
||||
<#ty as #crate_::wasm::FromFFIValue>::from_ffi_value(__runtime_interface_result_)
|
||||
},
|
||||
};
|
||||
|
||||
let mut call_into_ffi_value = Vec::new();
|
||||
let mut drop_args = Vec::new();
|
||||
let mut ffi_names = Vec::new();
|
||||
for (nth, arg) in get_function_arguments(&method.sig).enumerate() {
|
||||
let arg_name = &arg.pat;
|
||||
let arg_ty = &arg.ty;
|
||||
let ffi_name =
|
||||
Ident::new(&format!("__runtime_interface_ffi_value_{}_", nth), arg.pat.span());
|
||||
let destructor_name =
|
||||
Ident::new(&format!("__runtime_interface_arg_destructor_{}_", nth), arg.pat.span());
|
||||
|
||||
ffi_names.push(ffi_name.clone());
|
||||
|
||||
call_into_ffi_value.push(quote! {
|
||||
let mut #arg_name = #arg_name;
|
||||
let (#ffi_name, #destructor_name) = <#arg_ty as #crate_::wasm::IntoFFIValue>::into_ffi_value(&mut #arg_name);
|
||||
});
|
||||
|
||||
drop_args.push(quote! {
|
||||
#[allow(dropping_copy_types)]
|
||||
::core::mem::drop(#destructor_name);
|
||||
});
|
||||
}
|
||||
|
||||
// Drop in the reverse order to construction.
|
||||
drop_args.reverse();
|
||||
|
||||
Ok(quote! {
|
||||
#(#cfg_attrs)*
|
||||
#[doc = #doc_string]
|
||||
pub fn #function ( #( #unpacked_args ),* ) #unpacked_return_value {
|
||||
#[cfg_attr(any(target_arch = "riscv32", target_arch = "riscv64"), #crate_::polkavm::polkavm_import(abi = #crate_::polkavm::polkavm_abi))]
|
||||
extern "C" {
|
||||
pub fn #ext_function (
|
||||
#( #arg_names: <#arg_types as #crate_::RIType>::FFIType ),*
|
||||
) #ffi_return_value;
|
||||
}
|
||||
|
||||
#(#call_into_ffi_value)*
|
||||
let __runtime_interface_result_ = unsafe { #ext_function( #( #ffi_names ),* ) };
|
||||
#(#drop_args)*
|
||||
#convert_return_value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate the host exchangeable function for the given method.
|
||||
fn generate_exchangeable_host_function(method: &TraitItemFn) -> Result<TokenStream> {
|
||||
let mut method = method.clone();
|
||||
crate::utils::unpack_inner_types_in_signature(&mut method.sig);
|
||||
|
||||
let crate_ = generate_crate_access();
|
||||
let arg_types = get_function_argument_types(&method.sig);
|
||||
let function = &method.sig.ident;
|
||||
let exchangeable_function = create_exchangeable_host_function_ident(&method.sig.ident);
|
||||
let doc_string = format!(" Exchangeable host function used by [`{}`].", method.sig.ident);
|
||||
let output = &method.sig.output;
|
||||
let cfg_attrs = method.attrs.iter().filter(|a| a.path().is_ident("cfg"));
|
||||
|
||||
Ok(quote! {
|
||||
#(#cfg_attrs)*
|
||||
#[cfg(substrate_runtime)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[doc = #doc_string]
|
||||
pub static #exchangeable_function : #crate_::wasm::ExchangeableFunction<
|
||||
fn ( #( #arg_types ),* ) #output
|
||||
> = #crate_::wasm::ExchangeableFunction::new(extern_host_function_impls::#function);
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate the `HostFunctions` struct that implements `wasm-interface::HostFunctions` to provide
|
||||
/// implementations for the extern host functions.
|
||||
fn generate_host_functions_struct(
|
||||
trait_def: &ItemTrait,
|
||||
is_wasm_only: bool,
|
||||
) -> Result<TokenStream> {
|
||||
let crate_ = generate_crate_access();
|
||||
|
||||
let mut host_function_impls = Vec::new();
|
||||
let mut register_bodies = Vec::new();
|
||||
let mut append_hf_bodies = Vec::new();
|
||||
|
||||
for (version, method) in get_runtime_interface(trait_def)?.all_versions() {
|
||||
let (implementation, register_body, append_hf_body) =
|
||||
generate_host_function_implementation(&trait_def.ident, method, version, is_wasm_only)?;
|
||||
host_function_impls.push(implementation);
|
||||
register_bodies.push(register_body);
|
||||
append_hf_bodies.push(append_hf_body);
|
||||
}
|
||||
|
||||
Ok(quote! {
|
||||
#(#host_function_impls)*
|
||||
|
||||
/// Provides implementations for the extern host functions.
|
||||
#[cfg(not(substrate_runtime))]
|
||||
pub struct HostFunctions;
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl #crate_::sp_wasm_interface::HostFunctions for HostFunctions {
|
||||
fn host_functions() -> Vec<&'static dyn #crate_::sp_wasm_interface::Function> {
|
||||
let mut host_functions_list = Vec::new();
|
||||
#(#append_hf_bodies)*
|
||||
host_functions_list
|
||||
}
|
||||
|
||||
#crate_::sp_wasm_interface::if_wasmtime_is_enabled! {
|
||||
fn register_static<T>(registry: &mut T) -> core::result::Result<(), T::Error>
|
||||
where T: #crate_::sp_wasm_interface::HostFunctionRegistry
|
||||
{
|
||||
#(#register_bodies)*
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Generates the host function struct that implements `wasm_interface::Function` and returns a
|
||||
/// static reference to this struct.
|
||||
///
|
||||
/// When calling from wasm into the host, we will call the `execute` function that calls the native
|
||||
/// implementation of the function.
|
||||
fn generate_host_function_implementation(
|
||||
trait_name: &Ident,
|
||||
method: &RuntimeInterfaceFunction,
|
||||
version: u32,
|
||||
is_wasm_only: bool,
|
||||
) -> Result<(TokenStream, TokenStream, TokenStream)> {
|
||||
let name = create_host_function_ident(&method.sig.ident, version, trait_name).to_string();
|
||||
let struct_name = Ident::new(&name.to_pascal_case(), Span::call_site());
|
||||
let crate_ = generate_crate_access();
|
||||
let signature = generate_wasm_interface_signature_for_host_function(&method.sig)?;
|
||||
|
||||
let fn_name = create_function_ident_with_version(&method.sig.ident, version);
|
||||
|
||||
// List of variable names containing WASM FFI-compatible arguments.
|
||||
let mut ffi_names = Vec::new();
|
||||
|
||||
// List of `$name: $ty` tokens containing WASM FFI-compatible arguments.
|
||||
let mut ffi_args_prototype = Vec::new();
|
||||
|
||||
// List of variable names containing arguments already converted into native Rust types.
|
||||
// Also includes the preceding `&` or `&mut`. To be used to call the actual implementation of
|
||||
// the host function.
|
||||
let mut host_names_with_ref = Vec::new();
|
||||
|
||||
// List of code snippets to copy over the results returned from a host function through
|
||||
// any `&mut` arguments back into WASM's linear memory.
|
||||
let mut copy_data_into_ref_mut_args = Vec::new();
|
||||
|
||||
// List of code snippets to convert dynamic FFI args (`Value` enum) into concrete static FFI
|
||||
// types (`u32`, etc.).
|
||||
let mut convert_args_dynamic_ffi_to_static_ffi = Vec::new();
|
||||
|
||||
// List of code snippets to convert static FFI args (`u32`, etc.) into native Rust types.
|
||||
let mut convert_args_static_ffi_to_host = Vec::new();
|
||||
|
||||
for (host_name, host_ty) in get_function_argument_names_and_types(&method.sig) {
|
||||
let ffi_name = generate_ffi_value_var_name(&host_name)?;
|
||||
let host_name_ident = match *host_name {
|
||||
Pat::Ident(ref pat_ident) => pat_ident.ident.clone(),
|
||||
_ => unreachable!("`generate_ffi_value_var_name` above would return an error on `Pat` != `Ident`; qed"),
|
||||
};
|
||||
|
||||
let ffi_ty = quote! { <#host_ty as #crate_::RIType>::FFIType };
|
||||
ffi_args_prototype.push(quote! { #ffi_name: #ffi_ty });
|
||||
ffi_names.push(quote! { #ffi_name });
|
||||
|
||||
let convert_arg_error = format!(
|
||||
"could not marshal the '{}' argument through the WASM FFI boundary while executing '{}' from interface '{}'",
|
||||
host_name_ident,
|
||||
method.sig.ident,
|
||||
trait_name
|
||||
);
|
||||
convert_args_static_ffi_to_host.push(quote! {
|
||||
let mut #host_name = <#host_ty as #crate_::host::FromFFIValue>::from_ffi_value(__function_context__, #ffi_name)
|
||||
.map_err(|err| #crate_::alloc::format!("{}: {}", err, #convert_arg_error))?;
|
||||
});
|
||||
|
||||
host_names_with_ref.push(
|
||||
quote! { <#host_ty as #crate_::host::FromFFIValue>::take_from_owned(&mut #host_name) },
|
||||
);
|
||||
copy_data_into_ref_mut_args.push(quote! {
|
||||
<#host_ty as #crate_::host::FromFFIValue>::write_back_into_runtime(#host_name, __function_context__, #ffi_name)?;
|
||||
});
|
||||
|
||||
let arg_count_mismatch_error = format!(
|
||||
"missing argument '{}': number of arguments given to '{}' from interface '{}' does not match the expected number of arguments",
|
||||
host_name_ident,
|
||||
method.sig.ident,
|
||||
trait_name
|
||||
);
|
||||
convert_args_dynamic_ffi_to_static_ffi.push(quote! {
|
||||
let #ffi_name = args.next().ok_or_else(|| #crate_::alloc::borrow::ToOwned::to_owned(#arg_count_mismatch_error))?;
|
||||
let #ffi_name: #ffi_ty = #crate_::sp_wasm_interface::TryFromValue::try_from_value(#ffi_name)
|
||||
.ok_or_else(|| #crate_::alloc::borrow::ToOwned::to_owned(#convert_arg_error))?;
|
||||
});
|
||||
}
|
||||
|
||||
let ffi_return_ty = match &method.sig.output {
|
||||
ReturnType::Type(_, ty) => quote! { <#ty as #crate_::RIType>::FFIType },
|
||||
ReturnType::Default => quote! { () },
|
||||
};
|
||||
|
||||
let convert_return_value_host_to_static_ffi = match &method.sig.output {
|
||||
ReturnType::Type(_, ty) => quote! {
|
||||
let __result__ = <#ty as #crate_::host::IntoFFIValue>::into_ffi_value(
|
||||
__result__,
|
||||
__function_context__
|
||||
);
|
||||
},
|
||||
ReturnType::Default => quote! {
|
||||
let __result__ = Ok(__result__);
|
||||
},
|
||||
};
|
||||
|
||||
let convert_return_value_static_ffi_to_dynamic_ffi = match &method.sig.output {
|
||||
ReturnType::Type(_, _) => quote! {
|
||||
let __result__ = Ok(Some(#crate_::sp_wasm_interface::IntoValue::into_value(__result__)));
|
||||
},
|
||||
ReturnType::Default => quote! {
|
||||
let __result__ = Ok(None);
|
||||
},
|
||||
};
|
||||
|
||||
if is_wasm_only {
|
||||
host_names_with_ref.push(quote! {
|
||||
__function_context__
|
||||
});
|
||||
}
|
||||
|
||||
let cfg_attrs: Vec<_> =
|
||||
method.attrs.iter().filter(|a| a.path().is_ident("cfg")).cloned().collect();
|
||||
if version > 1 && !cfg_attrs.is_empty() {
|
||||
return Err(Error::new(
|
||||
method.span(),
|
||||
"Conditional compilation is not supported for versioned functions",
|
||||
));
|
||||
}
|
||||
|
||||
let implementation = quote! {
|
||||
#(#cfg_attrs)*
|
||||
#[cfg(not(substrate_runtime))]
|
||||
struct #struct_name;
|
||||
|
||||
#(#cfg_attrs)*
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl #struct_name {
|
||||
fn call(
|
||||
__function_context__: &mut dyn #crate_::sp_wasm_interface::FunctionContext,
|
||||
#(#ffi_args_prototype),*
|
||||
) -> ::core::result::Result<#ffi_return_ty, #crate_::alloc::string::String> {
|
||||
#(#convert_args_static_ffi_to_host)*
|
||||
let __result__ = #fn_name(#(#host_names_with_ref),*);
|
||||
#(#copy_data_into_ref_mut_args)*
|
||||
#convert_return_value_host_to_static_ffi
|
||||
__result__
|
||||
}
|
||||
}
|
||||
|
||||
#(#cfg_attrs)*
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl #crate_::sp_wasm_interface::Function for #struct_name {
|
||||
fn name(&self) -> &str {
|
||||
#name
|
||||
}
|
||||
|
||||
fn signature(&self) -> #crate_::sp_wasm_interface::Signature {
|
||||
#signature
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&self,
|
||||
__function_context__: &mut dyn #crate_::sp_wasm_interface::FunctionContext,
|
||||
args: &mut dyn Iterator<Item = #crate_::sp_wasm_interface::Value>,
|
||||
) -> ::core::result::Result<Option<#crate_::sp_wasm_interface::Value>, #crate_::alloc::string::String> {
|
||||
#(#convert_args_dynamic_ffi_to_static_ffi)*
|
||||
let __result__ = Self::call(
|
||||
__function_context__,
|
||||
#(#ffi_names),*
|
||||
)?;
|
||||
#convert_return_value_static_ffi_to_dynamic_ffi
|
||||
__result__
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let register_body = quote! {
|
||||
#(#cfg_attrs)*
|
||||
registry.register_static(
|
||||
#crate_::sp_wasm_interface::Function::name(&#struct_name),
|
||||
|mut caller: #crate_::sp_wasm_interface::wasmtime::Caller<T::State>, #(#ffi_args_prototype),*|
|
||||
-> ::core::result::Result<#ffi_return_ty, #crate_::sp_wasm_interface::anyhow::Error>
|
||||
{
|
||||
T::with_function_context(caller, move |__function_context__| {
|
||||
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
#struct_name::call(
|
||||
__function_context__,
|
||||
#(#ffi_names,)*
|
||||
).map_err(#crate_::sp_wasm_interface::anyhow::Error::msg)
|
||||
}));
|
||||
match result {
|
||||
Ok(result) => result,
|
||||
Err(panic) => {
|
||||
let message =
|
||||
if let Some(message) = panic.downcast_ref::<#crate_::alloc::string::String>() {
|
||||
#crate_::alloc::format!("host code panicked while being called by the runtime: {}", message)
|
||||
} else if let Some(message) = panic.downcast_ref::<&'static str>() {
|
||||
#crate_::alloc::format!("host code panicked while being called by the runtime: {}", message)
|
||||
} else {
|
||||
#crate_::alloc::borrow::ToOwned::to_owned("host code panicked while being called by the runtime")
|
||||
};
|
||||
return Err(#crate_::sp_wasm_interface::anyhow::Error::msg(message));
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
)?;
|
||||
};
|
||||
|
||||
let append_hf_body = quote! {
|
||||
#(#cfg_attrs)*
|
||||
host_functions_list.push(&#struct_name as &dyn #crate_::sp_wasm_interface::Function);
|
||||
};
|
||||
|
||||
Ok((implementation, register_body, append_hf_body))
|
||||
}
|
||||
|
||||
/// Generate the `wasm_interface::Signature` for the given host function `sig`.
|
||||
fn generate_wasm_interface_signature_for_host_function(sig: &Signature) -> Result<TokenStream> {
|
||||
let crate_ = generate_crate_access();
|
||||
let return_value = match &sig.output {
|
||||
ReturnType::Type(_, ty) => quote! {
|
||||
Some( <<#ty as #crate_::RIType>::FFIType as #crate_::sp_wasm_interface::IntoValue>::VALUE_TYPE )
|
||||
},
|
||||
ReturnType::Default => quote!(None),
|
||||
};
|
||||
let arg_types = get_function_argument_types(sig).map(|ty| {
|
||||
quote! {
|
||||
<<#ty as #crate_::RIType>::FFIType as #crate_::sp_wasm_interface::IntoValue>::VALUE_TYPE
|
||||
}
|
||||
});
|
||||
|
||||
Ok(quote! {
|
||||
#crate_::sp_wasm_interface::Signature {
|
||||
args: #crate_::alloc::borrow::Cow::Borrowed(&[ #( #arg_types ),* ][..]),
|
||||
return_value: #return_value,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate the variable name that stores the FFI value.
|
||||
fn generate_ffi_value_var_name(pat: &Pat) -> Result<Ident> {
|
||||
match pat {
|
||||
Pat::Ident(pat_ident) =>
|
||||
if let Some(by_ref) = pat_ident.by_ref {
|
||||
Err(Error::new(by_ref.span(), "`ref` not supported!"))
|
||||
} else if let Some(sub_pattern) = &pat_ident.subpat {
|
||||
Err(Error::new(sub_pattern.0.span(), "Not supported!"))
|
||||
} else {
|
||||
Ok(Ident::new(&format!("{}_ffi_value", pat_ident.ident), Span::call_site()))
|
||||
},
|
||||
_ => Err(Error::new(pat.span(), "Not supported as variable name!")),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 crate::utils::generate_runtime_interface_include;
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
|
||||
use syn::{Ident, ItemTrait, Result};
|
||||
|
||||
use inflector::Inflector;
|
||||
|
||||
use quote::quote;
|
||||
|
||||
mod bare_function_interface;
|
||||
mod host_function_interface;
|
||||
mod trait_decl_impl;
|
||||
|
||||
/// Custom keywords supported by the `runtime_interface` attribute.
|
||||
pub mod keywords {
|
||||
// Custom keyword `wasm_only` that can be given as attribute to [`runtime_interface`].
|
||||
syn::custom_keyword!(wasm_only);
|
||||
// Disable tracing-macros added to the [`runtime_interface`] by specifying this optional entry
|
||||
syn::custom_keyword!(no_tracing);
|
||||
}
|
||||
|
||||
/// Implementation of the `runtime_interface` attribute.
|
||||
///
|
||||
/// It expects the trait definition the attribute was put above and if this should be an wasm only
|
||||
/// interface.
|
||||
pub fn runtime_interface_impl(
|
||||
trait_def: ItemTrait,
|
||||
is_wasm_only: bool,
|
||||
tracing: bool,
|
||||
) -> Result<TokenStream> {
|
||||
let bare_functions = bare_function_interface::generate(&trait_def, is_wasm_only, tracing)?;
|
||||
let crate_include = generate_runtime_interface_include();
|
||||
let mod_name = Ident::new(&trait_def.ident.to_string().to_snake_case(), Span::call_site());
|
||||
let trait_decl_impl = trait_decl_impl::process(&trait_def, is_wasm_only)?;
|
||||
let host_functions = host_function_interface::generate(&trait_def, is_wasm_only)?;
|
||||
let vis = trait_def.vis;
|
||||
let attrs = &trait_def.attrs;
|
||||
|
||||
let res = quote! {
|
||||
#( #attrs )*
|
||||
#vis mod #mod_name {
|
||||
use super::*;
|
||||
#crate_include
|
||||
|
||||
#bare_functions
|
||||
|
||||
#trait_decl_impl
|
||||
|
||||
#host_functions
|
||||
}
|
||||
};
|
||||
|
||||
let res = expander::Expander::new("runtime_interface")
|
||||
.dry(std::env::var("EXPAND_MACROS").is_err())
|
||||
.verbose(true)
|
||||
.write_to_out_dir(res)
|
||||
.expect("Does not fail because of IO in OUT_DIR; qed");
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! Checks the trait declaration, makes the trait declaration module local, removes all method
|
||||
//! default implementations and implements the trait for `&mut dyn Externalities`.
|
||||
|
||||
use crate::utils::{
|
||||
create_function_ident_with_version, generate_crate_access, get_function_argument_types,
|
||||
get_runtime_interface,
|
||||
};
|
||||
|
||||
use syn::{
|
||||
fold::{self, Fold},
|
||||
spanned::Spanned,
|
||||
Error, Generics, ItemTrait, Receiver, Result, TraitItemFn, Type, Visibility,
|
||||
};
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
|
||||
use quote::quote;
|
||||
|
||||
/// Process the given trait definition, by checking that the definition is valid, fold it to the
|
||||
/// essential definition and implement this essential definition for `dyn Externalities`.
|
||||
pub fn process(trait_def: &ItemTrait, is_wasm_only: bool) -> Result<TokenStream> {
|
||||
let impl_trait = impl_trait_for_externalities(trait_def, is_wasm_only)?;
|
||||
let essential_trait_def = declare_essential_trait(trait_def)?;
|
||||
|
||||
Ok(quote! {
|
||||
#impl_trait
|
||||
|
||||
#essential_trait_def
|
||||
})
|
||||
}
|
||||
|
||||
/// Converts the given trait definition into the essential trait definition without method
|
||||
/// default implementations and visibility set to inherited.
|
||||
struct ToEssentialTraitDef {
|
||||
/// All errors found while doing the conversion.
|
||||
errors: Vec<Error>,
|
||||
methods: Vec<TraitItemFn>,
|
||||
}
|
||||
|
||||
impl ToEssentialTraitDef {
|
||||
fn new() -> Self {
|
||||
ToEssentialTraitDef { errors: vec![], methods: vec![] }
|
||||
}
|
||||
|
||||
fn into_methods(self) -> Result<Vec<TraitItemFn>> {
|
||||
let mut errors = self.errors;
|
||||
let methods = self.methods;
|
||||
if let Some(first_error) = errors.pop() {
|
||||
Err(errors.into_iter().fold(first_error, |mut o, n| {
|
||||
o.combine(n);
|
||||
o
|
||||
}))
|
||||
} else {
|
||||
Ok(methods)
|
||||
}
|
||||
}
|
||||
|
||||
fn process(&mut self, method: &TraitItemFn, version: u32) {
|
||||
let mut folded = self.fold_trait_item_fn(method.clone());
|
||||
folded.sig.ident = create_function_ident_with_version(&folded.sig.ident, version);
|
||||
crate::utils::unpack_inner_types_in_signature(&mut folded.sig);
|
||||
self.methods.push(folded);
|
||||
}
|
||||
|
||||
fn push_error<S: Spanned>(&mut self, span: &S, msg: &str) {
|
||||
self.errors.push(Error::new(span.span(), msg));
|
||||
}
|
||||
|
||||
fn error_on_generic_parameters(&mut self, generics: &Generics) {
|
||||
if let Some(param) = generics.params.first() {
|
||||
self.push_error(param, "Generic parameters not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Fold for ToEssentialTraitDef {
|
||||
fn fold_trait_item_fn(&mut self, mut method: TraitItemFn) -> TraitItemFn {
|
||||
if method.default.take().is_none() {
|
||||
self.push_error(&method, "Methods need to have an implementation.");
|
||||
}
|
||||
|
||||
let arg_types = get_function_argument_types(&method.sig);
|
||||
arg_types
|
||||
.filter_map(|ty| match *ty {
|
||||
Type::ImplTrait(impl_trait) => Some(impl_trait),
|
||||
_ => None,
|
||||
})
|
||||
.for_each(|invalid| self.push_error(&invalid, "`impl Trait` syntax not supported."));
|
||||
|
||||
self.error_on_generic_parameters(&method.sig.generics);
|
||||
|
||||
method.attrs.retain(|a| !a.path().is_ident("version"));
|
||||
|
||||
fold::fold_trait_item_fn(self, method)
|
||||
}
|
||||
|
||||
fn fold_item_trait(&mut self, mut trait_def: ItemTrait) -> ItemTrait {
|
||||
self.error_on_generic_parameters(&trait_def.generics);
|
||||
|
||||
trait_def.vis = Visibility::Inherited;
|
||||
fold::fold_item_trait(self, trait_def)
|
||||
}
|
||||
|
||||
fn fold_receiver(&mut self, receiver: Receiver) -> Receiver {
|
||||
if receiver.reference.is_none() {
|
||||
self.push_error(&receiver, "Taking `Self` by value is not allowed.");
|
||||
}
|
||||
|
||||
fold::fold_receiver(self, receiver)
|
||||
}
|
||||
}
|
||||
|
||||
fn declare_essential_trait(trait_def: &ItemTrait) -> Result<TokenStream> {
|
||||
let trait_ = &trait_def.ident;
|
||||
|
||||
if let Some(param) = trait_def.generics.params.first() {
|
||||
return Err(Error::new(param.span(), "Generic parameters not supported."));
|
||||
}
|
||||
|
||||
let interface = get_runtime_interface(trait_def)?;
|
||||
let mut folder = ToEssentialTraitDef::new();
|
||||
for (version, interface_method) in interface.all_versions() {
|
||||
folder.process(interface_method, version);
|
||||
}
|
||||
let methods = folder.into_methods()?;
|
||||
|
||||
Ok(quote! {
|
||||
trait #trait_ {
|
||||
#( #methods )*
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Implements the given trait definition for `dyn Externalities`.
|
||||
fn impl_trait_for_externalities(trait_def: &ItemTrait, is_wasm_only: bool) -> Result<TokenStream> {
|
||||
let trait_ = &trait_def.ident;
|
||||
let crate_ = generate_crate_access();
|
||||
let interface = get_runtime_interface(trait_def)?;
|
||||
let methods = interface.all_versions().map(|(version, method)| {
|
||||
let mut cloned = (*method).clone();
|
||||
cloned.attrs.retain(|a| !a.path().is_ident("version"));
|
||||
cloned.sig.ident = create_function_ident_with_version(&cloned.sig.ident, version);
|
||||
crate::utils::unpack_inner_types_in_signature(&mut cloned.sig);
|
||||
cloned
|
||||
});
|
||||
|
||||
let impl_type = if is_wasm_only {
|
||||
quote!( &mut dyn #crate_::sp_wasm_interface::FunctionContext )
|
||||
} else {
|
||||
quote!( &mut dyn #crate_::Externalities )
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl #trait_ for #impl_type {
|
||||
#( #methods )*
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! Util function used by this crate.
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
|
||||
use syn::{
|
||||
parse::Parse, parse_quote, spanned::Spanned, token, Error, FnArg, Ident, ItemTrait, LitInt,
|
||||
Pat, PatType, Result, Signature, TraitItem, TraitItemFn, Type,
|
||||
};
|
||||
|
||||
use proc_macro_crate::{crate_name, FoundCrate};
|
||||
|
||||
use std::{
|
||||
collections::{btree_map::Entry, BTreeMap},
|
||||
env,
|
||||
};
|
||||
|
||||
use quote::quote;
|
||||
|
||||
use inflector::Inflector;
|
||||
|
||||
mod attributes {
|
||||
syn::custom_keyword!(register_only);
|
||||
}
|
||||
|
||||
/// A concrete, specific version of a runtime interface function.
|
||||
pub struct RuntimeInterfaceFunction {
|
||||
item: TraitItemFn,
|
||||
should_trap_on_return: bool,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for RuntimeInterfaceFunction {
|
||||
type Target = TraitItemFn;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.item
|
||||
}
|
||||
}
|
||||
|
||||
impl RuntimeInterfaceFunction {
|
||||
fn new(item: &TraitItemFn) -> Result<Self> {
|
||||
let mut item = item.clone();
|
||||
let mut should_trap_on_return = false;
|
||||
item.attrs.retain(|attr| {
|
||||
if attr.path().is_ident("trap_on_return") {
|
||||
should_trap_on_return = true;
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
if should_trap_on_return && !matches!(item.sig.output, syn::ReturnType::Default) {
|
||||
return Err(Error::new(
|
||||
item.sig.ident.span(),
|
||||
"Methods marked as #[trap_on_return] cannot return anything",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Self { item, should_trap_on_return })
|
||||
}
|
||||
|
||||
pub fn should_trap_on_return(&self) -> bool {
|
||||
self.should_trap_on_return
|
||||
}
|
||||
}
|
||||
|
||||
/// Runtime interface function with all associated versions of this function.
|
||||
struct RuntimeInterfaceFunctionSet {
|
||||
latest_version_to_call: Option<u32>,
|
||||
versions: BTreeMap<u32, RuntimeInterfaceFunction>,
|
||||
}
|
||||
|
||||
impl RuntimeInterfaceFunctionSet {
|
||||
fn new(version: VersionAttribute, trait_item: &TraitItemFn) -> Result<Self> {
|
||||
Ok(Self {
|
||||
latest_version_to_call: version.is_callable().then_some(version.version),
|
||||
versions: BTreeMap::from([(
|
||||
version.version,
|
||||
RuntimeInterfaceFunction::new(trait_item)?,
|
||||
)]),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the latest version of this runtime interface function plus the actual function
|
||||
/// implementation.
|
||||
///
|
||||
/// This isn't required to be the latest version, because a runtime interface function can be
|
||||
/// annotated with `register_only` to ensure that the host exposes the host function but it
|
||||
/// isn't used when compiling the runtime.
|
||||
pub fn latest_version_to_call(&self) -> Option<(u32, &RuntimeInterfaceFunction)> {
|
||||
self.latest_version_to_call.map(|v| {
|
||||
(
|
||||
v,
|
||||
self.versions.get(&v).expect(
|
||||
"If latest_version_to_call has a value, the key with this value is in the versions; qed",
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Add a different version of the function.
|
||||
fn add_version(&mut self, version: VersionAttribute, trait_item: &TraitItemFn) -> Result<()> {
|
||||
if let Some(existing_item) = self.versions.get(&version.version) {
|
||||
let mut err = Error::new(trait_item.span(), "Duplicated version attribute");
|
||||
err.combine(Error::new(
|
||||
existing_item.span(),
|
||||
"Previous version with the same number defined here",
|
||||
));
|
||||
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
self.versions
|
||||
.insert(version.version, RuntimeInterfaceFunction::new(trait_item)?);
|
||||
if self.latest_version_to_call.map_or(true, |v| v < version.version) &&
|
||||
version.is_callable()
|
||||
{
|
||||
self.latest_version_to_call = Some(version.version);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// All functions of a runtime interface grouped by the function names.
|
||||
pub struct RuntimeInterface {
|
||||
items: BTreeMap<syn::Ident, RuntimeInterfaceFunctionSet>,
|
||||
}
|
||||
|
||||
impl RuntimeInterface {
|
||||
/// Returns an iterator over all runtime interface function
|
||||
/// [`latest_version_to_call`](RuntimeInterfaceFunctionSet::latest_version).
|
||||
pub fn latest_versions_to_call(
|
||||
&self,
|
||||
) -> impl Iterator<Item = (u32, &RuntimeInterfaceFunction)> {
|
||||
self.items.iter().filter_map(|(_, item)| item.latest_version_to_call())
|
||||
}
|
||||
|
||||
pub fn all_versions(&self) -> impl Iterator<Item = (u32, &RuntimeInterfaceFunction)> {
|
||||
self.items
|
||||
.iter()
|
||||
.flat_map(|(_, item)| item.versions.iter())
|
||||
.map(|(v, i)| (*v, i))
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the include for the runtime-interface crate.
|
||||
pub fn generate_runtime_interface_include() -> TokenStream {
|
||||
match crate_name("sp-runtime-interface") {
|
||||
Ok(FoundCrate::Itself) => quote!(),
|
||||
Ok(FoundCrate::Name(crate_name)) => {
|
||||
let crate_name = Ident::new(&crate_name, Span::call_site());
|
||||
quote!(
|
||||
#[doc(hidden)]
|
||||
extern crate #crate_name as proc_macro_runtime_interface;
|
||||
)
|
||||
},
|
||||
Err(e) => {
|
||||
let err = Error::new(Span::call_site(), e).to_compile_error();
|
||||
quote!( #err )
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the access to the `sp-runtime-interface` crate.
|
||||
pub fn generate_crate_access() -> TokenStream {
|
||||
if env::var("CARGO_PKG_NAME").unwrap() == "sp-runtime-interface" {
|
||||
quote!(sp_runtime_interface)
|
||||
} else {
|
||||
quote!(proc_macro_runtime_interface)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create the exchangeable host function identifier for the given function name.
|
||||
pub fn create_exchangeable_host_function_ident(name: &Ident) -> Ident {
|
||||
Ident::new(&format!("host_{}", name), Span::call_site())
|
||||
}
|
||||
|
||||
/// Create the host function identifier for the given function name.
|
||||
pub fn create_host_function_ident(name: &Ident, version: u32, trait_name: &Ident) -> Ident {
|
||||
Ident::new(
|
||||
&format!("ext_{}_{}_version_{}", trait_name.to_string().to_snake_case(), name, version),
|
||||
Span::call_site(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Create the host function identifier for the given function name.
|
||||
pub fn create_function_ident_with_version(name: &Ident, version: u32) -> Ident {
|
||||
Ident::new(&format!("{}_version_{}", name, version), Span::call_site())
|
||||
}
|
||||
|
||||
/// Returns the function arguments of the given `Signature`, minus any `self` arguments.
|
||||
pub fn get_function_arguments(sig: &Signature) -> impl Iterator<Item = PatType> + '_ {
|
||||
sig.inputs
|
||||
.iter()
|
||||
.filter_map(|a| match a {
|
||||
FnArg::Receiver(_) => None,
|
||||
FnArg::Typed(pat_type) => Some(pat_type),
|
||||
})
|
||||
.enumerate()
|
||||
.map(|(i, arg)| {
|
||||
let mut res = arg.clone();
|
||||
if let Pat::Wild(wild) = &*arg.pat {
|
||||
let ident =
|
||||
Ident::new(&format!("__runtime_interface_generated_{}_", i), wild.span());
|
||||
|
||||
res.pat = Box::new(parse_quote!( #ident ))
|
||||
}
|
||||
|
||||
res
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the function argument names of the given `Signature`, minus any `self`.
|
||||
pub fn get_function_argument_names(sig: &Signature) -> impl Iterator<Item = Box<Pat>> + '_ {
|
||||
get_function_arguments(sig).map(|pt| pt.pat)
|
||||
}
|
||||
|
||||
/// Returns the function argument types of the given `Signature`, minus any `Self` type.
|
||||
pub fn get_function_argument_types(sig: &Signature) -> impl Iterator<Item = Box<Type>> + '_ {
|
||||
get_function_arguments(sig).map(|pt| pt.ty)
|
||||
}
|
||||
|
||||
/// Returns the function argument names and types, minus any `self`.
|
||||
pub fn get_function_argument_names_and_types(
|
||||
sig: &Signature,
|
||||
) -> impl Iterator<Item = (Box<Pat>, Box<Type>)> + '_ {
|
||||
get_function_arguments(sig).map(|pt| (pt.pat, pt.ty))
|
||||
}
|
||||
|
||||
/// Returns an iterator over all trait methods for the given trait definition.
|
||||
fn get_trait_methods(trait_def: &ItemTrait) -> impl Iterator<Item = &TraitItemFn> {
|
||||
trait_def.items.iter().filter_map(|i| match i {
|
||||
TraitItem::Fn(ref method) => Some(method),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// The version attribute that can be found above a runtime interface function.
|
||||
///
|
||||
/// Supports the following formats:
|
||||
/// - `#[version(1)]`
|
||||
/// - `#[version(1, register_only)]`
|
||||
///
|
||||
/// While this struct is only for parsing the inner parts inside the `()`.
|
||||
struct VersionAttribute {
|
||||
version: u32,
|
||||
register_only: Option<attributes::register_only>,
|
||||
}
|
||||
|
||||
impl VersionAttribute {
|
||||
/// Is this function version callable?
|
||||
fn is_callable(&self) -> bool {
|
||||
self.register_only.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VersionAttribute {
|
||||
fn default() -> Self {
|
||||
Self { version: 1, register_only: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for VersionAttribute {
|
||||
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
|
||||
let version: LitInt = input.parse()?;
|
||||
let register_only = if input.peek(token::Comma) {
|
||||
let _ = input.parse::<token::Comma>();
|
||||
Some(input.parse()?)
|
||||
} else {
|
||||
if !input.is_empty() {
|
||||
return Err(Error::new(input.span(), "Unexpected token, expected `,`."));
|
||||
}
|
||||
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Self { version: version.base10_parse()?, register_only })
|
||||
}
|
||||
}
|
||||
|
||||
/// Return [`VersionAttribute`], if present.
|
||||
fn get_item_version(item: &TraitItemFn) -> Result<Option<VersionAttribute>> {
|
||||
item.attrs
|
||||
.iter()
|
||||
.find(|attr| attr.path().is_ident("version"))
|
||||
.map(|attr| attr.parse_args())
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// Returns all runtime interface members, with versions.
|
||||
pub fn get_runtime_interface(trait_def: &ItemTrait) -> Result<RuntimeInterface> {
|
||||
let mut functions: BTreeMap<syn::Ident, RuntimeInterfaceFunctionSet> = BTreeMap::new();
|
||||
|
||||
for item in get_trait_methods(trait_def) {
|
||||
let name = item.sig.ident.clone();
|
||||
let version = get_item_version(item)?.unwrap_or_default();
|
||||
|
||||
if version.version < 1 {
|
||||
return Err(Error::new(item.span(), "Version needs to be at least `1`."));
|
||||
}
|
||||
|
||||
match functions.entry(name.clone()) {
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(RuntimeInterfaceFunctionSet::new(version, item)?);
|
||||
},
|
||||
Entry::Occupied(mut entry) => {
|
||||
entry.get_mut().add_version(version, item)?;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
for function in functions.values() {
|
||||
let mut next_expected = 1;
|
||||
for (version, item) in function.versions.iter() {
|
||||
if next_expected != *version {
|
||||
return Err(Error::new(
|
||||
item.span(),
|
||||
format!(
|
||||
"Unexpected version attribute: missing version '{}' for this function",
|
||||
next_expected
|
||||
),
|
||||
));
|
||||
}
|
||||
next_expected += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(RuntimeInterface { items: functions })
|
||||
}
|
||||
|
||||
pub fn host_inner_arg_ty(ty: &syn::Type) -> syn::Type {
|
||||
let crate_ = generate_crate_access();
|
||||
syn::parse2::<syn::Type>(quote! { <#ty as #crate_::RIType>::Inner })
|
||||
.expect("parsing doesn't fail")
|
||||
}
|
||||
|
||||
pub fn pat_ty_to_host_inner(mut pat: syn::PatType) -> syn::PatType {
|
||||
pat.ty = Box::new(host_inner_arg_ty(&pat.ty));
|
||||
pat
|
||||
}
|
||||
|
||||
pub fn host_inner_return_ty(ty: &syn::ReturnType) -> syn::ReturnType {
|
||||
let crate_ = generate_crate_access();
|
||||
match ty {
|
||||
syn::ReturnType::Default => syn::ReturnType::Default,
|
||||
syn::ReturnType::Type(ref arrow, ref ty) =>
|
||||
syn::parse2::<syn::ReturnType>(quote! { #arrow <#ty as #crate_::RIType>::Inner })
|
||||
.expect("parsing doesn't fail"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unpack_inner_types_in_signature(sig: &mut syn::Signature) {
|
||||
sig.output = crate::utils::host_inner_return_ty(&sig.output);
|
||||
for arg in sig.inputs.iter_mut() {
|
||||
match arg {
|
||||
syn::FnArg::Typed(ref mut pat_ty) => {
|
||||
*pat_ty = crate::utils::pat_ty_to_host_inner(pat_ty.clone());
|
||||
},
|
||||
syn::FnArg::Receiver(..) => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! Traits required by the runtime interface from the host side.
|
||||
|
||||
use crate::RIType;
|
||||
|
||||
use sp_wasm_interface::{FunctionContext, Result};
|
||||
|
||||
/// A type used as a return value in a host function. Can be turned into an FFI value.
|
||||
pub trait IntoFFIValue: RIType {
|
||||
/// Convert `Self::Inner` into an FFI value.
|
||||
fn into_ffi_value(
|
||||
value: Self::Inner,
|
||||
context: &mut dyn FunctionContext,
|
||||
) -> Result<Self::FFIType>;
|
||||
}
|
||||
|
||||
/// A type used as a parameter in a host function. Can be created from an FFI value.
|
||||
///
|
||||
/// Implementations are safe to assume that the `arg` given to `from_ffi_value`
|
||||
/// is only generated by the corresponding [`wasm::IntoFFIValue`](crate::wasm::IntoFFIValue)
|
||||
/// implementation.
|
||||
pub trait FromFFIValue<'a>: RIType {
|
||||
/// The owned inner type.
|
||||
type Owned;
|
||||
|
||||
/// Creates `Self::Owned` from the given `arg` received through the FFI boundary from the
|
||||
/// runtime.
|
||||
fn from_ffi_value(context: &mut dyn FunctionContext, arg: Self::FFIType)
|
||||
-> Result<Self::Owned>;
|
||||
|
||||
/// Creates `Self::Inner` from an owned value.
|
||||
fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner;
|
||||
|
||||
/// Write back a modified `value` back into the runtime's memory.
|
||||
///
|
||||
/// Only makes sense for parameters like e.g. `&mut [u8]`.
|
||||
#[inline]
|
||||
fn write_back_into_runtime(
|
||||
_value: Self::Owned,
|
||||
_context: &mut dyn FunctionContext,
|
||||
_arg: Self::FFIType,
|
||||
) -> Result<()> {
|
||||
// Default dummy implementation, because the vast majority of impls won't need this.
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! Provides implementations for the runtime interface types which can be
|
||||
//! passed directly without any serialization strategy wrappers.
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
use crate::host::*;
|
||||
#[cfg(substrate_runtime)]
|
||||
use crate::wasm::*;
|
||||
use crate::{Pointer, RIType};
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
use sp_wasm_interface::{FunctionContext, Result};
|
||||
|
||||
// Make sure that our assumptions for storing a pointer + its size in `u64` is valid.
|
||||
#[cfg(all(substrate_runtime, not(feature = "disable_target_static_assertions")))]
|
||||
const _: () = {
|
||||
assert!(core::mem::size_of::<usize>() == core::mem::size_of::<u32>());
|
||||
assert!(core::mem::size_of::<*const u8>() == core::mem::size_of::<u32>());
|
||||
};
|
||||
|
||||
/// Implement the traits for the given primitive traits.
|
||||
macro_rules! impl_traits_for_primitives {
|
||||
(
|
||||
$(
|
||||
$rty:ty, $fty:ty,
|
||||
)*
|
||||
) => {
|
||||
$(
|
||||
/// The type is passed directly.
|
||||
impl RIType for $rty {
|
||||
type FFIType = $fty;
|
||||
type Inner = Self;
|
||||
}
|
||||
|
||||
#[cfg(substrate_runtime)]
|
||||
impl IntoFFIValue for $rty {
|
||||
type Destructor = ();
|
||||
|
||||
fn into_ffi_value(value: &mut $rty) -> (Self::FFIType, Self::Destructor) {
|
||||
(*value as $fty, ())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(substrate_runtime)]
|
||||
impl FromFFIValue for $rty {
|
||||
fn from_ffi_value(arg: $fty) -> $rty {
|
||||
arg as $rty
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl<'a> FromFFIValue<'a> for $rty {
|
||||
type Owned = Self;
|
||||
|
||||
fn from_ffi_value(_: &mut dyn FunctionContext, arg: $fty) -> Result<$rty> {
|
||||
Ok(arg as $rty)
|
||||
}
|
||||
|
||||
fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner {
|
||||
*owned
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl IntoFFIValue for $rty {
|
||||
fn into_ffi_value(value: Self::Inner, _: &mut dyn FunctionContext) -> Result<$fty> {
|
||||
Ok(value as $fty)
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
impl_traits_for_primitives! {
|
||||
u8, u32,
|
||||
u16, u32,
|
||||
u32, u32,
|
||||
u64, u64,
|
||||
i8, i32,
|
||||
i16, i32,
|
||||
i32, i32,
|
||||
i64, i64,
|
||||
}
|
||||
|
||||
/// `bool` is passed as `u32`.
|
||||
///
|
||||
/// - `1`: true
|
||||
/// - `0`: false
|
||||
impl RIType for bool {
|
||||
type FFIType = u32;
|
||||
type Inner = Self;
|
||||
}
|
||||
|
||||
#[cfg(substrate_runtime)]
|
||||
impl IntoFFIValue for bool {
|
||||
type Destructor = ();
|
||||
|
||||
fn into_ffi_value(value: &mut bool) -> (Self::FFIType, Self::Destructor) {
|
||||
(if *value { 1 } else { 0 }, ())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(substrate_runtime)]
|
||||
impl FromFFIValue for bool {
|
||||
fn from_ffi_value(arg: u32) -> bool {
|
||||
arg == 1
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl<'a> FromFFIValue<'a> for bool {
|
||||
type Owned = Self;
|
||||
|
||||
fn from_ffi_value(_: &mut dyn FunctionContext, arg: u32) -> Result<bool> {
|
||||
Ok(arg == 1)
|
||||
}
|
||||
|
||||
fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner {
|
||||
*owned
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl IntoFFIValue for bool {
|
||||
fn into_ffi_value(value: Self, _: &mut dyn FunctionContext) -> Result<u32> {
|
||||
Ok(if value { 1 } else { 0 })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl<T: sp_wasm_interface::PointerType> RIType for Pointer<T> {
|
||||
type FFIType = u32;
|
||||
type Inner = Self;
|
||||
}
|
||||
|
||||
/// The type is passed as `u32`.
|
||||
#[cfg(substrate_runtime)]
|
||||
impl<T> RIType for Pointer<T> {
|
||||
type FFIType = u32;
|
||||
type Inner = Self;
|
||||
}
|
||||
|
||||
#[cfg(substrate_runtime)]
|
||||
impl<T> IntoFFIValue for Pointer<T> {
|
||||
type Destructor = ();
|
||||
|
||||
fn into_ffi_value(value: &mut Pointer<T>) -> (Self::FFIType, Self::Destructor) {
|
||||
(*value as u32, ())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(substrate_runtime)]
|
||||
impl<T> FromFFIValue for Pointer<T> {
|
||||
fn from_ffi_value(arg: u32) -> Self {
|
||||
arg as _
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl<'a, T: sp_wasm_interface::PointerType> FromFFIValue<'a> for Pointer<T> {
|
||||
type Owned = Self;
|
||||
|
||||
fn from_ffi_value(_: &mut dyn FunctionContext, arg: u32) -> Result<Self> {
|
||||
Ok(Pointer::new(arg))
|
||||
}
|
||||
|
||||
fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner {
|
||||
*owned
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl<T: sp_wasm_interface::PointerType> IntoFFIValue for Pointer<T> {
|
||||
fn into_ffi_value(value: Self, _: &mut dyn FunctionContext) -> Result<u32> {
|
||||
Ok(value.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,395 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! Substrate runtime interface
|
||||
//!
|
||||
//! This crate provides types, traits and macros around runtime interfaces. A runtime interface is
|
||||
//! a fixed interface between a Substrate runtime (also called the "guest") and a Substrate node
|
||||
//! (also called the "host"). For a native runtime the interface maps to direct function calls of
|
||||
//! the implementation. For a non-native runtime the interface maps to an external function call.
|
||||
//! These external functions are exported by the runtime and they map to the same implementation
|
||||
//! as the native calls, just with some extra code to marshal them through the FFI boundary.
|
||||
//!
|
||||
//! # Using a type in a runtime interface
|
||||
//!
|
||||
//! Every argument type and return type must be wrapped in a marker newtype specifying the
|
||||
//! marshalling strategy used to pass the value through the FFI boundary between the host
|
||||
//! and the runtime. The only exceptions to this rule are a couple of basic, primitive types
|
||||
//! which can be passed directly through the FFI boundary and which don't require any special
|
||||
//! handling besides a straightforward, direct conversion.
|
||||
//!
|
||||
//! You can find the strategy wrapper types in the [`crate::pass_by`] module.
|
||||
//!
|
||||
//! The newtype wrappers are automatically stripped away when the function is called
|
||||
//! and applied when the function returns by the `runtime_interface` macro.
|
||||
//!
|
||||
//! # Declaring a runtime interface
|
||||
//!
|
||||
//! Declaring a runtime interface is similar to declaring a trait in Rust:
|
||||
//!
|
||||
//! ```
|
||||
//! # mod wrapper {
|
||||
//! # use sp_runtime_interface::pass_by::PassFatPointerAndRead;
|
||||
//!
|
||||
//! #[sp_runtime_interface::runtime_interface]
|
||||
//! trait RuntimeInterface {
|
||||
//! fn some_function(value: PassFatPointerAndRead<&[u8]>) -> bool {
|
||||
//! value.iter().all(|v| *v > 125)
|
||||
//! }
|
||||
//! }
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! For more information on declaring a runtime interface, see
|
||||
//! [`#[runtime_interface]`](./attr.runtime_interface.html).
|
||||
|
||||
#![no_std]
|
||||
|
||||
pub extern crate alloc;
|
||||
extern crate self as sp_runtime_interface;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(not(substrate_runtime))]
|
||||
pub use sp_wasm_interface;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use sp_tracing;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use sp_std;
|
||||
|
||||
/// Attribute macro for transforming a trait declaration into a runtime interface.
|
||||
///
|
||||
/// A runtime interface is a fixed interface between a Substrate compatible runtime and the
|
||||
/// native node. This interface is callable from a native and a wasm runtime. The macro will
|
||||
/// generate the corresponding code for the native implementation and the code for calling from
|
||||
/// the wasm side to the native implementation.
|
||||
///
|
||||
/// The macro expects the runtime interface declaration as trait declaration:
|
||||
///
|
||||
/// ```
|
||||
/// # mod wrapper {
|
||||
/// # use sp_runtime_interface::runtime_interface;
|
||||
/// # use sp_runtime_interface::pass_by::{PassFatPointerAndDecode, PassFatPointerAndRead, AllocateAndReturnFatPointer};
|
||||
///
|
||||
/// #[runtime_interface]
|
||||
/// trait Interface {
|
||||
/// /// A function that can be called from native/wasm.
|
||||
/// ///
|
||||
/// /// The implementation given to this function is only compiled on native.
|
||||
/// fn call(data: PassFatPointerAndRead<&[u8]>) -> AllocateAndReturnFatPointer<Vec<u8>> {
|
||||
/// // Here you could call some rather complex code that only compiles on native or
|
||||
/// // is way faster in native than executing it in wasm.
|
||||
/// Vec::new()
|
||||
/// }
|
||||
/// /// Call function, but different version.
|
||||
/// ///
|
||||
/// /// For new runtimes, only function with latest version is reachable.
|
||||
/// /// But old version (above) is still accessible for old runtimes.
|
||||
/// /// Default version is 1.
|
||||
/// #[version(2)]
|
||||
/// fn call(data: PassFatPointerAndRead<&[u8]>) -> AllocateAndReturnFatPointer<Vec<u8>> {
|
||||
/// // Here you could call some rather complex code that only compiles on native or
|
||||
/// // is way faster in native than executing it in wasm.
|
||||
/// [17].to_vec()
|
||||
/// }
|
||||
///
|
||||
/// /// Call function, different version and only being registered.
|
||||
/// ///
|
||||
/// /// This `register_only` version is only being registered, aka exposed to the runtime,
|
||||
/// /// but the runtime will still use the version 2 of this function. This is useful for when
|
||||
/// /// new host functions should be introduced. Adding new host functions requires that all
|
||||
/// /// nodes have the host functions available, because otherwise they fail at instantiation
|
||||
/// /// of the runtime. With `register_only` the function will not be used when compiling the
|
||||
/// /// runtime, but it will already be there for a future version of the runtime that will
|
||||
/// /// switch to using these host function.
|
||||
/// #[version(3, register_only)]
|
||||
/// fn call(data: PassFatPointerAndRead<&[u8]>) -> AllocateAndReturnFatPointer<Vec<u8>> {
|
||||
/// // Here you could call some rather complex code that only compiles on native or
|
||||
/// // is way faster in native than executing it in wasm.
|
||||
/// [18].to_vec()
|
||||
/// }
|
||||
///
|
||||
/// /// A function can take a `&self` or `&mut self` argument to get access to the
|
||||
/// /// `Externalities`. (The generated method does not require
|
||||
/// /// this argument, so the function can be called just with the `optional` argument)
|
||||
/// fn set_or_clear(&mut self, optional: PassFatPointerAndDecode<Option<Vec<u8>>>) {
|
||||
/// match optional {
|
||||
/// Some(value) => self.set_storage([1, 2, 3, 4].to_vec(), value),
|
||||
/// None => self.clear_storage(&[1, 2, 3, 4]),
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// /// A function can be gated behind a configuration (`cfg`) attribute.
|
||||
/// /// To prevent ambiguity and confusion about what will be the final exposed host
|
||||
/// /// functions list, conditionally compiled functions can't be versioned.
|
||||
/// /// That is, conditionally compiled functions with `version`s greater than 1
|
||||
/// /// are not allowed.
|
||||
/// #[cfg(feature = "experimental-function")]
|
||||
/// fn gated_call(data: PassFatPointerAndRead<&[u8]>) -> AllocateAndReturnFatPointer<Vec<u8>> {
|
||||
/// [42].to_vec()
|
||||
/// }
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// The given example will generate roughly the following code for native:
|
||||
///
|
||||
/// ```
|
||||
/// // The name of the trait is converted to snake case and used as mod name.
|
||||
/// //
|
||||
/// // Be aware that this module is not `public`, the visibility of the module is determined based
|
||||
/// // on the visibility of the trait declaration.
|
||||
/// mod interface {
|
||||
/// trait Interface {
|
||||
/// fn call_version_1(data: &[u8]) -> Vec<u8>;
|
||||
/// fn call_version_2(data: &[u8]) -> Vec<u8>;
|
||||
/// fn call_version_3(data: &[u8]) -> Vec<u8>;
|
||||
/// fn set_or_clear_version_1(&mut self, optional: Option<Vec<u8>>);
|
||||
/// #[cfg(feature = "experimental-function")]
|
||||
/// fn gated_call_version_1(data: &[u8]) -> Vec<u8>;
|
||||
/// }
|
||||
///
|
||||
/// impl Interface for &mut dyn sp_externalities::Externalities {
|
||||
/// fn call_version_1(data: &[u8]) -> Vec<u8> { Vec::new() }
|
||||
/// fn call_version_2(data: &[u8]) -> Vec<u8> { [17].to_vec() }
|
||||
/// fn call_version_3(data: &[u8]) -> Vec<u8> { [18].to_vec() }
|
||||
/// fn set_or_clear_version_1(&mut self, optional: Option<Vec<u8>>) {
|
||||
/// match optional {
|
||||
/// Some(value) => self.set_storage([1, 2, 3, 4].to_vec(), value),
|
||||
/// None => self.clear_storage(&[1, 2, 3, 4]),
|
||||
/// }
|
||||
/// }
|
||||
/// #[cfg(feature = "experimental-function")]
|
||||
/// fn gated_call_version_1(data: &[u8]) -> Vec<u8> { [42].to_vec() }
|
||||
/// }
|
||||
///
|
||||
/// pub fn call(data: &[u8]) -> Vec<u8> {
|
||||
/// // only latest version is exposed
|
||||
/// call_version_2(data)
|
||||
/// }
|
||||
///
|
||||
/// fn call_version_1(data: &[u8]) -> Vec<u8> {
|
||||
/// <&mut dyn sp_externalities::Externalities as Interface>::call_version_1(data)
|
||||
/// }
|
||||
///
|
||||
/// fn call_version_2(data: &[u8]) -> Vec<u8> {
|
||||
/// <&mut dyn sp_externalities::Externalities as Interface>::call_version_2(data)
|
||||
/// }
|
||||
///
|
||||
/// fn call_version_3(data: &[u8]) -> Vec<u8> {
|
||||
/// <&mut dyn sp_externalities::Externalities as Interface>::call_version_3(data)
|
||||
/// }
|
||||
///
|
||||
/// pub fn set_or_clear(optional: Option<Vec<u8>>) {
|
||||
/// set_or_clear_version_1(optional)
|
||||
/// }
|
||||
///
|
||||
/// fn set_or_clear_version_1(optional: Option<Vec<u8>>) {
|
||||
/// sp_externalities::with_externalities(|mut ext| Interface::set_or_clear_version_1(&mut ext, optional))
|
||||
/// .expect("`set_or_clear` called outside of an Externalities-provided environment.")
|
||||
/// }
|
||||
///
|
||||
/// #[cfg(feature = "experimental-function")]
|
||||
/// pub fn gated_call(data: &[u8]) -> Vec<u8> {
|
||||
/// gated_call_version_1(data)
|
||||
/// }
|
||||
///
|
||||
/// #[cfg(feature = "experimental-function")]
|
||||
/// fn gated_call_version_1(data: &[u8]) -> Vec<u8> {
|
||||
/// <&mut dyn sp_externalities::Externalities as Interface>::gated_call_version_1(data)
|
||||
/// }
|
||||
///
|
||||
/// /// This type implements the `HostFunctions` trait (from `sp-wasm-interface`) and
|
||||
/// /// provides the host implementation for the wasm side. The host implementation converts the
|
||||
/// /// arguments from wasm to native and calls the corresponding native function.
|
||||
/// ///
|
||||
/// /// This type needs to be passed to the wasm executor, so that the host functions will be
|
||||
/// /// registered in the executor.
|
||||
/// pub struct HostFunctions;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The given example will generate roughly the following code for wasm:
|
||||
///
|
||||
/// ```
|
||||
/// mod interface {
|
||||
/// mod extern_host_functions_impls {
|
||||
/// /// Every function is exported by the native code as `ext_FUNCTION_NAME_version_VERSION`.
|
||||
/// ///
|
||||
/// /// The type for each argument of the exported function depends on
|
||||
/// /// `<ARGUMENT_TYPE as RIType>::FFIType`.
|
||||
/// ///
|
||||
/// /// `key` holds the pointer and the length to the `data` slice.
|
||||
/// pub fn call(data: &[u8]) -> Vec<u8> {
|
||||
/// extern "C" { pub fn ext_call_version_2(key: u64); }
|
||||
/// // Should call into external `ext_call_version_2(<[u8] as IntoFFIValue>::into_ffi_value(key))`
|
||||
/// // But this is too much to replicate in a doc test so here we just return a dummy vector.
|
||||
/// // Note that we jump into the latest version not marked as `register_only` (i.e. version 2).
|
||||
/// Vec::new()
|
||||
/// }
|
||||
///
|
||||
/// /// `key` holds the pointer and the length of the `option` value.
|
||||
/// pub fn set_or_clear(option: Option<Vec<u8>>) {
|
||||
/// extern "C" { pub fn ext_set_or_clear_version_1(key: u64); }
|
||||
/// // Same as above
|
||||
/// }
|
||||
///
|
||||
/// /// `key` holds the pointer and the length to the `data` slice.
|
||||
/// #[cfg(feature = "experimental-function")]
|
||||
/// pub fn gated_call(data: &[u8]) -> Vec<u8> {
|
||||
/// extern "C" { pub fn ext_gated_call_version_1(key: u64); }
|
||||
/// /// Same as above
|
||||
/// Vec::new()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// /// The type is actually `ExchangeableFunction` (from `sp-runtime-interface`) and
|
||||
/// /// by default this is initialized to jump into the corresponding function in
|
||||
/// /// `extern_host_functions_impls`.
|
||||
/// ///
|
||||
/// /// This can be used to replace the implementation of the `call` function.
|
||||
/// /// Instead of calling into the host, the callee will automatically call the other
|
||||
/// /// implementation.
|
||||
/// ///
|
||||
/// /// To replace the implementation:
|
||||
/// ///
|
||||
/// /// `host_call.replace_implementation(some_other_impl)`
|
||||
/// pub static host_call: () = ();
|
||||
/// pub static host_set_or_clear: () = ();
|
||||
/// #[cfg(feature = "experimental-feature")]
|
||||
/// pub static gated_call: () = ();
|
||||
///
|
||||
/// pub fn call(data: &[u8]) -> Vec<u8> {
|
||||
/// // This is the actual call: `host_call.get()(data)`
|
||||
/// //
|
||||
/// // But that does not work for several reasons in this example, so we just return an
|
||||
/// // empty vector.
|
||||
/// Vec::new()
|
||||
/// }
|
||||
///
|
||||
/// pub fn set_or_clear(optional: Option<Vec<u8>>) {
|
||||
/// // Same as above
|
||||
/// }
|
||||
///
|
||||
/// #[cfg(feature = "experimental-feature")]
|
||||
/// pub fn gated_call(data: &[u8]) -> Vec<u8> {
|
||||
/// // Same as above
|
||||
/// Vec::new()
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Argument and return types
|
||||
///
|
||||
/// Every argument type and return type must be wrapped in a marker newtype specifying the
|
||||
/// marshalling strategy used to pass the value through the FFI boundary between the host
|
||||
/// and the runtime. The only exceptions to this rule are a couple of basic, primitive types
|
||||
/// which can be passed directly through the FFI boundary and which don't require any special
|
||||
/// handling besides a straightforward, direct conversion.
|
||||
///
|
||||
/// The following table documents those types which can be passed between the host and the
|
||||
/// runtime without a marshalling strategy wrapper:
|
||||
///
|
||||
/// | Type | FFI type | Conversion |
|
||||
/// |----|----|----|
|
||||
/// | `u8` | `u32` | zero-extended to 32-bits |
|
||||
/// | `u16` | `u32` | zero-extended to 32-bits |
|
||||
/// | `u32` | `u32` | `Identity` |
|
||||
/// | `u64` | `u64` | `Identity` |
|
||||
/// | `i8` | `i32` | sign-extended to 32-bits |
|
||||
/// | `i16` | `i32` | sign-extended to 32-bits |
|
||||
/// | `i32` | `i32` | `Identity` |
|
||||
/// | `i64` | `i64` | `Identity` |
|
||||
/// | `bool` | `u32` | `if v { 1 } else { 0 }` |
|
||||
/// | `*const T` | `u32` | `Identity` |
|
||||
///
|
||||
/// `Identity` means that the value is passed as-is directly in a bit-exact fashion.
|
||||
///
|
||||
/// You can find the strategy wrapper types in the [`crate::pass_by`] module.
|
||||
///
|
||||
/// The newtype wrappers are automatically stripped away when the function is called
|
||||
/// and applied when the function returns by the `runtime_interface` macro.
|
||||
///
|
||||
/// # Wasm only interfaces
|
||||
///
|
||||
/// Some interfaces are only required from within the wasm runtime e.g. the allocator
|
||||
/// interface. To support this, the macro can be called like `#[runtime_interface(wasm_only)]`.
|
||||
/// This instructs the macro to make two significant changes to the generated code:
|
||||
///
|
||||
/// 1. The generated functions are not callable from the native side.
|
||||
/// 2. The trait as shown above is not implemented for [`Externalities`] and is instead
|
||||
/// implemented for `FunctionContext` (from `sp-wasm-interface`).
|
||||
///
|
||||
/// # Disable tracing
|
||||
/// By adding `no_tracing` to the list of options you can prevent the wasm-side interface from
|
||||
/// generating the default `sp-tracing`-calls. Note that this is rarely needed but only meant
|
||||
/// for the case when that would create a circular dependency. You usually _do not_ want to add
|
||||
/// this flag, as tracing doesn't cost you anything by default anyways (it is added as a no-op)
|
||||
/// but is super useful for debugging later.
|
||||
pub use sp_runtime_interface_proc_macro::runtime_interface;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(not(substrate_runtime))]
|
||||
pub use sp_externalities::{
|
||||
set_and_run_with_externalities, with_externalities, ExtensionStore, Externalities,
|
||||
ExternalitiesExt,
|
||||
};
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use codec;
|
||||
|
||||
#[cfg(all(any(target_arch = "riscv32", target_arch = "riscv64"), substrate_runtime))]
|
||||
pub mod polkavm;
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
pub mod host;
|
||||
pub(crate) mod impls;
|
||||
pub mod pass_by;
|
||||
#[cfg(any(substrate_runtime, doc))]
|
||||
pub mod wasm;
|
||||
|
||||
mod util;
|
||||
|
||||
pub use util::{pack_ptr_and_len, unpack_ptr_and_len};
|
||||
|
||||
/// Something that can be used by the runtime interface as type to communicate between the runtime
|
||||
/// and the host.
|
||||
///
|
||||
/// Every type that should be used in a runtime interface function signature needs to implement
|
||||
/// this trait.
|
||||
pub trait RIType: Sized {
|
||||
/// The raw FFI type that is used to pass `Self` through the host <-> runtime boundary.
|
||||
#[cfg(not(substrate_runtime))]
|
||||
type FFIType: sp_wasm_interface::IntoValue
|
||||
+ sp_wasm_interface::TryFromValue
|
||||
+ sp_wasm_interface::WasmTy;
|
||||
|
||||
#[cfg(substrate_runtime)]
|
||||
type FFIType;
|
||||
|
||||
/// The inner type without any serialization strategy wrapper.
|
||||
type Inner;
|
||||
}
|
||||
|
||||
/// A raw pointer that can be used in a runtime interface function signature.
|
||||
#[cfg(substrate_runtime)]
|
||||
pub type Pointer<T> = *mut T;
|
||||
|
||||
/// A raw pointer that can be used in a runtime interface function signature.
|
||||
#[cfg(not(substrate_runtime))]
|
||||
pub type Pointer<T> = sp_wasm_interface::Pointer<T>;
|
||||
@@ -0,0 +1,712 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! Provides host <-> runtime FFI marshalling strategy newtype wrappers
|
||||
//! for defining runtime interfaces.
|
||||
|
||||
use crate::{
|
||||
util::{pack_ptr_and_len, unpack_ptr_and_len},
|
||||
RIType,
|
||||
};
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
use crate::host::*;
|
||||
|
||||
#[cfg(substrate_runtime)]
|
||||
use crate::wasm::*;
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
use sp_wasm_interface::{FunctionContext, Pointer, Result};
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
use alloc::{format, string::String};
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use core::{any::type_name, marker::PhantomData};
|
||||
|
||||
/// Pass a value into the host by a thin pointer.
|
||||
///
|
||||
/// This casts the value into a `&[u8]` using `AsRef<[u8]>` and passes a pointer to that byte blob
|
||||
/// to the host. Then the host reads `N` bytes from that address into an `[u8; N]`, converts it
|
||||
/// into target type using `From<[u8; N]>` and passes it into the host function by a copy.
|
||||
///
|
||||
/// Use [`PassPointerAndRead`] if you want to have the host function accept a reference type
|
||||
/// on the host side or if you'd like to avoid the extra copy.
|
||||
///
|
||||
/// Raw FFI type: `u32` (a pointer)
|
||||
pub struct PassPointerAndReadCopy<T, const N: usize>(PhantomData<(T, [u8; N])>);
|
||||
|
||||
impl<T, const N: usize> RIType for PassPointerAndReadCopy<T, N> {
|
||||
type FFIType = u32;
|
||||
type Inner = T;
|
||||
}
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl<'a, T, const N: usize> FromFFIValue<'a> for PassPointerAndReadCopy<T, N>
|
||||
where
|
||||
T: From<[u8; N]> + Copy,
|
||||
{
|
||||
type Owned = T;
|
||||
|
||||
fn from_ffi_value(
|
||||
context: &mut dyn FunctionContext,
|
||||
arg: Self::FFIType,
|
||||
) -> Result<Self::Owned> {
|
||||
let mut out = [0; N];
|
||||
context.read_memory_into(Pointer::new(arg), &mut out)?;
|
||||
Ok(T::from(out))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner {
|
||||
*owned
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(substrate_runtime)]
|
||||
impl<T, const N: usize> IntoFFIValue for PassPointerAndReadCopy<T, N>
|
||||
where
|
||||
T: AsRef<[u8]>,
|
||||
{
|
||||
type Destructor = ();
|
||||
|
||||
fn into_ffi_value(value: &mut Self::Inner) -> (Self::FFIType, Self::Destructor) {
|
||||
// Using an 'assert' instead of a 'T: AsRef<[u8; N]>` bound since a '[u8; N]' *doesn't*
|
||||
// implement it.
|
||||
assert_eq!(value.as_ref().len(), N);
|
||||
(value.as_ref().as_ptr() as u32, ())
|
||||
}
|
||||
}
|
||||
|
||||
/// Pass a value into the host by a thin pointer.
|
||||
///
|
||||
/// This casts the value into a `&[u8]` using `AsRef<[u8]>` and passes a pointer to that byte blob
|
||||
/// to the host. Then the host reads `N` bytes from that address into an `[u8; N]`, converts it
|
||||
/// into target type using `From<[u8; N]>` and passes it into the host function by a reference.
|
||||
///
|
||||
/// This can only be used with reference types (e.g. `&[u8; 32]`). Use [`PassPointerAndReadCopy`]
|
||||
/// if you want to have the host function accept a non-reference type on the host side.
|
||||
///
|
||||
/// Raw FFI type: `u32` (a pointer)
|
||||
pub struct PassPointerAndRead<T, const N: usize>(PhantomData<(T, [u8; N])>);
|
||||
|
||||
impl<'a, T, const N: usize> RIType for PassPointerAndRead<&'a T, N> {
|
||||
type FFIType = u32;
|
||||
type Inner = &'a T;
|
||||
}
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl<'a, T, const N: usize> FromFFIValue<'a> for PassPointerAndRead<&'a T, N>
|
||||
where
|
||||
T: From<[u8; N]>,
|
||||
{
|
||||
type Owned = T;
|
||||
|
||||
fn from_ffi_value(
|
||||
context: &mut dyn FunctionContext,
|
||||
arg: Self::FFIType,
|
||||
) -> Result<Self::Owned> {
|
||||
let mut out = [0; N];
|
||||
context.read_memory_into(Pointer::new(arg), &mut out)?;
|
||||
Ok(T::from(out))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner {
|
||||
&*owned
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(substrate_runtime)]
|
||||
impl<'a, T, const N: usize> IntoFFIValue for PassPointerAndRead<&'a T, N>
|
||||
where
|
||||
T: AsRef<[u8]>,
|
||||
{
|
||||
type Destructor = ();
|
||||
|
||||
fn into_ffi_value(value: &mut Self::Inner) -> (Self::FFIType, Self::Destructor) {
|
||||
assert_eq!(value.as_ref().len(), N);
|
||||
(value.as_ref().as_ptr() as u32, ())
|
||||
}
|
||||
}
|
||||
|
||||
/// Pass a value into the host by a fat pointer.
|
||||
///
|
||||
/// This casts the value into a `&[u8]` and passes a pointer to that byte blob and its length
|
||||
/// to the host. Then the host reads that blob and converts it into an owned type and passes it
|
||||
/// (either as an owned type or as a reference) to the host function.
|
||||
///
|
||||
/// Raw FFI type: `u64` (a fat pointer; upper 32 bits is the size, lower 32 bits is the pointer)
|
||||
pub struct PassFatPointerAndRead<T>(PhantomData<T>);
|
||||
|
||||
impl<T> RIType for PassFatPointerAndRead<T> {
|
||||
type FFIType = u64;
|
||||
type Inner = T;
|
||||
}
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl<'a> FromFFIValue<'a> for PassFatPointerAndRead<&'a [u8]> {
|
||||
type Owned = Vec<u8>;
|
||||
|
||||
fn from_ffi_value(
|
||||
context: &mut dyn FunctionContext,
|
||||
arg: Self::FFIType,
|
||||
) -> Result<Self::Owned> {
|
||||
let (ptr, len) = unpack_ptr_and_len(arg);
|
||||
context.read_memory(Pointer::new(ptr), len)
|
||||
}
|
||||
|
||||
fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner {
|
||||
&*owned
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl<'a> FromFFIValue<'a> for PassFatPointerAndRead<&'a str> {
|
||||
type Owned = String;
|
||||
|
||||
fn from_ffi_value(
|
||||
context: &mut dyn FunctionContext,
|
||||
arg: Self::FFIType,
|
||||
) -> Result<Self::Owned> {
|
||||
let (ptr, len) = unpack_ptr_and_len(arg);
|
||||
let vec = context.read_memory(Pointer::new(ptr), len)?;
|
||||
String::from_utf8(vec).map_err(|_| "could not parse '&str' when marshalling hostcall's arguments through the FFI boundary: the string is not valid UTF-8".into())
|
||||
}
|
||||
|
||||
fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner {
|
||||
&*owned
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl<'a> FromFFIValue<'a> for PassFatPointerAndRead<Vec<u8>> {
|
||||
type Owned = Vec<u8>;
|
||||
|
||||
fn from_ffi_value(
|
||||
context: &mut dyn FunctionContext,
|
||||
arg: Self::FFIType,
|
||||
) -> Result<Self::Owned> {
|
||||
<PassFatPointerAndRead<&[u8]> as FromFFIValue>::from_ffi_value(context, arg)
|
||||
}
|
||||
|
||||
fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner {
|
||||
core::mem::take(owned)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(substrate_runtime)]
|
||||
impl<T> IntoFFIValue for PassFatPointerAndRead<T>
|
||||
where
|
||||
T: AsRef<[u8]>,
|
||||
{
|
||||
type Destructor = ();
|
||||
|
||||
fn into_ffi_value(value: &mut Self::Inner) -> (Self::FFIType, Self::Destructor) {
|
||||
let value = value.as_ref();
|
||||
(pack_ptr_and_len(value.as_ptr() as u32, value.len() as u32), ())
|
||||
}
|
||||
}
|
||||
|
||||
/// Pass a value into the host by a fat pointer, writing it back after the host call ends.
|
||||
///
|
||||
/// This casts the value into a `&mut [u8]` and passes a pointer to that byte blob and its length
|
||||
/// to the host. Then the host reads that blob and converts it into an owned type and passes it
|
||||
/// as a mutable reference to the host function. After the host function finishes the byte blob
|
||||
/// is written back into the guest memory.
|
||||
///
|
||||
/// Raw FFI type: `u64` (a fat pointer; upper 32 bits is the size, lower 32 bits is the pointer)
|
||||
pub struct PassFatPointerAndReadWrite<T>(PhantomData<T>);
|
||||
|
||||
impl<T> RIType for PassFatPointerAndReadWrite<T> {
|
||||
type FFIType = u64;
|
||||
type Inner = T;
|
||||
}
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl<'a> FromFFIValue<'a> for PassFatPointerAndReadWrite<&'a mut [u8]> {
|
||||
type Owned = Vec<u8>;
|
||||
|
||||
fn from_ffi_value(
|
||||
context: &mut dyn FunctionContext,
|
||||
arg: Self::FFIType,
|
||||
) -> Result<Self::Owned> {
|
||||
let (ptr, len) = unpack_ptr_and_len(arg);
|
||||
context.read_memory(Pointer::new(ptr), len)
|
||||
}
|
||||
|
||||
fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner {
|
||||
&mut *owned
|
||||
}
|
||||
|
||||
fn write_back_into_runtime(
|
||||
value: Self::Owned,
|
||||
context: &mut dyn FunctionContext,
|
||||
arg: Self::FFIType,
|
||||
) -> Result<()> {
|
||||
let (ptr, len) = unpack_ptr_and_len(arg);
|
||||
assert_eq!(len as usize, value.len());
|
||||
context.write_memory(Pointer::new(ptr), &value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(substrate_runtime)]
|
||||
impl<'a> IntoFFIValue for PassFatPointerAndReadWrite<&'a mut [u8]> {
|
||||
type Destructor = ();
|
||||
|
||||
fn into_ffi_value(value: &mut Self::Inner) -> (Self::FFIType, Self::Destructor) {
|
||||
(pack_ptr_and_len(value.as_ptr() as u32, value.len() as u32), ())
|
||||
}
|
||||
}
|
||||
|
||||
/// Pass a pointer into the host and write to it after the host call ends.
|
||||
///
|
||||
/// This casts a given type into `&mut [u8]` using `AsMut<[u8]>` and passes a pointer to
|
||||
/// that byte slice into the host. The host *doesn't* read from this and instead creates
|
||||
/// a default instance of type `T` and passes it as a `&mut T` into the host function
|
||||
/// implementation. After the host function finishes this value is then cast into a `&[u8]` using
|
||||
/// `AsRef<[u8]>` and written back into the guest memory.
|
||||
///
|
||||
/// Raw FFI type: `u32` (a pointer)
|
||||
pub struct PassPointerAndWrite<T, const N: usize>(PhantomData<(T, [u8; N])>);
|
||||
|
||||
impl<T, const N: usize> RIType for PassPointerAndWrite<T, N> {
|
||||
type FFIType = u32;
|
||||
type Inner = T;
|
||||
}
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl<'a, T, const N: usize> FromFFIValue<'a> for PassPointerAndWrite<&'a mut T, N>
|
||||
where
|
||||
T: Default + AsRef<[u8]>,
|
||||
{
|
||||
type Owned = T;
|
||||
|
||||
fn from_ffi_value(
|
||||
_context: &mut dyn FunctionContext,
|
||||
_arg: Self::FFIType,
|
||||
) -> Result<Self::Owned> {
|
||||
Ok(T::default())
|
||||
}
|
||||
|
||||
fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner {
|
||||
&mut *owned
|
||||
}
|
||||
|
||||
fn write_back_into_runtime(
|
||||
value: Self::Owned,
|
||||
context: &mut dyn FunctionContext,
|
||||
arg: Self::FFIType,
|
||||
) -> Result<()> {
|
||||
let value = value.as_ref();
|
||||
assert_eq!(value.len(), N);
|
||||
context.write_memory(Pointer::new(arg), value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(substrate_runtime)]
|
||||
impl<'a, T, const N: usize> IntoFFIValue for PassPointerAndWrite<&'a mut T, N>
|
||||
where
|
||||
T: AsMut<[u8]>,
|
||||
{
|
||||
type Destructor = ();
|
||||
|
||||
fn into_ffi_value(value: &mut Self::Inner) -> (Self::FFIType, Self::Destructor) {
|
||||
let value = value.as_mut();
|
||||
assert_eq!(value.len(), N);
|
||||
(value.as_ptr() as u32, ())
|
||||
}
|
||||
}
|
||||
|
||||
/// Pass a `T` into the host using the SCALE codec.
|
||||
///
|
||||
/// This encodes a `T` into a `Vec<u8>` using the SCALE codec and then
|
||||
/// passes a pointer to that byte blob and its length to the host,
|
||||
/// which then reads it and decodes back into `T`.
|
||||
///
|
||||
/// Raw FFI type: `u64` (a fat pointer; upper 32 bits is the size, lower 32 bits is the pointer)
|
||||
pub struct PassFatPointerAndDecode<T>(PhantomData<T>);
|
||||
|
||||
impl<T> RIType for PassFatPointerAndDecode<T> {
|
||||
type FFIType = u64;
|
||||
type Inner = T;
|
||||
}
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl<'a, T: codec::Decode> FromFFIValue<'a> for PassFatPointerAndDecode<T> {
|
||||
type Owned = Option<T>;
|
||||
|
||||
fn from_ffi_value(
|
||||
context: &mut dyn FunctionContext,
|
||||
arg: Self::FFIType,
|
||||
) -> Result<Self::Owned> {
|
||||
let (ptr, len) = unpack_ptr_and_len(arg);
|
||||
let vec = context.read_memory(Pointer::new(ptr), len)?;
|
||||
T::decode(&mut &vec[..]).map_err(|error| format!(
|
||||
"could not SCALE-decode '{}' when marshalling hostcall's arguments through the FFI boundary: {error}",
|
||||
type_name::<T>())
|
||||
).map(Some)
|
||||
}
|
||||
|
||||
fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner {
|
||||
owned.take().expect("this is called only once and is never 'None'")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(substrate_runtime)]
|
||||
impl<T: codec::Encode> IntoFFIValue for PassFatPointerAndDecode<T> {
|
||||
type Destructor = Vec<u8>;
|
||||
|
||||
fn into_ffi_value(value: &mut Self::Inner) -> (Self::FFIType, Self::Destructor) {
|
||||
let data = value.encode();
|
||||
(pack_ptr_and_len(data.as_ptr() as u32, data.len() as u32), data)
|
||||
}
|
||||
}
|
||||
|
||||
/// Pass a `&[T]` into the host using the SCALE codec.
|
||||
///
|
||||
/// This encodes a `&[T]` into a `Vec<u8>` using the SCALE codec and then
|
||||
/// passes a pointer to that byte blob and its length to the host,
|
||||
/// which then reads it and decodes back into `Vec<T>` and passes
|
||||
/// a reference to that (as `&[T]`) into the host function.
|
||||
///
|
||||
/// Raw FFI type: `u64` (a fat pointer; upper 32 bits is the size, lower 32 bits is the pointer)
|
||||
pub struct PassFatPointerAndDecodeSlice<T>(PhantomData<T>);
|
||||
|
||||
impl<T> RIType for PassFatPointerAndDecodeSlice<T> {
|
||||
type FFIType = u64;
|
||||
type Inner = T;
|
||||
}
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl<'a, T: codec::Decode> FromFFIValue<'a> for PassFatPointerAndDecodeSlice<&'a [T]> {
|
||||
type Owned = Vec<T>;
|
||||
|
||||
fn from_ffi_value(
|
||||
context: &mut dyn FunctionContext,
|
||||
arg: Self::FFIType,
|
||||
) -> Result<Self::Owned> {
|
||||
let (ptr, len) = unpack_ptr_and_len(arg);
|
||||
let vec = context.read_memory(Pointer::new(ptr), len)?;
|
||||
<Vec::<T> as codec::Decode>::decode(&mut &vec[..]).map_err(|error| format!(
|
||||
"could not SCALE-decode '{}' when marshalling hostcall's arguments through the FFI boundary: {error}",
|
||||
type_name::<Vec<T>>()
|
||||
))
|
||||
}
|
||||
|
||||
fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner {
|
||||
&*owned
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(substrate_runtime)]
|
||||
impl<'a, T: codec::Encode> IntoFFIValue for PassFatPointerAndDecodeSlice<&'a [T]> {
|
||||
type Destructor = Vec<u8>;
|
||||
|
||||
fn into_ffi_value(value: &mut Self::Inner) -> (Self::FFIType, Self::Destructor) {
|
||||
let data = codec::Encode::encode(value);
|
||||
(pack_ptr_and_len(data.as_ptr() as u32, data.len() as u32), data)
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait signifying a primitive type.
|
||||
trait Primitive: Copy {}
|
||||
|
||||
impl Primitive for u8 {}
|
||||
impl Primitive for u16 {}
|
||||
impl Primitive for u32 {}
|
||||
impl Primitive for u64 {}
|
||||
|
||||
impl Primitive for i8 {}
|
||||
impl Primitive for i16 {}
|
||||
impl Primitive for i32 {}
|
||||
impl Primitive for i64 {}
|
||||
|
||||
/// Pass `T` through the FFI boundary by first converting it to `U` in the runtime, and then
|
||||
/// converting it back to `T` on the host's side.
|
||||
///
|
||||
/// Raw FFI type: same as `U`'s FFI type
|
||||
pub struct PassAs<T, U>(PhantomData<(T, U)>);
|
||||
|
||||
impl<T, U> RIType for PassAs<T, U>
|
||||
where
|
||||
U: RIType,
|
||||
{
|
||||
type FFIType = <U as RIType>::FFIType;
|
||||
type Inner = T;
|
||||
}
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl<'a, T, U> FromFFIValue<'a> for PassAs<T, U>
|
||||
where
|
||||
U: RIType + FromFFIValue<'a> + Primitive,
|
||||
T: TryFrom<<U as FromFFIValue<'a>>::Owned> + Copy,
|
||||
{
|
||||
type Owned = T;
|
||||
|
||||
fn from_ffi_value(
|
||||
context: &mut dyn FunctionContext,
|
||||
arg: Self::FFIType,
|
||||
) -> Result<Self::Owned> {
|
||||
<U as FromFFIValue>::from_ffi_value(context, arg).and_then(|value| value.try_into()
|
||||
.map_err(|_| format!(
|
||||
"failed to convert '{}' (passed as '{}') into '{}' when marshalling hostcall's arguments through the FFI boundary",
|
||||
type_name::<U>(),
|
||||
type_name::<Self::FFIType>(),
|
||||
type_name::<Self::Owned>()
|
||||
)))
|
||||
}
|
||||
|
||||
fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner {
|
||||
*owned
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(substrate_runtime)]
|
||||
impl<T, U> IntoFFIValue for PassAs<T, U>
|
||||
where
|
||||
U: RIType + IntoFFIValue + Primitive,
|
||||
U::Inner: From<T>,
|
||||
T: Copy,
|
||||
{
|
||||
type Destructor = <U as IntoFFIValue>::Destructor;
|
||||
|
||||
fn into_ffi_value(value: &mut Self::Inner) -> (Self::FFIType, Self::Destructor) {
|
||||
let mut value = U::Inner::from(*value);
|
||||
<U as IntoFFIValue>::into_ffi_value(&mut value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `T` through the FFI boundary by first converting it to `U` on the host's side, and then
|
||||
/// converting it back to `T` in the runtime.
|
||||
///
|
||||
/// Raw FFI type: same as `U`'s FFI type
|
||||
pub struct ReturnAs<T, U>(PhantomData<(T, U)>);
|
||||
|
||||
impl<T, U> RIType for ReturnAs<T, U>
|
||||
where
|
||||
U: RIType,
|
||||
{
|
||||
type FFIType = <U as RIType>::FFIType;
|
||||
type Inner = T;
|
||||
}
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl<T, U> IntoFFIValue for ReturnAs<T, U>
|
||||
where
|
||||
U: RIType + IntoFFIValue + Primitive,
|
||||
<U as RIType>::Inner: From<Self::Inner>,
|
||||
{
|
||||
fn into_ffi_value(
|
||||
value: Self::Inner,
|
||||
context: &mut dyn FunctionContext,
|
||||
) -> Result<Self::FFIType> {
|
||||
let value: <U as RIType>::Inner = value.into();
|
||||
<U as IntoFFIValue>::into_ffi_value(value, context)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(substrate_runtime)]
|
||||
impl<T, U> FromFFIValue for ReturnAs<T, U>
|
||||
where
|
||||
U: RIType + FromFFIValue + Primitive,
|
||||
Self::Inner: TryFrom<U::Inner>,
|
||||
{
|
||||
fn from_ffi_value(arg: Self::FFIType) -> Self::Inner {
|
||||
let value = <U as FromFFIValue>::from_ffi_value(arg);
|
||||
match Self::Inner::try_from(value) {
|
||||
Ok(value) => value,
|
||||
Err(_) => {
|
||||
panic!(
|
||||
"failed to convert '{}' (passed as '{}') into a '{}' when marshalling a hostcall's return value through the FFI boundary",
|
||||
type_name::<U::Inner>(),
|
||||
type_name::<Self::FFIType>(),
|
||||
type_name::<Self::Inner>()
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// (DEPRECATED) Return `T` as a blob of bytes into the runtime.
|
||||
///
|
||||
/// Uses `T::AsRef<[u8]>` to cast `T` into a `&[u8]`, allocates runtime memory
|
||||
/// using the legacy allocator, copies the slice into the runtime memory, and
|
||||
/// returns a pointer to it.
|
||||
///
|
||||
/// THIS STRATEGY IS DEPRECATED; DO NOT USE FOR NEW HOST FUNCTIONS!
|
||||
///
|
||||
/// Ideally use a mutable slice to return data to the guest, for example using
|
||||
/// the [`PassPointerAndWrite`] strategy.
|
||||
///
|
||||
/// Raw FFI type: `u32` (a pointer to the byte blob)
|
||||
pub struct AllocateAndReturnPointer<T, const N: usize>(PhantomData<(T, [u8; N])>);
|
||||
|
||||
impl<T, const N: usize> RIType for AllocateAndReturnPointer<T, N> {
|
||||
type FFIType = u32;
|
||||
type Inner = T;
|
||||
}
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl<T, const N: usize> IntoFFIValue for AllocateAndReturnPointer<T, N>
|
||||
where
|
||||
T: AsRef<[u8]>,
|
||||
{
|
||||
fn into_ffi_value(
|
||||
value: Self::Inner,
|
||||
context: &mut dyn FunctionContext,
|
||||
) -> Result<Self::FFIType> {
|
||||
let value = value.as_ref();
|
||||
assert_eq!(
|
||||
value.len(),
|
||||
N,
|
||||
"expected the byte blob to be {N} bytes long, is {} bytes when returning '{}' from a host function",
|
||||
value.len(),
|
||||
type_name::<T>()
|
||||
);
|
||||
|
||||
let addr = context.allocate_memory(value.len() as u32)?;
|
||||
context.write_memory(addr, value)?;
|
||||
Ok(addr.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(substrate_runtime)]
|
||||
impl<T: codec::Decode, const N: usize> FromFFIValue for AllocateAndReturnPointer<T, N>
|
||||
where
|
||||
T: From<[u8; N]>,
|
||||
{
|
||||
fn from_ffi_value(arg: Self::FFIType) -> Self::Inner {
|
||||
// SAFETY: This memory was allocated by the host allocator with the exact
|
||||
// capacity needed, so it's safe to make a `Vec` out of it.
|
||||
let value = unsafe { Vec::from_raw_parts(arg as *mut u8, N, N) };
|
||||
|
||||
// SAFETY: Reading a `[u8; N]` from a `&[u8]` which is at least `N` elements long is safe.
|
||||
let array = unsafe { *(value.as_ptr() as *const [u8; N]) };
|
||||
T::from(array)
|
||||
}
|
||||
}
|
||||
|
||||
/// (DEPRECATED) Return `T` as a blob of bytes into the runtime.
|
||||
///
|
||||
/// Uses `T::AsRef<[u8]>` to cast `T` into a `&[u8]`, allocates runtime memory
|
||||
/// using the legacy allocator, copies the slice into the runtime memory, and
|
||||
/// returns a pointer to it.
|
||||
///
|
||||
/// THIS STRATEGY IS DEPRECATED; DO NOT USE FOR NEW HOST FUNCTIONS!
|
||||
///
|
||||
/// Ideally use a mutable slice to return data to the guest, for example using
|
||||
/// the [`PassPointerAndWrite`] strategy.
|
||||
///
|
||||
/// Raw FFI type: `u64` (a fat pointer; upper 32 bits is the size, lower 32 bits is the pointer)
|
||||
pub struct AllocateAndReturnFatPointer<T>(PhantomData<T>);
|
||||
|
||||
impl<T> RIType for AllocateAndReturnFatPointer<T> {
|
||||
type FFIType = u64;
|
||||
type Inner = T;
|
||||
}
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl<T> IntoFFIValue for AllocateAndReturnFatPointer<T>
|
||||
where
|
||||
T: AsRef<[u8]>,
|
||||
{
|
||||
fn into_ffi_value(
|
||||
value: Self::Inner,
|
||||
context: &mut dyn FunctionContext,
|
||||
) -> Result<Self::FFIType> {
|
||||
let value = value.as_ref();
|
||||
let ptr = context.allocate_memory(value.len() as u32)?;
|
||||
context.write_memory(ptr, &value)?;
|
||||
Ok(pack_ptr_and_len(ptr.into(), value.len() as u32))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(substrate_runtime)]
|
||||
impl<T> FromFFIValue for AllocateAndReturnFatPointer<T>
|
||||
where
|
||||
T: From<Vec<u8>>,
|
||||
{
|
||||
fn from_ffi_value(arg: Self::FFIType) -> Self::Inner {
|
||||
let (ptr, len) = unpack_ptr_and_len(arg);
|
||||
let len = len as usize;
|
||||
let vec = if len == 0 {
|
||||
Vec::new()
|
||||
} else {
|
||||
// SAFETY: This memory was allocated by the host allocator with the exact
|
||||
// capacity needed, so it's safe to make a `Vec` out of it.
|
||||
unsafe { Vec::from_raw_parts(ptr as *mut u8, len, len) }
|
||||
};
|
||||
|
||||
T::from(vec)
|
||||
}
|
||||
}
|
||||
|
||||
/// (DEPRECATED) Return `T` into the runtime using the SCALE codec.
|
||||
///
|
||||
/// Encodes `T` using the SCALE codec, allocates runtime memory using the legacy
|
||||
/// allocator, copies the encoded payload into the runtime memory, and returns
|
||||
/// a fat pointer to it.
|
||||
///
|
||||
/// THIS STRATEGY IS DEPRECATED; DO NOT USE FOR NEW HOST FUNCTIONS!
|
||||
///
|
||||
/// Ideally use a mutable slice to return data to the guest, for example using
|
||||
/// the [`PassPointerAndWrite`] strategy.
|
||||
///
|
||||
/// Raw FFI type: `u64` (a fat pointer; upper 32 bits is the size, lower 32 bits is the pointer)
|
||||
pub struct AllocateAndReturnByCodec<T>(PhantomData<T>);
|
||||
|
||||
impl<T> RIType for AllocateAndReturnByCodec<T> {
|
||||
type FFIType = u64;
|
||||
type Inner = T;
|
||||
}
|
||||
|
||||
#[cfg(not(substrate_runtime))]
|
||||
impl<T: codec::Encode> IntoFFIValue for AllocateAndReturnByCodec<T> {
|
||||
fn into_ffi_value(value: T, context: &mut dyn FunctionContext) -> Result<Self::FFIType> {
|
||||
let vec = value.encode();
|
||||
let ptr = context.allocate_memory(vec.len() as u32)?;
|
||||
context.write_memory(ptr, &vec)?;
|
||||
Ok(pack_ptr_and_len(ptr.into(), vec.len() as u32))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(substrate_runtime)]
|
||||
impl<T: codec::Decode> FromFFIValue for AllocateAndReturnByCodec<T> {
|
||||
fn from_ffi_value(arg: Self::FFIType) -> Self::Inner {
|
||||
let (ptr, len) = unpack_ptr_and_len(arg);
|
||||
let len = len as usize;
|
||||
|
||||
let encoded = if len == 0 {
|
||||
bytes::Bytes::new()
|
||||
} else {
|
||||
// SAFETY: This memory was allocated by the host allocator with the exact
|
||||
// capacity needed, so it's safe to make a `Vec` out of it.
|
||||
bytes::Bytes::from(unsafe { Vec::from_raw_parts(ptr as *mut u8, len, len) })
|
||||
};
|
||||
|
||||
match codec::decode_from_bytes(encoded) {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
panic!(
|
||||
"failed to decode '{}' when marshalling a hostcall's return value through the FFI boundary: {error}",
|
||||
type_name::<T>(),
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
pub use polkavm_derive::{polkavm_export, polkavm_import};
|
||||
|
||||
#[polkavm_derive::polkavm_define_abi(allow_extra_input_registers)]
|
||||
pub mod polkavm_abi {}
|
||||
|
||||
impl self::polkavm_abi::FromHost for *mut u8 {
|
||||
type Regs = (u32,);
|
||||
|
||||
#[inline]
|
||||
fn from_host((value,): Self::Regs) -> Self {
|
||||
value as *mut u8
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! Various utilities that help interfacing with wasm runtime code.
|
||||
|
||||
/// Pack a pointer and length into an `u64`.
|
||||
pub fn pack_ptr_and_len(ptr: u32, len: u32) -> u64 {
|
||||
// The static assertions from above are changed into a runtime check.
|
||||
#[cfg(all(substrate_runtime, feature = "disable_target_static_assertions"))]
|
||||
assert_eq!(4, core::mem::size_of::<usize>());
|
||||
|
||||
(u64::from(len) << 32) | u64::from(ptr)
|
||||
}
|
||||
|
||||
/// Unpacks an `u64` into the pointer and length.
|
||||
///
|
||||
/// Runtime API functions return a 64-bit value which encodes a pointer in the least-significant
|
||||
/// 32-bits and a length in the most-significant 32 bits. This interprets the returned value as a
|
||||
/// pointer, length tuple.
|
||||
pub fn unpack_ptr_and_len(val: u64) -> (u32, u32) {
|
||||
// The static assertions from above are changed into a runtime check.
|
||||
#[cfg(all(substrate_runtime, feature = "disable_target_static_assertions"))]
|
||||
assert_eq!(4, core::mem::size_of::<usize>());
|
||||
|
||||
let ptr = (val & (!0u32 as u64)) as u32;
|
||||
let len = (val >> 32) as u32;
|
||||
|
||||
(ptr, len)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{pack_ptr_and_len, unpack_ptr_and_len};
|
||||
|
||||
#[test]
|
||||
fn ptr_len_packing_unpacking() {
|
||||
const PTR: u32 = 0x1337;
|
||||
const LEN: u32 = 0x7f000000;
|
||||
|
||||
let packed = pack_ptr_and_len(PTR, LEN);
|
||||
let (ptr, len) = unpack_ptr_and_len(packed);
|
||||
|
||||
assert_eq!(PTR, ptr);
|
||||
assert_eq!(LEN, len);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! Traits required by the runtime interface from the wasm side.
|
||||
|
||||
use crate::RIType;
|
||||
|
||||
use core::cell::Cell;
|
||||
|
||||
/// A type used as a return value in a host function. Can be created from an FFI value.
|
||||
///
|
||||
/// Implementations are safe to assume that the `arg` given to `from_ffi_value`
|
||||
/// is only generated by the corresponding [`host::IntoFFIValue`](crate::host::IntoFFIValue)
|
||||
/// implementation.
|
||||
pub trait FromFFIValue: Sized + RIType {
|
||||
/// Create `Self::Inner` from the given FFI value.
|
||||
fn from_ffi_value(arg: Self::FFIType) -> Self::Inner;
|
||||
}
|
||||
|
||||
/// A type used as a parameter in a host function. Can be turned into an FFI value.
|
||||
pub trait IntoFFIValue: RIType {
|
||||
/// Destructor for the value passed into `into_ffi_value`.
|
||||
type Destructor;
|
||||
|
||||
/// Convert `Self::Inner` into an FFI type, with an optional destructor.
|
||||
fn into_ffi_value(value: &mut Self::Inner) -> (Self::FFIType, Self::Destructor);
|
||||
}
|
||||
|
||||
/// The state of an exchangeable function.
|
||||
#[derive(Clone, Copy)]
|
||||
enum ExchangeableFunctionState {
|
||||
/// Original function is present
|
||||
Original,
|
||||
/// The function has been replaced.
|
||||
Replaced,
|
||||
}
|
||||
|
||||
/// A function which implementation can be exchanged.
|
||||
///
|
||||
/// Internally this works by swapping function pointers.
|
||||
pub struct ExchangeableFunction<T>(Cell<(T, ExchangeableFunctionState)>);
|
||||
|
||||
impl<T> ExchangeableFunction<T> {
|
||||
/// Create a new instance of `ExchangeableFunction`.
|
||||
pub const fn new(impl_: T) -> Self {
|
||||
Self(Cell::new((impl_, ExchangeableFunctionState::Original)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> ExchangeableFunction<T> {
|
||||
/// Replace the implementation with `new_impl`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics when trying to replace an already replaced implementation.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns the original implementation wrapped in [`RestoreImplementation`].
|
||||
pub fn replace_implementation(&'static self, new_impl: T) -> RestoreImplementation<T> {
|
||||
if let ExchangeableFunctionState::Replaced = self.0.get().1 {
|
||||
panic!("Trying to replace an already replaced implementation!")
|
||||
}
|
||||
|
||||
let old = self.0.replace((new_impl, ExchangeableFunctionState::Replaced));
|
||||
|
||||
RestoreImplementation(self, Some(old.0))
|
||||
}
|
||||
|
||||
/// Restore the original implementation.
|
||||
fn restore_orig_implementation(&self, orig: T) {
|
||||
self.0.set((orig, ExchangeableFunctionState::Original));
|
||||
}
|
||||
|
||||
/// Returns the internal function pointer.
|
||||
pub fn get(&self) -> T {
|
||||
self.0.get().0
|
||||
}
|
||||
}
|
||||
|
||||
// Wasm does not support threads, so this is safe; qed.
|
||||
unsafe impl<T> Sync for ExchangeableFunction<T> {}
|
||||
|
||||
/// Restores a function implementation on drop.
|
||||
///
|
||||
/// Stores a static reference to the function object and the original implementation.
|
||||
pub struct RestoreImplementation<T: 'static + Copy>(&'static ExchangeableFunction<T>, Option<T>);
|
||||
|
||||
impl<T: Copy> Drop for RestoreImplementation<T> {
|
||||
fn drop(&mut self) {
|
||||
self.0
|
||||
.restore_orig_implementation(self.1.take().expect("Value is only taken on drop; qed"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
[package]
|
||||
name = "sp-runtime-interface-test-wasm-deprecated"
|
||||
version = "2.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
build = "build.rs"
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
sp-core = { workspace = true }
|
||||
sp-io = { workspace = true }
|
||||
sp-runtime-interface = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
substrate-wasm-builder = { optional = true, workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"sp-core/std",
|
||||
"sp-io/std",
|
||||
"sp-runtime-interface/std",
|
||||
"substrate-wasm-builder",
|
||||
]
|
||||
@@ -0,0 +1,28 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
fn main() {
|
||||
#[cfg(feature = "std")]
|
||||
{
|
||||
substrate_wasm_builder::WasmBuilder::new()
|
||||
.with_current_project()
|
||||
.export_heap_base()
|
||||
.import_memory()
|
||||
.disable_runtime_version_section_check()
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! Tests for the runtime interface traits and proc macros.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use sp_core::wasm_export_functions;
|
||||
use sp_runtime_interface::runtime_interface;
|
||||
|
||||
// Include the WASM binary
|
||||
#[cfg(feature = "std")]
|
||||
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
|
||||
|
||||
/// Wasm binary unwrapped. If built with `SKIP_WASM_BUILD`, the function panics.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn wasm_binary_unwrap() -> &'static [u8] {
|
||||
WASM_BINARY.expect(
|
||||
"Development wasm binary is not available. Testing is only supported with the flag \
|
||||
disabled.",
|
||||
)
|
||||
}
|
||||
|
||||
/// This function is not used, but we require it for the compiler to include `sp-io`.
|
||||
/// `sp-io` is required for its panic and oom handler.
|
||||
#[cfg(not(feature = "std"))]
|
||||
#[no_mangle]
|
||||
pub fn import_sp_io() {
|
||||
sp_io::misc::print_utf8(&[]);
|
||||
}
|
||||
|
||||
#[runtime_interface]
|
||||
pub trait TestApi {
|
||||
fn test_versioning(&self, _data: u32) -> bool {
|
||||
// should not be called
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
wasm_export_functions! {
|
||||
fn test_versioning_works() {
|
||||
// old api allows only 42 and 50
|
||||
assert!(test_api::test_versioning(42));
|
||||
assert!(test_api::test_versioning(50));
|
||||
|
||||
assert!(!test_api::test_versioning(142));
|
||||
assert!(!test_api::test_versioning(0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
[package]
|
||||
name = "sp-runtime-interface-test-wasm"
|
||||
version = "2.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
build = "build.rs"
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
bytes = { workspace = true }
|
||||
sp-core = { workspace = true }
|
||||
sp-io = { workspace = true }
|
||||
sp-runtime-interface = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
substrate-wasm-builder = { optional = true, workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bytes/std",
|
||||
"sp-core/std",
|
||||
"sp-io/std",
|
||||
"sp-runtime-interface/std",
|
||||
"substrate-wasm-builder",
|
||||
]
|
||||
@@ -0,0 +1,28 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
fn main() {
|
||||
#[cfg(feature = "std")]
|
||||
{
|
||||
substrate_wasm_builder::WasmBuilder::new()
|
||||
.with_current_project()
|
||||
.export_heap_base()
|
||||
.import_memory()
|
||||
.disable_runtime_version_section_check()
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,415 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! Tests for the runtime interface traits and proc macros.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use sp_runtime_interface::{
|
||||
pass_by::{
|
||||
AllocateAndReturnByCodec, AllocateAndReturnFatPointer, AllocateAndReturnPointer, PassAs,
|
||||
PassFatPointerAndDecode, PassFatPointerAndDecodeSlice, PassFatPointerAndRead,
|
||||
PassFatPointerAndReadWrite, PassPointerAndRead, PassPointerAndReadCopy,
|
||||
PassPointerAndWrite, ReturnAs,
|
||||
},
|
||||
runtime_interface,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use core::mem;
|
||||
|
||||
use alloc::{vec, vec::Vec};
|
||||
use sp_core::{sr25519::Public, wasm_export_functions};
|
||||
|
||||
// Include the WASM binary
|
||||
#[cfg(feature = "std")]
|
||||
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
|
||||
|
||||
/// Wasm binary unwrapped. If built with `SKIP_WASM_BUILD`, the function panics.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn wasm_binary_unwrap() -> &'static [u8] {
|
||||
WASM_BINARY.expect(
|
||||
"Development wasm binary is not available. Testing is only \
|
||||
supported with the flag disabled.",
|
||||
)
|
||||
}
|
||||
|
||||
/// Used in the `test_array_as_mutable_reference` test.
|
||||
const TEST_ARRAY: [u8; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
|
||||
|
||||
#[runtime_interface]
|
||||
pub trait TestApi {
|
||||
/// Returns the input data as result.
|
||||
fn return_input(data: PassFatPointerAndRead<Vec<u8>>) -> AllocateAndReturnFatPointer<Vec<u8>> {
|
||||
data
|
||||
}
|
||||
|
||||
/// Returns 16kb data.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// We return a `Vec<u32>` because this will use the code path that uses SCALE
|
||||
/// to pass the data between native/wasm. (`Vec<u8>` is passed without encoding the
|
||||
/// data)
|
||||
fn return_16kb() -> AllocateAndReturnByCodec<Vec<u32>> {
|
||||
vec![0; 4 * 1024]
|
||||
}
|
||||
|
||||
fn return_option_vec() -> AllocateAndReturnByCodec<Option<Vec<u8>>> {
|
||||
let mut vec = Vec::new();
|
||||
vec.resize(16 * 1024, 0xAA);
|
||||
Some(vec)
|
||||
}
|
||||
|
||||
fn return_option_bytes() -> AllocateAndReturnByCodec<Option<bytes::Bytes>> {
|
||||
let mut vec = Vec::new();
|
||||
vec.resize(16 * 1024, 0xAA);
|
||||
Some(vec.into())
|
||||
}
|
||||
|
||||
/// Set the storage at key with value.
|
||||
fn set_storage(
|
||||
&mut self,
|
||||
key: PassFatPointerAndRead<&[u8]>,
|
||||
data: PassFatPointerAndRead<&[u8]>,
|
||||
) {
|
||||
self.place_storage(key.to_vec(), Some(data.to_vec()));
|
||||
}
|
||||
|
||||
/// Copy `hello` into the given mutable reference
|
||||
fn return_value_into_mutable_reference(&self, data: PassFatPointerAndReadWrite<&mut [u8]>) {
|
||||
let res = "hello";
|
||||
data[..res.len()].copy_from_slice(res.as_bytes());
|
||||
}
|
||||
|
||||
/// Returns the input data wrapped in an `Option` as result.
|
||||
fn return_option_input(
|
||||
data: PassFatPointerAndRead<Vec<u8>>,
|
||||
) -> AllocateAndReturnByCodec<Option<Vec<u8>>> {
|
||||
Some(data)
|
||||
}
|
||||
|
||||
/// Get an array as input and returns a subset of this array.
|
||||
fn get_and_return_array(
|
||||
data: PassPointerAndReadCopy<[u8; 34], 34>,
|
||||
) -> AllocateAndReturnPointer<[u8; 16], 16> {
|
||||
let mut res = [0u8; 16];
|
||||
res.copy_from_slice(&data[..16]);
|
||||
res
|
||||
}
|
||||
|
||||
/// Take and fill mutable array.
|
||||
fn array_as_mutable_reference(data: PassPointerAndWrite<&mut [u8; 16], 16>) {
|
||||
data.copy_from_slice(&TEST_ARRAY);
|
||||
}
|
||||
|
||||
/// Returns the given public key as result.
|
||||
fn return_input_public_key(
|
||||
key: PassPointerAndReadCopy<Public, 32>,
|
||||
) -> AllocateAndReturnPointer<Public, 32> {
|
||||
key
|
||||
}
|
||||
|
||||
/// A function that is called with invalid utf8 data from the runtime.
|
||||
///
|
||||
/// This also checks that we accept `_` (wild card) argument names.
|
||||
fn invalid_utf8_data(_: PassFatPointerAndRead<&str>) {}
|
||||
|
||||
/// Overwrite the native implementation in wasm. The native implementation always returns
|
||||
/// `false` and the replacement function will return always `true`.
|
||||
fn overwrite_native_function_implementation() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn test_versioning(&self, data: u32) -> bool {
|
||||
data == 42 || data == 50
|
||||
}
|
||||
|
||||
#[version(2)]
|
||||
fn test_versioning(&self, data: u32) -> bool {
|
||||
data == 42
|
||||
}
|
||||
|
||||
fn test_versioning_register_only(&self, data: u32) -> bool {
|
||||
data == 80
|
||||
}
|
||||
|
||||
#[version(2, register_only)]
|
||||
fn test_versioning_register_only(&self, data: u32) -> bool {
|
||||
data == 42
|
||||
}
|
||||
|
||||
/// Returns the input values as tuple.
|
||||
fn return_input_as_tuple(
|
||||
a: PassFatPointerAndRead<Vec<u8>>,
|
||||
b: u32,
|
||||
c: PassFatPointerAndDecode<Option<Vec<u32>>>,
|
||||
d: u8,
|
||||
) -> AllocateAndReturnByCodec<(Vec<u8>, u32, Option<Vec<u32>>, u8)> {
|
||||
(a, b, c, d)
|
||||
}
|
||||
|
||||
// Host functions for testing every marshaling strategy:
|
||||
|
||||
fn pass_pointer_and_read_copy(value: PassPointerAndReadCopy<[u8; 3], 3>) {
|
||||
assert_eq!(value, [1, 2, 3]);
|
||||
}
|
||||
|
||||
fn pass_pointer_and_read(value: PassPointerAndRead<&[u8; 3], 3>) {
|
||||
assert_eq!(value, &[1, 2, 3]);
|
||||
}
|
||||
|
||||
fn pass_fat_pointer_and_read(value: PassFatPointerAndRead<&[u8]>) {
|
||||
assert_eq!(value, [1, 2, 3]);
|
||||
}
|
||||
|
||||
fn pass_fat_pointer_and_read_write(value: PassFatPointerAndReadWrite<&mut [u8]>) {
|
||||
assert_eq!(value, [1, 2, 3]);
|
||||
value.copy_from_slice(&[4, 5, 6]);
|
||||
}
|
||||
|
||||
fn pass_pointer_and_write(value: PassPointerAndWrite<&mut [u8; 3], 3>) {
|
||||
assert_eq!(*value, [0, 0, 0]);
|
||||
*value = [1, 2, 3];
|
||||
}
|
||||
|
||||
fn pass_by_codec(value: PassFatPointerAndDecode<Vec<u16>>) {
|
||||
assert_eq!(value, [1, 2, 3]);
|
||||
}
|
||||
|
||||
fn pass_slice_ref_by_codec(value: PassFatPointerAndDecodeSlice<&[u16]>) {
|
||||
assert_eq!(value, [1, 2, 3]);
|
||||
}
|
||||
|
||||
fn pass_as(value: PassAs<Opaque, u32>) {
|
||||
assert_eq!(value.0, 123);
|
||||
}
|
||||
|
||||
fn return_as() -> ReturnAs<Opaque, u32> {
|
||||
Opaque(123)
|
||||
}
|
||||
|
||||
fn allocate_and_return_pointer() -> AllocateAndReturnPointer<[u8; 3], 3> {
|
||||
[1, 2, 3]
|
||||
}
|
||||
|
||||
fn allocate_and_return_fat_pointer() -> AllocateAndReturnFatPointer<Vec<u8>> {
|
||||
vec![1, 2, 3]
|
||||
}
|
||||
|
||||
fn allocate_and_return_by_codec() -> AllocateAndReturnByCodec<Vec<u16>> {
|
||||
vec![1, 2, 3]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Opaque(u32);
|
||||
|
||||
impl From<Opaque> for u32 {
|
||||
fn from(value: Opaque) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for Opaque {
|
||||
type Error = ();
|
||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||
Ok(Opaque(value))
|
||||
}
|
||||
}
|
||||
|
||||
/// This function is not used, but we require it for the compiler to include `sp-io`.
|
||||
/// `sp-io` is required for its panic and oom handler.
|
||||
#[no_mangle]
|
||||
pub fn import_sp_io() {
|
||||
sp_io::misc::print_utf8(&[]);
|
||||
}
|
||||
|
||||
wasm_export_functions! {
|
||||
fn test_return_data() {
|
||||
let input = vec![1, 2, 3, 4, 5, 6];
|
||||
let res = test_api::return_input(input.clone());
|
||||
|
||||
assert_eq!(input, res);
|
||||
}
|
||||
|
||||
fn test_return_option_data() {
|
||||
let input = vec![1, 2, 3, 4, 5, 6];
|
||||
let res = test_api::return_option_input(input.clone());
|
||||
|
||||
assert_eq!(Some(input), res);
|
||||
}
|
||||
|
||||
fn test_set_storage() {
|
||||
let key = "hello";
|
||||
let value = "world";
|
||||
|
||||
test_api::set_storage(key.as_bytes(), value.as_bytes());
|
||||
}
|
||||
|
||||
fn test_return_value_into_mutable_reference() {
|
||||
let mut data = vec![1, 2, 3, 4, 5, 6];
|
||||
|
||||
test_api::return_value_into_mutable_reference(&mut data);
|
||||
|
||||
let expected = "hello";
|
||||
assert_eq!(expected.as_bytes(), &data[..expected.len()]);
|
||||
}
|
||||
|
||||
fn test_get_and_return_array() {
|
||||
let mut input = unsafe { mem::MaybeUninit::<[u8; 34]>::zeroed().assume_init() };
|
||||
input.copy_from_slice(&[
|
||||
24, 3, 23, 20, 2, 16, 32, 1, 12, 26, 27, 8, 29, 31, 6, 5, 4, 19, 10, 28, 34, 21, 18, 33, 9,
|
||||
13, 22, 25, 15, 11, 30, 7, 14, 17,
|
||||
]);
|
||||
|
||||
let res = test_api::get_and_return_array(input);
|
||||
|
||||
assert_eq!(&res, &input[..16]);
|
||||
}
|
||||
|
||||
fn test_array_as_mutable_reference() {
|
||||
let mut array = [0u8; 16];
|
||||
test_api::array_as_mutable_reference(&mut array);
|
||||
|
||||
assert_eq!(array, TEST_ARRAY);
|
||||
}
|
||||
|
||||
fn test_return_input_public_key() {
|
||||
let key = Public::try_from(
|
||||
&[
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
|
||||
25, 26, 27, 28, 29, 30, 31, 32,
|
||||
][..],
|
||||
).unwrap();
|
||||
let ret_key = test_api::return_input_public_key(key.clone());
|
||||
|
||||
let key_data: &[u8] = key.as_ref();
|
||||
let ret_key_data: &[u8] = ret_key.as_ref();
|
||||
assert_eq!(key_data, ret_key_data);
|
||||
}
|
||||
|
||||
fn test_invalid_utf8_data_should_return_an_error() {
|
||||
let data = vec![0, 159, 146, 150];
|
||||
// I'm an evil hacker, trying to hack!
|
||||
let data_str = unsafe { alloc::str::from_utf8_unchecked(&data) };
|
||||
|
||||
test_api::invalid_utf8_data(data_str);
|
||||
}
|
||||
|
||||
fn test_overwrite_native_function_implementation() {
|
||||
fn new_implementation() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
// Check native implementation
|
||||
assert!(!test_api::overwrite_native_function_implementation());
|
||||
|
||||
let _guard = test_api::host_overwrite_native_function_implementation
|
||||
.replace_implementation(new_implementation);
|
||||
|
||||
assert!(test_api::overwrite_native_function_implementation());
|
||||
}
|
||||
|
||||
fn test_vec_return_value_memory_is_freed() {
|
||||
let mut len = 0;
|
||||
for _ in 0..1024 {
|
||||
len += test_api::return_16kb().len();
|
||||
}
|
||||
assert_eq!(1024 * 1024 * 4, len);
|
||||
}
|
||||
|
||||
fn test_encoded_return_value_memory_is_freed() {
|
||||
let mut len = 0;
|
||||
for _ in 0..1024 {
|
||||
len += test_api::return_option_input(vec![0; 16 * 1024]).map(|v| v.len()).unwrap();
|
||||
}
|
||||
assert_eq!(1024 * 1024 * 16, len);
|
||||
}
|
||||
|
||||
fn test_array_return_value_memory_is_freed() {
|
||||
let mut len = 0;
|
||||
for _ in 0..1024 * 1024 {
|
||||
len += test_api::get_and_return_array([0; 34])[1];
|
||||
}
|
||||
assert_eq!(0, len);
|
||||
}
|
||||
|
||||
fn test_versioning_works() {
|
||||
// we fix new api to accept only 42 as a proper input
|
||||
// as opposed to sp-runtime-interface-test-wasm-deprecated::test_api::verify_input
|
||||
// which accepted 42 and 50.
|
||||
assert!(test_api::test_versioning(42));
|
||||
|
||||
assert!(!test_api::test_versioning(50));
|
||||
assert!(!test_api::test_versioning(102));
|
||||
}
|
||||
|
||||
fn test_versioning_register_only_works() {
|
||||
// Ensure that we will import the version of the runtime interface function that
|
||||
// isn't tagged with `register_only`.
|
||||
assert!(!test_api::test_versioning_register_only(42));
|
||||
assert!(test_api::test_versioning_register_only(80));
|
||||
}
|
||||
|
||||
fn test_return_input_as_tuple() {
|
||||
let a = vec![1, 3, 4, 5];
|
||||
let b = 10000;
|
||||
let c = Some(vec![2, 3]);
|
||||
let d = 5;
|
||||
|
||||
let res = test_api::return_input_as_tuple(a.clone(), b, c.clone(), d);
|
||||
|
||||
assert_eq!(a, res.0);
|
||||
assert_eq!(b, res.1);
|
||||
assert_eq!(c, res.2);
|
||||
assert_eq!(d, res.3);
|
||||
}
|
||||
|
||||
fn test_return_option_vec() {
|
||||
test_api::return_option_vec();
|
||||
}
|
||||
|
||||
fn test_return_option_bytes() {
|
||||
test_api::return_option_bytes();
|
||||
}
|
||||
|
||||
fn test_marshalling_strategies() {
|
||||
test_api::pass_pointer_and_read_copy([1_u8, 2, 3]);
|
||||
test_api::pass_pointer_and_read(&[1_u8, 2, 3]);
|
||||
test_api::pass_fat_pointer_and_read(&[1_u8, 2, 3][..]);
|
||||
{
|
||||
let mut slice = [1_u8, 2, 3];
|
||||
test_api::pass_fat_pointer_and_read_write(&mut slice);
|
||||
assert_eq!(slice, [4_u8, 5, 6]);
|
||||
}
|
||||
{
|
||||
let mut slice = [9_u8, 9, 9];
|
||||
test_api::pass_pointer_and_write(&mut slice);
|
||||
assert_eq!(slice, [1_u8, 2, 3]);
|
||||
}
|
||||
test_api::pass_by_codec(vec![1_u16, 2, 3]);
|
||||
test_api::pass_slice_ref_by_codec(&[1_u16, 2, 3][..]);
|
||||
test_api::pass_as(Opaque(123));
|
||||
assert_eq!(test_api::return_as(), Opaque(123));
|
||||
assert_eq!(test_api::allocate_and_return_pointer(), [1_u8, 2, 3]);
|
||||
assert_eq!(test_api::allocate_and_return_fat_pointer(), vec![1_u8, 2, 3]);
|
||||
assert_eq!(test_api::allocate_and_return_by_codec(), vec![1_u16, 2, 3]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
[package]
|
||||
name = "sp-runtime-interface-test"
|
||||
version = "2.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
publish = false
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
sc-executor = { workspace = true, default-features = true }
|
||||
sc-executor-common = { workspace = true, default-features = true }
|
||||
sp-io = { workspace = true, default-features = true }
|
||||
sp-runtime = { workspace = true, default-features = true }
|
||||
sp-runtime-interface = { workspace = true, default-features = true }
|
||||
sp-runtime-interface-test-wasm = { workspace = true }
|
||||
sp-runtime-interface-test-wasm-deprecated = { workspace = true }
|
||||
sp-state-machine = { workspace = true, default-features = true }
|
||||
tracing = { workspace = true, default-features = true }
|
||||
tracing-core = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
runtime-benchmarks = [
|
||||
"sc-executor/runtime-benchmarks",
|
||||
"sp-io/runtime-benchmarks",
|
||||
"sp-runtime-interface/runtime-benchmarks",
|
||||
"sp-runtime/runtime-benchmarks",
|
||||
"sp-state-machine/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,302 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! Integration tests for runtime interface primitives
|
||||
#![cfg(test)]
|
||||
|
||||
use sp_runtime_interface::*;
|
||||
|
||||
use sp_runtime_interface_test_wasm::{test_api::HostFunctions, wasm_binary_unwrap};
|
||||
use sp_runtime_interface_test_wasm_deprecated::wasm_binary_unwrap as wasm_binary_deprecated_unwrap;
|
||||
|
||||
use sc_executor_common::{runtime_blob::RuntimeBlob, wasm_runtime::AllocationStats};
|
||||
use sp_wasm_interface::{ExtendedHostFunctions, HostFunctions as HostFunctionsT};
|
||||
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
type TestExternalities = sp_state_machine::TestExternalities<sp_runtime::traits::BlakeTwo256>;
|
||||
|
||||
fn call_wasm_method_with_result<HF: HostFunctionsT>(
|
||||
binary: &[u8],
|
||||
method: &str,
|
||||
) -> (Result<TestExternalities, String>, Option<AllocationStats>) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext_ext = ext.ext();
|
||||
|
||||
let executor = sc_executor::WasmExecutor::<
|
||||
ExtendedHostFunctions<sp_io::SubstrateHostFunctions, HF>,
|
||||
>::builder()
|
||||
.build();
|
||||
|
||||
let (result, allocation_stats) = executor.uncached_call_with_allocation_stats(
|
||||
RuntimeBlob::uncompress_if_needed(binary).expect("Failed to parse binary"),
|
||||
&mut ext_ext,
|
||||
false,
|
||||
method,
|
||||
&[],
|
||||
);
|
||||
let result = result
|
||||
.map_err(|e| format!("Failed to execute `{}`: {}", method, e))
|
||||
.map(|_| ext);
|
||||
(result, allocation_stats)
|
||||
}
|
||||
|
||||
fn call_wasm_method<HF: HostFunctionsT>(binary: &[u8], method: &str) -> TestExternalities {
|
||||
call_wasm_method_with_result::<HF>(binary, method).0.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_return_data() {
|
||||
call_wasm_method::<HostFunctions>(wasm_binary_unwrap(), "test_return_data");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_return_option_data() {
|
||||
call_wasm_method::<HostFunctions>(wasm_binary_unwrap(), "test_return_option_data");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_storage() {
|
||||
let mut ext = call_wasm_method::<HostFunctions>(wasm_binary_unwrap(), "test_set_storage");
|
||||
|
||||
let expected = "world";
|
||||
assert_eq!(expected.as_bytes(), &ext.ext().storage("hello".as_bytes()).unwrap()[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_return_value_into_mutable_reference() {
|
||||
call_wasm_method::<HostFunctions>(
|
||||
wasm_binary_unwrap(),
|
||||
"test_return_value_into_mutable_reference",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_and_return_array() {
|
||||
call_wasm_method::<HostFunctions>(wasm_binary_unwrap(), "test_get_and_return_array");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_array_as_mutable_reference() {
|
||||
call_wasm_method::<HostFunctions>(wasm_binary_unwrap(), "test_array_as_mutable_reference");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_return_input_public_key() {
|
||||
call_wasm_method::<HostFunctions>(wasm_binary_unwrap(), "test_return_input_public_key");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn host_function_not_found() {
|
||||
let err = call_wasm_method_with_result::<()>(wasm_binary_unwrap(), "test_return_data")
|
||||
.0
|
||||
.unwrap_err();
|
||||
|
||||
assert!(err.contains("test_return_data"));
|
||||
assert!(err.contains(" Failed to create module"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_utf8_data_should_return_an_error() {
|
||||
call_wasm_method_with_result::<HostFunctions>(
|
||||
wasm_binary_unwrap(),
|
||||
"test_invalid_utf8_data_should_return_an_error",
|
||||
)
|
||||
.0
|
||||
.unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_overwrite_native_function_implementation() {
|
||||
call_wasm_method::<HostFunctions>(
|
||||
wasm_binary_unwrap(),
|
||||
"test_overwrite_native_function_implementation",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vec_return_value_memory_is_freed() {
|
||||
call_wasm_method::<HostFunctions>(
|
||||
wasm_binary_unwrap(),
|
||||
"test_vec_return_value_memory_is_freed",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encoded_return_value_memory_is_freed() {
|
||||
call_wasm_method::<HostFunctions>(
|
||||
wasm_binary_unwrap(),
|
||||
"test_encoded_return_value_memory_is_freed",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_array_return_value_memory_is_freed() {
|
||||
call_wasm_method::<HostFunctions>(
|
||||
wasm_binary_unwrap(),
|
||||
"test_array_return_value_memory_is_freed",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_versioning_with_new_host_works() {
|
||||
// We call to the new wasm binary with new host function.
|
||||
call_wasm_method::<HostFunctions>(wasm_binary_unwrap(), "test_versioning_works");
|
||||
|
||||
// we call to the old wasm binary with a new host functions
|
||||
// old versions of host functions should be called and test should be ok!
|
||||
call_wasm_method::<HostFunctions>(wasm_binary_deprecated_unwrap(), "test_versioning_works");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_versioning_register_only() {
|
||||
call_wasm_method::<HostFunctions>(wasm_binary_unwrap(), "test_versioning_register_only_works");
|
||||
}
|
||||
|
||||
fn run_test_in_another_process(
|
||||
test_name: &str,
|
||||
test_body: impl FnOnce(),
|
||||
) -> Option<std::process::Output> {
|
||||
if std::env::var("RUN_FORKED_TEST").is_ok() {
|
||||
test_body();
|
||||
None
|
||||
} else {
|
||||
let output = std::process::Command::new(std::env::current_exe().unwrap())
|
||||
.arg(test_name)
|
||||
.env("RUN_FORKED_TEST", "1")
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
assert!(output.status.success());
|
||||
Some(output)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tracing() {
|
||||
// Run in a different process to ensure that the `Span` is registered with our local
|
||||
// `TracingSubscriber`.
|
||||
run_test_in_another_process("test_tracing", || {
|
||||
use std::fmt;
|
||||
use tracing::span::Id as SpanId;
|
||||
use tracing_core::field::{Field, Visit};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TracingSubscriber(Arc<Mutex<Inner>>);
|
||||
|
||||
struct FieldConsumer(&'static str, Option<String>);
|
||||
impl Visit for FieldConsumer {
|
||||
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
|
||||
if field.name() == self.0 {
|
||||
self.1 = Some(format!("{:?}", value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Inner {
|
||||
spans: HashSet<String>,
|
||||
}
|
||||
|
||||
impl tracing::subscriber::Subscriber for TracingSubscriber {
|
||||
fn enabled(&self, _: &tracing::Metadata) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn new_span(&self, span: &tracing::span::Attributes) -> tracing::Id {
|
||||
let mut inner = self.0.lock().unwrap();
|
||||
let id = SpanId::from_u64((inner.spans.len() + 1) as _);
|
||||
let mut f = FieldConsumer("name", None);
|
||||
span.record(&mut f);
|
||||
inner.spans.insert(f.1.unwrap_or_else(|| span.metadata().name().to_owned()));
|
||||
id
|
||||
}
|
||||
|
||||
fn record(&self, _: &SpanId, _: &tracing::span::Record) {}
|
||||
|
||||
fn record_follows_from(&self, _: &SpanId, _: &SpanId) {}
|
||||
|
||||
fn event(&self, _: &tracing::Event) {}
|
||||
|
||||
fn enter(&self, _: &SpanId) {}
|
||||
|
||||
fn exit(&self, _: &SpanId) {}
|
||||
}
|
||||
|
||||
let subscriber = TracingSubscriber(Default::default());
|
||||
let _guard = tracing::subscriber::set_default(subscriber.clone());
|
||||
|
||||
// Call some method to generate a trace
|
||||
call_wasm_method::<HostFunctions>(wasm_binary_unwrap(), "test_return_data");
|
||||
|
||||
let inner = subscriber.0.lock().unwrap();
|
||||
assert!(inner.spans.contains("return_input_version_1"));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_return_input_as_tuple() {
|
||||
call_wasm_method::<HostFunctions>(wasm_binary_unwrap(), "test_return_input_as_tuple");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_returning_option_bytes_from_a_host_function_is_efficient() {
|
||||
let (result, stats_vec) = call_wasm_method_with_result::<HostFunctions>(
|
||||
wasm_binary_unwrap(),
|
||||
"test_return_option_vec",
|
||||
);
|
||||
result.unwrap();
|
||||
let (result, stats_bytes) = call_wasm_method_with_result::<HostFunctions>(
|
||||
wasm_binary_unwrap(),
|
||||
"test_return_option_bytes",
|
||||
);
|
||||
result.unwrap();
|
||||
|
||||
let stats_vec = stats_vec.unwrap();
|
||||
let stats_bytes = stats_bytes.unwrap();
|
||||
|
||||
// The way we currently implement marshaling of `Option<Vec<u8>>` through
|
||||
// the WASM FFI boundary from the host to the runtime requires that it is
|
||||
// marshaled through SCALE. This is quite inefficient as it requires two
|
||||
// memory allocations inside of the runtime:
|
||||
//
|
||||
// 1) the first allocation to copy the SCALE-encoded blob into the runtime;
|
||||
// 2) and another allocation for the resulting `Vec<u8>` when decoding that blob.
|
||||
//
|
||||
// Both of these allocations are are as big as the `Vec<u8>` which is being
|
||||
// passed to the runtime. This is especially bad when fetching big values
|
||||
// from storage, as it can lead to an out-of-memory situation.
|
||||
//
|
||||
// Our `Option<Bytes>` marshaling is better; it still must go through SCALE,
|
||||
// and it still requires two allocations, however since `Bytes` is zero-copy
|
||||
// only the first allocation is `Vec<u8>`-sized, and the second allocation
|
||||
// which creates the deserialized `Bytes` is tiny, and is only necessary because
|
||||
// the underlying `Bytes` buffer from which we're deserializing gets automatically
|
||||
// turned into an `Arc`.
|
||||
//
|
||||
// So this assertion tests that deserializing `Option<Bytes>` allocates less than
|
||||
// deserializing `Option<Vec<u8>>`.
|
||||
assert_eq!(stats_bytes.bytes_allocated_sum + 16 * 1024 + 8, stats_vec.bytes_allocated_sum);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_marshalling_strategies() {
|
||||
call_wasm_method::<HostFunctions>(wasm_binary_unwrap(), "test_marshalling_strategies");
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
#[rustversion::attr(not(stable), ignore)]
|
||||
#[test]
|
||||
fn ui() {
|
||||
// Only run the ui tests when `RUN_UI_TESTS` is set.
|
||||
if std::env::var("RUN_UI_TESTS").is_err() {
|
||||
return;
|
||||
}
|
||||
|
||||
// As trybuild is using `cargo check`, we don't need the real WASM binaries.
|
||||
std::env::set_var("SKIP_WASM_BUILD", "1");
|
||||
|
||||
let t = trybuild::TestCases::new();
|
||||
t.compile_fail("tests/ui/*.rs");
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
mod test {
|
||||
#![allow(unexpected_cfgs)]
|
||||
|
||||
use sp_runtime_interface::runtime_interface;
|
||||
|
||||
#[runtime_interface]
|
||||
trait Test {
|
||||
fn test(&self, a: Option<u8>) -> Option<u8> {}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,154 @@
|
||||
error[E0277]: the trait bound `Option<u8>: RIType` is not satisfied
|
||||
--> tests/ui/improperly_wrapped_ri_type.rs:23:2
|
||||
|
|
||||
23 | #[runtime_interface]
|
||||
| ^^^^^^^^^^^^^^^^^^^^ the trait `RIType` is not implemented for `Option<u8>`
|
||||
|
|
||||
= help: the following other types implement trait `RIType`:
|
||||
AllocateAndReturnByCodec<T>
|
||||
AllocateAndReturnFatPointer<T>
|
||||
AllocateAndReturnPointer<T, N>
|
||||
PassAs<T, U>
|
||||
PassFatPointerAndDecode<T>
|
||||
PassFatPointerAndDecodeSlice<T>
|
||||
PassFatPointerAndRead<T>
|
||||
PassFatPointerAndReadWrite<T>
|
||||
and $N others
|
||||
= note: this error originates in the attribute macro `runtime_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `Option<u8>: RIType` is not satisfied
|
||||
--> tests/ui/improperly_wrapped_ri_type.rs:23:2
|
||||
|
|
||||
23 | #[runtime_interface]
|
||||
| ^^^^^^^^^^^^^^^^^^^^ the trait `RIType` is not implemented for `Option<u8>`
|
||||
|
|
||||
= help: the following other types implement trait `RIType`:
|
||||
AllocateAndReturnByCodec<T>
|
||||
AllocateAndReturnFatPointer<T>
|
||||
AllocateAndReturnPointer<T, N>
|
||||
PassAs<T, U>
|
||||
PassFatPointerAndDecode<T>
|
||||
PassFatPointerAndDecodeSlice<T>
|
||||
PassFatPointerAndRead<T>
|
||||
PassFatPointerAndReadWrite<T>
|
||||
and $N others
|
||||
|
||||
error[E0277]: the trait bound `Option<u8>: RIType` is not satisfied
|
||||
--> tests/ui/improperly_wrapped_ri_type.rs:25:24
|
||||
|
|
||||
25 | fn test(&self, a: Option<u8>) -> Option<u8> {}
|
||||
| ^ the trait `RIType` is not implemented for `Option<u8>`
|
||||
|
|
||||
= help: the following other types implement trait `RIType`:
|
||||
AllocateAndReturnByCodec<T>
|
||||
AllocateAndReturnFatPointer<T>
|
||||
AllocateAndReturnPointer<T, N>
|
||||
PassAs<T, U>
|
||||
PassFatPointerAndDecode<T>
|
||||
PassFatPointerAndDecodeSlice<T>
|
||||
PassFatPointerAndRead<T>
|
||||
PassFatPointerAndReadWrite<T>
|
||||
and $N others
|
||||
|
||||
error[E0277]: the trait bound `Option<u8>: FromFFIValue<'_>` is not satisfied
|
||||
--> tests/ui/improperly_wrapped_ri_type.rs:25:27
|
||||
|
|
||||
25 | fn test(&self, a: Option<u8>) -> Option<u8> {}
|
||||
| ^^^^^^^^^^ the trait `FromFFIValue<'_>` is not implemented for `Option<u8>`
|
||||
|
|
||||
= help: the following other types implement trait `FromFFIValue<'a>`:
|
||||
PassAs<T, U>
|
||||
PassFatPointerAndDecode<T>
|
||||
PassFatPointerAndDecodeSlice<&'a [T]>
|
||||
PassFatPointerAndRead<&'a [u8]>
|
||||
PassFatPointerAndRead<&'a str>
|
||||
PassFatPointerAndRead<Vec<u8>>
|
||||
PassFatPointerAndReadWrite<&'a mut [u8]>
|
||||
PassPointerAndRead<&'a T, N>
|
||||
and $N others
|
||||
|
||||
error[E0277]: the trait bound `Option<u8>: IntoFFIValue` is not satisfied
|
||||
--> tests/ui/improperly_wrapped_ri_type.rs:25:42
|
||||
|
|
||||
25 | fn test(&self, a: Option<u8>) -> Option<u8> {}
|
||||
| ^^^^^^^^^^ the trait `IntoFFIValue` is not implemented for `Option<u8>`
|
||||
|
|
||||
= help: the following other types implement trait `IntoFFIValue`:
|
||||
AllocateAndReturnByCodec<T>
|
||||
AllocateAndReturnFatPointer<T>
|
||||
AllocateAndReturnPointer<T, N>
|
||||
ReturnAs<T, U>
|
||||
bool
|
||||
i16
|
||||
i32
|
||||
i64
|
||||
and $N others
|
||||
|
||||
error[E0277]: the trait bound `Option<u8>: FromFFIValue<'_>` is not satisfied
|
||||
--> tests/ui/improperly_wrapped_ri_type.rs:25:24
|
||||
|
|
||||
25 | fn test(&self, a: Option<u8>) -> Option<u8> {}
|
||||
| ^ the trait `FromFFIValue<'_>` is not implemented for `Option<u8>`
|
||||
|
|
||||
= help: the following other types implement trait `FromFFIValue<'a>`:
|
||||
PassAs<T, U>
|
||||
PassFatPointerAndDecode<T>
|
||||
PassFatPointerAndDecodeSlice<&'a [T]>
|
||||
PassFatPointerAndRead<&'a [u8]>
|
||||
PassFatPointerAndRead<&'a str>
|
||||
PassFatPointerAndRead<Vec<u8>>
|
||||
PassFatPointerAndReadWrite<&'a mut [u8]>
|
||||
PassPointerAndRead<&'a T, N>
|
||||
and $N others
|
||||
|
||||
error[E0277]: the trait bound `Option<u8>: FromFFIValue<'_>` is not satisfied
|
||||
--> tests/ui/improperly_wrapped_ri_type.rs:23:2
|
||||
|
|
||||
23 | #[runtime_interface]
|
||||
| ^^^^^^^^^^^^^^^^^^^^ the trait `FromFFIValue<'_>` is not implemented for `Option<u8>`
|
||||
|
|
||||
= help: the following other types implement trait `FromFFIValue<'a>`:
|
||||
PassAs<T, U>
|
||||
PassFatPointerAndDecode<T>
|
||||
PassFatPointerAndDecodeSlice<&'a [T]>
|
||||
PassFatPointerAndRead<&'a [u8]>
|
||||
PassFatPointerAndRead<&'a str>
|
||||
PassFatPointerAndRead<Vec<u8>>
|
||||
PassFatPointerAndReadWrite<&'a mut [u8]>
|
||||
PassPointerAndRead<&'a T, N>
|
||||
and $N others
|
||||
= note: this error originates in the attribute macro `runtime_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `Option<u8>: RIType` is not satisfied
|
||||
--> tests/ui/improperly_wrapped_ri_type.rs:25:27
|
||||
|
|
||||
25 | fn test(&self, a: Option<u8>) -> Option<u8> {}
|
||||
| ^^^^^^^^^^ the trait `RIType` is not implemented for `Option<u8>`
|
||||
|
|
||||
= help: the following other types implement trait `RIType`:
|
||||
AllocateAndReturnByCodec<T>
|
||||
AllocateAndReturnFatPointer<T>
|
||||
AllocateAndReturnPointer<T, N>
|
||||
PassAs<T, U>
|
||||
PassFatPointerAndDecode<T>
|
||||
PassFatPointerAndDecodeSlice<T>
|
||||
PassFatPointerAndRead<T>
|
||||
PassFatPointerAndReadWrite<T>
|
||||
and $N others
|
||||
|
||||
error[E0277]: the trait bound `Option<u8>: RIType` is not satisfied
|
||||
--> tests/ui/improperly_wrapped_ri_type.rs:25:42
|
||||
|
|
||||
25 | fn test(&self, a: Option<u8>) -> Option<u8> {}
|
||||
| ^^^^^^^^^^ the trait `RIType` is not implemented for `Option<u8>`
|
||||
|
|
||||
= help: the following other types implement trait `RIType`:
|
||||
AllocateAndReturnByCodec<T>
|
||||
AllocateAndReturnFatPointer<T>
|
||||
AllocateAndReturnPointer<T, N>
|
||||
PassAs<T, U>
|
||||
PassFatPointerAndDecode<T>
|
||||
PassFatPointerAndDecodeSlice<T>
|
||||
PassFatPointerAndRead<T>
|
||||
PassFatPointerAndReadWrite<T>
|
||||
and $N others
|
||||
@@ -0,0 +1,28 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 sp_runtime_interface::runtime_interface;
|
||||
|
||||
#[runtime_interface]
|
||||
trait Test {
|
||||
#[version(2)]
|
||||
fn test() { }
|
||||
#[version(2)]
|
||||
fn test() { }
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,11 @@
|
||||
error: Duplicated version attribute
|
||||
--> tests/ui/no_duplicate_versions.rs:24:2
|
||||
|
|
||||
24 | #[version(2)]
|
||||
| ^
|
||||
|
||||
error: Previous version with the same number defined here
|
||||
--> tests/ui/no_duplicate_versions.rs:22:2
|
||||
|
|
||||
22 | #[version(2)]
|
||||
| ^
|
||||
@@ -0,0 +1,39 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
// Due to https://github.com/dtolnay/trybuild/issues/302, the `substrate_runtime`
|
||||
// cfg is also unexpected. This is a `trybuild` bug, and this part of error is not
|
||||
// exposed to a real developer.
|
||||
|
||||
use sp_runtime_interface::runtime_interface;
|
||||
|
||||
#[runtime_interface]
|
||||
trait Test {
|
||||
fn foo() {}
|
||||
|
||||
#[cfg(feature = "bar-feature")]
|
||||
fn bar() {}
|
||||
|
||||
#[cfg(not(feature = "bar-feature"))]
|
||||
fn qux() {}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test::foo();
|
||||
test::bar();
|
||||
test::qux();
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
error[E0425]: cannot find function `bar` in module `test`
|
||||
--> tests/ui/no_feature_gated_method.rs:37:8
|
||||
|
|
||||
37 | test::bar();
|
||||
| ^^^ not found in `test`
|
||||
|
|
||||
note: found an item that was configured out
|
||||
--> tests/ui/no_feature_gated_method.rs:29:5
|
||||
|
|
||||
29 | fn bar() {}
|
||||
| ^^^
|
||||
note: the item is gated behind the `bar-feature` feature
|
||||
--> tests/ui/no_feature_gated_method.rs:28:8
|
||||
|
|
||||
28 | #[cfg(feature = "bar-feature")]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
note: found an item that was configured out
|
||||
--> tests/ui/no_feature_gated_method.rs:29:5
|
||||
|
|
||||
29 | fn bar() {}
|
||||
| ^^^
|
||||
note: the item is gated here
|
||||
--> tests/ui/no_feature_gated_method.rs:24:1
|
||||
|
|
||||
24 | #[runtime_interface]
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
= note: this error originates in the attribute macro `runtime_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
warning: unexpected `cfg` condition name: `substrate_runtime`
|
||||
--> tests/ui/no_feature_gated_method.rs:28:2
|
||||
|
|
||||
28 | #[cfg(feature = "bar-feature")]
|
||||
| ^
|
||||
|
|
||||
= help: expected names are: `docsrs`, `feature`, and `test` and 31 more
|
||||
= help: consider using a Cargo feature instead
|
||||
= help: or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(substrate_runtime)'] }
|
||||
= help: or consider adding `println!("cargo::rustc-check-cfg=cfg(substrate_runtime)");` to the top of the `build.rs`
|
||||
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg/cargo-specifics.html> for more information about checking conditional configuration
|
||||
= note: `#[warn(unexpected_cfgs)]` on by default
|
||||
|
||||
warning: unexpected `cfg` condition value: `bar-feature`
|
||||
--> tests/ui/no_feature_gated_method.rs:28:8
|
||||
|
|
||||
28 | #[cfg(feature = "bar-feature")]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: expected values for `feature` are: `default`, `disable_target_static_assertions`, and `std`
|
||||
= help: consider adding `bar-feature` as a feature in `Cargo.toml`
|
||||
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg/cargo-specifics.html> for more information about checking conditional configuration
|
||||
|
||||
warning: unexpected `cfg` condition name: `substrate_runtime`
|
||||
--> tests/ui/no_feature_gated_method.rs:24:1
|
||||
|
|
||||
24 | #[runtime_interface]
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: using a cfg inside a attribute macro will use the cfgs from the destination crate and not the ones from the defining crate
|
||||
= help: try referring to `runtime_interface` crate for guidance on how handle this unexpected cfg
|
||||
= help: the attribute macro `runtime_interface` may come from an old version of the `sp_runtime_interface_proc_macro` crate, try updating your dependency with `cargo update -p sp_runtime_interface_proc_macro`
|
||||
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg/cargo-specifics.html> for more information about checking conditional configuration
|
||||
= note: this warning originates in the attribute macro `runtime_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
warning: unexpected `cfg` condition name: `substrate_runtime`
|
||||
--> tests/ui/no_feature_gated_method.rs:26:2
|
||||
|
|
||||
26 | fn foo() {}
|
||||
| ^^
|
||||
|
|
||||
= help: consider using a Cargo feature instead
|
||||
= help: or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(substrate_runtime)'] }
|
||||
= help: or consider adding `println!("cargo::rustc-check-cfg=cfg(substrate_runtime)");` to the top of the `build.rs`
|
||||
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg/cargo-specifics.html> for more information about checking conditional configuration
|
||||
|
||||
warning: unexpected `cfg` condition name: `substrate_runtime`
|
||||
--> tests/ui/no_feature_gated_method.rs:31:2
|
||||
|
|
||||
31 | #[cfg(not(feature = "bar-feature"))]
|
||||
| ^
|
||||
|
|
||||
= help: consider using a Cargo feature instead
|
||||
= help: or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(substrate_runtime)'] }
|
||||
= help: or consider adding `println!("cargo::rustc-check-cfg=cfg(substrate_runtime)");` to the top of the `build.rs`
|
||||
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg/cargo-specifics.html> for more information about checking conditional configuration
|
||||
|
||||
warning: unexpected `cfg` condition value: `bar-feature`
|
||||
--> tests/ui/no_feature_gated_method.rs:31:12
|
||||
|
|
||||
31 | #[cfg(not(feature = "bar-feature"))]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: expected values for `feature` are: `default`, `disable_target_static_assertions`, and `std`
|
||||
= help: consider adding `bar-feature` as a feature in `Cargo.toml`
|
||||
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg/cargo-specifics.html> for more information about checking conditional configuration
|
||||
@@ -0,0 +1,34 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 sp_runtime_interface::runtime_interface;
|
||||
|
||||
#[runtime_interface]
|
||||
trait Test {
|
||||
#[version(1)]
|
||||
fn test2() {}
|
||||
#[version(2)]
|
||||
fn test2() {}
|
||||
#[version(3)]
|
||||
fn test2() {}
|
||||
|
||||
fn test() { }
|
||||
#[version(3)]
|
||||
fn test() { }
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Unexpected version attribute: missing version '2' for this function
|
||||
--> tests/ui/no_gaps_in_versions.rs:30:2
|
||||
|
|
||||
30 | #[version(3)]
|
||||
| ^
|
||||
@@ -0,0 +1,25 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 sp_runtime_interface::runtime_interface;
|
||||
|
||||
#[runtime_interface]
|
||||
trait Test {
|
||||
fn test<T>() {}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Generic parameters not supported.
|
||||
--> tests/ui/no_generic_parameters_method.rs:22:10
|
||||
|
|
||||
22 | fn test<T>() {}
|
||||
| ^
|
||||
@@ -0,0 +1,25 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 sp_runtime_interface::runtime_interface;
|
||||
|
||||
#[runtime_interface]
|
||||
trait Test<T> {
|
||||
fn test() {}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Generic parameters not supported.
|
||||
--> tests/ui/no_generic_parameters_trait.rs:21:12
|
||||
|
|
||||
21 | trait Test<T> {
|
||||
| ^
|
||||
@@ -0,0 +1,25 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 sp_runtime_interface::runtime_interface;
|
||||
|
||||
#[runtime_interface]
|
||||
trait Test {
|
||||
fn test();
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Methods need to have an implementation.
|
||||
--> tests/ui/no_method_implementation.rs:22:2
|
||||
|
|
||||
22 | fn test();
|
||||
| ^^
|
||||
@@ -0,0 +1,29 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 sp_runtime_interface::runtime_interface;
|
||||
|
||||
#[runtime_interface]
|
||||
trait Test {
|
||||
fn foo() {}
|
||||
|
||||
#[version(2)]
|
||||
#[cfg(feature = "foo-feature")]
|
||||
fn foo() {}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Conditional compilation is not supported for versioned functions
|
||||
--> tests/ui/no_versioned_conditional_build.rs:24:2
|
||||
|
|
||||
24 | #[version(2)]
|
||||
| ^
|
||||
@@ -0,0 +1,25 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 sp_runtime_interface::runtime_interface;
|
||||
|
||||
#[runtime_interface]
|
||||
trait Test {
|
||||
fn test(self) {}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Taking `Self` by value is not allowed.
|
||||
--> tests/ui/take_self_by_value.rs:22:10
|
||||
|
|
||||
22 | fn test(self) {}
|
||||
| ^^^^
|
||||
Reference in New Issue
Block a user