feat: initialize Kurdistan SDK - independent fork of Polkadot SDK

This commit is contained in:
2025-12-13 15:44:15 +03:00
commit e4778b4576
6838 changed files with 1847450 additions and 0 deletions
@@ -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 &#124; v.as_ptr() 32bit</code> |
| `&[u8]` | `u64` | <code>v.len() 32bit << 32 &#124; v.as_ptr() 32bit</code> |
| `Vec<u8>` | `u64` | <code>v.len() 32bit << 32 &#124; v.as_ptr() 32bit</code> |
| `Vec<T> where T: Encode` | `u64` | `let e = v.encode();`<br><br><code>e.len() 32bit << 32 &#124; e.as_ptr() 32bit</code> |
| `&[T] where T: Encode` | `u64` | `let e = v.encode();`<br><br><code>e.len() 32bit << 32 &#124; 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 &#124; 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 &#124; 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()
}
@@ -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(_)))
}
@@ -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)
}
@@ -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) {}
| ^^^^