mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 23:31:07 +00:00
v0.50.0: Integrate frame-decode, redo storage APIs and break up Error. (#2100)
* WIP integrating new frame-decode and working out new storage APIS * WIP: first pass adding new storage things to subxt-core * Second pass over Address type and start impl in Subxt * WIP new storage APIs * WIP New storage APIs roughly completed, lots of errors still * Remove PlainorMap enum; plain and map values now use same struct to simplify usage * Begin 'fixing' errors * WIP splitting errors and tidying payload/address traits * Get subxt-core compiling * Small fixes in subxt-core and remove metadata mod * subxt-core: cargo check --all-targets passes * Fix test * WIP starting to update subxt from subxt-core changes * WIP splitting up subxt errors into smaller variants * WIP errors: add DispatchError errors * Port new Storage APIs to subxt-core * cargo check -p subxt passes * Quick-fix errors in subxt-cli (explore subcommand) * fmt * Finish fixing codegen up and start fixing examples * get Subxt examples compiling and bytes_at for constants * Add some arcs to limit lifetimes in subxt/subxt-core storage APIs * A little Arcing to allow more method chaining in Storage APIs, aligning with Subxt * Update codegen test * cargo check --all-targets passing * cargo check --features 'unstable-light-client' passing * clippy * Remove unused dep in subxt * use published frame-decode * fix wasm-example * Add new tx extension to fix daily tests * Remove unused subxt_core::dynamic::DecodedValue type * Update book to match changes * Update docs to fix more broken bits * Add missing docs * fmt * allow larger result errs for now * Add missing alloc imports in subxt-core * Fix doc tests and fix bug getting constant info * Fix V14 -> Metadata transform for storage & constants * Fix parachain example * Fix FFI example * BlockLength decodes t ostruct, not u128 * use fetch/iter shorthands rather than entry in most storage tests * Fix some integration tests * Fix Runtime codegen tests * Expose the dynamic custom_value selecter and use in a UI test * Update codegen metadata * Tidy CLI storage query and support (str,str) as a storage address * Add (str,str) as valid constant address too * Show string tuple in constants example * Via the magic of traits, avoid needing any clones of queries/addresses and accept references to them * clippy
This commit is contained in:
Generated
+6
-6
@@ -1953,9 +1953,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "frame-decode"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "641e3739fa708a278d35b008a05244008c221240abc3e1c27138466c13e999ed"
|
||||
checksum = "f8c26b7a0fb90c471cc2c90b1a9fa60ab78c6a35a4079130feefd625999708d7"
|
||||
dependencies = [
|
||||
"frame-metadata 23.0.0",
|
||||
"parity-scale-codec",
|
||||
@@ -4464,9 +4464,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "scale-info-legacy"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5da3f59983b08a37d8d979d2326bdc00e8cca57b3d28fb05bdc0f6d7c28600c"
|
||||
checksum = "bd183213b6831b6bc08fda67a310bf9299889d669e264a2a2168679079a0c522"
|
||||
dependencies = [
|
||||
"hashbrown 0.15.3",
|
||||
"scale-type-resolver",
|
||||
@@ -4520,9 +4520,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "scale-value"
|
||||
version = "0.18.0"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ca8b26b451ecb7fd7b62b259fa28add63d12ec49bbcac0e01fcb4b5ae0c09aa"
|
||||
checksum = "884aab179aba344c67ddcd1d7dd8e3f8fee202f2e570d97ec34ec8688442a5b3"
|
||||
dependencies = [
|
||||
"base58",
|
||||
"blake2",
|
||||
|
||||
+3
-3
@@ -81,7 +81,7 @@ 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.10.0", default-features = false }
|
||||
frame-decode = { version = "0.11.1", default-features = false }
|
||||
frame-metadata = { version = "23.0.0", default-features = false }
|
||||
futures = { version = "0.3.31", default-features = false, features = ["std"] }
|
||||
getrandom = { version = "0.2", default-features = false }
|
||||
@@ -98,12 +98,12 @@ proc-macro2 = "1.0.86"
|
||||
quote = "1.0.37"
|
||||
regex = { version = "1.11.0", default-features = false }
|
||||
scale-info = { version = "2.11.4", default-features = false }
|
||||
scale-value = { version = "0.18.0", default-features = false }
|
||||
scale-value = { version = "0.18.1", default-features = false }
|
||||
scale-bits = { version = "0.7.0", default-features = false }
|
||||
scale-decode = { version = "0.16.0", default-features = false }
|
||||
scale-encode = { version = "0.10.0", default-features = false }
|
||||
scale-type-resolver = { version = "0.2.0" }
|
||||
scale-info-legacy = { version = "0.2.3" }
|
||||
scale-info-legacy = { version = "0.2.4" }
|
||||
scale-typegen = "0.11.1"
|
||||
scale-typegen-description = "0.11.0"
|
||||
serde = { version = "1.0.210", default-features = false, features = ["derive"] }
|
||||
|
||||
+14
-27
@@ -215,7 +215,6 @@ struct StorageEntryDiff {
|
||||
key_different: bool,
|
||||
value_different: bool,
|
||||
default_different: bool,
|
||||
modifier_different: bool,
|
||||
}
|
||||
|
||||
impl StorageEntryDiff {
|
||||
@@ -225,41 +224,32 @@ impl StorageEntryDiff {
|
||||
metadata_1: &Metadata,
|
||||
metadata_2: &Metadata,
|
||||
) -> Self {
|
||||
let value_1_ty_id = storage_entry_1.entry_type().value_ty();
|
||||
let value_1_ty_id = storage_entry_1.value_ty();
|
||||
let value_1_hash = metadata_1
|
||||
.type_hash(value_1_ty_id)
|
||||
.expect("type is in metadata; qed");
|
||||
let value_2_ty_id = storage_entry_2.entry_type().value_ty();
|
||||
let value_2_ty_id = storage_entry_2.value_ty();
|
||||
let value_2_hash = metadata_2
|
||||
.type_hash(value_2_ty_id)
|
||||
.expect("type is in metadata; qed");
|
||||
let value_different = value_1_hash != value_2_hash;
|
||||
|
||||
let key_1_hash = storage_entry_1
|
||||
.entry_type()
|
||||
.key_ty()
|
||||
.map(|key_ty| {
|
||||
metadata_1
|
||||
.type_hash(key_ty)
|
||||
.expect("type is in metadata; qed")
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let key_2_hash = storage_entry_2
|
||||
.entry_type()
|
||||
.key_ty()
|
||||
.map(|key_ty| {
|
||||
metadata_2
|
||||
.type_hash(key_ty)
|
||||
.expect("type is in metadata; qed")
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let key_different = key_1_hash != key_2_hash;
|
||||
let key_parts_same = storage_entry_1.keys().len() == storage_entry_2.keys().len()
|
||||
&& storage_entry_1
|
||||
.keys()
|
||||
.zip(storage_entry_2.keys())
|
||||
.all(|(a, b)| {
|
||||
let a_hash = metadata_1.type_hash(a.key_id).expect("type is in metadata");
|
||||
let b_hash = metadata_2.type_hash(b.key_id).expect("type is in metadata");
|
||||
a.hasher == b.hasher && a_hash == b_hash
|
||||
});
|
||||
|
||||
let key_different = !key_parts_same;
|
||||
|
||||
StorageEntryDiff {
|
||||
key_different,
|
||||
value_different,
|
||||
default_different: storage_entry_1.default_bytes() != storage_entry_2.default_bytes(),
|
||||
modifier_different: storage_entry_1.modifier() != storage_entry_2.modifier(),
|
||||
default_different: storage_entry_1.default_value() != storage_entry_2.default_value(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,9 +261,6 @@ impl StorageEntryDiff {
|
||||
if self.value_different {
|
||||
strings.push("value type");
|
||||
}
|
||||
if self.modifier_different {
|
||||
strings.push("modifier");
|
||||
}
|
||||
if self.default_different {
|
||||
strings.push("default value");
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use subxt::utils::H256;
|
||||
use subxt::{
|
||||
OfflineClient,
|
||||
config::SubstrateConfig,
|
||||
metadata::{Metadata, types::PalletMetadata},
|
||||
metadata::{Metadata, PalletMetadata},
|
||||
};
|
||||
|
||||
use crate::utils::{
|
||||
|
||||
@@ -2,7 +2,7 @@ use clap::Args;
|
||||
use color_eyre::eyre::eyre;
|
||||
use indoc::{formatdoc, writedoc};
|
||||
use scale_typegen_description::type_description;
|
||||
use subxt::metadata::{Metadata, types::PalletMetadata};
|
||||
use subxt::metadata::{Metadata, PalletMetadata};
|
||||
|
||||
use crate::utils::{Indent, SyntaxHighlight, first_paragraph_of_docs, format_scale_value};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use clap::Args;
|
||||
use color_eyre::eyre::eyre;
|
||||
use indoc::{formatdoc, writedoc};
|
||||
use scale_info::{Variant, form::PortableForm};
|
||||
use subxt::metadata::{Metadata, types::PalletMetadata};
|
||||
use subxt::metadata::{Metadata, PalletMetadata};
|
||||
|
||||
use crate::utils::{Indent, fields_description, first_paragraph_of_docs};
|
||||
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
use clap::Args;
|
||||
use color_eyre::{
|
||||
eyre::{bail, eyre},
|
||||
owo_colors::OwoColorize,
|
||||
};
|
||||
use color_eyre::{eyre::bail, owo_colors::OwoColorize};
|
||||
use indoc::{formatdoc, writedoc};
|
||||
use scale_typegen_description::type_description;
|
||||
use scale_value::Value;
|
||||
use std::fmt::Write;
|
||||
use std::write;
|
||||
use subxt::metadata::{
|
||||
Metadata,
|
||||
types::{PalletMetadata, StorageEntryType, StorageMetadata},
|
||||
};
|
||||
use subxt::metadata::{Metadata, PalletMetadata, StorageMetadata};
|
||||
|
||||
use crate::utils::{
|
||||
FileOrUrl, Indent, SyntaxHighlight, create_client, first_paragraph_of_docs,
|
||||
@@ -75,12 +69,7 @@ pub async fn explore_storage(
|
||||
);
|
||||
};
|
||||
|
||||
let (return_ty_id, key_ty_id) = match storage.entry_type() {
|
||||
StorageEntryType::Plain(value) => (*value, None),
|
||||
StorageEntryType::Map {
|
||||
value_ty, key_ty, ..
|
||||
} => (*value_ty, Some(*key_ty)),
|
||||
};
|
||||
let return_ty_id = storage.value_ty();
|
||||
|
||||
let key_value_placeholder = "<KEY_VALUE>".blue();
|
||||
|
||||
@@ -114,15 +103,30 @@ pub async fn explore_storage(
|
||||
"}?;
|
||||
|
||||
// inform user about shape of the key if it can be provided:
|
||||
if let Some(key_ty_id) = key_ty_id {
|
||||
let key_ty_description = type_description(key_ty_id, metadata.types(), true)
|
||||
.expect("No type Description")
|
||||
.indent(4)
|
||||
.highlight();
|
||||
let storage_keys = storage.keys().collect::<Vec<_>>();
|
||||
if !storage_keys.is_empty() {
|
||||
let key_ty_description = format!(
|
||||
"({})",
|
||||
storage_keys
|
||||
.iter()
|
||||
.map(|key| type_description(key.key_id, metadata.types(), true)
|
||||
.expect("No type Description"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
.indent(4)
|
||||
.highlight();
|
||||
|
||||
let key_ty_example = type_example(key_ty_id, metadata.types())
|
||||
.indent(4)
|
||||
.highlight();
|
||||
let key_ty_example = format!(
|
||||
"({})",
|
||||
storage_keys
|
||||
.iter()
|
||||
.map(|key| type_example(key.key_id, metadata.types()).to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
.indent(4)
|
||||
.highlight();
|
||||
|
||||
writedoc! {output, "
|
||||
|
||||
@@ -144,7 +148,8 @@ pub async fn explore_storage(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let storage_entry_keys: Vec<Value> = match (!trailing_args.is_empty(), key_ty_id.is_some()) {
|
||||
let storage_entry_keys: Vec<Value> = match (!trailing_args.is_empty(), !storage_keys.is_empty())
|
||||
{
|
||||
// keys provided, keys not needed.
|
||||
(true, false) => {
|
||||
let trailing_args_str = trailing_args.join(" ");
|
||||
@@ -190,18 +195,17 @@ pub async fn explore_storage(
|
||||
// construct the client:
|
||||
let client = create_client(&file_or_url).await?;
|
||||
|
||||
let storage_query = subxt::dynamic::storage(pallet_name, storage.name(), storage_entry_keys);
|
||||
let decoded_value_thunk_or_none = client
|
||||
// Fetch the value:
|
||||
let storage_value = client
|
||||
.storage()
|
||||
.at_latest()
|
||||
.await?
|
||||
.fetch(&storage_query)
|
||||
.await?;
|
||||
.fetch((pallet_name, storage.name()), storage_entry_keys)
|
||||
.await?
|
||||
.decode()?;
|
||||
|
||||
let decoded_value_thunk =
|
||||
decoded_value_thunk_or_none.ok_or(eyre!("Value not found in storage."))?;
|
||||
let value = storage_value.to_string().highlight();
|
||||
|
||||
let value = decoded_value_thunk.to_value()?.to_string().highlight();
|
||||
writedoc! {output, "
|
||||
|
||||
The value of the storage entry is:
|
||||
|
||||
@@ -101,15 +101,13 @@ pub async fn run<'a>(
|
||||
return format!("The method does not require an {input_value_placeholder}");
|
||||
}
|
||||
|
||||
let fields: Vec<(Option<&str>, u32)> = method
|
||||
.inputs()
|
||||
.map(|f| (Some(f.name.as_str()), f.ty))
|
||||
.collect();
|
||||
let fields: Vec<(Option<&str>, u32)> =
|
||||
method.inputs().map(|f| (Some(&*f.name), f.id)).collect();
|
||||
let fields_description =
|
||||
fields_description(&fields, method.name(), metadata.types()).indent(4);
|
||||
|
||||
let fields_example =
|
||||
fields_composite_example(method.inputs().map(|e| e.ty), metadata.types())
|
||||
fields_composite_example(method.inputs().map(|e| e.id), metadata.types())
|
||||
.indent(4)
|
||||
.highlight();
|
||||
|
||||
@@ -164,13 +162,14 @@ pub async fn run<'a>(
|
||||
{value_str}
|
||||
"}?;
|
||||
// encode, then decode. This ensures that the scale value is of the correct shape for the param:
|
||||
let bytes = value.encode_as_type(ty.ty, metadata.types())?;
|
||||
let value = Value::decode_as_type(&mut &bytes[..], ty.ty, metadata.types())?;
|
||||
let bytes = value.encode_as_type(ty.id, metadata.types())?;
|
||||
let value = Value::decode_as_type(&mut &bytes[..], ty.id, metadata.types())?;
|
||||
Ok(value)
|
||||
})
|
||||
.collect::<color_eyre::Result<Vec<Value>>>()?;
|
||||
|
||||
let method_call = subxt::dynamic::runtime_api_call(api_name, method.name(), args_data);
|
||||
let method_call =
|
||||
subxt::dynamic::runtime_api_call::<_, Value>(api_name, method.name(), args_data);
|
||||
let client = create_client(&file_or_url).await?;
|
||||
let output_value = client
|
||||
.runtime_api()
|
||||
@@ -179,7 +178,7 @@ pub async fn run<'a>(
|
||||
.call(method_call)
|
||||
.await?;
|
||||
|
||||
let output_value = output_value.to_value()?.to_string().highlight();
|
||||
let output_value = output_value.to_string().highlight();
|
||||
writedoc! {output, "
|
||||
|
||||
Returned value:
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
# result_large_err lint complains if error variant is 128 bytes or more by default.
|
||||
# Our error is. Let's up this limit a bit for now to avoid lots of warnings.
|
||||
large-error-threshold = 256
|
||||
large-error-threshold = 512
|
||||
@@ -57,16 +57,17 @@ fn generate_custom_value_fn(
|
||||
.types()
|
||||
.resolve(custom_value.type_id())
|
||||
.is_some();
|
||||
|
||||
let (return_ty, decodable) = if type_is_valid {
|
||||
let return_ty = type_gen
|
||||
.resolve_type_path(custom_value.type_id())
|
||||
.expect("type is in metadata; qed")
|
||||
.to_token_stream(type_gen.settings());
|
||||
let decodable = quote!(#crate_path::utils::Yes);
|
||||
let decodable = quote!(#crate_path::utils::Maybe);
|
||||
(return_ty, decodable)
|
||||
} else {
|
||||
// if type registry does not contain the type, we can just return the Encoded scale bytes.
|
||||
(quote!(()), quote!(()))
|
||||
(quote!(()), quote!(#crate_path::utils::No))
|
||||
};
|
||||
|
||||
Some(quote!(
|
||||
|
||||
@@ -12,7 +12,45 @@ use scale_typegen::typegen::ir::ToTokensWithSettings;
|
||||
use std::collections::HashSet;
|
||||
use subxt_metadata::{PalletMetadata, ViewFunctionMetadata};
|
||||
|
||||
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(pallet.name(), vf, type_gen, crate_path))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
let view_functions_types = view_functions.iter().map(|(apis, _)| apis);
|
||||
let view_functions_methods = 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_methods )*
|
||||
}
|
||||
|
||||
#( #view_functions_types )*
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_pallet_view_function(
|
||||
pallet_name: &str,
|
||||
view_function: ViewFunctionMetadata<'_>,
|
||||
type_gen: &TypeGenerator,
|
||||
crate_path: &syn::Path,
|
||||
@@ -20,9 +58,7 @@ fn generate_pallet_view_function(
|
||||
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 view_function_name_ident = format_ident!("{view_function_name_str}");
|
||||
let validation_hash = view_function.hash();
|
||||
|
||||
let docs = view_function.docs();
|
||||
@@ -68,7 +104,7 @@ fn generate_pallet_view_function(
|
||||
|
||||
// Path to the actual type we'll have generated for this input.
|
||||
let type_path = type_gen
|
||||
.resolve_type_path(input.ty)
|
||||
.resolve_type_path(input.id)
|
||||
.expect("view function input type is in metadata; qed")
|
||||
.to_token_stream(type_gen.settings());
|
||||
|
||||
@@ -81,12 +117,11 @@ fn generate_pallet_view_function(
|
||||
.collect()
|
||||
};
|
||||
|
||||
let input_struct_params = view_function_inputs
|
||||
let input_tuple_types = view_function_inputs
|
||||
.iter()
|
||||
.map(|i| {
|
||||
let arg = &i.name;
|
||||
let ty = &i.type_alias;
|
||||
quote!(pub #arg: #ty)
|
||||
quote!(#view_function_name_ident::#ty)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -111,19 +146,12 @@ fn generate_pallet_view_function(
|
||||
.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!(
|
||||
let view_function_types = 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 {
|
||||
@@ -134,61 +162,23 @@ fn generate_pallet_view_function(
|
||||
);
|
||||
|
||||
// Define the getter method that will live on the `ViewFunctionApi` type.
|
||||
let view_function_getter = quote!(
|
||||
let view_function_method = quote!(
|
||||
#docs
|
||||
pub fn #view_function_name_ident(
|
||||
&self,
|
||||
#(#input_args),*
|
||||
) -> #crate_path::view_functions::payload::StaticPayload<
|
||||
#view_function_name_ident::Input,
|
||||
(#(#input_tuple_types,)*),
|
||||
#view_function_name_ident::output::Output
|
||||
> {
|
||||
#crate_path::view_functions::payload::StaticPayload::new_static(
|
||||
[#(#query_id,)*],
|
||||
#view_function_name_ident::Input {
|
||||
#(#input_param_names,)*
|
||||
},
|
||||
#pallet_name,
|
||||
#view_function_name_str,
|
||||
(#(#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 )*
|
||||
}
|
||||
})
|
||||
Ok((view_function_types, view_function_method))
|
||||
}
|
||||
|
||||
+190
-193
@@ -16,170 +16,6 @@ use quote::{format_ident, quote};
|
||||
|
||||
use crate::CodegenError;
|
||||
|
||||
/// Generates runtime functions for the given API metadata.
|
||||
fn generate_runtime_api(
|
||||
api: RuntimeApiMetadata,
|
||||
type_gen: &TypeGenerator,
|
||||
crate_path: &syn::Path,
|
||||
) -> Result<(TokenStream2, TokenStream2), CodegenError> {
|
||||
// Trait name must remain as is (upper case) to identify the runtime call.
|
||||
let trait_name_str = api.name();
|
||||
// The snake case for the trait name.
|
||||
let trait_name_snake = format_ident!("{}", api.name().to_snake_case());
|
||||
let docs = api.docs();
|
||||
let docs: TokenStream2 = type_gen
|
||||
.settings()
|
||||
.should_gen_docs
|
||||
.then_some(quote! { #( #[doc = #docs ] )* })
|
||||
.unwrap_or_default();
|
||||
|
||||
let structs_and_methods = api
|
||||
.methods()
|
||||
.map(|method| {
|
||||
let method_name = format_ident!("{}", method.name());
|
||||
let method_name_str = method.name();
|
||||
|
||||
let docs = method.docs();
|
||||
let docs: TokenStream2 = type_gen
|
||||
.settings()
|
||||
.should_gen_docs
|
||||
.then_some(quote! { #( #[doc = #docs ] )* })
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut unique_names = HashSet::new();
|
||||
let mut unique_aliases = HashSet::new();
|
||||
|
||||
let inputs: Vec<_> = method
|
||||
.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 is already used, append the index until it is unique.
|
||||
name = format!("{name}_param{idx}");
|
||||
}
|
||||
|
||||
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!("{alias}Param{idx}");
|
||||
}
|
||||
|
||||
let (alias_name, name) = (format_ident!("{alias}"), format_ident!("{name}"));
|
||||
|
||||
// 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());
|
||||
let aliased_param = quote!( pub type #alias_name = #ty; );
|
||||
|
||||
// Structures are placed on the same level as the alias module.
|
||||
let struct_ty_path = quote!( #method_name::#alias_name );
|
||||
let struct_param = quote!(#name: #struct_ty_path);
|
||||
|
||||
// Function parameters must be indented by `types`.
|
||||
let fn_param = quote!(#name: types::#struct_ty_path);
|
||||
(fn_param, struct_param, name, aliased_param)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let fn_params = inputs.iter().map(|(fn_param, _, _, _)| fn_param);
|
||||
let struct_params = inputs.iter().map(|(_, struct_param, _, _)| struct_param);
|
||||
let param_names = inputs.iter().map(|(_, _, name, _)| name);
|
||||
let type_aliases = inputs.iter().map(|(_, _, _, aliased_param)| aliased_param);
|
||||
let types_mod_ident = type_gen.types_mod_ident();
|
||||
|
||||
let output = type_gen.resolve_type_path(method.output_ty())?.to_token_stream(type_gen.settings());
|
||||
let aliased_module = quote!(
|
||||
pub mod #method_name {
|
||||
use super::#types_mod_ident;
|
||||
|
||||
#( #type_aliases )*
|
||||
|
||||
// Guard the `Output` name against collisions by placing it in a dedicated module.
|
||||
pub mod output {
|
||||
use super::#types_mod_ident;
|
||||
pub type Output = #output;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// From the method metadata generate a structure that holds
|
||||
// all parameter types. This structure is used with metadata
|
||||
// to encode parameters to the call via `encode_as_fields_to`.
|
||||
let derives = type_gen.settings().derives.default_derives();
|
||||
let struct_name = format_ident!("{}", method.name().to_upper_camel_case());
|
||||
let struct_input = quote!(
|
||||
#aliased_module
|
||||
|
||||
#derives
|
||||
pub struct #struct_name {
|
||||
#( pub #struct_params, )*
|
||||
}
|
||||
);
|
||||
|
||||
let call_hash = method.hash();
|
||||
let method = quote!(
|
||||
#docs
|
||||
pub fn #method_name(&self, #( #fn_params, )* ) -> #crate_path::runtime_api::payload::StaticPayload<types::#struct_name, types::#method_name::output::Output> {
|
||||
#crate_path::runtime_api::payload::StaticPayload::new_static(
|
||||
#trait_name_str,
|
||||
#method_name_str,
|
||||
types::#struct_name { #( #param_names, )* },
|
||||
[#(#call_hash,)*],
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
Ok((struct_input, method))
|
||||
})
|
||||
.collect::<Result<Vec<_>, CodegenError>>()?;
|
||||
|
||||
let trait_name = format_ident!("{}", trait_name_str);
|
||||
|
||||
let structs = structs_and_methods.iter().map(|(struct_, _)| struct_);
|
||||
let methods = structs_and_methods.iter().map(|(_, method)| method);
|
||||
let types_mod_ident = type_gen.types_mod_ident();
|
||||
|
||||
let runtime_api = quote!(
|
||||
pub mod #trait_name_snake {
|
||||
use super::root_mod;
|
||||
use super::#types_mod_ident;
|
||||
|
||||
#docs
|
||||
pub struct #trait_name;
|
||||
|
||||
impl #trait_name {
|
||||
#( #methods )*
|
||||
}
|
||||
|
||||
pub mod types {
|
||||
use super::#types_mod_ident;
|
||||
|
||||
#( #structs )*
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// A getter for the `RuntimeApi` to get the trait structure.
|
||||
let trait_getter = quote!(
|
||||
pub fn #trait_name_snake(&self) -> #trait_name_snake::#trait_name {
|
||||
#trait_name_snake::#trait_name
|
||||
}
|
||||
);
|
||||
|
||||
Ok((runtime_api, trait_getter))
|
||||
}
|
||||
|
||||
/// Generate the runtime APIs.
|
||||
pub fn generate_runtime_apis(
|
||||
metadata: &Metadata,
|
||||
@@ -192,8 +28,8 @@ pub fn generate_runtime_apis(
|
||||
.map(|api| generate_runtime_api(api, type_gen, crate_path))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
let runtime_apis_def = runtime_fns.iter().map(|(apis, _)| apis);
|
||||
let runtime_apis_getters = runtime_fns.iter().map(|(_, getters)| getters);
|
||||
let trait_defs = runtime_fns.iter().map(|(apis, _)| apis);
|
||||
let trait_getters = runtime_fns.iter().map(|(_, getters)| getters);
|
||||
|
||||
Ok(quote! {
|
||||
pub mod runtime_apis {
|
||||
@@ -205,14 +41,196 @@ pub fn generate_runtime_apis(
|
||||
pub struct RuntimeApi;
|
||||
|
||||
impl RuntimeApi {
|
||||
#( #runtime_apis_getters )*
|
||||
#( #trait_getters )*
|
||||
}
|
||||
|
||||
#( #runtime_apis_def )*
|
||||
#( #trait_defs )*
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Generates runtime functions for the given API metadata.
|
||||
fn generate_runtime_api(
|
||||
api: RuntimeApiMetadata,
|
||||
type_gen: &TypeGenerator,
|
||||
crate_path: &syn::Path,
|
||||
) -> Result<(TokenStream2, TokenStream2), CodegenError> {
|
||||
let types_mod_ident = type_gen.types_mod_ident();
|
||||
// Trait name must remain as is (upper case) to identify the runtime call.
|
||||
let trait_name_str = api.name();
|
||||
// The snake case for the trait name.
|
||||
let trait_name_snake = format_ident!("{}", api.name().to_snake_case());
|
||||
|
||||
let docs = api.docs();
|
||||
let docs: TokenStream2 = type_gen
|
||||
.settings()
|
||||
.should_gen_docs
|
||||
.then_some(quote! { #( #[doc = #docs ] )* })
|
||||
.unwrap_or_default();
|
||||
|
||||
let types_and_methods = api
|
||||
.methods()
|
||||
.map(|method| {
|
||||
let method_name = format_ident!("{}", method.name());
|
||||
let method_name_str = method.name();
|
||||
let validation_hash = method.hash();
|
||||
|
||||
let docs = method.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 runtime_api_inputs: Vec<Input> = {
|
||||
let mut unique_names = HashSet::new();
|
||||
let mut unique_aliases = HashSet::new();
|
||||
|
||||
method
|
||||
.inputs()
|
||||
.enumerate()
|
||||
.map(|(idx, input)| {
|
||||
// The method argument name is either the input name or the
|
||||
// index (eg _1, _2 etc) if one isn't provided.
|
||||
// if we get unlucky we'll end up with param_param1 etc.
|
||||
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!("{name}_param{idx}");
|
||||
}
|
||||
|
||||
// The alias is either InputName if provided, or Param1, Param2 etc if not.
|
||||
// If we get unlucky we may even end up with ParamParam1 etc.
|
||||
let mut alias = name.trim_start_matches('_').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!("{alias}Param{idx}");
|
||||
}
|
||||
|
||||
// Generate alias for runtime type.
|
||||
let type_path = type_gen
|
||||
.resolve_type_path(input.id)
|
||||
.expect("runtime api 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_tuple_types = runtime_api_inputs
|
||||
.iter()
|
||||
.map(|i| {
|
||||
let ty = &i.type_alias;
|
||||
quote!(#method_name::#ty)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let input_args = runtime_api_inputs
|
||||
.iter()
|
||||
.map(|i| {
|
||||
let arg = &i.name;
|
||||
let ty = &i.type_alias;
|
||||
quote!(#arg: #method_name::#ty)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let input_param_names = runtime_api_inputs.iter().map(|i| &i.name);
|
||||
|
||||
let input_type_aliases = runtime_api_inputs.iter().map(|i| {
|
||||
let ty = &i.type_alias;
|
||||
let path = &i.type_path;
|
||||
quote!(pub type #ty = #path;)
|
||||
});
|
||||
|
||||
let output_type_path = type_gen
|
||||
.resolve_type_path(method.output_ty())?
|
||||
.to_token_stream(type_gen.settings());
|
||||
|
||||
// Define the input and output type bits for the method.
|
||||
let runtime_api_types = quote! {
|
||||
pub mod #method_name {
|
||||
use super::root_mod;
|
||||
use super::#types_mod_ident;
|
||||
|
||||
#(#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 runtime_api_method = quote!(
|
||||
#docs
|
||||
pub fn #method_name(
|
||||
&self,
|
||||
#(#input_args),*
|
||||
) -> #crate_path::runtime_api::payload::StaticPayload<
|
||||
(#(#input_tuple_types,)*),
|
||||
#method_name::output::Output
|
||||
> {
|
||||
#crate_path::runtime_api::payload::StaticPayload::new_static(
|
||||
#trait_name_str,
|
||||
#method_name_str,
|
||||
(#(#input_param_names,)*),
|
||||
[#(#validation_hash,)*],
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
Ok((runtime_api_types, runtime_api_method))
|
||||
})
|
||||
.collect::<Result<Vec<_>, CodegenError>>()?;
|
||||
|
||||
let trait_name = format_ident!("{}", trait_name_str);
|
||||
let types = types_and_methods.iter().map(|(types, _)| types);
|
||||
let methods = types_and_methods.iter().map(|(_, methods)| methods);
|
||||
|
||||
// The runtime API definition and types.
|
||||
let trait_defs = quote!(
|
||||
pub mod #trait_name_snake {
|
||||
use super::root_mod;
|
||||
use super::#types_mod_ident;
|
||||
|
||||
#docs
|
||||
pub struct #trait_name;
|
||||
|
||||
impl #trait_name {
|
||||
#( #methods )*
|
||||
}
|
||||
|
||||
#( #types )*
|
||||
}
|
||||
);
|
||||
|
||||
// A getter for the `RuntimeApi` to get the trait structure.
|
||||
let trait_getter = quote!(
|
||||
pub fn #trait_name_snake(&self) -> #trait_name_snake::#trait_name {
|
||||
#trait_name_snake::#trait_name
|
||||
}
|
||||
);
|
||||
|
||||
Ok((trait_defs, trait_getter))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::RuntimeGenerator;
|
||||
@@ -295,14 +313,9 @@ mod tests {
|
||||
|
||||
let code = generate_code(runtime_apis);
|
||||
|
||||
let structure = quote! {
|
||||
pub struct Test {
|
||||
pub foo: test::Foo,
|
||||
pub bar: test::Bar,
|
||||
}
|
||||
};
|
||||
let expected_alias = quote!(
|
||||
pub mod test {
|
||||
use super::root_mod;
|
||||
use super::runtime_types;
|
||||
pub type Foo = ::core::primitive::bool;
|
||||
pub type Bar = ::core::primitive::bool;
|
||||
@@ -312,7 +325,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
);
|
||||
assert!(code.contains(&structure.to_string()));
|
||||
|
||||
assert!(code.contains(&expected_alias.to_string()));
|
||||
}
|
||||
|
||||
@@ -345,15 +358,9 @@ mod tests {
|
||||
|
||||
let code = generate_code(runtime_apis);
|
||||
|
||||
let structure = quote! {
|
||||
pub struct Test {
|
||||
pub a: test::A,
|
||||
pub a_param1: test::AParam1,
|
||||
pub a_param2: test::AParam2,
|
||||
}
|
||||
};
|
||||
let expected_alias = quote!(
|
||||
pub mod test {
|
||||
use super::root_mod;
|
||||
use super::runtime_types;
|
||||
pub type A = ::core::primitive::bool;
|
||||
pub type AParam1 = ::core::primitive::bool;
|
||||
@@ -365,7 +372,6 @@ mod tests {
|
||||
}
|
||||
);
|
||||
|
||||
assert!(code.contains(&structure.to_string()));
|
||||
assert!(code.contains(&expected_alias.to_string()));
|
||||
}
|
||||
|
||||
@@ -406,17 +412,9 @@ mod tests {
|
||||
|
||||
let code = generate_code(runtime_apis);
|
||||
|
||||
let structure = quote! {
|
||||
pub struct Test {
|
||||
pub _0: test::Param0,
|
||||
pub a: test::A,
|
||||
pub param_0: test::Param0Param2,
|
||||
pub _3: test::Param3,
|
||||
pub param_0_param_2: test::Param0Param2Param4,
|
||||
}
|
||||
};
|
||||
let expected_alias = quote!(
|
||||
pub mod test {
|
||||
use super::root_mod;
|
||||
use super::runtime_types;
|
||||
pub type Param0 = ::core::primitive::bool;
|
||||
pub type A = ::core::primitive::bool;
|
||||
@@ -430,7 +428,6 @@ mod tests {
|
||||
}
|
||||
);
|
||||
|
||||
assert!(code.contains(&structure.to_string()));
|
||||
assert!(code.contains(&expected_alias.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
+97
-230
@@ -2,14 +2,11 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use heck::{ToSnakeCase as _, ToUpperCamelCase};
|
||||
use proc_macro2::{Ident, TokenStream as TokenStream2, TokenStream};
|
||||
use heck::ToSnakeCase as _;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{format_ident, quote};
|
||||
use scale_info::TypeDef;
|
||||
use scale_typegen::TypeGenerator;
|
||||
use subxt_metadata::{
|
||||
PalletMetadata, StorageEntryMetadata, StorageEntryModifier, StorageEntryType, StorageHasher,
|
||||
};
|
||||
use subxt_metadata::{PalletMetadata, StorageEntryMetadata};
|
||||
|
||||
use super::CodegenError;
|
||||
|
||||
@@ -29,33 +26,34 @@ pub fn generate_storage(
|
||||
crate_path: &syn::Path,
|
||||
) -> Result<TokenStream2, CodegenError> {
|
||||
let Some(storage) = pallet.storage() else {
|
||||
// If there are no storage entries in this pallet, we
|
||||
// don't generate anything.
|
||||
return Ok(quote!());
|
||||
};
|
||||
|
||||
let (storage_fns, alias_modules): (Vec<TokenStream2>, Vec<TokenStream2>) = storage
|
||||
let storage_entries = storage
|
||||
.entries()
|
||||
.iter()
|
||||
.map(|entry| generate_storage_entry_fns(type_gen, pallet, entry, crate_path))
|
||||
.collect::<Result<Vec<_>, CodegenError>>()?
|
||||
.into_iter()
|
||||
.unzip();
|
||||
.collect::<Result<Vec<_>, CodegenError>>()?;
|
||||
|
||||
let storage_entry_types = storage_entries.iter().map(|(types, _)| types);
|
||||
let storage_entry_methods = storage_entries.iter().map(|(_, method)| method);
|
||||
|
||||
let types_mod_ident = type_gen.types_mod_ident();
|
||||
|
||||
Ok(quote! {
|
||||
pub mod storage {
|
||||
use super::root_mod;
|
||||
use super::#types_mod_ident;
|
||||
|
||||
pub mod types {
|
||||
use super::#types_mod_ident;
|
||||
|
||||
#( #alias_modules )*
|
||||
}
|
||||
|
||||
pub struct StorageApi;
|
||||
|
||||
impl StorageApi {
|
||||
#( #storage_fns )*
|
||||
#( #storage_entry_methods )*
|
||||
}
|
||||
|
||||
#( #storage_entry_types )*
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -67,239 +65,108 @@ fn generate_storage_entry_fns(
|
||||
storage_entry: &StorageEntryMetadata,
|
||||
crate_path: &syn::Path,
|
||||
) -> Result<(TokenStream2, TokenStream2), CodegenError> {
|
||||
let snake_case_name = storage_entry.name().to_snake_case();
|
||||
let storage_entry_ty = storage_entry.entry_type().value_ty();
|
||||
let storage_entry_value_ty = type_gen
|
||||
.resolve_type_path(storage_entry_ty)
|
||||
.expect("storage type is in metadata; qed")
|
||||
.to_token_stream(type_gen.settings());
|
||||
|
||||
let alias_name = format_ident!("{}", storage_entry.name().to_upper_camel_case());
|
||||
let alias_module_name = format_ident!("{snake_case_name}");
|
||||
let alias_storage_path = quote!( types::#alias_module_name::#alias_name );
|
||||
|
||||
struct MapEntryKey {
|
||||
arg_name: Ident,
|
||||
alias_type_def: TokenStream,
|
||||
alias_type_path: TokenStream,
|
||||
hasher: StorageHasher,
|
||||
}
|
||||
|
||||
let map_entry_key = |idx, id, hasher| -> MapEntryKey {
|
||||
let arg_name: Ident = format_ident!("_{}", idx);
|
||||
let ty_path = type_gen
|
||||
.resolve_type_path(id)
|
||||
.expect("type is in metadata; qed");
|
||||
|
||||
let alias_name = format_ident!("Param{}", idx);
|
||||
let alias_type = ty_path.to_token_stream(type_gen.settings());
|
||||
|
||||
let alias_type_def = quote!( pub type #alias_name = #alias_type; );
|
||||
let alias_type_path = quote!( types::#alias_module_name::#alias_name );
|
||||
|
||||
MapEntryKey {
|
||||
arg_name,
|
||||
alias_type_def,
|
||||
alias_type_path,
|
||||
hasher,
|
||||
}
|
||||
};
|
||||
|
||||
let keys: Vec<MapEntryKey> = match storage_entry.entry_type() {
|
||||
StorageEntryType::Plain(_) => vec![],
|
||||
StorageEntryType::Map {
|
||||
key_ty, hashers, ..
|
||||
} => {
|
||||
if hashers.len() == 1 {
|
||||
// If there's exactly 1 hasher, then we have a plain StorageMap. We can't
|
||||
// break the key down (even if it's a tuple) because the hasher applies to
|
||||
// the whole key.
|
||||
vec![map_entry_key(0, *key_ty, hashers[0])]
|
||||
} else {
|
||||
// If there are multiple hashers, then we have a StorageDoubleMap or StorageNMap.
|
||||
// We expect the key type to be tuple, and we will return a MapEntryKey for each
|
||||
// key in the tuple.
|
||||
let hasher_count = hashers.len();
|
||||
let tuple = match &type_gen
|
||||
.resolve_type(*key_ty)
|
||||
.expect("key type should be present")
|
||||
.type_def
|
||||
{
|
||||
TypeDef::Tuple(tuple) => tuple,
|
||||
_ => {
|
||||
return Err(CodegenError::InvalidStorageHasherCount {
|
||||
storage_entry_name: storage_entry.name().to_owned(),
|
||||
key_count: 1,
|
||||
hasher_count,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// We should have the same number of hashers and keys.
|
||||
let key_count = tuple.fields.len();
|
||||
if hasher_count != key_count {
|
||||
return Err(CodegenError::InvalidStorageHasherCount {
|
||||
storage_entry_name: storage_entry.name().to_owned(),
|
||||
key_count,
|
||||
hasher_count,
|
||||
});
|
||||
}
|
||||
|
||||
// Collect them together.
|
||||
tuple
|
||||
.fields
|
||||
.iter()
|
||||
.zip(hashers)
|
||||
.enumerate()
|
||||
.map(|(idx, (field, hasher))| map_entry_key(idx, field.id, *hasher))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
};
|
||||
let types_mod_ident = type_gen.types_mod_ident();
|
||||
|
||||
let pallet_name = pallet.name();
|
||||
let storage_name = storage_entry.name();
|
||||
let Some(storage_hash) = pallet.storage_hash(storage_name) else {
|
||||
let storage_entry_name_str = storage_entry.name();
|
||||
let storage_entry_snake_case_name = storage_entry_name_str.to_snake_case();
|
||||
let storage_entry_snake_case_ident = format_ident!("{storage_entry_snake_case_name}");
|
||||
let Some(validation_hash) = pallet.storage_hash(storage_entry_name_str) else {
|
||||
return Err(CodegenError::MissingStorageMetadata(
|
||||
pallet_name.into(),
|
||||
storage_name.into(),
|
||||
storage_entry_name_str.into(),
|
||||
));
|
||||
};
|
||||
|
||||
let docs = storage_entry.docs();
|
||||
let docs = type_gen
|
||||
let docs: TokenStream2 = type_gen
|
||||
.settings()
|
||||
.should_gen_docs
|
||||
.then_some(quote! { #( #[doc = #docs ] )* })
|
||||
.unwrap_or_default();
|
||||
|
||||
let is_defaultable_type = match storage_entry.modifier() {
|
||||
StorageEntryModifier::Default => quote!(#crate_path::utils::Yes),
|
||||
StorageEntryModifier::Optional => quote!(()),
|
||||
struct Input {
|
||||
type_alias: syn::Ident,
|
||||
type_path: TokenStream2,
|
||||
}
|
||||
|
||||
let storage_key_types: Vec<Input> = storage_entry
|
||||
.keys()
|
||||
.enumerate()
|
||||
.map(|(idx, key)| {
|
||||
// Storage key aliases are just indexes; no names to use.
|
||||
let type_alias = format_ident!("Param{}", idx);
|
||||
|
||||
// Path to the actual type we'll have generated for this input.
|
||||
let type_path = type_gen
|
||||
.resolve_type_path(key.key_id)
|
||||
.expect("view function input type is in metadata; qed")
|
||||
.to_token_stream(type_gen.settings());
|
||||
|
||||
Input {
|
||||
type_alias,
|
||||
type_path,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let storage_key_tuple_types = storage_key_types
|
||||
.iter()
|
||||
.map(|i| {
|
||||
let ty = &i.type_alias;
|
||||
quote!(#storage_entry_snake_case_ident::#ty)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let storage_key_type_aliases = storage_key_types
|
||||
.iter()
|
||||
.map(|i| {
|
||||
let ty = &i.type_alias;
|
||||
let path = &i.type_path;
|
||||
quote!(pub type #ty = #path;)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let storage_value_type_path = type_gen
|
||||
.resolve_type_path(storage_entry.value_ty())?
|
||||
.to_token_stream(type_gen.settings());
|
||||
|
||||
let is_plain = if storage_entry.keys().len() == 0 {
|
||||
quote!(#crate_path::utils::Yes)
|
||||
} else {
|
||||
quote!(#crate_path::utils::Maybe)
|
||||
};
|
||||
|
||||
// Note: putting `#crate_path::storage::address::StaticStorageKey` into this variable is necessary
|
||||
// to get the line width below a certain limit. If not done, rustfmt will refuse to format the following big expression.
|
||||
// for more information see [this post](https://users.rust-lang.org/t/rustfmt-silently-fails-to-work/75485/4).
|
||||
let static_storage_key: TokenStream = quote!(#crate_path::storage::address::StaticStorageKey);
|
||||
let all_fns = (0..=keys.len()).map(|n_keys| {
|
||||
let keys_slice = &keys[..n_keys];
|
||||
let (fn_name, is_fetchable, is_iterable) = if n_keys == keys.len() {
|
||||
let fn_name = format_ident!("{snake_case_name}");
|
||||
(fn_name, true, false)
|
||||
} else {
|
||||
let fn_name = if n_keys == 0 {
|
||||
format_ident!("{snake_case_name}_iter")
|
||||
} else {
|
||||
format_ident!("{snake_case_name}_iter{}", n_keys)
|
||||
};
|
||||
(fn_name, false, true)
|
||||
};
|
||||
let is_fetchable_type = is_fetchable
|
||||
.then_some(quote!(#crate_path::utils::Yes))
|
||||
.unwrap_or(quote!(()));
|
||||
let is_iterable_type = is_iterable
|
||||
.then_some(quote!(#crate_path::utils::Yes))
|
||||
.unwrap_or(quote!(()));
|
||||
|
||||
let (keys, keys_type) = match keys_slice.len() {
|
||||
0 => (quote!(()), quote!(())),
|
||||
1 => {
|
||||
let key = &keys_slice[0];
|
||||
if key.hasher.ends_with_key() {
|
||||
let arg = &key.arg_name;
|
||||
let keys = quote!(#static_storage_key::new(#arg));
|
||||
let path = &key.alias_type_path;
|
||||
let path = quote!(#static_storage_key<#path>);
|
||||
(keys, path)
|
||||
} else {
|
||||
(quote!(()), quote!(()))
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let keys_iter = keys_slice.iter().map(
|
||||
|MapEntryKey {
|
||||
arg_name, hasher, ..
|
||||
}| {
|
||||
if hasher.ends_with_key() {
|
||||
quote!( #static_storage_key::new(#arg_name) )
|
||||
} else {
|
||||
quote!(())
|
||||
}
|
||||
},
|
||||
);
|
||||
let keys = quote!( (#(#keys_iter,)*) );
|
||||
let paths_iter = keys_slice.iter().map(
|
||||
|MapEntryKey {
|
||||
alias_type_path,
|
||||
hasher,
|
||||
..
|
||||
}| {
|
||||
if hasher.ends_with_key() {
|
||||
quote!( #static_storage_key<#alias_type_path> )
|
||||
} else {
|
||||
quote!(())
|
||||
}
|
||||
},
|
||||
);
|
||||
let paths = quote!( (#(#paths_iter,)*) );
|
||||
(keys, paths)
|
||||
}
|
||||
};
|
||||
|
||||
let key_args = keys_slice.iter().map(
|
||||
|MapEntryKey {
|
||||
arg_name,
|
||||
alias_type_path,
|
||||
..
|
||||
}| quote!( #arg_name: #alias_type_path ),
|
||||
);
|
||||
|
||||
quote!(
|
||||
#docs
|
||||
pub fn #fn_name(
|
||||
&self,
|
||||
#(#key_args,)*
|
||||
) -> #crate_path::storage::address::StaticAddress::<
|
||||
#keys_type,
|
||||
#alias_storage_path,
|
||||
#is_fetchable_type,
|
||||
#is_defaultable_type,
|
||||
#is_iterable_type
|
||||
> {
|
||||
#crate_path::storage::address::StaticAddress::new_static(
|
||||
#pallet_name,
|
||||
#storage_name,
|
||||
#keys,
|
||||
[#(#storage_hash,)*]
|
||||
)
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
let alias_types = keys
|
||||
.iter()
|
||||
.map(|MapEntryKey { alias_type_def, .. }| alias_type_def);
|
||||
|
||||
let types_mod_ident = type_gen.types_mod_ident();
|
||||
// Generate type alias for the return type only, since
|
||||
// the keys of the storage entry are not explicitly named.
|
||||
let alias_module = quote! {
|
||||
pub mod #alias_module_name {
|
||||
let storage_entry_types = quote!(
|
||||
pub mod #storage_entry_snake_case_ident {
|
||||
use super::root_mod;
|
||||
use super::#types_mod_ident;
|
||||
|
||||
pub type #alias_name = #storage_entry_value_ty;
|
||||
#(#storage_key_type_aliases)*
|
||||
|
||||
#( #alias_types )*
|
||||
pub mod output {
|
||||
use super::#types_mod_ident;
|
||||
pub type Output = #storage_value_type_path;
|
||||
}
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
Ok((
|
||||
quote! {
|
||||
#( #all_fns )*
|
||||
},
|
||||
alias_module,
|
||||
))
|
||||
let storage_entry_method = quote!(
|
||||
#docs
|
||||
pub fn #storage_entry_snake_case_ident(&self) -> #crate_path::storage::address::StaticAddress<
|
||||
(#(#storage_key_tuple_types,)*),
|
||||
#storage_entry_snake_case_ident::output::Output,
|
||||
#is_plain
|
||||
> {
|
||||
#crate_path::storage::address::StaticAddress::new_static(
|
||||
#pallet_name,
|
||||
#storage_entry_name_str,
|
||||
[#(#validation_hash,)*],
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
Ok((storage_entry_types, storage_entry_method))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -27,6 +27,8 @@ std = [
|
||||
"tracing/std",
|
||||
"impl-serde/std",
|
||||
"primitive-types/std",
|
||||
"sp-core/std",
|
||||
"sp-keyring/std",
|
||||
"sp-crypto-hashing/std",
|
||||
]
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ use crate::config::transaction_extensions::{
|
||||
ChargeAssetTxPayment, ChargeTransactionPayment, CheckNonce,
|
||||
};
|
||||
use crate::dynamic::Value;
|
||||
use crate::{Metadata, config::Config, error::Error};
|
||||
use crate::error::ExtrinsicError;
|
||||
use crate::{Metadata, config::Config};
|
||||
use alloc::borrow::ToOwned;
|
||||
use frame_decode::extrinsics::ExtrinsicExtensions;
|
||||
use scale_decode::DecodeAsType;
|
||||
|
||||
@@ -50,7 +52,7 @@ impl<'a, T: Config> ExtrinsicTransactionExtensions<'a, T> {
|
||||
/// Searches through all signed extensions to find a specific one.
|
||||
/// If the Signed Extension is not found `Ok(None)` is returned.
|
||||
/// If the Signed Extension is found but decoding failed `Err(_)` is returned.
|
||||
pub fn find<S: TransactionExtension<T>>(&self) -> Result<Option<S::Decoded>, Error> {
|
||||
pub fn find<S: TransactionExtension<T>>(&self) -> Result<Option<S::Decoded>, ExtrinsicError> {
|
||||
for ext in self.iter() {
|
||||
match ext.as_signed_extension::<S>() {
|
||||
// We found a match; return it:
|
||||
@@ -117,12 +119,16 @@ impl<'a, T: Config> ExtrinsicTransactionExtension<'a, T> {
|
||||
}
|
||||
|
||||
/// Signed Extension as a [`scale_value::Value`]
|
||||
pub fn value(&self) -> Result<Value<u32>, Error> {
|
||||
pub fn value(&self) -> Result<Value<u32>, ExtrinsicError> {
|
||||
let value = scale_value::scale::decode_as_type(
|
||||
&mut &self.bytes[..],
|
||||
self.ty_id,
|
||||
self.metadata.types(),
|
||||
)?;
|
||||
)
|
||||
.map_err(|e| ExtrinsicError::CouldNotDecodeTransactionExtension {
|
||||
name: self.identifier.to_owned(),
|
||||
error: e.into(),
|
||||
})?;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
@@ -131,15 +137,19 @@ impl<'a, T: Config> ExtrinsicTransactionExtension<'a, T> {
|
||||
/// decode with.
|
||||
pub fn as_signed_extension<S: TransactionExtension<T>>(
|
||||
&self,
|
||||
) -> Result<Option<S::Decoded>, Error> {
|
||||
) -> Result<Option<S::Decoded>, ExtrinsicError> {
|
||||
if !S::matches(self.identifier, self.ty_id, self.metadata.types()) {
|
||||
return Ok(None);
|
||||
}
|
||||
self.as_type::<S::Decoded>().map(Some)
|
||||
}
|
||||
|
||||
fn as_type<E: DecodeAsType>(&self) -> Result<E, Error> {
|
||||
let value = E::decode_as_type(&mut &self.bytes[..], self.ty_id, self.metadata.types())?;
|
||||
fn as_type<E: DecodeAsType>(&self) -> Result<E, ExtrinsicError> {
|
||||
let value = E::decode_as_type(&mut &self.bytes[..], self.ty_id, self.metadata.types())
|
||||
.map_err(|e| ExtrinsicError::CouldNotDecodeTransactionExtension {
|
||||
name: self.identifier.to_owned(),
|
||||
error: e,
|
||||
})?;
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,16 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use super::BlockError;
|
||||
use crate::blocks::extrinsic_transaction_extensions::ExtrinsicTransactionExtensions;
|
||||
use crate::{
|
||||
Metadata,
|
||||
config::{Config, HashFor, Hasher},
|
||||
error::{Error, MetadataError},
|
||||
error::{ExtrinsicDecodeErrorAt, ExtrinsicDecodeErrorAtReason, ExtrinsicError},
|
||||
};
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec::Vec;
|
||||
use core::ops::Deref;
|
||||
use frame_decode::extrinsics::Extrinsic;
|
||||
use scale_decode::DecodeAsType;
|
||||
use subxt_metadata::PalletMetadata;
|
||||
use scale_decode::{DecodeAsFields, DecodeAsType};
|
||||
|
||||
pub use crate::blocks::StaticExtrinsic;
|
||||
|
||||
@@ -30,7 +27,10 @@ impl<T: Config> Extrinsics<T> {
|
||||
/// Instantiate a new [`Extrinsics`] object, given a vector containing
|
||||
/// each extrinsic hash (in the form of bytes) and some metadata that
|
||||
/// we'll use to decode them.
|
||||
pub fn decode_from(extrinsics: Vec<Vec<u8>>, metadata: Metadata) -> Result<Self, Error> {
|
||||
pub fn decode_from(
|
||||
extrinsics: Vec<Vec<u8>>,
|
||||
metadata: Metadata,
|
||||
) -> Result<Self, ExtrinsicDecodeErrorAt> {
|
||||
let hasher = T::Hasher::new(&metadata);
|
||||
let extrinsics = extrinsics
|
||||
.into_iter()
|
||||
@@ -39,29 +39,25 @@ impl<T: Config> Extrinsics<T> {
|
||||
let cursor = &mut &*bytes;
|
||||
|
||||
// Try to decode the extrinsic.
|
||||
let decoded_info = frame_decode::extrinsics::decode_extrinsic(
|
||||
cursor,
|
||||
metadata.deref(),
|
||||
metadata.types(),
|
||||
)
|
||||
.map_err(|error| BlockError::ExtrinsicDecodeError {
|
||||
extrinsic_index,
|
||||
error,
|
||||
})?
|
||||
.into_owned();
|
||||
let decoded_info =
|
||||
frame_decode::extrinsics::decode_extrinsic(cursor, &metadata, metadata.types())
|
||||
.map_err(|error| ExtrinsicDecodeErrorAt {
|
||||
extrinsic_index,
|
||||
error: ExtrinsicDecodeErrorAtReason::DecodeError(error),
|
||||
})?
|
||||
.into_owned();
|
||||
|
||||
// We didn't consume all bytes, so decoding probably failed.
|
||||
if !cursor.is_empty() {
|
||||
return Err(BlockError::LeftoverBytes {
|
||||
return Err(ExtrinsicDecodeErrorAt {
|
||||
extrinsic_index,
|
||||
num_leftover_bytes: cursor.len(),
|
||||
}
|
||||
.into());
|
||||
error: ExtrinsicDecodeErrorAtReason::LeftoverBytes(cursor.to_vec()),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Arc::new((decoded_info, bytes)))
|
||||
})
|
||||
.collect::<Result<_, Error>>()?;
|
||||
.collect::<Result<_, ExtrinsicDecodeErrorAt>>()?;
|
||||
|
||||
Ok(Self {
|
||||
extrinsics,
|
||||
@@ -106,7 +102,7 @@ impl<T: Config> Extrinsics<T> {
|
||||
/// If an error occurs, all subsequent iterations return `None`.
|
||||
pub fn find<E: StaticExtrinsic>(
|
||||
&self,
|
||||
) -> impl Iterator<Item = Result<FoundExtrinsic<T, E>, Error>> {
|
||||
) -> impl Iterator<Item = Result<FoundExtrinsic<T, E>, ExtrinsicError>> {
|
||||
self.iter().filter_map(|details| {
|
||||
match details.as_extrinsic::<E>() {
|
||||
// Failed to decode extrinsic:
|
||||
@@ -120,18 +116,22 @@ impl<T: Config> Extrinsics<T> {
|
||||
|
||||
/// Iterate through the extrinsics using metadata to dynamically decode and skip
|
||||
/// them, and return the first extrinsic found which decodes to the provided `E` type.
|
||||
pub fn find_first<E: StaticExtrinsic>(&self) -> Result<Option<FoundExtrinsic<T, E>>, Error> {
|
||||
pub fn find_first<E: StaticExtrinsic>(
|
||||
&self,
|
||||
) -> Result<Option<FoundExtrinsic<T, E>>, ExtrinsicError> {
|
||||
self.find::<E>().next().transpose()
|
||||
}
|
||||
|
||||
/// Iterate through the extrinsics using metadata to dynamically decode and skip
|
||||
/// them, and return the last extrinsic found which decodes to the provided `Ev` type.
|
||||
pub fn find_last<E: StaticExtrinsic>(&self) -> Result<Option<FoundExtrinsic<T, E>>, Error> {
|
||||
pub fn find_last<E: StaticExtrinsic>(
|
||||
&self,
|
||||
) -> Result<Option<FoundExtrinsic<T, E>>, ExtrinsicError> {
|
||||
self.find::<E>().last().transpose()
|
||||
}
|
||||
|
||||
/// Find an extrinsics that decodes to the type provided. Returns true if it was found.
|
||||
pub fn has<E: StaticExtrinsic>(&self) -> Result<bool, Error> {
|
||||
pub fn has<E: StaticExtrinsic>(&self) -> Result<bool, ExtrinsicError> {
|
||||
Ok(self.find::<E>().next().transpose()?.is_some())
|
||||
}
|
||||
}
|
||||
@@ -264,61 +264,63 @@ where
|
||||
}
|
||||
|
||||
/// The index of the extrinsic variant that the extrinsic originated from.
|
||||
pub fn variant_index(&self) -> u8 {
|
||||
pub fn call_index(&self) -> u8 {
|
||||
self.decoded_info().call_index()
|
||||
}
|
||||
|
||||
/// The name of the pallet from whence the extrinsic originated.
|
||||
pub fn pallet_name(&self) -> Result<&str, Error> {
|
||||
Ok(self.extrinsic_metadata()?.pallet.name())
|
||||
pub fn pallet_name(&self) -> &str {
|
||||
self.decoded_info().pallet_name()
|
||||
}
|
||||
|
||||
/// The name of the call (ie the name of the variant that it corresponds to).
|
||||
pub fn variant_name(&self) -> Result<&str, Error> {
|
||||
Ok(&self.extrinsic_metadata()?.variant.name)
|
||||
}
|
||||
|
||||
/// Fetch the metadata for this extrinsic.
|
||||
pub fn extrinsic_metadata(&self) -> Result<ExtrinsicMetadataDetails<'_>, Error> {
|
||||
let pallet = self.metadata.pallet_by_index_err(self.pallet_index())?;
|
||||
let variant = pallet
|
||||
.call_variant_by_index(self.variant_index())
|
||||
.ok_or_else(|| MetadataError::VariantIndexNotFound(self.variant_index()))?;
|
||||
|
||||
Ok(ExtrinsicMetadataDetails { pallet, variant })
|
||||
pub fn call_name(&self) -> &str {
|
||||
self.decoded_info().call_name()
|
||||
}
|
||||
|
||||
/// Decode and provide the extrinsic fields back in the form of a [`scale_value::Composite`]
|
||||
/// type which represents the named or unnamed fields that were present in the extrinsic.
|
||||
pub fn field_values(&self) -> Result<scale_value::Composite<u32>, Error> {
|
||||
pub fn decode_as_fields<E: DecodeAsFields>(&self) -> Result<E, ExtrinsicError> {
|
||||
let bytes = &mut self.field_bytes();
|
||||
let extrinsic_metadata = self.extrinsic_metadata()?;
|
||||
|
||||
let mut fields = extrinsic_metadata
|
||||
.variant
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f| scale_decode::Field::new(f.ty.id, f.name.as_deref()));
|
||||
let mut fields = self.decoded_info().call_data().map(|d| {
|
||||
let name = if d.name().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(d.name())
|
||||
};
|
||||
scale_decode::Field::new(*d.ty(), name)
|
||||
});
|
||||
let decoded =
|
||||
scale_value::scale::decode_as_fields(bytes, &mut fields, self.metadata.types())?;
|
||||
E::decode_as_fields(bytes, &mut fields, self.metadata.types()).map_err(|e| {
|
||||
ExtrinsicError::CannotDecodeFields {
|
||||
extrinsic_index: self.index as usize,
|
||||
error: e,
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(decoded)
|
||||
}
|
||||
|
||||
/// Attempt to decode these [`ExtrinsicDetails`] into a type representing the extrinsic fields.
|
||||
/// Such types are exposed in the codegen as `pallet_name::calls::types::CallName` types.
|
||||
pub fn as_extrinsic<E: StaticExtrinsic>(&self) -> Result<Option<E>, Error> {
|
||||
let extrinsic_metadata = self.extrinsic_metadata()?;
|
||||
if extrinsic_metadata.pallet.name() == E::PALLET
|
||||
&& extrinsic_metadata.variant.name == E::CALL
|
||||
pub fn as_extrinsic<E: StaticExtrinsic>(&self) -> Result<Option<E>, ExtrinsicError> {
|
||||
if self.decoded_info().pallet_name() == E::PALLET
|
||||
&& self.decoded_info().call_name() == E::CALL
|
||||
{
|
||||
let mut fields = extrinsic_metadata
|
||||
.variant
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f| scale_decode::Field::new(f.ty.id, f.name.as_deref()));
|
||||
let mut fields = self.decoded_info().call_data().map(|d| {
|
||||
let name = if d.name().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(d.name())
|
||||
};
|
||||
scale_decode::Field::new(*d.ty(), name)
|
||||
});
|
||||
let decoded =
|
||||
E::decode_as_fields(&mut self.field_bytes(), &mut fields, self.metadata.types())?;
|
||||
E::decode_as_fields(&mut self.field_bytes(), &mut fields, self.metadata.types())
|
||||
.map_err(|e| ExtrinsicError::CannotDecodeFields {
|
||||
extrinsic_index: self.index as usize,
|
||||
error: e,
|
||||
})?;
|
||||
Ok(Some(decoded))
|
||||
} else {
|
||||
Ok(None)
|
||||
@@ -328,12 +330,16 @@ where
|
||||
/// Attempt to decode these [`ExtrinsicDetails`] into an outer call enum type (which includes
|
||||
/// the pallet and extrinsic enum variants as well as the extrinsic fields). A compatible
|
||||
/// type for this is exposed via static codegen as a root level `Call` type.
|
||||
pub fn as_root_extrinsic<E: DecodeAsType>(&self) -> Result<E, Error> {
|
||||
pub fn as_root_extrinsic<E: DecodeAsType>(&self) -> Result<E, ExtrinsicError> {
|
||||
let decoded = E::decode_as_type(
|
||||
&mut &self.call_bytes()[..],
|
||||
self.metadata.outer_enums().call_enum_ty(),
|
||||
self.metadata.types(),
|
||||
)?;
|
||||
)
|
||||
.map_err(|e| ExtrinsicError::CannotDecodeIntoRootExtrinsic {
|
||||
extrinsic_index: self.index as usize,
|
||||
error: e,
|
||||
})?;
|
||||
|
||||
Ok(decoded)
|
||||
}
|
||||
@@ -351,14 +357,6 @@ pub struct FoundExtrinsic<T: Config, E> {
|
||||
pub value: E,
|
||||
}
|
||||
|
||||
/// Details for the given extrinsic plucked from the metadata.
|
||||
pub struct ExtrinsicMetadataDetails<'a> {
|
||||
/// Metadata for the pallet that the extrinsic belongs to.
|
||||
pub pallet: PalletMetadata<'a>,
|
||||
/// Metadata for the variant which describes the pallet extrinsics.
|
||||
pub variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -488,7 +486,7 @@ mod tests {
|
||||
let runtime_metadata: RuntimeMetadataPrefixed = meta.into();
|
||||
let metadata: subxt_metadata::Metadata = runtime_metadata.try_into().unwrap();
|
||||
|
||||
Metadata::from(metadata)
|
||||
metadata
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -513,12 +511,10 @@ mod tests {
|
||||
let result = Extrinsics::<SubstrateConfig>::decode_from(vec![vec![]], metadata);
|
||||
assert_matches!(
|
||||
result.err(),
|
||||
Some(crate::Error::Block(
|
||||
crate::error::BlockError::ExtrinsicDecodeError {
|
||||
extrinsic_index: 0,
|
||||
error: _
|
||||
}
|
||||
))
|
||||
Some(crate::error::ExtrinsicDecodeErrorAt {
|
||||
extrinsic_index: 0,
|
||||
error: _
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -533,12 +529,12 @@ mod tests {
|
||||
|
||||
assert_matches!(
|
||||
result.err(),
|
||||
Some(crate::Error::Block(
|
||||
crate::error::BlockError::ExtrinsicDecodeError {
|
||||
extrinsic_index: 0,
|
||||
error: ExtrinsicDecodeError::VersionNotSupported(3),
|
||||
}
|
||||
))
|
||||
Some(crate::error::ExtrinsicDecodeErrorAt {
|
||||
extrinsic_index: 0,
|
||||
error: ExtrinsicDecodeErrorAtReason::DecodeError(
|
||||
ExtrinsicDecodeError::VersionNotSupported(3)
|
||||
),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -611,20 +607,10 @@ mod tests {
|
||||
assert_eq!(extrinsic.index(), 0);
|
||||
|
||||
assert_eq!(extrinsic.pallet_index(), 0);
|
||||
assert_eq!(
|
||||
extrinsic
|
||||
.pallet_name()
|
||||
.expect("Valid metadata contains pallet name"),
|
||||
"Test"
|
||||
);
|
||||
assert_eq!(extrinsic.pallet_name(), "Test");
|
||||
|
||||
assert_eq!(extrinsic.variant_index(), 2);
|
||||
assert_eq!(
|
||||
extrinsic
|
||||
.variant_name()
|
||||
.expect("Valid metadata contains variant name"),
|
||||
"TestCall"
|
||||
);
|
||||
assert_eq!(extrinsic.call_index(), 2);
|
||||
assert_eq!(extrinsic.call_name(), "TestCall");
|
||||
|
||||
// Decode the extrinsic to the root enum.
|
||||
let decoded_extrinsic = extrinsic
|
||||
|
||||
+10
-11
@@ -15,7 +15,7 @@
|
||||
//!
|
||||
//! use subxt_macro::subxt;
|
||||
//! use subxt_core::blocks;
|
||||
//! use subxt_core::metadata;
|
||||
//! use subxt_core::Metadata;
|
||||
//! use subxt_core::config::PolkadotConfig;
|
||||
//! use alloc::vec;
|
||||
//!
|
||||
@@ -28,7 +28,7 @@
|
||||
//!
|
||||
//! // Some metadata we'd like to use to help us decode extrinsics:
|
||||
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
|
||||
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
|
||||
//! let metadata = Metadata::decode_from(&metadata_bytes[..]).unwrap();
|
||||
//!
|
||||
//! // Some extrinsics we'd like to decode:
|
||||
//! let ext_bytes = vec![
|
||||
@@ -45,14 +45,14 @@
|
||||
//!
|
||||
//! // We can iterate over them and decode various details out of them.
|
||||
//! for ext in exts.iter() {
|
||||
//! println!("Pallet: {}", ext.pallet_name().unwrap());
|
||||
//! println!("Call: {}", ext.variant_name().unwrap());
|
||||
//! println!("Pallet: {}", ext.pallet_name());
|
||||
//! println!("Call: {}", ext.call_name());
|
||||
//! }
|
||||
//!
|
||||
//! # let ext_details: Vec<_> = exts.iter()
|
||||
//! # .map(|ext| {
|
||||
//! # let pallet = ext.pallet_name().unwrap().to_string();
|
||||
//! # let call = ext.variant_name().unwrap().to_string();
|
||||
//! # let pallet = ext.pallet_name().to_string();
|
||||
//! # let call = ext.call_name().to_string();
|
||||
//! # (pallet, call)
|
||||
//! # })
|
||||
//! # .collect();
|
||||
@@ -70,14 +70,13 @@ mod static_extrinsic;
|
||||
|
||||
use crate::Metadata;
|
||||
use crate::config::Config;
|
||||
use crate::error::Error;
|
||||
use crate::error::ExtrinsicDecodeErrorAt;
|
||||
pub use crate::error::ExtrinsicError;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
pub use crate::error::BlockError;
|
||||
pub use extrinsic_transaction_extensions::{
|
||||
ExtrinsicTransactionExtension, ExtrinsicTransactionExtensions,
|
||||
};
|
||||
pub use extrinsics::{ExtrinsicDetails, ExtrinsicMetadataDetails, Extrinsics, FoundExtrinsic};
|
||||
pub use extrinsics::{ExtrinsicDetails, Extrinsics, FoundExtrinsic};
|
||||
pub use static_extrinsic::StaticExtrinsic;
|
||||
|
||||
/// Instantiate a new [`Extrinsics`] object, given a vector containing each extrinsic hash (in the
|
||||
@@ -87,6 +86,6 @@ pub use static_extrinsic::StaticExtrinsic;
|
||||
pub fn decode_from<T: Config>(
|
||||
extrinsics: Vec<Vec<u8>>,
|
||||
metadata: Metadata,
|
||||
) -> Result<Extrinsics<T>, Error> {
|
||||
) -> Result<Extrinsics<T>, ExtrinsicDecodeErrorAt> {
|
||||
Extrinsics::decode_from(extrinsics, metadata)
|
||||
}
|
||||
|
||||
+1
-1
@@ -5,8 +5,8 @@
|
||||
//! A couple of client types that we use elsewhere.
|
||||
|
||||
use crate::{
|
||||
Metadata,
|
||||
config::{Config, HashFor},
|
||||
metadata::Metadata,
|
||||
};
|
||||
use derive_where::derive_where;
|
||||
|
||||
|
||||
@@ -4,17 +4,16 @@
|
||||
|
||||
//! Construct addresses to access constants with.
|
||||
|
||||
use crate::dynamic::DecodedValueThunk;
|
||||
use crate::metadata::DecodeWithMetadata;
|
||||
use alloc::borrow::Cow;
|
||||
use alloc::string::String;
|
||||
use derive_where::derive_where;
|
||||
use scale_decode::DecodeAsType;
|
||||
|
||||
/// This represents a constant address. Anything implementing this trait
|
||||
/// can be used to fetch constants.
|
||||
pub trait Address {
|
||||
/// The target type of the value that lives at this address.
|
||||
type Target: DecodeWithMetadata;
|
||||
type Target: DecodeAsType;
|
||||
|
||||
/// The name of the pallet that the constant lives under.
|
||||
fn pallet_name(&self) -> &str;
|
||||
@@ -30,22 +29,54 @@ pub trait Address {
|
||||
}
|
||||
}
|
||||
|
||||
// Any reference to an address is a valid address.
|
||||
impl<A: Address + ?Sized> Address for &'_ A {
|
||||
type Target = A::Target;
|
||||
|
||||
fn pallet_name(&self) -> &str {
|
||||
A::pallet_name(*self)
|
||||
}
|
||||
|
||||
fn constant_name(&self) -> &str {
|
||||
A::constant_name(*self)
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
A::validation_hash(*self)
|
||||
}
|
||||
}
|
||||
|
||||
// (str, str) and similar are valid addresses.
|
||||
impl<A: AsRef<str>, B: AsRef<str>> Address for (A, B) {
|
||||
type Target = scale_value::Value;
|
||||
|
||||
fn pallet_name(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
}
|
||||
|
||||
fn constant_name(&self) -> &str {
|
||||
self.1.as_ref()
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// This represents the address of a constant.
|
||||
#[derive_where(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
|
||||
pub struct DefaultAddress<ReturnTy> {
|
||||
pub struct StaticAddress<ReturnTy> {
|
||||
pallet_name: Cow<'static, str>,
|
||||
constant_name: Cow<'static, str>,
|
||||
constant_hash: Option<[u8; 32]>,
|
||||
_marker: core::marker::PhantomData<ReturnTy>,
|
||||
}
|
||||
|
||||
/// The type of address used by our static codegen.
|
||||
pub type StaticAddress<ReturnTy> = DefaultAddress<ReturnTy>;
|
||||
/// The type of address typically used to return dynamic constant values.
|
||||
pub type DynamicAddress = DefaultAddress<DecodedValueThunk>;
|
||||
/// A dynamic lookup address to access a constant.
|
||||
pub type DynamicAddress<ReturnTy> = StaticAddress<ReturnTy>;
|
||||
|
||||
impl<ReturnTy> DefaultAddress<ReturnTy> {
|
||||
/// Create a new [`DefaultAddress`] to use to look up a constant.
|
||||
impl<ReturnTy> StaticAddress<ReturnTy> {
|
||||
/// Create a new [`StaticAddress`] to use to look up a constant.
|
||||
pub fn new(pallet_name: impl Into<String>, constant_name: impl Into<String>) -> Self {
|
||||
Self {
|
||||
pallet_name: Cow::Owned(pallet_name.into()),
|
||||
@@ -55,7 +86,7 @@ impl<ReturnTy> DefaultAddress<ReturnTy> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`DefaultAddress`] that will be validated
|
||||
/// Create a new [`StaticAddress`] that will be validated
|
||||
/// against node metadata using the hash given.
|
||||
#[doc(hidden)]
|
||||
pub fn new_static(
|
||||
@@ -82,7 +113,7 @@ impl<ReturnTy> DefaultAddress<ReturnTy> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<ReturnTy: DecodeWithMetadata> Address for DefaultAddress<ReturnTy> {
|
||||
impl<ReturnTy: DecodeAsType> Address for StaticAddress<ReturnTy> {
|
||||
type Target = ReturnTy;
|
||||
|
||||
fn pallet_name(&self) -> &str {
|
||||
@@ -99,6 +130,9 @@ impl<ReturnTy: DecodeWithMetadata> Address for DefaultAddress<ReturnTy> {
|
||||
}
|
||||
|
||||
/// Construct a new dynamic constant lookup.
|
||||
pub fn dynamic(pallet_name: impl Into<String>, constant_name: impl Into<String>) -> DynamicAddress {
|
||||
pub fn dynamic<ReturnTy: DecodeAsType>(
|
||||
pallet_name: impl Into<String>,
|
||||
constant_name: impl Into<String>,
|
||||
) -> DynamicAddress<ReturnTy> {
|
||||
DynamicAddress::new(pallet_name, constant_name)
|
||||
}
|
||||
|
||||
+44
-20
@@ -12,7 +12,7 @@
|
||||
//! ```rust
|
||||
//! use subxt_macro::subxt;
|
||||
//! use subxt_core::constants;
|
||||
//! use subxt_core::metadata;
|
||||
//! use subxt_core::Metadata;
|
||||
//!
|
||||
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
|
||||
//! #[subxt(
|
||||
@@ -23,7 +23,7 @@
|
||||
//!
|
||||
//! // Some metadata we'd like to access constants in:
|
||||
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
|
||||
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
|
||||
//! let metadata = Metadata::decode_from(&metadata_bytes[..]).unwrap();
|
||||
//!
|
||||
//! // We can use a static address to obtain some constant:
|
||||
//! let address = polkadot::constants().balances().existential_deposit();
|
||||
@@ -40,26 +40,32 @@
|
||||
|
||||
pub mod address;
|
||||
|
||||
use crate::Metadata;
|
||||
use crate::error::ConstantError;
|
||||
use address::Address;
|
||||
use alloc::borrow::ToOwned;
|
||||
|
||||
use crate::{Error, Metadata, error::MetadataError, metadata::DecodeWithMetadata};
|
||||
use alloc::string::ToString;
|
||||
use alloc::vec::Vec;
|
||||
use frame_decode::constants::ConstantTypeInfo;
|
||||
use scale_decode::IntoVisitor;
|
||||
|
||||
/// When the provided `address` is statically generated via the `#[subxt]` macro, this validates
|
||||
/// that the shape of the constant value is the same as the shape expected by the static address.
|
||||
///
|
||||
/// When the provided `address` is dynamic (and thus does not come with any expectation of the
|
||||
/// shape of the constant value), this just returns `Ok(())`
|
||||
pub fn validate<Addr: Address>(address: &Addr, metadata: &Metadata) -> Result<(), Error> {
|
||||
pub fn validate<Addr: Address>(address: Addr, metadata: &Metadata) -> Result<(), ConstantError> {
|
||||
if let Some(actual_hash) = address.validation_hash() {
|
||||
let expected_hash = metadata
|
||||
.pallet_by_name_err(address.pallet_name())?
|
||||
.pallet_by_name(address.pallet_name())
|
||||
.ok_or_else(|| ConstantError::PalletNameNotFound(address.pallet_name().to_string()))?
|
||||
.constant_hash(address.constant_name())
|
||||
.ok_or_else(|| {
|
||||
MetadataError::ConstantNameNotFound(address.constant_name().to_owned())
|
||||
.ok_or_else(|| ConstantError::ConstantNameNotFound {
|
||||
pallet_name: address.pallet_name().to_string(),
|
||||
constant_name: address.constant_name().to_owned(),
|
||||
})?;
|
||||
if actual_hash != expected_hash {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
return Err(ConstantError::IncompatibleCodegen);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -67,19 +73,37 @@ pub fn validate<Addr: Address>(address: &Addr, metadata: &Metadata) -> Result<()
|
||||
|
||||
/// Fetch a constant out of the metadata given a constant address. If the `address` has been
|
||||
/// statically generated, this will validate that the constant shape is as expected, too.
|
||||
pub fn get<Addr: Address>(address: &Addr, metadata: &Metadata) -> Result<Addr::Target, Error> {
|
||||
pub fn get<Addr: Address>(
|
||||
address: Addr,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Addr::Target, ConstantError> {
|
||||
// 1. Validate constant shape if hash given:
|
||||
validate(address, metadata)?;
|
||||
validate(&address, metadata)?;
|
||||
|
||||
// 2. Attempt to decode the constant into the type given:
|
||||
let constant = metadata
|
||||
.pallet_by_name_err(address.pallet_name())?
|
||||
.constant_by_name(address.constant_name())
|
||||
.ok_or_else(|| MetadataError::ConstantNameNotFound(address.constant_name().to_owned()))?;
|
||||
let value = <Addr::Target as DecodeWithMetadata>::decode_with_metadata(
|
||||
&mut constant.value(),
|
||||
constant.ty(),
|
||||
let constant = frame_decode::constants::decode_constant(
|
||||
address.pallet_name(),
|
||||
address.constant_name(),
|
||||
metadata,
|
||||
)?;
|
||||
Ok(value)
|
||||
metadata.types(),
|
||||
Addr::Target::into_visitor(),
|
||||
)
|
||||
.map_err(ConstantError::CouldNotDecodeConstant)?;
|
||||
|
||||
Ok(constant)
|
||||
}
|
||||
|
||||
/// Access the bytes of a constant by the address it is registered under.
|
||||
pub fn get_bytes<Addr: Address>(
|
||||
address: Addr,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Vec<u8>, ConstantError> {
|
||||
// 1. Validate custom value shape if hash given:
|
||||
validate(&address, metadata)?;
|
||||
|
||||
// 2. Return the underlying bytes:
|
||||
let constant = metadata
|
||||
.constant_info(address.pallet_name(), address.constant_name())
|
||||
.map_err(|e| ConstantError::ConstantInfoError(e.into_owned()))?;
|
||||
Ok(constant.bytes.to_vec())
|
||||
}
|
||||
|
||||
@@ -4,22 +4,23 @@
|
||||
|
||||
//! Construct addresses to access custom values with.
|
||||
|
||||
use crate::dynamic::DecodedValueThunk;
|
||||
use crate::metadata::DecodeWithMetadata;
|
||||
use alloc::borrow::Cow;
|
||||
use alloc::string::String;
|
||||
use derive_where::derive_where;
|
||||
use scale_decode::DecodeAsType;
|
||||
|
||||
/// Use this with [`Address::IsDecodable`].
|
||||
pub use crate::utils::Yes;
|
||||
pub use crate::utils::{Maybe, No, NoMaybe};
|
||||
|
||||
/// This represents the address of a custom value in the metadata.
|
||||
/// Anything that implements it can be used to fetch custom values from the metadata.
|
||||
/// The trait is implemented by [`str`] for dynamic lookup and [`StaticAddress`] for static queries.
|
||||
pub trait Address {
|
||||
/// The type of the custom value.
|
||||
type Target: DecodeWithMetadata;
|
||||
type Target: DecodeAsType;
|
||||
/// Should be set to `Yes` for Dynamic values and static values that have a valid type.
|
||||
/// Should be `()` for custom values, that have an invalid type id.
|
||||
type IsDecodable;
|
||||
/// Should be `No` for custom values, that have an invalid type id.
|
||||
type IsDecodable: NoMaybe;
|
||||
|
||||
/// the name (key) by which the custom value can be accessed in the metadata.
|
||||
fn name(&self) -> &str;
|
||||
@@ -30,9 +31,24 @@ pub trait Address {
|
||||
}
|
||||
}
|
||||
|
||||
// Any reference to an address is a valid address
|
||||
impl<A: Address + ?Sized> Address for &'_ A {
|
||||
type Target = A::Target;
|
||||
type IsDecodable = A::IsDecodable;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
A::name(*self)
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
A::validation_hash(*self)
|
||||
}
|
||||
}
|
||||
|
||||
// Support plain strings for looking up custom values.
|
||||
impl Address for str {
|
||||
type Target = DecodedValueThunk;
|
||||
type IsDecodable = Yes;
|
||||
type Target = scale_value::Value;
|
||||
type IsDecodable = Maybe;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
self
|
||||
@@ -42,19 +58,31 @@ impl Address for str {
|
||||
/// A static address to a custom value.
|
||||
#[derive_where(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
|
||||
pub struct StaticAddress<ReturnTy, IsDecodable> {
|
||||
name: &'static str,
|
||||
name: Cow<'static, str>,
|
||||
hash: Option<[u8; 32]>,
|
||||
phantom: core::marker::PhantomData<(ReturnTy, IsDecodable)>,
|
||||
marker: core::marker::PhantomData<(ReturnTy, IsDecodable)>,
|
||||
}
|
||||
|
||||
/// A dynamic address to a custom value.
|
||||
pub type DynamicAddress<ReturnTy> = StaticAddress<ReturnTy, Maybe>;
|
||||
|
||||
impl<ReturnTy, IsDecodable> StaticAddress<ReturnTy, IsDecodable> {
|
||||
#[doc(hidden)]
|
||||
/// Creates a new StaticAddress.
|
||||
pub fn new_static(name: &'static str, hash: [u8; 32]) -> StaticAddress<ReturnTy, IsDecodable> {
|
||||
StaticAddress::<ReturnTy, IsDecodable> {
|
||||
name,
|
||||
pub fn new_static(name: &'static str, hash: [u8; 32]) -> Self {
|
||||
Self {
|
||||
name: Cow::Borrowed(name),
|
||||
hash: Some(hash),
|
||||
phantom: core::marker::PhantomData,
|
||||
marker: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`StaticAddress`]
|
||||
pub fn new(name: impl Into<String>) -> Self {
|
||||
Self {
|
||||
name: name.into().into(),
|
||||
hash: None,
|
||||
marker: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,20 +91,27 @@ impl<ReturnTy, IsDecodable> StaticAddress<ReturnTy, IsDecodable> {
|
||||
Self {
|
||||
name: self.name,
|
||||
hash: None,
|
||||
phantom: self.phantom,
|
||||
marker: self.marker,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: DecodeWithMetadata, Y> Address for StaticAddress<R, Y> {
|
||||
type Target = R;
|
||||
type IsDecodable = Y;
|
||||
impl<Target: DecodeAsType, IsDecodable: NoMaybe> Address for StaticAddress<Target, IsDecodable> {
|
||||
type Target = Target;
|
||||
type IsDecodable = IsDecodable;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
self.name
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
self.hash
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a new dynamic custom value lookup.
|
||||
pub fn dynamic<ReturnTy: DecodeAsType>(
|
||||
custom_value_name: impl Into<String>,
|
||||
) -> DynamicAddress<ReturnTy> {
|
||||
DynamicAddress::new(custom_value_name)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
//! ```rust
|
||||
//! use subxt_macro::subxt;
|
||||
//! use subxt_core::custom_values;
|
||||
//! use subxt_core::metadata;
|
||||
//! use subxt_core::Metadata;
|
||||
//!
|
||||
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
|
||||
//! #[subxt(
|
||||
@@ -23,7 +23,7 @@
|
||||
//!
|
||||
//! // Some metadata we'd like to access custom values in:
|
||||
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
|
||||
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
|
||||
//! let metadata = Metadata::decode_from(&metadata_bytes[..]).unwrap();
|
||||
//!
|
||||
//! // At the moment, we don't expect to see any custom values in the metadata
|
||||
//! // for Polkadot, so this will return an error:
|
||||
@@ -32,61 +32,64 @@
|
||||
|
||||
pub mod address;
|
||||
|
||||
use crate::utils::Yes;
|
||||
use crate::{Error, Metadata, error::MetadataError, metadata::DecodeWithMetadata};
|
||||
use crate::utils::Maybe;
|
||||
use crate::{Metadata, error::CustomValueError};
|
||||
use address::Address;
|
||||
use alloc::vec::Vec;
|
||||
use frame_decode::custom_values::CustomValueTypeInfo;
|
||||
use scale_decode::IntoVisitor;
|
||||
|
||||
/// Run the validation logic against some custom value address you'd like to access. Returns `Ok(())`
|
||||
/// if the address is valid (or if it's not possible to check since the address has no validation hash).
|
||||
/// Returns an error if the address was not valid (wrong name, type or raw bytes)
|
||||
pub fn validate<Addr: Address + ?Sized>(address: &Addr, metadata: &Metadata) -> Result<(), Error> {
|
||||
pub fn validate<Addr: Address>(address: Addr, metadata: &Metadata) -> Result<(), CustomValueError> {
|
||||
if let Some(actual_hash) = address.validation_hash() {
|
||||
let custom = metadata.custom();
|
||||
let custom_value = custom
|
||||
.get(address.name())
|
||||
.ok_or_else(|| MetadataError::CustomValueNameNotFound(address.name().into()))?;
|
||||
.ok_or_else(|| CustomValueError::NotFound(address.name().into()))?;
|
||||
let expected_hash = custom_value.hash();
|
||||
if actual_hash != expected_hash {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
return Err(CustomValueError::IncompatibleCodegen);
|
||||
}
|
||||
}
|
||||
if metadata.custom().get(address.name()).is_none() {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Access a custom value by the address it is registered under. This can be just a [str] to get back a dynamic value,
|
||||
/// or a static address from the generated static interface to get a value of a static type returned.
|
||||
pub fn get<Addr: Address<IsDecodable = Yes> + ?Sized>(
|
||||
address: &Addr,
|
||||
pub fn get<Addr: Address<IsDecodable = Maybe>>(
|
||||
address: Addr,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Addr::Target, Error> {
|
||||
) -> Result<Addr::Target, CustomValueError> {
|
||||
// 1. Validate custom value shape if hash given:
|
||||
validate(address, metadata)?;
|
||||
validate(&address, metadata)?;
|
||||
|
||||
// 2. Attempt to decode custom value:
|
||||
let custom_value = metadata.custom_value_by_name_err(address.name())?;
|
||||
let value = <Addr::Target as DecodeWithMetadata>::decode_with_metadata(
|
||||
&mut custom_value.bytes(),
|
||||
custom_value.type_id(),
|
||||
let value = frame_decode::custom_values::decode_custom_value(
|
||||
address.name(),
|
||||
metadata,
|
||||
)?;
|
||||
metadata.types(),
|
||||
Addr::Target::into_visitor(),
|
||||
)
|
||||
.map_err(CustomValueError::CouldNotDecodeCustomValue)?;
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Access the bytes of a custom value by the address it is registered under.
|
||||
pub fn get_bytes<Addr: Address + ?Sized>(
|
||||
address: &Addr,
|
||||
pub fn get_bytes<Addr: Address>(
|
||||
address: Addr,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
) -> Result<Vec<u8>, CustomValueError> {
|
||||
// 1. Validate custom value shape if hash given:
|
||||
validate(address, metadata)?;
|
||||
validate(&address, metadata)?;
|
||||
|
||||
// 2. Return the underlying bytes:
|
||||
let custom_value = metadata.custom_value_by_name_err(address.name())?;
|
||||
Ok(custom_value.bytes().to_vec())
|
||||
let custom_value = metadata
|
||||
.custom_value_info(address.name())
|
||||
.map_err(|e| CustomValueError::NotFound(e.not_found))?;
|
||||
Ok(custom_value.bytes.to_vec())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -154,7 +157,7 @@ mod tests {
|
||||
};
|
||||
|
||||
let metadata: subxt_metadata::Metadata = frame_metadata.try_into().unwrap();
|
||||
Metadata::from(metadata)
|
||||
metadata
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -162,8 +165,9 @@ mod tests {
|
||||
let metadata = mock_metadata();
|
||||
|
||||
assert!(custom_values::get("Invalid Address", &metadata).is_err());
|
||||
let person_decoded_value_thunk = custom_values::get("Mr. Robot", &metadata).unwrap();
|
||||
let person: Person = person_decoded_value_thunk.as_type().unwrap();
|
||||
|
||||
let person_addr = custom_values::address::dynamic::<Person>("Mr. Robot");
|
||||
let person = custom_values::get(&person_addr, &metadata).unwrap();
|
||||
assert_eq!(
|
||||
person,
|
||||
Person {
|
||||
|
||||
+2
-62
@@ -5,17 +5,8 @@
|
||||
//! This module provides the entry points to create dynamic
|
||||
//! transactions, storage and constant lookups.
|
||||
|
||||
use crate::metadata::{DecodeWithMetadata, Metadata};
|
||||
use alloc::vec::Vec;
|
||||
use scale_decode::DecodeAsType;
|
||||
pub use scale_value::{At, Value};
|
||||
|
||||
/// A [`scale_value::Value`] type endowed with contextual information
|
||||
/// regarding what type was used to decode each part of it. This implements
|
||||
/// [`crate::metadata::DecodeWithMetadata`], and is used as a return type
|
||||
/// for dynamic requests.
|
||||
pub type DecodedValue = scale_value::Value<u32>;
|
||||
|
||||
// Submit dynamic transactions.
|
||||
pub use crate::tx::payload::dynamic as tx;
|
||||
|
||||
@@ -31,56 +22,5 @@ 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.
|
||||
pub struct DecodedValueThunk {
|
||||
type_id: u32,
|
||||
metadata: Metadata,
|
||||
scale_bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
impl DecodeWithMetadata for DecodedValueThunk {
|
||||
fn decode_with_metadata(
|
||||
bytes: &mut &[u8],
|
||||
type_id: u32,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Self, scale_decode::Error> {
|
||||
let mut v = Vec::with_capacity(bytes.len());
|
||||
v.extend_from_slice(bytes);
|
||||
*bytes = &[];
|
||||
Ok(DecodedValueThunk {
|
||||
type_id,
|
||||
metadata: metadata.clone(),
|
||||
scale_bytes: v,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DecodedValueThunk {
|
||||
/// Return the SCALE encoded bytes handed back from the node.
|
||||
pub fn into_encoded(self) -> Vec<u8> {
|
||||
self.scale_bytes
|
||||
}
|
||||
/// Return the SCALE encoded bytes handed back from the node without taking ownership of them.
|
||||
pub fn encoded(&self) -> &[u8] {
|
||||
&self.scale_bytes
|
||||
}
|
||||
/// Decode the SCALE encoded storage entry into a dynamic [`DecodedValue`] type.
|
||||
pub fn to_value(&self) -> Result<DecodedValue, scale_decode::Error> {
|
||||
let val = scale_value::scale::decode_as_type(
|
||||
&mut &*self.scale_bytes,
|
||||
self.type_id,
|
||||
self.metadata.types(),
|
||||
)?;
|
||||
Ok(val)
|
||||
}
|
||||
/// decode the `DecodedValueThunk` into a concrete type.
|
||||
pub fn as_type<T: DecodeAsType>(&self) -> Result<T, scale_decode::Error> {
|
||||
T::decode_as_type(
|
||||
&mut &self.scale_bytes[..],
|
||||
self.type_id,
|
||||
self.metadata.types(),
|
||||
)
|
||||
}
|
||||
}
|
||||
/// Obtain a custom value from the metadata.
|
||||
pub use crate::custom_values::address::dynamic as custom_value;
|
||||
|
||||
+248
-156
@@ -6,53 +6,238 @@
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use alloc::string::String;
|
||||
use subxt_metadata::StorageHasher;
|
||||
use alloc::vec::Vec;
|
||||
use thiserror::Error as DeriveError;
|
||||
|
||||
/// The error emitted when something goes wrong.
|
||||
#[derive(Debug, DeriveError)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum Error {
|
||||
/// Codec error.
|
||||
#[error("Codec error: {0}")]
|
||||
Codec(codec::Error),
|
||||
/// Metadata error.
|
||||
#[error(transparent)]
|
||||
Metadata(#[from] MetadataError),
|
||||
/// Storage address error.
|
||||
StorageError(#[from] StorageError),
|
||||
#[error(transparent)]
|
||||
StorageAddress(#[from] StorageAddressError),
|
||||
/// Error decoding to a [`crate::dynamic::Value`].
|
||||
#[error("Error decoding into dynamic value: {0}")]
|
||||
Decode(#[from] scale_decode::Error),
|
||||
/// Error encoding from a [`crate::dynamic::Value`].
|
||||
#[error("Error encoding from dynamic value: {0}")]
|
||||
Encode(#[from] scale_encode::Error),
|
||||
/// Error constructing an extrinsic.
|
||||
#[error("Error constructing transaction: {0}")]
|
||||
Extrinsic(#[from] ExtrinsicError),
|
||||
/// Block body error.
|
||||
#[error("Error working with block_body: {0}")]
|
||||
Block(#[from] BlockError),
|
||||
#[error(transparent)]
|
||||
Constant(#[from] ConstantError),
|
||||
#[error(transparent)]
|
||||
CustomValue(#[from] CustomValueError),
|
||||
#[error(transparent)]
|
||||
RuntimeApi(#[from] RuntimeApiError),
|
||||
#[error(transparent)]
|
||||
ViewFunction(#[from] ViewFunctionError),
|
||||
#[error(transparent)]
|
||||
Events(#[from] EventsError),
|
||||
}
|
||||
|
||||
impl From<scale_decode::visitor::DecodeError> for Error {
|
||||
fn from(err: scale_decode::visitor::DecodeError) -> Error {
|
||||
Error::Decode(err.into())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: when `codec::Error` implements `core::Error`
|
||||
// remove this impl and replace it by thiserror #[from]
|
||||
impl From<codec::Error> for Error {
|
||||
fn from(err: codec::Error) -> Error {
|
||||
Error::Codec(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Block error
|
||||
#[derive(Debug, DeriveError)]
|
||||
pub enum BlockError {
|
||||
/// Leftover bytes found after decoding the extrinsic.
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs)]
|
||||
pub enum EventsError {
|
||||
#[error("Can't decode event: can't decode phase: {0}")]
|
||||
CannotDecodePhase(codec::Error),
|
||||
#[error("Can't decode event: can't decode pallet index: {0}")]
|
||||
CannotDecodePalletIndex(codec::Error),
|
||||
#[error("Can't decode event: can't decode variant index: {0}")]
|
||||
CannotDecodeVariantIndex(codec::Error),
|
||||
#[error("Can't decode event: can't find pallet with index {0}")]
|
||||
CannotFindPalletWithIndex(u8),
|
||||
#[error(
|
||||
"Can't decode event: can't find variant with index {variant_index} in pallet {pallet_name}"
|
||||
)]
|
||||
CannotFindVariantWithIndex {
|
||||
pallet_name: String,
|
||||
variant_index: u8,
|
||||
},
|
||||
#[error("Can't decode field {field_name:?} in event {pallet_name}.{event_name}: {reason}")]
|
||||
CannotDecodeFieldInEvent {
|
||||
pallet_name: String,
|
||||
event_name: String,
|
||||
field_name: String,
|
||||
reason: scale_decode::visitor::DecodeError,
|
||||
},
|
||||
#[error("Can't decode event topics: {0}")]
|
||||
CannotDecodeEventTopics(codec::Error),
|
||||
#[error("Can't decode the fields of event {pallet_name}.{event_name}: {reason}")]
|
||||
CannotDecodeEventFields {
|
||||
pallet_name: String,
|
||||
event_name: String,
|
||||
reason: scale_decode::Error,
|
||||
},
|
||||
#[error("Can't decode event {pallet_name}.{event_name} to Event enum: {reason}")]
|
||||
CannotDecodeEventEnum {
|
||||
pallet_name: String,
|
||||
event_name: String,
|
||||
reason: scale_decode::Error,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, DeriveError)]
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs)]
|
||||
pub enum ViewFunctionError {
|
||||
#[error("The static View Function address used is not compatible with the live chain")]
|
||||
IncompatibleCodegen,
|
||||
#[error("Can't find View Function: pallet {0} not found")]
|
||||
PalletNotFound(String),
|
||||
#[error("Can't find View Function {function_name} in pallet {pallet_name}")]
|
||||
ViewFunctionNotFound {
|
||||
pallet_name: String,
|
||||
function_name: String,
|
||||
},
|
||||
#[error("Failed to encode View Function inputs: {0}")]
|
||||
CouldNotEncodeInputs(frame_decode::view_functions::ViewFunctionInputsEncodeError),
|
||||
#[error("Failed to decode View Function: {0}")]
|
||||
CouldNotDecodeResponse(frame_decode::view_functions::ViewFunctionDecodeError<u32>),
|
||||
}
|
||||
|
||||
#[derive(Debug, DeriveError)]
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs)]
|
||||
pub enum RuntimeApiError {
|
||||
#[error("The static Runtime API address used is not compatible with the live chain")]
|
||||
IncompatibleCodegen,
|
||||
#[error("Runtime API trait not found: {0}")]
|
||||
TraitNotFound(String),
|
||||
#[error("Runtime API method {method_name} not found in trait {trait_name}")]
|
||||
MethodNotFound {
|
||||
trait_name: String,
|
||||
method_name: String,
|
||||
},
|
||||
#[error("Failed to encode Runtime API inputs: {0}")]
|
||||
CouldNotEncodeInputs(frame_decode::runtime_apis::RuntimeApiInputsEncodeError),
|
||||
#[error("Failed to decode Runtime API: {0}")]
|
||||
CouldNotDecodeResponse(frame_decode::runtime_apis::RuntimeApiDecodeError<u32>),
|
||||
}
|
||||
|
||||
#[derive(Debug, DeriveError)]
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs)]
|
||||
pub enum CustomValueError {
|
||||
#[error("The static custom value address used is not compatible with the live chain")]
|
||||
IncompatibleCodegen,
|
||||
#[error("The custom value '{0}' was not found")]
|
||||
NotFound(String),
|
||||
#[error("Failed to decode custom value: {0}")]
|
||||
CouldNotDecodeCustomValue(frame_decode::custom_values::CustomValueDecodeError<u32>),
|
||||
}
|
||||
|
||||
/// Something went wrong working with a constant.
|
||||
#[derive(Debug, DeriveError)]
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs)]
|
||||
pub enum ConstantError {
|
||||
#[error("The static constant address used is not compatible with the live chain")]
|
||||
IncompatibleCodegen,
|
||||
#[error("Can't find constant: pallet with name {0} not found")]
|
||||
PalletNameNotFound(String),
|
||||
#[error(
|
||||
"Constant '{constant_name}' not found in pallet {pallet_name} in the live chain metadata"
|
||||
)]
|
||||
ConstantNameNotFound {
|
||||
pallet_name: String,
|
||||
constant_name: String,
|
||||
},
|
||||
#[error("Failed to decode constant: {0}")]
|
||||
CouldNotDecodeConstant(frame_decode::constants::ConstantDecodeError<u32>),
|
||||
#[error("Cannot obtain constant information from metadata: {0}")]
|
||||
ConstantInfoError(frame_decode::constants::ConstantInfoError<'static>),
|
||||
}
|
||||
|
||||
/// Something went wrong trying to encode or decode a storage address.
|
||||
#[derive(Debug, DeriveError)]
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs)]
|
||||
pub enum StorageError {
|
||||
#[error("The static storage address used is not compatible with the live chain")]
|
||||
IncompatibleCodegen,
|
||||
#[error("Can't find storage value: pallet with name {0} not found")]
|
||||
PalletNameNotFound(String),
|
||||
#[error(
|
||||
"Storage entry '{entry_name}' not found in pallet {pallet_name} in the live chain metadata"
|
||||
)]
|
||||
StorageEntryNotFound {
|
||||
pallet_name: String,
|
||||
entry_name: String,
|
||||
},
|
||||
#[error("Cannot obtain storage information from metadata: {0}")]
|
||||
StorageInfoError(frame_decode::storage::StorageInfoError<'static>),
|
||||
#[error("Cannot encode storage key: {0}")]
|
||||
StorageKeyEncodeError(frame_decode::storage::StorageKeyEncodeError),
|
||||
#[error("Cannot create a key to iterate over a plain entry")]
|
||||
CannotIterPlainEntry {
|
||||
pallet_name: String,
|
||||
entry_name: String,
|
||||
},
|
||||
#[error(
|
||||
"Wrong number of key parts provided to iterate a storage address. We expected at most {max_expected} key parts but got {got} key parts"
|
||||
)]
|
||||
WrongNumberOfKeyPartsProvidedForIterating { max_expected: usize, got: usize },
|
||||
#[error(
|
||||
"Wrong number of key parts provided to fetch a storage address. We expected {expected} key parts but got {got} key parts"
|
||||
)]
|
||||
WrongNumberOfKeyPartsProvidedForFetching { expected: usize, got: usize },
|
||||
}
|
||||
|
||||
#[derive(Debug, DeriveError)]
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs)]
|
||||
pub enum StorageKeyError {
|
||||
#[error("Can't decode the storage key: {error}")]
|
||||
StorageKeyDecodeError {
|
||||
bytes: Vec<u8>,
|
||||
error: frame_decode::storage::StorageKeyDecodeError<u32>,
|
||||
},
|
||||
#[error("Can't decode the values from the storage key: {0}")]
|
||||
CannotDecodeValuesInKey(frame_decode::storage::StorageKeyValueDecodeError),
|
||||
#[error(
|
||||
"Cannot decode storage key: there were leftover bytes, indicating that the decoding failed"
|
||||
)]
|
||||
LeftoverBytes { bytes: Vec<u8> },
|
||||
#[error("Can't decode a single value from the storage key part at index {index}: {error}")]
|
||||
CannotDecodeValueInKey {
|
||||
index: usize,
|
||||
error: scale_decode::Error,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, DeriveError)]
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs)]
|
||||
pub enum StorageValueError {
|
||||
#[error("Cannot decode storage value: {0}")]
|
||||
CannotDecode(frame_decode::storage::StorageValueDecodeError<u32>),
|
||||
#[error(
|
||||
"Cannot decode storage value: there were leftover bytes, indicating that the decoding failed"
|
||||
)]
|
||||
LeftoverBytes { bytes: Vec<u8> },
|
||||
}
|
||||
|
||||
/// An error that can be encountered when constructing a transaction.
|
||||
#[derive(Debug, DeriveError)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum ExtrinsicError {
|
||||
#[error("The extrinsic payload is not compatible with the live chain")]
|
||||
IncompatibleCodegen,
|
||||
#[error("Can't find extrinsic: pallet with name {0} not found")]
|
||||
PalletNameNotFound(String),
|
||||
#[error("Can't find extrinsic: call name {call_name} doesn't exist in pallet {pallet_name}")]
|
||||
CallNameNotFound {
|
||||
pallet_name: String,
|
||||
call_name: String,
|
||||
},
|
||||
#[error("Can't encode the extrinsic call data: {0}")]
|
||||
CannotEncodeCallData(scale_encode::Error),
|
||||
#[error("Subxt does not support the extrinsic versions expected by the chain")]
|
||||
UnsupportedVersion,
|
||||
#[error("Cannot construct the required transaction extensions: {0}")]
|
||||
Params(#[from] ExtrinsicParamsError),
|
||||
#[error("Cannot decode transaction extension '{name}': {error}")]
|
||||
CouldNotDecodeTransactionExtension {
|
||||
/// The extension name.
|
||||
name: String,
|
||||
/// The decode error.
|
||||
error: scale_decode::Error,
|
||||
},
|
||||
#[error(
|
||||
"After decoding the extrinsic at index {extrinsic_index}, {num_leftover_bytes} bytes were left, suggesting that decoding may have failed"
|
||||
)]
|
||||
@@ -62,140 +247,49 @@ pub enum BlockError {
|
||||
/// Number of bytes leftover after decoding the extrinsic.
|
||||
num_leftover_bytes: usize,
|
||||
},
|
||||
/// Something went wrong decoding the extrinsic.
|
||||
#[error("Failed to decode extrinsic at index {extrinsic_index}: {error}")]
|
||||
ExtrinsicDecodeError {
|
||||
/// Index of the extrinsic that failed to decode.
|
||||
#[error("{0}")]
|
||||
ExtrinsicDecodeErrorAt(#[from] ExtrinsicDecodeErrorAt),
|
||||
#[error("Failed to decode the fields of an extrinsic at index {extrinsic_index}: {error}")]
|
||||
CannotDecodeFields {
|
||||
/// Index of the extrinsic whose fields we could not decode
|
||||
extrinsic_index: usize,
|
||||
/// The decode error.
|
||||
error: ExtrinsicDecodeError,
|
||||
error: scale_decode::Error,
|
||||
},
|
||||
#[error("Failed to decode the extrinsic at index {extrinsic_index} to a root enum: {error}")]
|
||||
CannotDecodeIntoRootExtrinsic {
|
||||
/// Index of the extrinsic that we failed to decode
|
||||
extrinsic_index: usize,
|
||||
/// The decode error.
|
||||
error: scale_decode::Error,
|
||||
},
|
||||
}
|
||||
|
||||
/// An alias for [`frame_decode::extrinsics::ExtrinsicDecodeError`].
|
||||
///
|
||||
pub type ExtrinsicDecodeError = frame_decode::extrinsics::ExtrinsicDecodeError;
|
||||
|
||||
/// Something went wrong trying to access details in the metadata.
|
||||
#[derive(Clone, Debug, PartialEq, DeriveError)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum MetadataError {
|
||||
/// The DispatchError type isn't available in the metadata
|
||||
#[error("The DispatchError type isn't available")]
|
||||
DispatchErrorNotFound,
|
||||
/// Type not found in metadata.
|
||||
#[error("Type with ID {0} not found")]
|
||||
TypeNotFound(u32),
|
||||
/// Pallet not found (index).
|
||||
#[error("Pallet with index {0} not found")]
|
||||
PalletIndexNotFound(u8),
|
||||
/// Pallet not found (name).
|
||||
#[error("Pallet with name {0} not found")]
|
||||
PalletNameNotFound(String),
|
||||
/// Variant not found.
|
||||
#[error("Variant with index {0} not found")]
|
||||
VariantIndexNotFound(u8),
|
||||
/// Constant not found.
|
||||
#[error("Constant with name {0} not found")]
|
||||
ConstantNameNotFound(String),
|
||||
/// Call not found.
|
||||
#[error("Call with name {0} not found")]
|
||||
CallNameNotFound(String),
|
||||
/// Runtime trait not found.
|
||||
#[error("Runtime trait with name {0} not found")]
|
||||
RuntimeTraitNotFound(String),
|
||||
/// 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),
|
||||
/// Event type not found in metadata.
|
||||
#[error("Event type not found in pallet with index {0}")]
|
||||
EventTypeNotFoundInPallet(u8),
|
||||
/// Storage details not found in metadata.
|
||||
#[error("Storage details not found in pallet with name {0}")]
|
||||
StorageNotFoundInPallet(String),
|
||||
/// Storage entry not found.
|
||||
#[error("Storage entry {0} not found")]
|
||||
StorageEntryNotFound(String),
|
||||
/// The generated interface used is not compatible with the node.
|
||||
#[error("The generated code is not compatible with the node")]
|
||||
IncompatibleCodegen,
|
||||
/// Custom value not found.
|
||||
#[error("Custom value with name {0} not found")]
|
||||
CustomValueNameNotFound(String),
|
||||
#[allow(missing_docs)]
|
||||
#[error("Cannot decode extrinsic at index {extrinsic_index}: {error}")]
|
||||
pub struct ExtrinsicDecodeErrorAt {
|
||||
pub extrinsic_index: usize,
|
||||
pub error: ExtrinsicDecodeErrorAtReason,
|
||||
}
|
||||
|
||||
/// Something went wrong trying to encode or decode a storage address.
|
||||
#[derive(Clone, Debug, DeriveError)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum StorageAddressError {
|
||||
/// Storage lookup does not have the expected number of keys.
|
||||
#[error("Storage lookup requires {expected} keys but more keys have been provided.")]
|
||||
TooManyKeys {
|
||||
/// The number of keys provided in the storage address.
|
||||
expected: usize,
|
||||
},
|
||||
/// This storage entry in the metadata does not have the correct number of hashers to fields.
|
||||
#[error("Storage entry in metadata does not have the correct number of hashers to fields")]
|
||||
WrongNumberOfHashers {
|
||||
/// The number of hashers in the metadata for this storage entry.
|
||||
hashers: usize,
|
||||
/// The number of fields in the metadata for this storage entry.
|
||||
fields: usize,
|
||||
},
|
||||
/// We weren't given enough bytes to decode the storage address/key.
|
||||
#[error("Not enough remaining bytes to decode the storage address/key")]
|
||||
NotEnoughBytes,
|
||||
/// We have leftover bytes after decoding the storage address.
|
||||
#[error("We have leftover bytes after decoding the storage address")]
|
||||
TooManyBytes,
|
||||
/// The bytes of a storage address are not the expected address for decoding the storage keys of the address.
|
||||
#[error(
|
||||
"Storage address bytes are not the expected format. Addresses need to be at least 16 bytes (pallet ++ entry) and follow a structure given by the hashers defined in the metadata"
|
||||
)]
|
||||
UnexpectedAddressBytes,
|
||||
/// An invalid hasher was used to reconstruct a value from a chunk of bytes that is part of a storage address. Hashers where the hash does not contain the original value are invalid for this purpose.
|
||||
#[error(
|
||||
"An invalid hasher was used to reconstruct a value with type ID {ty_id} from a hash formed by a {hasher:?} hasher. This is only possible for concat-style hashers or the identity hasher"
|
||||
)]
|
||||
HasherCannotReconstructKey {
|
||||
/// Type id of the key's type.
|
||||
ty_id: u32,
|
||||
/// The invalid hasher that caused this error.
|
||||
hasher: StorageHasher,
|
||||
},
|
||||
}
|
||||
|
||||
/// An error that can be encountered when constructing a transaction.
|
||||
#[derive(Debug, DeriveError)]
|
||||
#[non_exhaustive]
|
||||
pub enum ExtrinsicError {
|
||||
/// Transaction version not supported by Subxt.
|
||||
#[error("Subxt does not support the extrinsic versions expected by the chain")]
|
||||
UnsupportedVersion,
|
||||
/// Issue encoding transaction extensions.
|
||||
#[error("Cannot construct the required transaction extensions: {0}")]
|
||||
Params(#[from] ExtrinsicParamsError),
|
||||
}
|
||||
|
||||
impl From<ExtrinsicParamsError> for Error {
|
||||
fn from(value: ExtrinsicParamsError) -> Self {
|
||||
Error::Extrinsic(value.into())
|
||||
}
|
||||
#[allow(missing_docs)]
|
||||
pub enum ExtrinsicDecodeErrorAtReason {
|
||||
#[error("{0}")]
|
||||
DecodeError(frame_decode::extrinsics::ExtrinsicDecodeError),
|
||||
#[error("Leftover bytes")]
|
||||
LeftoverBytes(Vec<u8>),
|
||||
}
|
||||
|
||||
/// An error that can be emitted when trying to construct an instance of [`crate::config::ExtrinsicParams`],
|
||||
/// encode data from the instance, or match on signed extensions.
|
||||
#[derive(Debug, DeriveError)]
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs)]
|
||||
pub enum ExtrinsicParamsError {
|
||||
/// Cannot find a type id in the metadata. The context provides some additional
|
||||
/// information about the source of the error (eg the signed extension name).
|
||||
#[error("Cannot find type id '{type_id} in the metadata (context: {context})")]
|
||||
MissingTypeId {
|
||||
/// Type ID.
|
||||
@@ -203,10 +297,8 @@ pub enum ExtrinsicParamsError {
|
||||
/// Some arbitrary context to help narrow the source of the error.
|
||||
context: &'static str,
|
||||
},
|
||||
/// A signed extension in use on some chain was not provided.
|
||||
#[error("The chain expects a signed extension with the name {0}, but we did not provide one")]
|
||||
UnknownTransactionExtension(String),
|
||||
/// Some custom error.
|
||||
#[error("Error constructing extrinsic parameters: {0}")]
|
||||
Custom(Box<dyn core::error::Error + Send + Sync + 'static>),
|
||||
}
|
||||
|
||||
+62
-26
@@ -10,7 +10,8 @@
|
||||
//! use subxt_macro::subxt;
|
||||
//! use subxt_core::config::PolkadotConfig;
|
||||
//! use subxt_core::events;
|
||||
//! use subxt_core::metadata;
|
||||
//! use subxt_core::Metadata;
|
||||
//! use subxt_core::dynamic::Value;
|
||||
//!
|
||||
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
|
||||
//! #[subxt(
|
||||
@@ -21,7 +22,7 @@
|
||||
//!
|
||||
//! // Some metadata we'll use to work with storage entries:
|
||||
//! let metadata_bytes = include_bytes!("../../artifacts/polkadot_metadata_full.scale");
|
||||
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
|
||||
//! let metadata = Metadata::decode_from(&metadata_bytes[..]).unwrap();
|
||||
//!
|
||||
//! // Some bytes representing events (located in System.Events storage):
|
||||
//! let event_bytes = hex::decode("1c00000000000000a2e9b53d5517020000000100000000000310c96d901d0102000000020000000408d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dbeea5a030000000000000000000000000000020000000402d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48102700000000000000000000000000000000020000000407be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25fbeea5a030000000000000000000000000000020000002100d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dbeea5a03000000000000000000000000000000000000000000000000000000000000020000000000426df03e00000000").unwrap();
|
||||
@@ -34,10 +35,11 @@
|
||||
//! let ev = ev.unwrap();
|
||||
//! println!("Index: {}", ev.index());
|
||||
//! println!("Name: {}.{}", ev.pallet_name(), ev.variant_name());
|
||||
//! println!("Fields: {:?}", ev.field_values().unwrap());
|
||||
//! println!("Fields: {:?}", ev.decode_as_fields::<Value>().unwrap());
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use alloc::string::ToString;
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Compact, Decode, Encode};
|
||||
@@ -46,9 +48,9 @@ use scale_decode::{DecodeAsFields, DecodeAsType};
|
||||
use subxt_metadata::PalletMetadata;
|
||||
|
||||
use crate::{
|
||||
Error, Metadata,
|
||||
Metadata,
|
||||
config::{Config, HashFor},
|
||||
error::MetadataError,
|
||||
error::EventsError,
|
||||
};
|
||||
|
||||
/// Create a new [`Events`] instance from the given bytes.
|
||||
@@ -148,7 +150,7 @@ impl<T: Config> Events<T> {
|
||||
// use of it with our `FilterEvents` stuff.
|
||||
pub fn iter(
|
||||
&self,
|
||||
) -> impl Iterator<Item = Result<EventDetails<T>, Error>> + Send + Sync + 'static {
|
||||
) -> impl Iterator<Item = Result<EventDetails<T>, EventsError>> + Send + Sync + 'static {
|
||||
// The event bytes ignoring the compact encoded length on the front:
|
||||
let event_bytes = self.event_bytes.clone();
|
||||
let metadata = self.metadata.clone();
|
||||
@@ -184,25 +186,25 @@ impl<T: Config> Events<T> {
|
||||
/// Iterate through the events using metadata to dynamically decode and skip
|
||||
/// them, and return only those which should decode to the provided `Ev` type.
|
||||
/// If an error occurs, all subsequent iterations return `None`.
|
||||
pub fn find<Ev: StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, Error>> {
|
||||
pub fn find<Ev: StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, EventsError>> {
|
||||
self.iter()
|
||||
.filter_map(|ev| ev.and_then(|ev| ev.as_event::<Ev>()).transpose())
|
||||
}
|
||||
|
||||
/// Iterate through the events using metadata to dynamically decode and skip
|
||||
/// them, and return the first event found which decodes to the provided `Ev` type.
|
||||
pub fn find_first<Ev: StaticEvent>(&self) -> Result<Option<Ev>, Error> {
|
||||
pub fn find_first<Ev: StaticEvent>(&self) -> Result<Option<Ev>, EventsError> {
|
||||
self.find::<Ev>().next().transpose()
|
||||
}
|
||||
|
||||
/// Iterate through the events using metadata to dynamically decode and skip
|
||||
/// them, and return the last event found which decodes to the provided `Ev` type.
|
||||
pub fn find_last<Ev: StaticEvent>(&self) -> Result<Option<Ev>, Error> {
|
||||
pub fn find_last<Ev: StaticEvent>(&self) -> Result<Option<Ev>, EventsError> {
|
||||
self.find::<Ev>().last().transpose()
|
||||
}
|
||||
|
||||
/// Find an event that decodes to the type provided. Returns true if it was found.
|
||||
pub fn has<Ev: StaticEvent>(&self) -> Result<bool, Error> {
|
||||
pub fn has<Ev: StaticEvent>(&self) -> Result<bool, EventsError> {
|
||||
Ok(self.find::<Ev>().next().transpose()?.is_some())
|
||||
}
|
||||
}
|
||||
@@ -246,23 +248,29 @@ impl<T: Config> EventDetails<T> {
|
||||
all_bytes: Arc<[u8]>,
|
||||
start_idx: usize,
|
||||
index: u32,
|
||||
) -> Result<EventDetails<T>, Error> {
|
||||
) -> Result<EventDetails<T>, EventsError> {
|
||||
let input = &mut &all_bytes[start_idx..];
|
||||
|
||||
let phase = Phase::decode(input)?;
|
||||
let phase = Phase::decode(input).map_err(EventsError::CannotDecodePhase)?;
|
||||
|
||||
let event_start_idx = all_bytes.len() - input.len();
|
||||
|
||||
let pallet_index = u8::decode(input)?;
|
||||
let variant_index = u8::decode(input)?;
|
||||
let pallet_index = u8::decode(input).map_err(EventsError::CannotDecodePalletIndex)?;
|
||||
let variant_index = u8::decode(input).map_err(EventsError::CannotDecodeVariantIndex)?;
|
||||
|
||||
let event_fields_start_idx = all_bytes.len() - input.len();
|
||||
|
||||
// Get metadata for the event:
|
||||
let event_pallet = metadata.pallet_by_index_err(pallet_index)?;
|
||||
let event_pallet = metadata
|
||||
.pallet_by_index(pallet_index)
|
||||
.ok_or_else(|| EventsError::CannotFindPalletWithIndex(pallet_index))?;
|
||||
let event_variant = event_pallet
|
||||
.event_variant_by_index(variant_index)
|
||||
.ok_or(MetadataError::VariantIndexNotFound(variant_index))?;
|
||||
.ok_or_else(|| EventsError::CannotFindVariantWithIndex {
|
||||
pallet_name: event_pallet.name().to_string(),
|
||||
variant_index,
|
||||
})?;
|
||||
|
||||
tracing::debug!(
|
||||
"Decoding Event '{}::{}'",
|
||||
event_pallet.name(),
|
||||
@@ -278,14 +286,23 @@ impl<T: Config> EventDetails<T> {
|
||||
metadata.types(),
|
||||
scale_decode::visitor::IgnoreVisitor::new(),
|
||||
)
|
||||
.map_err(scale_decode::Error::from)?;
|
||||
.map_err(|e| EventsError::CannotDecodeFieldInEvent {
|
||||
pallet_name: event_pallet.name().to_string(),
|
||||
event_name: event_variant.name.clone(),
|
||||
field_name: field_metadata
|
||||
.name
|
||||
.clone()
|
||||
.unwrap_or("<unknown>".to_string()),
|
||||
reason: e,
|
||||
})?;
|
||||
}
|
||||
|
||||
// the end of the field bytes.
|
||||
let event_fields_end_idx = all_bytes.len() - input.len();
|
||||
|
||||
// topics come after the event data in EventRecord.
|
||||
let topics = Vec::<HashFor<T>>::decode(input)?;
|
||||
let topics =
|
||||
Vec::<HashFor<T>>::decode(input).map_err(EventsError::CannotDecodeEventTopics)?;
|
||||
|
||||
// what bytes did we skip over in total, including topics.
|
||||
let end_idx = all_bytes.len() - input.len();
|
||||
@@ -367,7 +384,7 @@ impl<T: Config> EventDetails<T> {
|
||||
|
||||
/// Decode and provide the event fields back in the form of a [`scale_value::Composite`]
|
||||
/// type which represents the named or unnamed fields that were present in the event.
|
||||
pub fn field_values(&self) -> Result<scale_value::Composite<u32>, Error> {
|
||||
pub fn decode_as_fields<E: DecodeAsFields>(&self) -> Result<E, EventsError> {
|
||||
let bytes = &mut self.field_bytes();
|
||||
let event_metadata = self.event_metadata();
|
||||
|
||||
@@ -378,14 +395,20 @@ impl<T: Config> EventDetails<T> {
|
||||
.map(|f| scale_decode::Field::new(f.ty.id, f.name.as_deref()));
|
||||
|
||||
let decoded =
|
||||
scale_value::scale::decode_as_fields(bytes, &mut fields, self.metadata.types())?;
|
||||
E::decode_as_fields(bytes, &mut fields, self.metadata.types()).map_err(|e| {
|
||||
EventsError::CannotDecodeEventFields {
|
||||
pallet_name: event_metadata.pallet.name().to_string(),
|
||||
event_name: event_metadata.variant.name.clone(),
|
||||
reason: e,
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(decoded)
|
||||
}
|
||||
|
||||
/// Attempt to decode these [`EventDetails`] into a type representing the event fields.
|
||||
/// Such types are exposed in the codegen as `pallet_name::events::EventName` types.
|
||||
pub fn as_event<E: StaticEvent>(&self) -> Result<Option<E>, Error> {
|
||||
pub fn as_event<E: StaticEvent>(&self) -> Result<Option<E>, EventsError> {
|
||||
let ev_metadata = self.event_metadata();
|
||||
if ev_metadata.pallet.name() == E::PALLET && ev_metadata.variant.name == E::EVENT {
|
||||
let mut fields = ev_metadata
|
||||
@@ -394,7 +417,12 @@ impl<T: Config> EventDetails<T> {
|
||||
.iter()
|
||||
.map(|f| scale_decode::Field::new(f.ty.id, f.name.as_deref()));
|
||||
let decoded =
|
||||
E::decode_as_fields(&mut self.field_bytes(), &mut fields, self.metadata.types())?;
|
||||
E::decode_as_fields(&mut self.field_bytes(), &mut fields, self.metadata.types())
|
||||
.map_err(|e| EventsError::CannotDecodeEventFields {
|
||||
pallet_name: E::PALLET.to_string(),
|
||||
event_name: E::EVENT.to_string(),
|
||||
reason: e,
|
||||
})?;
|
||||
Ok(Some(decoded))
|
||||
} else {
|
||||
Ok(None)
|
||||
@@ -404,14 +432,22 @@ impl<T: Config> EventDetails<T> {
|
||||
/// Attempt to decode these [`EventDetails`] into a root event type (which includes
|
||||
/// the pallet and event enum variants as well as the event fields). A compatible
|
||||
/// type for this is exposed via static codegen as a root level `Event` type.
|
||||
pub fn as_root_event<E: DecodeAsType>(&self) -> Result<E, Error> {
|
||||
pub fn as_root_event<E: DecodeAsType>(&self) -> Result<E, EventsError> {
|
||||
let bytes = &self.all_bytes[self.event_start_idx..self.event_fields_end_idx];
|
||||
|
||||
let decoded = E::decode_as_type(
|
||||
&mut &bytes[..],
|
||||
self.metadata.outer_enums().event_enum_ty(),
|
||||
self.metadata.types(),
|
||||
)?;
|
||||
)
|
||||
.map_err(|e| {
|
||||
let md = self.event_metadata();
|
||||
EventsError::CannotDecodeEventEnum {
|
||||
pallet_name: md.pallet.name().to_string(),
|
||||
event_name: md.variant.name.clone(),
|
||||
reason: e,
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(decoded)
|
||||
}
|
||||
@@ -553,7 +589,7 @@ pub(crate) mod test_utils {
|
||||
let runtime_metadata: RuntimeMetadataPrefixed = meta.into();
|
||||
let metadata: subxt_metadata::Metadata = runtime_metadata.try_into().unwrap();
|
||||
|
||||
Metadata::from(metadata)
|
||||
metadata
|
||||
}
|
||||
|
||||
/// Build an `Events` object for test purposes, based on the details provided,
|
||||
@@ -623,7 +659,7 @@ mod tests {
|
||||
expected: TestRawEventDetails,
|
||||
) {
|
||||
let actual_fields_no_context: Vec<_> = actual
|
||||
.field_values()
|
||||
.decode_as_fields::<scale_value::Composite<()>>()
|
||||
.expect("can decode field values (2)")
|
||||
.into_values()
|
||||
.map(|value| value.remove_context())
|
||||
|
||||
+1
-3
@@ -12,7 +12,6 @@
|
||||
//! - [`blocks`]: decode and explore block bodies.
|
||||
//! - [`constants`]: access and validate the constant addresses in some metadata.
|
||||
//! - [`custom_values`]: access and validate the custom value addresses in some metadata.
|
||||
//! - [`metadata`]: decode bytes into the metadata used throughout this library.
|
||||
//! - [`storage`]: construct storage request payloads and decode the results you'd get back.
|
||||
//! - [`tx`]: construct and sign transactions (extrinsics).
|
||||
//! - [`runtime_api`]: construct runtime API request payloads and decode the results you'd get back.
|
||||
@@ -31,7 +30,6 @@ pub mod custom_values;
|
||||
pub mod dynamic;
|
||||
pub mod error;
|
||||
pub mod events;
|
||||
pub mod metadata;
|
||||
pub mod runtime_api;
|
||||
pub mod storage;
|
||||
pub mod tx;
|
||||
@@ -40,7 +38,7 @@ pub mod view_functions;
|
||||
|
||||
pub use config::Config;
|
||||
pub use error::Error;
|
||||
pub use metadata::Metadata;
|
||||
pub use subxt_metadata::Metadata;
|
||||
|
||||
/// Re-exports of some of the key external crates.
|
||||
pub mod ext {
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
// 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.
|
||||
|
||||
use super::Metadata;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
/// This trait is implemented for all types that also implement [`scale_decode::DecodeAsType`].
|
||||
pub trait DecodeWithMetadata: Sized {
|
||||
/// Given some metadata and a type ID, attempt to SCALE decode the provided bytes into `Self`.
|
||||
fn decode_with_metadata(
|
||||
bytes: &mut &[u8],
|
||||
type_id: u32,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Self, scale_decode::Error>;
|
||||
}
|
||||
|
||||
impl<T: scale_decode::DecodeAsType> DecodeWithMetadata for T {
|
||||
fn decode_with_metadata(
|
||||
bytes: &mut &[u8],
|
||||
type_id: u32,
|
||||
metadata: &Metadata,
|
||||
) -> Result<T, scale_decode::Error> {
|
||||
let val = T::decode_as_type(bytes, type_id, metadata.types())?;
|
||||
Ok(val)
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait is implemented for all types that also implement [`scale_encode::EncodeAsType`].
|
||||
pub trait EncodeWithMetadata {
|
||||
/// SCALE encode this type to bytes, possibly with the help of metadata.
|
||||
fn encode_with_metadata(
|
||||
&self,
|
||||
type_id: u32,
|
||||
metadata: &Metadata,
|
||||
bytes: &mut Vec<u8>,
|
||||
) -> Result<(), scale_encode::Error>;
|
||||
}
|
||||
|
||||
impl<T: scale_encode::EncodeAsType> EncodeWithMetadata for T {
|
||||
/// SCALE encode this type to bytes, possibly with the help of metadata.
|
||||
fn encode_with_metadata(
|
||||
&self,
|
||||
type_id: u32,
|
||||
metadata: &Metadata,
|
||||
bytes: &mut Vec<u8>,
|
||||
) -> Result<(), scale_encode::Error> {
|
||||
self.encode_as_type_to(type_id, metadata.types(), bytes)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
// 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.
|
||||
|
||||
use crate::error::MetadataError;
|
||||
|
||||
use alloc::borrow::ToOwned;
|
||||
use alloc::sync::Arc;
|
||||
|
||||
/// A cheaply clone-able representation of the runtime metadata received from a node.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Metadata {
|
||||
inner: Arc<subxt_metadata::Metadata>,
|
||||
}
|
||||
|
||||
impl core::ops::Deref for Metadata {
|
||||
type Target = subxt_metadata::Metadata;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
/// Identical to `metadata.pallet_by_name()`, but returns an error if the pallet is not found.
|
||||
pub fn pallet_by_name_err(
|
||||
&self,
|
||||
name: &str,
|
||||
) -> Result<subxt_metadata::PalletMetadata<'_>, MetadataError> {
|
||||
self.pallet_by_name(name)
|
||||
.ok_or_else(|| MetadataError::PalletNameNotFound(name.to_owned()))
|
||||
}
|
||||
|
||||
/// Identical to `metadata.pallet_by_index()`, but returns an error if the pallet is not found.
|
||||
pub fn pallet_by_index_err(
|
||||
&self,
|
||||
index: u8,
|
||||
) -> Result<subxt_metadata::PalletMetadata<'_>, MetadataError> {
|
||||
self.pallet_by_index(index)
|
||||
.ok_or(MetadataError::PalletIndexNotFound(index))
|
||||
}
|
||||
|
||||
/// Identical to `metadata.runtime_api_trait_by_name()`, but returns an error if the trait is not found.
|
||||
pub fn runtime_api_trait_by_name_err(
|
||||
&self,
|
||||
name: &str,
|
||||
) -> Result<subxt_metadata::RuntimeApiMetadata<'_>, MetadataError> {
|
||||
self.runtime_api_trait_by_name(name)
|
||||
.ok_or_else(|| MetadataError::RuntimeTraitNotFound(name.to_owned()))
|
||||
}
|
||||
|
||||
/// Identical to `metadata.custom().get(name)`, but returns an error if the trait is not found.
|
||||
pub fn custom_value_by_name_err(
|
||||
&self,
|
||||
name: &str,
|
||||
) -> Result<subxt_metadata::CustomValueMetadata<'_>, MetadataError> {
|
||||
self.custom()
|
||||
.get(name)
|
||||
.ok_or_else(|| MetadataError::CustomValueNameNotFound(name.to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<subxt_metadata::Metadata> for Metadata {
|
||||
fn from(md: subxt_metadata::Metadata) -> Self {
|
||||
Metadata {
|
||||
inner: Arc::new(md),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<frame_metadata::RuntimeMetadataPrefixed> for Metadata {
|
||||
type Error = subxt_metadata::TryFromError;
|
||||
fn try_from(value: frame_metadata::RuntimeMetadataPrefixed) -> Result<Self, Self::Error> {
|
||||
subxt_metadata::Metadata::try_from(value).map(Metadata::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl codec::Decode for Metadata {
|
||||
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
|
||||
subxt_metadata::Metadata::decode(input).map(Metadata::from)
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
// 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.
|
||||
|
||||
//! A [`Metadata`] type, which is used through this crate.
|
||||
//!
|
||||
//! This can be decoded from the bytes handed back from a node when asking for metadata.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```rust
|
||||
//! use subxt_core::metadata;
|
||||
//!
|
||||
//! // We need to fetch the bytes from somewhere, and then we can decode them:
|
||||
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
|
||||
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
|
||||
//! ```
|
||||
|
||||
mod decode_encode_traits;
|
||||
mod metadata_type;
|
||||
|
||||
use codec::Decode;
|
||||
|
||||
pub use decode_encode_traits::{DecodeWithMetadata, EncodeWithMetadata};
|
||||
pub use metadata_type::Metadata;
|
||||
|
||||
/// Attempt to decode some bytes into [`Metadata`], returning an error
|
||||
/// if decoding fails.
|
||||
///
|
||||
/// This is a shortcut for importing [`codec::Decode`] and using the
|
||||
/// implementation of that on [`Metadata`].
|
||||
pub fn decode_from(bytes: &[u8]) -> Result<Metadata, codec::Error> {
|
||||
Metadata::decode(&mut &*bytes)
|
||||
}
|
||||
+48
-31
@@ -10,7 +10,7 @@
|
||||
//! ```rust
|
||||
//! use subxt_macro::subxt;
|
||||
//! use subxt_core::runtime_api;
|
||||
//! use subxt_core::metadata;
|
||||
//! use subxt_core::Metadata;
|
||||
//!
|
||||
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
|
||||
//! #[subxt(
|
||||
@@ -21,7 +21,7 @@
|
||||
//!
|
||||
//! // Some metadata we'll use to work with storage entries:
|
||||
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
|
||||
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
|
||||
//! let metadata = Metadata::decode_from(&metadata_bytes[..]).unwrap();
|
||||
//!
|
||||
//! // Build a storage query to access account information.
|
||||
//! let payload = polkadot::apis().metadata().metadata_versions();
|
||||
@@ -43,61 +43,78 @@
|
||||
|
||||
pub mod payload;
|
||||
|
||||
use crate::error::{Error, MetadataError};
|
||||
use crate::metadata::{DecodeWithMetadata, Metadata};
|
||||
use alloc::borrow::ToOwned;
|
||||
use crate::Metadata;
|
||||
use crate::error::RuntimeApiError;
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::vec::Vec;
|
||||
use payload::Payload;
|
||||
use scale_decode::IntoVisitor;
|
||||
|
||||
/// Run the validation logic against some runtime API 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 runtime API 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 {
|
||||
pub fn validate<P: Payload>(payload: P, metadata: &Metadata) -> Result<(), RuntimeApiError> {
|
||||
let Some(hash) = payload.validation_hash() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let api_trait = metadata.runtime_api_trait_by_name_err(payload.trait_name())?;
|
||||
let Some(api_method) = api_trait.method_by_name(payload.method_name()) else {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
};
|
||||
let trait_name = payload.trait_name();
|
||||
let method_name = payload.method_name();
|
||||
|
||||
let runtime_hash = api_method.hash();
|
||||
if static_hash != runtime_hash {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
let api_trait = metadata
|
||||
.runtime_api_trait_by_name(trait_name)
|
||||
.ok_or_else(|| RuntimeApiError::TraitNotFound(trait_name.to_string()))?;
|
||||
let api_method =
|
||||
api_trait
|
||||
.method_by_name(method_name)
|
||||
.ok_or_else(|| RuntimeApiError::MethodNotFound {
|
||||
trait_name: trait_name.to_string(),
|
||||
method_name: method_name.to_string(),
|
||||
})?;
|
||||
|
||||
if hash != api_method.hash() {
|
||||
Err(RuntimeApiError::IncompatibleCodegen)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the name of the runtime API call from the payload.
|
||||
pub fn call_name<P: Payload>(payload: &P) -> String {
|
||||
pub fn call_name<P: Payload>(payload: P) -> String {
|
||||
format!("{}_{}", payload.trait_name(), payload.method_name())
|
||||
}
|
||||
|
||||
/// Return the encoded call args given a runtime API payload.
|
||||
pub fn call_args<P: Payload>(payload: &P, metadata: &Metadata) -> Result<Vec<u8>, Error> {
|
||||
payload.encode_args(metadata)
|
||||
pub fn call_args<P: Payload>(payload: P, metadata: &Metadata) -> Result<Vec<u8>, RuntimeApiError> {
|
||||
let value = frame_decode::runtime_apis::encode_runtime_api_inputs(
|
||||
payload.trait_name(),
|
||||
payload.method_name(),
|
||||
payload.args(),
|
||||
metadata,
|
||||
metadata.types(),
|
||||
)
|
||||
.map_err(RuntimeApiError::CouldNotEncodeInputs)?;
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Decode the value bytes at the location given by the provided runtime API payload.
|
||||
pub fn decode_value<P: Payload>(
|
||||
bytes: &mut &[u8],
|
||||
payload: &P,
|
||||
payload: P,
|
||||
metadata: &Metadata,
|
||||
) -> Result<P::ReturnType, Error> {
|
||||
let api_method = metadata
|
||||
.runtime_api_trait_by_name_err(payload.trait_name())?
|
||||
.method_by_name(payload.method_name())
|
||||
.ok_or_else(|| MetadataError::RuntimeMethodNotFound(payload.method_name().to_owned()))?;
|
||||
|
||||
let val = <P::ReturnType as DecodeWithMetadata>::decode_with_metadata(
|
||||
&mut &bytes[..],
|
||||
api_method.output_ty(),
|
||||
) -> Result<P::ReturnType, RuntimeApiError> {
|
||||
let value = frame_decode::runtime_apis::decode_runtime_api_response(
|
||||
payload.trait_name(),
|
||||
payload.method_name(),
|
||||
bytes,
|
||||
metadata,
|
||||
)?;
|
||||
metadata.types(),
|
||||
P::ReturnType::into_visitor(),
|
||||
)
|
||||
.map_err(RuntimeApiError::CouldNotDecodeResponse)?;
|
||||
|
||||
Ok(val)
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
@@ -6,45 +6,19 @@
|
||||
//! runtime API calls that can be made.
|
||||
|
||||
use alloc::borrow::Cow;
|
||||
use alloc::borrow::ToOwned;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use core::marker::PhantomData;
|
||||
use derive_where::derive_where;
|
||||
use scale_encode::EncodeAsFields;
|
||||
use scale_value::Composite;
|
||||
use frame_decode::runtime_apis::IntoEncodableValues;
|
||||
use scale_decode::DecodeAsType;
|
||||
|
||||
use crate::Error;
|
||||
use crate::dynamic::DecodedValueThunk;
|
||||
use crate::error::MetadataError;
|
||||
|
||||
use crate::metadata::{DecodeWithMetadata, Metadata};
|
||||
|
||||
/// This represents a runtime API payload that can call into the runtime of node.
|
||||
///
|
||||
/// # Components
|
||||
///
|
||||
/// - associated return type
|
||||
///
|
||||
/// Resulting bytes of the call are interpreted into this type.
|
||||
///
|
||||
/// - runtime function name
|
||||
///
|
||||
/// The function name of the runtime API call. This is obtained by concatenating
|
||||
/// the runtime trait name with the trait's method.
|
||||
///
|
||||
/// For example, the substrate runtime trait [Metadata](https://github.com/paritytech/substrate/blob/cb954820a8d8d765ce75021e244223a3b4d5722d/primitives/api/src/lib.rs#L745)
|
||||
/// contains the `metadata_at_version` function. The corresponding runtime function
|
||||
/// is `Metadata_metadata_at_version`.
|
||||
///
|
||||
/// - encoded arguments
|
||||
///
|
||||
/// Each argument of the runtime function must be scale-encoded.
|
||||
/// This represents a runtime API payload that can be used to call a Runtime API on
|
||||
/// a chain and decode the response.
|
||||
pub trait Payload {
|
||||
/// Type of the arguments.
|
||||
type ArgsType: IntoEncodableValues;
|
||||
/// The return type of the function call.
|
||||
// Note: `DecodeWithMetadata` is needed to decode the function call result
|
||||
// with the `subxt::Metadata.
|
||||
type ReturnType: DecodeWithMetadata;
|
||||
type ReturnType: DecodeAsType;
|
||||
|
||||
/// The runtime API trait name.
|
||||
fn trait_name(&self) -> &str;
|
||||
@@ -52,16 +26,8 @@ pub trait Payload {
|
||||
/// The runtime API method name.
|
||||
fn method_name(&self) -> &str;
|
||||
|
||||
/// 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)
|
||||
}
|
||||
/// The input arguments.
|
||||
fn args(&self) -> &Self::ArgsType;
|
||||
|
||||
/// Returns the statically generated validation hash.
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
@@ -69,29 +35,50 @@ pub trait Payload {
|
||||
}
|
||||
}
|
||||
|
||||
// Any reference to a payload is a valid payload.
|
||||
impl<P: Payload + ?Sized> Payload for &'_ P {
|
||||
type ArgsType = P::ArgsType;
|
||||
type ReturnType = P::ReturnType;
|
||||
|
||||
fn trait_name(&self) -> &str {
|
||||
P::trait_name(*self)
|
||||
}
|
||||
|
||||
fn method_name(&self) -> &str {
|
||||
P::method_name(*self)
|
||||
}
|
||||
|
||||
fn args(&self) -> &Self::ArgsType {
|
||||
P::args(*self)
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
P::validation_hash(*self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A runtime API 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> {
|
||||
#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; ArgsType)]
|
||||
pub struct StaticPayload<ArgsType, ReturnType> {
|
||||
trait_name: Cow<'static, str>,
|
||||
method_name: Cow<'static, str>,
|
||||
args_data: ArgsData,
|
||||
args: ArgsType,
|
||||
validation_hash: Option<[u8; 32]>,
|
||||
_marker: PhantomData<ReturnTy>,
|
||||
_marker: PhantomData<ReturnType>,
|
||||
}
|
||||
|
||||
/// A statically generated runtime API payload.
|
||||
pub type StaticPayload<ArgsData, ReturnTy> = DefaultPayload<ArgsData, ReturnTy>;
|
||||
/// A dynamic runtime API payload.
|
||||
pub type DynamicPayload = DefaultPayload<Composite<()>, DecodedValueThunk>;
|
||||
pub type DynamicPayload<ArgsType, ReturnType> = StaticPayload<ArgsType, ReturnType>;
|
||||
|
||||
impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> Payload
|
||||
for DefaultPayload<ArgsData, ReturnTy>
|
||||
impl<ArgsType: IntoEncodableValues, ReturnType: DecodeAsType> Payload
|
||||
for StaticPayload<ArgsType, ReturnType>
|
||||
{
|
||||
type ReturnType = ReturnTy;
|
||||
type ArgsType = ArgsType;
|
||||
type ReturnType = ReturnType;
|
||||
|
||||
fn trait_name(&self) -> &str {
|
||||
&self.trait_name
|
||||
@@ -101,18 +88,8 @@ impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> Payload
|
||||
&self.method_name
|
||||
}
|
||||
|
||||
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
|
||||
let api_method = metadata
|
||||
.runtime_api_trait_by_name_err(&self.trait_name)?
|
||||
.method_by_name(&self.method_name)
|
||||
.ok_or_else(|| MetadataError::RuntimeMethodNotFound((*self.method_name).to_owned()))?;
|
||||
let mut fields = api_method
|
||||
.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 args(&self) -> &Self::ArgsType {
|
||||
&self.args
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
@@ -120,23 +97,23 @@ impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> Payload
|
||||
}
|
||||
}
|
||||
|
||||
impl<ReturnTy, ArgsData> DefaultPayload<ArgsData, ReturnTy> {
|
||||
/// Create a new [`DefaultPayload`].
|
||||
impl<ArgsType, ReturnTy> StaticPayload<ArgsType, ReturnTy> {
|
||||
/// Create a new [`StaticPayload`].
|
||||
pub fn new(
|
||||
trait_name: impl Into<String>,
|
||||
method_name: impl Into<String>,
|
||||
args_data: ArgsData,
|
||||
args: ArgsType,
|
||||
) -> Self {
|
||||
DefaultPayload {
|
||||
trait_name: Cow::Owned(trait_name.into()),
|
||||
method_name: Cow::Owned(method_name.into()),
|
||||
args_data,
|
||||
StaticPayload {
|
||||
trait_name: trait_name.into().into(),
|
||||
method_name: method_name.into().into(),
|
||||
args,
|
||||
validation_hash: None,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new static [`DefaultPayload`] using static function name
|
||||
/// Create a new static [`StaticPayload`] using static function name
|
||||
/// and scale-encoded argument data.
|
||||
///
|
||||
/// This is only expected to be used from codegen.
|
||||
@@ -144,13 +121,13 @@ impl<ReturnTy, ArgsData> DefaultPayload<ArgsData, ReturnTy> {
|
||||
pub fn new_static(
|
||||
trait_name: &'static str,
|
||||
method_name: &'static str,
|
||||
args_data: ArgsData,
|
||||
args: ArgsType,
|
||||
hash: [u8; 32],
|
||||
) -> DefaultPayload<ArgsData, ReturnTy> {
|
||||
DefaultPayload {
|
||||
) -> StaticPayload<ArgsType, ReturnTy> {
|
||||
StaticPayload {
|
||||
trait_name: Cow::Borrowed(trait_name),
|
||||
method_name: Cow::Borrowed(method_name),
|
||||
args_data,
|
||||
args,
|
||||
validation_hash: Some(hash),
|
||||
_marker: core::marker::PhantomData,
|
||||
}
|
||||
@@ -173,18 +150,13 @@ impl<ReturnTy, ArgsData> DefaultPayload<ArgsData, ReturnTy> {
|
||||
pub fn method_name(&self) -> &str {
|
||||
&self.method_name
|
||||
}
|
||||
|
||||
/// Returns the arguments data.
|
||||
pub fn args_data(&self) -> &ArgsData {
|
||||
&self.args_data
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`DynamicPayload`].
|
||||
pub fn dynamic(
|
||||
pub fn dynamic<ArgsType, ReturnType>(
|
||||
trait_name: impl Into<String>,
|
||||
method_name: impl Into<String>,
|
||||
args_data: impl Into<Composite<()>>,
|
||||
) -> DynamicPayload {
|
||||
DefaultPayload::new(trait_name, method_name, args_data.into())
|
||||
args_data: ArgsType,
|
||||
) -> DynamicPayload<ArgsType, ReturnType> {
|
||||
DynamicPayload::new(trait_name, method_name, args_data)
|
||||
}
|
||||
|
||||
+105
-114
@@ -4,143 +4,125 @@
|
||||
|
||||
//! Construct addresses to access storage entries with.
|
||||
|
||||
use crate::{
|
||||
dynamic::DecodedValueThunk,
|
||||
error::{Error, MetadataError},
|
||||
metadata::{DecodeWithMetadata, Metadata},
|
||||
utils::Yes,
|
||||
};
|
||||
use derive_where::derive_where;
|
||||
|
||||
use alloc::borrow::{Cow, ToOwned};
|
||||
use crate::utils::{Maybe, YesMaybe};
|
||||
use alloc::borrow::Cow;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use frame_decode::storage::{IntoDecodableValues, IntoEncodableValues};
|
||||
use scale_decode::DecodeAsType;
|
||||
|
||||
// Re-export types used here:
|
||||
pub use super::storage_key::{StaticStorageKey, StorageHashers, StorageHashersIter, StorageKey};
|
||||
|
||||
/// This represents a storage address. Anything implementing this trait
|
||||
/// can be used to fetch and iterate over storage entries.
|
||||
/// A storage address. This allows access to a given storage entry, which can then
|
||||
/// be iterated over or fetched from by providing the relevant set of keys, or
|
||||
/// otherwise inspected.
|
||||
pub trait Address {
|
||||
/// The target type of the value that lives at this address.
|
||||
type Target: DecodeWithMetadata;
|
||||
/// The keys type used to construct this address.
|
||||
type Keys: StorageKey;
|
||||
/// Can an entry be fetched from this address?
|
||||
/// Set this type to [`Yes`] to enable the corresponding calls to be made.
|
||||
type IsFetchable;
|
||||
/// Can a default entry be obtained from this address?
|
||||
/// Set this type to [`Yes`] to enable the corresponding calls to be made.
|
||||
type IsDefaultable;
|
||||
/// Can this address be iterated over?
|
||||
/// Set this type to [`Yes`] to enable the corresponding calls to be made.
|
||||
type IsIterable;
|
||||
/// All of the keys required to get to an individual value at this address.
|
||||
/// Keys must always impl [`IntoEncodableValues`], and for iteration must
|
||||
/// also impl [`frame_decode::storage::IntoDecodableValues`].
|
||||
type KeyParts: IntoEncodableValues + IntoDecodableValues;
|
||||
/// Type of the storage value at this location.
|
||||
type Value: DecodeAsType;
|
||||
/// Does the address point to a plain value (as opposed to a map)?
|
||||
/// Set to [`crate::utils::Yes`] to enable APIs which require a map,
|
||||
/// or [`crate::utils::Maybe`] to enable APIs which allow a map.
|
||||
type IsPlain: YesMaybe;
|
||||
|
||||
/// The name of the pallet that the entry lives under.
|
||||
/// The pallet containing this storage entry.
|
||||
fn pallet_name(&self) -> &str;
|
||||
|
||||
/// The name of the entry in a given pallet that the item is at.
|
||||
/// The name of the storage entry.
|
||||
fn entry_name(&self) -> &str;
|
||||
|
||||
/// Output the non-prefix bytes; that is, any additional bytes that need
|
||||
/// to be appended to the key to dig into maps.
|
||||
fn append_entry_bytes(&self, metadata: &Metadata, bytes: &mut Vec<u8>) -> Result<(), Error>;
|
||||
/// Return a unique hash for this address which can be used to validate it against metadata.
|
||||
fn validation_hash(&self) -> Option<[u8; 32]>;
|
||||
}
|
||||
|
||||
// Any reference to an address is a valid address.
|
||||
impl<A: Address + ?Sized> Address for &'_ A {
|
||||
type KeyParts = A::KeyParts;
|
||||
type Value = A::Value;
|
||||
type IsPlain = A::IsPlain;
|
||||
|
||||
fn pallet_name(&self) -> &str {
|
||||
A::pallet_name(*self)
|
||||
}
|
||||
|
||||
fn entry_name(&self) -> &str {
|
||||
A::entry_name(*self)
|
||||
}
|
||||
|
||||
/// An optional hash which, if present, will be checked against
|
||||
/// the node metadata to confirm that the return type matches what
|
||||
/// we are expecting.
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
None
|
||||
A::validation_hash(*self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A concrete storage address. 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; Keys)]
|
||||
pub struct DefaultAddress<Keys: StorageKey, ReturnTy, Fetchable, Defaultable, Iterable> {
|
||||
/// An address which is generated by the static APIs.
|
||||
pub struct StaticAddress<KeyParts, Value, IsPlain> {
|
||||
pallet_name: Cow<'static, str>,
|
||||
entry_name: Cow<'static, str>,
|
||||
keys: Keys,
|
||||
validation_hash: Option<[u8; 32]>,
|
||||
_marker: core::marker::PhantomData<(ReturnTy, Fetchable, Defaultable, Iterable)>,
|
||||
marker: core::marker::PhantomData<(KeyParts, Value, IsPlain)>,
|
||||
}
|
||||
|
||||
/// A storage address constructed by the static codegen.
|
||||
pub type StaticAddress<Keys, ReturnTy, Fetchable, Defaultable, Iterable> =
|
||||
DefaultAddress<Keys, ReturnTy, Fetchable, Defaultable, Iterable>;
|
||||
/// A typical storage address constructed at runtime rather than via the `subxt` macro; this
|
||||
/// has no restriction on what it can be used for (since we don't statically know).
|
||||
pub type DynamicAddress<Keys> = DefaultAddress<Keys, DecodedValueThunk, Yes, Yes, Yes>;
|
||||
|
||||
impl<Keys: StorageKey> DynamicAddress<Keys> {
|
||||
/// Creates a new dynamic address. As `Keys` you can use a `Vec<scale_value::Value>`
|
||||
pub fn new(pallet_name: impl Into<String>, entry_name: impl Into<String>, keys: Keys) -> Self {
|
||||
impl<KeyParts, Value, IsPlain> Clone for StaticAddress<KeyParts, Value, IsPlain> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
pallet_name: Cow::Owned(pallet_name.into()),
|
||||
entry_name: Cow::Owned(entry_name.into()),
|
||||
keys,
|
||||
validation_hash: None,
|
||||
_marker: core::marker::PhantomData,
|
||||
pallet_name: self.pallet_name.clone(),
|
||||
entry_name: self.entry_name.clone(),
|
||||
validation_hash: self.validation_hash,
|
||||
marker: self.marker,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Keys, ReturnTy, Fetchable, Defaultable, Iterable>
|
||||
DefaultAddress<Keys, ReturnTy, Fetchable, Defaultable, Iterable>
|
||||
where
|
||||
Keys: StorageKey,
|
||||
ReturnTy: DecodeWithMetadata,
|
||||
{
|
||||
/// Create a new [`Address`] using static strings for the pallet and call name.
|
||||
impl<KeyParts, Value, IsPlain> core::fmt::Debug for StaticAddress<KeyParts, Value, IsPlain> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("StaticAddress")
|
||||
.field("pallet_name", &self.pallet_name)
|
||||
.field("entry_name", &self.entry_name)
|
||||
.field("validation_hash", &self.validation_hash)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<KeyParts, Value, IsPlain> StaticAddress<KeyParts, Value, IsPlain> {
|
||||
/// Create a new [`StaticAddress`] using static strings for the pallet and call name.
|
||||
/// This is only expected to be used from codegen.
|
||||
#[doc(hidden)]
|
||||
pub fn new_static(
|
||||
pallet_name: &'static str,
|
||||
entry_name: &'static str,
|
||||
keys: Keys,
|
||||
hash: [u8; 32],
|
||||
) -> Self {
|
||||
pub fn new_static(pallet_name: &'static str, entry_name: &'static str, hash: [u8; 32]) -> Self {
|
||||
Self {
|
||||
pallet_name: Cow::Borrowed(pallet_name),
|
||||
entry_name: Cow::Borrowed(entry_name),
|
||||
keys,
|
||||
validation_hash: Some(hash),
|
||||
_marker: core::marker::PhantomData,
|
||||
marker: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Keys, ReturnTy, Fetchable, Defaultable, Iterable>
|
||||
DefaultAddress<Keys, ReturnTy, Fetchable, Defaultable, Iterable>
|
||||
where
|
||||
Keys: StorageKey,
|
||||
ReturnTy: DecodeWithMetadata,
|
||||
{
|
||||
/// Do not validate this storage entry prior to accessing it.
|
||||
pub fn unvalidated(self) -> Self {
|
||||
/// Create a new address.
|
||||
pub fn new(pallet_name: impl Into<String>, entry_name: impl Into<String>) -> Self {
|
||||
Self {
|
||||
pallet_name: pallet_name.into().into(),
|
||||
entry_name: entry_name.into().into(),
|
||||
validation_hash: None,
|
||||
..self
|
||||
marker: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return bytes representing the root of this storage entry (a hash of the pallet and entry name).
|
||||
pub fn to_root_bytes(&self) -> Vec<u8> {
|
||||
super::get_address_root_bytes(self)
|
||||
/// Do not validate this storage entry prior to accessing it.
|
||||
pub fn unvalidated(mut self) -> Self {
|
||||
self.validation_hash = None;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Keys, ReturnTy, Fetchable, Defaultable, Iterable> Address
|
||||
for DefaultAddress<Keys, ReturnTy, Fetchable, Defaultable, Iterable>
|
||||
impl<KeyParts, Value, IsPlain> Address for StaticAddress<KeyParts, Value, IsPlain>
|
||||
where
|
||||
Keys: StorageKey,
|
||||
ReturnTy: DecodeWithMetadata,
|
||||
KeyParts: IntoEncodableValues + IntoDecodableValues,
|
||||
Value: DecodeAsType,
|
||||
IsPlain: YesMaybe,
|
||||
{
|
||||
type Target = ReturnTy;
|
||||
type Keys = Keys;
|
||||
type IsFetchable = Fetchable;
|
||||
type IsDefaultable = Defaultable;
|
||||
type IsIterable = Iterable;
|
||||
type KeyParts = KeyParts;
|
||||
type Value = Value;
|
||||
type IsPlain = IsPlain;
|
||||
|
||||
fn pallet_name(&self) -> &str {
|
||||
&self.pallet_name
|
||||
@@ -150,31 +132,40 @@ where
|
||||
&self.entry_name
|
||||
}
|
||||
|
||||
fn append_entry_bytes(&self, metadata: &Metadata, bytes: &mut Vec<u8>) -> Result<(), Error> {
|
||||
let pallet = metadata.pallet_by_name_err(self.pallet_name())?;
|
||||
let storage = pallet
|
||||
.storage()
|
||||
.ok_or_else(|| MetadataError::StorageNotFoundInPallet(self.pallet_name().to_owned()))?;
|
||||
let entry = storage
|
||||
.entry_by_name(self.entry_name())
|
||||
.ok_or_else(|| MetadataError::StorageEntryNotFound(self.entry_name().to_owned()))?;
|
||||
|
||||
let hashers = StorageHashers::new(entry.entry_type(), metadata.types())?;
|
||||
self.keys
|
||||
.encode_storage_key(bytes, &mut hashers.iter(), metadata.types())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
self.validation_hash
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a new dynamic storage lookup.
|
||||
pub fn dynamic<Keys: StorageKey>(
|
||||
impl<A: AsRef<str>, B: AsRef<str>> Address for (A, B) {
|
||||
type KeyParts = Vec<scale_value::Value>;
|
||||
type Value = scale_value::Value;
|
||||
type IsPlain = Maybe;
|
||||
|
||||
fn pallet_name(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
}
|
||||
|
||||
fn entry_name(&self) -> &str {
|
||||
self.1.as_ref()
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A dynamic address is simply a [`StaticAddress`] which asserts that the
|
||||
/// entry *might* be a map and *might* have a default value.
|
||||
pub type DynamicAddress<KeyParts = Vec<scale_value::Value>, Value = scale_value::Value> =
|
||||
StaticAddress<KeyParts, Value, Maybe>;
|
||||
|
||||
/// Construct a new dynamic storage address. You can define the type of the
|
||||
/// storage keys and value yourself here, but have no guarantee that they will
|
||||
/// be correct.
|
||||
pub fn dynamic<KeyParts: IntoEncodableValues, Value: DecodeAsType>(
|
||||
pallet_name: impl Into<String>,
|
||||
entry_name: impl Into<String>,
|
||||
storage_entry_keys: Keys,
|
||||
) -> DynamicAddress<Keys> {
|
||||
DynamicAddress::new(pallet_name, entry_name, storage_entry_keys)
|
||||
) -> DynamicAddress<KeyParts, Value> {
|
||||
DynamicAddress::<KeyParts, Value>::new(pallet_name.into(), entry_name.into())
|
||||
}
|
||||
|
||||
+39
-87
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
//! use subxt_signer::sr25519::dev;
|
||||
//! use subxt_macro::subxt;
|
||||
//! use subxt_core::storage;
|
||||
//! use subxt_core::metadata;
|
||||
//! use subxt_core::Metadata;
|
||||
//!
|
||||
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
|
||||
//! #[subxt(
|
||||
@@ -21,45 +21,54 @@
|
||||
//!
|
||||
//! // Some metadata we'll use to work with storage entries:
|
||||
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
|
||||
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
|
||||
//! let metadata = Metadata::decode_from(&metadata_bytes[..]).unwrap();
|
||||
//!
|
||||
//! // Build a storage query to access account information.
|
||||
//! let account = dev::alice().public_key().into();
|
||||
//! let address = polkadot::storage().system().account(account);
|
||||
//! let address = polkadot::storage().system().account();
|
||||
//!
|
||||
//! // We can validate that the address is compatible with the given metadata.
|
||||
//! storage::validate(&address, &metadata).unwrap();
|
||||
//!
|
||||
//! // Encode the address to bytes. These can be sent to a node to query the value.
|
||||
//! storage::get_address_bytes(&address, &metadata).unwrap();
|
||||
//! // We can fetch details about the storage entry associated with this address:
|
||||
//! let entry = storage::entry(address, &metadata).unwrap();
|
||||
//!
|
||||
//! // If we were to obtain a value back from the node at that address, we could
|
||||
//! // then decode it using the same address and metadata like so:
|
||||
//! // .. including generating a key to fetch the entry with:
|
||||
//! let fetch_key = entry.fetch_key((dev::alice().public_key().into(),)).unwrap();
|
||||
//!
|
||||
//! // .. or generating a key to iterate over entries with at a given depth:
|
||||
//! let iter_key = entry.iter_key(()).unwrap();
|
||||
//!
|
||||
//! // Given a value, we can decode it:
|
||||
//! let value_bytes = hex::decode("00000000000000000100000000000000000064a7b3b6e00d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080").unwrap();
|
||||
//! let value = storage::decode_value(&mut &*value_bytes, &address, &metadata).unwrap();
|
||||
//! let value = entry.value(value_bytes).decode().unwrap();
|
||||
//!
|
||||
//! println!("Alice's account info: {value:?}");
|
||||
//! ```
|
||||
|
||||
mod prefix_of;
|
||||
mod storage_entry;
|
||||
mod storage_key;
|
||||
mod utils;
|
||||
mod storage_key_value;
|
||||
mod storage_value;
|
||||
|
||||
pub mod address;
|
||||
|
||||
use crate::{Error, Metadata, error::MetadataError, metadata::DecodeWithMetadata};
|
||||
use crate::{Metadata, error::StorageError};
|
||||
use address::Address;
|
||||
use alloc::vec::Vec;
|
||||
use alloc::string::ToString;
|
||||
|
||||
// This isn't a part of the public API, but expose here because it's useful in Subxt.
|
||||
#[doc(hidden)]
|
||||
pub use utils::lookup_storage_entry_details;
|
||||
pub use prefix_of::{EqualOrPrefixOf, PrefixOf};
|
||||
pub use storage_entry::{StorageEntry, entry};
|
||||
pub use storage_key::{StorageHasher, StorageKey, StorageKeyPart};
|
||||
pub use storage_key_value::StorageKeyValue;
|
||||
pub use storage_value::StorageValue;
|
||||
|
||||
/// When the provided `address` is statically generated via the `#[subxt]` macro, this validates
|
||||
/// that the shape of the storage value is the same as the shape expected by the static address.
|
||||
///
|
||||
/// When the provided `address` is dynamic (and thus does not come with any expectation of the
|
||||
/// shape of the constant value), this just returns `Ok(())`
|
||||
pub fn validate<Addr: Address>(address: &Addr, metadata: &Metadata) -> Result<(), Error> {
|
||||
pub fn validate<Addr: Address>(address: Addr, metadata: &Metadata) -> Result<(), StorageError> {
|
||||
let Some(hash) = address.validation_hash() else {
|
||||
return Ok(());
|
||||
};
|
||||
@@ -67,76 +76,19 @@ pub fn validate<Addr: Address>(address: &Addr, metadata: &Metadata) -> Result<()
|
||||
let pallet_name = address.pallet_name();
|
||||
let entry_name = address.entry_name();
|
||||
|
||||
let pallet_metadata = metadata.pallet_by_name_err(pallet_name)?;
|
||||
let pallet_metadata = metadata
|
||||
.pallet_by_name(pallet_name)
|
||||
.ok_or_else(|| StorageError::PalletNameNotFound(pallet_name.to_string()))?;
|
||||
let storage_hash = pallet_metadata.storage_hash(entry_name).ok_or_else(|| {
|
||||
StorageError::StorageEntryNotFound {
|
||||
pallet_name: pallet_name.to_string(),
|
||||
entry_name: entry_name.to_string(),
|
||||
}
|
||||
})?;
|
||||
|
||||
let Some(expected_hash) = pallet_metadata.storage_hash(entry_name) else {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
};
|
||||
if expected_hash != hash {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
if storage_hash != hash {
|
||||
Err(StorageError::IncompatibleCodegen)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Given a storage address and some metadata, this encodes the address into bytes which can be
|
||||
/// handed to a node to retrieve the corresponding value.
|
||||
pub fn get_address_bytes<Addr: Address>(
|
||||
address: &Addr,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
let mut bytes = Vec::new();
|
||||
utils::write_storage_address_root_bytes(address, &mut bytes);
|
||||
address.append_entry_bytes(metadata, &mut bytes)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
/// Given a storage address and some metadata, this encodes the root of the address (ie the pallet
|
||||
/// and storage entry part) into bytes. If the entry being addressed is inside a map, this returns
|
||||
/// the bytes needed to iterate over all of the entries within it.
|
||||
pub fn get_address_root_bytes<Addr: Address>(address: &Addr) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
utils::write_storage_address_root_bytes(address, &mut bytes);
|
||||
bytes
|
||||
}
|
||||
|
||||
/// Given some storage value that we've retrieved from a node, the address used to retrieve it, and
|
||||
/// metadata from the node, this function attempts to decode the bytes into the target value specified
|
||||
/// by the address.
|
||||
pub fn decode_value<Addr: Address>(
|
||||
bytes: &mut &[u8],
|
||||
address: &Addr,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Addr::Target, Error> {
|
||||
let pallet_name = address.pallet_name();
|
||||
let entry_name = address.entry_name();
|
||||
|
||||
let (_, entry_metadata) =
|
||||
utils::lookup_storage_entry_details(pallet_name, entry_name, metadata)?;
|
||||
let value_ty_id = match entry_metadata.entry_type() {
|
||||
subxt_metadata::StorageEntryType::Plain(ty) => *ty,
|
||||
subxt_metadata::StorageEntryType::Map { value_ty, .. } => *value_ty,
|
||||
};
|
||||
|
||||
let val = Addr::Target::decode_with_metadata(bytes, value_ty_id, metadata)?;
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
/// Return the default value at a given storage address if one is available, or an error otherwise.
|
||||
pub fn default_value<Addr: Address>(
|
||||
address: &Addr,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Addr::Target, Error> {
|
||||
let pallet_name = address.pallet_name();
|
||||
let entry_name = address.entry_name();
|
||||
|
||||
let (_, entry_metadata) =
|
||||
utils::lookup_storage_entry_details(pallet_name, entry_name, metadata)?;
|
||||
let value_ty_id = match entry_metadata.entry_type() {
|
||||
subxt_metadata::StorageEntryType::Plain(ty) => *ty,
|
||||
subxt_metadata::StorageEntryType::Map { value_ty, .. } => *value_ty,
|
||||
};
|
||||
|
||||
let default_bytes = entry_metadata.default_bytes();
|
||||
let val = Addr::Target::decode_with_metadata(&mut &*default_bytes, value_ty_id, metadata)?;
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
// 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 alloc::vec::Vec;
|
||||
use frame_decode::helpers::IntoEncodableValues;
|
||||
use scale_encode::EncodeAsType;
|
||||
|
||||
/// For a given set of values that can be used as keys for a storage entry,
|
||||
/// this is implemented for any prefixes of that set. ie if the keys `(A,B,C)`
|
||||
/// would access a storage value, then `PrefixOf<(A,B,C)>` is implemented for
|
||||
/// `(A,B)`, `(A,)` and `()`.
|
||||
pub trait PrefixOf<Keys>: IntoEncodableValues {}
|
||||
|
||||
// If T impls PrefixOf<K>, &T impls PrefixOf<K>.
|
||||
impl<K, T: PrefixOf<K>> PrefixOf<K> for &T {}
|
||||
|
||||
// Impls for tuples up to length 6 (storage maps rarely require more than 2 entries
|
||||
// so it's very unlikely we'll ever need to go this deep).
|
||||
impl<A> PrefixOf<(A,)> for () {}
|
||||
|
||||
impl<A, B> PrefixOf<(A, B)> for () {}
|
||||
impl<A, B> PrefixOf<(A, B)> for (A,) where (A,): IntoEncodableValues {}
|
||||
|
||||
impl<A, B, C> PrefixOf<(A, B, C)> for () {}
|
||||
impl<A, B, C> PrefixOf<(A, B, C)> for (A,) where (A,): IntoEncodableValues {}
|
||||
impl<A, B, C> PrefixOf<(A, B, C)> for (A, B) where (A, B): IntoEncodableValues {}
|
||||
|
||||
impl<A, B, C, D> PrefixOf<(A, B, C, D)> for () {}
|
||||
impl<A, B, C, D> PrefixOf<(A, B, C, D)> for (A,) where (A,): IntoEncodableValues {}
|
||||
impl<A, B, C, D> PrefixOf<(A, B, C, D)> for (A, B) where (A, B): IntoEncodableValues {}
|
||||
impl<A, B, C, D> PrefixOf<(A, B, C, D)> for (A, B, C) where (A, B, C): IntoEncodableValues {}
|
||||
|
||||
impl<A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for () {}
|
||||
impl<A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for (A,) where (A,): IntoEncodableValues {}
|
||||
impl<A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for (A, B) where (A, B): IntoEncodableValues {}
|
||||
impl<A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for (A, B, C) where (A, B, C): IntoEncodableValues {}
|
||||
impl<A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for (A, B, C, D) where
|
||||
(A, B, C, D): IntoEncodableValues
|
||||
{
|
||||
}
|
||||
|
||||
impl<A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for () {}
|
||||
impl<A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A,) where (A,): IntoEncodableValues {}
|
||||
impl<A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A, B) where (A, B): IntoEncodableValues {}
|
||||
impl<A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A, B, C) where
|
||||
(A, B, C): IntoEncodableValues
|
||||
{
|
||||
}
|
||||
impl<A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A, B, C, D) where
|
||||
(A, B, C, D): IntoEncodableValues
|
||||
{
|
||||
}
|
||||
impl<A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A, B, C, D, E) where
|
||||
(A, B, C, D, E): IntoEncodableValues
|
||||
{
|
||||
}
|
||||
|
||||
// Vecs are prefixes of vecs. The length is not statically known and so
|
||||
// these would be given dynamically only, leaving the correct length to the user.
|
||||
impl<T: EncodeAsType> PrefixOf<Vec<T>> for Vec<T> {}
|
||||
|
||||
// We don't use arrays in Subxt for storage entry access, but `IntoEncodableValues`
|
||||
// supports them so let's allow impls which do use them to benefit too.
|
||||
macro_rules! array_impl {
|
||||
($n:literal: $($p:literal)+) => {
|
||||
$(
|
||||
impl <T: EncodeAsType> PrefixOf<[T; $n]> for [T; $p] {}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
array_impl!(1: 0);
|
||||
array_impl!(2: 1 0);
|
||||
array_impl!(3: 2 1 0);
|
||||
array_impl!(4: 3 2 1 0);
|
||||
array_impl!(5: 4 3 2 1 0);
|
||||
array_impl!(6: 5 4 3 2 1 0);
|
||||
|
||||
/// This is much like [`PrefixOf`] except that it also includes `Self` as an allowed type,
|
||||
/// where `Self` must impl [`IntoEncodableValues`] just as every [`PrefixOf<Self>`] does.
|
||||
pub trait EqualOrPrefixOf<K>: IntoEncodableValues {}
|
||||
|
||||
// Tuples
|
||||
macro_rules! tuple_impl_eq {
|
||||
($($t:ident)+) => {
|
||||
// Any T that is a PrefixOf<Keys> impls EqualOrPrefixOf<keys> too
|
||||
impl <$($t,)+ T: PrefixOf<($($t,)+)>> EqualOrPrefixOf<($($t,)+)> for T {}
|
||||
// Keys impls EqualOrPrefixOf<Keys>
|
||||
impl <$($t),+> EqualOrPrefixOf<($($t,)+)> for ($($t,)+) where ($($t,)+): IntoEncodableValues {}
|
||||
// &'a Keys impls EqualOrPrefixOf<Keys>
|
||||
impl <'a, $($t),+> EqualOrPrefixOf<($($t,)+)> for &'a ($($t,)+) where ($($t,)+): IntoEncodableValues {}
|
||||
}
|
||||
}
|
||||
|
||||
tuple_impl_eq!(A);
|
||||
tuple_impl_eq!(A B);
|
||||
tuple_impl_eq!(A B C);
|
||||
tuple_impl_eq!(A B C D);
|
||||
tuple_impl_eq!(A B C D E);
|
||||
tuple_impl_eq!(A B C D E F);
|
||||
|
||||
// Vec
|
||||
impl<T: EncodeAsType> EqualOrPrefixOf<Vec<T>> for Vec<T> {}
|
||||
impl<T: EncodeAsType> EqualOrPrefixOf<Vec<T>> for &Vec<T> {}
|
||||
|
||||
// Arrays
|
||||
macro_rules! array_impl_eq {
|
||||
($($n:literal)+) => {
|
||||
$(
|
||||
impl <A: EncodeAsType> EqualOrPrefixOf<[A; $n]> for [A; $n] {}
|
||||
impl <'a, A: EncodeAsType> EqualOrPrefixOf<[A; $n]> for &'a [A; $n] {}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize, A, T> EqualOrPrefixOf<[A; N]> for T where T: PrefixOf<[A; N]> {}
|
||||
array_impl_eq!(1 2 3 4 5 6);
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
struct Test<Keys: IntoEncodableValues>(core::marker::PhantomData<Keys>);
|
||||
|
||||
impl<Keys: IntoEncodableValues> Test<Keys> {
|
||||
fn new() -> Self {
|
||||
Test(core::marker::PhantomData)
|
||||
}
|
||||
fn accepts_prefix_of<P: PrefixOf<Keys>>(&self, keys: P) {
|
||||
let _encoder = keys.into_encodable_values();
|
||||
}
|
||||
fn accepts_eq_or_prefix_of<P: EqualOrPrefixOf<Keys>>(&self, keys: P) {
|
||||
let _encoder = keys.into_encodable_values();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prefix_of() {
|
||||
// In real life we'd have a struct a bit like this:
|
||||
let t = Test::<(bool, String, u64)>::new();
|
||||
|
||||
// And we'd want to be able to call some method like this:
|
||||
//// This shouldn't work:
|
||||
// t.accepts_prefix_of((true, String::from("hi"), 0));
|
||||
t.accepts_prefix_of(&(true, String::from("hi")));
|
||||
t.accepts_prefix_of((true, String::from("hi")));
|
||||
t.accepts_prefix_of((true,));
|
||||
t.accepts_prefix_of(());
|
||||
|
||||
let t = Test::<[u64; 5]>::new();
|
||||
|
||||
//// This shouldn't work:
|
||||
// t.accepts_prefix_of([0,1,2,3,4]);
|
||||
t.accepts_prefix_of([0, 1, 2, 3]);
|
||||
t.accepts_prefix_of([0, 1, 2, 3]);
|
||||
t.accepts_prefix_of([0, 1, 2]);
|
||||
t.accepts_prefix_of([0, 1]);
|
||||
t.accepts_prefix_of([0]);
|
||||
t.accepts_prefix_of([]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eq_or_prefix_of() {
|
||||
// In real life we'd have a struct a bit like this:
|
||||
let t = Test::<(bool, String, u64)>::new();
|
||||
|
||||
// And we'd want to be able to call some method like this:
|
||||
t.accepts_eq_or_prefix_of(&(true, String::from("hi"), 0));
|
||||
t.accepts_eq_or_prefix_of(&(true, String::from("hi")));
|
||||
t.accepts_eq_or_prefix_of((true,));
|
||||
t.accepts_eq_or_prefix_of(());
|
||||
|
||||
t.accepts_eq_or_prefix_of((true, String::from("hi"), 0));
|
||||
t.accepts_eq_or_prefix_of((true, String::from("hi")));
|
||||
t.accepts_eq_or_prefix_of((true,));
|
||||
t.accepts_eq_or_prefix_of(());
|
||||
|
||||
let t = Test::<[u64; 5]>::new();
|
||||
|
||||
t.accepts_eq_or_prefix_of([0, 1, 2, 3, 4]);
|
||||
t.accepts_eq_or_prefix_of([0, 1, 2, 3]);
|
||||
t.accepts_eq_or_prefix_of([0, 1, 2]);
|
||||
t.accepts_eq_or_prefix_of([0, 1]);
|
||||
t.accepts_eq_or_prefix_of([0]);
|
||||
t.accepts_eq_or_prefix_of([]);
|
||||
|
||||
t.accepts_eq_or_prefix_of([0, 1, 2, 3, 4]);
|
||||
t.accepts_eq_or_prefix_of([0, 1, 2, 3]);
|
||||
t.accepts_eq_or_prefix_of([0, 1, 2]);
|
||||
t.accepts_eq_or_prefix_of([0, 1]);
|
||||
t.accepts_eq_or_prefix_of([0]);
|
||||
t.accepts_eq_or_prefix_of([]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
// 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::{PrefixOf, StorageKeyValue, StorageValue, address::Address};
|
||||
use crate::error::StorageError;
|
||||
use crate::utils::YesMaybe;
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec::Vec;
|
||||
use frame_decode::storage::{IntoEncodableValues, StorageInfo};
|
||||
use scale_info::PortableRegistry;
|
||||
use subxt_metadata::Metadata;
|
||||
|
||||
/// Create a [`StorageEntry`] to work with a given storage entry.
|
||||
pub fn entry<'info, Addr: Address>(
|
||||
address: Addr,
|
||||
metadata: &'info Metadata,
|
||||
) -> Result<StorageEntry<'info, Addr>, StorageError> {
|
||||
super::validate(&address, metadata)?;
|
||||
|
||||
use frame_decode::storage::StorageTypeInfo;
|
||||
let types = metadata.types();
|
||||
let info = metadata
|
||||
.storage_info(address.pallet_name(), address.entry_name())
|
||||
.map_err(|e| StorageError::StorageInfoError(e.into_owned()))?;
|
||||
|
||||
Ok(StorageEntry(Arc::new(StorageEntryInner {
|
||||
address,
|
||||
info: Arc::new(info),
|
||||
types,
|
||||
})))
|
||||
}
|
||||
|
||||
/// This represents a single storage entry (be it a plain value or map).
|
||||
pub struct StorageEntry<'info, Addr>(Arc<StorageEntryInner<'info, Addr>>);
|
||||
|
||||
impl<'info, Addr> Clone for StorageEntry<'info, Addr> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
struct StorageEntryInner<'info, Addr> {
|
||||
address: Addr,
|
||||
info: Arc<StorageInfo<'info, u32>>,
|
||||
types: &'info PortableRegistry,
|
||||
}
|
||||
|
||||
impl<'info, Addr: Address> StorageEntry<'info, Addr> {
|
||||
/// Name of the pallet containing this storage entry.
|
||||
pub fn pallet_name(&self) -> &str {
|
||||
self.0.address.pallet_name()
|
||||
}
|
||||
|
||||
/// Name of the storage entry.
|
||||
pub fn entry_name(&self) -> &str {
|
||||
self.0.address.entry_name()
|
||||
}
|
||||
|
||||
/// Is the storage entry a plain value?
|
||||
pub fn is_plain(&self) -> bool {
|
||||
self.0.info.keys.is_empty()
|
||||
}
|
||||
|
||||
/// Is the storage entry a map?
|
||||
pub fn is_map(&self) -> bool {
|
||||
!self.is_plain()
|
||||
}
|
||||
|
||||
/// Instantiate a [`StorageKeyValue`] for this entry.
|
||||
///
|
||||
/// It is expected that the bytes are obtained by iterating key/value pairs at this address.
|
||||
pub fn key_value(
|
||||
&self,
|
||||
key_bytes: impl Into<Arc<[u8]>>,
|
||||
value_bytes: Vec<u8>,
|
||||
) -> StorageKeyValue<'info, Addr> {
|
||||
StorageKeyValue::new(
|
||||
self.0.info.clone(),
|
||||
self.0.types,
|
||||
key_bytes.into(),
|
||||
value_bytes,
|
||||
)
|
||||
}
|
||||
|
||||
/// Instantiate a [`StorageValue`] for this entry.
|
||||
///
|
||||
/// It is expected that the bytes are obtained by fetching a value at this address.
|
||||
pub fn value(&self, bytes: Vec<u8>) -> StorageValue<'info, Addr::Value> {
|
||||
StorageValue::new(self.0.info.clone(), self.0.types, bytes)
|
||||
}
|
||||
|
||||
/// Return the default [`StorageValue`] for this storage entry, if there is one.
|
||||
pub fn default_value(&self) -> Option<StorageValue<'info, Addr::Value>> {
|
||||
self.0.info.default_value.as_deref().map(|default_bytes| {
|
||||
StorageValue::new(self.0.info.clone(), self.0.types, default_bytes.to_vec())
|
||||
})
|
||||
}
|
||||
|
||||
/// The keys for plain storage values are always 32 byte hashes.
|
||||
pub fn key_prefix(&self) -> [u8; 32] {
|
||||
frame_decode::storage::encode_storage_key_prefix(
|
||||
self.0.address.pallet_name(),
|
||||
self.0.address.entry_name(),
|
||||
)
|
||||
}
|
||||
|
||||
// This has a less "strict" type signature and so is just used under the hood.
|
||||
fn key<Keys: IntoEncodableValues>(&self, key_parts: Keys) -> Result<Vec<u8>, StorageError> {
|
||||
let key = frame_decode::storage::encode_storage_key_with_info(
|
||||
self.0.address.pallet_name(),
|
||||
self.0.address.entry_name(),
|
||||
key_parts,
|
||||
&self.0.info,
|
||||
self.0.types,
|
||||
)
|
||||
.map_err(StorageError::StorageKeyEncodeError)?;
|
||||
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
/// This constructs a key suitable for fetching a value at the given map storage address. This will error
|
||||
/// if we can see that the wrong number of key parts are provided.
|
||||
pub fn fetch_key(&self, key_parts: Addr::KeyParts) -> Result<Vec<u8>, StorageError> {
|
||||
if key_parts.num_encodable_values() != self.0.info.keys.len() {
|
||||
Err(StorageError::WrongNumberOfKeyPartsProvidedForFetching {
|
||||
expected: self.0.info.keys.len(),
|
||||
got: key_parts.num_encodable_values(),
|
||||
})
|
||||
} else {
|
||||
self.key(key_parts)
|
||||
}
|
||||
}
|
||||
|
||||
/// This constructs a key suitable for iterating at the given storage address. This will error
|
||||
/// if we can see that too many key parts are provided.
|
||||
pub fn iter_key<Keys: PrefixOf<Addr::KeyParts>>(
|
||||
&self,
|
||||
key_parts: Keys,
|
||||
) -> Result<Vec<u8>, StorageError> {
|
||||
if Addr::IsPlain::is_yes() {
|
||||
Err(StorageError::CannotIterPlainEntry {
|
||||
pallet_name: self.0.address.pallet_name().into(),
|
||||
entry_name: self.0.address.entry_name().into(),
|
||||
})
|
||||
} else if key_parts.num_encodable_values() >= self.0.info.keys.len() {
|
||||
Err(StorageError::WrongNumberOfKeyPartsProvidedForIterating {
|
||||
max_expected: self.0.info.keys.len() - 1,
|
||||
got: key_parts.num_encodable_values(),
|
||||
})
|
||||
} else {
|
||||
self.key(key_parts)
|
||||
}
|
||||
}
|
||||
}
|
||||
+112
-445
@@ -1,471 +1,138 @@
|
||||
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
|
||||
// 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::utils::hash_bytes;
|
||||
use crate::error::{Error, MetadataError, StorageAddressError};
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use scale_decode::{DecodeAsType, visitor::IgnoreVisitor};
|
||||
use scale_encode::EncodeAsType;
|
||||
use scale_info::{PortableRegistry, TypeDef};
|
||||
use scale_value::Value;
|
||||
use subxt_metadata::{StorageEntryType, StorageHasher};
|
||||
use crate::error::StorageKeyError;
|
||||
use alloc::sync::Arc;
|
||||
use core::marker::PhantomData;
|
||||
use frame_decode::storage::{IntoDecodableValues, StorageInfo, StorageKey as StorageKeyPartInfo};
|
||||
use scale_info::PortableRegistry;
|
||||
|
||||
/// A collection of storage hashers paired with the type ids of the types they should hash.
|
||||
/// Can be created for each storage entry in the metadata via [`StorageHashers::new()`].
|
||||
#[derive(Debug)]
|
||||
pub struct StorageHashers {
|
||||
hashers_and_ty_ids: Vec<(StorageHasher, u32)>,
|
||||
pub use frame_decode::storage::StorageHasher;
|
||||
|
||||
/// This represents the different parts of a storage key.
|
||||
pub struct StorageKey<'info, KeyParts> {
|
||||
info: Arc<StorageKeyPartInfo<u32>>,
|
||||
types: &'info PortableRegistry,
|
||||
bytes: Arc<[u8]>,
|
||||
marker: PhantomData<KeyParts>,
|
||||
}
|
||||
|
||||
impl StorageHashers {
|
||||
/// Creates new [`StorageHashers`] from a storage entry. Looks at the [`StorageEntryType`] and
|
||||
/// assigns a hasher to each type id that makes up the key.
|
||||
pub fn new(storage_entry: &StorageEntryType, types: &PortableRegistry) -> Result<Self, Error> {
|
||||
let mut hashers_and_ty_ids = vec![];
|
||||
if let StorageEntryType::Map {
|
||||
hashers, key_ty, ..
|
||||
} = storage_entry
|
||||
{
|
||||
let ty = types
|
||||
.resolve(*key_ty)
|
||||
.ok_or(MetadataError::TypeNotFound(*key_ty))?;
|
||||
impl<'info, KeyParts: IntoDecodableValues> StorageKey<'info, KeyParts> {
|
||||
pub(crate) fn new(
|
||||
info: &StorageInfo<'info, u32>,
|
||||
types: &'info PortableRegistry,
|
||||
bytes: Arc<[u8]>,
|
||||
) -> Result<Self, StorageKeyError> {
|
||||
let cursor = &mut &*bytes;
|
||||
let storage_key_info = frame_decode::storage::decode_storage_key_with_info(
|
||||
cursor, info, types,
|
||||
)
|
||||
.map_err(|e| StorageKeyError::StorageKeyDecodeError {
|
||||
bytes: bytes.to_vec(),
|
||||
error: e,
|
||||
})?;
|
||||
|
||||
if hashers.len() == 1 {
|
||||
// If there's exactly 1 hasher, then we have a plain StorageMap. We can't
|
||||
// break the key down (even if it's a tuple) because the hasher applies to
|
||||
// the whole key.
|
||||
hashers_and_ty_ids = vec![(hashers[0], *key_ty)];
|
||||
} else {
|
||||
// If there are multiple hashers, then we have a StorageDoubleMap or StorageNMap.
|
||||
// We expect the key type to be tuple, and we will return a MapEntryKey for each
|
||||
// key in the tuple.
|
||||
let hasher_count = hashers.len();
|
||||
let tuple = match &ty.type_def {
|
||||
TypeDef::Tuple(tuple) => tuple,
|
||||
_ => {
|
||||
return Err(StorageAddressError::WrongNumberOfHashers {
|
||||
hashers: hasher_count,
|
||||
fields: 1,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
};
|
||||
|
||||
// We should have the same number of hashers and keys.
|
||||
let key_count = tuple.fields.len();
|
||||
if hasher_count != key_count {
|
||||
return Err(StorageAddressError::WrongNumberOfHashers {
|
||||
hashers: hasher_count,
|
||||
fields: key_count,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
// Collect them together.
|
||||
hashers_and_ty_ids = tuple
|
||||
.fields
|
||||
.iter()
|
||||
.zip(hashers)
|
||||
.map(|(field, hasher)| (*hasher, field.id))
|
||||
.collect();
|
||||
}
|
||||
if !cursor.is_empty() {
|
||||
return Err(StorageKeyError::LeftoverBytes {
|
||||
bytes: cursor.to_vec(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Self { hashers_and_ty_ids })
|
||||
}
|
||||
|
||||
/// Creates an iterator over the storage hashers and type ids.
|
||||
pub fn iter(&self) -> StorageHashersIter<'_> {
|
||||
StorageHashersIter {
|
||||
hashers: self,
|
||||
idx: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over all type ids of the key and the respective hashers.
|
||||
/// See [`StorageHashers::iter()`].
|
||||
#[derive(Debug)]
|
||||
pub struct StorageHashersIter<'a> {
|
||||
hashers: &'a StorageHashers,
|
||||
idx: usize,
|
||||
}
|
||||
|
||||
impl StorageHashersIter<'_> {
|
||||
fn next_or_err(&mut self) -> Result<(StorageHasher, u32), Error> {
|
||||
self.next().ok_or_else(|| {
|
||||
StorageAddressError::TooManyKeys {
|
||||
expected: self.hashers.hashers_and_ty_ids.len(),
|
||||
}
|
||||
.into()
|
||||
Ok(StorageKey {
|
||||
info: Arc::new(storage_key_info),
|
||||
types,
|
||||
bytes,
|
||||
marker: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for StorageHashersIter<'_> {
|
||||
type Item = (StorageHasher, u32);
|
||||
/// Attempt to decode the values contained within this storage key. The target type is
|
||||
/// given by the storage address used to access this entry. To decode into a custom type,
|
||||
/// use [`Self::parts()`] or [`Self::part()`] and decode each part.
|
||||
pub fn decode(&self) -> Result<KeyParts, StorageKeyError> {
|
||||
let values =
|
||||
frame_decode::storage::decode_storage_key_values(&self.bytes, &self.info, self.types)
|
||||
.map_err(StorageKeyError::CannotDecodeValuesInKey)?;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let item = self.hashers.hashers_and_ty_ids.get(self.idx).copied()?;
|
||||
self.idx += 1;
|
||||
Some(item)
|
||||
Ok(values)
|
||||
}
|
||||
|
||||
/// Iterate over the parts of this storage key. Each part of a storage key corresponds to a
|
||||
/// single value that has been hashed.
|
||||
pub fn parts(&self) -> impl ExactSizeIterator<Item = StorageKeyPart<'info>> {
|
||||
let parts_len = self.info.parts().len();
|
||||
(0..parts_len).map(move |index| StorageKeyPart {
|
||||
index,
|
||||
info: self.info.clone(),
|
||||
types: self.types,
|
||||
bytes: self.bytes.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the part of the storage key at the provided index, or `None` if the index is out of bounds.
|
||||
pub fn part(&self, index: usize) -> Option<StorageKeyPart<'info>> {
|
||||
if index < self.parts().len() {
|
||||
Some(StorageKeyPart {
|
||||
index,
|
||||
info: self.info.clone(),
|
||||
types: self.types,
|
||||
bytes: self.bytes.clone(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for StorageHashersIter<'_> {
|
||||
fn len(&self) -> usize {
|
||||
self.hashers.hashers_and_ty_ids.len() - self.idx
|
||||
}
|
||||
/// This represents a part of a storage key.
|
||||
pub struct StorageKeyPart<'info> {
|
||||
index: usize,
|
||||
info: Arc<StorageKeyPartInfo<u32>>,
|
||||
types: &'info PortableRegistry,
|
||||
bytes: Arc<[u8]>,
|
||||
}
|
||||
|
||||
/// This trait should be implemented by anything that can be used as one or multiple storage keys.
|
||||
pub trait StorageKey {
|
||||
/// Encodes the storage key into some bytes
|
||||
fn encode_storage_key(
|
||||
&self,
|
||||
bytes: &mut Vec<u8>,
|
||||
hashers: &mut StorageHashersIter,
|
||||
types: &PortableRegistry,
|
||||
) -> Result<(), Error>;
|
||||
|
||||
/// Attempts to decode the StorageKey given some bytes and a set of hashers and type IDs that they are meant to represent.
|
||||
/// The bytes passed to `decode` should start with:
|
||||
/// - 1. some fixed size hash (for all hashers except `Identity`)
|
||||
/// - 2. the plain key value itself (for `Identity`, `Blake2_128Concat` and `Twox64Concat` hashers)
|
||||
fn decode_storage_key(
|
||||
bytes: &mut &[u8],
|
||||
hashers: &mut StorageHashersIter,
|
||||
types: &PortableRegistry,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized + 'static;
|
||||
}
|
||||
|
||||
/// Implement `StorageKey` for `()` which can be used for keyless storage entries,
|
||||
/// or to otherwise just ignore some entry.
|
||||
impl StorageKey for () {
|
||||
fn encode_storage_key(
|
||||
&self,
|
||||
_bytes: &mut Vec<u8>,
|
||||
hashers: &mut StorageHashersIter,
|
||||
_types: &PortableRegistry,
|
||||
) -> Result<(), Error> {
|
||||
_ = hashers.next_or_err();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decode_storage_key(
|
||||
bytes: &mut &[u8],
|
||||
hashers: &mut StorageHashersIter,
|
||||
types: &PortableRegistry,
|
||||
) -> Result<Self, Error> {
|
||||
let (hasher, ty_id) = match hashers.next_or_err() {
|
||||
Ok((hasher, ty_id)) => (hasher, ty_id),
|
||||
Err(_) if bytes.is_empty() => return Ok(()),
|
||||
Err(err) => return Err(err),
|
||||
impl<'info> StorageKeyPart<'info> {
|
||||
/// Get the raw bytes for this part of the storage key.
|
||||
pub fn bytes(&self) -> &[u8] {
|
||||
let part = &self.info[self.index];
|
||||
let hash_range = part.hash_range();
|
||||
let value_range = part.value().map(|v| v.range()).unwrap_or(core::ops::Range {
|
||||
start: hash_range.end,
|
||||
end: hash_range.end,
|
||||
});
|
||||
let combined_range = core::ops::Range {
|
||||
start: hash_range.start,
|
||||
end: value_range.end,
|
||||
};
|
||||
consume_hash_returning_key_bytes(bytes, hasher, ty_id, types)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A storage key used as part of the static codegen.
|
||||
#[derive(Clone, Debug, PartialOrd, PartialEq, Eq)]
|
||||
pub struct StaticStorageKey<K> {
|
||||
key: K,
|
||||
}
|
||||
|
||||
impl<K> StaticStorageKey<K> {
|
||||
/// Creates a new static storage key.
|
||||
pub fn new(key: K) -> Self {
|
||||
StaticStorageKey { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Clone> StaticStorageKey<K> {
|
||||
/// Returns the decoded storage key.
|
||||
pub fn into_key(self) -> K {
|
||||
self.key
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: EncodeAsType + DecodeAsType> StorageKey for StaticStorageKey<K> {
|
||||
fn encode_storage_key(
|
||||
&self,
|
||||
bytes: &mut Vec<u8>,
|
||||
hashers: &mut StorageHashersIter,
|
||||
types: &PortableRegistry,
|
||||
) -> Result<(), Error> {
|
||||
let (hasher, ty_id) = hashers.next_or_err()?;
|
||||
let encoded_value = self.key.encode_as_type(ty_id, types)?;
|
||||
hash_bytes(&encoded_value, hasher, bytes);
|
||||
Ok(())
|
||||
&self.bytes[combined_range]
|
||||
}
|
||||
|
||||
fn decode_storage_key(
|
||||
bytes: &mut &[u8],
|
||||
hashers: &mut StorageHashersIter,
|
||||
types: &PortableRegistry,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
let (hasher, ty_id) = hashers.next_or_err()?;
|
||||
let key_bytes = consume_hash_returning_key_bytes(bytes, hasher, ty_id, types)?;
|
||||
/// Get the hasher that was used to construct this part of the storage key.
|
||||
pub fn hasher(&self) -> StorageHasher {
|
||||
self.info[self.index].hasher()
|
||||
}
|
||||
|
||||
// if the hasher had no key appended, we can't decode it into a `StaticStorageKey`.
|
||||
let Some(key_bytes) = key_bytes else {
|
||||
return Err(StorageAddressError::HasherCannotReconstructKey { ty_id, hasher }.into());
|
||||
/// For keys that were produced using "concat" or "identity" hashers, the value
|
||||
/// is available as a part of the key hash, allowing us to decode it into anything
|
||||
/// implementing [`scale_decode::DecodeAsType`]. If the key was produced using a
|
||||
/// different hasher, this will return `None`.
|
||||
pub fn decode_as<T: scale_decode::DecodeAsType>(&self) -> Result<Option<T>, StorageKeyError> {
|
||||
let part_info = &self.info[self.index];
|
||||
let Some(value_info) = part_info.value() else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// Decode and return the key.
|
||||
let key = K::decode_as_type(&mut &*key_bytes, ty_id, types)?;
|
||||
let key = StaticStorageKey { key };
|
||||
Ok(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl StorageKey for Vec<scale_value::Value> {
|
||||
fn encode_storage_key(
|
||||
&self,
|
||||
bytes: &mut Vec<u8>,
|
||||
hashers: &mut StorageHashersIter,
|
||||
types: &PortableRegistry,
|
||||
) -> Result<(), Error> {
|
||||
for value in self.iter() {
|
||||
let (hasher, ty_id) = hashers.next_or_err()?;
|
||||
let encoded_value = value.encode_as_type(ty_id, types)?;
|
||||
hash_bytes(&encoded_value, hasher, bytes);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decode_storage_key(
|
||||
bytes: &mut &[u8],
|
||||
hashers: &mut StorageHashersIter,
|
||||
types: &PortableRegistry,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
let mut result: Vec<scale_value::Value> = vec![];
|
||||
for (hasher, ty_id) in hashers.by_ref() {
|
||||
match consume_hash_returning_key_bytes(bytes, hasher, ty_id, types)? {
|
||||
Some(value_bytes) => {
|
||||
let value =
|
||||
scale_value::scale::decode_as_type(&mut &*value_bytes, ty_id, types)?;
|
||||
|
||||
result.push(value.remove_context());
|
||||
}
|
||||
None => {
|
||||
result.push(Value::unnamed_composite([]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We've consumed all of the hashers, so we expect to also consume all of the bytes:
|
||||
if !bytes.is_empty() {
|
||||
return Err(StorageAddressError::TooManyBytes.into());
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
// Skip over the hash bytes (including any key at the end), returning bytes
|
||||
// representing the key if one exists, or None if the hasher has no key appended.
|
||||
fn consume_hash_returning_key_bytes<'a>(
|
||||
bytes: &mut &'a [u8],
|
||||
hasher: StorageHasher,
|
||||
ty_id: u32,
|
||||
types: &PortableRegistry,
|
||||
) -> Result<Option<&'a [u8]>, Error> {
|
||||
// Strip the bytes off for the actual hash, consuming them.
|
||||
let bytes_to_strip = hasher.len_excluding_key();
|
||||
if bytes.len() < bytes_to_strip {
|
||||
return Err(StorageAddressError::NotEnoughBytes.into());
|
||||
}
|
||||
*bytes = &bytes[bytes_to_strip..];
|
||||
|
||||
// Now, find the bytes representing the key, consuming them.
|
||||
let before_key = *bytes;
|
||||
if hasher.ends_with_key() {
|
||||
scale_decode::visitor::decode_with_visitor(
|
||||
bytes,
|
||||
ty_id,
|
||||
types,
|
||||
IgnoreVisitor::<PortableRegistry>::new(),
|
||||
)
|
||||
.map_err(|err| Error::Decode(err.into()))?;
|
||||
// Return the key bytes, having advanced the input cursor past them.
|
||||
let key_bytes = &before_key[..before_key.len() - bytes.len()];
|
||||
|
||||
Ok(Some(key_bytes))
|
||||
} else {
|
||||
// There are no key bytes, so return None.
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates StorageKey implementations for tuples
|
||||
macro_rules! impl_tuples {
|
||||
($($ty:ident $n:tt),+) => {{
|
||||
impl<$($ty: StorageKey),+> StorageKey for ($( $ty ),+) {
|
||||
fn encode_storage_key(
|
||||
&self,
|
||||
bytes: &mut Vec<u8>,
|
||||
hashers: &mut StorageHashersIter,
|
||||
types: &PortableRegistry,
|
||||
) -> Result<(), Error> {
|
||||
$( self.$n.encode_storage_key(bytes, hashers, types)?; )+
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decode_storage_key(
|
||||
bytes: &mut &[u8],
|
||||
hashers: &mut StorageHashersIter,
|
||||
types: &PortableRegistry,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
Ok( ( $( $ty::decode_storage_key(bytes, hashers, types)?, )+ ) )
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
const _: () = {
|
||||
impl_tuples!(A 0, B 1);
|
||||
impl_tuples!(A 0, B 1, C 2);
|
||||
impl_tuples!(A 0, B 1, C 2, D 3);
|
||||
impl_tuples!(A 0, B 1, C 2, D 3, E 4);
|
||||
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5);
|
||||
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6);
|
||||
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7);
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use codec::Encode;
|
||||
use scale_info::{PortableRegistry, Registry, TypeInfo, meta_type};
|
||||
use subxt_metadata::StorageHasher;
|
||||
|
||||
use crate::utils::Era;
|
||||
|
||||
use alloc::string::String;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::{StaticStorageKey, StorageKey};
|
||||
|
||||
struct KeyBuilder {
|
||||
registry: Registry,
|
||||
bytes: Vec<u8>,
|
||||
hashers_and_ty_ids: Vec<(StorageHasher, u32)>,
|
||||
}
|
||||
|
||||
impl KeyBuilder {
|
||||
fn new() -> KeyBuilder {
|
||||
KeyBuilder {
|
||||
registry: Registry::new(),
|
||||
bytes: vec![],
|
||||
hashers_and_ty_ids: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn add<T: TypeInfo + Encode + 'static>(mut self, value: T, hasher: StorageHasher) -> Self {
|
||||
let id = self.registry.register_type(&meta_type::<T>()).id;
|
||||
|
||||
self.hashers_and_ty_ids.push((hasher, id));
|
||||
for _i in 0..hasher.len_excluding_key() {
|
||||
self.bytes.push(0);
|
||||
}
|
||||
value.encode_to(&mut self.bytes);
|
||||
self
|
||||
}
|
||||
|
||||
fn build(self) -> (PortableRegistry, Vec<u8>, Vec<(StorageHasher, u32)>) {
|
||||
(self.registry.into(), self.bytes, self.hashers_and_ty_ids)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn storage_key_decoding_fuzz() {
|
||||
let hashers = [
|
||||
StorageHasher::Blake2_128,
|
||||
StorageHasher::Blake2_128Concat,
|
||||
StorageHasher::Blake2_256,
|
||||
StorageHasher::Identity,
|
||||
StorageHasher::Twox128,
|
||||
StorageHasher::Twox256,
|
||||
StorageHasher::Twox64Concat,
|
||||
];
|
||||
|
||||
let key_preserving_hashers = [
|
||||
StorageHasher::Blake2_128Concat,
|
||||
StorageHasher::Identity,
|
||||
StorageHasher::Twox64Concat,
|
||||
];
|
||||
|
||||
type T4A = (
|
||||
(),
|
||||
StaticStorageKey<u32>,
|
||||
StaticStorageKey<String>,
|
||||
StaticStorageKey<Era>,
|
||||
);
|
||||
type T4B = (
|
||||
(),
|
||||
(StaticStorageKey<u32>, StaticStorageKey<String>),
|
||||
StaticStorageKey<Era>,
|
||||
);
|
||||
type T4C = (
|
||||
((), StaticStorageKey<u32>),
|
||||
(StaticStorageKey<String>, StaticStorageKey<Era>),
|
||||
);
|
||||
|
||||
let era = Era::Immortal;
|
||||
for h0 in hashers {
|
||||
for h1 in key_preserving_hashers {
|
||||
for h2 in key_preserving_hashers {
|
||||
for h3 in key_preserving_hashers {
|
||||
let (types, bytes, hashers_and_ty_ids) = KeyBuilder::new()
|
||||
.add((), h0)
|
||||
.add(13u32, h1)
|
||||
.add("Hello", h2)
|
||||
.add(era, h3)
|
||||
.build();
|
||||
|
||||
let hashers = super::StorageHashers { hashers_and_ty_ids };
|
||||
let keys_a =
|
||||
T4A::decode_storage_key(&mut &bytes[..], &mut hashers.iter(), &types)
|
||||
.unwrap();
|
||||
|
||||
let keys_b =
|
||||
T4B::decode_storage_key(&mut &bytes[..], &mut hashers.iter(), &types)
|
||||
.unwrap();
|
||||
|
||||
let keys_c =
|
||||
T4C::decode_storage_key(&mut &bytes[..], &mut hashers.iter(), &types)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(keys_a.1.into_key(), 13);
|
||||
assert_eq!(keys_b.1.0.into_key(), 13);
|
||||
assert_eq!(keys_c.0.1.into_key(), 13);
|
||||
|
||||
assert_eq!(keys_a.2.into_key(), "Hello");
|
||||
assert_eq!(keys_b.1.1.into_key(), "Hello");
|
||||
assert_eq!(keys_c.1.0.into_key(), "Hello");
|
||||
assert_eq!(keys_a.3.into_key(), era);
|
||||
assert_eq!(keys_b.2.into_key(), era);
|
||||
assert_eq!(keys_c.1.1.into_key(), era);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let value_bytes = &self.bytes[value_info.range()];
|
||||
let value_ty = *value_info.ty();
|
||||
|
||||
let decoded_key_part = T::decode_as_type(&mut &*value_bytes, value_ty, self.types)
|
||||
.map_err(|e| StorageKeyError::CannotDecodeValueInKey {
|
||||
index: self.index,
|
||||
error: e,
|
||||
})?;
|
||||
|
||||
Ok(Some(decoded_key_part))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// 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::{Address, StorageKey, StorageValue};
|
||||
use crate::error::StorageKeyError;
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec::Vec;
|
||||
use frame_decode::storage::StorageInfo;
|
||||
use scale_info::PortableRegistry;
|
||||
|
||||
/// This represents a storage key/value pair, which is typically returned from
|
||||
/// iterating over values in some storage map.
|
||||
#[derive(Debug)]
|
||||
pub struct StorageKeyValue<'info, Addr: Address> {
|
||||
key: Arc<[u8]>,
|
||||
// This contains the storage information already:
|
||||
value: StorageValue<'info, Addr::Value>,
|
||||
}
|
||||
|
||||
impl<'info, Addr: Address> StorageKeyValue<'info, Addr> {
|
||||
pub(crate) fn new(
|
||||
info: Arc<StorageInfo<'info, u32>>,
|
||||
types: &'info PortableRegistry,
|
||||
key_bytes: Arc<[u8]>,
|
||||
value_bytes: Vec<u8>,
|
||||
) -> Self {
|
||||
StorageKeyValue {
|
||||
key: key_bytes,
|
||||
value: StorageValue::new(info, types, value_bytes),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the raw bytes for this storage entry's key.
|
||||
pub fn key_bytes(&self) -> &[u8] {
|
||||
&self.key
|
||||
}
|
||||
|
||||
/// Decode the key for this storage entry. This gives back a type from which we can
|
||||
/// decode specific parts of the key hash (where applicable).
|
||||
pub fn key(&'_ self) -> Result<StorageKey<'info, Addr::KeyParts>, StorageKeyError> {
|
||||
StorageKey::new(&self.value.info, self.value.types, self.key.clone())
|
||||
}
|
||||
|
||||
/// Return the storage value.
|
||||
pub fn value(&self) -> &StorageValue<'info, Addr::Value> {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// 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 crate::error::StorageValueError;
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec::Vec;
|
||||
use core::marker::PhantomData;
|
||||
use frame_decode::storage::StorageInfo;
|
||||
use scale_decode::DecodeAsType;
|
||||
use scale_info::PortableRegistry;
|
||||
|
||||
/// This represents a storage value.
|
||||
#[derive(Debug)]
|
||||
pub struct StorageValue<'info, Value> {
|
||||
pub(crate) info: Arc<StorageInfo<'info, u32>>,
|
||||
pub(crate) types: &'info PortableRegistry,
|
||||
bytes: Vec<u8>,
|
||||
marker: PhantomData<Value>,
|
||||
}
|
||||
|
||||
impl<'info, Value: DecodeAsType> StorageValue<'info, Value> {
|
||||
pub(crate) fn new(
|
||||
info: Arc<StorageInfo<'info, u32>>,
|
||||
types: &'info PortableRegistry,
|
||||
bytes: Vec<u8>,
|
||||
) -> StorageValue<'info, Value> {
|
||||
StorageValue {
|
||||
info,
|
||||
types,
|
||||
bytes,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the raw bytes for this storage value.
|
||||
pub fn bytes(&self) -> &[u8] {
|
||||
&self.bytes
|
||||
}
|
||||
|
||||
/// Consume this storage value and return the raw bytes.
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
self.bytes.to_vec()
|
||||
}
|
||||
|
||||
/// Decode this storage value into the provided response type.
|
||||
pub fn decode(&self) -> Result<Value, StorageValueError> {
|
||||
self.decode_as::<Value>()
|
||||
}
|
||||
|
||||
/// Decode this storage value into an arbitrary type.
|
||||
pub fn decode_as<T: DecodeAsType>(&self) -> Result<T, StorageValueError> {
|
||||
let cursor = &mut &*self.bytes;
|
||||
|
||||
let value = frame_decode::storage::decode_storage_value_with_info(
|
||||
cursor,
|
||||
&self.info,
|
||||
self.types,
|
||||
T::into_visitor(),
|
||||
)
|
||||
.map_err(StorageValueError::CannotDecode)?;
|
||||
|
||||
if !cursor.is_empty() {
|
||||
return Err(StorageValueError::LeftoverBytes {
|
||||
bytes: cursor.to_vec(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
// 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.
|
||||
|
||||
//! these utility methods complement the [`Address`] trait, but
|
||||
//! aren't things that should ever be overridden, and so don't exist on
|
||||
//! the trait itself.
|
||||
|
||||
use super::address::Address;
|
||||
use crate::error::{Error, MetadataError};
|
||||
use crate::metadata::Metadata;
|
||||
use alloc::borrow::ToOwned;
|
||||
use alloc::vec::Vec;
|
||||
use subxt_metadata::{PalletMetadata, StorageEntryMetadata, StorageHasher};
|
||||
|
||||
/// Return the root of a given [`Address`]: hash the pallet name and entry name
|
||||
/// and append those bytes to the output.
|
||||
pub fn write_storage_address_root_bytes<Addr: Address>(addr: &Addr, out: &mut Vec<u8>) {
|
||||
out.extend(sp_crypto_hashing::twox_128(addr.pallet_name().as_bytes()));
|
||||
out.extend(sp_crypto_hashing::twox_128(addr.entry_name().as_bytes()));
|
||||
}
|
||||
|
||||
/// Take some SCALE encoded bytes and a [`StorageHasher`] and hash the bytes accordingly.
|
||||
pub fn hash_bytes(input: &[u8], hasher: StorageHasher, bytes: &mut Vec<u8>) {
|
||||
match hasher {
|
||||
StorageHasher::Identity => bytes.extend(input),
|
||||
StorageHasher::Blake2_128 => bytes.extend(sp_crypto_hashing::blake2_128(input)),
|
||||
StorageHasher::Blake2_128Concat => {
|
||||
bytes.extend(sp_crypto_hashing::blake2_128(input));
|
||||
bytes.extend(input);
|
||||
}
|
||||
StorageHasher::Blake2_256 => bytes.extend(sp_crypto_hashing::blake2_256(input)),
|
||||
StorageHasher::Twox128 => bytes.extend(sp_crypto_hashing::twox_128(input)),
|
||||
StorageHasher::Twox256 => bytes.extend(sp_crypto_hashing::twox_256(input)),
|
||||
StorageHasher::Twox64Concat => {
|
||||
bytes.extend(sp_crypto_hashing::twox_64(input));
|
||||
bytes.extend(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return details about the given storage entry.
|
||||
pub fn lookup_storage_entry_details<'a>(
|
||||
pallet_name: &str,
|
||||
entry_name: &str,
|
||||
metadata: &'a Metadata,
|
||||
) -> Result<(PalletMetadata<'a>, &'a StorageEntryMetadata), Error> {
|
||||
let pallet_metadata = metadata.pallet_by_name_err(pallet_name)?;
|
||||
let storage_metadata = pallet_metadata
|
||||
.storage()
|
||||
.ok_or_else(|| MetadataError::StorageNotFoundInPallet(pallet_name.to_owned()))?;
|
||||
let storage_entry = storage_metadata
|
||||
.entry_by_name(entry_name)
|
||||
.ok_or_else(|| MetadataError::StorageEntryNotFound(entry_name.to_owned()))?;
|
||||
Ok((pallet_metadata, storage_entry))
|
||||
}
|
||||
+37
-23
@@ -13,7 +13,7 @@
|
||||
//! use subxt_core::config::DefaultExtrinsicParamsBuilder as Params;
|
||||
//! use subxt_core::tx;
|
||||
//! use subxt_core::utils::H256;
|
||||
//! use subxt_core::metadata;
|
||||
//! use subxt_core::Metadata;
|
||||
//!
|
||||
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
|
||||
//! #[subxt(
|
||||
@@ -26,7 +26,7 @@
|
||||
//! let state = tx::ClientState::<PolkadotConfig> {
|
||||
//! metadata: {
|
||||
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
|
||||
//! metadata::decode_from(&metadata_bytes[..]).unwrap()
|
||||
//! Metadata::decode_from(&metadata_bytes[..]).unwrap()
|
||||
//! },
|
||||
//! genesis_hash: {
|
||||
//! let h = "91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3";
|
||||
@@ -59,11 +59,12 @@
|
||||
pub mod payload;
|
||||
pub mod signer;
|
||||
|
||||
use crate::Metadata;
|
||||
use crate::config::{Config, ExtrinsicParams, ExtrinsicParamsEncoder, HashFor, Hasher};
|
||||
use crate::error::{Error, ExtrinsicError, MetadataError};
|
||||
use crate::metadata::Metadata;
|
||||
use crate::error::ExtrinsicError;
|
||||
use crate::utils::Encoded;
|
||||
use alloc::borrow::{Cow, ToOwned};
|
||||
use alloc::borrow::Cow;
|
||||
use alloc::string::ToString;
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Compact, Encode};
|
||||
use payload::Payload;
|
||||
@@ -77,18 +78,28 @@ pub use crate::client::{ClientState, RuntimeVersion};
|
||||
/// if the call is valid (or if it's not possible to check since the call has no validation hash).
|
||||
/// Return an error if the call was not valid or something went wrong trying to validate it (ie
|
||||
/// the pallet or call in question do not exist at all).
|
||||
pub fn validate<Call: Payload>(call: &Call, metadata: &Metadata) -> Result<(), Error> {
|
||||
if let Some(details) = call.validation_details() {
|
||||
let expected_hash = metadata
|
||||
.pallet_by_name_err(details.pallet_name)?
|
||||
.call_hash(details.call_name)
|
||||
.ok_or_else(|| MetadataError::CallNameNotFound(details.call_name.to_owned()))?;
|
||||
pub fn validate<Call: Payload>(call: &Call, metadata: &Metadata) -> Result<(), ExtrinsicError> {
|
||||
let Some(details) = call.validation_details() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if details.hash != expected_hash {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
}
|
||||
let pallet_name = details.pallet_name;
|
||||
let call_name = details.call_name;
|
||||
|
||||
let expected_hash = metadata
|
||||
.pallet_by_name(pallet_name)
|
||||
.ok_or_else(|| ExtrinsicError::PalletNameNotFound(pallet_name.to_string()))?
|
||||
.call_hash(call_name)
|
||||
.ok_or_else(|| ExtrinsicError::CallNameNotFound {
|
||||
pallet_name: pallet_name.to_string(),
|
||||
call_name: call_name.to_string(),
|
||||
})?;
|
||||
|
||||
if details.hash != expected_hash {
|
||||
Err(ExtrinsicError::IncompatibleCodegen)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the suggested transaction versions to build for a given chain, or an error
|
||||
@@ -96,7 +107,7 @@ pub fn validate<Call: Payload>(call: &Call, metadata: &Metadata) -> Result<(), E
|
||||
///
|
||||
/// If the result is [`TransactionVersion::V4`], use the `v4` methods in this module. If it's
|
||||
/// [`TransactionVersion::V5`], use the `v5` ones.
|
||||
pub fn suggested_version(metadata: &Metadata) -> Result<TransactionVersion, Error> {
|
||||
pub fn suggested_version(metadata: &Metadata) -> Result<TransactionVersion, ExtrinsicError> {
|
||||
let versions = metadata.extrinsic().supported_versions();
|
||||
|
||||
if versions.contains(&4) {
|
||||
@@ -104,7 +115,7 @@ pub fn suggested_version(metadata: &Metadata) -> Result<TransactionVersion, Erro
|
||||
} else if versions.contains(&5) {
|
||||
Ok(TransactionVersion::V5)
|
||||
} else {
|
||||
Err(ExtrinsicError::UnsupportedVersion.into())
|
||||
Err(ExtrinsicError::UnsupportedVersion)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +129,10 @@ pub enum TransactionVersion {
|
||||
}
|
||||
|
||||
/// Return the SCALE encoded bytes representing the call data of the transaction.
|
||||
pub fn call_data<Call: Payload>(call: &Call, metadata: &Metadata) -> Result<Vec<u8>, Error> {
|
||||
pub fn call_data<Call: Payload>(
|
||||
call: &Call,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Vec<u8>, ExtrinsicError> {
|
||||
let mut bytes = Vec::new();
|
||||
call.encode_call_data_to(metadata, &mut bytes)?;
|
||||
Ok(bytes)
|
||||
@@ -128,7 +142,7 @@ pub fn call_data<Call: Payload>(call: &Call, metadata: &Metadata) -> Result<Vec<
|
||||
pub fn create_v4_unsigned<T: Config, Call: Payload>(
|
||||
call: &Call,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Transaction<T>, Error> {
|
||||
) -> Result<Transaction<T>, ExtrinsicError> {
|
||||
create_unsigned_at_version(call, 4, metadata)
|
||||
}
|
||||
|
||||
@@ -136,7 +150,7 @@ pub fn create_v4_unsigned<T: Config, Call: Payload>(
|
||||
pub fn create_v5_bare<T: Config, Call: Payload>(
|
||||
call: &Call,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Transaction<T>, Error> {
|
||||
) -> Result<Transaction<T>, ExtrinsicError> {
|
||||
create_unsigned_at_version(call, 5, metadata)
|
||||
}
|
||||
|
||||
@@ -145,7 +159,7 @@ fn create_unsigned_at_version<T: Config, Call: Payload>(
|
||||
call: &Call,
|
||||
tx_version: u8,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Transaction<T>, Error> {
|
||||
) -> Result<Transaction<T>, ExtrinsicError> {
|
||||
// 1. Validate this call against the current node metadata if the call comes
|
||||
// with a hash allowing us to do so.
|
||||
validate(call, metadata)?;
|
||||
@@ -176,7 +190,7 @@ pub fn create_v4_signed<T: Config, Call: Payload>(
|
||||
call: &Call,
|
||||
client_state: &ClientState<T>,
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<PartialTransactionV4<T>, Error> {
|
||||
) -> Result<PartialTransactionV4<T>, ExtrinsicError> {
|
||||
// 1. Validate this call against the current node metadata if the call comes
|
||||
// with a hash allowing us to do so.
|
||||
validate(call, &client_state.metadata)?;
|
||||
@@ -200,7 +214,7 @@ pub fn create_v5_general<T: Config, Call: Payload>(
|
||||
call: &Call,
|
||||
client_state: &ClientState<T>,
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<PartialTransactionV5<T>, Error> {
|
||||
) -> Result<PartialTransactionV5<T>, ExtrinsicError> {
|
||||
// 1. Validate this call against the current node metadata if the call comes
|
||||
// with a hash allowing us to do so.
|
||||
validate(call, &client_state.metadata)?;
|
||||
|
||||
+27
-14
@@ -5,12 +5,11 @@
|
||||
//! This module contains the trait and types used to represent
|
||||
//! transactions that can be submitted.
|
||||
|
||||
use crate::Error;
|
||||
use crate::error::MetadataError;
|
||||
use crate::metadata::Metadata;
|
||||
use alloc::borrow::{Cow, ToOwned};
|
||||
use crate::Metadata;
|
||||
use crate::error::ExtrinsicError;
|
||||
use alloc::borrow::Cow;
|
||||
use alloc::boxed::Box;
|
||||
use alloc::string::String;
|
||||
use alloc::string::{String, ToString};
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use codec::Encode;
|
||||
@@ -21,11 +20,15 @@ use scale_value::{Composite, Value, ValueDef, Variant};
|
||||
/// to a node.
|
||||
pub trait Payload {
|
||||
/// Encode call data to the provided output.
|
||||
fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
|
||||
fn encode_call_data_to(
|
||||
&self,
|
||||
metadata: &Metadata,
|
||||
out: &mut Vec<u8>,
|
||||
) -> Result<(), ExtrinsicError>;
|
||||
|
||||
/// Encode call data and return the output. This is a convenience
|
||||
/// wrapper around [`Payload::encode_call_data_to`].
|
||||
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
|
||||
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, ExtrinsicError> {
|
||||
let mut v = Vec::new();
|
||||
self.encode_call_data_to(metadata, &mut v)?;
|
||||
Ok(v)
|
||||
@@ -46,10 +49,10 @@ macro_rules! boxed_payload {
|
||||
&self,
|
||||
metadata: &Metadata,
|
||||
out: &mut Vec<u8>,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<(), ExtrinsicError> {
|
||||
self.as_ref().encode_call_data_to(metadata, out)
|
||||
}
|
||||
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
|
||||
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, ExtrinsicError> {
|
||||
self.as_ref().encode_call_data(metadata)
|
||||
}
|
||||
fn validation_details(&self) -> Option<ValidationDetails<'_>> {
|
||||
@@ -164,11 +167,20 @@ impl DefaultPayload<Composite<()>> {
|
||||
}
|
||||
|
||||
impl<CallData: EncodeAsFields> Payload for DefaultPayload<CallData> {
|
||||
fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
|
||||
let pallet = metadata.pallet_by_name_err(&self.pallet_name)?;
|
||||
fn encode_call_data_to(
|
||||
&self,
|
||||
metadata: &Metadata,
|
||||
out: &mut Vec<u8>,
|
||||
) -> Result<(), ExtrinsicError> {
|
||||
let pallet = metadata
|
||||
.pallet_by_name(&self.pallet_name)
|
||||
.ok_or_else(|| ExtrinsicError::PalletNameNotFound(self.pallet_name.to_string()))?;
|
||||
let call = pallet
|
||||
.call_variant_by_name(&self.call_name)
|
||||
.ok_or_else(|| MetadataError::CallNameNotFound((*self.call_name).to_owned()))?;
|
||||
.ok_or_else(|| ExtrinsicError::CallNameNotFound {
|
||||
pallet_name: pallet.name().to_string(),
|
||||
call_name: self.call_name.to_string(),
|
||||
})?;
|
||||
|
||||
let pallet_index = pallet.index();
|
||||
let call_index = call.index;
|
||||
@@ -182,7 +194,8 @@ impl<CallData: EncodeAsFields> Payload for DefaultPayload<CallData> {
|
||||
.map(|f| scale_encode::Field::new(f.ty.id, f.name.as_deref()));
|
||||
|
||||
self.call_data
|
||||
.encode_as_fields_to(&mut fields, metadata.types(), out)?;
|
||||
.encode_as_fields_to(&mut fields, metadata.types(), out)
|
||||
.map_err(ExtrinsicError::CannotEncodeCallData)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -208,7 +221,7 @@ pub fn dynamic(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::metadata::Metadata;
|
||||
use crate::Metadata;
|
||||
use codec::Decode;
|
||||
use scale_value::Composite;
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ mod multi_signature;
|
||||
mod static_type;
|
||||
mod unchecked_extrinsic;
|
||||
mod wrapper_opaque;
|
||||
mod yesnomaybe;
|
||||
|
||||
use alloc::borrow::ToOwned;
|
||||
use alloc::format;
|
||||
@@ -30,6 +31,7 @@ pub use primitive_types::{H160, H256, H512};
|
||||
pub use static_type::Static;
|
||||
pub use unchecked_extrinsic::UncheckedExtrinsic;
|
||||
pub use wrapper_opaque::WrapperKeepOpaque;
|
||||
pub use yesnomaybe::{Maybe, No, NoMaybe, Yes, YesMaybe, YesNo};
|
||||
|
||||
/// Wraps an already encoded byte vector, prevents being encoded as a raw byte vector as part of
|
||||
/// the transaction payload
|
||||
@@ -73,9 +75,6 @@ unsafe impl<T> Sync for PhantomDataSendSync<T> {}
|
||||
/// as `BTreeMap` which allows us to easily swap the two during codegen.
|
||||
pub type KeyedVec<K, V> = Vec<(K, V)>;
|
||||
|
||||
/// A unit marker struct.
|
||||
pub struct Yes;
|
||||
|
||||
/// A quick helper to encode some bytes to hex.
|
||||
pub fn to_hex(bytes: impl AsRef<[u8]>) -> String {
|
||||
format!("0x{}", hex::encode(bytes.as_ref()))
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// 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.
|
||||
|
||||
/// A unit marker enum.
|
||||
pub enum Yes {}
|
||||
/// A unit marker enum.
|
||||
pub enum Maybe {}
|
||||
/// A unit marker enum.
|
||||
pub enum No {}
|
||||
|
||||
/// This is implemented for [`Yes`] and [`No`] and
|
||||
/// allows us to check at runtime which of these types is present.
|
||||
pub trait YesNo {
|
||||
/// [`Yes`]
|
||||
fn is_yes() -> bool {
|
||||
false
|
||||
}
|
||||
/// [`No`]
|
||||
fn is_no() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl YesNo for Yes {
|
||||
fn is_yes() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
impl YesNo for No {
|
||||
fn is_no() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// This is implemented for [`Yes`] and [`Maybe`] and
|
||||
/// allows us to check at runtime which of these types is present.
|
||||
pub trait YesMaybe {
|
||||
/// [`Yes`]
|
||||
fn is_yes() -> bool {
|
||||
false
|
||||
}
|
||||
/// [`Maybe`]
|
||||
fn is_maybe() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl YesMaybe for Yes {
|
||||
fn is_yes() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
impl YesMaybe for Maybe {
|
||||
fn is_maybe() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// This is implemented for [`No`] and [`Maybe`] and
|
||||
/// allows us to check at runtime which of these types is present.
|
||||
pub trait NoMaybe {
|
||||
/// [`No`]
|
||||
fn is_no() -> bool {
|
||||
false
|
||||
}
|
||||
/// [`Maybe`]
|
||||
fn is_maybe() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl NoMaybe for No {
|
||||
fn is_no() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
impl NoMaybe for Maybe {
|
||||
fn is_maybe() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -7,28 +7,39 @@
|
||||
|
||||
pub mod payload;
|
||||
|
||||
use crate::error::{Error, MetadataError};
|
||||
use crate::metadata::{DecodeWithMetadata, Metadata};
|
||||
use crate::Metadata;
|
||||
use crate::error::ViewFunctionError;
|
||||
use alloc::string::ToString;
|
||||
use alloc::vec::Vec;
|
||||
use payload::Payload;
|
||||
use scale_decode::IntoVisitor;
|
||||
|
||||
/// 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 {
|
||||
pub fn validate<P: Payload>(payload: P, metadata: &Metadata) -> Result<(), ViewFunctionError> {
|
||||
let Some(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());
|
||||
}
|
||||
let pallet_name = payload.pallet_name();
|
||||
let function_name = payload.function_name();
|
||||
|
||||
Ok(())
|
||||
let view_function = metadata
|
||||
.pallet_by_name(pallet_name)
|
||||
.ok_or_else(|| ViewFunctionError::PalletNotFound(pallet_name.to_string()))?
|
||||
.view_function_by_name(function_name)
|
||||
.ok_or_else(|| ViewFunctionError::ViewFunctionNotFound {
|
||||
pallet_name: pallet_name.to_string(),
|
||||
function_name: function_name.to_string(),
|
||||
})?;
|
||||
|
||||
if hash != view_function.hash() {
|
||||
Err(ViewFunctionError::IncompatibleCodegen)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The name of the Runtime API call which can execute
|
||||
@@ -36,34 +47,37 @@ 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());
|
||||
pub fn call_args<P: Payload>(
|
||||
payload: P,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Vec<u8>, ViewFunctionError> {
|
||||
let inputs = frame_decode::view_functions::encode_view_function_inputs(
|
||||
payload.pallet_name(),
|
||||
payload.function_name(),
|
||||
payload.args(),
|
||||
metadata,
|
||||
metadata.types(),
|
||||
)
|
||||
.map_err(ViewFunctionError::CouldNotEncodeInputs)?;
|
||||
|
||||
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)
|
||||
Ok(inputs)
|
||||
}
|
||||
|
||||
/// 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,
|
||||
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(),
|
||||
) -> Result<P::ReturnType, ViewFunctionError> {
|
||||
let value = frame_decode::view_functions::decode_view_function_response(
|
||||
payload.pallet_name(),
|
||||
payload.function_name(),
|
||||
bytes,
|
||||
metadata,
|
||||
)?;
|
||||
metadata.types(),
|
||||
P::ReturnType::into_visitor(),
|
||||
)
|
||||
.map_err(ViewFunctionError::CouldNotDecodeResponse)?;
|
||||
|
||||
Ok(val)
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
@@ -5,17 +5,12 @@
|
||||
//! This module contains the trait and types used to represent
|
||||
//! View Function calls that can be made.
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use alloc::borrow::Cow;
|
||||
use alloc::string::String;
|
||||
use core::marker::PhantomData;
|
||||
use derive_where::derive_where;
|
||||
use scale_encode::EncodeAsFields;
|
||||
use scale_value::Composite;
|
||||
|
||||
use crate::Error;
|
||||
use crate::dynamic::DecodedValueThunk;
|
||||
use crate::error::MetadataError;
|
||||
|
||||
use crate::metadata::{DecodeWithMetadata, Metadata};
|
||||
use frame_decode::view_functions::IntoEncodableValues;
|
||||
use scale_decode::DecodeAsType;
|
||||
|
||||
/// This represents a View Function payload that can call into the runtime of node.
|
||||
///
|
||||
@@ -33,24 +28,19 @@ use crate::metadata::{DecodeWithMetadata, Metadata};
|
||||
///
|
||||
/// Each argument of the View Function must be scale-encoded.
|
||||
pub trait Payload {
|
||||
/// Type of the arguments for this call.
|
||||
type ArgsType: IntoEncodableValues;
|
||||
/// The return type of the function call.
|
||||
// Note: `DecodeWithMetadata` is needed to decode the function call result
|
||||
// with the `subxt::Metadata.
|
||||
type ReturnType: DecodeWithMetadata;
|
||||
type ReturnType: DecodeAsType;
|
||||
|
||||
/// The payload target.
|
||||
fn query_id(&self) -> &[u8; 32];
|
||||
/// The View Function pallet name.
|
||||
fn pallet_name(&self) -> &str;
|
||||
|
||||
/// Scale encode the arguments data.
|
||||
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
|
||||
/// The View Function function name.
|
||||
fn function_name(&self) -> &str;
|
||||
|
||||
/// 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)
|
||||
}
|
||||
/// The arguments.
|
||||
fn args(&self) -> &Self::ArgsType;
|
||||
|
||||
/// Returns the statically generated validation hash.
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
@@ -58,45 +48,61 @@ pub trait Payload {
|
||||
}
|
||||
}
|
||||
|
||||
// A reference to a payload is a valid payload.
|
||||
impl<P: Payload + ?Sized> Payload for &'_ P {
|
||||
type ArgsType = P::ArgsType;
|
||||
type ReturnType = P::ReturnType;
|
||||
|
||||
fn pallet_name(&self) -> &str {
|
||||
P::pallet_name(*self)
|
||||
}
|
||||
|
||||
fn function_name(&self) -> &str {
|
||||
P::function_name(*self)
|
||||
}
|
||||
|
||||
fn args(&self) -> &Self::ArgsType {
|
||||
P::args(*self)
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
P::validation_hash(*self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A View Function payload containing the generic argument data
|
||||
/// and interpreting the result of the call as `ReturnTy`.
|
||||
/// and interpreting the result of the call as `ReturnType`.
|
||||
///
|
||||
/// 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,
|
||||
#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; ArgsType)]
|
||||
pub struct StaticPayload<ArgsType, ReturnType> {
|
||||
pallet_name: Cow<'static, str>,
|
||||
function_name: Cow<'static, str>,
|
||||
args: ArgsType,
|
||||
validation_hash: Option<[u8; 32]>,
|
||||
_marker: PhantomData<ReturnTy>,
|
||||
_marker: PhantomData<ReturnType>,
|
||||
}
|
||||
|
||||
/// 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>;
|
||||
pub type DynamicPayload<ArgsType, ReturnType> = StaticPayload<ArgsType, ReturnType>;
|
||||
|
||||
impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> Payload
|
||||
for DefaultPayload<ArgsData, ReturnTy>
|
||||
impl<ArgsType: IntoEncodableValues, ReturnType: DecodeAsType> Payload
|
||||
for StaticPayload<ArgsType, ReturnType>
|
||||
{
|
||||
type ReturnType = ReturnTy;
|
||||
type ArgsType = ArgsType;
|
||||
type ReturnType = ReturnType;
|
||||
|
||||
fn query_id(&self) -> &[u8; 32] {
|
||||
&self.query_id
|
||||
fn pallet_name(&self) -> &str {
|
||||
&self.pallet_name
|
||||
}
|
||||
|
||||
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));
|
||||
fn function_name(&self) -> &str {
|
||||
&self.function_name
|
||||
}
|
||||
|
||||
self.args_data
|
||||
.encode_as_fields_to(&mut fields, metadata.types(), out)?;
|
||||
|
||||
Ok(())
|
||||
fn args(&self) -> &Self::ArgsType {
|
||||
&self.args
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
@@ -104,30 +110,37 @@ impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> Payload
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
impl<ReturnTy, ArgsType> StaticPayload<ArgsType, ReturnTy> {
|
||||
/// Create a new [`StaticPayload`] for a View Function call.
|
||||
pub fn new(
|
||||
pallet_name: impl Into<String>,
|
||||
function_name: impl Into<String>,
|
||||
args: ArgsType,
|
||||
) -> Self {
|
||||
StaticPayload {
|
||||
pallet_name: pallet_name.into().into(),
|
||||
function_name: function_name.into().into(),
|
||||
args,
|
||||
validation_hash: None,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new static [`DefaultPayload`] for a View Function call
|
||||
/// Create a new static [`StaticPayload`] 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,
|
||||
pallet_name: &'static str,
|
||||
function_name: &'static str,
|
||||
args: ArgsType,
|
||||
hash: [u8; 32],
|
||||
) -> DefaultPayload<ArgsData, ReturnTy> {
|
||||
DefaultPayload {
|
||||
query_id,
|
||||
args_data,
|
||||
) -> StaticPayload<ArgsType, ReturnTy> {
|
||||
StaticPayload {
|
||||
pallet_name: Cow::Borrowed(pallet_name),
|
||||
function_name: Cow::Borrowed(function_name),
|
||||
args,
|
||||
validation_hash: Some(hash),
|
||||
_marker: core::marker::PhantomData,
|
||||
}
|
||||
@@ -140,14 +153,13 @@ impl<ReturnTy, ArgsData> DefaultPayload<ArgsData, ReturnTy> {
|
||||
..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())
|
||||
pub fn dynamic<ArgsType, ReturnType>(
|
||||
pallet_name: impl Into<String>,
|
||||
function_name: impl Into<String>,
|
||||
args: ArgsType,
|
||||
) -> DynamicPayload<ArgsType, ReturnType> {
|
||||
DynamicPayload::new(pallet_name, function_name, args)
|
||||
}
|
||||
|
||||
Generated
+60
-12
@@ -475,6 +475,12 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "const_format"
|
||||
version = "0.2.34"
|
||||
@@ -660,6 +666,17 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"pem-rfc7468",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive-where"
|
||||
version = "1.5.0"
|
||||
@@ -756,21 +773,23 @@ version = "2.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
|
||||
dependencies = [
|
||||
"pkcs8",
|
||||
"signature",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-zebra"
|
||||
version = "4.0.3"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9"
|
||||
checksum = "0017d969298eec91e3db7a2985a8cab4df6341d86e6f3a6f5878b13fb7846bc9"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"ed25519",
|
||||
"hashbrown 0.14.5",
|
||||
"hex",
|
||||
"hashbrown 0.15.4",
|
||||
"pkcs8",
|
||||
"rand_core",
|
||||
"sha2 0.10.9",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -864,9 +883,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "frame-decode"
|
||||
version = "0.9.0"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c470df86cf28818dd3cd2fc4667b80dbefe2236c722c3dc1d09e7c6c82d6dfcd"
|
||||
checksum = "a0acacffe60911b0d57a55c0b323fc08ccd88659dc52056bb39dfeb5cedafb59"
|
||||
dependencies = [
|
||||
"frame-metadata",
|
||||
"parity-scale-codec",
|
||||
@@ -1751,6 +1770,15 @@ dependencies = [
|
||||
"password-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
@@ -1800,6 +1828,16 @@ dependencies = [
|
||||
"futures-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
|
||||
dependencies = [
|
||||
"der",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "3.8.0"
|
||||
@@ -2262,9 +2300,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "scale-value"
|
||||
version = "0.18.0"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ca8b26b451ecb7fd7b62b259fa28add63d12ec49bbcac0e01fcb4b5ae0c09aa"
|
||||
checksum = "884aab179aba344c67ddcd1d7dd8e3f8fee202f2e570d97ec34ec8688442a5b3"
|
||||
dependencies = [
|
||||
"base58",
|
||||
"blake2",
|
||||
@@ -2527,9 +2565,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "smoldot"
|
||||
version = "0.19.4"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e16e5723359f0048bf64bfdfba64e5732a56847d42c4fd3fe56f18280c813413"
|
||||
checksum = "724ab10d6485cccb4bab080ce436c0b361295274aec7847d7ba84ab1a79a5132"
|
||||
dependencies = [
|
||||
"arrayvec 0.7.6",
|
||||
"async-lock",
|
||||
@@ -2581,9 +2619,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "smoldot-light"
|
||||
version = "0.17.2"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bba9e591716567d704a8252feeb2f1261a286e1e2cbdd4e49e9197c34a14e2"
|
||||
checksum = "e8b4d4971f06f2471f4e57a662dbe8047fa0cc020957764a6211f3fad371f7bd"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-lock",
|
||||
@@ -2660,6 +2698,16 @@ version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
|
||||
@@ -53,12 +53,13 @@ pub extern "C" fn do_transfer(dest_hex: *const c_char, amount: u64) -> i32 {
|
||||
);
|
||||
|
||||
// Submit and wait for finalize
|
||||
let res: Result<_, subxt::Error> = tokio_rt().block_on(async {
|
||||
let res: Result<(), subxt::Error> = tokio_rt().block_on(async {
|
||||
let progress = client
|
||||
.tx()
|
||||
.sign_and_submit_then_watch_default(&tx, &signer)
|
||||
.await?;
|
||||
progress.wait_for_finalized_success().await
|
||||
progress.wait_for_finalized_success().await?;
|
||||
Ok(())
|
||||
});
|
||||
|
||||
// Return code
|
||||
|
||||
Generated
+73
-16
@@ -504,6 +504,12 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "const_format"
|
||||
version = "0.2.34"
|
||||
@@ -689,6 +695,17 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"pem-rfc7468",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive-where"
|
||||
version = "1.2.7"
|
||||
@@ -785,21 +802,23 @@ version = "2.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
|
||||
dependencies = [
|
||||
"pkcs8",
|
||||
"signature",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-zebra"
|
||||
version = "4.0.3"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9"
|
||||
checksum = "0017d969298eec91e3db7a2985a8cab4df6341d86e6f3a6f5878b13fb7846bc9"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"ed25519",
|
||||
"hashbrown 0.14.5",
|
||||
"hex",
|
||||
"hashbrown 0.15.2",
|
||||
"pkcs8",
|
||||
"rand_core",
|
||||
"sha2 0.10.8",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -903,6 +922,12 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.1"
|
||||
@@ -914,9 +939,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "frame-decode"
|
||||
version = "0.9.0"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c470df86cf28818dd3cd2fc4667b80dbefe2236c722c3dc1d09e7c6c82d6dfcd"
|
||||
checksum = "a0acacffe60911b0d57a55c0b323fc08ccd88659dc52056bb39dfeb5cedafb59"
|
||||
dependencies = [
|
||||
"frame-metadata",
|
||||
"parity-scale-codec",
|
||||
@@ -1096,6 +1121,9 @@ version = "0.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -1823,6 +1851,15 @@ dependencies = [
|
||||
"password-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
@@ -1872,6 +1909,16 @@ dependencies = [
|
||||
"futures-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
|
||||
dependencies = [
|
||||
"der",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "3.3.2"
|
||||
@@ -2329,9 +2376,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "scale-value"
|
||||
version = "0.18.0"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ca8b26b451ecb7fd7b62b259fa28add63d12ec49bbcac0e01fcb4b5ae0c09aa"
|
||||
checksum = "884aab179aba344c67ddcd1d7dd8e3f8fee202f2e570d97ec34ec8688442a5b3"
|
||||
dependencies = [
|
||||
"base58",
|
||||
"blake2",
|
||||
@@ -2598,9 +2645,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "smoldot"
|
||||
version = "0.19.4"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e16e5723359f0048bf64bfdfba64e5732a56847d42c4fd3fe56f18280c813413"
|
||||
checksum = "724ab10d6485cccb4bab080ce436c0b361295274aec7847d7ba84ab1a79a5132"
|
||||
dependencies = [
|
||||
"arrayvec 0.7.6",
|
||||
"async-lock 3.3.0",
|
||||
@@ -2652,9 +2699,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "smoldot-light"
|
||||
version = "0.17.2"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bba9e591716567d704a8252feeb2f1261a286e1e2cbdd4e49e9197c34a14e2"
|
||||
checksum = "e8b4d4971f06f2471f4e57a662dbe8047fa0cc020957764a6211f3fad371f7bd"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-lock 3.3.0",
|
||||
@@ -2731,6 +2778,16 @@ version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
@@ -2751,9 +2808,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.5.0"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "subxt"
|
||||
@@ -3711,9 +3768,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.7.0"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
|
||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
@@ -63,14 +63,14 @@ async fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("NFT created.");
|
||||
|
||||
// check in storage, that alice is the official owner of the NFT:
|
||||
let nft_owner_storage_query = statemint::storage().uniques().asset(COLLECTION_ID, NTF_ID);
|
||||
let nft_owner_storage_query = statemint::storage().uniques().asset();
|
||||
let nft_storage_details = api
|
||||
.storage()
|
||||
.at_latest()
|
||||
.await?
|
||||
.fetch(&nft_owner_storage_query)
|
||||
.fetch(nft_owner_storage_query, (COLLECTION_ID, NTF_ID))
|
||||
.await?
|
||||
.ok_or("The NFT should have an owner (alice)")?;
|
||||
.decode()?;
|
||||
|
||||
// make sure that alice is the owner of the NFT:
|
||||
assert_eq!(nft_storage_details.owner, dev::alice().public_key().into());
|
||||
|
||||
Generated
+4
-4
@@ -667,9 +667,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "frame-decode"
|
||||
version = "0.9.0"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c470df86cf28818dd3cd2fc4667b80dbefe2236c722c3dc1d09e7c6c82d6dfcd"
|
||||
checksum = "a0acacffe60911b0d57a55c0b323fc08ccd88659dc52056bb39dfeb5cedafb59"
|
||||
dependencies = [
|
||||
"frame-metadata",
|
||||
"parity-scale-codec",
|
||||
@@ -2114,9 +2114,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "scale-value"
|
||||
version = "0.18.0"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ca8b26b451ecb7fd7b62b259fa28add63d12ec49bbcac0e01fcb4b5ae0c09aa"
|
||||
checksum = "884aab179aba344c67ddcd1d7dd8e3f8fee202f2e570d97ec34ec8688442a5b3"
|
||||
dependencies = [
|
||||
"base58",
|
||||
"blake2",
|
||||
|
||||
@@ -28,7 +28,7 @@ pub(crate) async fn fetch_events_dynamically() -> Result<Vec<String>, subxt::Err
|
||||
let event = event?;
|
||||
let pallet = event.pallet_name();
|
||||
let variant = event.variant_name();
|
||||
let field_values = event.field_values()?;
|
||||
let field_values = event.decode_as_fields::<subxt::dynamic::Value>()?;
|
||||
event_strings.push(format!("{pallet}::{variant}: {field_values}"));
|
||||
}
|
||||
Ok(event_strings)
|
||||
@@ -66,7 +66,7 @@ pub(crate) async fn subscribe_to_finalized_blocks(
|
||||
|
||||
let pallet_name = evt.pallet_name();
|
||||
let event_name = evt.variant_name();
|
||||
let event_values = evt.field_values()?;
|
||||
let event_values = evt.decode_as_fields::<subxt::dynamic::Value>()?;
|
||||
|
||||
writeln!(output, " {pallet_name}_{event_name}").ok();
|
||||
writeln!(output, " {}", event_values).ok();
|
||||
|
||||
@@ -43,7 +43,7 @@ async fn main() -> Result<(), Error> {
|
||||
println!(
|
||||
" {}: {}",
|
||||
field.name(),
|
||||
field.decode::<scale_value::Value>().unwrap()
|
||||
field.decode_as::<scale_value::Value>().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ async fn main() -> Result<(), Error> {
|
||||
extrinsic
|
||||
.call()
|
||||
.fields()
|
||||
.decode::<scale_value::Composite<_>>()
|
||||
.decode_as::<scale_value::Composite<_>>()
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
@@ -66,14 +66,14 @@ async fn main() -> Result<(), Error> {
|
||||
println!(
|
||||
" {}: {}",
|
||||
extension.name(),
|
||||
extension.decode::<scale_value::Value>().unwrap()
|
||||
extension.decode_as::<scale_value::Value>().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
// Or all of them at once:
|
||||
println!(
|
||||
" All: {}",
|
||||
extensions.decode::<scale_value::Composite<_>>().unwrap()
|
||||
extensions.decode_as::<scale_value::Composite<_>>().unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,25 +19,22 @@ async fn main() -> Result<(), Error> {
|
||||
let client_at_block = client.at(block_number).await?;
|
||||
|
||||
// We'll work the account balances at the given block, for this example.
|
||||
let account_balances = client_at_block
|
||||
.storage()
|
||||
.entry("System", "Account")?
|
||||
.into_map()?;
|
||||
let account_balances = client_at_block.storage().entry("System", "Account")?;
|
||||
|
||||
// We can see the default value for this entry at this block, if one exists.
|
||||
if let Some(default_value) = account_balances.default() {
|
||||
let default_balance_info = default_value.decode::<scale_value::Value>()?;
|
||||
if let Some(default_value) = account_balances.default_value() {
|
||||
let default_balance_info = default_value.decode_as::<scale_value::Value>()?;
|
||||
println!(" Default balance info: {default_balance_info}");
|
||||
}
|
||||
|
||||
// We can fetch a specific account balance by its key, like so (here I just picked a random key
|
||||
// I knew to exist from iterating over storage entries):
|
||||
let account_id_hex = "9a4d0faa2ba8c3cc5711852960940793acf55bf195b6eecf88fa78e961d0ce4a";
|
||||
let account_id = hex::decode(account_id_hex).unwrap();
|
||||
let account_id: [u8; 32] = hex::decode(account_id_hex).unwrap().try_into().unwrap();
|
||||
if let Some(entry) = account_balances.fetch((account_id,)).await? {
|
||||
// We can decode the value into our generic `scale_value::Value` type, which can
|
||||
// represent any SCALE-encoded value, like so:
|
||||
let _balance_info = entry.decode::<scale_value::Value>()?;
|
||||
let _balance_info = entry.decode_as::<scale_value::Value>()?;
|
||||
|
||||
// Or, if we know what shape to expect, we can decode the parts of the value that we care
|
||||
// about directly into a static type, which is more efficient and allows easy type-safe
|
||||
@@ -53,7 +50,7 @@ async fn main() -> Result<(), Error> {
|
||||
misc_frozen: u128,
|
||||
fee_frozen: u128,
|
||||
}
|
||||
let balance_info = entry.decode::<BalanceInfo>()?;
|
||||
let balance_info = entry.decode_as::<BalanceInfo>()?;
|
||||
|
||||
println!(
|
||||
" Single balance info from {account_id_hex} => free: {} reserved: {} misc_frozen: {} fee_frozen: {}",
|
||||
@@ -72,23 +69,38 @@ async fn main() -> Result<(), Error> {
|
||||
let mut all_balances = account_balances.iter(()).await?.take(10);
|
||||
while let Some(entry) = all_balances.next().await {
|
||||
let entry = entry?;
|
||||
let key = entry.decode_key()?;
|
||||
let key = entry.key()?;
|
||||
|
||||
// Decode the account ID from the key (we know here that we're working
|
||||
// with a map which has one value, an account ID, so we just decode that part:
|
||||
let account_id = key
|
||||
.part(0)
|
||||
.unwrap()
|
||||
.decode::<[u8; 32]>()?
|
||||
.decode_as::<[u8; 32]>()?
|
||||
.expect("We expect this key to decode into a 32 byte AccountId");
|
||||
|
||||
let account_id_hex = hex::encode(account_id);
|
||||
|
||||
// Decode these values into our generic scale_value::Value type. Less efficient than
|
||||
// defining a static type as above, but easier for the sake of the example.
|
||||
let balance_info = entry.decode_value::<scale_value::Value>()?;
|
||||
let balance_info = entry.value().decode_as::<scale_value::Value>()?;
|
||||
println!(" {account_id_hex} => {balance_info}");
|
||||
}
|
||||
|
||||
// We can also chain things together to fetch and decode a value in one go.
|
||||
let _val = client_at_block
|
||||
.storage()
|
||||
.entry("System", "Account")?
|
||||
.fetch((account_id,))
|
||||
.await?
|
||||
.unwrap()
|
||||
.decode_as::<scale_value::Value>()?;
|
||||
|
||||
let _vals = client_at_block
|
||||
.storage()
|
||||
.entry("System", "Account")?
|
||||
.iter(())
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
+19
-10
@@ -21,10 +21,6 @@ pub enum Error {
|
||||
#[error(transparent)]
|
||||
StorageError(#[from] StorageError),
|
||||
#[error(transparent)]
|
||||
StorageEntryIsNotAMap(#[from] StorageEntryIsNotAMap),
|
||||
#[error(transparent)]
|
||||
StorageEntryIsNotAPlainValue(#[from] StorageEntryIsNotAPlainValue),
|
||||
#[error(transparent)]
|
||||
StorageKeyError(#[from] StorageKeyError),
|
||||
#[error(transparent)]
|
||||
StorageValueError(#[from] StorageValueError),
|
||||
@@ -214,22 +210,22 @@ pub enum ExtrinsicCallError {
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("Storage entry is not a map: pallet {pallet_name}, storage {storage_name}")]
|
||||
#[error("Storage entry is not a map: pallet {pallet_name}, storage {entry_name}")]
|
||||
pub struct StorageEntryIsNotAMap {
|
||||
/// The pallet containing the storage entry that was not found.
|
||||
pub pallet_name: String,
|
||||
/// The storage entry that was not found.
|
||||
pub storage_name: String,
|
||||
pub entry_name: String,
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("Storage entry is not a plain value: pallet {pallet_name}, storage {storage_name}")]
|
||||
#[error("Storage entry is not a plain value: pallet {pallet_name}, storage {entry_name}")]
|
||||
pub struct StorageEntryIsNotAPlainValue {
|
||||
/// The pallet containing the storage entry that was not found.
|
||||
pub pallet_name: String,
|
||||
/// The storage entry that was not found.
|
||||
pub storage_name: String,
|
||||
pub entry_name: String,
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
@@ -252,14 +248,23 @@ pub enum StorageError {
|
||||
reason: frame_decode::storage::StorageKeyEncodeError,
|
||||
},
|
||||
#[error(
|
||||
"Too many keys provided: expected {num_keys_expected} keys, but got {num_keys_provided}"
|
||||
"Wrong number of keys provided to fetch a value: expected {num_keys_expected} keys, but got {num_keys_provided}"
|
||||
)]
|
||||
WrongNumberOfKeysProvided {
|
||||
WrongNumberOfKeysProvidedForFetch {
|
||||
/// The number of keys that were provided.
|
||||
num_keys_provided: usize,
|
||||
/// The number of keys expected.
|
||||
num_keys_expected: usize,
|
||||
},
|
||||
#[error(
|
||||
"too many keys were provided to iterate over a storage entry: expected at most {max_keys_expected} keys, but got {num_keys_provided}"
|
||||
)]
|
||||
TooManyKeysProvidedForIter {
|
||||
/// The number of keys that were provided.
|
||||
num_keys_provided: usize,
|
||||
/// The maximum number of keys that we expect.
|
||||
max_keys_expected: usize,
|
||||
},
|
||||
#[error(
|
||||
"Could not extract storage information from metadata: Unsupported metadata version ({version})"
|
||||
)]
|
||||
@@ -295,6 +300,10 @@ pub enum StorageKeyError {
|
||||
index: usize,
|
||||
reason: scale_decode::Error,
|
||||
},
|
||||
#[error("Could not decode values out of the storage key: {reason}")]
|
||||
DecodeKeyValueError {
|
||||
reason: frame_decode::storage::StorageKeyValueDecodeError,
|
||||
},
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
|
||||
@@ -7,7 +7,7 @@ mod extrinsic_info;
|
||||
mod extrinsic_transaction_extensions;
|
||||
mod extrinsics_type;
|
||||
|
||||
pub use extrinsic_transaction_extensions::ExtrinsicTransactionExtensions;
|
||||
pub use extrinsic_transaction_extensions::ExtrinsicTransactionParams;
|
||||
pub use extrinsics_type::{Extrinsic, Extrinsics};
|
||||
|
||||
/// Work with extrinsics.
|
||||
|
||||
@@ -98,7 +98,7 @@ impl<'extrinsics, 'atblock> ExtrinsicCallFields<'extrinsics, 'atblock> {
|
||||
}
|
||||
|
||||
/// Attempt to decode the fields into the given type.
|
||||
pub fn decode<T: scale_decode::DecodeAsFields>(&self) -> Result<T, ExtrinsicCallError> {
|
||||
pub fn decode_as<T: scale_decode::DecodeAsFields>(&self) -> Result<T, ExtrinsicCallError> {
|
||||
with_info!(&self.info => {
|
||||
let cursor = &mut self.bytes();
|
||||
let mut fields = &mut info.info.call_data().map(|named_arg| {
|
||||
@@ -156,7 +156,7 @@ impl<'extrinsics, 'atblock> ExtrinsicCallField<'extrinsics, 'atblock> {
|
||||
}
|
||||
|
||||
/// Attempt to decode the value of this field into the given type.
|
||||
pub fn decode<T: scale_decode::DecodeAsType>(&self) -> Result<T, ExtrinsicCallError> {
|
||||
pub fn decode_as<T: scale_decode::DecodeAsType>(&self) -> Result<T, ExtrinsicCallError> {
|
||||
with_call_field_info!(&self.info => {
|
||||
let cursor = &mut &*self.field_bytes;
|
||||
let decoded = T::decode_as_type(cursor, info.info.ty().clone(), info.resolver)
|
||||
|
||||
@@ -16,7 +16,7 @@ struct ExtrinsicExtensionsInfo<'extrinsics, 'atblock, TypeId, Resolver> {
|
||||
}
|
||||
|
||||
/// This represents the transaction extensions of an extrinsic.
|
||||
pub struct ExtrinsicTransactionExtensions<'extrinsics, 'atblock> {
|
||||
pub struct ExtrinsicTransactionParams<'extrinsics, 'atblock> {
|
||||
all_bytes: &'extrinsics [u8],
|
||||
info: AnyExtrinsicExtensionsInfo<'extrinsics, 'atblock>,
|
||||
}
|
||||
@@ -31,7 +31,7 @@ macro_rules! with_extensions_info {
|
||||
};
|
||||
}
|
||||
|
||||
impl<'extrinsics, 'atblock> ExtrinsicTransactionExtensions<'extrinsics, 'atblock> {
|
||||
impl<'extrinsics, 'atblock> ExtrinsicTransactionParams<'extrinsics, 'atblock> {
|
||||
pub(crate) fn new(
|
||||
all_bytes: &'extrinsics [u8],
|
||||
info: &'extrinsics AnyExtrinsicInfo<'atblock>,
|
||||
@@ -105,7 +105,7 @@ impl<'extrinsics, 'atblock> ExtrinsicTransactionExtensions<'extrinsics, 'atblock
|
||||
|
||||
/// Attempt to decode the transaction extensions into a type where each field name is the name of the transaction
|
||||
/// extension and the field value is the decoded extension.
|
||||
pub fn decode<T: scale_decode::DecodeAsFields>(
|
||||
pub fn decode_as<T: scale_decode::DecodeAsFields>(
|
||||
&self,
|
||||
) -> Result<T, ExtrinsicTransactionExtensionError> {
|
||||
with_extensions_info!(&self.info => {
|
||||
@@ -189,7 +189,7 @@ impl<'extrinsics, 'atblock> ExtrinsicTransactionExtension<'extrinsics, 'atblock>
|
||||
}
|
||||
|
||||
/// Decode the bytes for this transaction extension into a type that implements `scale_decode::DecodeAsType`.
|
||||
pub fn decode<T: scale_decode::DecodeAsType>(
|
||||
pub fn decode_as<T: scale_decode::DecodeAsType>(
|
||||
&self,
|
||||
) -> Result<T, ExtrinsicTransactionExtensionError> {
|
||||
with_extension_info!(&self.info => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::extrinsic_call::ExtrinsicCall;
|
||||
use super::extrinsic_info::{AnyExtrinsicInfo, with_info};
|
||||
use super::extrinsic_transaction_extensions::ExtrinsicTransactionExtensions;
|
||||
use super::extrinsic_transaction_extensions::ExtrinsicTransactionParams;
|
||||
use crate::client::OfflineClientAtBlockT;
|
||||
use crate::config::Config;
|
||||
use crate::error::ExtrinsicsError;
|
||||
@@ -106,8 +106,8 @@ impl<'extrinsics, 'atblock> Extrinsic<'extrinsics, 'atblock> {
|
||||
/// Get information about the transaction extensions of this extrinsic.
|
||||
pub fn transaction_extensions(
|
||||
&self,
|
||||
) -> Option<ExtrinsicTransactionExtensions<'extrinsics, 'atblock>> {
|
||||
ExtrinsicTransactionExtensions::new(self.bytes, self.info)
|
||||
) -> Option<ExtrinsicTransactionParams<'extrinsics, 'atblock>> {
|
||||
ExtrinsicTransactionParams::new(self.bytes, self.info)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+94
-219
@@ -5,16 +5,17 @@ mod storage_value;
|
||||
|
||||
use crate::client::{OfflineClientAtBlockT, OnlineClientAtBlockT};
|
||||
use crate::config::Config;
|
||||
use crate::error::{StorageEntryIsNotAMap, StorageEntryIsNotAPlainValue, StorageError};
|
||||
use crate::error::StorageError;
|
||||
use crate::storage::storage_info::with_info;
|
||||
use std::borrow::Cow;
|
||||
use std::sync::Arc;
|
||||
use storage_info::AnyStorageInfo;
|
||||
|
||||
pub use storage_entry::StorageEntry;
|
||||
pub use storage_key::{StorageHasher, StorageKey, StorageKeyPart};
|
||||
pub use storage_value::StorageValue;
|
||||
// We take how storage keys can be passed in from `frame-decode`, so re-export here.
|
||||
pub use frame_decode::storage::{IntoStorageKeys, StorageKeys};
|
||||
pub use frame_decode::storage::{EncodableValues, IntoEncodableValues};
|
||||
|
||||
/// Work with storage.
|
||||
pub struct StorageClient<'atblock, Client, T> {
|
||||
@@ -33,44 +34,34 @@ impl<'atblock, Client, T> StorageClient<'atblock, Client, T> {
|
||||
}
|
||||
|
||||
// Things that we can do offline with storage.
|
||||
impl<'atblock, 'client: 'atblock, Client, T> StorageClient<'atblock, Client, T>
|
||||
impl<'atblock, Client, T> StorageClient<'atblock, Client, T>
|
||||
where
|
||||
T: Config + 'client,
|
||||
Client: OfflineClientAtBlockT<'client, T>,
|
||||
T: Config + 'atblock,
|
||||
Client: OfflineClientAtBlockT<'atblock, T>,
|
||||
{
|
||||
/// Select the storage entry you'd like to work with.
|
||||
pub fn entry(
|
||||
&self,
|
||||
pallet_name: impl Into<String>,
|
||||
storage_name: impl Into<String>,
|
||||
entry_name: impl Into<String>,
|
||||
) -> Result<StorageEntryClient<'atblock, Client, T>, StorageError> {
|
||||
let pallet_name = pallet_name.into();
|
||||
let storage_name = storage_name.into();
|
||||
let entry_name = entry_name.into();
|
||||
|
||||
let storage_info = AnyStorageInfo::new(
|
||||
&pallet_name,
|
||||
&storage_name,
|
||||
&entry_name,
|
||||
self.client.metadata(),
|
||||
self.client.legacy_types(),
|
||||
)?;
|
||||
|
||||
if storage_info.is_map() {
|
||||
Ok(StorageEntryClient::Map(StorageEntryMapClient {
|
||||
client: self.client,
|
||||
pallet_name,
|
||||
storage_name,
|
||||
info: storage_info,
|
||||
marker: std::marker::PhantomData,
|
||||
}))
|
||||
} else {
|
||||
Ok(StorageEntryClient::Plain(StorageEntryPlainClient {
|
||||
client: self.client,
|
||||
pallet_name,
|
||||
storage_name,
|
||||
info: storage_info,
|
||||
marker: std::marker::PhantomData,
|
||||
}))
|
||||
}
|
||||
Ok(StorageEntryClient {
|
||||
client: self.client,
|
||||
pallet_name,
|
||||
entry_name,
|
||||
info: Arc::new(storage_info),
|
||||
marker: std::marker::PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterate over all of the storage entries listed in the metadata for the current block. This does **not** include well known
|
||||
@@ -78,6 +69,7 @@ where
|
||||
pub fn entries(&self) -> impl Iterator<Item = StorageEntriesItem<'atblock, Client, T>> {
|
||||
let client = self.client;
|
||||
let metadata = client.metadata();
|
||||
|
||||
frame_decode::helpers::list_storage_entries_any(metadata).map(|entry| StorageEntriesItem {
|
||||
entry,
|
||||
client: self.client,
|
||||
@@ -88,24 +80,24 @@ where
|
||||
|
||||
/// Working with a specific storage entry.
|
||||
pub struct StorageEntriesItem<'atblock, Client, T> {
|
||||
entry: frame_decode::helpers::StorageEntry<'atblock>,
|
||||
entry: frame_decode::storage::StorageEntry<'atblock>,
|
||||
client: &'atblock Client,
|
||||
marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'atblock, 'client: 'atblock, Client, T> StorageEntriesItem<'atblock, Client, T>
|
||||
impl<'atblock, Client, T> StorageEntriesItem<'atblock, Client, T>
|
||||
where
|
||||
T: Config + 'client,
|
||||
Client: OfflineClientAtBlockT<'client, T>,
|
||||
T: Config + 'atblock,
|
||||
Client: OfflineClientAtBlockT<'atblock, T>,
|
||||
{
|
||||
/// The pallet name.
|
||||
pub fn pallet_name(&self) -> &str {
|
||||
self.entry.pallet()
|
||||
&self.entry.pallet_name
|
||||
}
|
||||
|
||||
/// The storage entry name.
|
||||
pub fn storage_name(&self) -> &str {
|
||||
self.entry.entry()
|
||||
pub fn entry_name(&self) -> &str {
|
||||
&self.entry.storage_entry
|
||||
}
|
||||
|
||||
/// Extract the relevant storage information so that we can work with this entry.
|
||||
@@ -115,87 +107,22 @@ where
|
||||
marker: std::marker::PhantomData,
|
||||
}
|
||||
.entry(
|
||||
self.entry.pallet().to_owned(),
|
||||
self.entry.entry().to_owned(),
|
||||
self.entry.pallet_name.clone(),
|
||||
self.entry.storage_entry.clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A client for working with a specific storage entry. This is an enum because the storage entry
|
||||
/// might be either a map or a plain value, and each has a different interface.
|
||||
pub enum StorageEntryClient<'atblock, Client, T> {
|
||||
Plain(StorageEntryPlainClient<'atblock, Client, T>),
|
||||
Map(StorageEntryMapClient<'atblock, Client, T>),
|
||||
/// A client for working with a specific storage entry.
|
||||
pub struct StorageEntryClient<'atblock, Client, T> {
|
||||
client: &'atblock Client,
|
||||
pallet_name: String,
|
||||
entry_name: String,
|
||||
info: Arc<AnyStorageInfo<'atblock>>,
|
||||
marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'atblock, Client, T> StorageEntryClient<'atblock, Client, T>
|
||||
where
|
||||
T: Config + 'atblock,
|
||||
Client: OfflineClientAtBlockT<'atblock, T>,
|
||||
{
|
||||
/// Get the pallet name.
|
||||
pub fn pallet_name(&self) -> &str {
|
||||
match self {
|
||||
StorageEntryClient::Plain(client) => &client.pallet_name,
|
||||
StorageEntryClient::Map(client) => &client.pallet_name,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the storage entry name.
|
||||
pub fn storage_name(&self) -> &str {
|
||||
match self {
|
||||
StorageEntryClient::Plain(client) => &client.storage_name,
|
||||
StorageEntryClient::Map(client) => &client.storage_name,
|
||||
}
|
||||
}
|
||||
|
||||
/// Is the storage entry a plain value?
|
||||
pub fn is_plain(&self) -> bool {
|
||||
matches!(self, StorageEntryClient::Plain(_))
|
||||
}
|
||||
|
||||
/// Is the storage entry a map?
|
||||
pub fn is_map(&self) -> bool {
|
||||
matches!(self, StorageEntryClient::Map(_))
|
||||
}
|
||||
|
||||
/// If this storage entry is a plain value, return the client for working with it. Else return an error.
|
||||
pub fn into_plain(
|
||||
self,
|
||||
) -> Result<StorageEntryPlainClient<'atblock, Client, T>, StorageEntryIsNotAPlainValue> {
|
||||
match self {
|
||||
StorageEntryClient::Plain(client) => Ok(client),
|
||||
StorageEntryClient::Map(_) => Err(StorageEntryIsNotAPlainValue {
|
||||
pallet_name: self.pallet_name().into(),
|
||||
storage_name: self.storage_name().into(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// If this storage entry is a map, return the client for working with it. Else return an error.
|
||||
pub fn into_map(
|
||||
self,
|
||||
) -> Result<StorageEntryMapClient<'atblock, Client, T>, StorageEntryIsNotAMap> {
|
||||
match self {
|
||||
StorageEntryClient::Plain(_) => Err(StorageEntryIsNotAMap {
|
||||
pallet_name: self.pallet_name().into(),
|
||||
storage_name: self.storage_name().into(),
|
||||
}),
|
||||
StorageEntryClient::Map(client) => Ok(client),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A client for working with a plain storage entry.
|
||||
pub struct StorageEntryPlainClient<'atblock, Client, T> {
|
||||
client: &'atblock Client,
|
||||
pallet_name: String,
|
||||
storage_name: String,
|
||||
info: AnyStorageInfo<'atblock>,
|
||||
marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'atblock, Client, T> StorageEntryPlainClient<'atblock, Client, T>
|
||||
where
|
||||
T: Config + 'atblock,
|
||||
Client: OfflineClientAtBlockT<'atblock, T>,
|
||||
@@ -206,134 +133,72 @@ where
|
||||
}
|
||||
|
||||
/// Get the storage entry name.
|
||||
pub fn storage_name(&self) -> &str {
|
||||
&self.storage_name
|
||||
pub fn entry_name(&self) -> &str {
|
||||
&self.entry_name
|
||||
}
|
||||
|
||||
/// Return the default value for this storage entry, if there is one. Returns `None` if there
|
||||
/// is no default value.
|
||||
pub fn default(&self) -> Option<StorageValue<'_, 'atblock>> {
|
||||
with_info!(info = &self.info => {
|
||||
info.info.default_value.as_ref().map(|default_value| {
|
||||
StorageValue::new(&self.info, default_value.clone())
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'atblock, Client, T> StorageEntryPlainClient<'atblock, Client, T>
|
||||
where
|
||||
T: Config + 'atblock,
|
||||
Client: OnlineClientAtBlockT<'atblock, T>,
|
||||
{
|
||||
/// Fetch the value for this storage entry.
|
||||
pub async fn fetch(&self) -> Result<Option<StorageValue<'_, 'atblock>>, StorageError> {
|
||||
let key_bytes = self.key();
|
||||
fetch(self.client, &key_bytes)
|
||||
.await
|
||||
.map(|v| v.map(|bytes| StorageValue::new(&self.info, Cow::Owned(bytes))))
|
||||
}
|
||||
|
||||
/// Fetch the value for this storage entry as per [`StorageEntryPlainClient::fetch`], but return the default
|
||||
/// value for the storage entry if one exists and the entry does not exist.
|
||||
pub async fn fetch_or_default(
|
||||
&self,
|
||||
) -> Result<Option<StorageValue<'_, 'atblock>>, StorageError> {
|
||||
self.fetch()
|
||||
.await
|
||||
.map(|option_val| option_val.or_else(|| self.default()))
|
||||
}
|
||||
|
||||
/// The key for this storage entry.
|
||||
pub fn key(&self) -> [u8; 32] {
|
||||
/// The key which points to this storage entry (but not necessarily any values within it).
|
||||
pub fn key_prefix(&self) -> [u8; 32] {
|
||||
let pallet_name = &*self.pallet_name;
|
||||
let storage_name = &*self.storage_name;
|
||||
let entry_name = &*self.entry_name;
|
||||
|
||||
frame_decode::storage::encode_prefix(pallet_name, storage_name)
|
||||
}
|
||||
}
|
||||
|
||||
/// A client for working with a storage entry that is a map.
|
||||
pub struct StorageEntryMapClient<'atblock, Client, T> {
|
||||
client: &'atblock Client,
|
||||
pallet_name: String,
|
||||
storage_name: String,
|
||||
info: AnyStorageInfo<'atblock>,
|
||||
marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'atblock, Client, T> StorageEntryMapClient<'atblock, Client, T>
|
||||
where
|
||||
T: Config + 'atblock,
|
||||
Client: OfflineClientAtBlockT<'atblock, T>,
|
||||
{
|
||||
/// Get the pallet name.
|
||||
pub fn pallet_name(&self) -> &str {
|
||||
&self.pallet_name
|
||||
}
|
||||
|
||||
/// Get the storage entry name.
|
||||
pub fn storage_name(&self) -> &str {
|
||||
&self.storage_name
|
||||
frame_decode::storage::encode_storage_key_prefix(pallet_name, entry_name)
|
||||
}
|
||||
|
||||
/// Return the default value for this storage entry, if there is one. Returns `None` if there
|
||||
/// is no default value.
|
||||
pub fn default(&self) -> Option<StorageValue<'_, 'atblock>> {
|
||||
with_info!(info = &self.info => {
|
||||
pub fn default_value(&self) -> Option<StorageValue<'atblock>> {
|
||||
with_info!(info = &*self.info => {
|
||||
info.info.default_value.as_ref().map(|default_value| {
|
||||
StorageValue::new(&self.info, default_value.clone())
|
||||
StorageValue::new(self.info.clone(), default_value.clone())
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'atblock, Client, T> StorageEntryMapClient<'atblock, Client, T>
|
||||
impl<'atblock, Client, T> StorageEntryClient<'atblock, Client, T>
|
||||
where
|
||||
T: Config + 'atblock,
|
||||
Client: OnlineClientAtBlockT<'atblock, T>,
|
||||
{
|
||||
/// Fetch a specific key in this map. If the number of keys provided is not equal
|
||||
/// to the number of keys required to fetch a single value from the map, then an error
|
||||
/// will be emitted.
|
||||
pub async fn fetch<Keys: IntoStorageKeys>(
|
||||
/// will be emitted. If no value exists but there is a default value for this storage
|
||||
/// entry, then the default value will be returned. Else, `None` will be returned.
|
||||
pub async fn fetch<Keys: IntoEncodableValues>(
|
||||
&self,
|
||||
keys: Keys,
|
||||
) -> Result<Option<StorageValue<'_, 'atblock>>, StorageError> {
|
||||
let expected_num_keys = with_info!(info = &self.info => {
|
||||
) -> Result<Option<StorageValue<'atblock>>, StorageError> {
|
||||
let expected_num_keys = with_info!(info = &*self.info => {
|
||||
info.info.keys.len()
|
||||
});
|
||||
|
||||
if expected_num_keys != keys.num_keys() {
|
||||
return Err(StorageError::WrongNumberOfKeysProvided {
|
||||
num_keys_provided: keys.num_keys(),
|
||||
// For fetching, we need exactly as many keys as exist for a storage entry.
|
||||
if expected_num_keys != keys.num_encodable_values() {
|
||||
return Err(StorageError::WrongNumberOfKeysProvidedForFetch {
|
||||
num_keys_provided: keys.num_encodable_values(),
|
||||
num_keys_expected: expected_num_keys,
|
||||
});
|
||||
}
|
||||
|
||||
let key_bytes = self.key(keys)?;
|
||||
fetch(self.client, &key_bytes)
|
||||
.await
|
||||
.map(|v| v.map(|bytes| StorageValue::new(&self.info, Cow::Owned(bytes))))
|
||||
}
|
||||
let info = self.info.clone();
|
||||
let value = fetch(self.client, &key_bytes)
|
||||
.await?
|
||||
.map(|bytes| StorageValue::new(info, Cow::Owned(bytes)))
|
||||
.or_else(|| self.default_value());
|
||||
|
||||
/// Fetch a specific key in this map as per [`StorageEntryMapClient::fetch`], but return the default
|
||||
/// value for the storage entry if one exists and the entry was not found.
|
||||
pub async fn fetch_or_default<Keys: IntoStorageKeys>(
|
||||
&self,
|
||||
keys: Keys,
|
||||
) -> Result<Option<StorageValue<'_, 'atblock>>, StorageError> {
|
||||
self.fetch(keys)
|
||||
.await
|
||||
.map(|option_val| option_val.or_else(|| self.default()))
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Iterate over the values underneath the provided keys.
|
||||
pub async fn iter<Keys: IntoStorageKeys>(
|
||||
pub async fn iter<Keys: IntoEncodableValues>(
|
||||
&self,
|
||||
keys: Keys,
|
||||
) -> Result<
|
||||
impl futures::Stream<Item = Result<StorageEntry<'_, 'atblock>, StorageError>> + Unpin,
|
||||
impl futures::Stream<Item = Result<StorageEntry<'atblock>, StorageError>>
|
||||
+ Unpin
|
||||
+ use<'atblock, Client, T, Keys>,
|
||||
StorageError,
|
||||
> {
|
||||
use futures::stream::StreamExt;
|
||||
@@ -341,6 +206,19 @@ where
|
||||
ArchiveStorageEvent, StorageQuery, StorageQueryType,
|
||||
};
|
||||
|
||||
let expected_num_keys = with_info!(info = &*self.info => {
|
||||
info.info.keys.len()
|
||||
});
|
||||
|
||||
// For iterating, we need at most one less key than the number that exists for a storage entry.
|
||||
// TODO: The error message will be confusing if == keys are provided!
|
||||
if keys.num_encodable_values() >= expected_num_keys {
|
||||
return Err(StorageError::TooManyKeysProvidedForIter {
|
||||
num_keys_provided: keys.num_encodable_values(),
|
||||
max_keys_expected: expected_num_keys - 1,
|
||||
});
|
||||
}
|
||||
|
||||
let block_hash = self.client.block_hash();
|
||||
let key_bytes = self.key(keys)?;
|
||||
|
||||
@@ -356,23 +234,22 @@ where
|
||||
.await
|
||||
.map_err(|e| StorageError::RpcError { reason: e })?;
|
||||
|
||||
let sub = sub.filter_map(async |item| {
|
||||
let item = match item {
|
||||
Ok(ArchiveStorageEvent::Item(item)) => item,
|
||||
Ok(ArchiveStorageEvent::Error(err)) => {
|
||||
return Some(Err(StorageError::StorageEventError { reason: err.error }));
|
||||
}
|
||||
Ok(ArchiveStorageEvent::Done) => return None,
|
||||
Err(e) => return Some(Err(StorageError::RpcError { reason: e })),
|
||||
};
|
||||
let info = self.info.clone();
|
||||
let sub = sub.filter_map(move |item| {
|
||||
let info = info.clone();
|
||||
async move {
|
||||
let item = match item {
|
||||
Ok(ArchiveStorageEvent::Item(item)) => item,
|
||||
Ok(ArchiveStorageEvent::Error(err)) => {
|
||||
return Some(Err(StorageError::StorageEventError { reason: err.error }));
|
||||
}
|
||||
Ok(ArchiveStorageEvent::Done) => return None,
|
||||
Err(e) => return Some(Err(StorageError::RpcError { reason: e })),
|
||||
};
|
||||
|
||||
item.value.map(|value| {
|
||||
Ok(StorageEntry::new(
|
||||
&self.info,
|
||||
item.key.0,
|
||||
Cow::Owned(value.0),
|
||||
))
|
||||
})
|
||||
item.value
|
||||
.map(|value| Ok(StorageEntry::new(info, item.key.0, Cow::Owned(value.0))))
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Box::pin(sub))
|
||||
@@ -384,16 +261,14 @@ where
|
||||
// Dev note: We don't have any functions that can take an already-encoded key and fetch an entry from
|
||||
// it yet, so we don't expose this. If we did expose it, we might want to return some struct that wraps
|
||||
// the key bytes and some metadata about them. Or maybe just fetch_raw and iter_raw.
|
||||
fn key<Keys: IntoStorageKeys>(&self, keys: Keys) -> Result<Vec<u8>, StorageError> {
|
||||
with_info!(info = &self.info => {
|
||||
let mut key_bytes = Vec::new();
|
||||
frame_decode::storage::encode_storage_key_with_info_to(
|
||||
fn key<Keys: IntoEncodableValues>(&self, keys: Keys) -> Result<Vec<u8>, StorageError> {
|
||||
with_info!(info = &*self.info => {
|
||||
let key_bytes = frame_decode::storage::encode_storage_key_with_info(
|
||||
&self.pallet_name,
|
||||
&self.storage_name,
|
||||
&self.entry_name,
|
||||
keys,
|
||||
&info.info,
|
||||
info.resolver,
|
||||
&mut key_bytes,
|
||||
).map_err(|e| StorageError::KeyEncodeError { reason: e })?;
|
||||
Ok(key_bytes)
|
||||
})
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
use super::storage_info::AnyStorageInfo;
|
||||
use super::storage_key::StorageKey;
|
||||
use super::storage_value::StorageValue;
|
||||
use crate::error::{StorageKeyError, StorageValueError};
|
||||
use scale_decode::DecodeAsType;
|
||||
use crate::error::StorageKeyError;
|
||||
use std::borrow::Cow;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// This represents a storage entry, which is a key-value pair in the storage.
|
||||
pub struct StorageEntry<'entry, 'atblock> {
|
||||
pub struct StorageEntry<'atblock> {
|
||||
key: Vec<u8>,
|
||||
// This contains the storage information already:
|
||||
value: StorageValue<'entry, 'atblock>,
|
||||
value: StorageValue<'atblock>,
|
||||
}
|
||||
|
||||
impl<'entry, 'atblock> StorageEntry<'entry, 'atblock> {
|
||||
impl<'atblock> StorageEntry<'atblock> {
|
||||
/// Create a new storage entry.
|
||||
pub fn new(
|
||||
info: &'entry AnyStorageInfo<'atblock>,
|
||||
info: Arc<AnyStorageInfo<'atblock>>,
|
||||
key: Vec<u8>,
|
||||
value: Cow<'atblock, [u8]>,
|
||||
) -> Self {
|
||||
@@ -30,11 +30,6 @@ impl<'entry, 'atblock> StorageEntry<'entry, 'atblock> {
|
||||
&self.key
|
||||
}
|
||||
|
||||
/// Get the raw bytes for this storage entry's value.
|
||||
pub fn value_bytes(&self) -> &[u8] {
|
||||
self.value.bytes()
|
||||
}
|
||||
|
||||
/// Consume this storage entry and return the raw bytes for the key and value.
|
||||
pub fn into_key_and_value_bytes(self) -> (Vec<u8>, Vec<u8>) {
|
||||
(self.key, self.value.into_bytes())
|
||||
@@ -42,12 +37,12 @@ impl<'entry, 'atblock> StorageEntry<'entry, 'atblock> {
|
||||
|
||||
/// Decode the key for this storage entry. This gives back a type from which we can
|
||||
/// decode specific parts of the key hash (where applicable).
|
||||
pub fn decode_key(&'_ self) -> Result<StorageKey<'_, 'atblock>, StorageKeyError> {
|
||||
StorageKey::new(self.value.info, &self.key)
|
||||
pub fn key(&'_ self) -> Result<StorageKey<'_, 'atblock>, StorageKeyError> {
|
||||
StorageKey::new(&self.value.info, &self.key)
|
||||
}
|
||||
|
||||
/// Decode this storage value.
|
||||
pub fn decode_value<T: DecodeAsType>(&self) -> Result<T, StorageValueError> {
|
||||
self.value.decode::<T>()
|
||||
/// Return the storage value.
|
||||
pub fn value(&self) -> &StorageValue<'atblock> {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ impl<'atblock> AnyStorageInfo<'atblock> {
|
||||
Resolver: scale_type_resolver::TypeResolver<TypeId = Info::TypeId>,
|
||||
AnyStorageInfo<'atblock>: From<StorageInfo<'atblock, Info::TypeId, Resolver>>,
|
||||
{
|
||||
m.get_storage_info(pallet_name, entry_name)
|
||||
m.storage_info(pallet_name, entry_name)
|
||||
.map(|frame_storage_info| {
|
||||
let info = StorageInfo {
|
||||
info: frame_storage_info,
|
||||
@@ -84,7 +84,7 @@ impl<'atblock> From<StorageInfo<'atblock, u32, scale_info::PortableRegistry>>
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StorageInfo<'atblock, TypeId, Resolver> {
|
||||
pub struct StorageInfo<'atblock, TypeId: Clone, Resolver> {
|
||||
pub info: frame_decode::storage::StorageInfo<'atblock, TypeId>,
|
||||
pub resolver: &'atblock Resolver,
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{error::StorageKeyError, storage::storage_info::with_info};
|
||||
use scale_info_legacy::{LookupName, TypeRegistrySet};
|
||||
|
||||
// This is part of our public interface.
|
||||
pub use frame_decode::storage::StorageHasher;
|
||||
pub use frame_decode::storage::{IntoDecodableValues, StorageHasher};
|
||||
|
||||
enum AnyStorageKeyInfo<'atblock> {
|
||||
Legacy(StorageKeyInfo<'atblock, LookupName, TypeRegistrySet<'atblock>>),
|
||||
@@ -78,6 +78,23 @@ impl<'entry, 'atblock> StorageKey<'entry, 'atblock> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Attempt to decode the values contained within this storage key to the `Target` type
|
||||
/// provided. This type is typically a tuple of types which each implement [`scale_decode::DecodeAsType`]
|
||||
/// and correspond to each of the key types present, in order.
|
||||
pub fn decode_as<Target: IntoDecodableValues>(&self) -> Result<Target, StorageKeyError> {
|
||||
with_key_info!(info = &self.info => {
|
||||
let values = frame_decode::storage::decode_storage_key_values(
|
||||
self.bytes,
|
||||
&info.info,
|
||||
info.resolver
|
||||
).map_err(|e| {
|
||||
StorageKeyError::DecodeKeyValueError { reason: e }
|
||||
})?;
|
||||
|
||||
Ok(values)
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterate over the parts of this storage key. Each part of a storage key corresponds to a
|
||||
/// single value that has been hashed.
|
||||
pub fn parts(&'_ self) -> impl ExactSizeIterator<Item = StorageKeyPart<'_, 'entry, 'atblock>> {
|
||||
@@ -137,7 +154,7 @@ impl<'key, 'entry, 'atblock> StorageKeyPart<'key, 'entry, 'atblock> {
|
||||
/// is available as a part of the key hash, allowing us to decode it into anything
|
||||
/// implementing [`scale_decode::DecodeAsType`]. If the key was produced using a
|
||||
/// different hasher, this will return `None`.
|
||||
pub fn decode<T: scale_decode::DecodeAsType>(&self) -> Result<Option<T>, StorageKeyError> {
|
||||
pub fn decode_as<T: scale_decode::DecodeAsType>(&self) -> Result<Option<T>, StorageKeyError> {
|
||||
with_key_info!(info = &self.info => {
|
||||
let part_info = &info.info[self.index];
|
||||
let Some(value_info) = part_info.value() else {
|
||||
|
||||
@@ -3,16 +3,17 @@ use super::storage_info::with_info;
|
||||
use crate::error::StorageValueError;
|
||||
use scale_decode::DecodeAsType;
|
||||
use std::borrow::Cow;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// This represents a storage value.
|
||||
pub struct StorageValue<'entry, 'atblock> {
|
||||
pub(crate) info: &'entry AnyStorageInfo<'atblock>,
|
||||
pub struct StorageValue<'atblock> {
|
||||
pub(crate) info: Arc<AnyStorageInfo<'atblock>>,
|
||||
bytes: Cow<'atblock, [u8]>,
|
||||
}
|
||||
|
||||
impl<'entry, 'atblock> StorageValue<'entry, 'atblock> {
|
||||
impl<'atblock> StorageValue<'atblock> {
|
||||
/// Create a new storage value.
|
||||
pub fn new(info: &'entry AnyStorageInfo<'atblock>, bytes: Cow<'atblock, [u8]>) -> Self {
|
||||
pub fn new(info: Arc<AnyStorageInfo<'atblock>>, bytes: Cow<'atblock, [u8]>) -> Self {
|
||||
Self { info, bytes }
|
||||
}
|
||||
|
||||
@@ -27,8 +28,8 @@ impl<'entry, 'atblock> StorageValue<'entry, 'atblock> {
|
||||
}
|
||||
|
||||
/// Decode this storage value.
|
||||
pub fn decode<T: DecodeAsType>(&self) -> Result<T, StorageValueError> {
|
||||
with_info!(info = &self.info => {
|
||||
pub fn decode_as<T: DecodeAsType>(&self) -> Result<T, StorageValueError> {
|
||||
with_info!(info = &*self.info => {
|
||||
let cursor = &mut &*self.bytes;
|
||||
|
||||
let value = T::decode_as_type(
|
||||
|
||||
@@ -74,11 +74,9 @@ fn bench_get_storage_hash(c: &mut Criterion) {
|
||||
};
|
||||
|
||||
for storage in storage_entries.entries() {
|
||||
let storage_name = storage.name();
|
||||
let bench_name = format!("{pallet_name}/{storage_name}");
|
||||
group.bench_function(&bench_name, |b| {
|
||||
b.iter(|| pallet.storage_hash(storage_name))
|
||||
});
|
||||
let entry_name = storage.name();
|
||||
let bench_name = format!("{pallet_name}/{entry_name}");
|
||||
group.bench_function(&bench_name, |b| b.iter(|| pallet.storage_hash(entry_name)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,15 @@ pub enum TryFromError {
|
||||
/// Invalid type path.
|
||||
#[error("Type has an invalid path {0}")]
|
||||
InvalidTypePath(String),
|
||||
/// Cannot decode storage entry information.
|
||||
#[error("Error decoding storage entry information: {0}")]
|
||||
StorageInfoError(#[from] frame_decode::storage::StorageInfoError<'static>),
|
||||
/// Cannot decode Runtime API information.
|
||||
#[error("Error decoding Runtime API information: {0}")]
|
||||
RuntimeInfoError(#[from] frame_decode::runtime_apis::RuntimeApiInfoError<'static>),
|
||||
/// Cannot decode View Function information.
|
||||
#[error("Error decoding View Function information: {0}")]
|
||||
ViewFunctionInfoError(#[from] frame_decode::view_functions::ViewFunctionInfoError<'static>),
|
||||
}
|
||||
|
||||
impl TryFrom<frame_metadata::RuntimeMetadataPrefixed> for crate::Metadata {
|
||||
|
||||
+39
-75
@@ -6,9 +6,8 @@ use super::TryFromError;
|
||||
|
||||
use crate::utils::variant_index::VariantIndex;
|
||||
use crate::{
|
||||
ArcStr, ConstantMetadata, CustomMetadataInner, ExtrinsicMetadata, Metadata, OuterEnumsMetadata,
|
||||
PalletMetadataInner, StorageEntryMetadata, StorageEntryModifier, StorageEntryType,
|
||||
StorageHasher, StorageMetadata, TransactionExtensionMetadataInner,
|
||||
ConstantMetadata, CustomMetadataInner, ExtrinsicMetadata, Metadata, OuterEnumsMetadata,
|
||||
PalletMetadataInner, StorageEntryMetadata, StorageMetadata, TransactionExtensionMetadataInner,
|
||||
utils::ordered_map::OrderedMap,
|
||||
};
|
||||
use alloc::borrow::ToOwned;
|
||||
@@ -16,6 +15,7 @@ use alloc::collections::BTreeMap;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use alloc::{format, vec};
|
||||
use frame_decode::storage::StorageTypeInfo;
|
||||
use frame_metadata::v14;
|
||||
use hashbrown::HashMap;
|
||||
use scale_info::form::PortableForm;
|
||||
@@ -28,23 +28,37 @@ impl TryFrom<v14::RuntimeMetadataV14> for Metadata {
|
||||
|
||||
let mut pallets = OrderedMap::new();
|
||||
let mut pallets_by_index = HashMap::new();
|
||||
for (pos, p) in m.pallets.into_iter().enumerate() {
|
||||
let name: ArcStr = p.name.into();
|
||||
for (pos, p) in m.pallets.iter().enumerate() {
|
||||
let name: String = p.name.clone();
|
||||
|
||||
let storage = p.storage.map(|s| StorageMetadata {
|
||||
prefix: s.prefix,
|
||||
entries: s
|
||||
.entries
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
let name: ArcStr = s.name.clone().into();
|
||||
(name.clone(), from_storage_entry_metadata(name, s))
|
||||
})
|
||||
.collect(),
|
||||
});
|
||||
let constants = p.constants.into_iter().map(|c| {
|
||||
let name: ArcStr = c.name.clone().into();
|
||||
(name.clone(), from_constant_metadata(name, c))
|
||||
let storage = match &p.storage {
|
||||
None => None,
|
||||
Some(s) => Some(StorageMetadata {
|
||||
prefix: s.prefix.clone(),
|
||||
entries: s
|
||||
.entries
|
||||
.iter()
|
||||
.map(|s| {
|
||||
let entry_name: String = s.name.clone();
|
||||
let storage_info = m
|
||||
.storage_info(&name, &entry_name)
|
||||
.map_err(|e| e.into_owned())?
|
||||
.into_owned();
|
||||
let storage_entry = StorageEntryMetadata {
|
||||
name: entry_name.clone(),
|
||||
info: storage_info,
|
||||
docs: s.docs.clone(),
|
||||
};
|
||||
|
||||
Ok::<_, TryFromError>((entry_name, storage_entry))
|
||||
})
|
||||
.collect::<Result<_, TryFromError>>()?,
|
||||
}),
|
||||
};
|
||||
|
||||
let constants = p.constants.iter().map(|c| {
|
||||
let name = c.name.clone();
|
||||
(name, from_constant_metadata(c.clone()))
|
||||
});
|
||||
|
||||
let call_variant_index =
|
||||
@@ -58,14 +72,14 @@ impl TryFrom<v14::RuntimeMetadataV14> for Metadata {
|
||||
pallets.push_insert(
|
||||
name.clone(),
|
||||
PalletMetadataInner {
|
||||
name,
|
||||
name: name.clone(),
|
||||
index: p.index,
|
||||
storage,
|
||||
call_ty: p.calls.map(|c| c.ty.id),
|
||||
call_ty: p.calls.as_ref().map(|c| c.ty.id),
|
||||
call_variant_index,
|
||||
event_ty: p.event.map(|e| e.ty.id),
|
||||
event_ty: p.event.as_ref().map(|e| e.ty.id),
|
||||
event_variant_index,
|
||||
error_ty: p.error.map(|e| e.ty.id),
|
||||
error_ty: p.error.as_ref().map(|e| e.ty.id),
|
||||
error_variant_index,
|
||||
constants: constants.collect(),
|
||||
view_functions: Default::default(),
|
||||
@@ -135,59 +149,9 @@ fn from_extrinsic_metadata(
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_hasher(value: v14::StorageHasher) -> StorageHasher {
|
||||
match value {
|
||||
v14::StorageHasher::Blake2_128 => StorageHasher::Blake2_128,
|
||||
v14::StorageHasher::Blake2_256 => StorageHasher::Blake2_256,
|
||||
v14::StorageHasher::Blake2_128Concat => StorageHasher::Blake2_128Concat,
|
||||
v14::StorageHasher::Twox128 => StorageHasher::Twox128,
|
||||
v14::StorageHasher::Twox256 => StorageHasher::Twox256,
|
||||
v14::StorageHasher::Twox64Concat => StorageHasher::Twox64Concat,
|
||||
v14::StorageHasher::Identity => StorageHasher::Identity,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_entry_type(value: v14::StorageEntryType<PortableForm>) -> StorageEntryType {
|
||||
match value {
|
||||
v14::StorageEntryType::Plain(ty) => StorageEntryType::Plain(ty.id),
|
||||
v14::StorageEntryType::Map {
|
||||
hashers,
|
||||
key,
|
||||
value,
|
||||
} => StorageEntryType::Map {
|
||||
hashers: hashers.into_iter().map(from_storage_hasher).collect(),
|
||||
key_ty: key.id,
|
||||
value_ty: value.id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_entry_modifier(value: v14::StorageEntryModifier) -> StorageEntryModifier {
|
||||
match value {
|
||||
v14::StorageEntryModifier::Optional => StorageEntryModifier::Optional,
|
||||
v14::StorageEntryModifier::Default => StorageEntryModifier::Default,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_entry_metadata(
|
||||
name: ArcStr,
|
||||
s: v14::StorageEntryMetadata<PortableForm>,
|
||||
) -> StorageEntryMetadata {
|
||||
StorageEntryMetadata {
|
||||
name,
|
||||
modifier: from_storage_entry_modifier(s.modifier),
|
||||
entry_type: from_storage_entry_type(s.ty),
|
||||
default: s.default,
|
||||
docs: s.docs,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_constant_metadata(
|
||||
name: ArcStr,
|
||||
s: v14::PalletConstantMetadata<PortableForm>,
|
||||
) -> ConstantMetadata {
|
||||
fn from_constant_metadata(s: v14::PalletConstantMetadata<PortableForm>) -> ConstantMetadata {
|
||||
ConstantMetadata {
|
||||
name,
|
||||
name: s.name,
|
||||
ty: s.ty.id,
|
||||
value: s.value,
|
||||
docs: s.docs,
|
||||
|
||||
+71
-123
@@ -6,14 +6,15 @@ use super::TryFromError;
|
||||
|
||||
use crate::utils::variant_index::VariantIndex;
|
||||
use crate::{
|
||||
ArcStr, ConstantMetadata, ExtrinsicMetadata, Metadata, MethodParamMetadata, OuterEnumsMetadata,
|
||||
PalletMetadataInner, RuntimeApiMetadataInner, RuntimeApiMethodMetadataInner,
|
||||
StorageEntryMetadata, StorageEntryModifier, StorageEntryType, StorageHasher, StorageMetadata,
|
||||
ConstantMetadata, ExtrinsicMetadata, Metadata, OuterEnumsMetadata, PalletMetadataInner,
|
||||
RuntimeApiMetadataInner, RuntimeApiMethodMetadataInner, StorageEntryMetadata, StorageMetadata,
|
||||
TransactionExtensionMetadataInner, utils::ordered_map::OrderedMap,
|
||||
};
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use frame_decode::runtime_apis::RuntimeApiTypeInfo;
|
||||
use frame_decode::storage::StorageTypeInfo;
|
||||
use frame_metadata::v15;
|
||||
use hashbrown::HashMap;
|
||||
use scale_info::form::PortableForm;
|
||||
@@ -23,23 +24,37 @@ impl TryFrom<v15::RuntimeMetadataV15> for Metadata {
|
||||
fn try_from(m: v15::RuntimeMetadataV15) -> Result<Self, TryFromError> {
|
||||
let mut pallets = OrderedMap::new();
|
||||
let mut pallets_by_index = HashMap::new();
|
||||
for (pos, p) in m.pallets.into_iter().enumerate() {
|
||||
let name: ArcStr = p.name.into();
|
||||
for (pos, p) in m.pallets.iter().enumerate() {
|
||||
let name = p.name.clone();
|
||||
|
||||
let storage = p.storage.map(|s| StorageMetadata {
|
||||
prefix: s.prefix,
|
||||
entries: s
|
||||
.entries
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
let name: ArcStr = s.name.clone().into();
|
||||
(name.clone(), from_storage_entry_metadata(name, s))
|
||||
})
|
||||
.collect(),
|
||||
});
|
||||
let constants = p.constants.into_iter().map(|c| {
|
||||
let name: ArcStr = c.name.clone().into();
|
||||
(name.clone(), from_constant_metadata(name, c))
|
||||
let storage = match &p.storage {
|
||||
None => None,
|
||||
Some(s) => Some(StorageMetadata {
|
||||
prefix: s.prefix.clone(),
|
||||
entries: s
|
||||
.entries
|
||||
.iter()
|
||||
.map(|s| {
|
||||
let entry_name = s.name.clone();
|
||||
let storage_info = m
|
||||
.storage_info(&name, &entry_name)
|
||||
.map_err(|e| e.into_owned())?
|
||||
.into_owned();
|
||||
let storage_entry = StorageEntryMetadata {
|
||||
name: entry_name.clone(),
|
||||
info: storage_info,
|
||||
docs: s.docs.clone(),
|
||||
};
|
||||
|
||||
Ok::<_, TryFromError>((entry_name, storage_entry))
|
||||
})
|
||||
.collect::<Result<_, TryFromError>>()?,
|
||||
}),
|
||||
};
|
||||
|
||||
let constants = p.constants.iter().map(|c| {
|
||||
let name = c.name.clone();
|
||||
(name, from_constant_metadata(c.clone()))
|
||||
});
|
||||
|
||||
let call_variant_index =
|
||||
@@ -56,24 +71,50 @@ impl TryFrom<v15::RuntimeMetadataV15> for Metadata {
|
||||
name,
|
||||
index: p.index,
|
||||
storage,
|
||||
call_ty: p.calls.map(|c| c.ty.id),
|
||||
call_ty: p.calls.as_ref().map(|c| c.ty.id),
|
||||
call_variant_index,
|
||||
event_ty: p.event.map(|e| e.ty.id),
|
||||
event_ty: p.event.as_ref().map(|e| e.ty.id),
|
||||
event_variant_index,
|
||||
error_ty: p.error.map(|e| e.ty.id),
|
||||
error_ty: p.error.as_ref().map(|e| e.ty.id),
|
||||
error_variant_index,
|
||||
constants: constants.collect(),
|
||||
view_functions: Default::default(),
|
||||
associated_types: Default::default(),
|
||||
docs: p.docs,
|
||||
docs: p.docs.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let apis = m.apis.into_iter().map(|api| {
|
||||
let name: ArcStr = api.name.clone().into();
|
||||
(name.clone(), from_runtime_api_metadata(name, api))
|
||||
});
|
||||
let apis = m
|
||||
.apis
|
||||
.iter()
|
||||
.map(|api| {
|
||||
let trait_name = api.name.clone();
|
||||
let methods = api
|
||||
.methods
|
||||
.iter()
|
||||
.map(|method| {
|
||||
let method_name = method.name.clone();
|
||||
let method_info = RuntimeApiMethodMetadataInner {
|
||||
info: m
|
||||
.runtime_api_info(&trait_name, &method.name)
|
||||
.map_err(|e| e.into_owned())?
|
||||
.into_owned(),
|
||||
name: method.name.clone(),
|
||||
docs: method.docs.clone(),
|
||||
};
|
||||
Ok((method_name, method_info))
|
||||
})
|
||||
.collect::<Result<_, TryFromError>>()?;
|
||||
|
||||
let runtime_api_metadata = RuntimeApiMetadataInner {
|
||||
name: trait_name.clone(),
|
||||
methods,
|
||||
docs: api.docs.clone(),
|
||||
};
|
||||
Ok((trait_name, runtime_api_metadata))
|
||||
})
|
||||
.collect::<Result<_, TryFromError>>()?;
|
||||
|
||||
let dispatch_error_ty = m
|
||||
.types
|
||||
@@ -88,7 +129,7 @@ impl TryFrom<v15::RuntimeMetadataV15> for Metadata {
|
||||
pallets_by_index,
|
||||
extrinsic: from_extrinsic_metadata(m.extrinsic),
|
||||
dispatch_error_ty,
|
||||
apis: apis.collect(),
|
||||
apis,
|
||||
outer_enums: OuterEnumsMetadata {
|
||||
call_enum_ty: m.outer_enums.call_enum_ty.id,
|
||||
event_enum_ty: m.outer_enums.event_enum_ty.id,
|
||||
@@ -130,104 +171,11 @@ fn from_extrinsic_metadata(value: v15::ExtrinsicMetadata<PortableForm>) -> Extri
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_hasher(value: v15::StorageHasher) -> StorageHasher {
|
||||
match value {
|
||||
v15::StorageHasher::Blake2_128 => StorageHasher::Blake2_128,
|
||||
v15::StorageHasher::Blake2_256 => StorageHasher::Blake2_256,
|
||||
v15::StorageHasher::Blake2_128Concat => StorageHasher::Blake2_128Concat,
|
||||
v15::StorageHasher::Twox128 => StorageHasher::Twox128,
|
||||
v15::StorageHasher::Twox256 => StorageHasher::Twox256,
|
||||
v15::StorageHasher::Twox64Concat => StorageHasher::Twox64Concat,
|
||||
v15::StorageHasher::Identity => StorageHasher::Identity,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_entry_type(value: v15::StorageEntryType<PortableForm>) -> StorageEntryType {
|
||||
match value {
|
||||
v15::StorageEntryType::Plain(ty) => StorageEntryType::Plain(ty.id),
|
||||
v15::StorageEntryType::Map {
|
||||
hashers,
|
||||
key,
|
||||
value,
|
||||
} => StorageEntryType::Map {
|
||||
hashers: hashers.into_iter().map(from_storage_hasher).collect(),
|
||||
key_ty: key.id,
|
||||
value_ty: value.id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_entry_modifier(value: v15::StorageEntryModifier) -> StorageEntryModifier {
|
||||
match value {
|
||||
v15::StorageEntryModifier::Optional => StorageEntryModifier::Optional,
|
||||
v15::StorageEntryModifier::Default => StorageEntryModifier::Default,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_entry_metadata(
|
||||
name: ArcStr,
|
||||
s: v15::StorageEntryMetadata<PortableForm>,
|
||||
) -> StorageEntryMetadata {
|
||||
StorageEntryMetadata {
|
||||
name,
|
||||
modifier: from_storage_entry_modifier(s.modifier),
|
||||
entry_type: from_storage_entry_type(s.ty),
|
||||
default: s.default,
|
||||
docs: s.docs,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_constant_metadata(
|
||||
name: ArcStr,
|
||||
s: v15::PalletConstantMetadata<PortableForm>,
|
||||
) -> ConstantMetadata {
|
||||
fn from_constant_metadata(s: v15::PalletConstantMetadata<PortableForm>) -> ConstantMetadata {
|
||||
ConstantMetadata {
|
||||
name,
|
||||
name: s.name,
|
||||
ty: s.ty.id,
|
||||
value: s.value,
|
||||
docs: s.docs,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_runtime_api_metadata(
|
||||
name: ArcStr,
|
||||
s: v15::RuntimeApiMetadata<PortableForm>,
|
||||
) -> RuntimeApiMetadataInner {
|
||||
RuntimeApiMetadataInner {
|
||||
name,
|
||||
docs: s.docs,
|
||||
methods: s
|
||||
.methods
|
||||
.into_iter()
|
||||
.map(|m| {
|
||||
let name: ArcStr = m.name.clone().into();
|
||||
(name.clone(), from_runtime_api_method_metadata(name, m))
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_runtime_api_method_metadata(
|
||||
name: ArcStr,
|
||||
s: v15::RuntimeApiMethodMetadata<PortableForm>,
|
||||
) -> RuntimeApiMethodMetadataInner {
|
||||
RuntimeApiMethodMetadataInner {
|
||||
name,
|
||||
inputs: s
|
||||
.inputs
|
||||
.into_iter()
|
||||
.map(from_runtime_api_method_param_metadata)
|
||||
.collect(),
|
||||
output_ty: s.output.id,
|
||||
docs: s.docs,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_runtime_api_method_param_metadata(
|
||||
s: v15::RuntimeApiMethodParamMetadata<PortableForm>,
|
||||
) -> MethodParamMetadata {
|
||||
MethodParamMetadata {
|
||||
name: s.name,
|
||||
ty: s.ty.id,
|
||||
}
|
||||
}
|
||||
|
||||
+96
-151
@@ -6,11 +6,13 @@ use super::TryFromError;
|
||||
|
||||
use crate::utils::variant_index::VariantIndex;
|
||||
use crate::{
|
||||
ArcStr, ConstantMetadata, ExtrinsicMetadata, Metadata, MethodParamMetadata, OuterEnumsMetadata,
|
||||
PalletMetadataInner, RuntimeApiMetadataInner, RuntimeApiMethodMetadataInner,
|
||||
StorageEntryMetadata, StorageEntryModifier, StorageEntryType, StorageHasher, StorageMetadata,
|
||||
ConstantMetadata, ExtrinsicMetadata, Metadata, OuterEnumsMetadata, PalletMetadataInner,
|
||||
RuntimeApiMetadataInner, RuntimeApiMethodMetadataInner, StorageEntryMetadata, StorageMetadata,
|
||||
TransactionExtensionMetadataInner, ViewFunctionMetadataInner, utils::ordered_map::OrderedMap,
|
||||
};
|
||||
use frame_decode::runtime_apis::RuntimeApiTypeInfo;
|
||||
use frame_decode::storage::StorageTypeInfo;
|
||||
use frame_decode::view_functions::ViewFunctionTypeInfo;
|
||||
use frame_metadata::{v15, v16};
|
||||
use hashbrown::HashMap;
|
||||
use scale_info::form::PortableForm;
|
||||
@@ -18,43 +20,67 @@ use scale_info::form::PortableForm;
|
||||
impl TryFrom<v16::RuntimeMetadataV16> for Metadata {
|
||||
type Error = TryFromError;
|
||||
fn try_from(m: v16::RuntimeMetadataV16) -> Result<Self, TryFromError> {
|
||||
let types = m.types;
|
||||
let types = &m.types;
|
||||
|
||||
let mut pallets = OrderedMap::new();
|
||||
let mut pallets_by_index = HashMap::new();
|
||||
for (pos, p) in m.pallets.into_iter().enumerate() {
|
||||
let name: ArcStr = p.name.into();
|
||||
for (pos, p) in m.pallets.iter().enumerate() {
|
||||
let name = p.name.clone();
|
||||
|
||||
let storage = p.storage.map(|s| StorageMetadata {
|
||||
prefix: s.prefix,
|
||||
entries: s
|
||||
.entries
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
let name: ArcStr = s.name.clone().into();
|
||||
(name.clone(), from_storage_entry_metadata(name, s))
|
||||
})
|
||||
.collect(),
|
||||
});
|
||||
let constants = p.constants.into_iter().map(|c| {
|
||||
let name: ArcStr = c.name.clone().into();
|
||||
(name.clone(), from_constant_metadata(name, c))
|
||||
});
|
||||
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 storage = match &p.storage {
|
||||
None => None,
|
||||
Some(s) => Some(StorageMetadata {
|
||||
prefix: s.prefix.clone(),
|
||||
entries: s
|
||||
.entries
|
||||
.iter()
|
||||
.map(|s| {
|
||||
let entry_name = s.name.clone();
|
||||
let storage_info = m
|
||||
.storage_info(&name, &entry_name)
|
||||
.map_err(|e| e.into_owned())?
|
||||
.into_owned();
|
||||
let storage_entry = StorageEntryMetadata {
|
||||
name: entry_name.clone(),
|
||||
info: storage_info,
|
||||
docs: s.docs.clone(),
|
||||
};
|
||||
|
||||
Ok::<_, TryFromError>((entry_name, storage_entry))
|
||||
})
|
||||
.collect::<Result<_, TryFromError>>()?,
|
||||
}),
|
||||
};
|
||||
|
||||
let view_functions = p
|
||||
.view_functions
|
||||
.iter()
|
||||
.map(|vf| {
|
||||
let view_function_metadata = ViewFunctionMetadataInner {
|
||||
name: vf.name.clone(),
|
||||
info: m
|
||||
.view_function_info(&name, &vf.name)
|
||||
.map_err(|e| e.into_owned())?
|
||||
.into_owned(),
|
||||
docs: vf.docs.clone(),
|
||||
};
|
||||
Ok((vf.name.clone(), view_function_metadata))
|
||||
})
|
||||
.collect::<Result<_, TryFromError>>()?;
|
||||
|
||||
let constants = p.constants.iter().map(|c| {
|
||||
let name = c.name.clone();
|
||||
(name, from_constant_metadata(c.clone()))
|
||||
});
|
||||
|
||||
let call_variant_index = VariantIndex::build(p.calls.as_ref().map(|c| c.ty.id), &types);
|
||||
let error_variant_index =
|
||||
VariantIndex::build(p.error.as_ref().map(|e| e.ty.id), &types);
|
||||
let event_variant_index =
|
||||
VariantIndex::build(p.event.as_ref().map(|e| e.ty.id), &types);
|
||||
let call_variant_index = VariantIndex::build(p.calls.as_ref().map(|c| c.ty.id), types);
|
||||
let error_variant_index = VariantIndex::build(p.error.as_ref().map(|e| e.ty.id), types);
|
||||
let event_variant_index = VariantIndex::build(p.event.as_ref().map(|e| e.ty.id), types);
|
||||
|
||||
let associated_types = p
|
||||
.associated_types
|
||||
.into_iter()
|
||||
.map(|t| (t.name, t.ty.id))
|
||||
.iter()
|
||||
.map(|t| (t.name.clone(), t.ty.id))
|
||||
.collect();
|
||||
|
||||
pallets_by_index.insert(p.index, pos);
|
||||
@@ -64,24 +90,50 @@ impl TryFrom<v16::RuntimeMetadataV16> for Metadata {
|
||||
name,
|
||||
index: p.index,
|
||||
storage,
|
||||
call_ty: p.calls.map(|c| c.ty.id),
|
||||
call_ty: p.calls.as_ref().map(|c| c.ty.id),
|
||||
call_variant_index,
|
||||
event_ty: p.event.map(|e| e.ty.id),
|
||||
event_ty: p.event.as_ref().map(|e| e.ty.id),
|
||||
event_variant_index,
|
||||
error_ty: p.error.map(|e| e.ty.id),
|
||||
error_ty: p.error.as_ref().map(|e| e.ty.id),
|
||||
error_variant_index,
|
||||
constants: constants.collect(),
|
||||
view_functions: view_functions.collect(),
|
||||
view_functions,
|
||||
associated_types,
|
||||
docs: p.docs,
|
||||
docs: p.docs.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let apis = m.apis.into_iter().map(|api| {
|
||||
let name: ArcStr = api.name.clone().into();
|
||||
(name.clone(), from_runtime_api_metadata(name, api))
|
||||
});
|
||||
let apis = m
|
||||
.apis
|
||||
.iter()
|
||||
.map(|api| {
|
||||
let trait_name = api.name.clone();
|
||||
let methods = api
|
||||
.methods
|
||||
.iter()
|
||||
.map(|method| {
|
||||
let method_name = method.name.clone();
|
||||
let method_info = RuntimeApiMethodMetadataInner {
|
||||
info: m
|
||||
.runtime_api_info(&trait_name, &method.name)
|
||||
.map_err(|e| e.into_owned())?
|
||||
.into_owned(),
|
||||
name: method.name.clone(),
|
||||
docs: method.docs.clone(),
|
||||
};
|
||||
Ok((method_name, method_info))
|
||||
})
|
||||
.collect::<Result<_, TryFromError>>()?;
|
||||
|
||||
let runtime_api_metadata = RuntimeApiMetadataInner {
|
||||
name: trait_name.clone(),
|
||||
methods,
|
||||
docs: api.docs.clone(),
|
||||
};
|
||||
Ok((trait_name, runtime_api_metadata))
|
||||
})
|
||||
.collect::<Result<_, TryFromError>>()?;
|
||||
|
||||
let custom_map = m
|
||||
.custom
|
||||
@@ -103,12 +155,12 @@ impl TryFrom<v16::RuntimeMetadataV16> for Metadata {
|
||||
.map(|ty| ty.id);
|
||||
|
||||
Ok(Metadata {
|
||||
types,
|
||||
types: m.types,
|
||||
pallets,
|
||||
pallets_by_index,
|
||||
extrinsic: from_extrinsic_metadata(m.extrinsic),
|
||||
dispatch_error_ty,
|
||||
apis: apis.collect(),
|
||||
apis,
|
||||
outer_enums: OuterEnumsMetadata {
|
||||
call_enum_ty: m.outer_enums.call_enum_ty.id,
|
||||
event_enum_ty: m.outer_enums.event_enum_ty.id,
|
||||
@@ -147,118 +199,11 @@ fn from_extrinsic_metadata(value: v16::ExtrinsicMetadata<PortableForm>) -> Extri
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_hasher(value: v16::StorageHasher) -> StorageHasher {
|
||||
match value {
|
||||
v16::StorageHasher::Blake2_128 => StorageHasher::Blake2_128,
|
||||
v16::StorageHasher::Blake2_256 => StorageHasher::Blake2_256,
|
||||
v16::StorageHasher::Blake2_128Concat => StorageHasher::Blake2_128Concat,
|
||||
v16::StorageHasher::Twox128 => StorageHasher::Twox128,
|
||||
v16::StorageHasher::Twox256 => StorageHasher::Twox256,
|
||||
v16::StorageHasher::Twox64Concat => StorageHasher::Twox64Concat,
|
||||
v16::StorageHasher::Identity => StorageHasher::Identity,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_entry_type(value: v16::StorageEntryType<PortableForm>) -> StorageEntryType {
|
||||
match value {
|
||||
v16::StorageEntryType::Plain(ty) => StorageEntryType::Plain(ty.id),
|
||||
v16::StorageEntryType::Map {
|
||||
hashers,
|
||||
key,
|
||||
value,
|
||||
} => StorageEntryType::Map {
|
||||
hashers: hashers.into_iter().map(from_storage_hasher).collect(),
|
||||
key_ty: key.id,
|
||||
value_ty: value.id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_entry_modifier(value: v16::StorageEntryModifier) -> StorageEntryModifier {
|
||||
match value {
|
||||
v16::StorageEntryModifier::Optional => StorageEntryModifier::Optional,
|
||||
v16::StorageEntryModifier::Default => StorageEntryModifier::Default,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_storage_entry_metadata(
|
||||
name: ArcStr,
|
||||
s: v16::StorageEntryMetadata<PortableForm>,
|
||||
) -> StorageEntryMetadata {
|
||||
StorageEntryMetadata {
|
||||
name,
|
||||
modifier: from_storage_entry_modifier(s.modifier),
|
||||
entry_type: from_storage_entry_type(s.ty),
|
||||
default: s.default,
|
||||
docs: s.docs,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_constant_metadata(
|
||||
name: ArcStr,
|
||||
s: v16::PalletConstantMetadata<PortableForm>,
|
||||
) -> ConstantMetadata {
|
||||
fn from_constant_metadata(s: v16::PalletConstantMetadata<PortableForm>) -> ConstantMetadata {
|
||||
ConstantMetadata {
|
||||
name,
|
||||
name: s.name,
|
||||
ty: s.ty.id,
|
||||
value: s.value,
|
||||
docs: s.docs,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_runtime_api_metadata(
|
||||
name: ArcStr,
|
||||
s: v16::RuntimeApiMetadata<PortableForm>,
|
||||
) -> RuntimeApiMetadataInner {
|
||||
RuntimeApiMetadataInner {
|
||||
name,
|
||||
docs: s.docs,
|
||||
methods: s
|
||||
.methods
|
||||
.into_iter()
|
||||
.map(|m| {
|
||||
let name: ArcStr = m.name.clone().into();
|
||||
(name.clone(), from_runtime_api_method_metadata(name, m))
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_runtime_api_method_metadata(
|
||||
name: ArcStr,
|
||||
s: v16::RuntimeApiMethodMetadata<PortableForm>,
|
||||
) -> RuntimeApiMethodMetadataInner {
|
||||
RuntimeApiMethodMetadataInner {
|
||||
name,
|
||||
inputs: s
|
||||
.inputs
|
||||
.into_iter()
|
||||
.map(|param| MethodParamMetadata {
|
||||
name: param.name,
|
||||
ty: param.ty.id,
|
||||
})
|
||||
.collect(),
|
||||
output_ty: s.output.id,
|
||||
docs: s.docs,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_view_function_metadata(
|
||||
name: ArcStr,
|
||||
s: v16::PalletViewFunctionMetadata<PortableForm>,
|
||||
) -> ViewFunctionMetadataInner {
|
||||
ViewFunctionMetadataInner {
|
||||
name,
|
||||
query_id: s.id,
|
||||
inputs: s
|
||||
.inputs
|
||||
.into_iter()
|
||||
.map(|param| MethodParamMetadata {
|
||||
name: param.name,
|
||||
ty: param.ty.id,
|
||||
})
|
||||
.collect(),
|
||||
output_ty: s.output.id,
|
||||
docs: s.docs,
|
||||
}
|
||||
}
|
||||
|
||||
+251
-155
@@ -24,13 +24,22 @@ mod utils;
|
||||
|
||||
use alloc::borrow::Cow;
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::string::String;
|
||||
use alloc::sync::Arc;
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::vec::Vec;
|
||||
use frame_decode::constants::{Constant, ConstantInfo, ConstantInfoError};
|
||||
use frame_decode::custom_values::{CustomValue, CustomValueInfo, CustomValueInfoError};
|
||||
use frame_decode::extrinsics::{
|
||||
ExtrinsicCallInfo, ExtrinsicExtensionInfo, ExtrinsicInfoArg, ExtrinsicInfoError,
|
||||
ExtrinsicSignatureInfo,
|
||||
};
|
||||
use frame_decode::runtime_apis::{
|
||||
RuntimeApi, RuntimeApiInfo, RuntimeApiInfoError, RuntimeApiInput,
|
||||
};
|
||||
use frame_decode::storage::{StorageEntry, StorageInfo, StorageInfoError, StorageKeyInfo};
|
||||
use frame_decode::view_functions::{
|
||||
ViewFunction, ViewFunctionInfo, ViewFunctionInfoError, ViewFunctionInput,
|
||||
};
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use scale_info::{PortableRegistry, Variant, form::PortableForm};
|
||||
use utils::{
|
||||
@@ -39,8 +48,7 @@ use utils::{
|
||||
variant_index::VariantIndex,
|
||||
};
|
||||
|
||||
type ArcStr = Arc<str>;
|
||||
|
||||
pub use frame_decode::storage::StorageHasher;
|
||||
pub use from::SUPPORTED_METADATA_VERSIONS;
|
||||
pub use from::TryFromError;
|
||||
pub use utils::validation::MetadataHasher;
|
||||
@@ -55,7 +63,7 @@ pub struct Metadata {
|
||||
/// Type registry containing all types used in the metadata.
|
||||
types: PortableRegistry,
|
||||
/// Metadata of all the pallets.
|
||||
pallets: OrderedMap<ArcStr, PalletMetadataInner>,
|
||||
pallets: OrderedMap<String, PalletMetadataInner>,
|
||||
/// Find the location in the pallet Vec by pallet index.
|
||||
pallets_by_index: HashMap<u8, usize>,
|
||||
/// Metadata of the extrinsic.
|
||||
@@ -65,7 +73,7 @@ pub struct Metadata {
|
||||
/// The type Id of the `DispatchError` type, which Subxt makes use of.
|
||||
dispatch_error_ty: Option<u32>,
|
||||
/// Details about each of the runtime API traits.
|
||||
apis: OrderedMap<ArcStr, RuntimeApiMetadataInner>,
|
||||
apis: OrderedMap<String, RuntimeApiMetadataInner>,
|
||||
/// Allows users to add custom types to the metadata. A map that associates a string key to a `CustomValueMetadata`.
|
||||
custom: CustomMetadataInner,
|
||||
}
|
||||
@@ -75,7 +83,7 @@ pub struct Metadata {
|
||||
impl frame_decode::extrinsics::ExtrinsicTypeInfo for Metadata {
|
||||
type TypeId = u32;
|
||||
|
||||
fn get_call_info(
|
||||
fn extrinsic_call_info(
|
||||
&self,
|
||||
pallet_index: u8,
|
||||
call_index: u8,
|
||||
@@ -108,7 +116,7 @@ impl frame_decode::extrinsics::ExtrinsicTypeInfo for Metadata {
|
||||
})
|
||||
}
|
||||
|
||||
fn get_signature_info(
|
||||
fn extrinsic_signature_info(
|
||||
&self,
|
||||
) -> Result<ExtrinsicSignatureInfo<Self::TypeId>, ExtrinsicInfoError<'_>> {
|
||||
Ok(ExtrinsicSignatureInfo {
|
||||
@@ -117,7 +125,7 @@ impl frame_decode::extrinsics::ExtrinsicTypeInfo for Metadata {
|
||||
})
|
||||
}
|
||||
|
||||
fn get_extension_info(
|
||||
fn extrinsic_extension_info(
|
||||
&self,
|
||||
extension_version: Option<u8>,
|
||||
) -> Result<ExtrinsicExtensionInfo<'_, Self::TypeId>, ExtrinsicInfoError<'_>> {
|
||||
@@ -142,8 +150,203 @@ impl frame_decode::extrinsics::ExtrinsicTypeInfo for Metadata {
|
||||
Ok(ExtrinsicExtensionInfo { extension_ids })
|
||||
}
|
||||
}
|
||||
impl frame_decode::storage::StorageTypeInfo for Metadata {
|
||||
type TypeId = u32;
|
||||
|
||||
fn storage_info(
|
||||
&self,
|
||||
pallet_name: &str,
|
||||
storage_entry: &str,
|
||||
) -> Result<StorageInfo<'_, Self::TypeId>, StorageInfoError<'_>> {
|
||||
let pallet =
|
||||
self.pallet_by_name(pallet_name)
|
||||
.ok_or_else(|| StorageInfoError::PalletNotFound {
|
||||
pallet_name: pallet_name.to_string(),
|
||||
})?;
|
||||
let entry = pallet
|
||||
.storage()
|
||||
.and_then(|storage| storage.entry_by_name(storage_entry))
|
||||
.ok_or_else(|| StorageInfoError::StorageNotFound {
|
||||
name: storage_entry.to_string(),
|
||||
pallet_name: Cow::Borrowed(pallet.name()),
|
||||
})?;
|
||||
|
||||
let info = StorageInfo {
|
||||
keys: Cow::Borrowed(&*entry.info.keys),
|
||||
value_id: entry.info.value_id,
|
||||
default_value: entry
|
||||
.info
|
||||
.default_value
|
||||
.as_ref()
|
||||
.map(|def| Cow::Borrowed(&**def)),
|
||||
};
|
||||
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
fn storage_entries(&self) -> impl Iterator<Item = StorageEntry<'_>> {
|
||||
self.pallets().flat_map(|pallet| {
|
||||
let pallet_name = pallet.name();
|
||||
pallet.storage().into_iter().flat_map(|storage| {
|
||||
storage.entries().iter().map(|entry| StorageEntry {
|
||||
pallet_name: Cow::Borrowed(pallet_name),
|
||||
storage_entry: Cow::Borrowed(entry.name()),
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
impl frame_decode::runtime_apis::RuntimeApiTypeInfo for Metadata {
|
||||
type TypeId = u32;
|
||||
|
||||
fn runtime_api_info(
|
||||
&self,
|
||||
trait_name: &str,
|
||||
method_name: &str,
|
||||
) -> Result<RuntimeApiInfo<'_, Self::TypeId>, RuntimeApiInfoError<'_>> {
|
||||
let api_trait =
|
||||
self.apis
|
||||
.get_by_key(trait_name)
|
||||
.ok_or_else(|| RuntimeApiInfoError::TraitNotFound {
|
||||
trait_name: trait_name.to_string(),
|
||||
})?;
|
||||
let api_method = api_trait.methods.get_by_key(method_name).ok_or_else(|| {
|
||||
RuntimeApiInfoError::MethodNotFound {
|
||||
trait_name: Cow::Borrowed(&api_trait.name),
|
||||
method_name: method_name.to_string(),
|
||||
}
|
||||
})?;
|
||||
|
||||
let info = RuntimeApiInfo {
|
||||
inputs: Cow::Borrowed(&api_method.info.inputs),
|
||||
output_id: api_method.info.output_id,
|
||||
};
|
||||
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
fn runtime_apis(&self) -> impl Iterator<Item = RuntimeApi<'_>> {
|
||||
self.runtime_api_traits().flat_map(|api_trait| {
|
||||
let trait_name = api_trait.name();
|
||||
api_trait.methods().map(|method| RuntimeApi {
|
||||
trait_name: Cow::Borrowed(trait_name),
|
||||
method_name: Cow::Borrowed(method.name()),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
impl frame_decode::view_functions::ViewFunctionTypeInfo for Metadata {
|
||||
type TypeId = u32;
|
||||
|
||||
fn view_function_info(
|
||||
&self,
|
||||
pallet_name: &str,
|
||||
function_name: &str,
|
||||
) -> Result<ViewFunctionInfo<'_, Self::TypeId>, ViewFunctionInfoError<'_>> {
|
||||
let pallet = self.pallet_by_name(pallet_name).ok_or_else(|| {
|
||||
ViewFunctionInfoError::PalletNotFound {
|
||||
pallet_name: pallet_name.to_string(),
|
||||
}
|
||||
})?;
|
||||
let function = pallet.view_function_by_name(function_name).ok_or_else(|| {
|
||||
ViewFunctionInfoError::FunctionNotFound {
|
||||
pallet_name: Cow::Borrowed(pallet.name()),
|
||||
function_name: function_name.to_string(),
|
||||
}
|
||||
})?;
|
||||
|
||||
let info = ViewFunctionInfo {
|
||||
inputs: Cow::Borrowed(&function.inner.info.inputs),
|
||||
output_id: function.inner.info.output_id,
|
||||
query_id: *function.query_id(),
|
||||
};
|
||||
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
fn view_functions(&self) -> impl Iterator<Item = ViewFunction<'_>> {
|
||||
self.pallets().flat_map(|pallet| {
|
||||
let pallet_name = pallet.name();
|
||||
pallet.view_functions().map(|function| ViewFunction {
|
||||
pallet_name: Cow::Borrowed(pallet_name),
|
||||
function_name: Cow::Borrowed(function.name()),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
impl frame_decode::constants::ConstantTypeInfo for Metadata {
|
||||
type TypeId = u32;
|
||||
|
||||
fn constant_info(
|
||||
&self,
|
||||
pallet_name: &str,
|
||||
constant_name: &str,
|
||||
) -> Result<ConstantInfo<'_, Self::TypeId>, ConstantInfoError<'_>> {
|
||||
let pallet =
|
||||
self.pallet_by_name(pallet_name)
|
||||
.ok_or_else(|| ConstantInfoError::PalletNotFound {
|
||||
pallet_name: pallet_name.to_string(),
|
||||
})?;
|
||||
let constant = pallet.constant_by_name(constant_name).ok_or_else(|| {
|
||||
ConstantInfoError::ConstantNotFound {
|
||||
pallet_name: Cow::Borrowed(pallet.name()),
|
||||
constant_name: constant_name.to_string(),
|
||||
}
|
||||
})?;
|
||||
|
||||
let info = ConstantInfo {
|
||||
bytes: &constant.value,
|
||||
type_id: constant.ty,
|
||||
};
|
||||
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
fn constants(&self) -> impl Iterator<Item = Constant<'_>> {
|
||||
self.pallets().flat_map(|pallet| {
|
||||
let pallet_name = pallet.name();
|
||||
pallet.constants().map(|constant| Constant {
|
||||
pallet_name: Cow::Borrowed(pallet_name),
|
||||
constant_name: Cow::Borrowed(constant.name()),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
impl frame_decode::custom_values::CustomValueTypeInfo for Metadata {
|
||||
type TypeId = u32;
|
||||
|
||||
fn custom_value_info(
|
||||
&self,
|
||||
name: &str,
|
||||
) -> Result<CustomValueInfo<'_, Self::TypeId>, CustomValueInfoError> {
|
||||
let custom_value = self
|
||||
.custom()
|
||||
.get(name)
|
||||
.ok_or_else(|| CustomValueInfoError {
|
||||
not_found: name.to_string(),
|
||||
})?;
|
||||
|
||||
let info = CustomValueInfo {
|
||||
bytes: custom_value.data,
|
||||
type_id: custom_value.type_id,
|
||||
};
|
||||
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
fn custom_values(&self) -> impl Iterator<Item = CustomValue<'_>> {
|
||||
self.custom.map.keys().map(|name| CustomValue {
|
||||
name: Cow::Borrowed(name),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
/// This is essentiall an alias for `<Metadata as codec::Decode>::decode(&mut bytes)`
|
||||
pub fn decode_from(mut bytes: &[u8]) -> Result<Self, codec::Error> {
|
||||
<Self as codec::Decode>::decode(&mut bytes)
|
||||
}
|
||||
|
||||
/// Access the underlying type registry.
|
||||
pub fn types(&self) -> &PortableRegistry {
|
||||
&self.types
|
||||
@@ -217,20 +420,6 @@ 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 {
|
||||
@@ -418,7 +607,7 @@ impl<'a> PalletMetadata<'a> {
|
||||
#[derive(Debug, Clone)]
|
||||
struct PalletMetadataInner {
|
||||
/// Pallet name.
|
||||
name: ArcStr,
|
||||
name: String,
|
||||
/// Pallet index.
|
||||
index: u8,
|
||||
/// Pallet storage metadata.
|
||||
@@ -436,9 +625,9 @@ struct PalletMetadataInner {
|
||||
/// Error variants by name/u8.
|
||||
error_variant_index: VariantIndex,
|
||||
/// Map from constant name to constant details.
|
||||
constants: OrderedMap<ArcStr, ConstantMetadata>,
|
||||
constants: OrderedMap<String, ConstantMetadata>,
|
||||
/// Details about each of the pallet view functions.
|
||||
view_functions: OrderedMap<ArcStr, ViewFunctionMetadataInner>,
|
||||
view_functions: OrderedMap<String, ViewFunctionMetadataInner>,
|
||||
/// Mapping from associated type to type ID describing its shape.
|
||||
associated_types: BTreeMap<String, u32>,
|
||||
/// Pallet documentation.
|
||||
@@ -451,7 +640,7 @@ pub struct StorageMetadata {
|
||||
/// The common prefix used by all storage entries.
|
||||
prefix: String,
|
||||
/// Map from storage entry name to details.
|
||||
entries: OrderedMap<ArcStr, StorageEntryMetadata>,
|
||||
entries: OrderedMap<String, StorageEntryMetadata>,
|
||||
}
|
||||
|
||||
impl StorageMetadata {
|
||||
@@ -475,13 +664,9 @@ impl StorageMetadata {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StorageEntryMetadata {
|
||||
/// Variable name of the storage entry.
|
||||
name: ArcStr,
|
||||
/// An `Option` modifier of that storage entry.
|
||||
modifier: StorageEntryModifier,
|
||||
/// Type of the value stored in the entry.
|
||||
entry_type: StorageEntryType,
|
||||
/// Default value (SCALE encoded).
|
||||
default: Vec<u8>,
|
||||
name: String,
|
||||
/// Information about the storage entry.
|
||||
info: StorageInfo<'static, u32>,
|
||||
/// Storage entry documentation.
|
||||
docs: Vec<String>,
|
||||
}
|
||||
@@ -491,17 +676,18 @@ impl StorageEntryMetadata {
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
/// Is the entry value optional or does it have a default value.
|
||||
pub fn modifier(&self) -> StorageEntryModifier {
|
||||
self.modifier
|
||||
/// Keys in this storage entry.
|
||||
pub fn keys(&self) -> impl ExactSizeIterator<Item = &StorageKeyInfo<u32>> {
|
||||
let keys = &*self.info.keys;
|
||||
keys.iter()
|
||||
}
|
||||
/// Type of the storage entry.
|
||||
pub fn entry_type(&self) -> &StorageEntryType {
|
||||
&self.entry_type
|
||||
/// Value type for this storage entry.
|
||||
pub fn value_ty(&self) -> u32 {
|
||||
self.info.value_id
|
||||
}
|
||||
/// The SCALE encoded default value for this entry.
|
||||
pub fn default_bytes(&self) -> &[u8] {
|
||||
&self.default
|
||||
/// The default value, if one exists, for this entry.
|
||||
pub fn default_value(&self) -> Option<&[u8]> {
|
||||
self.info.default_value.as_deref()
|
||||
}
|
||||
/// Storage entry documentation.
|
||||
pub fn docs(&self) -> &[String] {
|
||||
@@ -509,101 +695,11 @@ impl StorageEntryMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of a storage entry.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum StorageEntryType {
|
||||
/// Plain storage entry (just the value).
|
||||
Plain(u32),
|
||||
/// A storage map.
|
||||
Map {
|
||||
/// One or more hashers, should be one hasher per key element.
|
||||
hashers: Vec<StorageHasher>,
|
||||
/// The type of the key, can be a tuple with elements for each of the hashers.
|
||||
key_ty: u32,
|
||||
/// The type of the value.
|
||||
value_ty: u32,
|
||||
},
|
||||
}
|
||||
|
||||
impl StorageEntryType {
|
||||
/// The type of the value.
|
||||
pub fn value_ty(&self) -> u32 {
|
||||
match self {
|
||||
StorageEntryType::Map { value_ty, .. } | StorageEntryType::Plain(value_ty) => *value_ty,
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of the key, can be a tuple with elements for each of the hashers. None for a Plain storage entry.
|
||||
pub fn key_ty(&self) -> Option<u32> {
|
||||
match self {
|
||||
StorageEntryType::Map { key_ty, .. } => Some(*key_ty),
|
||||
StorageEntryType::Plain(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Hasher used by storage maps.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum StorageHasher {
|
||||
/// 128-bit Blake2 hash.
|
||||
Blake2_128,
|
||||
/// 256-bit Blake2 hash.
|
||||
Blake2_256,
|
||||
/// Multiple 128-bit Blake2 hashes concatenated.
|
||||
Blake2_128Concat,
|
||||
/// 128-bit XX hash.
|
||||
Twox128,
|
||||
/// 256-bit XX hash.
|
||||
Twox256,
|
||||
/// Multiple 64-bit XX hashes concatenated.
|
||||
Twox64Concat,
|
||||
/// Identity hashing (no hashing).
|
||||
Identity,
|
||||
}
|
||||
|
||||
impl StorageHasher {
|
||||
/// The hash produced by a [`StorageHasher`] can have these two components, in order:
|
||||
///
|
||||
/// 1. A fixed size hash. (not present for [`StorageHasher::Identity`]).
|
||||
/// 2. The SCALE encoded key that was used as an input to the hasher (only present for
|
||||
/// [`StorageHasher::Twox64Concat`], [`StorageHasher::Blake2_128Concat`] or [`StorageHasher::Identity`]).
|
||||
///
|
||||
/// This function returns the number of bytes used to represent the first of these.
|
||||
pub fn len_excluding_key(&self) -> usize {
|
||||
match self {
|
||||
StorageHasher::Blake2_128Concat => 16,
|
||||
StorageHasher::Twox64Concat => 8,
|
||||
StorageHasher::Blake2_128 => 16,
|
||||
StorageHasher::Blake2_256 => 32,
|
||||
StorageHasher::Twox128 => 16,
|
||||
StorageHasher::Twox256 => 32,
|
||||
StorageHasher::Identity => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the key used to produce the hash is appended to the hash itself.
|
||||
pub fn ends_with_key(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
StorageHasher::Blake2_128Concat | StorageHasher::Twox64Concat | StorageHasher::Identity
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Is the storage entry optional, or does it have a default value.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum StorageEntryModifier {
|
||||
/// The storage entry returns an `Option<T>`, with `None` if the key is not present.
|
||||
Optional,
|
||||
/// The storage entry returns `T::Default` if the key is not present.
|
||||
Default,
|
||||
}
|
||||
|
||||
/// Metadata for a single constant.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConstantMetadata {
|
||||
/// Name of the pallet constant.
|
||||
name: ArcStr,
|
||||
name: String,
|
||||
/// Type of the pallet constant.
|
||||
ty: u32,
|
||||
/// Value stored in the constant (SCALE encoded).
|
||||
@@ -816,9 +912,9 @@ impl<'a> RuntimeApiMetadata<'a> {
|
||||
#[derive(Debug, Clone)]
|
||||
struct RuntimeApiMetadataInner {
|
||||
/// Trait name.
|
||||
name: ArcStr,
|
||||
name: String,
|
||||
/// Trait methods.
|
||||
methods: OrderedMap<ArcStr, RuntimeApiMethodMetadataInner>,
|
||||
methods: OrderedMap<String, RuntimeApiMethodMetadataInner>,
|
||||
/// Trait documentation.
|
||||
docs: Vec<String>,
|
||||
}
|
||||
@@ -841,12 +937,15 @@ impl<'a> RuntimeApiMethodMetadata<'a> {
|
||||
&self.inner.docs
|
||||
}
|
||||
/// Method inputs.
|
||||
pub fn inputs(&self) -> impl ExactSizeIterator<Item = &'a MethodParamMetadata> + use<'a> {
|
||||
self.inner.inputs.iter()
|
||||
pub fn inputs(
|
||||
&self,
|
||||
) -> impl ExactSizeIterator<Item = &'a RuntimeApiInput<'static, u32>> + use<'a> {
|
||||
let inputs = &*self.inner.info.inputs;
|
||||
inputs.iter()
|
||||
}
|
||||
/// Method return type.
|
||||
pub fn output_ty(&self) -> u32 {
|
||||
self.inner.output_ty
|
||||
self.inner.info.output_id
|
||||
}
|
||||
/// Return a hash for the method.
|
||||
pub fn hash(&self) -> [u8; HASH_LEN] {
|
||||
@@ -857,11 +956,9 @@ impl<'a> RuntimeApiMethodMetadata<'a> {
|
||||
#[derive(Debug, Clone)]
|
||||
struct RuntimeApiMethodMetadataInner {
|
||||
/// Method name.
|
||||
name: ArcStr,
|
||||
/// Method parameters.
|
||||
inputs: Vec<MethodParamMetadata>,
|
||||
/// Method output type.
|
||||
output_ty: u32,
|
||||
name: String,
|
||||
/// Info.
|
||||
info: RuntimeApiInfo<'static, u32>,
|
||||
/// Method documentation.
|
||||
docs: Vec<String>,
|
||||
}
|
||||
@@ -882,19 +979,22 @@ impl<'a> ViewFunctionMetadata<'a> {
|
||||
/// 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) -> &'a [u8; 32] {
|
||||
&self.inner.query_id
|
||||
&self.inner.info.query_id
|
||||
}
|
||||
/// Method documentation.
|
||||
pub fn docs(&self) -> &'a [String] {
|
||||
&self.inner.docs
|
||||
}
|
||||
/// Method inputs.
|
||||
pub fn inputs(&self) -> impl ExactSizeIterator<Item = &'a MethodParamMetadata> + use<'a> {
|
||||
self.inner.inputs.iter()
|
||||
pub fn inputs(
|
||||
&self,
|
||||
) -> impl ExactSizeIterator<Item = &'a ViewFunctionInput<'static, u32>> + use<'a> {
|
||||
let inputs = &*self.inner.info.inputs;
|
||||
inputs.iter()
|
||||
}
|
||||
/// Method return type.
|
||||
pub fn output_ty(&self) -> u32 {
|
||||
self.inner.output_ty
|
||||
self.inner.info.output_id
|
||||
}
|
||||
/// 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
|
||||
@@ -907,13 +1007,9 @@ impl<'a> ViewFunctionMetadata<'a> {
|
||||
#[derive(Debug, Clone)]
|
||||
struct ViewFunctionMetadataInner {
|
||||
/// View function name.
|
||||
name: ArcStr,
|
||||
/// View function query ID.
|
||||
query_id: [u8; 32],
|
||||
/// Input types.
|
||||
inputs: Vec<MethodParamMetadata>,
|
||||
/// Output type.
|
||||
output_ty: u32,
|
||||
name: String,
|
||||
/// Info.
|
||||
info: ViewFunctionInfo<'static, u32>,
|
||||
/// Documentation.
|
||||
docs: Vec<String>,
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
|
||||
use crate::{
|
||||
CustomMetadata, CustomValueMetadata, ExtrinsicMetadata, Metadata, PalletMetadata,
|
||||
RuntimeApiMetadata, RuntimeApiMethodMetadata, StorageEntryMetadata, StorageEntryType,
|
||||
ViewFunctionMetadata,
|
||||
RuntimeApiMetadata, RuntimeApiMethodMetadata, StorageEntryMetadata, ViewFunctionMetadata,
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
use hashbrown::HashMap;
|
||||
@@ -290,29 +289,19 @@ fn get_extrinsic_hash(registry: &PortableRegistry, extrinsic: &ExtrinsicMetadata
|
||||
fn get_storage_entry_hash(registry: &PortableRegistry, entry: &StorageEntryMetadata) -> Hash {
|
||||
let mut bytes = concat_and_hash3(
|
||||
&hash(entry.name.as_bytes()),
|
||||
// Cloning 'entry.modifier' should essentially be a copy.
|
||||
&[entry.modifier as u8; HASH_LEN],
|
||||
&hash(&entry.default),
|
||||
&get_type_hash(registry, entry.info.value_id),
|
||||
&hash(entry.info.default_value.as_deref().unwrap_or_default()),
|
||||
);
|
||||
|
||||
match &entry.entry_type {
|
||||
StorageEntryType::Plain(ty) => concat_and_hash2(&bytes, &get_type_hash(registry, *ty)),
|
||||
StorageEntryType::Map {
|
||||
hashers,
|
||||
key_ty,
|
||||
value_ty,
|
||||
} => {
|
||||
for hasher in hashers {
|
||||
// Cloning the hasher should essentially be a copy.
|
||||
bytes = concat_and_hash2(&bytes, &[*hasher as u8; HASH_LEN]);
|
||||
}
|
||||
concat_and_hash3(
|
||||
&bytes,
|
||||
&get_type_hash(registry, *key_ty),
|
||||
&get_type_hash(registry, *value_ty),
|
||||
)
|
||||
}
|
||||
for key in &*entry.info.keys {
|
||||
bytes = concat_and_hash3(
|
||||
&bytes,
|
||||
&[key.hasher as u8; HASH_LEN],
|
||||
&get_type_hash(registry, key.key_id),
|
||||
)
|
||||
}
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
fn get_custom_metadata_hash(custom_metadata: &CustomMetadata) -> Hash {
|
||||
@@ -382,7 +371,7 @@ pub fn get_runtime_api_hash(runtime_api: &RuntimeApiMethodMetadata) -> Hash {
|
||||
bytes = concat_and_hash3(
|
||||
&bytes,
|
||||
&hash(input.name.as_bytes()),
|
||||
&get_type_hash(registry, input.ty),
|
||||
&get_type_hash(registry, input.id),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -419,7 +408,7 @@ pub fn get_view_function_hash(view_function: &ViewFunctionMetadata) -> Hash {
|
||||
bytes = concat_and_hash3(
|
||||
&bytes,
|
||||
&hash(input.name.as_bytes()),
|
||||
&get_type_hash(registry, input.ty),
|
||||
&get_type_hash(registry, input.id),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,10 +21,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
continue; // we do not look at inherents in this example
|
||||
};
|
||||
|
||||
let meta = ext.extrinsic_metadata()?;
|
||||
let fields = ext.field_values()?;
|
||||
// Decode the fields into our dynamic Value type to display:
|
||||
let fields = ext.decode_as_fields::<scale_value::Value>()?;
|
||||
|
||||
println!(" {}/{}", meta.pallet.name(), meta.variant.name);
|
||||
println!(" {}/{}", ext.pallet_name(), ext.call_name());
|
||||
println!(" Transaction Extensions:");
|
||||
for signed_ext in transaction_extensions.iter() {
|
||||
// We only want to take a look at these 3 signed extensions, because the others all just have unit fields.
|
||||
|
||||
@@ -42,7 +42,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let evt = evt?;
|
||||
let pallet_name = evt.pallet_name();
|
||||
let event_name = evt.variant_name();
|
||||
let event_values = evt.field_values()?;
|
||||
let event_values = evt.decode_as_fields::<scale_value::Value>()?;
|
||||
|
||||
println!(" {pallet_name}_{event_name}");
|
||||
println!(" {event_values}");
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#![allow(missing_docs)]
|
||||
use subxt::dynamic::Value;
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
|
||||
#[tokio::main]
|
||||
@@ -6,13 +7,20 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create a client to use:
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// A dynamic query to obtain some constant:
|
||||
let constant_query = subxt::dynamic::constant("System", "BlockLength");
|
||||
// We can query a constant by providing a tuple of the pallet and constant name. The return type
|
||||
// will be `Value` if we pass this query:
|
||||
let constant_query = ("System", "BlockLength");
|
||||
let _value = api.constants().at(&constant_query)?;
|
||||
|
||||
// Obtain the value:
|
||||
// Or we can use the library function to query a constant, which allows us to pass a generic type
|
||||
// that Subxt will attempt to decode the constant into:
|
||||
let constant_query = subxt::dynamic::constant::<Value>("System", "BlockLength");
|
||||
let value = api.constants().at(&constant_query)?;
|
||||
|
||||
println!("Constant bytes: {:?}", value.encoded());
|
||||
println!("Constant value: {}", value.to_value()?);
|
||||
// Or we can obtain the bytes for the constant, using either form of query.
|
||||
let bytes = api.constants().bytes_at(&constant_query)?;
|
||||
|
||||
println!("Constant bytes: {:?}", bytes);
|
||||
println!("Constant value: {}", value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Obtain the value:
|
||||
let value = api.constants().at(&constant_query)?;
|
||||
|
||||
// Or obtain the bytes:
|
||||
let bytes = api.constants().bytes_at(&constant_query)?;
|
||||
|
||||
println!("Encoded block length: {bytes:?}");
|
||||
println!("Block length: {value:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
let pallet = event.pallet_name();
|
||||
let variant = event.variant_name();
|
||||
let field_values = event.field_values()?;
|
||||
let field_values = event.decode_as_fields::<scale_value::Value>()?;
|
||||
|
||||
println!("{pallet}::{variant}: {field_values}");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#![allow(missing_docs)]
|
||||
use subxt::dynamic::Value;
|
||||
use subxt::utils::AccountId32;
|
||||
use subxt::{OnlineClient, config::PolkadotConfig};
|
||||
use subxt_signer::sr25519::dev;
|
||||
|
||||
@@ -8,14 +8,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create a client to use:
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Create a dynamically runtime API payload that calls the
|
||||
// `AccountNonceApi_account_nonce` function.
|
||||
let account = dev::alice().public_key();
|
||||
let runtime_api_call = subxt::dynamic::runtime_api_call(
|
||||
"AccountNonceApi",
|
||||
"account_nonce",
|
||||
vec![Value::from_bytes(account)],
|
||||
);
|
||||
// Create a "dynamic" runtime API payload that calls the
|
||||
// `AccountNonceApi_account_nonce` function. We could use the
|
||||
// `scale_value::Value` type as output, and a vec of those as inputs,
|
||||
// but since we know the input + return types we can pass them directly.
|
||||
// There is one input argument, so the inputs are a tuple of one element.
|
||||
let account: AccountId32 = dev::alice().public_key().into();
|
||||
let runtime_api_call =
|
||||
subxt::dynamic::runtime_api_call::<_, u64>("AccountNonceApi", "account_nonce", (account,));
|
||||
|
||||
// Submit the call to get back a result.
|
||||
let nonce = api
|
||||
@@ -25,6 +25,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.call(runtime_api_call)
|
||||
.await?;
|
||||
|
||||
println!("Account nonce: {:#?}", nonce.to_value());
|
||||
println!("Account nonce: {:#?}", nonce);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -10,22 +10,23 @@ pub mod polkadot {}
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create a new API client, configured to talk to Polkadot nodes.
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
let account = dev::alice().public_key().into();
|
||||
|
||||
// Build a storage query to access account information.
|
||||
let account = dev::alice().public_key().into();
|
||||
let storage_query = polkadot::storage().system().account(account);
|
||||
let storage_query = polkadot::storage().system().account();
|
||||
|
||||
// Use that query to `fetch` a result. This returns an `Option<_>`, which will be
|
||||
// `None` if no value exists at the given address. You can also use `fetch_default`
|
||||
// where applicable, which will return the default value if none exists.
|
||||
let result = api
|
||||
.storage()
|
||||
.at_latest()
|
||||
// Use that query to access a storage entry, fetch a result and decode the value.
|
||||
// The static address knows that fetching requires a tuple of one value, an
|
||||
// AccountId32.
|
||||
let client_at = api.storage().at_latest().await?;
|
||||
let account_info = client_at
|
||||
.entry(storage_query)?
|
||||
.fetch((account,))
|
||||
.await?
|
||||
.fetch(&storage_query)
|
||||
.await?;
|
||||
.decode()?;
|
||||
|
||||
let v = result.unwrap().data.free;
|
||||
println!("Alice: {v}");
|
||||
// The static address that we got from the subxt macro knows the expected input
|
||||
// and return types, so it is decoded into a static type for us.
|
||||
println!("Alice: {account_info:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#![allow(missing_docs)]
|
||||
use subxt::dynamic::{At, Value};
|
||||
use subxt::utils::AccountId32;
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
use subxt_signer::sr25519::dev;
|
||||
|
||||
@@ -9,20 +10,25 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Build a dynamic storage query to access account information.
|
||||
let account = dev::alice().public_key();
|
||||
let storage_query =
|
||||
subxt::dynamic::storage("System", "Account", vec![Value::from_bytes(account)]);
|
||||
// here, we assume that there is one value to provide at this entry
|
||||
// to access a value; an AccountId32. In this example we don't know the
|
||||
// return type and so we set it to `Value`, which anything can decode into.
|
||||
let account: AccountId32 = dev::alice().public_key().into();
|
||||
let storage_query = subxt::dynamic::storage::<(AccountId32,), Value>("System", "Account");
|
||||
|
||||
// Use that query to `fetch` a result. Because the query is dynamic, we don't know what the result
|
||||
// type will be either, and so we get a type back that can be decoded into a dynamic Value type.
|
||||
let result = api
|
||||
.storage()
|
||||
.at_latest()
|
||||
// Use that query to access a storage entry, fetch a result and decode the value.
|
||||
let client_at = api.storage().at_latest().await?;
|
||||
let account_info = client_at
|
||||
.entry(storage_query)?
|
||||
.fetch((account,))
|
||||
.await?
|
||||
.fetch(&storage_query)
|
||||
.await?;
|
||||
let value = result.unwrap().to_value()?;
|
||||
.decode()?;
|
||||
|
||||
println!("Alice has free balance: {:?}", value.at("data").at("free"));
|
||||
// With out `Value` type we can dig in to find what we want using the `At`
|
||||
// trait and `.at()` method that this provides on the Value.
|
||||
println!(
|
||||
"Alice has free balance: {}",
|
||||
account_info.at("data").at("free").unwrap()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#![allow(missing_docs)]
|
||||
use subxt::ext::futures::StreamExt;
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale")]
|
||||
// Generate an interface that we can use from the node's metadata.
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
#[tokio::main]
|
||||
@@ -9,17 +11,31 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create a new API client, configured to talk to Polkadot nodes.
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Build a storage query to iterate over account information.
|
||||
let storage_query = polkadot::storage().system().account_iter();
|
||||
// Build a storage query to access account information. Same as if we were
|
||||
// fetching a single value from this entry.
|
||||
let storage_query = polkadot::storage().system().account();
|
||||
|
||||
// Get back an iterator of results (here, we are fetching 10 items at
|
||||
// a time from the node, but we always iterate over one at a time).
|
||||
let mut results = api.storage().at_latest().await?.iter(storage_query).await?;
|
||||
// Use that query to access a storage entry, iterate over it and decode values.
|
||||
let client_at = api.storage().at_latest().await?;
|
||||
|
||||
while let Some(Ok(kv)) = results.next().await {
|
||||
println!("Keys decoded: {:?}", kv.keys);
|
||||
println!("Key: 0x{}", hex::encode(&kv.key_bytes));
|
||||
println!("Value: {:?}", kv.value);
|
||||
// We provide an empty tuple when iterating. If the storage entry had been an N map with
|
||||
// multiple keys, then we could provide any prefix of those keys to iterate over. This is
|
||||
// statically type checked, so only a valid number/type of keys in the tuple is accepted.
|
||||
let mut values = client_at.entry(storage_query)?.iter(()).await?;
|
||||
|
||||
while let Some(kv) = values.next().await {
|
||||
let kv = kv?;
|
||||
|
||||
// The key decodes into the type that the static address knows about, in this case a
|
||||
// tuple of one entry, because the only part of the key that we can decode is the
|
||||
// AccountId32 for each user.
|
||||
let (account_id32,) = kv.key()?.decode()?;
|
||||
|
||||
// The value decodes into a statically generated type which holds account information.
|
||||
let value = kv.value().decode()?;
|
||||
|
||||
let value_data = value.data;
|
||||
println!("{account_id32}:\n {value_data:?}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,23 +1,41 @@
|
||||
#![allow(missing_docs)]
|
||||
use subxt::{OnlineClient, PolkadotConfig, dynamic::Value};
|
||||
use subxt::ext::futures::StreamExt;
|
||||
use subxt::utils::AccountId32;
|
||||
use subxt::{
|
||||
OnlineClient, PolkadotConfig,
|
||||
dynamic::{At, Value},
|
||||
};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create a new API client, configured to talk to Polkadot nodes.
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Build a dynamic storage query to iterate account information.
|
||||
// With a dynamic query, we can just provide an empty vector as the keys to iterate over all entries.
|
||||
let keys: Vec<Value> = vec![];
|
||||
let storage_query = subxt::dynamic::storage("System", "Account", keys);
|
||||
// Build a dynamic storage query to access account information.
|
||||
// here, we assume that there is one value to provide at this entry
|
||||
// to access a value; an AccountId32. In this example we don't know the
|
||||
// return type and so we set it to `Value`, which anything can decode into.
|
||||
let storage_query = subxt::dynamic::storage::<(AccountId32,), Value>("System", "Account");
|
||||
|
||||
// Use that query to return an iterator over the results.
|
||||
let mut results = api.storage().at_latest().await?.iter(storage_query).await?;
|
||||
// Use that query to access a storage entry, iterate over it and decode values.
|
||||
let client_at = api.storage().at_latest().await?;
|
||||
let mut values = client_at.entry(storage_query)?.iter(()).await?;
|
||||
|
||||
while let Some(Ok(kv)) = results.next().await {
|
||||
println!("Keys decoded: {:?}", kv.keys);
|
||||
println!("Key: 0x{}", hex::encode(&kv.key_bytes));
|
||||
println!("Value: {:?}", kv.value.to_value()?);
|
||||
while let Some(kv) = values.next().await {
|
||||
let kv = kv?;
|
||||
|
||||
// The key decodes into the first type we provided in the address. Since there's just
|
||||
// one key, it is a tuple of one entry, an AccountId32. If we didn't know how many
|
||||
// keys or their type, we could set the key to `Vec<Value>` instead.
|
||||
let (account_id32,) = kv.key()?.decode()?;
|
||||
|
||||
// The value decodes into the second type we provided in the address. In this example,
|
||||
// we just decode it into our `Value` type and then look at the "data" field in this
|
||||
// (which implicitly assumes we get a struct shaped thing back with such a field).
|
||||
let value = kv.value().decode()?;
|
||||
|
||||
let value_data = value.at("data").unwrap();
|
||||
println!("{account_id32}:\n {value_data}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
#![allow(missing_docs)]
|
||||
use polkadot::multisig::events::NewMultisig;
|
||||
use polkadot::runtime_types::{
|
||||
frame_system::pallet::Call, rococo_runtime::RuntimeCall, sp_weights::weight_v2::Weight,
|
||||
};
|
||||
use subxt::utils::AccountId32;
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
use subxt_signer::sr25519::{Keypair, dev};
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create a new API client, configured to talk to Polkadot nodes.
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Prepare the chain to have 3 open multisig requests (2 of them are alice + bob):
|
||||
let alice_signer = dev::alice();
|
||||
let bob = AccountId32(dev::bob().public_key().0);
|
||||
let charlie = AccountId32(dev::charlie().public_key().0);
|
||||
|
||||
let new_multisig_1 = submit_remark_as_multi(&alice_signer, &bob, b"Hello", &api).await?;
|
||||
let new_multisig_2 = submit_remark_as_multi(&alice_signer, &bob, b"Hi", &api).await?;
|
||||
let new_multisig_3 = submit_remark_as_multi(&alice_signer, &charlie, b"Hello", &api).await?;
|
||||
|
||||
// Note: the NewMultisig event contains the multisig address we need to use for the storage queries:
|
||||
assert_eq!(new_multisig_1.multisig, new_multisig_2.multisig);
|
||||
assert_ne!(new_multisig_1.multisig, new_multisig_3.multisig);
|
||||
|
||||
// Build a storage query to iterate over open multisig extrinsics from
|
||||
// new_multisig_1.multisig which is the AccountId of the alice + bob multisig account
|
||||
let alice_bob_account_id = new_multisig_1.multisig;
|
||||
let storage_query = polkadot::storage()
|
||||
.multisig()
|
||||
.multisigs_iter1(alice_bob_account_id);
|
||||
|
||||
// Get back an iterator of results.
|
||||
let mut results = api.storage().at_latest().await?.iter(storage_query).await?;
|
||||
|
||||
while let Some(Ok(kv)) = results.next().await {
|
||||
println!("Keys decoded: {:?}", kv.keys);
|
||||
println!("Key: 0x{}", hex::encode(&kv.key_bytes));
|
||||
println!("Value: {:?}", kv.value);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn submit_remark_as_multi(
|
||||
signer: &Keypair,
|
||||
other: &AccountId32,
|
||||
remark: &[u8],
|
||||
api: &OnlineClient<PolkadotConfig>,
|
||||
) -> Result<NewMultisig, Box<dyn std::error::Error>> {
|
||||
let multisig_remark_tx = polkadot::tx().multisig().as_multi(
|
||||
2,
|
||||
vec![other.clone()],
|
||||
None,
|
||||
RuntimeCall::System(Call::remark {
|
||||
remark: remark.to_vec(),
|
||||
}),
|
||||
Weight {
|
||||
ref_time: 0,
|
||||
proof_size: 0,
|
||||
},
|
||||
);
|
||||
let events = api
|
||||
.tx()
|
||||
.sign_and_submit_then_watch_default(&multisig_remark_tx, signer)
|
||||
.await?
|
||||
.wait_for_finalized_success()
|
||||
.await?;
|
||||
let new_multisig = events
|
||||
.find_first::<polkadot::multisig::events::NewMultisig>()?
|
||||
.expect("should contain event");
|
||||
Ok(new_multisig)
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
// see LICENSE for license details.
|
||||
|
||||
use crate::config::{Config, HashFor};
|
||||
use crate::error::Error;
|
||||
use crate::error::BackendError;
|
||||
use futures::{FutureExt, Stream, StreamExt, TryStreamExt};
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
@@ -34,12 +34,16 @@ pub type FollowEventStreamGetter<Hash> = Box<dyn FnMut() -> FollowEventStreamFut
|
||||
|
||||
/// The future which will return a stream of follow events and the subscription ID for it.
|
||||
pub type FollowEventStreamFut<Hash> = Pin<
|
||||
Box<dyn Future<Output = Result<(FollowEventStream<Hash>, String), Error>> + Send + 'static>,
|
||||
Box<
|
||||
dyn Future<Output = Result<(FollowEventStream<Hash>, String), BackendError>>
|
||||
+ Send
|
||||
+ 'static,
|
||||
>,
|
||||
>;
|
||||
|
||||
/// The stream of follow events.
|
||||
pub type FollowEventStream<Hash> =
|
||||
Pin<Box<dyn Stream<Item = Result<FollowEvent<Hash>, Error>> + Send + 'static>>;
|
||||
Pin<Box<dyn Stream<Item = Result<FollowEvent<Hash>, BackendError>> + Send + 'static>>;
|
||||
|
||||
/// Either a ready message with the current subscription ID, or
|
||||
/// an event from the stream itself.
|
||||
@@ -108,7 +112,7 @@ impl<Hash> FollowStream<Hash> {
|
||||
let stream = methods.chainhead_v1_follow(true).await?;
|
||||
// Extract the subscription ID:
|
||||
let Some(sub_id) = stream.subscription_id().map(ToOwned::to_owned) else {
|
||||
return Err(Error::Other(
|
||||
return Err(BackendError::Other(
|
||||
"Subscription ID expected for chainHead_follow response, but not given"
|
||||
.to_owned(),
|
||||
));
|
||||
@@ -128,7 +132,7 @@ impl<Hash> FollowStream<Hash> {
|
||||
impl<Hash> std::marker::Unpin for FollowStream<Hash> {}
|
||||
|
||||
impl<Hash> Stream for FollowStream<Hash> {
|
||||
type Item = Result<FollowStreamMsg<Hash>, Error>;
|
||||
type Item = Result<FollowStreamMsg<Hash>, BackendError>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let this = self.get_mut();
|
||||
@@ -228,7 +232,7 @@ pub(super) mod test_utils {
|
||||
where
|
||||
Hash: Send + 'static,
|
||||
F: Fn() -> I + Send + 'static,
|
||||
I: IntoIterator<Item = Result<FollowEvent<Hash>, Error>>,
|
||||
I: IntoIterator<Item = Result<FollowEvent<Hash>, BackendError>>,
|
||||
{
|
||||
let start_idx = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
@@ -307,7 +311,7 @@ pub mod test {
|
||||
Ok(FollowEvent::Stop),
|
||||
Ok(ev_new_block(1, 2)),
|
||||
// Nothing should be emitted after an error:
|
||||
Err(Error::Other("ended".to_owned())),
|
||||
Err(BackendError::Other("ended".to_owned())),
|
||||
Ok(ev_new_block(2, 3)),
|
||||
]
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
use super::follow_stream_unpin::{BlockRef, FollowStreamMsg, FollowStreamUnpin};
|
||||
use crate::config::Hash;
|
||||
use crate::error::{Error, RpcError};
|
||||
use crate::error::{BackendError, RpcError};
|
||||
use futures::stream::{Stream, StreamExt};
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::ops::DerefMut;
|
||||
@@ -42,7 +42,7 @@ impl<H: Hash> FollowStreamDriver<H> {
|
||||
}
|
||||
|
||||
impl<H: Hash> Stream for FollowStreamDriver<H> {
|
||||
type Item = Result<(), Error>;
|
||||
type Item = Result<(), BackendError>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
match self.inner.poll_next_unpin(cx) {
|
||||
@@ -421,7 +421,7 @@ where
|
||||
H: Hash,
|
||||
F: Fn(FollowEvent<BlockRef<H>>) -> Vec<BlockRef<H>>,
|
||||
{
|
||||
type Item = Result<(String, Vec<BlockRef<H>>), Error>;
|
||||
type Item = Result<(String, Vec<BlockRef<H>>), BackendError>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
if self.is_done {
|
||||
@@ -500,7 +500,7 @@ mod test_utils {
|
||||
where
|
||||
H: Hash + 'static,
|
||||
F: Fn() -> I + Send + 'static,
|
||||
I: IntoIterator<Item = Result<FollowEvent<H>, Error>>,
|
||||
I: IntoIterator<Item = Result<FollowEvent<H>, BackendError>>,
|
||||
{
|
||||
let (stream, _) = test_unpin_stream_getter(events, max_life);
|
||||
FollowStreamDriver::new(stream)
|
||||
@@ -537,7 +537,7 @@ mod test {
|
||||
Ok(ev_new_block(0, 1)),
|
||||
Ok(ev_best_block(1)),
|
||||
Ok(ev_finalized([1], [])),
|
||||
Err(Error::Other("ended".to_owned())),
|
||||
Err(BackendError::Other("ended".to_owned())),
|
||||
]
|
||||
},
|
||||
10,
|
||||
@@ -580,7 +580,7 @@ mod test {
|
||||
Ok(ev_finalized([1], [])),
|
||||
Ok(ev_new_block(1, 2)),
|
||||
Ok(ev_new_block(2, 3)),
|
||||
Err(Error::Other("ended".to_owned())),
|
||||
Err(BackendError::Other("ended".to_owned())),
|
||||
]
|
||||
},
|
||||
10,
|
||||
@@ -630,7 +630,7 @@ mod test {
|
||||
Ok(ev_new_block(1, 2)),
|
||||
Ok(ev_new_block(2, 3)),
|
||||
Ok(ev_finalized([1], [])),
|
||||
Err(Error::Other("ended".to_owned())),
|
||||
Err(BackendError::Other("ended".to_owned())),
|
||||
]
|
||||
},
|
||||
10,
|
||||
@@ -668,7 +668,7 @@ mod test {
|
||||
Ok(FollowEvent::Stop),
|
||||
Ok(ev_initialized(1)),
|
||||
Ok(ev_finalized([2], [])),
|
||||
Err(Error::Other("ended".to_owned())),
|
||||
Err(BackendError::Other("ended".to_owned())),
|
||||
]
|
||||
},
|
||||
10,
|
||||
@@ -714,7 +714,7 @@ mod test {
|
||||
// Emulate that we missed some blocks.
|
||||
Ok(ev_initialized(13)),
|
||||
Ok(ev_finalized([14], [])),
|
||||
Err(Error::Other("ended".to_owned())),
|
||||
Err(BackendError::Other("ended".to_owned())),
|
||||
]
|
||||
},
|
||||
10,
|
||||
@@ -742,7 +742,7 @@ mod test {
|
||||
)
|
||||
);
|
||||
assert!(
|
||||
matches!(&evs[1], Err(Error::Rpc(RpcError::ClientError(subxt_rpcs::Error::DisconnectedWillReconnect(e)))) if e.contains("Missed at least one block when the connection was lost"))
|
||||
matches!(&evs[1], Err(BackendError::Rpc(RpcError::ClientError(subxt_rpcs::Error::DisconnectedWillReconnect(e)))) if e.contains("Missed at least one block when the connection was lost"))
|
||||
);
|
||||
assert_eq!(
|
||||
evs[2].as_ref().unwrap(),
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
use super::ChainHeadRpcMethods;
|
||||
use super::follow_stream::FollowStream;
|
||||
use crate::config::{Config, Hash, HashFor};
|
||||
use crate::error::Error;
|
||||
use crate::error::BackendError;
|
||||
use futures::stream::{FuturesUnordered, Stream, StreamExt};
|
||||
use subxt_rpcs::methods::chain_head::{
|
||||
BestBlockChanged, Finalized, FollowEvent, Initialized, NewBlock,
|
||||
@@ -71,7 +71,7 @@ pub type UnpinFut = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
|
||||
impl<H: Hash> std::marker::Unpin for FollowStreamUnpin<H> {}
|
||||
|
||||
impl<H: Hash> Stream for FollowStreamUnpin<H> {
|
||||
type Item = Result<FollowStreamMsg<BlockRef<H>>, Error>;
|
||||
type Item = Result<FollowStreamMsg<BlockRef<H>>, BackendError>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let mut this = self.as_mut();
|
||||
@@ -482,7 +482,7 @@ pub(super) mod test_utils {
|
||||
where
|
||||
H: Hash + 'static,
|
||||
F: Fn() -> I + Send + 'static,
|
||||
I: IntoIterator<Item = Result<FollowEvent<H>, Error>>,
|
||||
I: IntoIterator<Item = Result<FollowEvent<H>, BackendError>>,
|
||||
{
|
||||
// Unpin requests will come here so that we can look out for them.
|
||||
let (unpin_tx, unpin_rx) = std::sync::mpsc::channel();
|
||||
@@ -567,7 +567,7 @@ mod test {
|
||||
Ok(ev_new_block(0, 1)),
|
||||
Ok(ev_new_block(1, 2)),
|
||||
Ok(ev_new_block(2, 3)),
|
||||
Err(Error::Other("ended".to_owned())),
|
||||
Err(BackendError::Other("ended".to_owned())),
|
||||
]
|
||||
},
|
||||
10,
|
||||
@@ -593,7 +593,7 @@ mod test {
|
||||
[
|
||||
Ok(ev_initialized(0)),
|
||||
Ok(ev_finalized([1], [])),
|
||||
Err(Error::Other("ended".to_owned())),
|
||||
Err(BackendError::Other("ended".to_owned())),
|
||||
]
|
||||
},
|
||||
3,
|
||||
@@ -624,7 +624,7 @@ mod test {
|
||||
Ok(ev_finalized([3], [])),
|
||||
Ok(ev_finalized([4], [])),
|
||||
Ok(ev_finalized([5], [])),
|
||||
Err(Error::Other("ended".to_owned())),
|
||||
Err(BackendError::Other("ended".to_owned())),
|
||||
]
|
||||
},
|
||||
3,
|
||||
@@ -663,7 +663,7 @@ mod test {
|
||||
Ok(ev_new_block(1, 2)),
|
||||
Ok(ev_finalized([1], [])),
|
||||
Ok(ev_finalized([2], [])),
|
||||
Err(Error::Other("ended".to_owned())),
|
||||
Err(BackendError::Other("ended".to_owned())),
|
||||
]
|
||||
},
|
||||
10,
|
||||
@@ -711,7 +711,7 @@ mod test {
|
||||
Ok(ev_finalized([1], [])),
|
||||
Ok(ev_finalized([2], [3])),
|
||||
Ok(ev_finalized([4], [])),
|
||||
Err(Error::Other("ended".to_owned())),
|
||||
Err(BackendError::Other("ended".to_owned())),
|
||||
]
|
||||
},
|
||||
10,
|
||||
@@ -771,7 +771,7 @@ mod test {
|
||||
Ok(ev_best_block(1)),
|
||||
Ok(ev_finalized([1], [])),
|
||||
Ok(ev_finalized([2], [])),
|
||||
Err(Error::Other("ended".to_owned())),
|
||||
Err(BackendError::Other("ended".to_owned())),
|
||||
]
|
||||
},
|
||||
10,
|
||||
|
||||
@@ -22,7 +22,7 @@ use crate::backend::{
|
||||
TransactionStatus, utils::retry,
|
||||
};
|
||||
use crate::config::{Config, Hash, HashFor};
|
||||
use crate::error::{Error, RpcError};
|
||||
use crate::error::{BackendError, RpcError};
|
||||
use async_trait::async_trait;
|
||||
use follow_stream_driver::{FollowStreamDriver, FollowStreamDriverHandle};
|
||||
use futures::future::Either;
|
||||
@@ -229,7 +229,7 @@ impl<T: Config> ChainHeadBackend<T> {
|
||||
async fn stream_headers<F>(
|
||||
&self,
|
||||
f: F,
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error>
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError>
|
||||
where
|
||||
F: Fn(
|
||||
FollowEvent<follow_stream_unpin::BlockRef<HashFor<T>>>,
|
||||
@@ -290,7 +290,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
&self,
|
||||
keys: Vec<Vec<u8>>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<StreamOfResults<StorageResponse>, Error> {
|
||||
) -> Result<StreamOfResults<StorageResponse>, BackendError> {
|
||||
retry(|| async {
|
||||
let queries = keys.iter().map(|key| StorageQuery {
|
||||
key: &**key,
|
||||
@@ -325,7 +325,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
&self,
|
||||
key: Vec<u8>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<StreamOfResults<Vec<u8>>, Error> {
|
||||
) -> Result<StreamOfResults<Vec<u8>>, BackendError> {
|
||||
retry(|| async {
|
||||
// Ask for hashes, and then just ignore them and return the keys that come back.
|
||||
let query = StorageQuery {
|
||||
@@ -351,7 +351,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
&self,
|
||||
key: Vec<u8>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<StreamOfResults<StorageResponse>, Error> {
|
||||
) -> Result<StreamOfResults<StorageResponse>, BackendError> {
|
||||
retry(|| async {
|
||||
let query = StorageQuery {
|
||||
key: &*key,
|
||||
@@ -386,7 +386,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn genesis_hash(&self) -> Result<HashFor<T>, Error> {
|
||||
async fn genesis_hash(&self) -> Result<HashFor<T>, BackendError> {
|
||||
retry(|| async {
|
||||
let genesis_hash = self.methods.chainspec_v1_genesis_hash().await?;
|
||||
Ok(genesis_hash)
|
||||
@@ -394,7 +394,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn block_header(&self, at: HashFor<T>) -> Result<Option<T::Header>, Error> {
|
||||
async fn block_header(&self, at: HashFor<T>) -> Result<Option<T::Header>, BackendError> {
|
||||
retry(|| async {
|
||||
let sub_id = get_subscription_id(&self.follow_handle).await?;
|
||||
let header = self.methods.chainhead_v1_header(&sub_id, at).await?;
|
||||
@@ -403,7 +403,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn block_body(&self, at: HashFor<T>) -> Result<Option<Vec<Vec<u8>>>, Error> {
|
||||
async fn block_body(&self, at: HashFor<T>) -> Result<Option<Vec<Vec<u8>>>, BackendError> {
|
||||
retry(|| async {
|
||||
let sub_id = get_subscription_id(&self.follow_handle).await?;
|
||||
|
||||
@@ -432,7 +432,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn latest_finalized_block_ref(&self) -> Result<BlockRef<HashFor<T>>, Error> {
|
||||
async fn latest_finalized_block_ref(&self) -> Result<BlockRef<HashFor<T>>, BackendError> {
|
||||
let next_ref: Option<BlockRef<HashFor<T>>> = self
|
||||
.follow_handle
|
||||
.subscribe()
|
||||
@@ -452,17 +452,19 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
next_ref.ok_or_else(|| RpcError::SubscriptionDropped.into())
|
||||
}
|
||||
|
||||
async fn current_runtime_version(&self) -> Result<RuntimeVersion, Error> {
|
||||
async fn current_runtime_version(&self) -> Result<RuntimeVersion, BackendError> {
|
||||
// Just start a stream of version infos, and return the first value we get from it.
|
||||
let runtime_version = self.stream_runtime_version().await?.next().await;
|
||||
match runtime_version {
|
||||
None => Err(Error::Rpc(RpcError::SubscriptionDropped)),
|
||||
None => Err(BackendError::Rpc(RpcError::SubscriptionDropped)),
|
||||
Some(Err(e)) => Err(e),
|
||||
Some(Ok(version)) => Ok(version),
|
||||
}
|
||||
}
|
||||
|
||||
async fn stream_runtime_version(&self) -> Result<StreamOfResults<RuntimeVersion>, Error> {
|
||||
async fn stream_runtime_version(
|
||||
&self,
|
||||
) -> Result<StreamOfResults<RuntimeVersion>, BackendError> {
|
||||
// Keep track of runtime details announced in new blocks, and then when blocks
|
||||
// are finalized, find the latest of these that has runtime details, and clear the rest.
|
||||
let mut runtimes = HashMap::new();
|
||||
@@ -526,7 +528,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
|
||||
let runtime_details = match runtime_event {
|
||||
RuntimeEvent::Invalid(err) => {
|
||||
return std::future::ready(Some(Err(Error::Other(err.error))))
|
||||
return std::future::ready(Some(Err(BackendError::Other(format!("Invalid runtime error using chainHead RPCs: {}", err.error)))))
|
||||
}
|
||||
RuntimeEvent::Valid(ev) => ev,
|
||||
};
|
||||
@@ -544,7 +546,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
async fn stream_all_block_headers(
|
||||
&self,
|
||||
_hasher: T::Hasher,
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error> {
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError> {
|
||||
// TODO: https://github.com/paritytech/subxt/issues/1568
|
||||
//
|
||||
// It's possible that blocks may be silently missed if
|
||||
@@ -562,7 +564,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
async fn stream_best_block_headers(
|
||||
&self,
|
||||
_hasher: T::Hasher,
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error> {
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError> {
|
||||
// TODO: https://github.com/paritytech/subxt/issues/1568
|
||||
//
|
||||
// It's possible that blocks may be silently missed if
|
||||
@@ -578,7 +580,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
async fn stream_finalized_block_headers(
|
||||
&self,
|
||||
_hasher: T::Hasher,
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error> {
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError> {
|
||||
self.stream_headers(|ev| match ev {
|
||||
FollowEvent::Initialized(init) => init.finalized_block_hashes,
|
||||
FollowEvent::Finalized(ev) => ev.finalized_block_hashes,
|
||||
@@ -590,12 +592,12 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
async fn submit_transaction(
|
||||
&self,
|
||||
extrinsic: &[u8],
|
||||
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, Error> {
|
||||
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, BackendError> {
|
||||
// Submit a transaction. This makes no attempt to sync with follow events,
|
||||
async fn submit_transaction_ignoring_follow_events<T: Config>(
|
||||
extrinsic: &[u8],
|
||||
methods: &ChainHeadRpcMethods<T>,
|
||||
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, Error> {
|
||||
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, BackendError> {
|
||||
let tx_progress = methods
|
||||
.transactionwatch_v1_submit_and_watch(extrinsic)
|
||||
.await?
|
||||
@@ -637,7 +639,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
transaction_timeout_secs: u64,
|
||||
methods: &ChainHeadRpcMethods<T>,
|
||||
follow_handle: &FollowStreamDriverHandle<HashFor<T>>,
|
||||
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, Error> {
|
||||
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, BackendError> {
|
||||
// We care about new and finalized block hashes.
|
||||
enum SeenBlockMarker {
|
||||
New,
|
||||
@@ -664,7 +666,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
let start_instant = web_time::Instant::now();
|
||||
|
||||
// A quick helper to return a generic error.
|
||||
let err_other = |s: &str| Some(Err(Error::Other(s.into())));
|
||||
let err_other = |s: &str| Some(Err(BackendError::Other(s.into())));
|
||||
|
||||
// Now we can attempt to associate tx events with pinned blocks.
|
||||
let tx_stream = futures::stream::poll_fn(move |cx| {
|
||||
@@ -828,7 +830,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
method: &str,
|
||||
call_parameters: Option<&[u8]>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
) -> Result<Vec<u8>, BackendError> {
|
||||
retry(|| async {
|
||||
let sub_id = get_subscription_id(&self.follow_handle).await?;
|
||||
|
||||
@@ -867,7 +869,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
/// A helper to obtain a subscription ID.
|
||||
async fn get_subscription_id<H: Hash>(
|
||||
follow_handle: &FollowStreamDriverHandle<H>,
|
||||
) -> Result<String, Error> {
|
||||
) -> Result<String, BackendError> {
|
||||
let Some(sub_id) = follow_handle.subscribe().subscription_id().await else {
|
||||
return Err(RpcError::SubscriptionDropped.into());
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
use super::follow_stream_driver::FollowStreamDriverHandle;
|
||||
use super::follow_stream_unpin::BlockRef;
|
||||
use crate::config::{Config, HashFor};
|
||||
use crate::error::{Error, RpcError};
|
||||
use crate::error::{BackendError, RpcError};
|
||||
use futures::{FutureExt, Stream, StreamExt};
|
||||
use std::collections::VecDeque;
|
||||
use std::future::Future;
|
||||
@@ -36,7 +36,7 @@ impl<T: Config> StorageItems<T> {
|
||||
at: HashFor<T>,
|
||||
follow_handle: &FollowStreamDriverHandle<HashFor<T>>,
|
||||
methods: ChainHeadRpcMethods<T>,
|
||||
) -> Result<Self, Error> {
|
||||
) -> Result<Self, BackendError> {
|
||||
let sub_id = super::get_subscription_id(follow_handle).await?;
|
||||
|
||||
// Subscribe to events and make the initial request to get an operation ID.
|
||||
@@ -92,10 +92,10 @@ impl<T: Config> StorageItems<T> {
|
||||
pub type FollowEventStream<Hash> =
|
||||
Pin<Box<dyn Stream<Item = FollowEvent<BlockRef<Hash>>> + Send + 'static>>;
|
||||
pub type ContinueFutGetter = Box<dyn Fn() -> ContinueFut + Send + 'static>;
|
||||
pub type ContinueFut = Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'static>>;
|
||||
pub type ContinueFut = Pin<Box<dyn Future<Output = Result<(), BackendError>> + Send + 'static>>;
|
||||
|
||||
impl<T: Config> Stream for StorageItems<T> {
|
||||
type Item = Result<StorageResult, Error>;
|
||||
type Item = Result<StorageResult, BackendError>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
loop {
|
||||
@@ -157,7 +157,7 @@ impl<T: Config> Stream for StorageItems<T> {
|
||||
FollowEvent::OperationError(err) if err.operation_id == *self.operation_id => {
|
||||
// Something went wrong obtaining storage items; mark as done and return the error.
|
||||
self.done = true;
|
||||
return Poll::Ready(Some(Err(Error::Other(err.error))));
|
||||
return Poll::Ready(Some(Err(BackendError::Other(err.error))));
|
||||
}
|
||||
_ => {
|
||||
// We don't care about this event; wait for the next.
|
||||
|
||||
+27
-26
@@ -11,10 +11,8 @@ use crate::backend::{
|
||||
Backend, BlockRef, RuntimeVersion, StorageResponse, StreamOf, StreamOfResults,
|
||||
TransactionStatus,
|
||||
};
|
||||
use crate::{
|
||||
Error,
|
||||
config::{Config, HashFor, Header},
|
||||
};
|
||||
use crate::config::{Config, HashFor, Header};
|
||||
use crate::error::BackendError;
|
||||
use async_trait::async_trait;
|
||||
use futures::TryStreamExt;
|
||||
use futures::{Future, FutureExt, Stream, StreamExt, future, future::Either, stream};
|
||||
@@ -101,12 +99,12 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
&self,
|
||||
keys: Vec<Vec<u8>>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<StreamOfResults<StorageResponse>, Error> {
|
||||
) -> Result<StreamOfResults<StorageResponse>, BackendError> {
|
||||
fn get_entry<T: Config>(
|
||||
key: Vec<u8>,
|
||||
at: HashFor<T>,
|
||||
methods: LegacyRpcMethods<T>,
|
||||
) -> impl Future<Output = Result<Option<StorageResponse>, Error>> {
|
||||
) -> impl Future<Output = Result<Option<StorageResponse>, BackendError>> {
|
||||
retry(move || {
|
||||
let methods = methods.clone();
|
||||
let key = key.clone();
|
||||
@@ -138,7 +136,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
&self,
|
||||
key: Vec<u8>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<StreamOfResults<Vec<u8>>, Error> {
|
||||
) -> Result<StreamOfResults<Vec<u8>>, BackendError> {
|
||||
let keys = StorageFetchDescendantKeysStream {
|
||||
at,
|
||||
key,
|
||||
@@ -169,7 +167,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
&self,
|
||||
key: Vec<u8>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<StreamOfResults<StorageResponse>, Error> {
|
||||
) -> Result<StreamOfResults<StorageResponse>, BackendError> {
|
||||
let keys_stream = StorageFetchDescendantKeysStream {
|
||||
at,
|
||||
key,
|
||||
@@ -187,7 +185,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
})))
|
||||
}
|
||||
|
||||
async fn genesis_hash(&self) -> Result<HashFor<T>, Error> {
|
||||
async fn genesis_hash(&self) -> Result<HashFor<T>, BackendError> {
|
||||
retry(|| async {
|
||||
let hash = self.methods.genesis_hash().await?;
|
||||
Ok(hash)
|
||||
@@ -195,7 +193,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn block_header(&self, at: HashFor<T>) -> Result<Option<T::Header>, Error> {
|
||||
async fn block_header(&self, at: HashFor<T>) -> Result<Option<T::Header>, BackendError> {
|
||||
retry(|| async {
|
||||
let header = self.methods.chain_get_header(Some(at)).await?;
|
||||
Ok(header)
|
||||
@@ -203,7 +201,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn block_body(&self, at: HashFor<T>) -> Result<Option<Vec<Vec<u8>>>, Error> {
|
||||
async fn block_body(&self, at: HashFor<T>) -> Result<Option<Vec<Vec<u8>>>, BackendError> {
|
||||
retry(|| async {
|
||||
let Some(details) = self.methods.chain_get_block(Some(at)).await? else {
|
||||
return Ok(None);
|
||||
@@ -215,7 +213,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn latest_finalized_block_ref(&self) -> Result<BlockRef<HashFor<T>>, Error> {
|
||||
async fn latest_finalized_block_ref(&self) -> Result<BlockRef<HashFor<T>>, BackendError> {
|
||||
retry(|| async {
|
||||
let hash = self.methods.chain_get_finalized_head().await?;
|
||||
Ok(BlockRef::from_hash(hash))
|
||||
@@ -223,7 +221,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn current_runtime_version(&self) -> Result<RuntimeVersion, Error> {
|
||||
async fn current_runtime_version(&self) -> Result<RuntimeVersion, BackendError> {
|
||||
retry(|| async {
|
||||
let details = self.methods.state_get_runtime_version(None).await?;
|
||||
Ok(RuntimeVersion {
|
||||
@@ -234,7 +232,9 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn stream_runtime_version(&self) -> Result<StreamOfResults<RuntimeVersion>, Error> {
|
||||
async fn stream_runtime_version(
|
||||
&self,
|
||||
) -> Result<StreamOfResults<RuntimeVersion>, BackendError> {
|
||||
let methods = self.methods.clone();
|
||||
|
||||
let retry_sub = retry_stream(move || {
|
||||
@@ -274,7 +274,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
async fn stream_all_block_headers(
|
||||
&self,
|
||||
hasher: T::Hasher,
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error> {
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError> {
|
||||
let methods = self.methods.clone();
|
||||
let retry_sub = retry_stream(move || {
|
||||
let methods = methods.clone();
|
||||
@@ -297,7 +297,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
async fn stream_best_block_headers(
|
||||
&self,
|
||||
hasher: T::Hasher,
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error> {
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError> {
|
||||
let methods = self.methods.clone();
|
||||
|
||||
let retry_sub = retry_stream(move || {
|
||||
@@ -321,7 +321,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
async fn stream_finalized_block_headers(
|
||||
&self,
|
||||
hasher: T::Hasher,
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error> {
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError> {
|
||||
let this = self.clone();
|
||||
|
||||
let retry_sub = retry_stream(move || {
|
||||
@@ -361,7 +361,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
async fn submit_transaction(
|
||||
&self,
|
||||
extrinsic: &[u8],
|
||||
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, Error> {
|
||||
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, BackendError> {
|
||||
let sub = self
|
||||
.methods
|
||||
.author_submit_and_watch_extrinsic(extrinsic)
|
||||
@@ -423,7 +423,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
method: &str,
|
||||
call_parameters: Option<&[u8]>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
) -> Result<Vec<u8>, BackendError> {
|
||||
retry(|| async {
|
||||
let res = self
|
||||
.methods
|
||||
@@ -442,11 +442,11 @@ pub fn subscribe_to_block_headers_filling_in_gaps<T, S, E>(
|
||||
methods: LegacyRpcMethods<T>,
|
||||
sub: S,
|
||||
mut last_block_num: Option<u64>,
|
||||
) -> impl Stream<Item = Result<T::Header, Error>> + Send
|
||||
) -> impl Stream<Item = Result<T::Header, BackendError>> + Send
|
||||
where
|
||||
T: Config,
|
||||
S: Stream<Item = Result<T::Header, E>> + Send,
|
||||
E: Into<Error> + Send + 'static,
|
||||
E: Into<BackendError> + Send + 'static,
|
||||
{
|
||||
sub.flat_map(move |s| {
|
||||
// Get the header, or return a stream containing just the error.
|
||||
@@ -470,7 +470,7 @@ where
|
||||
async move {
|
||||
let hash = methods.chain_get_block_hash(Some(n.into())).await?;
|
||||
let header = methods.chain_get_header(hash).await?;
|
||||
Ok::<_, Error>(header)
|
||||
Ok::<_, BackendError>(header)
|
||||
}
|
||||
})
|
||||
.filter_map(async |h| h.transpose());
|
||||
@@ -495,7 +495,8 @@ pub struct StorageFetchDescendantKeysStream<T: Config> {
|
||||
// What key do we start paginating from? None = from the beginning.
|
||||
pagination_start_key: Option<Vec<u8>>,
|
||||
// Keys, future and cached:
|
||||
keys_fut: Option<Pin<Box<dyn Future<Output = Result<Vec<Vec<u8>>, Error>> + Send + 'static>>>,
|
||||
keys_fut:
|
||||
Option<Pin<Box<dyn Future<Output = Result<Vec<Vec<u8>>, BackendError>> + Send + 'static>>>,
|
||||
// Set to true when we're done:
|
||||
done: bool,
|
||||
}
|
||||
@@ -503,7 +504,7 @@ pub struct StorageFetchDescendantKeysStream<T: Config> {
|
||||
impl<T: Config> std::marker::Unpin for StorageFetchDescendantKeysStream<T> {}
|
||||
|
||||
impl<T: Config> Stream for StorageFetchDescendantKeysStream<T> {
|
||||
type Item = Result<Vec<Vec<u8>>, Error>;
|
||||
type Item = Result<Vec<Vec<u8>>, BackendError>;
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let mut this = self.as_mut();
|
||||
loop {
|
||||
@@ -584,7 +585,7 @@ pub struct StorageFetchDescendantValuesStream<T: Config> {
|
||||
results_fut: Option<
|
||||
Pin<
|
||||
Box<
|
||||
dyn Future<Output = Result<Option<VecDeque<(Vec<u8>, Vec<u8>)>>, Error>>
|
||||
dyn Future<Output = Result<Option<VecDeque<(Vec<u8>, Vec<u8>)>>, BackendError>>
|
||||
+ Send
|
||||
+ 'static,
|
||||
>,
|
||||
@@ -595,7 +596,7 @@ pub struct StorageFetchDescendantValuesStream<T: Config> {
|
||||
}
|
||||
|
||||
impl<T: Config> Stream for StorageFetchDescendantValuesStream<T> {
|
||||
type Item = Result<StorageResponse, Error>;
|
||||
type Item = Result<StorageResponse, BackendError>;
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let mut this = self.as_mut();
|
||||
loop {
|
||||
|
||||
+39
-32
@@ -11,14 +11,14 @@ pub mod legacy;
|
||||
pub mod utils;
|
||||
|
||||
use crate::config::{Config, HashFor};
|
||||
use crate::error::Error;
|
||||
use crate::metadata::Metadata;
|
||||
use crate::error::BackendError;
|
||||
use async_trait::async_trait;
|
||||
use codec::{Decode, Encode};
|
||||
use futures::{Stream, StreamExt};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use subxt_core::client::RuntimeVersion;
|
||||
use subxt_metadata::Metadata;
|
||||
|
||||
/// Some re-exports from the [`subxt_rpcs`] crate, also accessible in full via [`crate::ext::subxt_rpcs`].
|
||||
pub mod rpc {
|
||||
@@ -82,66 +82,67 @@ pub trait Backend<T: Config>: sealed::Sealed + Send + Sync + 'static {
|
||||
&self,
|
||||
keys: Vec<Vec<u8>>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<StreamOfResults<StorageResponse>, Error>;
|
||||
) -> Result<StreamOfResults<StorageResponse>, BackendError>;
|
||||
|
||||
/// Fetch keys underneath the given key from storage.
|
||||
async fn storage_fetch_descendant_keys(
|
||||
&self,
|
||||
key: Vec<u8>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<StreamOfResults<Vec<u8>>, Error>;
|
||||
) -> Result<StreamOfResults<Vec<u8>>, BackendError>;
|
||||
|
||||
/// Fetch values underneath the given key from storage.
|
||||
async fn storage_fetch_descendant_values(
|
||||
&self,
|
||||
key: Vec<u8>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<StreamOfResults<StorageResponse>, Error>;
|
||||
) -> Result<StreamOfResults<StorageResponse>, BackendError>;
|
||||
|
||||
/// Fetch the genesis hash
|
||||
async fn genesis_hash(&self) -> Result<HashFor<T>, Error>;
|
||||
async fn genesis_hash(&self) -> Result<HashFor<T>, BackendError>;
|
||||
|
||||
/// Get a block header
|
||||
async fn block_header(&self, at: HashFor<T>) -> Result<Option<T::Header>, Error>;
|
||||
async fn block_header(&self, at: HashFor<T>) -> Result<Option<T::Header>, BackendError>;
|
||||
|
||||
/// Return the extrinsics found in the block. Each extrinsic is represented
|
||||
/// by a vector of bytes which has _not_ been SCALE decoded (in other words, the
|
||||
/// first bytes in the vector will decode to the compact encoded length of the extrinsic)
|
||||
async fn block_body(&self, at: HashFor<T>) -> Result<Option<Vec<Vec<u8>>>, Error>;
|
||||
async fn block_body(&self, at: HashFor<T>) -> Result<Option<Vec<Vec<u8>>>, BackendError>;
|
||||
|
||||
/// Get the most recent finalized block hash.
|
||||
/// Note: needed only in blocks client for finalized block stream; can prolly be removed.
|
||||
async fn latest_finalized_block_ref(&self) -> Result<BlockRef<HashFor<T>>, Error>;
|
||||
async fn latest_finalized_block_ref(&self) -> Result<BlockRef<HashFor<T>>, BackendError>;
|
||||
|
||||
/// Get information about the current runtime.
|
||||
async fn current_runtime_version(&self) -> Result<RuntimeVersion, Error>;
|
||||
async fn current_runtime_version(&self) -> Result<RuntimeVersion, BackendError>;
|
||||
|
||||
/// A stream of all new runtime versions as they occur.
|
||||
async fn stream_runtime_version(&self) -> Result<StreamOfResults<RuntimeVersion>, Error>;
|
||||
async fn stream_runtime_version(&self)
|
||||
-> Result<StreamOfResults<RuntimeVersion>, BackendError>;
|
||||
|
||||
/// A stream of all new block headers as they arrive.
|
||||
async fn stream_all_block_headers(
|
||||
&self,
|
||||
hasher: T::Hasher,
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error>;
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError>;
|
||||
|
||||
/// A stream of best block headers.
|
||||
async fn stream_best_block_headers(
|
||||
&self,
|
||||
hasher: T::Hasher,
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error>;
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError>;
|
||||
|
||||
/// A stream of finalized block headers.
|
||||
async fn stream_finalized_block_headers(
|
||||
&self,
|
||||
hasher: T::Hasher,
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error>;
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError>;
|
||||
|
||||
/// Submit a transaction. This will return a stream of events about it.
|
||||
async fn submit_transaction(
|
||||
&self,
|
||||
bytes: &[u8],
|
||||
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, Error>;
|
||||
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, BackendError>;
|
||||
|
||||
/// Make a call to some runtime API.
|
||||
async fn call(
|
||||
@@ -149,7 +150,7 @@ pub trait Backend<T: Config>: sealed::Sealed + Send + Sync + 'static {
|
||||
method: &str,
|
||||
call_parameters: Option<&[u8]>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<Vec<u8>, Error>;
|
||||
) -> Result<Vec<u8>, BackendError>;
|
||||
}
|
||||
|
||||
/// helpful utility methods derived from those provided on [`Backend`]
|
||||
@@ -160,7 +161,7 @@ pub trait BackendExt<T: Config>: Backend<T> {
|
||||
&self,
|
||||
key: Vec<u8>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<Option<Vec<u8>>, Error> {
|
||||
) -> Result<Option<Vec<u8>>, BackendError> {
|
||||
self.storage_fetch_values(vec![key], at)
|
||||
.await?
|
||||
.next()
|
||||
@@ -176,32 +177,39 @@ pub trait BackendExt<T: Config>: Backend<T> {
|
||||
method: &str,
|
||||
call_parameters: Option<&[u8]>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<D, Error> {
|
||||
) -> Result<D, BackendError> {
|
||||
let bytes = self.call(method, call_parameters, at).await?;
|
||||
let res = D::decode(&mut &*bytes)?;
|
||||
let res =
|
||||
D::decode(&mut &*bytes).map_err(BackendError::CouldNotScaleDecodeRuntimeResponse)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Return the metadata at some version.
|
||||
async fn metadata_at_version(&self, version: u32, at: HashFor<T>) -> Result<Metadata, Error> {
|
||||
async fn metadata_at_version(
|
||||
&self,
|
||||
version: u32,
|
||||
at: HashFor<T>,
|
||||
) -> Result<Metadata, BackendError> {
|
||||
let param = version.encode();
|
||||
|
||||
let opaque: Option<frame_metadata::OpaqueMetadata> = self
|
||||
.call_decoding("Metadata_metadata_at_version", Some(¶m), at)
|
||||
.await?;
|
||||
let Some(opaque) = opaque else {
|
||||
return Err(Error::Other("Metadata version not found".into()));
|
||||
return Err(BackendError::MetadataVersionNotFound(version));
|
||||
};
|
||||
|
||||
let metadata: Metadata = Decode::decode(&mut &opaque.0[..])?;
|
||||
let metadata: Metadata =
|
||||
Decode::decode(&mut &opaque.0[..]).map_err(BackendError::CouldNotDecodeMetadata)?;
|
||||
Ok(metadata)
|
||||
}
|
||||
|
||||
/// Return V14 metadata from the legacy `Metadata_metadata` call.
|
||||
async fn legacy_metadata(&self, at: HashFor<T>) -> Result<Metadata, Error> {
|
||||
async fn legacy_metadata(&self, at: HashFor<T>) -> Result<Metadata, BackendError> {
|
||||
let opaque: frame_metadata::OpaqueMetadata =
|
||||
self.call_decoding("Metadata_metadata", None, at).await?;
|
||||
let metadata: Metadata = Decode::decode(&mut &opaque.0[..])?;
|
||||
let metadata: Metadata =
|
||||
Decode::decode(&mut &opaque.0[..]).map_err(BackendError::CouldNotDecodeMetadata)?;
|
||||
Ok(metadata)
|
||||
}
|
||||
}
|
||||
@@ -325,8 +333,8 @@ impl<T> StreamOf<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A stream of [`Result<Item, Error>`].
|
||||
pub type StreamOfResults<T> = StreamOf<Result<T, Error>>;
|
||||
/// A stream of [`Result<Item, BackendError>`].
|
||||
pub type StreamOfResults<T> = StreamOf<Result<T, BackendError>>;
|
||||
|
||||
/// The status of the transaction.
|
||||
///
|
||||
@@ -541,7 +549,7 @@ mod test {
|
||||
/// - `call`
|
||||
/// The test covers them because they follow the simple pattern of:
|
||||
/// ```rust,no_run,standalone_crate
|
||||
/// async fn THE_THING(&self) -> Result<HashFor<T>, Error> {
|
||||
/// async fn THE_THING(&self) -> Result<HashFor<T>, BackendError> {
|
||||
/// retry(|| <DO THE THING> ).await
|
||||
/// }
|
||||
/// ```
|
||||
@@ -574,7 +582,7 @@ mod test {
|
||||
/// ```rust,no_run,standalone_crate
|
||||
/// async fn stream_the_thing(
|
||||
/// &self,
|
||||
/// ) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error> {
|
||||
/// ) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError> {
|
||||
/// let methods = self.methods.clone();
|
||||
/// let retry_sub = retry_stream(move || {
|
||||
/// let methods = methods.clone();
|
||||
@@ -635,7 +643,7 @@ mod test {
|
||||
);
|
||||
assert!(matches!(
|
||||
results.next().await.unwrap(),
|
||||
Err(Error::Rpc(RpcError::ClientError(
|
||||
Err(BackendError::Rpc(RpcError::ClientError(
|
||||
subxt_rpcs::Error::Client(_)
|
||||
)))
|
||||
));
|
||||
@@ -644,7 +652,6 @@ mod test {
|
||||
}
|
||||
|
||||
mod unstable_backend {
|
||||
use crate::error::RpcError;
|
||||
use subxt_rpcs::methods::chain_head::{
|
||||
self, Bytes, Initialized, MethodResponse, MethodResponseStarted, OperationError,
|
||||
OperationId, OperationStorageItems, RuntimeSpec, RuntimeVersionEvent,
|
||||
@@ -858,7 +865,7 @@ mod test {
|
||||
.next()
|
||||
.await
|
||||
.unwrap()
|
||||
.is_err_and(|e| matches!(e, Error::Other(e) if e == "error"))
|
||||
.is_err_and(|e| matches!(e, BackendError::Other(e) if e == "error"))
|
||||
);
|
||||
assert!(response.next().await.is_none());
|
||||
}
|
||||
@@ -1047,7 +1054,7 @@ mod test {
|
||||
let response = backend
|
||||
.storage_fetch_values(["ID1".into()].into(), random_hash())
|
||||
.await;
|
||||
assert!(matches!(response, Err(Error::Rpc(RpcError::LimitReached))));
|
||||
assert!(matches!(response, Err(e) if e.is_rpc_limit_reached()));
|
||||
|
||||
// Advance the driver until a new chainHead_follow subscription has been started up.
|
||||
let _ = driver.next().await.unwrap();
|
||||
|
||||
+15
-14
@@ -1,7 +1,7 @@
|
||||
//! RPC utils.
|
||||
|
||||
use super::{StreamOf, StreamOfResults};
|
||||
use crate::error::Error;
|
||||
use crate::error::BackendError;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::{FutureExt, Stream, StreamExt};
|
||||
use std::{future::Future, pin::Pin, task::Poll};
|
||||
@@ -10,10 +10,11 @@ use std::{future::Future, pin::Pin, task::Poll};
|
||||
type ResubscribeGetter<T> = Box<dyn FnMut() -> ResubscribeFuture<T> + Send>;
|
||||
|
||||
/// Future that resolves to a subscription stream.
|
||||
type ResubscribeFuture<T> = Pin<Box<dyn Future<Output = Result<StreamOfResults<T>, Error>> + Send>>;
|
||||
type ResubscribeFuture<T> =
|
||||
Pin<Box<dyn Future<Output = Result<StreamOfResults<T>, BackendError>> + Send>>;
|
||||
|
||||
pub(crate) enum PendingOrStream<T> {
|
||||
Pending(BoxFuture<'static, Result<StreamOfResults<T>, Error>>),
|
||||
Pending(BoxFuture<'static, Result<StreamOfResults<T>, BackendError>>),
|
||||
Stream(StreamOfResults<T>),
|
||||
}
|
||||
|
||||
@@ -35,7 +36,7 @@ struct RetrySubscription<T> {
|
||||
impl<T> std::marker::Unpin for RetrySubscription<T> {}
|
||||
|
||||
impl<T> Stream for RetrySubscription<T> {
|
||||
type Item = Result<T, Error>;
|
||||
type Item = Result<T, BackendError>;
|
||||
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
@@ -92,7 +93,7 @@ impl<T> Stream for RetrySubscription<T> {
|
||||
/// ```rust,no_run,standalone_crate
|
||||
/// use subxt::backend::utils::retry;
|
||||
///
|
||||
/// async fn some_future() -> Result<(), subxt::error::Error> {
|
||||
/// async fn some_future() -> Result<(), subxt::error::BackendError> {
|
||||
/// Ok(())
|
||||
/// }
|
||||
///
|
||||
@@ -101,10 +102,10 @@ impl<T> Stream for RetrySubscription<T> {
|
||||
/// let result = retry(|| some_future()).await;
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn retry<T, F, R>(mut retry_future: F) -> Result<R, Error>
|
||||
pub async fn retry<T, F, R>(mut retry_future: F) -> Result<R, BackendError>
|
||||
where
|
||||
F: FnMut() -> T,
|
||||
T: Future<Output = Result<R, Error>>,
|
||||
T: Future<Output = Result<R, BackendError>>,
|
||||
{
|
||||
const REJECTED_MAX_RETRIES: usize = 10;
|
||||
let mut rejected_retries = 0;
|
||||
@@ -163,7 +164,7 @@ where
|
||||
/// }).await;
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn retry_stream<F, R>(sub_stream: F) -> Result<StreamOfResults<R>, Error>
|
||||
pub async fn retry_stream<F, R>(sub_stream: F) -> Result<StreamOfResults<R>, BackendError>
|
||||
where
|
||||
F: FnMut() -> ResubscribeFuture<R> + Send + 'static + Clone,
|
||||
R: Send + 'static,
|
||||
@@ -187,12 +188,12 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::backend::StreamOf;
|
||||
|
||||
fn disconnect_err() -> Error {
|
||||
Error::Rpc(subxt_rpcs::Error::DisconnectedWillReconnect(String::new()).into())
|
||||
fn disconnect_err() -> BackendError {
|
||||
BackendError::Rpc(subxt_rpcs::Error::DisconnectedWillReconnect(String::new()).into())
|
||||
}
|
||||
|
||||
fn custom_err() -> Error {
|
||||
Error::Other(String::new())
|
||||
fn custom_err() -> BackendError {
|
||||
BackendError::Other(String::new())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -213,7 +214,7 @@ mod tests {
|
||||
|
||||
let result = retry_stream
|
||||
.take(5)
|
||||
.collect::<Vec<Result<usize, Error>>>()
|
||||
.collect::<Vec<Result<usize, BackendError>>>()
|
||||
.await;
|
||||
|
||||
assert!(matches!(result[0], Ok(r) if r == 1));
|
||||
@@ -270,6 +271,6 @@ mod tests {
|
||||
|
||||
assert!(matches!(result[0], Ok(r) if r == 1));
|
||||
assert!(matches!(result[1], Err(ref e) if e.is_disconnected_will_reconnect()));
|
||||
assert!(matches!(result[2], Err(ref e) if matches!(e, Error::Other(_))));
|
||||
assert!(matches!(result[2], Err(ref e) if matches!(e, BackendError::Other(_))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ use crate::{
|
||||
blocks::Extrinsics,
|
||||
client::{OfflineClientT, OnlineClientT},
|
||||
config::{Config, HashFor, Header},
|
||||
error::{BlockError, DecodeError, Error},
|
||||
error::{AccountNonceError, BlockError, EventsError, ExtrinsicError},
|
||||
events,
|
||||
runtime_api::RuntimeApi,
|
||||
storage::Storage,
|
||||
storage::StorageClientAt,
|
||||
};
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
@@ -84,38 +84,51 @@ where
|
||||
C: OnlineClientT<T>,
|
||||
{
|
||||
/// Return the events associated with the block, fetching them from the node if necessary.
|
||||
pub async fn events(&self) -> Result<events::Events<T>, Error> {
|
||||
pub async fn events(&self) -> Result<events::Events<T>, EventsError> {
|
||||
get_events(&self.client, self.hash(), &self.cached_events).await
|
||||
}
|
||||
|
||||
/// Fetch and return the extrinsics in the block body.
|
||||
pub async fn extrinsics(&self) -> Result<Extrinsics<T, C>, Error> {
|
||||
pub async fn extrinsics(&self) -> Result<Extrinsics<T, C>, ExtrinsicError> {
|
||||
let block_hash = self.hash();
|
||||
let Some(extrinsics) = self.client.backend().block_body(block_hash).await? else {
|
||||
return Err(BlockError::not_found(block_hash).into());
|
||||
};
|
||||
|
||||
Extrinsics::new(
|
||||
let extrinsics = self
|
||||
.client
|
||||
.backend()
|
||||
.block_body(block_hash)
|
||||
.await
|
||||
.map_err(ExtrinsicError::CannotGetBlockBody)?
|
||||
.ok_or_else(|| ExtrinsicError::BlockNotFound(block_hash.into()))?;
|
||||
|
||||
let extrinsics = Extrinsics::new(
|
||||
self.client.clone(),
|
||||
extrinsics,
|
||||
self.cached_events.clone(),
|
||||
block_hash,
|
||||
)
|
||||
)?;
|
||||
|
||||
Ok(extrinsics)
|
||||
}
|
||||
|
||||
/// Work with storage.
|
||||
pub fn storage(&self) -> Storage<T, C> {
|
||||
Storage::new(self.client.clone(), self.block_ref.clone())
|
||||
pub fn storage(&self) -> StorageClientAt<T, C> {
|
||||
StorageClientAt::new(self.client.clone(), self.block_ref.clone())
|
||||
}
|
||||
|
||||
/// Execute a runtime API call at this block.
|
||||
pub async fn runtime_api(&self) -> Result<RuntimeApi<T, C>, Error> {
|
||||
Ok(RuntimeApi::new(self.client.clone(), self.block_ref.clone()))
|
||||
pub async fn runtime_api(&self) -> RuntimeApi<T, C> {
|
||||
RuntimeApi::new(self.client.clone(), self.block_ref.clone())
|
||||
}
|
||||
|
||||
/// Get the account nonce for a given account ID at this block.
|
||||
pub async fn account_nonce(&self, account_id: &T::AccountId) -> Result<u64, Error> {
|
||||
get_account_nonce(&self.client, account_id, self.hash()).await
|
||||
pub async fn account_nonce(&self, account_id: &T::AccountId) -> Result<u64, BlockError> {
|
||||
get_account_nonce(&self.client, account_id, self.hash())
|
||||
.await
|
||||
.map_err(|e| BlockError::AccountNonceError {
|
||||
block_hash: self.hash().into(),
|
||||
account_id: account_id.encode().into(),
|
||||
reason: e,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +137,7 @@ pub(crate) async fn get_events<C, T>(
|
||||
client: &C,
|
||||
block_hash: HashFor<T>,
|
||||
cached_events: &AsyncMutex<Option<events::Events<T>>>,
|
||||
) -> Result<events::Events<T>, Error>
|
||||
) -> Result<events::Events<T>, EventsError>
|
||||
where
|
||||
T: Config,
|
||||
C: OnlineClientT<T>,
|
||||
@@ -152,7 +165,7 @@ pub(crate) async fn get_account_nonce<C, T>(
|
||||
client: &C,
|
||||
account_id: &T::AccountId,
|
||||
block_hash: HashFor<T>,
|
||||
) -> Result<u64, Error>
|
||||
) -> Result<u64, AccountNonceError>
|
||||
where
|
||||
C: OnlineClientT<T>,
|
||||
T: Config,
|
||||
@@ -173,10 +186,9 @@ where
|
||||
4 => u32::decode(cursor)?.into(),
|
||||
8 => u64::decode(cursor)?,
|
||||
_ => {
|
||||
return Err(Error::Decode(DecodeError::custom_string(format!(
|
||||
"state call AccountNonceApi_account_nonce returned an unexpected number of bytes: {} (expected 2, 4 or 8)",
|
||||
account_nonce_bytes.len()
|
||||
))));
|
||||
return Err(AccountNonceError::WrongNumberOfBytes(
|
||||
account_nonce_bytes.len(),
|
||||
));
|
||||
}
|
||||
};
|
||||
Ok(account_nonce)
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::{
|
||||
backend::{BlockRef, StreamOfResults},
|
||||
client::OnlineClientT,
|
||||
config::{Config, HashFor},
|
||||
error::{BlockError, Error},
|
||||
error::BlockError,
|
||||
utils::PhantomDataSendSync,
|
||||
};
|
||||
use derive_where::derive_where;
|
||||
@@ -15,7 +15,7 @@ use futures::StreamExt;
|
||||
use std::future::Future;
|
||||
|
||||
type BlockStream<T> = StreamOfResults<T>;
|
||||
type BlockStreamRes<T> = Result<BlockStream<T>, Error>;
|
||||
type BlockStreamRes<T> = Result<BlockStream<T>, BlockError>;
|
||||
|
||||
/// A client for working with blocks.
|
||||
#[derive_where(Clone; Client)]
|
||||
@@ -49,14 +49,14 @@ where
|
||||
pub fn at(
|
||||
&self,
|
||||
block_ref: impl Into<BlockRef<HashFor<T>>>,
|
||||
) -> impl Future<Output = Result<Block<T, Client>, Error>> + Send + 'static {
|
||||
) -> impl Future<Output = Result<Block<T, Client>, BlockError>> + Send + 'static {
|
||||
self.at_or_latest(Some(block_ref.into()))
|
||||
}
|
||||
|
||||
/// Obtain block details of the latest finalized block.
|
||||
pub fn at_latest(
|
||||
&self,
|
||||
) -> impl Future<Output = Result<Block<T, Client>, Error>> + Send + 'static {
|
||||
) -> impl Future<Output = Result<Block<T, Client>, BlockError>> + Send + 'static {
|
||||
self.at_or_latest(None)
|
||||
}
|
||||
|
||||
@@ -65,18 +65,35 @@ where
|
||||
fn at_or_latest(
|
||||
&self,
|
||||
block_ref: Option<BlockRef<HashFor<T>>>,
|
||||
) -> impl Future<Output = Result<Block<T, Client>, Error>> + Send + 'static {
|
||||
) -> impl Future<Output = Result<Block<T, Client>, BlockError>> + Send + 'static {
|
||||
let client = self.client.clone();
|
||||
async move {
|
||||
// If a block ref isn't provided, we'll get the latest finalized ref to use.
|
||||
let block_ref = match block_ref {
|
||||
Some(r) => r,
|
||||
None => client.backend().latest_finalized_block_ref().await?,
|
||||
None => client
|
||||
.backend()
|
||||
.latest_finalized_block_ref()
|
||||
.await
|
||||
.map_err(BlockError::CouldNotGetLatestBlock)?,
|
||||
};
|
||||
|
||||
let block_header = match client.backend().block_header(block_ref.hash()).await? {
|
||||
let maybe_block_header = client
|
||||
.backend()
|
||||
.block_header(block_ref.hash())
|
||||
.await
|
||||
.map_err(|e| BlockError::CouldNotGetBlockHeader {
|
||||
block_hash: block_ref.hash().into(),
|
||||
reason: e,
|
||||
})?;
|
||||
|
||||
let block_header = match maybe_block_header {
|
||||
Some(header) => header,
|
||||
None => return Err(BlockError::not_found(block_ref.hash()).into()),
|
||||
None => {
|
||||
return Err(BlockError::BlockNotFound {
|
||||
block_hash: block_ref.hash().into(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Block::new(block_header, block_ref, client))
|
||||
@@ -89,14 +106,18 @@ where
|
||||
/// the time.
|
||||
pub fn subscribe_all(
|
||||
&self,
|
||||
) -> impl Future<Output = Result<BlockStream<Block<T, Client>>, Error>> + Send + 'static
|
||||
) -> impl Future<Output = Result<BlockStream<Block<T, Client>>, BlockError>> + Send + 'static
|
||||
where
|
||||
Client: Send + Sync + 'static,
|
||||
{
|
||||
let client = self.client.clone();
|
||||
let hasher = client.hasher();
|
||||
header_sub_fut_to_block_sub(self.clone(), async move {
|
||||
let stream = client.backend().stream_all_block_headers(hasher).await?;
|
||||
let stream = client
|
||||
.backend()
|
||||
.stream_all_block_headers(hasher)
|
||||
.await
|
||||
.map_err(BlockError::CouldNotSubscribeToAllBlocks)?;
|
||||
BlockStreamRes::Ok(stream)
|
||||
})
|
||||
}
|
||||
@@ -107,14 +128,18 @@ where
|
||||
/// the time.
|
||||
pub fn subscribe_best(
|
||||
&self,
|
||||
) -> impl Future<Output = Result<BlockStream<Block<T, Client>>, Error>> + Send + 'static
|
||||
) -> impl Future<Output = Result<BlockStream<Block<T, Client>>, BlockError>> + Send + 'static
|
||||
where
|
||||
Client: Send + Sync + 'static,
|
||||
{
|
||||
let client = self.client.clone();
|
||||
let hasher = client.hasher();
|
||||
header_sub_fut_to_block_sub(self.clone(), async move {
|
||||
let stream = client.backend().stream_best_block_headers(hasher).await?;
|
||||
let stream = client
|
||||
.backend()
|
||||
.stream_best_block_headers(hasher)
|
||||
.await
|
||||
.map_err(BlockError::CouldNotSubscribeToBestBlocks)?;
|
||||
BlockStreamRes::Ok(stream)
|
||||
})
|
||||
}
|
||||
@@ -122,7 +147,7 @@ where
|
||||
/// Subscribe to finalized blocks.
|
||||
pub fn subscribe_finalized(
|
||||
&self,
|
||||
) -> impl Future<Output = Result<BlockStream<Block<T, Client>>, Error>> + Send + 'static
|
||||
) -> impl Future<Output = Result<BlockStream<Block<T, Client>>, BlockError>> + Send + 'static
|
||||
where
|
||||
Client: Send + Sync + 'static,
|
||||
{
|
||||
@@ -132,7 +157,8 @@ where
|
||||
let stream = client
|
||||
.backend()
|
||||
.stream_finalized_block_headers(hasher)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(BlockError::CouldNotSubscribeToFinalizedBlocks)?;
|
||||
BlockStreamRes::Ok(stream)
|
||||
})
|
||||
}
|
||||
@@ -143,10 +169,10 @@ where
|
||||
async fn header_sub_fut_to_block_sub<T, Client, S>(
|
||||
blocks_client: BlocksClient<T, Client>,
|
||||
sub: S,
|
||||
) -> Result<BlockStream<Block<T, Client>>, Error>
|
||||
) -> Result<BlockStream<Block<T, Client>>, BlockError>
|
||||
where
|
||||
T: Config,
|
||||
S: Future<Output = Result<BlockStream<(T::Header, BlockRef<HashFor<T>>)>, Error>>
|
||||
S: Future<Output = Result<BlockStream<(T::Header, BlockRef<HashFor<T>>)>, BlockError>>
|
||||
+ Send
|
||||
+ 'static,
|
||||
Client: OnlineClientT<T> + Send + Sync + 'static,
|
||||
|
||||
@@ -6,18 +6,16 @@ use crate::{
|
||||
blocks::block_types::{CachedEvents, get_events},
|
||||
client::{OfflineClientT, OnlineClientT},
|
||||
config::{Config, HashFor},
|
||||
error::Error,
|
||||
error::{EventsError, ExtrinsicDecodeErrorAt, ExtrinsicError},
|
||||
events,
|
||||
};
|
||||
|
||||
use derive_where::derive_where;
|
||||
use scale_decode::DecodeAsType;
|
||||
use scale_decode::{DecodeAsFields, DecodeAsType};
|
||||
use subxt_core::blocks::{ExtrinsicDetails as CoreExtrinsicDetails, Extrinsics as CoreExtrinsics};
|
||||
|
||||
// Re-export anything that's directly returned/used in the APIs below.
|
||||
pub use subxt_core::blocks::{
|
||||
ExtrinsicMetadataDetails, ExtrinsicTransactionExtension, ExtrinsicTransactionExtensions,
|
||||
StaticExtrinsic,
|
||||
ExtrinsicTransactionExtension, ExtrinsicTransactionExtensions, StaticExtrinsic,
|
||||
};
|
||||
|
||||
/// The body of a block.
|
||||
@@ -38,7 +36,7 @@ where
|
||||
extrinsics: Vec<Vec<u8>>,
|
||||
cached_events: CachedEvents<T>,
|
||||
hash: HashFor<T>,
|
||||
) -> Result<Self, Error> {
|
||||
) -> Result<Self, ExtrinsicDecodeErrorAt> {
|
||||
let inner = CoreExtrinsics::decode_from(extrinsics, client.metadata())?;
|
||||
Ok(Self {
|
||||
inner,
|
||||
@@ -81,10 +79,10 @@ where
|
||||
/// If an error occurs, all subsequent iterations return `None`.
|
||||
pub fn find<E: StaticExtrinsic>(
|
||||
&self,
|
||||
) -> impl Iterator<Item = Result<FoundExtrinsic<T, C, E>, Error>> {
|
||||
) -> impl Iterator<Item = Result<FoundExtrinsic<T, C, E>, ExtrinsicError>> {
|
||||
self.inner.find::<E>().map(|res| {
|
||||
match res {
|
||||
Err(e) => Err(Error::from(e)),
|
||||
Err(e) => Err(ExtrinsicError::from(e)),
|
||||
Ok(ext) => {
|
||||
// Wrap details from subxt-core into what we want here:
|
||||
let details = ExtrinsicDetails::new(
|
||||
@@ -105,18 +103,22 @@ where
|
||||
|
||||
/// Iterate through the extrinsics using metadata to dynamically decode and skip
|
||||
/// them, and return the first extrinsic found which decodes to the provided `E` type.
|
||||
pub fn find_first<E: StaticExtrinsic>(&self) -> Result<Option<FoundExtrinsic<T, C, E>>, Error> {
|
||||
pub fn find_first<E: StaticExtrinsic>(
|
||||
&self,
|
||||
) -> Result<Option<FoundExtrinsic<T, C, E>>, ExtrinsicError> {
|
||||
self.find::<E>().next().transpose()
|
||||
}
|
||||
|
||||
/// Iterate through the extrinsics using metadata to dynamically decode and skip
|
||||
/// them, and return the last extrinsic found which decodes to the provided `Ev` type.
|
||||
pub fn find_last<E: StaticExtrinsic>(&self) -> Result<Option<FoundExtrinsic<T, C, E>>, Error> {
|
||||
pub fn find_last<E: StaticExtrinsic>(
|
||||
&self,
|
||||
) -> Result<Option<FoundExtrinsic<T, C, E>>, ExtrinsicError> {
|
||||
self.find::<E>().last().transpose()
|
||||
}
|
||||
|
||||
/// Find an extrinsics that decodes to the type provided. Returns true if it was found.
|
||||
pub fn has<E: StaticExtrinsic>(&self) -> Result<bool, Error> {
|
||||
pub fn has<E: StaticExtrinsic>(&self) -> Result<bool, ExtrinsicError> {
|
||||
Ok(self.find::<E>().next().transpose()?.is_some())
|
||||
}
|
||||
}
|
||||
@@ -207,38 +209,33 @@ where
|
||||
self.inner.pallet_index()
|
||||
}
|
||||
|
||||
/// See [`subxt_core::blocks::ExtrinsicDetails::variant_index()`].
|
||||
pub fn variant_index(&self) -> u8 {
|
||||
self.inner.variant_index()
|
||||
/// See [`subxt_core::blocks::ExtrinsicDetails::call_index()`].
|
||||
pub fn call_index(&self) -> u8 {
|
||||
self.inner.call_index()
|
||||
}
|
||||
|
||||
/// See [`subxt_core::blocks::ExtrinsicDetails::pallet_name()`].
|
||||
pub fn pallet_name(&self) -> Result<&str, Error> {
|
||||
self.inner.pallet_name().map_err(Into::into)
|
||||
pub fn pallet_name(&self) -> &str {
|
||||
self.inner.pallet_name()
|
||||
}
|
||||
|
||||
/// See [`subxt_core::blocks::ExtrinsicDetails::variant_name()`].
|
||||
pub fn variant_name(&self) -> Result<&str, Error> {
|
||||
self.inner.variant_name().map_err(Into::into)
|
||||
/// See [`subxt_core::blocks::ExtrinsicDetails::call_name()`].
|
||||
pub fn call_name(&self) -> &str {
|
||||
self.inner.call_name()
|
||||
}
|
||||
|
||||
/// See [`subxt_core::blocks::ExtrinsicDetails::extrinsic_metadata()`].
|
||||
pub fn extrinsic_metadata(&self) -> Result<ExtrinsicMetadataDetails<'_>, Error> {
|
||||
self.inner.extrinsic_metadata().map_err(Into::into)
|
||||
}
|
||||
|
||||
/// See [`subxt_core::blocks::ExtrinsicDetails::field_values()`].
|
||||
pub fn field_values(&self) -> Result<scale_value::Composite<u32>, Error> {
|
||||
self.inner.field_values().map_err(Into::into)
|
||||
/// See [`subxt_core::blocks::ExtrinsicDetails::decode_as_fields()`].
|
||||
pub fn decode_as_fields<E: DecodeAsFields>(&self) -> Result<E, ExtrinsicError> {
|
||||
self.inner.decode_as_fields().map_err(Into::into)
|
||||
}
|
||||
|
||||
/// See [`subxt_core::blocks::ExtrinsicDetails::as_extrinsic()`].
|
||||
pub fn as_extrinsic<E: StaticExtrinsic>(&self) -> Result<Option<E>, Error> {
|
||||
pub fn as_extrinsic<E: StaticExtrinsic>(&self) -> Result<Option<E>, ExtrinsicError> {
|
||||
self.inner.as_extrinsic::<E>().map_err(Into::into)
|
||||
}
|
||||
|
||||
/// See [`subxt_core::blocks::ExtrinsicDetails::as_root_extrinsic()`].
|
||||
pub fn as_root_extrinsic<E: DecodeAsType>(&self) -> Result<E, Error> {
|
||||
pub fn as_root_extrinsic<E: DecodeAsType>(&self) -> Result<E, ExtrinsicError> {
|
||||
self.inner.as_root_extrinsic::<E>().map_err(Into::into)
|
||||
}
|
||||
}
|
||||
@@ -249,7 +246,7 @@ where
|
||||
C: OnlineClientT<T>,
|
||||
{
|
||||
/// The events associated with the extrinsic.
|
||||
pub async fn events(&self) -> Result<ExtrinsicEvents<T>, Error> {
|
||||
pub async fn events(&self) -> Result<ExtrinsicEvents<T>, EventsError> {
|
||||
let events = get_events(&self.client, self.block_hash, &self.cached_events).await?;
|
||||
let ext_hash = self.inner.hash();
|
||||
Ok(ExtrinsicEvents::new(ext_hash, self.index(), events))
|
||||
@@ -308,7 +305,7 @@ impl<T: Config> ExtrinsicEvents<T> {
|
||||
///
|
||||
/// This works in the same way that [`events::Events::iter()`] does, with the
|
||||
/// exception that it filters out events not related to the submitted extrinsic.
|
||||
pub fn iter(&self) -> impl Iterator<Item = Result<events::EventDetails<T>, Error>> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = Result<events::EventDetails<T>, EventsError>> {
|
||||
self.events.iter().filter(|ev| {
|
||||
ev.as_ref()
|
||||
.map(|ev| ev.phase() == events::Phase::ApplyExtrinsic(self.idx))
|
||||
@@ -320,7 +317,7 @@ impl<T: Config> ExtrinsicEvents<T> {
|
||||
///
|
||||
/// This works in the same way that [`events::Events::find()`] does, with the
|
||||
/// exception that it filters out events not related to the submitted extrinsic.
|
||||
pub fn find<Ev: events::StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, Error>> {
|
||||
pub fn find<Ev: events::StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, EventsError>> {
|
||||
self.iter()
|
||||
.filter_map(|ev| ev.and_then(|ev| ev.as_event::<Ev>()).transpose())
|
||||
}
|
||||
@@ -330,7 +327,7 @@ impl<T: Config> ExtrinsicEvents<T> {
|
||||
///
|
||||
/// This works in the same way that [`events::Events::find_first()`] does, with the
|
||||
/// exception that it ignores events not related to the submitted extrinsic.
|
||||
pub fn find_first<Ev: events::StaticEvent>(&self) -> Result<Option<Ev>, Error> {
|
||||
pub fn find_first<Ev: events::StaticEvent>(&self) -> Result<Option<Ev>, EventsError> {
|
||||
self.find::<Ev>().next().transpose()
|
||||
}
|
||||
|
||||
@@ -339,7 +336,7 @@ impl<T: Config> ExtrinsicEvents<T> {
|
||||
///
|
||||
/// This works in the same way that [`events::Events::find_last()`] does, with the
|
||||
/// exception that it ignores events not related to the submitted extrinsic.
|
||||
pub fn find_last<Ev: events::StaticEvent>(&self) -> Result<Option<Ev>, Error> {
|
||||
pub fn find_last<Ev: events::StaticEvent>(&self) -> Result<Option<Ev>, EventsError> {
|
||||
self.find::<Ev>().last().transpose()
|
||||
}
|
||||
|
||||
@@ -347,7 +344,7 @@ impl<T: Config> ExtrinsicEvents<T> {
|
||||
///
|
||||
/// This works in the same way that [`events::Events::has()`] does, with the
|
||||
/// exception that it ignores events not related to the submitted extrinsic.
|
||||
pub fn has<Ev: events::StaticEvent>(&self) -> Result<bool, Error> {
|
||||
pub fn has<Ev: events::StaticEvent>(&self) -> Result<bool, EventsError> {
|
||||
Ok(self.find::<Ev>().next().transpose()?.is_some())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
//! - Subscribe to [all](crate::blocks::BlocksClient::subscribe_all()),
|
||||
//! [best](crate::blocks::BlocksClient::subscribe_best()) or
|
||||
//! [finalized](crate::blocks::BlocksClient::subscribe_finalized()) blocks as they are produced.
|
||||
//! Prefer to subscribe to finalized blocks unless you know what you're doing.
|
||||
//! **Prefer to subscribe to finalized blocks unless you know what you're doing.**
|
||||
//!
|
||||
//! In either case, you'll end up with [`crate::blocks::Block`]'s, from which you can access various
|
||||
//! information about the block, such a the [header](crate::blocks::Block::header()), [block
|
||||
//! number](crate::blocks::Block::number()) and [body (the extrinsics)](crate::blocks::Block::extrinsics()).
|
||||
//! information about the block, such a the [header](crate::blocks::Block::header()),
|
||||
//! [block number](crate::blocks::Block::number()) and [body (the extrinsics)](crate::blocks::Block::extrinsics()).
|
||||
//! [`crate::blocks::Block`]'s also provide shortcuts to other Subxt APIs that will operate at the
|
||||
//! given block:
|
||||
//!
|
||||
@@ -29,8 +29,8 @@
|
||||
//!
|
||||
//! ## Decoding Extrinsics
|
||||
//!
|
||||
//! Given a block, you can [download the block body](crate::blocks::Block::extrinsics()) and [iterate over
|
||||
//! the extrinsics](crate::blocks::Extrinsics::iter()) stored within it. The extrinsics yielded are of type
|
||||
//! Given a block, you can [download the block body](crate::blocks::Block::extrinsics()) and
|
||||
//! [iterate over the extrinsics](crate::blocks::Extrinsics::iter) stored within it. The extrinsics yielded are of type
|
||||
//! [ExtrinsicDetails](crate::blocks::ExtrinsicDetails), which is just a blob of bytes that also stores which
|
||||
//! pallet and call in that pallet it belongs to. It also contains information about signed extensions that
|
||||
//! have been used for submitting this extrinsic.
|
||||
@@ -64,8 +64,8 @@
|
||||
//! get only [the first one](crate::blocks::Extrinsics::find_first), or [the last one](crate::blocks::Extrinsics::find_last).
|
||||
//!
|
||||
//! The following example monitors `TransferKeepAlive` extrinsics on the Polkadot network.
|
||||
//! We statically decode them and access the [tip](crate::blocks::ExtrinsicTransactionExtensions::tip()) and [account nonce](crate::blocks::ExtrinsicTransactionExtensions::nonce())
|
||||
//! transaction extensions.
|
||||
//! We statically decode them and access the [tip](crate::blocks::ExtrinsicTransactionExtensions::tip()) and
|
||||
//! [account nonce](crate::blocks::ExtrinsicTransactionExtensions::nonce()) transaction extensions.
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../examples/block_decoding_static.rs")]
|
||||
@@ -73,11 +73,13 @@
|
||||
//!
|
||||
//! ### Dynamically decode the extrinsic
|
||||
//!
|
||||
//! Sometimes you might use subxt with metadata that is not known at compile time. In this case, you do not have access to a statically generated
|
||||
//! interface module that contains the relevant Rust types. You can [decode ExtrinsicDetails dynamically](crate::blocks::ExtrinsicDetails::field_values()),
|
||||
//! which gives you access to it's fields as a [scale value composite](scale_value::Composite).
|
||||
//! The following example looks for signed extrinsics on the Polkadot network and retrieves their pallet name, variant name, data fields and transaction extensions dynamically.
|
||||
//! Notice how we do not need to use code generation via the subxt macro. The only fixed component we provide is the [PolkadotConfig](crate::config::PolkadotConfig).
|
||||
//! Sometimes you might use subxt with metadata that is not known at compile time. In this case, you do not
|
||||
//! have access to a statically generated interface module that contains the relevant Rust types. You can
|
||||
//! [decode ExtrinsicDetails dynamically](crate::blocks::ExtrinsicDetails::decode_as_fields()), which gives
|
||||
//! you access to it's fields as a [scale value composite](scale_value::Composite). The following example
|
||||
//! looks for signed extrinsics on the Polkadot network and retrieves their pallet name, variant name, data
|
||||
//! fields and transaction extensions dynamically. Notice how we do not need to use code generation via the
|
||||
//! subxt macro. The only fixed component we provide is the [PolkadotConfig](crate::config::PolkadotConfig).
|
||||
//! Other than that it works in a chain-agnostic way:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
@@ -90,10 +92,12 @@
|
||||
//! The [Config](crate::Config) implementation for your chain defines which transaction extensions you expect.
|
||||
//! Once you get hold of the [ExtrinsicDetails](crate::blocks::ExtrinsicDetails) for an extrinsic you are interested in,
|
||||
//! you can try to [get its transaction extensions](crate::blocks::ExtrinsicDetails::transaction_extensions()).
|
||||
//! These are only available on V4 signed extrinsics or V5 general extrinsics. You can try to [find a specific transaction extension](crate::blocks::ExtrinsicTransactionExtensions::find),
|
||||
//! in the returned [transaction extensions](crate::blocks::ExtrinsicTransactionExtensions).
|
||||
//! These are only available on V4 signed extrinsics or V5 general extrinsics. You can try to
|
||||
//! [find a specific transaction extension](crate::blocks::ExtrinsicTransactionExtensions::find), in the returned
|
||||
//! [transaction extensions](crate::blocks::ExtrinsicTransactionExtensions).
|
||||
//!
|
||||
//! Subxt also provides utility functions to get the [tip](crate::blocks::ExtrinsicTransactionExtensions::tip()) and the
|
||||
//! [account nonce](crate::blocks::ExtrinsicTransactionExtensions::tip()) associated with an extrinsic, given its transaction extensions.
|
||||
//! If you prefer to do things dynamically you can get the data of the transaction extension as a [scale value](crate::blocks::ExtrinsicTransactionExtension::value()).
|
||||
//! Subxt also provides utility functions to get the [tip](crate::blocks::ExtrinsicTransactionExtensions::tip()) and
|
||||
//! the [account nonce](crate::blocks::ExtrinsicTransactionExtensions::nonce()) associated with an extrinsic, given
|
||||
//! its transaction extensions. If you prefer to do things dynamically you can get the data of the transaction extension
|
||||
//! as a [scale value](crate::blocks::ExtrinsicTransactionExtension::value()).
|
||||
//!
|
||||
|
||||
@@ -22,23 +22,22 @@
|
||||
//! let constant_query = polkadot::constants().system().block_length();
|
||||
//! ```
|
||||
//!
|
||||
//! Alternately, we can dynamically construct a constant query:
|
||||
//! Alternately, we can dynamically construct a constant query. A dynamic query needs the return
|
||||
//! type to be specified, where we can use [`crate::dynamic::Value`] if unsure:
|
||||
//!
|
||||
//! ```rust,no_run,standalone_crate
|
||||
//! use subxt::dynamic::Value;
|
||||
//!
|
||||
//! let storage_query = subxt::dynamic::constant("System", "BlockLength");
|
||||
//! let storage_query = subxt::dynamic::constant::<Value>("System", "BlockLength");
|
||||
//! ```
|
||||
//!
|
||||
//! Static queries also have a static return type, so the constant is decoded appropriately. In
|
||||
//! addition, they are validated at runtime to ensure that they align with the current node state.
|
||||
//! Dynamic queries must be decoded into some static type manually, or into the dynamic
|
||||
//! [`crate::dynamic::Value`] type.
|
||||
//!
|
||||
//! ## Submitting it
|
||||
//!
|
||||
//! Constant queries are handed to Subxt via [`crate::constants::ConstantsClient::at()`]. It's worth
|
||||
//! noting that constant values are pulled directly out of the node metadata which Subxt has
|
||||
//! Call [`crate::constants::ConstantsClient::at()`] to return and decode the constant into the
|
||||
//! type given by the address, or [`crate::constants::ConstantsClient::bytes_at()`] to return the
|
||||
//! raw bytes for some constant.
|
||||
//!
|
||||
//! Constant values are pulled directly out of the node metadata which Subxt has
|
||||
//! already acquired, and so this function requires no network access and is available from a
|
||||
//! [`crate::OfflineClient`].
|
||||
//!
|
||||
|
||||
@@ -13,13 +13,32 @@
|
||||
//!
|
||||
//! ## Getting a custom value
|
||||
//!
|
||||
//! Custom values can be accessed via a [`CustomValuesClient`](crate::custom_values::CustomValuesClient).
|
||||
//! The client exposes an `at` function by which a custom value can be fetched, given an address to this custom value.
|
||||
//! An address can be as simple as the aforementioned __name__ as a [str]. This will return a dynamic value, that you can manually decode into the type you want.
|
||||
//! Suppose, the custom types contain a value of type `Foo` under the name `"foo"` you can access it like in this example:
|
||||
//! First, you must construct an address to access a custom value. This can be either:
|
||||
//! - a raw [`str`] which assumes the return type to be the dynamic [`crate::dynamic::Value`] type,
|
||||
//! - created via [`dynamic`](crate::custom_values::dynamic) function whereby you set the return type
|
||||
//! that you want back,
|
||||
//! - created via statically generated addresses as part of the `#[subxt]` macro which define the return type.
|
||||
//!
|
||||
//! With an address, use [`at`](crate::custom_values::CustomValuesClient::at) to access and decode specific values, and
|
||||
//! [`bytes_at`](crate::custom_values::CustomValuesClient::bytes_at) to access the raw bytes.
|
||||
//!
|
||||
//! ## Examples
|
||||
//!
|
||||
//! Dynamically accessing a custom value using a [`str`] to select which one:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! use subxt::{OnlineClient, PolkadotConfig, ext::{codec::Decode, scale_decode::DecodeAsType}};
|
||||
//! use subxt::{OnlineClient, PolkadotConfig, ext::scale_decode::DecodeAsType};
|
||||
//! use subxt::dynamic::Value;
|
||||
//!
|
||||
//! let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
//! let custom_value_client = api.custom_values();
|
||||
//! let foo: Value = custom_value_client.at("foo")?;
|
||||
//! ```
|
||||
//!
|
||||
//! Use the [`dynamic`](crate::custom_values::dynamic) function to select the return type:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! use subxt::{OnlineClient, PolkadotConfig, ext::scale_decode::DecodeAsType};
|
||||
//!
|
||||
//! #[derive(Decode, DecodeAsType, Debug)]
|
||||
//! struct Foo {
|
||||
@@ -29,9 +48,8 @@
|
||||
//!
|
||||
//! let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
//! let custom_value_client = api.custom_values();
|
||||
//! let foo_dynamic = custom_value_client.at("foo")?;
|
||||
//! let foo: Foo = foo_dynamic.as_type()?;
|
||||
//!
|
||||
//! let custom_value_addr = subxt::custom_values::dynamic::<Foo>("foo");
|
||||
//! let foo: Foo = custom_value_client.at(&custom_value_addr)?;
|
||||
//! ```
|
||||
//!
|
||||
//! Alternatively we also provide a statically generated api for custom values:
|
||||
@@ -49,6 +67,3 @@
|
||||
//! let foo = custom_value_client.at(&static_address)?;
|
||||
//! ```
|
||||
//!
|
||||
//! Note: Names of custom values are converted to __snake_case__ to produce a valid function name during code generation.
|
||||
//! If there are multiple values where the names would be equal when converted to __snake_case__, functions might not be statically generated for some of them, because of naming conflicts.
|
||||
//! Make sure names in the custom values of your metadata differ significantly.
|
||||
|
||||
@@ -28,15 +28,17 @@
|
||||
//! let runtime_call = polkadot::apis().metadata().metadata_versions();
|
||||
//! ```
|
||||
//!
|
||||
//! Alternately, we can dynamically construct a runtime call:
|
||||
//! Alternately, we can dynamically construct a runtime call. The input type can be a tuple or
|
||||
//! vec or valid types implementing [`scale_encode::EncodeAsType`], and the output can be anything
|
||||
//! implementing [`scale_decode::DecodeAsType`]:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use subxt::dynamic::Value;
|
||||
//!
|
||||
//! let runtime_call = subxt::dynamic::runtime_api_call(
|
||||
//! let runtime_call = subxt::dynamic::runtime_api_call::<(), Vec<u32>>(
|
||||
//! "Metadata",
|
||||
//! "metadata_versions",
|
||||
//! Vec::<Value<()>>::new()
|
||||
//! ()
|
||||
//! );
|
||||
//! ```
|
||||
//!
|
||||
|
||||
@@ -9,90 +9,57 @@
|
||||
//! node storage. With Subxt, you can query this key/value storage with the following steps:
|
||||
//!
|
||||
//! 1. [Constructing a storage query](#constructing-a-storage-query).
|
||||
//! 2. [Submitting the query to get back the associated values](#submitting-it).
|
||||
//! 2. [Submitting the query to get back the associated entry](#submitting-it).
|
||||
//! 3. [Fetching](#fetching-storage-entries) or [iterating](#iterating-storage-entries) over that
|
||||
//! entry to retrieve the value or values within it.
|
||||
//!
|
||||
//! ## Constructing a storage query
|
||||
//!
|
||||
//! We can use the statically generated interface to build storage queries:
|
||||
//!
|
||||
//! ```rust,no_run,standalone_crate
|
||||
//! use subxt_signer::sr25519::dev;
|
||||
//!
|
||||
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
|
||||
//! pub mod polkadot {}
|
||||
//!
|
||||
//! let account = dev::alice().public_key().into();
|
||||
//! let storage_query = polkadot::storage().system().account(account);
|
||||
//! let storage_query = polkadot::storage().system().account();
|
||||
//! ```
|
||||
//!
|
||||
//! Alternately, we can dynamically construct a storage query. This will not be type checked or
|
||||
//! validated until it's submitted:
|
||||
//! Alternately, we can dynamically construct a storage query. A dynamic query needs the input
|
||||
//! and return value types to be specified, where we can use [`crate::dynamic::Value`] if unsure.
|
||||
//!
|
||||
//! ```rust,no_run,standalone_crate
|
||||
//! use subxt_signer::sr25519::dev;
|
||||
//! use subxt::dynamic::Value;
|
||||
//!
|
||||
//! let account = dev::alice().public_key();
|
||||
//! let storage_query = subxt::dynamic::storage("System", "Account", vec![
|
||||
//! Value::from_bytes(account)
|
||||
//! ]);
|
||||
//! let storage_query = subxt::dynamic::storage::<(Value,), Value>("System", "Account");
|
||||
//! ```
|
||||
//!
|
||||
//! As well as accessing specific entries, some storage locations can also be iterated over (such as
|
||||
//! the map of account information). To do this, suffix `_iter` onto the query constructor (this
|
||||
//! will only be available on static constructors when iteration is actually possible):
|
||||
//!
|
||||
//! ```rust,no_run,standalone_crate
|
||||
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
|
||||
//! pub mod polkadot {}
|
||||
//!
|
||||
//! // A static query capable of iterating over accounts:
|
||||
//! let storage_query = polkadot::storage().system().account_iter();
|
||||
//! // A dynamic query to do the same:
|
||||
//! let storage_query = subxt::dynamic::storage("System", "Account", ());
|
||||
//! ```
|
||||
//!
|
||||
//! Some storage entries are maps with multiple keys. As an example, we might end up with
|
||||
//! an API like `runtime::storage().foo().bar(u8, bool, u16, String)` to fetch some entry "bar".
|
||||
//! When this is the case, the codegen will generate multiple iterator query functions alongside
|
||||
//! the function to fetch an individual value:
|
||||
//!
|
||||
//! - `runtime::storage().foo().bar(u8, bool, u16, String)`: fetch a single entry from the "bar" map.
|
||||
//! - `runtime::storage().foo().bar_iter()`: iterate over all of the entries in the "bar" map.
|
||||
//! - `runtime::storage().foo().bar_iter1(u8)`: iterate over all of the entries in the "bar" map under
|
||||
//! a given `u8`.
|
||||
//! - `runtime::storage().foo().bar_iter2(u8, bool)`: iterate over all of the entries in the "bar" map under
|
||||
//! a given `u8` and `bool` value.
|
||||
//! - `runtime::storage().foo().bar_iter3(u8, bool, u16)`: iterate over all of the entries in the "bar" map under
|
||||
//! a given `u8`, `bool` and `u16` value.
|
||||
//!
|
||||
//! All valid storage queries implement [`crate::storage::Address`]. As well as describing
|
||||
//! how to build a valid storage query, this trait also has some associated types that determine the
|
||||
//! shape of the result you'll get back, and determine what you can do with it (ie, can you iterate
|
||||
//! over storage entries using it).
|
||||
//!
|
||||
//! Static queries set appropriate values for these associated types, and can therefore only be used
|
||||
//! where it makes sense. Dynamic queries don't know any better and can be used in more places, but
|
||||
//! may fail at runtime instead if they are invalid in those places.
|
||||
//!
|
||||
//! ## Submitting it
|
||||
//!
|
||||
//! Storage queries can be handed to various functions in [`crate::storage::Storage`] in order to
|
||||
//! Storage queries can be handed to various functions in [`crate::storage::StorageClientAt`] in order to
|
||||
//! obtain the associated values (also referred to as storage entries) back.
|
||||
//!
|
||||
//! The core API here is [`crate::storage::StorageClientAt::entry()`], which takes a query and looks up the
|
||||
//! corresponding storage entry, from which you can then fetch or iterate over the values contained within.
|
||||
//! [`crate::storage::StorageClientAt::fetch()`] and [`crate::storage::StorageClientAt::iter()`] are shorthand
|
||||
//! for this.
|
||||
//!
|
||||
//! When you wish to manually query some entry, [`crate::storage::StorageClientAt::fetch_raw()`] exists to take
|
||||
//! in raw bytes pointing at some storage value, and return the value bytes if possible. [`crate::storage::StorageClientAt::storage_version()`]
|
||||
//! and [`crate::storage::StorageClientAt::runtime_wasm_code()`] use this to retrieve the version of some storage API
|
||||
//! and the current Runtime WASM blob respectively.
|
||||
//!
|
||||
//! ### Fetching storage entries
|
||||
//!
|
||||
//! The simplest way to access storage entries is to construct a query and then call either
|
||||
//! [`crate::storage::Storage::fetch()`] or [`crate::storage::Storage::fetch_or_default()`] (the
|
||||
//! latter will only work for storage queries that have a default value when empty):
|
||||
//! [`crate::storage::StorageClientAt::fetch()`]:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../examples/storage_fetch.rs")]
|
||||
//! ```
|
||||
//!
|
||||
//! For completeness, below is an example using a dynamic query instead. The return type from a
|
||||
//! dynamic query is a [`crate::dynamic::DecodedValueThunk`], which can be decoded into a
|
||||
//! [`crate::dynamic::Value`], or else the raw bytes can be accessed instead.
|
||||
//! For completeness, below is an example using a dynamic query instead. Dynamic queries can define the types that
|
||||
//! they wish to accept inputs and decode the return value into ([`crate::dynamic::Value`] can be used here anywhere we
|
||||
//! are not sure of the specific types).
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../examples/storage_fetch_dynamic.rs")]
|
||||
@@ -113,18 +80,3 @@
|
||||
#![doc = include_str!("../../../examples/storage_iterating_dynamic.rs")]
|
||||
//! ```
|
||||
//!
|
||||
//! Here is an example of iterating over partial keys. In this example some multi-signature operations
|
||||
//! are sent to the node. We can iterate over the pending multisig operations of a single multisig account:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../examples/storage_iterating_partial.rs")]
|
||||
//! ```
|
||||
//!
|
||||
//! ### Advanced
|
||||
//!
|
||||
//! For more advanced use cases, have a look at [`crate::storage::Storage::fetch_raw`] and
|
||||
//! [`crate::storage::Storage::fetch_raw_keys`]. Both of these take raw bytes as arguments, which can be
|
||||
//! obtained from a [`crate::storage::Address`] by using
|
||||
//! [`crate::storage::StorageClient::address_bytes()`] or
|
||||
//! [`crate::storage::StorageClient::address_root_bytes()`].
|
||||
//!
|
||||
|
||||
@@ -13,6 +13,6 @@ mod online_client;
|
||||
|
||||
pub use offline_client::{OfflineClient, OfflineClientT};
|
||||
pub use online_client::{
|
||||
ClientRuntimeUpdater, OnlineClient, OnlineClientT, RuntimeUpdaterStream, Update, UpgradeError,
|
||||
ClientRuntimeUpdater, OnlineClient, OnlineClientT, RuntimeUpdaterStream, Update,
|
||||
};
|
||||
pub use subxt_core::client::{ClientState, RuntimeVersion};
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
blocks::{BlockRef, BlocksClient},
|
||||
config::{Config, HashFor},
|
||||
constants::ConstantsClient,
|
||||
error::Error,
|
||||
error::{BackendError, OnlineClientError, RuntimeUpdateeApplyError, RuntimeUpdaterError},
|
||||
events::EventsClient,
|
||||
runtime_api::RuntimeApiClient,
|
||||
storage::StorageClient,
|
||||
@@ -18,6 +18,7 @@ use crate::{
|
||||
view_functions::ViewFunctionsClient,
|
||||
};
|
||||
use derive_where::derive_where;
|
||||
use futures::TryFutureExt;
|
||||
use futures::future;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use subxt_core::client::{ClientState, RuntimeVersion};
|
||||
@@ -60,13 +61,13 @@ impl<T: Config> std::fmt::Debug for OnlineClient<T> {
|
||||
impl<T: Config> OnlineClient<T> {
|
||||
/// Construct a new [`OnlineClient`] using default settings which
|
||||
/// point to a locally running node on `ws://127.0.0.1:9944`.
|
||||
pub async fn new() -> Result<OnlineClient<T>, Error> {
|
||||
pub async fn new() -> Result<OnlineClient<T>, OnlineClientError> {
|
||||
let url = "ws://127.0.0.1:9944";
|
||||
OnlineClient::from_url(url).await
|
||||
}
|
||||
|
||||
/// Construct a new [`OnlineClient`], providing a URL to connect to.
|
||||
pub async fn from_url(url: impl AsRef<str>) -> Result<OnlineClient<T>, Error> {
|
||||
pub async fn from_url(url: impl AsRef<str>) -> Result<OnlineClient<T>, OnlineClientError> {
|
||||
subxt_rpcs::utils::validate_url_is_secure(url.as_ref())?;
|
||||
OnlineClient::from_insecure_url(url).await
|
||||
}
|
||||
@@ -74,7 +75,9 @@ impl<T: Config> OnlineClient<T> {
|
||||
/// Construct a new [`OnlineClient`], providing a URL to connect to.
|
||||
///
|
||||
/// Allows insecure URLs without SSL encryption, e.g. (http:// and ws:// URLs).
|
||||
pub async fn from_insecure_url(url: impl AsRef<str>) -> Result<OnlineClient<T>, Error> {
|
||||
pub async fn from_insecure_url(
|
||||
url: impl AsRef<str>,
|
||||
) -> Result<OnlineClient<T>, OnlineClientError> {
|
||||
let client = RpcClient::from_insecure_url(url).await?;
|
||||
let backend = LegacyBackend::builder().build(client);
|
||||
OnlineClient::from_backend(Arc::new(backend)).await
|
||||
@@ -86,7 +89,7 @@ impl<T: Config> OnlineClient<T> {
|
||||
/// This will use the current default [`Backend`], which may change in future releases.
|
||||
pub async fn from_rpc_client(
|
||||
rpc_client: impl Into<RpcClient>,
|
||||
) -> Result<OnlineClient<T>, Error> {
|
||||
) -> Result<OnlineClient<T>, OnlineClientError> {
|
||||
let rpc_client = rpc_client.into();
|
||||
let backend = Arc::new(LegacyBackend::builder().build(rpc_client));
|
||||
OnlineClient::from_backend(backend).await
|
||||
@@ -110,7 +113,7 @@ impl<T: Config> OnlineClient<T> {
|
||||
runtime_version: RuntimeVersion,
|
||||
metadata: impl Into<Metadata>,
|
||||
rpc_client: impl Into<RpcClient>,
|
||||
) -> Result<OnlineClient<T>, Error> {
|
||||
) -> Result<OnlineClient<T>, OnlineClientError> {
|
||||
let rpc_client = rpc_client.into();
|
||||
let backend = Arc::new(LegacyBackend::builder().build(rpc_client));
|
||||
OnlineClient::from_backend_with(genesis_hash, runtime_version, metadata, backend)
|
||||
@@ -118,13 +121,23 @@ impl<T: Config> OnlineClient<T> {
|
||||
|
||||
/// Construct a new [`OnlineClient`] by providing an underlying [`Backend`]
|
||||
/// implementation to power it. Other details will be obtained from the chain.
|
||||
pub async fn from_backend<B: Backend<T>>(backend: Arc<B>) -> Result<OnlineClient<T>, Error> {
|
||||
let latest_block = backend.latest_finalized_block_ref().await?;
|
||||
pub async fn from_backend<B: Backend<T>>(
|
||||
backend: Arc<B>,
|
||||
) -> Result<OnlineClient<T>, OnlineClientError> {
|
||||
let latest_block = backend
|
||||
.latest_finalized_block_ref()
|
||||
.await
|
||||
.map_err(OnlineClientError::CannotGetLatestFinalizedBlock)?;
|
||||
|
||||
let (genesis_hash, runtime_version, metadata) = future::join3(
|
||||
backend.genesis_hash(),
|
||||
backend.current_runtime_version(),
|
||||
OnlineClient::fetch_metadata(&*backend, latest_block.hash()),
|
||||
backend
|
||||
.genesis_hash()
|
||||
.map_err(OnlineClientError::CannotGetGenesisHash),
|
||||
backend
|
||||
.current_runtime_version()
|
||||
.map_err(OnlineClientError::CannotGetCurrentRuntimeVersion),
|
||||
OnlineClient::fetch_metadata(&*backend, latest_block.hash())
|
||||
.map_err(OnlineClientError::CannotFetchMetadata),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -148,7 +161,7 @@ impl<T: Config> OnlineClient<T> {
|
||||
runtime_version: RuntimeVersion,
|
||||
metadata: impl Into<Metadata>,
|
||||
backend: Arc<B>,
|
||||
) -> Result<OnlineClient<T>, Error> {
|
||||
) -> Result<OnlineClient<T>, OnlineClientError> {
|
||||
use subxt_core::config::Hasher;
|
||||
|
||||
let metadata = metadata.into();
|
||||
@@ -169,7 +182,7 @@ impl<T: Config> OnlineClient<T> {
|
||||
async fn fetch_metadata(
|
||||
backend: &dyn Backend<T>,
|
||||
block_hash: HashFor<T>,
|
||||
) -> Result<Metadata, Error> {
|
||||
) -> Result<Metadata, BackendError> {
|
||||
#[cfg(feature = "unstable-metadata")]
|
||||
{
|
||||
/// The unstable metadata version number.
|
||||
@@ -194,7 +207,7 @@ impl<T: Config> OnlineClient<T> {
|
||||
async fn fetch_latest_stable_metadata(
|
||||
backend: &dyn Backend<T>,
|
||||
block_hash: HashFor<T>,
|
||||
) -> Result<Metadata, Error> {
|
||||
) -> Result<Metadata, BackendError> {
|
||||
// The metadata versions we support in Subxt, from newest to oldest.
|
||||
use subxt_metadata::SUPPORTED_METADATA_VERSIONS;
|
||||
|
||||
@@ -235,7 +248,7 @@ impl<T: Config> OnlineClient<T> {
|
||||
/// tokio::spawn(async move {
|
||||
/// let mut update_stream = updater.runtime_updates().await.unwrap();
|
||||
///
|
||||
/// while let Some(Ok(update)) = update_stream.next().await {
|
||||
/// while let Ok(update) = update_stream.next().await {
|
||||
/// let version = update.runtime_version().spec_version;
|
||||
///
|
||||
/// match updater.apply_update(update) {
|
||||
@@ -416,9 +429,9 @@ impl<T: Config> ClientRuntimeUpdater<T> {
|
||||
}
|
||||
|
||||
/// Tries to apply a new update.
|
||||
pub fn apply_update(&self, update: Update) -> Result<(), UpgradeError> {
|
||||
pub fn apply_update(&self, update: Update) -> Result<(), RuntimeUpdateeApplyError> {
|
||||
if !self.is_runtime_version_different(&update.runtime_version) {
|
||||
return Err(UpgradeError::SameVersion);
|
||||
return Err(RuntimeUpdateeApplyError::SameVersion);
|
||||
}
|
||||
|
||||
self.do_update(update);
|
||||
@@ -430,12 +443,12 @@ impl<T: Config> ClientRuntimeUpdater<T> {
|
||||
///
|
||||
/// *Note:* This will run indefinitely until it errors, so the typical usage
|
||||
/// would be to run it in a separate background task.
|
||||
pub async fn perform_runtime_updates(&self) -> Result<(), Error> {
|
||||
pub async fn perform_runtime_updates(&self) -> Result<(), RuntimeUpdaterError> {
|
||||
// Obtain an update subscription to further detect changes in the runtime version of the node.
|
||||
let mut runtime_version_stream = self.runtime_updates().await?;
|
||||
|
||||
while let Some(update) = runtime_version_stream.next().await {
|
||||
let update = update?;
|
||||
loop {
|
||||
let update = runtime_version_stream.next().await?;
|
||||
|
||||
// This only fails if received the runtime version is the same the current runtime version
|
||||
// which might occur because that runtime subscriptions in substrate sends out the initial
|
||||
@@ -443,8 +456,6 @@ impl<T: Config> ClientRuntimeUpdater<T> {
|
||||
// Thus, fine to ignore here as it strictly speaking isn't really an error
|
||||
let _ = self.apply_update(update);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Low-level API to get runtime updates as a stream but it's doesn't check if the
|
||||
@@ -452,9 +463,16 @@ impl<T: Config> ClientRuntimeUpdater<T> {
|
||||
///
|
||||
/// Instead that's up to the user of this API to decide when to update and
|
||||
/// to perform the actual updating.
|
||||
pub async fn runtime_updates(&self) -> Result<RuntimeUpdaterStream<T>, Error> {
|
||||
pub async fn runtime_updates(&self) -> Result<RuntimeUpdaterStream<T>, RuntimeUpdaterError> {
|
||||
let stream = self
|
||||
.0
|
||||
.backend()
|
||||
.stream_runtime_version()
|
||||
.await
|
||||
.map_err(RuntimeUpdaterError::CannotStreamRuntimeVersion)?;
|
||||
|
||||
Ok(RuntimeUpdaterStream {
|
||||
stream: self.0.backend().stream_runtime_version().await?,
|
||||
stream,
|
||||
client: self.0.clone(),
|
||||
})
|
||||
}
|
||||
@@ -468,38 +486,27 @@ pub struct RuntimeUpdaterStream<T: Config> {
|
||||
|
||||
impl<T: Config> RuntimeUpdaterStream<T> {
|
||||
/// Wait for the next runtime update.
|
||||
pub async fn next(&mut self) -> Option<Result<Update, Error>> {
|
||||
let runtime_version = match self.stream.next().await? {
|
||||
Ok(runtime_version) => runtime_version,
|
||||
Err(err) => return Some(Err(err)),
|
||||
};
|
||||
pub async fn next(&mut self) -> Result<Update, RuntimeUpdaterError> {
|
||||
let runtime_version = self
|
||||
.stream
|
||||
.next()
|
||||
.await
|
||||
.ok_or(RuntimeUpdaterError::UnexpectedEndOfUpdateStream)?
|
||||
.map_err(RuntimeUpdaterError::CannotGetNextRuntimeVersion)?;
|
||||
|
||||
let at =
|
||||
match wait_runtime_upgrade_in_finalized_block(&self.client, &runtime_version).await? {
|
||||
Ok(at) => at,
|
||||
Err(err) => return Some(Err(err)),
|
||||
};
|
||||
let at = wait_runtime_upgrade_in_finalized_block(&self.client, &runtime_version).await?;
|
||||
|
||||
let metadata = match OnlineClient::fetch_metadata(self.client.backend(), at.hash()).await {
|
||||
Ok(metadata) => metadata,
|
||||
Err(err) => return Some(Err(err)),
|
||||
};
|
||||
let metadata = OnlineClient::fetch_metadata(self.client.backend(), at.hash())
|
||||
.await
|
||||
.map_err(RuntimeUpdaterError::CannotFetchNewMetadata)?;
|
||||
|
||||
Some(Ok(Update {
|
||||
Ok(Update {
|
||||
metadata,
|
||||
runtime_version,
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Error that can occur during upgrade.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum UpgradeError {
|
||||
/// The version is the same as the current version.
|
||||
SameVersion,
|
||||
}
|
||||
|
||||
/// Represents the state when a runtime upgrade occurred.
|
||||
pub struct Update {
|
||||
runtime_version: RuntimeVersion,
|
||||
@@ -522,64 +529,52 @@ impl Update {
|
||||
async fn wait_runtime_upgrade_in_finalized_block<T: Config>(
|
||||
client: &OnlineClient<T>,
|
||||
runtime_version: &RuntimeVersion,
|
||||
) -> Option<Result<BlockRef<HashFor<T>>, Error>> {
|
||||
use scale_value::At;
|
||||
|
||||
) -> Result<BlockRef<HashFor<T>>, RuntimeUpdaterError> {
|
||||
let hasher = client
|
||||
.inner
|
||||
.read()
|
||||
.expect("Lock shouldn't be poisoned")
|
||||
.hasher;
|
||||
|
||||
let mut block_sub = match client
|
||||
let mut block_sub = client
|
||||
.backend()
|
||||
.stream_finalized_block_headers(hasher)
|
||||
.await
|
||||
{
|
||||
Ok(s) => s,
|
||||
Err(err) => return Some(Err(err)),
|
||||
};
|
||||
.map_err(RuntimeUpdaterError::CannotStreamFinalizedBlocks)?;
|
||||
|
||||
let block_ref = loop {
|
||||
let (_, block_ref) = match block_sub.next().await? {
|
||||
Ok(n) => n,
|
||||
Err(err) => return Some(Err(err)),
|
||||
};
|
||||
let (_, block_ref) = block_sub
|
||||
.next()
|
||||
.await
|
||||
.ok_or(RuntimeUpdaterError::UnexpectedEndOfBlockStream)?
|
||||
.map_err(RuntimeUpdaterError::CannotGetNextFinalizedBlock)?;
|
||||
|
||||
let key: Vec<scale_value::Value> = vec![];
|
||||
let addr = crate::dynamic::storage("System", "LastRuntimeUpgrade", key);
|
||||
let addr =
|
||||
crate::dynamic::storage::<(), scale_value::Value>("System", "LastRuntimeUpgrade");
|
||||
|
||||
let chunk = match client.storage().at(block_ref.hash()).fetch(&addr).await {
|
||||
Ok(Some(v)) => v,
|
||||
Ok(None) => {
|
||||
// The storage `system::lastRuntimeUpgrade` should always exist.
|
||||
// <https://github.com/paritytech/polkadot-sdk/blob/master/substrate/frame/system/src/lib.rs#L958>
|
||||
unreachable!("The storage item `system::lastRuntimeUpgrade` should always exist")
|
||||
}
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
let client_at = client.storage().at(block_ref.hash());
|
||||
let value = client_at
|
||||
.entry(addr)
|
||||
// The storage `system::lastRuntimeUpgrade` should always exist.
|
||||
// <https://github.com/paritytech/polkadot-sdk/blob/master/substrate/frame/system/src/lib.rs#L958>
|
||||
.map_err(|_| RuntimeUpdaterError::CantFindSystemLastRuntimeUpgrade)?
|
||||
.fetch(())
|
||||
.await
|
||||
.map_err(RuntimeUpdaterError::CantFetchLastRuntimeUpgrade)?
|
||||
.decode_as::<LastRuntimeUpgrade>()
|
||||
.map_err(RuntimeUpdaterError::CannotDecodeLastRuntimeUpgrade)?;
|
||||
|
||||
let scale_val = match chunk.to_value() {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Some(Err(e.into())),
|
||||
};
|
||||
|
||||
let Some(Ok(spec_version)) = scale_val
|
||||
.at("spec_version")
|
||||
.and_then(|v| v.as_u128())
|
||||
.map(u32::try_from)
|
||||
else {
|
||||
return Some(Err(Error::Other(
|
||||
"Decoding `RuntimeVersion::spec_version` as u32 failed".to_string(),
|
||||
)));
|
||||
};
|
||||
#[derive(scale_decode::DecodeAsType)]
|
||||
struct LastRuntimeUpgrade {
|
||||
spec_version: u32,
|
||||
}
|
||||
|
||||
// We are waiting for the chain to have the same spec version
|
||||
// as sent out via the runtime subscription.
|
||||
if spec_version == runtime_version.spec_version {
|
||||
if value.spec_version == runtime_version.spec_version {
|
||||
break block_ref;
|
||||
}
|
||||
};
|
||||
|
||||
Some(Ok(block_ref))
|
||||
Ok(block_ref)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use crate::{Config, client::OfflineClientT, error::Error};
|
||||
use crate::{Config, client::OfflineClientT, error::ConstantError};
|
||||
use derive_where::derive_where;
|
||||
use subxt_core::constants::address::Address;
|
||||
|
||||
@@ -28,16 +28,22 @@ impl<T: Config, Client: OfflineClientT<T>> ConstantsClient<T, Client> {
|
||||
/// if the address is valid (or if it's not possible to check since the address has no validation hash).
|
||||
/// Return an error if the address was not valid or something went wrong trying to validate it (ie
|
||||
/// the pallet or constant in question do not exist at all).
|
||||
pub fn validate<Addr: Address>(&self, address: &Addr) -> Result<(), Error> {
|
||||
pub fn validate<Addr: Address>(&self, address: Addr) -> Result<(), ConstantError> {
|
||||
let metadata = self.client.metadata();
|
||||
subxt_core::constants::validate(address, &metadata).map_err(Error::from)
|
||||
subxt_core::constants::validate(address, &metadata)
|
||||
}
|
||||
|
||||
/// Access the constant at the address given, returning the type defined by this address.
|
||||
/// This is probably used with addresses given from static codegen, although you can manually
|
||||
/// construct your own, too.
|
||||
pub fn at<Addr: Address>(&self, address: &Addr) -> Result<Addr::Target, Error> {
|
||||
pub fn at<Addr: Address>(&self, address: Addr) -> Result<Addr::Target, ConstantError> {
|
||||
let metadata = self.client.metadata();
|
||||
subxt_core::constants::get(address, &metadata).map_err(Error::from)
|
||||
subxt_core::constants::get(address, &metadata)
|
||||
}
|
||||
|
||||
/// Access the bytes of a constant by the address it is registered under.
|
||||
pub fn bytes_at<Addr: Address>(&self, address: Addr) -> Result<Vec<u8>, ConstantError> {
|
||||
let metadata = self.client.metadata();
|
||||
subxt_core::constants::get_bytes(address, &metadata)
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user