diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index f7bf247e02..ba96516dd7 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -7442,6 +7442,7 @@ dependencies = [ "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-test-wasm", + "sp-runtime-interface-test-wasm-deprecated", "sp-state-machine", ] @@ -7456,6 +7457,17 @@ dependencies = [ "substrate-wasm-builder-runner", ] +[[package]] +name = "sp-runtime-interface-test-wasm-deprecated" +version = "2.0.0-dev" +dependencies = [ + "sp-core", + "sp-io", + "sp-runtime-interface", + "sp-std", + "substrate-wasm-builder-runner", +] + [[package]] name = "sp-sandbox" version = "0.8.0-alpha.4" diff --git a/substrate/Cargo.toml b/substrate/Cargo.toml index 5a88ed28dd..d86bab8abd 100644 --- a/substrate/Cargo.toml +++ b/substrate/Cargo.toml @@ -134,6 +134,7 @@ members = [ "primitives/runtime-interface", "primitives/runtime-interface/proc-macro", "primitives/runtime-interface/test-wasm", + "primitives/runtime-interface/test-wasm-deprecated", "primitives/runtime-interface/test", "primitives/serializer", "primitives/session", diff --git a/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/bare_function_interface.rs b/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/bare_function_interface.rs index bdddc5eba7..e7c34fbf99 100644 --- a/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/bare_function_interface.rs +++ b/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/bare_function_interface.rs @@ -30,7 +30,7 @@ use crate::utils::{ generate_crate_access, create_exchangeable_host_function_ident, get_function_arguments, - get_function_argument_names, get_trait_methods, + get_function_argument_names, get_runtime_interface, create_function_ident_with_version, }; use syn::{ @@ -47,19 +47,40 @@ use std::iter; /// of the trait method. pub fn generate(trait_def: &ItemTrait, is_wasm_only: bool) -> Result { let trait_name = &trait_def.ident; - get_trait_methods(trait_def).try_fold(TokenStream::new(), |mut t, m| { - t.extend(function_for_method(trait_name, m, is_wasm_only)?); + let runtime_interface = get_runtime_interface(trait_def)?; + + // latest version dispatch + let token_stream: Result = runtime_interface.latest_versions() + .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 = runtime_interface.all_versions().try_fold(token_stream?, |mut t, (version, method)| + { + t.extend(function_std_impl(trait_name, method, version, is_wasm_only)?); Ok(t) - }) + }); + + result } /// Generates the bare function implementation for the given method for the host and wasm side. fn function_for_method( - trait_name: &Ident, method: &TraitItemMethod, + latest_version: u32, is_wasm_only: bool, ) -> Result { - let std_impl = function_std_impl(trait_name, method, is_wasm_only)?; + 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)?; Ok( @@ -78,7 +99,7 @@ fn function_no_std_impl(method: &TraitItemMethod) -> Result { let args = get_function_arguments(&method.sig); let arg_names = get_function_argument_names(&method.sig); let return_value = &method.sig.output; - let attrs = &method.attrs; + let attrs = method.attrs.iter().filter(|a| !a.path.is_ident("version")); Ok( quote! { @@ -92,13 +113,40 @@ fn function_no_std_impl(method: &TraitItemMethod) -> Result { ) } +/// Generate call to latest function version for `cfg((feature = "std")` +/// +/// This should generate simple `fn func(..) { func_version_(..) }`. +fn function_std_latest_impl( + method: &TraitItemMethod, + latest_version: u32, +) -> Result { + let function_name = &method.sig.ident; + let args = get_function_arguments(&method.sig).map(FnArg::Typed); + let arg_names = get_function_argument_names(&method.sig).collect::>(); + let return_value = &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(feature = "std")] + #( #attrs )* + pub fn #function_name( #( #args, )* ) #return_value { + #latest_function_name( + #( #arg_names, )* + ) + } + }) +} + /// Generates the bare function implementation for `cfg(feature = "std")`. fn function_std_impl( trait_name: &Ident, method: &TraitItemMethod, + version: u32, is_wasm_only: bool, ) -> Result { - let function_name = &method.sig.ident; + let function_name = create_function_ident_with_version(&method.sig.ident, version); + let crate_ = generate_crate_access(); let args = get_function_arguments(&method.sig).map(FnArg::Typed).chain( // Add the function context as last parameter when this is a wasm only interface. @@ -115,16 +163,15 @@ fn function_std_impl( ).take(1), ); let return_value = &method.sig.output; - let attrs = &method.attrs; + 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 vis = if is_wasm_only { quote!() } else { quote!(pub) }; - let call_to_trait = generate_call_to_trait(trait_name, method, is_wasm_only); + let call_to_trait = generate_call_to_trait(trait_name, method, version, is_wasm_only); Ok( quote_spanned! { method.span() => #[cfg(feature = "std")] #( #attrs )* - #vis fn #function_name( #( #args, )* ) #return_value { + fn #function_name( #( #args, )* ) #return_value { #call_to_trait } } @@ -135,10 +182,11 @@ fn function_std_impl( fn generate_call_to_trait( trait_name: &Ident, method: &TraitItemMethod, + version: u32, is_wasm_only: bool, ) -> TokenStream { let crate_ = generate_crate_access(); - let method_name = &method.sig.ident; + 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, diff --git a/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs b/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs index 42bfe3c712..205ee87105 100644 --- a/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs +++ b/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs @@ -23,13 +23,13 @@ use crate::utils::{ generate_crate_access, create_host_function_ident, get_function_argument_names, get_function_argument_types_without_ref, get_function_argument_types_ref_and_mut, - get_function_argument_names_and_types_without_ref, get_trait_methods, get_function_arguments, - get_function_argument_types, create_exchangeable_host_function_ident, + get_function_argument_names_and_types_without_ref, get_function_arguments, + get_function_argument_types, create_exchangeable_host_function_ident, get_runtime_interface, + create_function_ident_with_version, }; use syn::{ - ItemTrait, TraitItemMethod, Result, ReturnType, Ident, TraitItem, Pat, Error, Signature, - spanned::Spanned, + ItemTrait, TraitItemMethod, Result, ReturnType, Ident, Pat, Error, Signature, spanned::Spanned, }; use proc_macro2::{TokenStream, Span}; @@ -44,13 +44,15 @@ use std::iter::{Iterator, self}; /// implementations for the host functions on the host. pub fn generate(trait_def: &ItemTrait, is_wasm_only: bool) -> Result { let trait_name = &trait_def.ident; - let extern_host_function_impls = get_trait_methods(trait_def) - .try_fold(TokenStream::new(), |mut t, m| { - t.extend(generate_extern_host_function(m, trait_name)?); + let extern_host_function_impls = get_runtime_interface(trait_def)? + .latest_versions() + .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_trait_methods(trait_def) - .try_fold(TokenStream::new(), |mut t, m| { + let exchangeable_host_functions = get_runtime_interface(trait_def)? + .latest_versions() + .try_fold(TokenStream::new(), |mut t, (_, m)| { t.extend(generate_exchangeable_host_function(m)?); Ok::<_, Error>(t) })?; @@ -76,7 +78,7 @@ pub fn generate(trait_def: &ItemTrait, is_wasm_only: bool) -> Result Result { +fn generate_extern_host_function(method: &TraitItemMethod, version: u32, trait_name: &Ident) -> Result { let crate_ = generate_crate_access(); let args = get_function_arguments(&method.sig); let arg_types = get_function_argument_types_without_ref(&method.sig); @@ -85,7 +87,7 @@ fn generate_extern_host_function(method: &TraitItemMethod, trait_name: &Ident) - let arg_names2 = get_function_argument_names(&method.sig); let arg_names3 = get_function_argument_names(&method.sig); let function = &method.sig.ident; - let ext_function = create_host_function_ident(&method.sig.ident, trait_name); + 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, @@ -157,14 +159,12 @@ fn generate_exchangeable_host_function(method: &TraitItemMethod) -> Result Result { let crate_ = generate_crate_access(); - let host_functions = trait_def - .items - .iter() - .filter_map(|i| match i { - TraitItem::Method(ref method) => Some(method), - _ => None, - }) - .map(|m| generate_host_function_implementation(&trait_def.ident, m, is_wasm_only)) + + let host_functions = get_runtime_interface(trait_def)? + .all_versions() + .map(|(version, method)| + generate_host_function_implementation(&trait_def.ident, method, version, is_wasm_only) + ) .collect::>>()?; Ok( @@ -191,9 +191,10 @@ fn generate_host_functions_struct(trait_def: &ItemTrait, is_wasm_only: bool) -> fn generate_host_function_implementation( trait_name: &Ident, method: &TraitItemMethod, + version: u32, is_wasm_only: bool, ) -> Result { - let name = create_host_function_ident(&method.sig.ident, trait_name).to_string(); + 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)?; @@ -202,7 +203,7 @@ fn generate_host_function_implementation( trait_name, ).collect::>>()?; let ffi_to_host_values = generate_ffi_to_host_value(&method.sig).collect::>>()?; - let host_function_call = generate_host_function_call(&method.sig, is_wasm_only); + let host_function_call = generate_host_function_call(&method.sig, version, is_wasm_only); let into_preallocated_ffi_value = generate_into_preallocated_ffi_value(&method.sig)?; let convert_return_value = generate_return_value_into_wasm_value(&method.sig); @@ -211,7 +212,6 @@ fn generate_host_function_implementation( { struct #struct_name; - #[allow(unused)] impl #crate_::sp_wasm_interface::Function for #struct_name { fn name(&self) -> &str { #name @@ -322,8 +322,8 @@ fn generate_ffi_to_host_value<'a>( } /// Generate the code to call the host function and the ident that stores the result. -fn generate_host_function_call(sig: &Signature, is_wasm_only: bool) -> TokenStream { - let host_function_name = &sig.ident; +fn generate_host_function_call(sig: &Signature, version: u32, is_wasm_only: bool) -> TokenStream { + let host_function_name = create_function_ident_with_version(&sig.ident, version); let result_var_name = generate_host_function_result_var_name(&sig.ident); let ref_and_mut = get_function_argument_types_ref_and_mut(sig).map(|ram| ram.map(|(vr, vm)| quote!(#vr #vm)) diff --git a/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/trait_decl_impl.rs b/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/trait_decl_impl.rs index af71ba3241..542c4ca4b8 100644 --- a/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/trait_decl_impl.rs +++ b/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/trait_decl_impl.rs @@ -17,10 +17,15 @@ //! 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::{generate_crate_access, get_function_argument_types_without_ref}; +use crate::utils::{ + generate_crate_access, + get_function_argument_types_without_ref, + get_runtime_interface, + create_function_ident_with_version, +}; use syn::{ - ItemTrait, TraitItemMethod, Result, TraitItem, Error, fold::{self, Fold}, spanned::Spanned, + ItemTrait, TraitItemMethod, Result, Error, fold::{self, Fold}, spanned::Spanned, Visibility, Receiver, Type, Generics, }; @@ -32,7 +37,7 @@ use quote::quote; /// essential definition and implement this essential definition for `dyn Externalities`. pub fn process(trait_def: &ItemTrait, is_wasm_only: bool) -> Result { let impl_trait = impl_trait_for_externalities(trait_def, is_wasm_only)?; - let essential_trait_def = ToEssentialTraitDef::convert(trait_def.clone())?; + let essential_trait_def = declare_essential_trait(trait_def)?; Ok( quote! { @@ -48,29 +53,35 @@ pub fn process(trait_def: &ItemTrait, is_wasm_only: bool) -> Result struct ToEssentialTraitDef { /// All errors found while doing the conversion. errors: Vec, + methods: Vec, } impl ToEssentialTraitDef { - /// Convert the given trait definition to the essential trait definition. - fn convert(trait_def: ItemTrait) -> Result { - let mut folder = ToEssentialTraitDef { - errors: Vec::new(), - }; + fn new() -> Self { + ToEssentialTraitDef { errors: vec![], methods: vec![] } + } - let res = folder.fold_item_trait(trait_def); - - if let Some(first_error) = folder.errors.pop() { + fn into_methods(self) -> Result> { + let mut errors = self.errors; + let methods = self.methods; + if let Some(first_error) = errors.pop() { Err( - folder.errors.into_iter().fold(first_error, |mut o, n| { + errors.into_iter().fold(first_error, |mut o, n| { o.combine(n); o }) ) } else { - Ok(res) + Ok(methods) } } + fn process(&mut self, method: &TraitItemMethod, version: u32) { + let mut folded = self.fold_trait_item_method(method.clone()); + folded.sig.ident = create_function_ident_with_version(&folded.sig.ident, version); + self.methods.push(folded); + } + fn push_error(&mut self, span: &S, msg: &str) { self.errors.push(Error::new(span.span(), msg)); } @@ -98,6 +109,8 @@ impl Fold for ToEssentialTraitDef { self.error_on_generic_parameters(&method.sig.generics); + method.attrs.retain(|a| !a.path.is_ident("version")); + fold::fold_trait_item_method(self, method) } @@ -117,17 +130,40 @@ impl Fold for ToEssentialTraitDef { } } +fn declare_essential_trait(trait_def: &ItemTrait) -> Result { + 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 { let trait_ = &trait_def.ident; let crate_ = generate_crate_access(); - let methods = trait_def - .items - .iter() - .filter_map(|i| match i { - TraitItem::Method(ref method) => Some(method), - _ => None, - }); + 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); + cloned + }); let impl_type = if is_wasm_only { quote!( &mut dyn #crate_::sp_wasm_interface::FunctionContext ) diff --git a/substrate/primitives/runtime-interface/proc-macro/src/utils.rs b/substrate/primitives/runtime-interface/proc-macro/src/utils.rs index 15c65f11ca..45f66e3bf6 100644 --- a/substrate/primitives/runtime-interface/proc-macro/src/utils.rs +++ b/substrate/primitives/runtime-interface/proc-macro/src/utils.rs @@ -20,17 +20,60 @@ use proc_macro2::{TokenStream, Span}; use syn::{ Ident, Error, Signature, Pat, PatType, FnArg, Type, token, TraitItemMethod, ItemTrait, - TraitItem, parse_quote, spanned::Spanned, + TraitItem, parse_quote, spanned::Spanned, Result, Meta, NestedMeta, Lit, Attribute, }; use proc_macro_crate::crate_name; use std::env; +use std::collections::{BTreeMap, btree_map::Entry}; use quote::quote; use inflector::Inflector; +/// Runtime interface function with all associated versions of this function. +pub struct RuntimeInterfaceFunction<'a> { + latest_version: u32, + versions: BTreeMap, +} + +impl<'a> RuntimeInterfaceFunction<'a> { + fn new(version: u32, trait_item: &'a TraitItemMethod) -> Self { + Self { + latest_version: version, + versions: { + let mut res = BTreeMap::new(); + res.insert(version, trait_item); + res + }, + } + } + + pub fn latest_version(&self) -> (u32, &TraitItemMethod) { + ( + self.latest_version, + self.versions.get(&self.latest_version) + .expect("If latest_version has a value, the key with this value is in the versions; qed") + ) + } +} + +/// All functions of a runtime interface grouped by the function names. +pub struct RuntimeInterface<'a> { + items: BTreeMap>, +} + +impl<'a> RuntimeInterface<'a> { + pub fn latest_versions(&self) -> impl Iterator { + self.items.iter().map(|(_, item)| item.latest_version()) + } + + pub fn all_versions(&self) -> impl Iterator { + 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 { if env::var("CARGO_PKG_NAME").unwrap() == "sp-runtime-interface" { @@ -67,12 +110,25 @@ pub fn create_exchangeable_host_function_ident(name: &Ident) -> Ident { } /// Create the host function identifier for the given function name. -pub fn create_host_function_ident(name: &Ident, trait_name: &Ident) -> Ident { +pub fn create_host_function_ident(name: &Ident, version: u32, trait_name: &Ident) -> Ident { Ident::new( &format!( - "ext_{}_{}_version_1", + "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(), ) @@ -151,7 +207,7 @@ pub fn get_function_argument_types_ref_and_mut<'a>( } /// Returns an iterator over all trait methods for the given trait definition. -pub fn get_trait_methods<'a>(trait_def: &'a ItemTrait) -> impl Iterator { +fn get_trait_methods<'a>(trait_def: &'a ItemTrait) -> impl Iterator { trait_def .items .iter() @@ -160,3 +216,85 @@ pub fn get_trait_methods<'a>(trait_def: &'a ItemTrait) -> impl Iterator None, }) } + +/// Parse version attribute. +/// +/// Returns error if it is in incorrent format. Correct format is only `#[version(X)]`. +fn parse_version_attribute(version: &Attribute) -> Result { + let meta = version.parse_meta()?; + + let err = Err(Error::new( + meta.span(), + "Unexpected `version` attribute. The supported format is `#[version(1)]`", + ) + ); + + match meta { + Meta::List(list) => { + if list.nested.len() != 1 { + err + } else if let Some(NestedMeta::Lit(Lit::Int(i))) = list.nested.first() { + i.base10_parse() + } else { + err + } + }, + _ => err, + } +} + +/// Return item version (`#[version(X)]`) attribute, if present. +fn get_item_version(item: &TraitItemMethod) -> Result> { + item.attrs.iter().find(|attr| attr.path.is_ident("version")) + .map(|attr| parse_version_attribute(attr)) + .transpose() +} + +/// Returns all runtime interface members, with versions. +pub fn get_runtime_interface<'a>(trait_def: &'a ItemTrait) + -> Result> +{ + let mut functions: BTreeMap> = BTreeMap::new(); + + for item in get_trait_methods(trait_def) { + let name = item.sig.ident.clone(); + let version = get_item_version(item)?.unwrap_or(1); + + match functions.entry(name.clone()) { + Entry::Vacant(entry) => { entry.insert(RuntimeInterfaceFunction::new(version, item)); }, + Entry::Occupied(mut entry) => { + if let Some(existing_item) = entry.get().versions.get(&version) { + let mut err = Error::new( + item.span(), + "Duplicated version attribute", + ); + err.combine(Error::new( + existing_item.span(), + "Previous version with the same number defined here", + )); + + return Err(err); + } + + let interface_item = entry.get_mut(); + if interface_item.latest_version < version { interface_item.latest_version = version; } + interface_item.versions.insert(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 }) +} \ No newline at end of file diff --git a/substrate/primitives/runtime-interface/src/lib.rs b/substrate/primitives/runtime-interface/src/lib.rs index 609f4f600b..fd158d4b8a 100644 --- a/substrate/primitives/runtime-interface/src/lib.rs +++ b/substrate/primitives/runtime-interface/src/lib.rs @@ -129,11 +129,22 @@ pub use sp_std; /// /// A function that can be called from native/wasm. /// /// /// /// The implementation given to this function is only compiled on native. -/// fn call_some_complex_code(data: &[u8]) -> Vec { +/// fn call(data: &[u8]) -> Vec { /// // 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: &[u8]) -> Vec { +/// // 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() +/// } /// /// /// A function can take a `&self` or `&mut self` argument to get access to the /// /// `Externalities`. (The generated method does not require @@ -157,13 +168,15 @@ pub use sp_std; /// // on the visibility of the trait declaration. /// mod interface { /// trait Interface { -/// fn call_some_complex_code(data: &[u8]) -> Vec; -/// fn set_or_clear(&mut self, optional: Option>); +/// fn call_version_1(data: &[u8]) -> Vec; +/// fn call_version_2(data: &[u8]) -> Vec; +/// fn set_or_clear_version_1(&mut self, optional: Option>); /// } /// /// impl Interface for &mut dyn sp_externalities::Externalities { -/// fn call_some_complex_code(data: &[u8]) -> Vec { Vec::new() } -/// fn set_or_clear(&mut self, optional: Option>) { +/// fn call_version_1(data: &[u8]) -> Vec { Vec::new() } +/// fn call_version_2(data: &[u8]) -> Vec { [17].to_vec() } +/// fn set_or_clear_version_1(&mut self, optional: Option>) { /// match optional { /// Some(value) => self.set_storage([1, 2, 3, 4].to_vec(), value), /// None => self.clear_storage(&[1, 2, 3, 4]), @@ -171,12 +184,25 @@ pub use sp_std; /// } /// } /// -/// pub fn call_some_complex_code(data: &[u8]) -> Vec { -/// <&mut dyn sp_externalities::Externalities as Interface>::call_some_complex_code(data) +/// pub fn call(data: &[u8]) -> Vec { +/// // only latest version is exposed +/// call_version_2(data) +/// } +/// +/// fn call_version_1(data: &[u8]) -> Vec { +/// <&mut dyn sp_externalities::Externalities as Interface>::call_version_1(data) +/// } +/// +/// fn call_version_2(data: &[u8]) -> Vec { +/// <&mut dyn sp_externalities::Externalities as Interface>::call_version_2(data) /// } /// /// pub fn set_or_clear(optional: Option>) { -/// sp_externalities::with_externalities(|mut ext| Interface::set_or_clear(&mut ext, optional)) +/// set_or_clear_version_1(optional) +/// } +/// +/// fn set_or_clear_version_1(optional: Option>) { +/// 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.") /// } /// @@ -205,7 +231,7 @@ pub use sp_std; /// /// `::FFIType`. /// /// /// /// `data` holds the pointer and the length to the `[u8]` slice. -/// pub fn ext_Interface_call_some_complex_code_version_1(data: u64) -> u64; +/// pub fn ext_Interface_call_version_1(data: u64) -> u64; /// /// `optional` holds the pointer and the length of the encoded value. /// pub fn ext_Interface_set_or_clear_version_1(optional: u64); /// } @@ -213,18 +239,18 @@ pub use sp_std; /// /// /// The type is actually `ExchangeableFunction` (from `sp-runtime-interface`). /// /// -/// /// This can be used to replace the implementation of the `call_some_complex_code` function. +/// /// 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_some_complex_code.replace_implementation(some_other_impl)` -/// pub static host_call_some_complex_code: () = (); +/// /// `host_call.replace_implementation(some_other_impl)` +/// pub static host_call: () = (); /// pub static host_set_or_clear: () = (); /// -/// pub fn call_some_complex_code(data: &[u8]) -> Vec { -/// // This is the actual call: `host_call_some_complex_code.get()(data)` +/// pub fn call(data: &[u8]) -> Vec { +/// // 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. diff --git a/substrate/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml b/substrate/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml new file mode 100644 index 0000000000..128f6c9acc --- /dev/null +++ b/substrate/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "sp-runtime-interface-test-wasm-deprecated" +version = "2.0.0-dev" +authors = ["Parity Technologies "] +edition = "2018" +build = "build.rs" +license = "GPL-3.0" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" +publish = false + +[dependencies] +sp-runtime-interface = { version = "2.0.0-alpha.2", default-features = false, path = "../" } +sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../std" } +sp-io = { version = "2.0.0-alpha.2", default-features = false, path = "../../io" } +sp-core = { version = "2.0.0-alpha.2", default-features = false, path = "../../core" } + +[build-dependencies] +wasm-builder-runner = { version = "1.0.5", package = "substrate-wasm-builder-runner", path = "../../../utils/wasm-builder-runner" } + +[features] +default = [ "std" ] +std = [ "sp-runtime-interface/std", "sp-std/std", "sp-core/std", "sp-io/std" ] diff --git a/substrate/primitives/runtime-interface/test-wasm-deprecated/build.rs b/substrate/primitives/runtime-interface/test-wasm-deprecated/build.rs new file mode 100644 index 0000000000..647b476814 --- /dev/null +++ b/substrate/primitives/runtime-interface/test-wasm-deprecated/build.rs @@ -0,0 +1,26 @@ +// Copyright 2019-2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +use wasm_builder_runner::WasmBuilder; + +fn main() { + WasmBuilder::new() + .with_current_project() + .with_wasm_builder_from_crates_or_path("1.0.9", "../../../utils/wasm-builder") + .export_heap_base() + .import_memory() + .build() +} diff --git a/substrate/primitives/runtime-interface/test-wasm-deprecated/src/lib.rs b/substrate/primitives/runtime-interface/test-wasm-deprecated/src/lib.rs new file mode 100644 index 0000000000..29d28c75fa --- /dev/null +++ b/substrate/primitives/runtime-interface/test-wasm-deprecated/src/lib.rs @@ -0,0 +1,52 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! 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")); + +/// 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(&[]); +} + +#[runtime_interface] +pub trait TestApi { + fn test_versionning(&self, _data: u32) -> bool { + // should not be called + unimplemented!() + } +} + +wasm_export_functions! { + fn test_versionning_works() { + // old api allows only 42 and 50 + assert!(test_api::test_versionning(42)); + assert!(test_api::test_versionning(50)); + + assert!(!test_api::test_versionning(142)); + assert!(!test_api::test_versionning(0)); + } +} \ No newline at end of file diff --git a/substrate/primitives/runtime-interface/test-wasm/src/lib.rs b/substrate/primitives/runtime-interface/test-wasm/src/lib.rs index ee7120b1b8..700c77854a 100644 --- a/substrate/primitives/runtime-interface/test-wasm/src/lib.rs +++ b/substrate/primitives/runtime-interface/test-wasm/src/lib.rs @@ -21,7 +21,7 @@ use sp_runtime_interface::runtime_interface; #[cfg(not(feature = "std"))] -use sp_std::{vec, vec::Vec, mem, convert::TryFrom}; +use sp_std::{prelude::*, mem, convert::TryFrom}; use sp_core::{sr25519::Public, wasm_export_functions}; @@ -103,6 +103,15 @@ pub trait TestApi { fn get_and_return_i128(val: i128) -> i128 { val } + + fn test_versionning(&self, data: u32) -> bool { + data == 42 || data == 50 + } + + #[version(2)] + fn test_versionning(&self, data: u32) -> bool { + data == 42 + } } /// This function is not used, but we require it for the compiler to include `sp-io`. @@ -231,4 +240,14 @@ wasm_export_functions! { } assert_eq!(0, len); } + + fn test_versionning_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_versionning(42)); + + assert!(!test_api::test_versionning(50)); + assert!(!test_api::test_versionning(102)); + } } diff --git a/substrate/primitives/runtime-interface/test/Cargo.toml b/substrate/primitives/runtime-interface/test/Cargo.toml index b8da8042ed..53bdcd18bb 100644 --- a/substrate/primitives/runtime-interface/test/Cargo.toml +++ b/substrate/primitives/runtime-interface/test/Cargo.toml @@ -12,6 +12,7 @@ repository = "https://github.com/paritytech/substrate/" sp-runtime-interface = { version = "2.0.0-alpha.4", path = "../" } sc-executor = { version = "0.8.0-alpha.4", path = "../../../client/executor" } sp-runtime-interface-test-wasm = { version = "2.0.0-dev", path = "../test-wasm" } +sp-runtime-interface-test-wasm-deprecated = { version = "2.0.0-dev", path = "../test-wasm-deprecated" } sp-state-machine = { version = "0.8.0-alpha.4", path = "../../../primitives/state-machine" } sp-runtime = { version = "2.0.0-alpha.4", path = "../../runtime" } sp-io = { version = "2.0.0-alpha.4", path = "../../io" } diff --git a/substrate/primitives/runtime-interface/test/src/lib.rs b/substrate/primitives/runtime-interface/test/src/lib.rs index 5236bf147e..110eda980f 100644 --- a/substrate/primitives/runtime-interface/test/src/lib.rs +++ b/substrate/primitives/runtime-interface/test/src/lib.rs @@ -20,13 +20,16 @@ #![cfg(test)] use sp_runtime_interface::*; + use sp_runtime_interface_test_wasm::{WASM_BINARY, test_api::HostFunctions}; +use sp_runtime_interface_test_wasm_deprecated::WASM_BINARY as WASM_BINARY_DEPRECATED; + use sp_wasm_interface::HostFunctions as HostFunctionsT; use sc_executor::CallInWasm; type TestExternalities = sp_state_machine::TestExternalities; -fn call_wasm_method(method: &str) -> TestExternalities { +fn call_wasm_method(binary: &[u8], method: &str) -> TestExternalities { let mut ext = TestExternalities::default(); let mut ext_ext = ext.ext(); let mut host_functions = HF::host_functions(); @@ -40,7 +43,7 @@ fn call_wasm_method(method: &str) -> TestExternalities { 8, ); executor.call_in_wasm( - &WASM_BINARY[..], + binary, None, method, &[], @@ -52,17 +55,17 @@ fn call_wasm_method(method: &str) -> TestExternalities { #[test] fn test_return_data() { - call_wasm_method::("test_return_data"); + call_wasm_method::(&WASM_BINARY[..], "test_return_data"); } #[test] fn test_return_option_data() { - call_wasm_method::("test_return_option_data"); + call_wasm_method::(&WASM_BINARY[..], "test_return_option_data"); } #[test] fn test_set_storage() { - let mut ext = call_wasm_method::("test_set_storage"); + let mut ext = call_wasm_method::(&WASM_BINARY[..], "test_set_storage"); let expected = "world"; assert_eq!(expected.as_bytes(), &ext.ext().storage("hello".as_bytes()).unwrap()[..]); @@ -70,22 +73,22 @@ fn test_set_storage() { #[test] fn test_return_value_into_mutable_reference() { - call_wasm_method::("test_return_value_into_mutable_reference"); + call_wasm_method::(&WASM_BINARY[..], "test_return_value_into_mutable_reference"); } #[test] fn test_get_and_return_array() { - call_wasm_method::("test_get_and_return_array"); + call_wasm_method::(&WASM_BINARY[..], "test_get_and_return_array"); } #[test] fn test_array_as_mutable_reference() { - call_wasm_method::("test_array_as_mutable_reference"); + call_wasm_method::(&WASM_BINARY[..], "test_array_as_mutable_reference"); } #[test] fn test_return_input_public_key() { - call_wasm_method::("test_return_input_public_key"); + call_wasm_method::(&WASM_BINARY[..], "test_return_input_public_key"); } #[test] @@ -93,7 +96,7 @@ fn test_return_input_public_key() { expected = "Instantiation: Export ext_test_api_return_input_version_1 not found" )] fn host_function_not_found() { - call_wasm_method::<()>("test_return_data"); + call_wasm_method::<()>(&WASM_BINARY[..], "test_return_data"); } #[test] @@ -104,30 +107,46 @@ fn host_function_not_found() { \\\"Invalid utf8 data provided\\\")) }\"" )] fn test_invalid_utf8_data_should_return_an_error() { - call_wasm_method::("test_invalid_utf8_data_should_return_an_error"); + call_wasm_method::(&WASM_BINARY[..], "test_invalid_utf8_data_should_return_an_error"); } #[test] fn test_overwrite_native_function_implementation() { - call_wasm_method::("test_overwrite_native_function_implementation"); + call_wasm_method::(&WASM_BINARY[..], "test_overwrite_native_function_implementation"); } #[test] fn test_u128_i128_as_parameter_and_return_value() { - call_wasm_method::("test_u128_i128_as_parameter_and_return_value"); + call_wasm_method::(&WASM_BINARY[..], "test_u128_i128_as_parameter_and_return_value"); } #[test] fn test_vec_return_value_memory_is_freed() { - call_wasm_method::("test_vec_return_value_memory_is_freed"); + call_wasm_method::(&WASM_BINARY[..], "test_vec_return_value_memory_is_freed"); } #[test] fn test_encoded_return_value_memory_is_freed() { - call_wasm_method::("test_encoded_return_value_memory_is_freed"); + call_wasm_method::(&WASM_BINARY[..], "test_encoded_return_value_memory_is_freed"); } #[test] fn test_array_return_value_memory_is_freed() { - call_wasm_method::("test_array_return_value_memory_is_freed"); + call_wasm_method::(&WASM_BINARY[..], "test_array_return_value_memory_is_freed"); +} + +#[test] +fn test_versionining_with_new_host_works() { + // We call to the new wasm binary with new host function. + call_wasm_method::( + &WASM_BINARY[..], + "test_versionning_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::( + &WASM_BINARY_DEPRECATED[..], + "test_versionning_works", + ); } diff --git a/substrate/primitives/runtime-interface/tests/ui/no_duplicate_versions.rs b/substrate/primitives/runtime-interface/tests/ui/no_duplicate_versions.rs new file mode 100644 index 0000000000..948c327aa1 --- /dev/null +++ b/substrate/primitives/runtime-interface/tests/ui/no_duplicate_versions.rs @@ -0,0 +1,11 @@ +use sp_runtime_interface::runtime_interface; + +#[runtime_interface] +trait Test { + #[version(2)] + fn test() { } + #[version(2)] + fn test() { } +} + +fn main() {} diff --git a/substrate/primitives/runtime-interface/tests/ui/no_duplicate_versions.stderr b/substrate/primitives/runtime-interface/tests/ui/no_duplicate_versions.stderr new file mode 100644 index 0000000000..592dd9928c --- /dev/null +++ b/substrate/primitives/runtime-interface/tests/ui/no_duplicate_versions.stderr @@ -0,0 +1,11 @@ +error: Duplicated version attribute + --> $DIR/no_duplicate_versions.rs:7:2 + | +7 | #[version(2)] + | ^ + +error: Previous version with the same number defined here + --> $DIR/no_duplicate_versions.rs:5:2 + | +5 | #[version(2)] + | ^ diff --git a/substrate/primitives/runtime-interface/tests/ui/no_gaps_in_versions.rs b/substrate/primitives/runtime-interface/tests/ui/no_gaps_in_versions.rs new file mode 100644 index 0000000000..c468f48e37 --- /dev/null +++ b/substrate/primitives/runtime-interface/tests/ui/no_gaps_in_versions.rs @@ -0,0 +1,17 @@ +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() {} diff --git a/substrate/primitives/runtime-interface/tests/ui/no_gaps_in_versions.stderr b/substrate/primitives/runtime-interface/tests/ui/no_gaps_in_versions.stderr new file mode 100644 index 0000000000..cdefcf60c5 --- /dev/null +++ b/substrate/primitives/runtime-interface/tests/ui/no_gaps_in_versions.stderr @@ -0,0 +1,5 @@ +error: Unexpected version attribute: missing version '2' for this function + --> $DIR/no_gaps_in_versions.rs:13:2 + | +13 | #[version(3)] + | ^ diff --git a/substrate/primitives/runtime-interface/tests/ui/no_generic_parameters.stderr b/substrate/primitives/runtime-interface/tests/ui/no_generic_parameters.stderr deleted file mode 100644 index c3e46655e5..0000000000 --- a/substrate/primitives/runtime-interface/tests/ui/no_generic_parameters.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: Generic parameters not supported. - --> $DIR/no_generic_parameters.rs:5:10 - | -5 | fn test() {} - | ^ - -error: Generic parameters not supported. - --> $DIR/no_generic_parameters.rs:4:12 - | -4 | trait Test { - | ^ diff --git a/substrate/primitives/runtime-interface/tests/ui/no_generic_parameters_method.rs b/substrate/primitives/runtime-interface/tests/ui/no_generic_parameters_method.rs new file mode 100644 index 0000000000..407942eb5e --- /dev/null +++ b/substrate/primitives/runtime-interface/tests/ui/no_generic_parameters_method.rs @@ -0,0 +1,8 @@ +use sp_runtime_interface::runtime_interface; + +#[runtime_interface] +trait Test { + fn test() {} +} + +fn main() {} diff --git a/substrate/primitives/runtime-interface/tests/ui/no_generic_parameters_method.stderr b/substrate/primitives/runtime-interface/tests/ui/no_generic_parameters_method.stderr new file mode 100644 index 0000000000..8a549753ac --- /dev/null +++ b/substrate/primitives/runtime-interface/tests/ui/no_generic_parameters_method.stderr @@ -0,0 +1,5 @@ +error: Generic parameters not supported. + --> $DIR/no_generic_parameters_method.rs:5:10 + | +5 | fn test() {} + | ^ diff --git a/substrate/primitives/runtime-interface/tests/ui/no_generic_parameters.rs b/substrate/primitives/runtime-interface/tests/ui/no_generic_parameters_trait.rs similarity index 85% rename from substrate/primitives/runtime-interface/tests/ui/no_generic_parameters.rs rename to substrate/primitives/runtime-interface/tests/ui/no_generic_parameters_trait.rs index 17ddb00fab..35efac6761 100644 --- a/substrate/primitives/runtime-interface/tests/ui/no_generic_parameters.rs +++ b/substrate/primitives/runtime-interface/tests/ui/no_generic_parameters_trait.rs @@ -2,7 +2,7 @@ use sp_runtime_interface::runtime_interface; #[runtime_interface] trait Test { - fn test() {} + fn test() {} } fn main() {} diff --git a/substrate/primitives/runtime-interface/tests/ui/no_generic_parameters_trait.stderr b/substrate/primitives/runtime-interface/tests/ui/no_generic_parameters_trait.stderr new file mode 100644 index 0000000000..794e30bca7 --- /dev/null +++ b/substrate/primitives/runtime-interface/tests/ui/no_generic_parameters_trait.stderr @@ -0,0 +1,5 @@ +error: Generic parameters not supported. + --> $DIR/no_generic_parameters_trait.rs:4:12 + | +4 | trait Test { + | ^