mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 07:01:05 +00:00
Metadata V16: Implement support for Pallet View Functions (#1981)
* Support Pallet View Functions in Subxt * fmt * clippy * Move a little view function logic to subxt_core * clippy * Add back check that prob isnt needed * avoid vec macro in core * Add view funciton test and apply various fixes to get it working * Add test for dynamic view fn call and fix issues * clippy * fix test-runtime * fmt * remove export * avoid vec for nostd core * use const instead of fn for view fn call name * Update to support latest unstable metadata * Update metadata stripping tests for new v16 version
This commit is contained in:
Generated
+22
-20
@@ -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",
|
||||
|
||||
+2
-2
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Input> = {
|
||||
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::<Vec<_>>();
|
||||
|
||||
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::<Vec<_>>();
|
||||
|
||||
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<TokenStream2, CodegenError> {
|
||||
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::<Result<_, _>>()?;
|
||||
|
||||
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 )*
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<P: Payload>(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<P: Payload>(payload: &P, metadata: &Metadata) -> Result<Vec<u8>, 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<P: Payload>(
|
||||
bytes: &mut &[u8],
|
||||
payload: &P,
|
||||
metadata: &Metadata,
|
||||
) -> Result<P::ReturnType, Error> {
|
||||
let view_function = metadata
|
||||
.view_function_by_query_id(payload.query_id())
|
||||
.ok_or_else(|| MetadataError::ViewFunctionNotFound(*payload.query_id()))?;
|
||||
|
||||
let val = <P::ReturnType as DecodeWithMetadata>::decode_with_metadata(
|
||||
&mut &bytes[..],
|
||||
view_function.output_ty(),
|
||||
metadata,
|
||||
)?;
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
@@ -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<u8>) -> 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<Vec<u8>, 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<ArgsData, ReturnTy> {
|
||||
query_id: [u8; 32],
|
||||
args_data: ArgsData,
|
||||
validation_hash: Option<[u8; 32]>,
|
||||
_marker: PhantomData<ReturnTy>,
|
||||
}
|
||||
|
||||
/// A statically generated View Function payload.
|
||||
pub type StaticPayload<ArgsData, ReturnTy> = DefaultPayload<ArgsData, ReturnTy>;
|
||||
/// A dynamic View Function payload.
|
||||
pub type DynamicPayload = DefaultPayload<Composite<()>, DecodedValueThunk>;
|
||||
|
||||
impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> Payload
|
||||
for DefaultPayload<ArgsData, ReturnTy>
|
||||
{
|
||||
type ReturnType = ReturnTy;
|
||||
|
||||
fn query_id(&self) -> &[u8; 32] {
|
||||
&self.query_id
|
||||
}
|
||||
|
||||
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> 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<ReturnTy, ArgsData> DefaultPayload<ArgsData, ReturnTy> {
|
||||
/// 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<ArgsData, ReturnTy> {
|
||||
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<Composite<()>>) -> DynamicPayload {
|
||||
DefaultPayload::new(query_id, args_data.into())
|
||||
}
|
||||
@@ -68,7 +68,7 @@ impl TryFrom<v14::RuntimeMetadataV14> 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![],
|
||||
},
|
||||
|
||||
@@ -63,7 +63,7 @@ impl TryFrom<v15::RuntimeMetadataV15> 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,
|
||||
},
|
||||
|
||||
+16
-12
@@ -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<v16::RuntimeMetadataV16> 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<PortableForm>) -> 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<PortableForm>,
|
||||
) -> PalletViewFunctionMetadataInner {
|
||||
PalletViewFunctionMetadataInner {
|
||||
name: s.name,
|
||||
) -> ViewFunctionMetadataInner {
|
||||
ViewFunctionMetadataInner {
|
||||
name,
|
||||
query_id: s.id,
|
||||
inputs: s
|
||||
.inputs
|
||||
|
||||
+53
-14
@@ -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<ViewFunctionMetadata<'_>> {
|
||||
// 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<Item = PalletViewFunctionMetadata<'a>> {
|
||||
pub fn view_functions(&self) -> impl ExactSizeIterator<Item = ViewFunctionMetadata<'a>> {
|
||||
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<ViewFunctionMetadata<'a>> {
|
||||
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<ArcStr, ConstantMetadata>,
|
||||
/// Details about each of the pallet view functions.
|
||||
view_functions: Vec<PalletViewFunctionMetadataInner>,
|
||||
view_functions: OrderedMap<ArcStr, ViewFunctionMetadataInner>,
|
||||
/// Mapping from associated type to type ID describing its shape.
|
||||
associated_types: BTreeMap<String, u32>,
|
||||
/// Pallet documentation.
|
||||
@@ -832,41 +863,48 @@ struct RuntimeApiMethodMetadataInner {
|
||||
docs: Vec<String>,
|
||||
}
|
||||
|
||||
/// 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<Item = &MethodParamMetadata> {
|
||||
pub fn inputs(&self) -> impl ExactSizeIterator<Item = &'a MethodParamMetadata> {
|
||||
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()),
|
||||
};
|
||||
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::{
|
||||
runtime_api::RuntimeApiClient,
|
||||
storage::StorageClient,
|
||||
tx::TxClient,
|
||||
view_functions::ViewFunctionsClient,
|
||||
Metadata,
|
||||
};
|
||||
|
||||
@@ -67,11 +68,16 @@ pub trait OfflineClientT<T: Config>: Clone + Send + Sync + 'static {
|
||||
BlocksClient::new(self.clone())
|
||||
}
|
||||
|
||||
/// Work with runtime API.
|
||||
/// Work with runtime APIs.
|
||||
fn runtime_api(&self) -> RuntimeApiClient<T, Self> {
|
||||
RuntimeApiClient::new(self.clone())
|
||||
}
|
||||
|
||||
/// Work with View Functions.
|
||||
fn view_functions(&self) -> ViewFunctionsClient<T, Self> {
|
||||
ViewFunctionsClient::new(self.clone())
|
||||
}
|
||||
|
||||
/// Work this custom types.
|
||||
fn custom_values(&self) -> CustomValuesClient<T, Self> {
|
||||
CustomValuesClient::new(self.clone())
|
||||
@@ -150,6 +156,21 @@ impl<T: Config> OfflineClient<T> {
|
||||
<Self as OfflineClientT<T>>::constants(self)
|
||||
}
|
||||
|
||||
/// Work with blocks.
|
||||
pub fn blocks(&self) -> BlocksClient<T, Self> {
|
||||
<Self as OfflineClientT<T>>::blocks(self)
|
||||
}
|
||||
|
||||
/// Work with runtime APIs.
|
||||
pub fn runtime_api(&self) -> RuntimeApiClient<T, Self> {
|
||||
<Self as OfflineClientT<T>>::runtime_api(self)
|
||||
}
|
||||
|
||||
/// Work with View Functions.
|
||||
pub fn view_functions(&self) -> ViewFunctionsClient<T, Self> {
|
||||
<Self as OfflineClientT<T>>::view_functions(self)
|
||||
}
|
||||
|
||||
/// Access custom types
|
||||
pub fn custom_values(&self) -> CustomValuesClient<T, Self> {
|
||||
<Self as OfflineClientT<T>>::custom_values(self)
|
||||
|
||||
@@ -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<T: Config> OnlineClient<T> {
|
||||
<Self as OfflineClientT<T>>::constants(self)
|
||||
}
|
||||
|
||||
/// Access custom types.
|
||||
pub fn custom_values(&self) -> CustomValuesClient<T, Self> {
|
||||
<Self as OfflineClientT<T>>::custom_values(self)
|
||||
}
|
||||
|
||||
/// Work with blocks.
|
||||
pub fn blocks(&self) -> BlocksClient<T, Self> {
|
||||
<Self as OfflineClientT<T>>::blocks(self)
|
||||
@@ -362,6 +358,16 @@ impl<T: Config> OnlineClient<T> {
|
||||
pub fn runtime_api(&self) -> RuntimeApiClient<T, Self> {
|
||||
<Self as OfflineClientT<T>>::runtime_api(self)
|
||||
}
|
||||
|
||||
/// Work with View Functions.
|
||||
pub fn view_functions(&self) -> ViewFunctionsClient<T, Self> {
|
||||
<Self as OfflineClientT<T>>::view_functions(self)
|
||||
}
|
||||
|
||||
/// Access custom types.
|
||||
pub fn custom_values(&self) -> CustomValuesClient<T, Self> {
|
||||
<Self as OfflineClientT<T>>::custom_values(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> OfflineClientT<T> for OnlineClient<T> {
|
||||
|
||||
+3
-1
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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<T: Config, Client> {
|
||||
client: Client,
|
||||
block_ref: BlockRef<HashFor<T>>,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Config, Client> ViewFunctionsApi<T, Client> {
|
||||
/// Create a new [`ViewFunctionsApi`]
|
||||
pub(crate) fn new(client: Client, block_ref: BlockRef<HashFor<T>>) -> Self {
|
||||
Self {
|
||||
client,
|
||||
block_ref,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Client> ViewFunctionsApi<T, Client>
|
||||
where
|
||||
T: Config,
|
||||
Client: OnlineClientT<T>,
|
||||
{
|
||||
/// 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<Call: Payload>(&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<Call: Payload>(
|
||||
&self,
|
||||
payload: Call,
|
||||
) -> impl Future<Output = Result<Call::ReturnType, Error>> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<T, Client> {
|
||||
client: Client,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, Client> ViewFunctionsClient<T, Client> {
|
||||
/// Create a new [`ViewFunctionsClient`]
|
||||
pub fn new(client: Client) -> Self {
|
||||
Self {
|
||||
client,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Client> ViewFunctionsClient<T, Client>
|
||||
where
|
||||
T: Config,
|
||||
Client: OnlineClientT<T>,
|
||||
{
|
||||
/// Obtain an interface to call View Functions at some block hash.
|
||||
pub fn at(&self, block_ref: impl Into<BlockRef<HashFor<T>>>) -> ViewFunctionsApi<T, Client> {
|
||||
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<Output = Result<ViewFunctionsApi<T, Client>, 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"] }
|
||||
|
||||
@@ -7,6 +7,7 @@ mod client;
|
||||
mod codegen;
|
||||
mod frame;
|
||||
mod metadata_validation;
|
||||
mod pallet_view_functions;
|
||||
mod runtime_api;
|
||||
mod storage;
|
||||
mod transactions;
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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::<F>(),
|
||||
}],
|
||||
@@ -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::<J>(),
|
||||
}],
|
||||
@@ -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::<L>(),
|
||||
}],
|
||||
|
||||
Reference in New Issue
Block a user