diff --git a/Cargo.lock b/Cargo.lock index 4b15c29b1b..4c12e28071 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1720,11 +1720,11 @@ dependencies = [ [[package]] name = "frame-decode" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c554ce2394e2c04426a070b4cb133c72f6f14c86b665f4e13094addd8e8958" +checksum = "a7cb8796f93fa038f979a014234d632e9688a120e745f936e2635123c77537f7" dependencies = [ - "frame-metadata 20.0.0", + "frame-metadata 21.0.0", "parity-scale-codec", "scale-decode", "scale-info", @@ -1746,9 +1746,9 @@ dependencies = [ [[package]] name = "frame-metadata" -version = "20.0.0" +version = "21.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26de808fa6461f2485dc51811aefed108850064994fb4a62b3ac21ffa62ac8df" +checksum = "20dfd1d7eae1d94e32e869e2fb272d81f52dd8db57820a373adb83ea24d7d862" dependencies = [ "cfg-if", "parity-scale-codec", @@ -1897,7 +1897,7 @@ dependencies = [ name = "generate-custom-metadata" version = "0.41.0" dependencies = [ - "frame-metadata 20.0.0", + "frame-metadata 21.0.0", "parity-scale-codec", "scale-info", ] @@ -2407,12 +2407,13 @@ dependencies = [ "assert_matches", "cfg_aliases", "frame-decode", - "frame-metadata 20.0.0", + "frame-metadata 21.0.0", "futures", "hex", "parity-scale-codec", "regex", "scale-info", + "scale-value", "serde", "sp-core", "substrate-runner", @@ -5191,7 +5192,7 @@ dependencies = [ "bitvec", "derive-where", "either", - "frame-metadata 20.0.0", + "frame-metadata 21.0.0", "futures", "hex", "http-body", @@ -5233,7 +5234,7 @@ version = "0.41.0" dependencies = [ "clap", "color-eyre", - "frame-metadata 20.0.0", + "frame-metadata 21.0.0", "heck", "hex", "indoc", @@ -5262,7 +5263,7 @@ dependencies = [ name = "subxt-codegen" version = "0.41.0" dependencies = [ - "frame-metadata 20.0.0", + "frame-metadata 21.0.0", "getrandom", "heck", "parity-scale-codec", @@ -5285,7 +5286,7 @@ dependencies = [ "blake2", "derive-where", "frame-decode", - "frame-metadata 20.0.0", + "frame-metadata 21.0.0", "hashbrown 0.14.5", "hex", "impl-serde", @@ -5360,7 +5361,7 @@ dependencies = [ "bitvec", "criterion", "frame-decode", - "frame-metadata 20.0.0", + "frame-metadata 21.0.0", "hashbrown 0.14.5", "parity-scale-codec", "scale-info", @@ -5375,7 +5376,7 @@ version = "0.41.0" dependencies = [ "derive-where", "finito", - "frame-metadata 20.0.0", + "frame-metadata 21.0.0", "futures", "getrandom", "hex", @@ -5443,7 +5444,7 @@ dependencies = [ name = "subxt-utils-fetchmetadata" version = "0.41.0" dependencies = [ - "frame-metadata 20.0.0", + "frame-metadata 21.0.0", "hex", "jsonrpsee", "parity-scale-codec", @@ -5457,7 +5458,8 @@ name = "subxt-utils-stripmetadata" version = "0.41.0" dependencies = [ "either", - "frame-metadata 20.0.0", + "frame-metadata 21.0.0", + "parity-scale-codec", "scale-info", ] @@ -5649,9 +5651,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" dependencies = [ "backtrace", "bytes", @@ -5665,9 +5667,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", @@ -5935,7 +5937,7 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" name = "ui-tests" version = "0.41.0" dependencies = [ - "frame-metadata 20.0.0", + "frame-metadata 21.0.0", "generate-custom-metadata", "hex", "parity-scale-codec", diff --git a/Cargo.toml b/Cargo.toml index 174bb41d50..9c502dabf0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,8 +79,8 @@ darling = "0.20.10" derive-where = "1.2.7" either = { version = "1.13.0", default-features = false } finito = { version = "0.1.0", default-features = false } -frame-decode = { version = "0.7.0", default-features = false } -frame-metadata = { version = "20.0.0", default-features = false, features = ["unstable"] } +frame-decode = { version = "0.7.1", default-features = false } +frame-metadata = { version = "21.0.0", default-features = false, features = ["unstable"] } futures = { version = "0.3.31", default-features = false, features = ["std"] } getrandom = { version = "0.2", default-features = false } hashbrown = "0.14.5" diff --git a/codegen/src/api/mod.rs b/codegen/src/api/mod.rs index aa7f345fd1..8ee4d857dd 100644 --- a/codegen/src/api/mod.rs +++ b/codegen/src/api/mod.rs @@ -9,6 +9,7 @@ mod constants; mod custom_values; mod errors; mod events; +mod pallet_view_functions; mod runtime_apis; mod storage; @@ -170,12 +171,19 @@ impl RuntimeGenerator { let errors = errors::generate_error_type_alias(&type_gen, pallet)?; + let view_functions = pallet_view_functions::generate_pallet_view_functions( + &type_gen, + pallet, + &crate_path, + )?; + Ok(quote! { pub mod #mod_name { use super::root_mod; use super::#types_mod_ident; #errors #calls + #view_functions #event #storage_mod #constants_mod @@ -206,6 +214,12 @@ impl RuntimeGenerator { .filter_map(|(pallet, pallet_mod_name)| pallet.call_ty_id().map(|_| pallet_mod_name)) .collect(); + let pallets_with_view_functions: Vec<_> = pallets_with_mod_names + .iter() + .filter(|(pallet, _pallet_mod_name)| pallet.has_view_functions()) + .map(|(_, pallet_mod_name)| pallet_mod_name) + .collect(); + let rust_items = item_mod_ir.rust_items(); let apis_mod = runtime_apis::generate_runtime_apis( @@ -283,6 +297,10 @@ impl RuntimeGenerator { #apis_mod + pub fn view_functions() -> ViewFunctionsApi { + ViewFunctionsApi + } + pub fn custom() -> CustomValuesApi { CustomValuesApi } @@ -316,6 +334,15 @@ impl RuntimeGenerator { )* } + pub struct ViewFunctionsApi; + impl ViewFunctionsApi { + #( + pub fn #pallets_with_view_functions(&self) -> #pallets_with_view_functions::view_functions::ViewFunctionsApi { + #pallets_with_view_functions::view_functions::ViewFunctionsApi + } + )* + } + /// check whether the metadata provided is aligned with this statically generated code. pub fn is_codegen_valid_for(metadata: &#crate_path::Metadata) -> bool { let runtime_metadata_hash = metadata diff --git a/codegen/src/api/pallet_view_functions.rs b/codegen/src/api/pallet_view_functions.rs new file mode 100644 index 0000000000..22a2f4a8ea --- /dev/null +++ b/codegen/src/api/pallet_view_functions.rs @@ -0,0 +1,194 @@ +// Copyright 2019-2025 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +use heck::ToUpperCamelCase as _; + +use crate::CodegenError; +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use scale_typegen::typegen::ir::ToTokensWithSettings; +use scale_typegen::TypeGenerator; +use std::collections::HashSet; +use subxt_metadata::{PalletMetadata, ViewFunctionMetadata}; + +fn generate_pallet_view_function( + view_function: ViewFunctionMetadata<'_>, + type_gen: &TypeGenerator, + crate_path: &syn::Path, +) -> Result<(TokenStream2, TokenStream2), CodegenError> { + let types_mod_ident = type_gen.types_mod_ident(); + + let view_function_name_str = view_function.name(); + let view_function_name_ident = format_ident!("{}", view_function_name_str); + + let query_id = view_function.query_id(); + let validation_hash = view_function.hash(); + + let docs = view_function.docs(); + let docs: TokenStream2 = type_gen + .settings() + .should_gen_docs + .then_some(quote! { #( #[doc = #docs ] )* }) + .unwrap_or_default(); + + struct Input { + name: syn::Ident, + type_alias: syn::Ident, + type_path: TokenStream2, + } + + let view_function_inputs: Vec = { + let mut unique_names = HashSet::new(); + let mut unique_aliases = HashSet::new(); + + view_function + .inputs() + .enumerate() + .map(|(idx, input)| { + // These are method names, which can just be '_', but struct field names can't + // just be an underscore, so fix any such names we find to work in structs. + let mut name = input.name.trim_start_matches('_').to_string(); + if name.is_empty() { + name = format!("_{}", idx); + } + while !unique_names.insert(name.clone()) { + name = format!("{}_param{}", name, idx); + } + + // The alias type name is based on the name, above. + let mut alias = name.to_upper_camel_case(); + // Note: name is not empty. + if alias.as_bytes()[0].is_ascii_digit() { + alias = format!("Param{}", alias); + } + while !unique_aliases.insert(alias.clone()) { + alias = format!("{}Param{}", alias, idx); + } + + // Path to the actual type we'll have generated for this input. + let type_path = type_gen + .resolve_type_path(input.ty) + .expect("view function input type is in metadata; qed") + .to_token_stream(type_gen.settings()); + + Input { + name: format_ident!("{name}"), + type_alias: format_ident!("{alias}"), + type_path, + } + }) + .collect() + }; + + let input_struct_params = view_function_inputs + .iter() + .map(|i| { + let arg = &i.name; + let ty = &i.type_alias; + quote!(pub #arg: #ty) + }) + .collect::>(); + + let input_args = view_function_inputs + .iter() + .map(|i| { + let arg = &i.name; + let ty = &i.type_alias; + quote!(#arg: #view_function_name_ident::#ty) + }) + .collect::>(); + + let input_type_aliases = view_function_inputs.iter().map(|i| { + let ty = &i.type_alias; + let path = &i.type_path; + quote!(pub type #ty = #path;) + }); + + let input_param_names = view_function_inputs.iter().map(|i| &i.name); + + let output_type_path = type_gen + .resolve_type_path(view_function.output_ty())? + .to_token_stream(type_gen.settings()); + + let input_struct_derives = type_gen.settings().derives.default_derives(); + + // Define the input and output type bits. + let view_function_def = quote!( + pub mod #view_function_name_ident { + use super::root_mod; + use super::#types_mod_ident; + + #input_struct_derives + pub struct Input { + #(#input_struct_params,)* + } + + #(#input_type_aliases)* + + pub mod output { + use super::#types_mod_ident; + pub type Output = #output_type_path; + } + } + ); + + // Define the getter method that will live on the `ViewFunctionApi` type. + let view_function_getter = quote!( + #docs + pub fn #view_function_name_ident( + &self, + #(#input_args),* + ) -> #crate_path::view_functions::payload::StaticPayload< + #view_function_name_ident::Input, + #view_function_name_ident::output::Output + > { + #crate_path::view_functions::payload::StaticPayload::new_static( + [#(#query_id,)*], + #view_function_name_ident::Input { + #(#input_param_names,)* + }, + [#(#validation_hash,)*], + ) + } + ); + + Ok((view_function_def, view_function_getter)) +} + +pub fn generate_pallet_view_functions( + type_gen: &TypeGenerator, + pallet: &PalletMetadata, + crate_path: &syn::Path, +) -> Result { + if !pallet.has_view_functions() { + // If there are no view functions in this pallet, we + // don't generate anything. + return Ok(quote! {}); + } + + let view_functions: Vec<_> = pallet + .view_functions() + .map(|vf| generate_pallet_view_function(vf, type_gen, crate_path)) + .collect::>()?; + + let view_functions_defs = view_functions.iter().map(|(apis, _)| apis); + let view_functions_getters = view_functions.iter().map(|(_, getters)| getters); + + let types_mod_ident = type_gen.types_mod_ident(); + + Ok(quote! { + pub mod view_functions { + use super::root_mod; + use super::#types_mod_ident; + + pub struct ViewFunctionsApi; + + impl ViewFunctionsApi { + #( #view_functions_getters )* + } + + #( #view_functions_defs )* + } + }) +} diff --git a/codegen/src/api/runtime_apis.rs b/codegen/src/api/runtime_apis.rs index 57dd10cef9..1fe99feca5 100644 --- a/codegen/src/api/runtime_apis.rs +++ b/codegen/src/api/runtime_apis.rs @@ -78,7 +78,8 @@ fn generate_runtime_api( // Generate alias for runtime type. let ty = type_gen .resolve_type_path(input.ty) - .expect("runtime api input type is in metadata; qed").to_token_stream(type_gen.settings()); + .expect("runtime api input type is in metadata; qed") + .to_token_stream(type_gen.settings()); let aliased_param = quote!( pub type #alias_name = #ty; ); // Structures are placed on the same level as the alias module. diff --git a/core/src/dynamic.rs b/core/src/dynamic.rs index bde62862a5..8f02b2d04c 100644 --- a/core/src/dynamic.rs +++ b/core/src/dynamic.rs @@ -28,6 +28,9 @@ pub use crate::storage::address::dynamic as storage; // Execute runtime API function call dynamically. pub use crate::runtime_api::payload::dynamic as runtime_api_call; +// Execute View Function API function call dynamically. +pub use crate::view_functions::payload::dynamic as view_function_call; + /// This is the result of making a dynamic request to a node. From this, /// we can return the raw SCALE bytes that we were handed back, or we can /// complete the decoding of the bytes into a [`DecodedValue`] type. diff --git a/core/src/error.rs b/core/src/error.rs index 5e5724537a..e4ca7d9bd2 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -105,6 +105,9 @@ pub enum MetadataError { /// Runtime method not found. #[error("Runtime method with name {0} not found")] RuntimeMethodNotFound(String), + /// View Function not found. + #[error("View Function with query ID {} not found", hex::encode(.0))] + ViewFunctionNotFound([u8; 32]), /// Call type not found in metadata. #[error("Call type not found in pallet with index {0}")] CallTypeNotFoundInPallet(u8), diff --git a/core/src/lib.rs b/core/src/lib.rs index d31dae4d20..a39d5e1899 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -36,6 +36,7 @@ pub mod runtime_api; pub mod storage; pub mod tx; pub mod utils; +pub mod view_functions; pub use config::Config; pub use error::Error; diff --git a/core/src/view_functions/mod.rs b/core/src/view_functions/mod.rs new file mode 100644 index 0000000000..3d422804fb --- /dev/null +++ b/core/src/view_functions/mod.rs @@ -0,0 +1,69 @@ +// Copyright 2019-2024 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +//! Encode View Function payloads, decode the associated values returned from them, and validate +//! static View Function payloads. + +pub mod payload; + +use crate::error::{Error, MetadataError}; +use crate::metadata::{DecodeWithMetadata, Metadata}; +use alloc::vec::Vec; +use payload::Payload; + +/// Run the validation logic against some View Function payload you'd like to use. Returns `Ok(())` +/// if the payload is valid (or if it's not possible to check since the payload has no validation hash). +/// Return an error if the payload was not valid or something went wrong trying to validate it (ie +/// the View Function in question do not exist at all) +pub fn validate(payload: &P, metadata: &Metadata) -> Result<(), Error> { + let Some(static_hash) = payload.validation_hash() else { + return Ok(()); + }; + + let view_function = metadata + .view_function_by_query_id(payload.query_id()) + .ok_or_else(|| MetadataError::ViewFunctionNotFound(*payload.query_id()))?; + if static_hash != view_function.hash() { + return Err(MetadataError::IncompatibleCodegen.into()); + } + + Ok(()) +} + +/// The name of the Runtime API call which can execute +pub const CALL_NAME: &str = "RuntimeViewFunction_execute_view_function"; + +/// Encode the bytes that will be passed to the "execute_view_function" Runtime API call, +/// to execute the View Function represented by the given payload. +pub fn call_args(payload: &P, metadata: &Metadata) -> Result, Error> { + let mut call_args = Vec::with_capacity(32); + call_args.extend_from_slice(payload.query_id()); + + let mut call_arg_params = Vec::new(); + payload.encode_args_to(metadata, &mut call_arg_params)?; + + use codec::Encode; + call_arg_params.encode_to(&mut call_args); + + Ok(call_args) +} + +/// Decode the value bytes at the location given by the provided View Function payload. +pub fn decode_value( + bytes: &mut &[u8], + payload: &P, + metadata: &Metadata, +) -> Result { + let view_function = metadata + .view_function_by_query_id(payload.query_id()) + .ok_or_else(|| MetadataError::ViewFunctionNotFound(*payload.query_id()))?; + + let val = ::decode_with_metadata( + &mut &bytes[..], + view_function.output_ty(), + metadata, + )?; + + Ok(val) +} diff --git a/core/src/view_functions/payload.rs b/core/src/view_functions/payload.rs new file mode 100644 index 0000000000..26d2415c20 --- /dev/null +++ b/core/src/view_functions/payload.rs @@ -0,0 +1,153 @@ +// Copyright 2019-2024 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +//! This module contains the trait and types used to represent +//! View Function calls that can be made. + +use alloc::vec::Vec; +use core::marker::PhantomData; +use derive_where::derive_where; +use scale_encode::EncodeAsFields; +use scale_value::Composite; + +use crate::dynamic::DecodedValueThunk; +use crate::error::MetadataError; +use crate::Error; + +use crate::metadata::{DecodeWithMetadata, Metadata}; + +/// This represents a View Function payload that can call into the runtime of node. +/// +/// # Components +/// +/// - associated return type +/// +/// Resulting bytes of the call are interpreted into this type. +/// +/// - query ID +/// +/// The ID used to identify in the runtime which view function to call. +/// +/// - encoded arguments +/// +/// Each argument of the View Function must be scale-encoded. +pub trait Payload { + /// The return type of the function call. + // Note: `DecodeWithMetadata` is needed to decode the function call result + // with the `subxt::Metadata. + type ReturnType: DecodeWithMetadata; + + /// The payload target. + fn query_id(&self) -> &[u8; 32]; + + /// Scale encode the arguments data. + fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec) -> Result<(), Error>; + + /// Encode arguments data and return the output. This is a convenience + /// wrapper around [`Payload::encode_args_to`]. + fn encode_args(&self, metadata: &Metadata) -> Result, Error> { + let mut v = Vec::new(); + self.encode_args_to(metadata, &mut v)?; + Ok(v) + } + + /// Returns the statically generated validation hash. + fn validation_hash(&self) -> Option<[u8; 32]> { + None + } +} + +/// A View Function payload containing the generic argument data +/// and interpreting the result of the call as `ReturnTy`. +/// +/// This can be created from static values (ie those generated +/// via the `subxt` macro) or dynamic values via [`dynamic`]. +#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; ArgsData)] +pub struct DefaultPayload { + query_id: [u8; 32], + args_data: ArgsData, + validation_hash: Option<[u8; 32]>, + _marker: PhantomData, +} + +/// A statically generated View Function payload. +pub type StaticPayload = DefaultPayload; +/// A dynamic View Function payload. +pub type DynamicPayload = DefaultPayload, DecodedValueThunk>; + +impl Payload + for DefaultPayload +{ + type ReturnType = ReturnTy; + + fn query_id(&self) -> &[u8; 32] { + &self.query_id + } + + fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec) -> Result<(), Error> { + let view_function = metadata + .view_function_by_query_id(&self.query_id) + .ok_or(MetadataError::ViewFunctionNotFound(self.query_id))?; + let mut fields = view_function + .inputs() + .map(|input| scale_encode::Field::named(input.ty, &input.name)); + + self.args_data + .encode_as_fields_to(&mut fields, metadata.types(), out)?; + + Ok(()) + } + + fn validation_hash(&self) -> Option<[u8; 32]> { + self.validation_hash + } +} + +impl DefaultPayload { + /// Create a new [`DefaultPayload`] for a View Function call. + pub fn new(query_id: [u8; 32], args_data: ArgsData) -> Self { + DefaultPayload { + query_id, + args_data, + validation_hash: None, + _marker: PhantomData, + } + } + + /// Create a new static [`DefaultPayload`] for a View Function call + /// using static function name and scale-encoded argument data. + /// + /// This is only expected to be used from codegen. + #[doc(hidden)] + pub fn new_static( + query_id: [u8; 32], + args_data: ArgsData, + hash: [u8; 32], + ) -> DefaultPayload { + DefaultPayload { + query_id, + args_data, + validation_hash: Some(hash), + _marker: core::marker::PhantomData, + } + } + + /// Do not validate this call prior to submitting it. + pub fn unvalidated(self) -> Self { + Self { + validation_hash: None, + ..self + } + } + + /// Returns the arguments data. + pub fn args_data(&self) -> &ArgsData { + &self.args_data + } +} + +/// Create a new [`DynamicPayload`] to call a View Function. +pub fn dynamic(query_id: [u8; 32], args_data: impl Into>) -> DynamicPayload { + DefaultPayload::new(query_id, args_data.into()) +} diff --git a/metadata/src/from/v14.rs b/metadata/src/from/v14.rs index 02c713829a..0b9ed168a2 100644 --- a/metadata/src/from/v14.rs +++ b/metadata/src/from/v14.rs @@ -68,7 +68,7 @@ impl TryFrom for Metadata { error_ty: p.error.map(|e| e.ty.id), error_variant_index, constants: constants.collect(), - view_functions: vec![], + view_functions: Default::default(), associated_types: Default::default(), docs: vec![], }, diff --git a/metadata/src/from/v15.rs b/metadata/src/from/v15.rs index ebfb623167..544e5992e5 100644 --- a/metadata/src/from/v15.rs +++ b/metadata/src/from/v15.rs @@ -63,7 +63,7 @@ impl TryFrom for Metadata { error_ty: p.error.map(|e| e.ty.id), error_variant_index, constants: constants.collect(), - view_functions: vec![], + view_functions: Default::default(), associated_types: Default::default(), docs: p.docs, }, diff --git a/metadata/src/from/v16.rs b/metadata/src/from/v16.rs index 419136d90f..085ba0624e 100644 --- a/metadata/src/from/v16.rs +++ b/metadata/src/from/v16.rs @@ -7,10 +7,9 @@ use super::TryFromError; use crate::utils::variant_index::VariantIndex; use crate::{ utils::ordered_map::OrderedMap, ArcStr, ConstantMetadata, ExtrinsicMetadata, Metadata, - MethodParamMetadata, OuterEnumsMetadata, PalletMetadataInner, PalletViewFunctionMetadataInner, - RuntimeApiMetadataInner, RuntimeApiMethodMetadataInner, StorageEntryMetadata, - StorageEntryModifier, StorageEntryType, StorageHasher, StorageMetadata, - TransactionExtensionMetadataInner, + MethodParamMetadata, OuterEnumsMetadata, PalletMetadataInner, RuntimeApiMetadataInner, + RuntimeApiMethodMetadataInner, StorageEntryMetadata, StorageEntryModifier, StorageEntryType, + StorageHasher, StorageMetadata, TransactionExtensionMetadataInner, ViewFunctionMetadataInner, }; use frame_metadata::{v15, v16}; use hashbrown::HashMap; @@ -41,10 +40,10 @@ impl TryFrom for Metadata { let name: ArcStr = c.name.clone().into(); (name.clone(), from_constant_metadata(name, c)) }); - let view_functions = p - .view_functions - .into_iter() - .map(from_view_function_metadata); + let view_functions = p.view_functions.into_iter().map(|v| { + let name: ArcStr = v.name.clone().into(); + (name.clone(), from_view_function_metadata(name, v)) + }); let call_variant_index = VariantIndex::build(p.calls.as_ref().map(|c| c.ty.id), &types); let error_variant_index = @@ -133,7 +132,11 @@ fn from_transaction_extension_metadata( fn from_extrinsic_metadata(value: v16::ExtrinsicMetadata) -> ExtrinsicMetadata { ExtrinsicMetadata { supported_versions: value.versions, - transaction_extensions_by_version: value.transaction_extensions_by_version, + transaction_extensions_by_version: value + .transaction_extensions_by_version + .into_iter() + .map(|(version, idxs)| (version, idxs.into_iter().map(|idx| idx.0).collect())) + .collect(), transaction_extensions: value .transaction_extensions .into_iter() @@ -241,10 +244,11 @@ fn from_runtime_api_method_metadata( } fn from_view_function_metadata( + name: ArcStr, s: v16::PalletViewFunctionMetadata, -) -> PalletViewFunctionMetadataInner { - PalletViewFunctionMetadataInner { - name: s.name, +) -> ViewFunctionMetadataInner { + ViewFunctionMetadataInner { + name, query_id: s.id, inputs: s .inputs diff --git a/metadata/src/lib.rs b/metadata/src/lib.rs index 9d8427319a..db60ce1bec 100644 --- a/metadata/src/lib.rs +++ b/metadata/src/lib.rs @@ -216,6 +216,20 @@ impl Metadata { }) } + /// Access a view function given its query ID, if any. + pub fn view_function_by_query_id( + &'_ self, + query_id: &[u8; 32], + ) -> Option> { + // Dev note: currently, we only have pallet view functions, and here + // we just do a naive thing of iterating over the pallets to find the one + // we're looking for. Eventually we should construct a separate map of view + // functions for easy querying here. + self.pallets() + .flat_map(|p| p.view_functions()) + .find(|vf| vf.query_id() == query_id) + } + /// Returns custom user defined types pub fn custom(&self) -> CustomMetadata<'_> { CustomMetadata { @@ -293,12 +307,29 @@ impl<'a> PalletMetadata<'a> { ) } + /// Does this pallet have any view functions? + pub fn has_view_functions(&self) -> bool { + !self.inner.view_functions.is_empty() + } + /// Return an iterator over the View Functions in this pallet, if any. - pub fn view_functions(&self) -> impl ExactSizeIterator> { + pub fn view_functions(&self) -> impl ExactSizeIterator> { self.inner .view_functions + .values() .iter() - .map(|vf: &'a _| PalletViewFunctionMetadata { + .map(|vf: &'a _| ViewFunctionMetadata { + inner: vf, + types: self.types, + }) + } + + /// Return the view function with a given name, if any + pub fn view_function_by_name(&self, name: &str) -> Option> { + self.inner + .view_functions + .get_by_key(name) + .map(|vf: &'a _| ViewFunctionMetadata { inner: vf, types: self.types, }) @@ -404,7 +435,7 @@ struct PalletMetadataInner { /// Map from constant name to constant details. constants: OrderedMap, /// Details about each of the pallet view functions. - view_functions: Vec, + view_functions: OrderedMap, /// Mapping from associated type to type ID describing its shape. associated_types: BTreeMap, /// Pallet documentation. @@ -832,41 +863,48 @@ struct RuntimeApiMethodMetadataInner { docs: Vec, } -/// Metadata for the available pallet View Functions. +/// Metadata for the available View Functions. Currently these exist only +/// at the pallet level, but eventually they could exist at the runtime level too. #[derive(Debug, Clone, Copy)] -pub struct PalletViewFunctionMetadata<'a> { - inner: &'a PalletViewFunctionMetadataInner, +pub struct ViewFunctionMetadata<'a> { + inner: &'a ViewFunctionMetadataInner, types: &'a PortableRegistry, } -impl PalletViewFunctionMetadata<'_> { +impl<'a> ViewFunctionMetadata<'a> { /// Method name. - pub fn name(&self) -> &str { + pub fn name(&self) -> &'a str { &self.inner.name } /// Query ID. This is used to query the function. Roughly, it is constructed by doing /// `twox_128(pallet_name) ++ twox_128("fn_name(fnarg_types) -> return_ty")` . - pub fn query_id(&self) -> [u8; 32] { - self.inner.query_id + pub fn query_id(&self) -> &'a [u8; 32] { + &self.inner.query_id } /// Method documentation. - pub fn docs(&self) -> &[String] { + pub fn docs(&self) -> &'a [String] { &self.inner.docs } /// Method inputs. - pub fn inputs(&self) -> impl ExactSizeIterator { + pub fn inputs(&self) -> impl ExactSizeIterator { self.inner.inputs.iter() } /// Method return type. pub fn output_ty(&self) -> u32 { self.inner.output_ty } + /// Return a hash for the method. The query ID of a view function validates it to some + /// degree, but only takes type _names_ into account. This hash takes into account the + /// actual _shape_ of each argument and the return type. + pub fn hash(&self) -> [u8; HASH_LEN] { + crate::utils::validation::get_view_function_hash(self) + } } #[derive(Debug, Clone)] -struct PalletViewFunctionMetadataInner { +struct ViewFunctionMetadataInner { /// View function name. - name: String, + name: ArcStr, /// View function query ID. query_id: [u8; 32], /// Input types. @@ -966,6 +1004,7 @@ impl codec::Decode for Metadata { let metadata = match metadata.1 { frame_metadata::RuntimeMetadata::V14(md) => md.try_into(), frame_metadata::RuntimeMetadata::V15(md) => md.try_into(), + frame_metadata::RuntimeMetadata::V16(md) => md.try_into(), _ => return Err("Cannot try_into() to Metadata: unsupported metadata version".into()), }; diff --git a/metadata/src/utils/validation.rs b/metadata/src/utils/validation.rs index 2237571f17..e114e27c57 100644 --- a/metadata/src/utils/validation.rs +++ b/metadata/src/utils/validation.rs @@ -6,8 +6,8 @@ use crate::{ CustomMetadata, CustomValueMetadata, ExtrinsicMetadata, Metadata, PalletMetadata, - PalletViewFunctionMetadata, RuntimeApiMetadata, RuntimeApiMethodMetadata, StorageEntryMetadata, - StorageEntryType, + RuntimeApiMetadata, RuntimeApiMethodMetadata, StorageEntryMetadata, StorageEntryType, + ViewFunctionMetadata, }; use alloc::vec::Vec; use hashbrown::HashMap; @@ -406,12 +406,12 @@ pub fn get_runtime_apis_hash(trait_metadata: RuntimeApiMetadata) -> Hash { }) } -/// Obtain the hash of a specific pallet view function, or an error if it's not found. -pub fn get_pallet_view_function_hash(view_function: &PalletViewFunctionMetadata) -> Hash { +/// Obtain the hash of a specific view function, or an error if it's not found. +pub fn get_view_function_hash(view_function: &ViewFunctionMetadata) -> Hash { let registry = view_function.types; // The Query ID is `twox_128(pallet_name) ++ twox_128("fn_name(fnarg_types) -> return_ty")`. - let mut bytes = view_function.query_id(); + let mut bytes = *view_function.query_id(); // This only takes type _names_ into account, so we beef this up by combining with actual // type hashes, in a similar approach to runtime APIs.. @@ -439,7 +439,7 @@ fn get_pallet_view_functions_hash(pallet_metadata: &PalletMetadata) -> Hash { // be identical regardless. For this, we can just XOR the hashes for each method // together; we'll get the same output whichever order they are XOR'd together in, // so long as each individual method is the same. - xor(bytes, get_pallet_view_function_hash(&method_metadata)) + xor(bytes, get_view_function_hash(&method_metadata)) }) } diff --git a/subxt/src/client/offline_client.rs b/subxt/src/client/offline_client.rs index 9f8a8f0d68..15845ef255 100644 --- a/subxt/src/client/offline_client.rs +++ b/subxt/src/client/offline_client.rs @@ -11,6 +11,7 @@ use crate::{ runtime_api::RuntimeApiClient, storage::StorageClient, tx::TxClient, + view_functions::ViewFunctionsClient, Metadata, }; @@ -67,11 +68,16 @@ pub trait OfflineClientT: Clone + Send + Sync + 'static { BlocksClient::new(self.clone()) } - /// Work with runtime API. + /// Work with runtime APIs. fn runtime_api(&self) -> RuntimeApiClient { RuntimeApiClient::new(self.clone()) } + /// Work with View Functions. + fn view_functions(&self) -> ViewFunctionsClient { + ViewFunctionsClient::new(self.clone()) + } + /// Work this custom types. fn custom_values(&self) -> CustomValuesClient { CustomValuesClient::new(self.clone()) @@ -150,6 +156,21 @@ impl OfflineClient { >::constants(self) } + /// Work with blocks. + pub fn blocks(&self) -> BlocksClient { + >::blocks(self) + } + + /// Work with runtime APIs. + pub fn runtime_api(&self) -> RuntimeApiClient { + >::runtime_api(self) + } + + /// Work with View Functions. + pub fn view_functions(&self) -> ViewFunctionsClient { + >::view_functions(self) + } + /// Access custom types pub fn custom_values(&self) -> CustomValuesClient { >::custom_values(self) diff --git a/subxt/src/client/online_client.rs b/subxt/src/client/online_client.rs index 9824520fad..e5032fb16a 100644 --- a/subxt/src/client/online_client.rs +++ b/subxt/src/client/online_client.rs @@ -14,6 +14,7 @@ use crate::{ runtime_api::RuntimeApiClient, storage::StorageClient, tx::TxClient, + view_functions::ViewFunctionsClient, Metadata, }; use derive_where::derive_where; @@ -348,11 +349,6 @@ impl OnlineClient { >::constants(self) } - /// Access custom types. - pub fn custom_values(&self) -> CustomValuesClient { - >::custom_values(self) - } - /// Work with blocks. pub fn blocks(&self) -> BlocksClient { >::blocks(self) @@ -362,6 +358,16 @@ impl OnlineClient { pub fn runtime_api(&self) -> RuntimeApiClient { >::runtime_api(self) } + + /// Work with View Functions. + pub fn view_functions(&self) -> ViewFunctionsClient { + >::view_functions(self) + } + + /// Access custom types. + pub fn custom_values(&self) -> CustomValuesClient { + >::custom_values(self) + } } impl OfflineClientT for OnlineClient { diff --git a/subxt/src/lib.rs b/subxt/src/lib.rs index 57dc44263c..57b8e02e34 100644 --- a/subxt/src/lib.rs +++ b/subxt/src/lib.rs @@ -49,6 +49,7 @@ pub mod runtime_api; pub mod storage; pub mod tx; pub mod utils; +pub mod view_functions; /// This module provides a [`Config`] type, which is used to define various /// types that are important in order to speak to a particular chain. @@ -75,7 +76,8 @@ pub mod metadata { /// Submit dynamic transactions. pub mod dynamic { pub use subxt_core::dynamic::{ - constant, runtime_api_call, storage, tx, At, DecodedValue, DecodedValueThunk, Value, + constant, runtime_api_call, storage, tx, view_function_call, At, DecodedValue, + DecodedValueThunk, Value, }; } diff --git a/subxt/src/view_functions/mod.rs b/subxt/src/view_functions/mod.rs new file mode 100644 index 0000000000..c8fa159512 --- /dev/null +++ b/subxt/src/view_functions/mod.rs @@ -0,0 +1,14 @@ +// Copyright 2019-2025 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +//! Types associated with executing View Function calls. + +mod view_function_types; +mod view_functions_client; + +pub use subxt_core::view_functions::payload::{ + dynamic, DefaultPayload, DynamicPayload, Payload, StaticPayload, +}; +pub use view_function_types::ViewFunctionsApi; +pub use view_functions_client::ViewFunctionsClient; diff --git a/subxt/src/view_functions/view_function_types.rs b/subxt/src/view_functions/view_function_types.rs new file mode 100644 index 0000000000..fef1ac47e2 --- /dev/null +++ b/subxt/src/view_functions/view_function_types.rs @@ -0,0 +1,79 @@ +// Copyright 2019-2025 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +use super::Payload; +use crate::{ + backend::BlockRef, + client::OnlineClientT, + config::{Config, HashFor}, + error::Error, +}; +use derive_where::derive_where; +use std::{future::Future, marker::PhantomData}; + +/// Execute View Function calls. +#[derive_where(Clone; Client)] +pub struct ViewFunctionsApi { + client: Client, + block_ref: BlockRef>, + _marker: PhantomData, +} + +impl ViewFunctionsApi { + /// Create a new [`ViewFunctionsApi`] + pub(crate) fn new(client: Client, block_ref: BlockRef>) -> Self { + Self { + client, + block_ref, + _marker: PhantomData, + } + } +} + +impl ViewFunctionsApi +where + T: Config, + Client: OnlineClientT, +{ + /// Run the validation logic against some View Function payload you'd like to use. Returns `Ok(())` + /// if the payload is valid (or if it's not possible to check since the payload has no validation hash). + /// Return an error if the payload was not valid or something went wrong trying to validate it (ie + /// the View Function in question do not exist at all) + pub fn validate(&self, payload: &Call) -> Result<(), Error> { + subxt_core::view_functions::validate(payload, &self.client.metadata()).map_err(Into::into) + } + + /// Execute a View Function call. + pub fn call( + &self, + payload: Call, + ) -> impl Future> { + let client = self.client.clone(); + let block_hash = self.block_ref.hash(); + // Ensure that the returned future doesn't have a lifetime tied to api.view_functions(), + // which is a temporary thing we'll be throwing away quickly: + async move { + let metadata = client.metadata(); + + // Validate the View Function payload hash against the compile hash from codegen. + subxt_core::view_functions::validate(&payload, &metadata)?; + + // Assemble the data to call the "execute_view_function" runtime API, which + // then calls the relevant view function. + let call_name = subxt_core::view_functions::CALL_NAME; + let call_args = subxt_core::view_functions::call_args(&payload, &metadata)?; + + // Make the call. + let bytes = client + .backend() + .call(call_name, Some(call_args.as_slice()), block_hash) + .await?; + + // Decode the response. + let value = + subxt_core::view_functions::decode_value(&mut &*bytes, &payload, &metadata)?; + Ok(value) + } + } +} diff --git a/subxt/src/view_functions/view_functions_client.rs b/subxt/src/view_functions/view_functions_client.rs new file mode 100644 index 0000000000..9c1dae3483 --- /dev/null +++ b/subxt/src/view_functions/view_functions_client.rs @@ -0,0 +1,57 @@ +// Copyright 2019-2025 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +use super::view_function_types::ViewFunctionsApi; + +use crate::{ + backend::BlockRef, + client::OnlineClientT, + config::{Config, HashFor}, + error::Error, +}; +use derive_where::derive_where; +use std::{future::Future, marker::PhantomData}; + +/// Make View Function calls at some block. +#[derive_where(Clone; Client)] +pub struct ViewFunctionsClient { + client: Client, + _marker: PhantomData, +} + +impl ViewFunctionsClient { + /// Create a new [`ViewFunctionsClient`] + pub fn new(client: Client) -> Self { + Self { + client, + _marker: PhantomData, + } + } +} + +impl ViewFunctionsClient +where + T: Config, + Client: OnlineClientT, +{ + /// Obtain an interface to call View Functions at some block hash. + pub fn at(&self, block_ref: impl Into>>) -> ViewFunctionsApi { + ViewFunctionsApi::new(self.client.clone(), block_ref.into()) + } + + /// Obtain an interface to call View Functions at the latest block hash. + pub fn at_latest( + &self, + ) -> impl Future, Error>> + Send + 'static { + // Clone and pass the client in like this so that we can explicitly + // return a Future that's Send + 'static, rather than tied to &self. + let client = self.client.clone(); + async move { + // get the ref for the latest finalized block and use that. + let block_ref = client.backend().latest_finalized_block_ref().await?; + + Ok(ViewFunctionsApi::new(client, block_ref)) + } + } +} diff --git a/testing/integration-tests/Cargo.toml b/testing/integration-tests/Cargo.toml index 400c4e3a6c..780f9bd2d8 100644 --- a/testing/integration-tests/Cargo.toml +++ b/testing/integration-tests/Cargo.toml @@ -35,6 +35,7 @@ hex = { workspace = true } regex = { workspace = true } serde = { workspace = true } scale-info = { workspace = true, features = ["bit-vec"] } +scale-value = { workspace = true } sp-core = { workspace = true, features = ["std"] } syn = { workspace = true } subxt = { workspace = true, features = ["unstable-metadata", "native", "jsonrpsee", "reconnecting-rpc-client"] } diff --git a/testing/integration-tests/src/full_client/mod.rs b/testing/integration-tests/src/full_client/mod.rs index 8e4646625d..89bfc7b546 100644 --- a/testing/integration-tests/src/full_client/mod.rs +++ b/testing/integration-tests/src/full_client/mod.rs @@ -7,6 +7,7 @@ mod client; mod codegen; mod frame; mod metadata_validation; +mod pallet_view_functions; mod runtime_api; mod storage; mod transactions; diff --git a/testing/integration-tests/src/full_client/pallet_view_functions.rs b/testing/integration-tests/src/full_client/pallet_view_functions.rs new file mode 100644 index 0000000000..b783287212 --- /dev/null +++ b/testing/integration-tests/src/full_client/pallet_view_functions.rs @@ -0,0 +1,60 @@ +use crate::{subxt_test, test_context}; +use test_runtime::node_runtime_unstable; + +#[subxt_test] +async fn call_view_function() -> Result<(), subxt::Error> { + let ctx = test_context().await; + let api = ctx.client(); + + use node_runtime_unstable::proxy::view_functions::check_permissions::{Call, ProxyType}; + + // This is one of only two view functions that currently exists at the time of writing. + let call = Call::System(node_runtime_unstable::system::Call::remark { + remark: b"hi".to_vec(), + }); + let proxy_type = ProxyType::Any; + let view_function_call = node_runtime_unstable::view_functions() + .proxy() + .check_permissions(call, proxy_type); + + // Submit the call and get back a result. + let _is_call_allowed = api + .view_functions() + .at_latest() + .await? + .call(view_function_call) + .await?; + + Ok(()) +} + +#[subxt_test] +async fn call_view_function_dynamically() -> Result<(), subxt::Error> { + let ctx = test_context().await; + let api = ctx.client(); + let metadata = api.metadata(); + + let query_id = metadata + .pallet_by_name("Proxy") + .unwrap() + .view_function_by_name("check_permissions") + .unwrap() + .query_id(); + + use scale_value::value; + + let view_function_call = subxt::dynamic::view_function_call( + *query_id, + vec![value!(System(remark(b"hi".to_vec()))), value!(Any())], + ); + + // Submit the call and get back a result. + let _is_call_allowed = api + .view_functions() + .at_latest() + .await? + .call(view_function_call) + .await?; + + Ok(()) +} diff --git a/testing/test-runtime/build.rs b/testing/test-runtime/build.rs index 9ef1dae647..63bd8535fd 100644 --- a/testing/test-runtime/build.rs +++ b/testing/test-runtime/build.rs @@ -9,6 +9,9 @@ use substrate_runner::{Error as SubstrateNodeError, SubstrateNode}; // This variable accepts a single binary name or comma separated list. static SUBSTRATE_BIN_ENV_VAR: &str = "SUBSTRATE_NODE_PATH"; +const V15_METADATA_VERSION: u32 = 15; +const UNSTABLE_METADATA_VERSION: u32 = u32::MAX; + #[tokio::main] async fn main() { run().await; @@ -37,11 +40,62 @@ async fn run() { }; let port = node.ws_port(); + let out_dir_env_var = env::var_os("OUT_DIR"); + let out_dir = out_dir_env_var.as_ref().unwrap().to_str().unwrap(); - // Download metadata from binary. Avoid Subxt dep on `subxt::rpc::types::Bytes`and just impl here. - // This may at least prevent this script from running so often (ie whenever we change Subxt). - const V15_METADATA_VERSION: u32 = 15; - let bytes = V15_METADATA_VERSION.encode(); + let (stable_metadata_path, unstable_metadata_path) = tokio::join!( + download_and_save_metadata(V15_METADATA_VERSION, port, out_dir, "v15"), + download_and_save_metadata(UNSTABLE_METADATA_VERSION, port, out_dir, "unstable") + ); + + // Write out our expression to generate the runtime API to a file. Ideally, we'd just write this code + // in lib.rs, but we must pass a string literal (and not `concat!(..)`) as an arg to `runtime_metadata_path`, + // and so we need to spit it out here and include it verbatim instead. + let runtime_api_contents = format!( + r#" + /// Generated types for the locally running Substrate node using V15 metadata. + #[subxt::subxt( + runtime_metadata_path = "{stable_metadata_path}", + derive_for_all_types = "Eq, PartialEq", + )] + pub mod node_runtime {{}} + + /// Generated types for the locally running Substrate node using the unstable metadata. + #[subxt::subxt( + runtime_metadata_path = "{unstable_metadata_path}", + derive_for_all_types = "Eq, PartialEq", + )] + pub mod node_runtime_unstable {{}} + "# + ); + let runtime_path = Path::new(&out_dir).join("runtime.rs"); + fs::write(runtime_path, runtime_api_contents).expect("Couldn't write runtime rust output"); + + for substrate_node_path in substrate_bins_vec { + let Ok(full_path) = which::which(substrate_node_path) else { + continue; + }; + + // Re-build if the substrate binary we're pointed to changes (mtime): + println!("cargo:rerun-if-changed={}", full_path.to_string_lossy()); + } + + // Re-build if we point to a different substrate binary: + println!("cargo:rerun-if-env-changed={SUBSTRATE_BIN_ENV_VAR}"); + // Re-build if this file changes: + println!("cargo:rerun-if-changed=build.rs"); +} + +// Download metadata from binary. Avoid Subxt dep on `subxt::rpc::types::Bytes`and just impl here. +// This may at least prevent this script from running so often (ie whenever we change Subxt). +async fn download_and_save_metadata( + version: u32, + port: u16, + out_dir: &str, + suffix: &str, +) -> String { + // Download it: + let bytes = version.encode(); let version: String = format!("0x{}", hex::encode(&bytes)); let raw: String = { use client::ClientT; @@ -61,42 +115,16 @@ async fn run() { .unwrap_or_else(|e| panic!("Failed to decode metadata bytes: {e}")); let metadata_bytes = bytes.expect("Metadata version not found"); - // Save metadata to a file: - let out_dir = env::var_os("OUT_DIR").unwrap(); - let metadata_path = Path::new(&out_dir).join("test_node_runtime_metadata.scale"); + // Save it to a file: + let metadata_path = + Path::new(&out_dir).join(format!("test_node_runtime_metadata_{suffix}.scale")); fs::write(&metadata_path, metadata_bytes).expect("Couldn't write metadata output"); - // Write out our expression to generate the runtime API to a file. Ideally, we'd just write this code - // in lib.rs, but we must pass a string literal (and not `concat!(..)`) as an arg to `runtime_metadata_path`, - // and so we need to spit it out here and include it verbatim instead. - let runtime_api_contents = format!( - r#" - #[subxt::subxt( - runtime_metadata_path = "{}", - derive_for_all_types = "Eq, PartialEq", - )] - pub mod node_runtime {{}} - "#, - metadata_path - .to_str() - .expect("Path to metadata should be stringifiable") - ); - let runtime_path = Path::new(&out_dir).join("runtime.rs"); - fs::write(runtime_path, runtime_api_contents).expect("Couldn't write runtime rust output"); - - for substrate_node_path in substrate_bins_vec { - let Ok(full_path) = which::which(substrate_node_path) else { - continue; - }; - - // Re-build if the substrate binary we're pointed to changes (mtime): - println!("cargo:rerun-if-changed={}", full_path.to_string_lossy()); - } - - // Re-build if we point to a different substrate binary: - println!("cargo:rerun-if-env-changed={SUBSTRATE_BIN_ENV_VAR}"); - // Re-build if this file changes: - println!("cargo:rerun-if-changed=build.rs"); + // Convert path to string because we need this to interpolate into string + metadata_path + .to_str() + .expect("Path to metadata should be stringifiable") + .to_owned() } // Use jsonrpsee to obtain metadata from the node. diff --git a/testing/test-runtime/src/lib.rs b/testing/test-runtime/src/lib.rs index a9f1f4c458..26de88d537 100644 --- a/testing/test-runtime/src/lib.rs +++ b/testing/test-runtime/src/lib.rs @@ -7,7 +7,7 @@ /// The SCALE encoded metadata obtained from a local run of a substrate node. pub static METADATA: &[u8] = include_bytes!(concat!( env!("OUT_DIR"), - "/test_node_runtime_metadata.scale" + "/test_node_runtime_metadata_v15.scale" )); include!(concat!(env!("OUT_DIR"), "/runtime.rs")); diff --git a/utils/strip-metadata/Cargo.toml b/utils/strip-metadata/Cargo.toml index 2cd85616a2..146c2bc819 100644 --- a/utils/strip-metadata/Cargo.toml +++ b/utils/strip-metadata/Cargo.toml @@ -14,6 +14,7 @@ homepage.workspace = true description = "subxt utility to strip metadata" [dependencies] +codec = { workspace = true } frame-metadata = { workspace = true, features = ["std"] } scale-info = { workspace = true, features = ["std"] } either = { workspace = true } diff --git a/utils/strip-metadata/src/lib.rs b/utils/strip-metadata/src/lib.rs index 8a8527f708..e74c689bf8 100644 --- a/utils/strip-metadata/src/lib.rs +++ b/utils/strip-metadata/src/lib.rs @@ -439,6 +439,7 @@ mod test { use std::collections::BTreeMap; use super::*; + use codec::Compact; use scale_info::meta_type; /// Create dummy types that we can check the presense of with is_in_types. @@ -817,7 +818,7 @@ mod test { view_functions: vec![v16::PalletViewFunctionMetadata { name: "some_view_function", id: [0; 32], - inputs: vec![v16::PalletViewFunctionParamMetadata { + inputs: vec![v16::FunctionParamMetadata { name: "input1", ty: meta_type::(), }], @@ -842,12 +843,12 @@ mod test { let runtime_apis = vec![ v16::RuntimeApiMetadata { name: "SomeApi", - version: 2, + version: Compact(2), docs: vec![], deprecation_info: v16::DeprecationStatus::NotDeprecated, methods: vec![v16::RuntimeApiMethodMetadata { name: "some_method", - inputs: vec![v16::RuntimeApiMethodParamMetadata { + inputs: vec![v16::FunctionParamMetadata { name: "input1", ty: meta_type::(), }], @@ -858,12 +859,12 @@ mod test { }, v16::RuntimeApiMetadata { name: "AnotherApi", - version: 1, + version: Compact(1), docs: vec![], deprecation_info: v16::DeprecationStatus::NotDeprecated, methods: vec![v16::RuntimeApiMethodMetadata { name: "another_method", - inputs: vec![v16::RuntimeApiMethodParamMetadata { + inputs: vec![v16::FunctionParamMetadata { name: "input1", ty: meta_type::(), }],