mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-29 05:35:42 +00:00
Merge v0.50.x to master (#2127)
* 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 * [v0.50] update scale-info-legacy and frame-decode to latest (#2119) * bump scale-info-legacy and frame-decode to latest * Remove something we don't need in this PR * Fully remove unused for now dep * [v0.50] Convert historic metadata to subxt::Metadata (#2120) * First pass converting historic metadatas to our subxt::Metadata type * use published frame-decode * fmt and rename legacy metadata macro * Enable legacy feature where needed in subxt_metadata so it compiles on its own * Use cargo hack more in CI and fix subxt-metadata features * Add tests for metadata conversion (need to optimise; some too expensive right now * Address performance and equality issues in metadata conversion testing * fmt * fmt all * clippy * Fix a doc link * Test codegen and fixes to make it work * Remove local frame-decode patch * bump frame-decode to latest * [v0.50.0] Allow visiting extrinsic fields in subxt_historic (#2124) * Allow visiting extrinsic fields * fmt * Don't use local scale-decode dep * Clippy and tidy * Extend 'subxt codegen' CLI to work with legacy metadatas * Simplify historic extrinsics example now that AccountId32s have paths/names * clippy * clippy * clippy.. * Allow visiting storage values, too, and clean up extrinsic visiting a little by narrowing lifetime * Try to fix flaky test * Add custom value decode to extrinsics example * Remove useless else branch ra thought I needed * Simplify examples * Prep to release v0.0.5 (#2126)
This commit is contained in:
+13
-25
@@ -181,41 +181,29 @@ jobs:
|
||||
- name: Cargo check all targets.
|
||||
run: cargo check --all-targets
|
||||
|
||||
# Next, check subxt features.
|
||||
# Next, check each subxt feature in isolation.
|
||||
# - `native` feature must always be enabled
|
||||
# - `web` feature is always ignored.
|
||||
# - This means, don't check --no-default-features and don't try enabling --all-features; both will fail
|
||||
- name: Cargo hack; check each subxt feature
|
||||
run: cargo hack -p subxt --each-feature check --exclude-no-default-features --exclude-all-features --exclude-features web --features native
|
||||
run: cargo hack -p subxt --each-feature check --exclude-features web --features native
|
||||
|
||||
# Same with subxt-historic
|
||||
- name: Cargo hack; check each subxt feature
|
||||
run: cargo hack -p subxt-historic --each-feature check --exclude-no-default-features --exclude-all-features --exclude-features web --features native
|
||||
run: cargo hack -p subxt-historic --each-feature check --exclude-features web --features native
|
||||
|
||||
# Subxt-signer has the "subxt" features enabled in the "check all targets" test. Run it on its own to
|
||||
# check it without. We can't enable subxt or web features here, so no cargo hack.
|
||||
- name: Cargo check subxt-signer
|
||||
run: |
|
||||
cargo check -p subxt-signer
|
||||
cargo check -p subxt-signer --no-default-features --features sr25519
|
||||
cargo check -p subxt-signer --no-default-features --features ecdsa
|
||||
cargo check -p subxt-signer --no-default-features --features unstable-eth
|
||||
# And with subxt-rpcs
|
||||
- name: Cargo hack; check each subxt-rpcs feature
|
||||
run: cargo hack -p subxt-rpcs --each-feature check --exclude-features web --features native
|
||||
|
||||
# Subxt-rpcs has a bunch of clients that can be exposed. Check that they all stand on their own.
|
||||
- name: Cargo check subxt-rpcs
|
||||
run: |
|
||||
cargo check -p subxt-rpcs
|
||||
cargo check -p subxt-rpcs --no-default-features --features native
|
||||
cargo check -p subxt-rpcs --no-default-features --features native,subxt
|
||||
cargo check -p subxt-rpcs --no-default-features --features native,jsonrpsee
|
||||
cargo check -p subxt-rpcs --no-default-features --features native,reconnecting-rpc-client
|
||||
cargo check -p subxt-rpcs --no-default-features --features native,mock-rpc-client
|
||||
cargo check -p subxt-rpcs --no-default-features --features native,unstable-light-client
|
||||
# And with subxt-signer (seems to work with a more basic check here; disable web if it becomes an issue).
|
||||
- name: Cargo hack; check each subxt-signer feature
|
||||
run: cargo hack -p subxt-signer --each-feature check
|
||||
|
||||
# We can't enable web features here, so no cargo hack.
|
||||
# And for subxt-lightclient.
|
||||
- name: Cargo check subxt-lightclient
|
||||
run: cargo check -p subxt-lightclient
|
||||
run: cargo hack -p subxt-lightclient --each-feature check --exclude-features web --features native
|
||||
|
||||
# Next, check each other package in isolation.
|
||||
# Next, check all other crates.
|
||||
- name: Cargo hack; check each feature/crate on its own
|
||||
run: cargo hack --exclude subxt --exclude subxt-historic --exclude subxt-signer --exclude subxt-lightclient --exclude subxt-rpcs --exclude-all-features --each-feature check --workspace
|
||||
|
||||
|
||||
Generated
+33
-15
@@ -1953,9 +1953,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "frame-decode"
|
||||
version = "0.10.0"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "641e3739fa708a278d35b008a05244008c221240abc3e1c27138466c13e999ed"
|
||||
checksum = "0fb3bfa2988ef40247e0e0eecfb171a01ad6f4e399485503aad4c413a5f236f3"
|
||||
dependencies = [
|
||||
"frame-metadata 23.0.0",
|
||||
"parity-scale-codec",
|
||||
@@ -4383,9 +4383,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "scale-decode"
|
||||
version = "0.16.0"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d78196772d25b90a98046794ce0fe2588b39ebdfbdc1e45b4c6c85dd43bebad"
|
||||
checksum = "8d6ed61699ad4d54101ab5a817169259b5b0efc08152f8632e61482d8a27ca3d"
|
||||
dependencies = [
|
||||
"parity-scale-codec",
|
||||
"primitive-types",
|
||||
@@ -4398,9 +4398,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "scale-decode-derive"
|
||||
version = "0.16.0"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f4b54a1211260718b92832b661025d1f1a4b6930fbadd6908e00edd265fa5f7"
|
||||
checksum = "65cb245f7fdb489e7ba43a616cbd34427fe3ba6fe0edc1d0d250085e6c84f3ec"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
@@ -4464,9 +4464,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "scale-info-legacy"
|
||||
version = "0.2.3"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5da3f59983b08a37d8d979d2326bdc00e8cca57b3d28fb05bdc0f6d7c28600c"
|
||||
checksum = "2500adfb429a0ffda37919df92c05d0c1359c10e0444c17253c84b84dfce542f"
|
||||
dependencies = [
|
||||
"hashbrown 0.15.3",
|
||||
"scale-type-resolver",
|
||||
@@ -4500,6 +4500,19 @@ dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scale-typegen"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "642d2f13f3fc9a34ea2c1e36142984eba78cd2405a61632492f8b52993e98879"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"scale-info",
|
||||
"syn 2.0.101",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scale-typegen-description"
|
||||
version = "0.11.1"
|
||||
@@ -4513,16 +4526,16 @@ dependencies = [
|
||||
"rand",
|
||||
"rand_chacha",
|
||||
"scale-info",
|
||||
"scale-typegen",
|
||||
"scale-typegen 0.11.1",
|
||||
"scale-value",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[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",
|
||||
@@ -5645,6 +5658,7 @@ version = "0.44.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"color-eyre",
|
||||
"frame-decode",
|
||||
"frame-metadata 23.0.0",
|
||||
"heck",
|
||||
"hex",
|
||||
@@ -5654,7 +5668,8 @@ dependencies = [
|
||||
"pretty_assertions",
|
||||
"quote",
|
||||
"scale-info",
|
||||
"scale-typegen",
|
||||
"scale-info-legacy",
|
||||
"scale-typegen 0.12.0",
|
||||
"scale-typegen-description",
|
||||
"scale-value",
|
||||
"serde",
|
||||
@@ -5682,7 +5697,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"scale-info",
|
||||
"scale-typegen",
|
||||
"scale-typegen 0.12.0",
|
||||
"subxt-metadata",
|
||||
"syn 2.0.101",
|
||||
"thiserror 2.0.12",
|
||||
@@ -5724,7 +5739,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "subxt-historic"
|
||||
version = "0.0.4"
|
||||
version = "0.0.5"
|
||||
dependencies = [
|
||||
"frame-decode",
|
||||
"frame-metadata 23.0.0",
|
||||
@@ -5779,7 +5794,7 @@ dependencies = [
|
||||
"quote",
|
||||
"sc-executor",
|
||||
"sc-executor-common",
|
||||
"scale-typegen",
|
||||
"scale-typegen 0.12.0",
|
||||
"sp-io",
|
||||
"sp-maybe-compressed-blob",
|
||||
"sp-state-machine",
|
||||
@@ -5800,7 +5815,10 @@ dependencies = [
|
||||
"hashbrown 0.14.5",
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
"scale-info-legacy",
|
||||
"scale-type-resolver",
|
||||
"sp-crypto-hashing",
|
||||
"subxt-codegen",
|
||||
"subxt-utils-stripmetadata",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
+5
-5
@@ -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.15.0", 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,13 +98,13 @@ 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-decode = { version = "0.16.2", 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-typegen = "0.11.1"
|
||||
scale-info-legacy = { version = "0.4.0", default-features = false }
|
||||
scale-typegen = "0.12.0"
|
||||
scale-typegen-description = "0.11.0"
|
||||
serde = { version = "1.0.210", default-features = false, features = ["derive"] }
|
||||
serde_json = { version = "1.0.128", default-features = false }
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3
-1
@@ -30,16 +30,18 @@ subxt-codegen = { workspace = true }
|
||||
scale-typegen = { workspace = true }
|
||||
subxt-utils-fetchmetadata = { workspace = true, features = ["url"] }
|
||||
subxt-utils-stripmetadata = { workspace = true }
|
||||
subxt-metadata = { workspace = true }
|
||||
subxt-metadata = { workspace = true, features = ["legacy"] }
|
||||
subxt = { workspace = true, features = ["default"] }
|
||||
clap = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
color-eyre = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
hex = { workspace = true }
|
||||
frame-decode = { workspace = true, features = ["legacy-types"] }
|
||||
frame-metadata = { workspace = true }
|
||||
codec = { package = "parity-scale-codec", workspace = true }
|
||||
scale-info = { workspace = true }
|
||||
scale-info-legacy = { workspace = true }
|
||||
scale-value = { workspace = true }
|
||||
syn = { workspace = true }
|
||||
quote = { workspace = true }
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
|
||||
use crate::utils::{FileOrUrl, validate_url_security};
|
||||
use clap::Parser as ClapParser;
|
||||
use codec::Decode;
|
||||
use color_eyre::eyre::eyre;
|
||||
use scale_typegen_description::scale_typegen::typegen::{
|
||||
settings::substitutes::path_segments,
|
||||
validation::{registry_contains_type_path, similar_type_paths_in_registry},
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
use subxt_codegen::CodegenBuilder;
|
||||
use subxt_metadata::Metadata;
|
||||
|
||||
@@ -28,6 +28,12 @@ pub struct Opts {
|
||||
/// Additional attributes
|
||||
#[clap(long = "attribute")]
|
||||
attributes: Vec<String>,
|
||||
/// Path to legacy type definitions (required for metadatas pre-V14)
|
||||
#[clap(long)]
|
||||
legacy_types: Option<PathBuf>,
|
||||
/// The spec version of the legacy metadata (required for metadatas pre-V14)
|
||||
#[clap(long)]
|
||||
legacy_spec_version: Option<u64>,
|
||||
/// Additional derives for a given type.
|
||||
///
|
||||
/// Example 1: `--derive-for-type my_module::my_type=serde::Serialize`.
|
||||
@@ -145,9 +151,20 @@ pub async fn run(opts: Opts, output: &mut impl std::io::Write) -> color_eyre::Re
|
||||
validate_url_security(opts.file_or_url.url.as_ref(), opts.allow_insecure)?;
|
||||
|
||||
let bytes = opts.file_or_url.fetch().await?;
|
||||
let legacy_types = opts
|
||||
.legacy_types
|
||||
.map(|path| {
|
||||
let bytes = std::fs::read(path).map_err(|e| eyre!("Cannot read legacy_types: {e}"))?;
|
||||
let types = frame_decode::legacy_types::from_bytes(&bytes)
|
||||
.map_err(|e| eyre!("Cannot deserialize legacy_types: {e}"))?;
|
||||
Ok::<_, color_eyre::eyre::Error>(types)
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
codegen(
|
||||
&bytes,
|
||||
legacy_types,
|
||||
opts.legacy_spec_version,
|
||||
opts.derives,
|
||||
opts.attributes,
|
||||
opts.derives_for_type,
|
||||
@@ -175,6 +192,8 @@ impl syn::parse::Parse for OuterAttribute {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn codegen(
|
||||
metadata_bytes: &[u8],
|
||||
legacy_types: Option<scale_info_legacy::ChainTypeRegistry>,
|
||||
legacy_spec_version: Option<u64>,
|
||||
raw_derives: Vec<String>,
|
||||
raw_attributes: Vec<String>,
|
||||
derives_for_type: Vec<DeriveForType>,
|
||||
@@ -211,8 +230,79 @@ fn codegen(
|
||||
}
|
||||
|
||||
let metadata = {
|
||||
let mut metadata = subxt_metadata::Metadata::decode(&mut &*metadata_bytes)
|
||||
.map_err(|e| eyre!("Cannot decode the provided metadata: {e}"))?;
|
||||
let runtime_metadata = subxt_metadata::decode_runtime_metadata(metadata_bytes)?;
|
||||
let mut metadata = match runtime_metadata {
|
||||
// Too old to work with:
|
||||
frame_metadata::RuntimeMetadata::V0(_)
|
||||
| frame_metadata::RuntimeMetadata::V1(_)
|
||||
| frame_metadata::RuntimeMetadata::V2(_)
|
||||
| frame_metadata::RuntimeMetadata::V3(_)
|
||||
| frame_metadata::RuntimeMetadata::V4(_)
|
||||
| frame_metadata::RuntimeMetadata::V5(_)
|
||||
| frame_metadata::RuntimeMetadata::V6(_)
|
||||
| frame_metadata::RuntimeMetadata::V7(_) => {
|
||||
Err(eyre!("Metadata V1-V7 cannot be decoded from"))
|
||||
}
|
||||
// Converting legacy metadatas:
|
||||
frame_metadata::RuntimeMetadata::V8(md) => {
|
||||
let legacy_types = legacy_types
|
||||
.ok_or_else(|| eyre!("--legacy-types needed to load V8 metadata"))?;
|
||||
let legacy_spec = legacy_spec_version
|
||||
.ok_or_else(|| eyre!("--legacy-spec-version needed to load V8 metadata"))?;
|
||||
Metadata::from_v8(&md, &legacy_types.for_spec_version(legacy_spec))
|
||||
.map_err(|e| eyre!("Cannot load V8 metadata: {e}"))
|
||||
}
|
||||
frame_metadata::RuntimeMetadata::V9(md) => {
|
||||
let legacy_types = legacy_types
|
||||
.ok_or_else(|| eyre!("--legacy-types needed to load V9 metadata"))?;
|
||||
let legacy_spec = legacy_spec_version
|
||||
.ok_or_else(|| eyre!("--legacy-spec-version needed to load V9 metadata"))?;
|
||||
Metadata::from_v9(&md, &legacy_types.for_spec_version(legacy_spec))
|
||||
.map_err(|e| eyre!("Cannot load V9 metadata: {e}"))
|
||||
}
|
||||
frame_metadata::RuntimeMetadata::V10(md) => {
|
||||
let legacy_types = legacy_types
|
||||
.ok_or_else(|| eyre!("--legacy-types needed to load V10 metadata"))?;
|
||||
let legacy_spec = legacy_spec_version
|
||||
.ok_or_else(|| eyre!("--legacy-spec-version needed to load V10 metadata"))?;
|
||||
Metadata::from_v10(&md, &legacy_types.for_spec_version(legacy_spec))
|
||||
.map_err(|e| eyre!("Cannot load V10 metadata: {e}"))
|
||||
}
|
||||
frame_metadata::RuntimeMetadata::V11(md) => {
|
||||
let legacy_types = legacy_types
|
||||
.ok_or_else(|| eyre!("--legacy-types needed to load V11 metadata"))?;
|
||||
let legacy_spec = legacy_spec_version
|
||||
.ok_or_else(|| eyre!("--legacy-spec-version needed to load V11 metadata"))?;
|
||||
Metadata::from_v11(&md, &legacy_types.for_spec_version(legacy_spec))
|
||||
.map_err(|e| eyre!("Cannot load V11 metadata: {e}"))
|
||||
}
|
||||
frame_metadata::RuntimeMetadata::V12(md) => {
|
||||
let legacy_types = legacy_types
|
||||
.ok_or_else(|| eyre!("--legacy-types needed to load V12 metadata"))?;
|
||||
let legacy_spec = legacy_spec_version
|
||||
.ok_or_else(|| eyre!("--legacy-spec-version needed to load V12 metadata"))?;
|
||||
Metadata::from_v12(&md, &legacy_types.for_spec_version(legacy_spec))
|
||||
.map_err(|e| eyre!("Cannot load V12 metadata: {e}"))
|
||||
}
|
||||
frame_metadata::RuntimeMetadata::V13(md) => {
|
||||
let legacy_types = legacy_types
|
||||
.ok_or_else(|| eyre!("--legacy-types needed to load V13 metadata"))?;
|
||||
let legacy_spec = legacy_spec_version
|
||||
.ok_or_else(|| eyre!("--legacy-spec-version needed to load V13 metadata"))?;
|
||||
Metadata::from_v13(&md, &legacy_types.for_spec_version(legacy_spec))
|
||||
.map_err(|e| eyre!("Cannot load V13 metadata: {e}"))
|
||||
}
|
||||
// Converting modern metadatas:
|
||||
frame_metadata::RuntimeMetadata::V14(md) => {
|
||||
Metadata::from_v14(md).map_err(|e| eyre!("Cannot load V14 metadata: {e}"))
|
||||
}
|
||||
frame_metadata::RuntimeMetadata::V15(md) => {
|
||||
Metadata::from_v15(md).map_err(|e| eyre!("Cannot load V15 metadata: {e}"))
|
||||
}
|
||||
frame_metadata::RuntimeMetadata::V16(md) => {
|
||||
Metadata::from_v16(md).map_err(|e| eyre!("Cannot load V16 metadata: {e}"))
|
||||
}
|
||||
}?;
|
||||
|
||||
// Run this first to ensure type paths are unique (which may result in 1,2,3 suffixes being added
|
||||
// to type paths), so that when we validate derives/substitutions below, they are allowed for such
|
||||
|
||||
+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]
|
||||
@@ -496,7 +494,7 @@ mod tests {
|
||||
let metadata = metadata();
|
||||
|
||||
// Except our metadata to contain the registered types.
|
||||
let pallet = metadata.pallet_by_index(0).expect("pallet exists");
|
||||
let pallet = metadata.pallet_by_call_index(0).expect("pallet exists");
|
||||
let extrinsic = pallet
|
||||
.call_variant_by_index(2)
|
||||
.expect("metadata contains the RuntimeCall enum with this pallet");
|
||||
@@ -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>),
|
||||
}
|
||||
|
||||
+63
-27
@@ -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_event_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();
|
||||
@@ -342,7 +359,7 @@ impl<T: Config> EventDetails<T> {
|
||||
pub fn event_metadata(&self) -> EventMetadataDetails<'_> {
|
||||
let pallet = self
|
||||
.metadata
|
||||
.pallet_by_index(self.pallet_index())
|
||||
.pallet_by_event_index(self.pallet_index())
|
||||
.expect("event pallet to be found; we did this already during decoding");
|
||||
let variant = pallet
|
||||
.event_variant_by_index(self.variant_index())
|
||||
@@ -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)?;
|
||||
|
||||
+28
-15
@@ -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,13 +167,22 @@ 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 pallet_index = pallet.call_index();
|
||||
let call_index = call.index;
|
||||
|
||||
pallet_index.encode_to(out);
|
||||
@@ -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();
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# subxt-historic changelog
|
||||
|
||||
This is separate from the Subxt changelog as subxt-historic is currently releasaed separately.
|
||||
|
||||
Eventually this project will merge with Subxt and no longer exist, but until then it's being maintained and updated where needed.
|
||||
|
||||
## 0.0.5 (2025-11-21)
|
||||
|
||||
- Rename some fields for consistency.
|
||||
- Update versions of underlying libraries being used.
|
||||
- Add `.visit()` methods to extrinsic fields and storage values, and examples of using this to our examples.
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "subxt-historic"
|
||||
version = "0.0.4"
|
||||
version = "0.0.5"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
@@ -45,7 +45,7 @@ jsonrpsee = [
|
||||
subxt-rpcs = { workspace = true }
|
||||
frame-decode = { workspace = true, features = ["legacy", "legacy-types"] }
|
||||
frame-metadata = { workspace = true, features = ["std", "legacy"] }
|
||||
scale-type-resolver = { workspace = true }
|
||||
scale-type-resolver = { workspace = true, features = ["scale-info"] }
|
||||
codec = { workspace = true }
|
||||
primitive-types = { workspace = true }
|
||||
scale-info = { workspace = true }
|
||||
@@ -60,4 +60,4 @@ futures = { workspace = true }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
scale-value = { workspace = true }
|
||||
scale-decode = { workspace = true, features = ["derive"] }
|
||||
hex = { workspace = true }
|
||||
hex = { workspace = true }
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#![allow(missing_docs)]
|
||||
use subxt_historic::{Error, OnlineClient, PolkadotConfig};
|
||||
use subxt_historic::{OnlineClient, PolkadotConfig};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Error> {
|
||||
async fn main() -> Result<(), Box<dyn core::error::Error + Send + Sync + 'static>> {
|
||||
// Configuration for the Polkadot relay chain.
|
||||
let config = PolkadotConfig::new();
|
||||
|
||||
@@ -10,7 +10,7 @@ async fn main() -> Result<(), Error> {
|
||||
let client = OnlineClient::from_url(config, "wss://rpc.polkadot.io").await?;
|
||||
|
||||
// Iterate through some randomly selected old blocks to show how to fetch and decode extrinsics.
|
||||
for block_number in 123456.. {
|
||||
for block_number in 1234567.. {
|
||||
println!("=== Block {block_number} ===");
|
||||
|
||||
// Point the client at a specific block number. By default this will download and cache
|
||||
@@ -40,10 +40,26 @@ async fn main() -> Result<(), Error> {
|
||||
// scale_value::Value type, which can represent any SCALE encoded data, but if you
|
||||
// have an idea of the type then you can try to decode into that type instead):
|
||||
for field in extrinsic.call().fields().iter() {
|
||||
// We can visit fields, which gives us the ability to inspect and decode information
|
||||
// from them selectively, returning whatever we like from it. Here we demo our
|
||||
// type name visitor which is defined below:
|
||||
let tn = field
|
||||
.visit(type_name::GetTypeName::new())?
|
||||
.unwrap_or_default();
|
||||
|
||||
// When visiting fields we can also decode into a custom shape like so:
|
||||
let _custom_value = field.visit(value::GetValue::new())?;
|
||||
|
||||
// We can also obtain and decode things without the complexity of the above:
|
||||
println!(
|
||||
" {}: {}",
|
||||
" {}: {} {}",
|
||||
field.name(),
|
||||
field.decode::<scale_value::Value>().unwrap()
|
||||
field.decode_as::<scale_value::Value>().unwrap(),
|
||||
if tn.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("(type name: {tn})")
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,7 +69,7 @@ async fn main() -> Result<(), Error> {
|
||||
extrinsic
|
||||
.call()
|
||||
.fields()
|
||||
.decode::<scale_value::Composite<_>>()
|
||||
.decode_as::<scale_value::Composite<_>>()
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
@@ -66,14 +82,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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -81,3 +97,354 @@ async fn main() -> Result<(), Error> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This module defines an example visitor which retrieves the name of a type.
|
||||
/// This is a more advanced use case and can typically be avoided.
|
||||
mod type_name {
|
||||
use scale_decode::{
|
||||
Visitor,
|
||||
visitor::types::{Composite, Sequence, Variant},
|
||||
visitor::{TypeIdFor, Unexpected},
|
||||
};
|
||||
use scale_type_resolver::TypeResolver;
|
||||
|
||||
/// This is a visitor which obtains type names.
|
||||
pub struct GetTypeName<R> {
|
||||
marker: core::marker::PhantomData<R>,
|
||||
}
|
||||
|
||||
impl<R> GetTypeName<R> {
|
||||
/// Construct our TypeName visitor.
|
||||
pub fn new() -> Self {
|
||||
GetTypeName {
|
||||
marker: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: TypeResolver> Visitor for GetTypeName<R> {
|
||||
type Value<'scale, 'resolver> = Option<&'resolver str>;
|
||||
type Error = scale_decode::Error;
|
||||
type TypeResolver = R;
|
||||
|
||||
// Look at the path of types that have paths and return the ident from that.
|
||||
fn visit_composite<'scale, 'resolver>(
|
||||
self,
|
||||
value: &mut Composite<'scale, 'resolver, Self::TypeResolver>,
|
||||
_type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
Ok(value.path().last())
|
||||
}
|
||||
fn visit_variant<'scale, 'resolver>(
|
||||
self,
|
||||
value: &mut Variant<'scale, 'resolver, Self::TypeResolver>,
|
||||
_type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
Ok(value.path().last())
|
||||
}
|
||||
fn visit_sequence<'scale, 'resolver>(
|
||||
self,
|
||||
value: &mut Sequence<'scale, 'resolver, Self::TypeResolver>,
|
||||
_type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
Ok(value.path().last())
|
||||
}
|
||||
|
||||
// Else, we return nothing as we can't find a name for the type.
|
||||
fn visit_unexpected<'scale, 'resolver>(
|
||||
self,
|
||||
_unexpected: Unexpected,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This visitor demonstrates how to decode and return a custom Value shape
|
||||
mod value {
|
||||
use scale_decode::{
|
||||
Visitor,
|
||||
visitor::TypeIdFor,
|
||||
visitor::types::{Array, BitSequence, Composite, Sequence, Str, Tuple, Variant},
|
||||
};
|
||||
use scale_type_resolver::TypeResolver;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// A value type we're decoding into.
|
||||
#[allow(dead_code)]
|
||||
pub enum Value {
|
||||
Number(f64),
|
||||
BigNumber(String),
|
||||
Bool(bool),
|
||||
Char(char),
|
||||
Array(Vec<Value>),
|
||||
String(String),
|
||||
Address(Vec<u8>),
|
||||
I256([u8; 32]),
|
||||
U256([u8; 32]),
|
||||
Struct(HashMap<String, Value>),
|
||||
Variant(String, VariantFields),
|
||||
}
|
||||
|
||||
pub enum VariantFields {
|
||||
Unnamed(Vec<Value>),
|
||||
Named(HashMap<String, Value>),
|
||||
}
|
||||
|
||||
/// An error we can encounter trying to decode things into a [`Value`]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ValueError {
|
||||
#[error("Decode error: {0}")]
|
||||
Decode(#[from] scale_decode::visitor::DecodeError),
|
||||
#[error("Cannot decode bit sequence: {0}")]
|
||||
CannotDecodeBitSequence(codec::Error),
|
||||
}
|
||||
|
||||
/// This is a visitor which obtains type names.
|
||||
pub struct GetValue<R> {
|
||||
marker: core::marker::PhantomData<R>,
|
||||
}
|
||||
|
||||
impl<R> GetValue<R> {
|
||||
/// Construct our TypeName visitor.
|
||||
pub fn new() -> Self {
|
||||
GetValue {
|
||||
marker: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: TypeResolver> Visitor for GetValue<R> {
|
||||
type Value<'scale, 'resolver> = Value;
|
||||
type Error = ValueError;
|
||||
type TypeResolver = R;
|
||||
|
||||
fn visit_i256<'resolver>(
|
||||
self,
|
||||
value: &[u8; 32],
|
||||
_type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'_, 'resolver>, Self::Error> {
|
||||
Ok(Value::I256(*value))
|
||||
}
|
||||
|
||||
fn visit_u256<'resolver>(
|
||||
self,
|
||||
value: &[u8; 32],
|
||||
_type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'_, 'resolver>, Self::Error> {
|
||||
Ok(Value::U256(*value))
|
||||
}
|
||||
|
||||
fn visit_i128<'scale, 'resolver>(
|
||||
self,
|
||||
value: i128,
|
||||
_type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
let attempt = value as f64;
|
||||
if attempt as i128 == value {
|
||||
Ok(Value::Number(attempt))
|
||||
} else {
|
||||
Ok(Value::BigNumber(value.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_i64<'scale, 'resolver>(
|
||||
self,
|
||||
value: i64,
|
||||
type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
self.visit_i128(value.into(), type_id)
|
||||
}
|
||||
|
||||
fn visit_i32<'scale, 'resolver>(
|
||||
self,
|
||||
value: i32,
|
||||
type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
self.visit_i128(value.into(), type_id)
|
||||
}
|
||||
|
||||
fn visit_i16<'scale, 'resolver>(
|
||||
self,
|
||||
value: i16,
|
||||
type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
self.visit_i128(value.into(), type_id)
|
||||
}
|
||||
|
||||
fn visit_i8<'scale, 'resolver>(
|
||||
self,
|
||||
value: i8,
|
||||
type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
self.visit_i128(value.into(), type_id)
|
||||
}
|
||||
|
||||
fn visit_u128<'scale, 'resolver>(
|
||||
self,
|
||||
value: u128,
|
||||
_type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
let attempt = value as f64;
|
||||
if attempt as u128 == value {
|
||||
Ok(Value::Number(attempt))
|
||||
} else {
|
||||
Ok(Value::BigNumber(value.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_u64<'scale, 'resolver>(
|
||||
self,
|
||||
value: u64,
|
||||
type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
self.visit_u128(value.into(), type_id)
|
||||
}
|
||||
|
||||
fn visit_u32<'scale, 'resolver>(
|
||||
self,
|
||||
value: u32,
|
||||
type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
self.visit_u128(value.into(), type_id)
|
||||
}
|
||||
|
||||
fn visit_u16<'scale, 'resolver>(
|
||||
self,
|
||||
value: u16,
|
||||
type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
self.visit_u128(value.into(), type_id)
|
||||
}
|
||||
|
||||
fn visit_u8<'scale, 'resolver>(
|
||||
self,
|
||||
value: u8,
|
||||
type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
self.visit_u128(value.into(), type_id)
|
||||
}
|
||||
|
||||
fn visit_bool<'scale, 'resolver>(
|
||||
self,
|
||||
value: bool,
|
||||
_type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
Ok(Value::Bool(value))
|
||||
}
|
||||
|
||||
fn visit_char<'scale, 'resolver>(
|
||||
self,
|
||||
value: char,
|
||||
_type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
Ok(Value::Char(value))
|
||||
}
|
||||
|
||||
fn visit_array<'scale, 'resolver>(
|
||||
self,
|
||||
values: &mut Array<'scale, 'resolver, Self::TypeResolver>,
|
||||
_type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
Ok(Value::Array(to_array(values.remaining(), values)?))
|
||||
}
|
||||
|
||||
fn visit_sequence<'scale, 'resolver>(
|
||||
self,
|
||||
values: &mut Sequence<'scale, 'resolver, Self::TypeResolver>,
|
||||
_type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
Ok(Value::Array(to_array(values.remaining(), values)?))
|
||||
}
|
||||
|
||||
fn visit_str<'scale, 'resolver>(
|
||||
self,
|
||||
value: &mut Str<'scale>,
|
||||
_type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
Ok(Value::String(value.as_str()?.to_owned()))
|
||||
}
|
||||
|
||||
fn visit_tuple<'scale, 'resolver>(
|
||||
self,
|
||||
values: &mut Tuple<'scale, 'resolver, Self::TypeResolver>,
|
||||
_type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
Ok(Value::Array(to_array(values.remaining(), values)?))
|
||||
}
|
||||
|
||||
fn visit_bitsequence<'scale, 'resolver>(
|
||||
self,
|
||||
value: &mut BitSequence<'scale>,
|
||||
_type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
let bits = value.decode()?;
|
||||
let mut out = Vec::with_capacity(bits.len());
|
||||
for b in bits {
|
||||
let b = b.map_err(ValueError::CannotDecodeBitSequence)?;
|
||||
out.push(Value::Bool(b));
|
||||
}
|
||||
Ok(Value::Array(out))
|
||||
}
|
||||
|
||||
fn visit_composite<'scale, 'resolver>(
|
||||
self,
|
||||
value: &mut Composite<'scale, 'resolver, Self::TypeResolver>,
|
||||
_type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
// Special case for ss58 addresses:
|
||||
if let Some(n) = value.name()
|
||||
&& n == "AccountId32"
|
||||
&& value.bytes_from_start().len() == 32
|
||||
{
|
||||
return Ok(Value::Address(value.bytes_from_start().to_vec()));
|
||||
}
|
||||
|
||||
// Reuse logic for decoding variant fields:
|
||||
match to_variant_fieldish(value)? {
|
||||
VariantFields::Named(s) => Ok(Value::Struct(s)),
|
||||
VariantFields::Unnamed(a) => Ok(Value::Array(a)),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_variant<'scale, 'resolver>(
|
||||
self,
|
||||
value: &mut Variant<'scale, 'resolver, Self::TypeResolver>,
|
||||
_type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
let name = value.name().to_owned();
|
||||
let fields = to_variant_fieldish(value.fields())?;
|
||||
Ok(Value::Variant(name, fields))
|
||||
}
|
||||
}
|
||||
|
||||
fn to_variant_fieldish<'scale, 'resolver, R: TypeResolver>(
|
||||
value: &mut Composite<'scale, 'resolver, R>,
|
||||
) -> Result<VariantFields, ValueError> {
|
||||
// If fields are unnamed, treat as array:
|
||||
if value.fields().iter().all(|f| f.name.is_none()) {
|
||||
return Ok(VariantFields::Unnamed(to_array(value.remaining(), value)?));
|
||||
}
|
||||
|
||||
// Otherwise object:
|
||||
let mut out = HashMap::new();
|
||||
for field in value {
|
||||
let field = field?;
|
||||
let name = field.name().unwrap().to_string();
|
||||
let value = field.decode_with_visitor(GetValue::new())?;
|
||||
out.insert(name, value);
|
||||
}
|
||||
Ok(VariantFields::Named(out))
|
||||
}
|
||||
|
||||
fn to_array<'scale, 'resolver, R: TypeResolver>(
|
||||
len: usize,
|
||||
mut values: impl scale_decode::visitor::DecodeItemIterator<'scale, 'resolver, R>,
|
||||
) -> Result<Vec<Value>, ValueError> {
|
||||
let mut out = Vec::with_capacity(len);
|
||||
while let Some(value) = values.decode_item(GetValue::new()) {
|
||||
out.push(value?);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#![allow(missing_docs)]
|
||||
use subxt_historic::{Error, OnlineClient, PolkadotConfig, ext::StreamExt};
|
||||
use subxt_historic::{OnlineClient, PolkadotConfig, ext::StreamExt};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Error> {
|
||||
async fn main() -> Result<(), Box<dyn core::error::Error + Send + Sync + 'static>> {
|
||||
// Configuration for the Polkadot relay chain.
|
||||
let config = PolkadotConfig::new();
|
||||
|
||||
@@ -19,25 +19,28 @@ 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>()?;
|
||||
|
||||
// We can visit the value, which is a more advanced use case and allows us to extract more
|
||||
// data from the type, here the name of it, if it exists:
|
||||
let tn = entry
|
||||
.visit(type_name::GetTypeName::new())?
|
||||
.unwrap_or("<unknown>");
|
||||
|
||||
// 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,10 +56,10 @@ 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: {}",
|
||||
" Single balance info from {account_id_hex} => free: {} reserved: {} misc_frozen: {} fee_frozen: {} (type name: {tn})",
|
||||
balance_info.data.free,
|
||||
balance_info.data.reserved,
|
||||
balance_info.data.misc_frozen,
|
||||
@@ -72,24 +75,101 @@ 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(())
|
||||
}
|
||||
|
||||
/// This module defines an example visitor which retrieves the name of a type.
|
||||
/// This is a more advanced use case and can typically be avoided.
|
||||
mod type_name {
|
||||
use scale_decode::{
|
||||
Visitor,
|
||||
visitor::types::{Composite, Sequence, Variant},
|
||||
visitor::{TypeIdFor, Unexpected},
|
||||
};
|
||||
use scale_type_resolver::TypeResolver;
|
||||
|
||||
/// This is a visitor which obtains type names.
|
||||
pub struct GetTypeName<R> {
|
||||
marker: core::marker::PhantomData<R>,
|
||||
}
|
||||
|
||||
impl<R> GetTypeName<R> {
|
||||
/// Construct our TypeName visitor.
|
||||
pub fn new() -> Self {
|
||||
GetTypeName {
|
||||
marker: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: TypeResolver> Visitor for GetTypeName<R> {
|
||||
type Value<'scale, 'resolver> = Option<&'resolver str>;
|
||||
type Error = scale_decode::Error;
|
||||
type TypeResolver = R;
|
||||
|
||||
// Look at the path of types that have paths and return the ident from that.
|
||||
fn visit_composite<'scale, 'resolver>(
|
||||
self,
|
||||
value: &mut Composite<'scale, 'resolver, Self::TypeResolver>,
|
||||
_type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
Ok(value.path().last())
|
||||
}
|
||||
fn visit_variant<'scale, 'resolver>(
|
||||
self,
|
||||
value: &mut Variant<'scale, 'resolver, Self::TypeResolver>,
|
||||
_type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
Ok(value.path().last())
|
||||
}
|
||||
fn visit_sequence<'scale, 'resolver>(
|
||||
self,
|
||||
value: &mut Sequence<'scale, 'resolver, Self::TypeResolver>,
|
||||
_type_id: TypeIdFor<Self>,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
Ok(value.path().last())
|
||||
}
|
||||
|
||||
// Else, we return nothing as we can't find a name for the type.
|
||||
fn visit_unexpected<'scale, 'resolver>(
|
||||
self,
|
||||
_unexpected: Unexpected,
|
||||
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+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.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::extrinsic_info::{AnyExtrinsicInfo, with_info};
|
||||
use crate::error::ExtrinsicCallError;
|
||||
use crate::utils::Either;
|
||||
use crate::utils::{AnyResolver, AnyTypeId};
|
||||
use scale_info_legacy::{LookupName, TypeRegistrySet};
|
||||
|
||||
/// This represents the call data in the extrinsic.
|
||||
@@ -53,6 +54,7 @@ impl<'extrinsics, 'atblock> ExtrinsicCall<'extrinsics, 'atblock> {
|
||||
pub struct ExtrinsicCallFields<'extrinsics, 'atblock> {
|
||||
all_bytes: &'extrinsics [u8],
|
||||
info: &'extrinsics AnyExtrinsicInfo<'atblock>,
|
||||
resolver: AnyResolver<'atblock>,
|
||||
}
|
||||
|
||||
impl<'extrinsics, 'atblock> ExtrinsicCallFields<'extrinsics, 'atblock> {
|
||||
@@ -60,7 +62,16 @@ impl<'extrinsics, 'atblock> ExtrinsicCallFields<'extrinsics, 'atblock> {
|
||||
all_bytes: &'extrinsics [u8],
|
||||
info: &'extrinsics AnyExtrinsicInfo<'atblock>,
|
||||
) -> Self {
|
||||
Self { all_bytes, info }
|
||||
let resolver = match info {
|
||||
AnyExtrinsicInfo::Legacy(info) => AnyResolver::B(info.resolver),
|
||||
AnyExtrinsicInfo::Current(info) => AnyResolver::A(info.resolver),
|
||||
};
|
||||
|
||||
Self {
|
||||
all_bytes,
|
||||
info,
|
||||
resolver,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the bytes representing the fields stored in this extrinsic.
|
||||
@@ -74,11 +85,12 @@ impl<'extrinsics, 'atblock> ExtrinsicCallFields<'extrinsics, 'atblock> {
|
||||
}
|
||||
|
||||
/// Iterate over each of the fields of the extrinsic call data.
|
||||
pub fn iter(&self) -> impl Iterator<Item = ExtrinsicCallField<'extrinsics, 'atblock>> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = ExtrinsicCallField<'_, 'extrinsics, 'atblock>> {
|
||||
match &self.info {
|
||||
AnyExtrinsicInfo::Legacy(info) => {
|
||||
Either::A(info.info.call_data().map(|named_arg| ExtrinsicCallField {
|
||||
field_bytes: &self.all_bytes[named_arg.range()],
|
||||
resolver: &self.resolver,
|
||||
info: AnyExtrinsicCallFieldInfo::Legacy(ExtrinsicCallFieldInfo {
|
||||
info: named_arg,
|
||||
resolver: info.resolver,
|
||||
@@ -88,6 +100,7 @@ impl<'extrinsics, 'atblock> ExtrinsicCallFields<'extrinsics, 'atblock> {
|
||||
AnyExtrinsicInfo::Current(info) => {
|
||||
Either::B(info.info.call_data().map(|named_arg| ExtrinsicCallField {
|
||||
field_bytes: &self.all_bytes[named_arg.range()],
|
||||
resolver: &self.resolver,
|
||||
info: AnyExtrinsicCallFieldInfo::Current(ExtrinsicCallFieldInfo {
|
||||
info: named_arg,
|
||||
resolver: info.resolver,
|
||||
@@ -98,7 +111,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| {
|
||||
@@ -119,9 +132,10 @@ impl<'extrinsics, 'atblock> ExtrinsicCallFields<'extrinsics, 'atblock> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExtrinsicCallField<'extrinsics, 'atblock> {
|
||||
pub struct ExtrinsicCallField<'fields, 'extrinsics, 'atblock> {
|
||||
field_bytes: &'extrinsics [u8],
|
||||
info: AnyExtrinsicCallFieldInfo<'extrinsics, 'atblock>,
|
||||
resolver: &'fields AnyResolver<'atblock>,
|
||||
}
|
||||
|
||||
enum AnyExtrinsicCallFieldInfo<'extrinsics, 'atblock> {
|
||||
@@ -144,7 +158,7 @@ macro_rules! with_call_field_info {
|
||||
};
|
||||
}
|
||||
|
||||
impl<'extrinsics, 'atblock> ExtrinsicCallField<'extrinsics, 'atblock> {
|
||||
impl<'fields, 'extrinsics, 'atblock> ExtrinsicCallField<'fields, 'extrinsics, 'atblock> {
|
||||
/// Get the raw bytes for this field.
|
||||
pub fn bytes(&self) -> &'extrinsics [u8] {
|
||||
self.field_bytes
|
||||
@@ -155,8 +169,24 @@ impl<'extrinsics, 'atblock> ExtrinsicCallField<'extrinsics, 'atblock> {
|
||||
with_call_field_info!(&self.info => info.info.name())
|
||||
}
|
||||
|
||||
/// Visit the given field with a [`scale_decode::visitor::Visitor`]. This is like a lower level
|
||||
/// version of [`ExtrinsicCallField::decode_as`], as the visitor is able to preserve lifetimes
|
||||
/// and has access to more type information than is available via [`ExtrinsicCallField::decode_as`].
|
||||
pub fn visit<V: scale_decode::visitor::Visitor<TypeResolver = AnyResolver<'atblock>>>(
|
||||
&self,
|
||||
visitor: V,
|
||||
) -> Result<V::Value<'extrinsics, 'fields>, V::Error> {
|
||||
let type_id = match &self.info {
|
||||
AnyExtrinsicCallFieldInfo::Current(info) => AnyTypeId::A(*info.info.ty()),
|
||||
AnyExtrinsicCallFieldInfo::Legacy(info) => AnyTypeId::B(info.info.ty().clone()),
|
||||
};
|
||||
let cursor = &mut self.bytes();
|
||||
|
||||
scale_decode::visitor::decode_with_visitor(cursor, type_id, self.resolver, visitor)
|
||||
}
|
||||
|
||||
/// 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,10 +106,7 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO add extrinsic.call() with .bytes, and .decode function to make it easy to decode call fields into Value or whatever.
|
||||
// Then add this to the example. Make sure we can do everything that dot-block-decoder does easily.
|
||||
|
||||
@@ -20,3 +20,8 @@ pub use error::Error;
|
||||
pub mod ext {
|
||||
pub use futures::stream::{Stream, StreamExt};
|
||||
}
|
||||
|
||||
/// Helper types that could be useful.
|
||||
pub mod helpers {
|
||||
pub use crate::utils::{AnyResolver, AnyResolverError, AnyTypeId};
|
||||
}
|
||||
|
||||
+113
-225
@@ -1,3 +1,4 @@
|
||||
mod list_storage_entries_any;
|
||||
mod storage_entry;
|
||||
mod storage_info;
|
||||
mod storage_key;
|
||||
@@ -5,16 +6,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 +35,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,34 +70,50 @@ 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,
|
||||
marker: std::marker::PhantomData,
|
||||
|
||||
let mut pallet_name = Cow::Borrowed("");
|
||||
list_storage_entries_any::list_storage_entries_any(metadata).filter_map(move |entry| {
|
||||
match entry {
|
||||
frame_decode::storage::StorageEntry::In(name) => {
|
||||
// Set the pallet name for upcoming entries:
|
||||
pallet_name = name;
|
||||
None
|
||||
}
|
||||
frame_decode::storage::StorageEntry::Name(entry_name) => {
|
||||
// Output each entry with the last seen pallet name:
|
||||
Some(StorageEntriesItem {
|
||||
pallet_name: pallet_name.clone(),
|
||||
entry_name,
|
||||
client: self.client,
|
||||
marker: std::marker::PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Working with a specific storage entry.
|
||||
pub struct StorageEntriesItem<'atblock, Client, T> {
|
||||
entry: frame_decode::helpers::StorageEntry<'atblock>,
|
||||
pallet_name: Cow<'atblock, str>,
|
||||
entry_name: Cow<'atblock, str>,
|
||||
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.pallet_name
|
||||
}
|
||||
|
||||
/// The storage entry name.
|
||||
pub fn storage_name(&self) -> &str {
|
||||
self.entry.entry()
|
||||
pub fn entry_name(&self) -> &str {
|
||||
&self.entry_name
|
||||
}
|
||||
|
||||
/// Extract the relevant storage information so that we can work with this entry.
|
||||
@@ -114,88 +122,20 @@ where
|
||||
client: self.client,
|
||||
marker: std::marker::PhantomData,
|
||||
}
|
||||
.entry(
|
||||
self.entry.pallet().to_owned(),
|
||||
self.entry.entry().to_owned(),
|
||||
)
|
||||
.entry(&*self.pallet_name, &*self.entry_name)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 +146,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 +219,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 +247,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 +274,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)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
use frame_decode::storage::StorageEntryInfo;
|
||||
use frame_metadata::RuntimeMetadata;
|
||||
|
||||
pub use frame_decode::storage::StorageEntry;
|
||||
|
||||
/// Returns an iterator listing the available storage entries in some metadata.
|
||||
///
|
||||
/// This basically calls [`StorageEntryInfo::storage_entries()`] for each metadata version,
|
||||
/// returning an empty iterator where applicable (ie when passing legacy metadata and the
|
||||
/// `legacy` features flag is not enabled).
|
||||
pub fn list_storage_entries_any(
|
||||
metadata: &RuntimeMetadata,
|
||||
) -> impl Iterator<Item = StorageEntry<'_>> {
|
||||
match metadata {
|
||||
RuntimeMetadata::V0(_deprecated_metadata)
|
||||
| RuntimeMetadata::V1(_deprecated_metadata)
|
||||
| RuntimeMetadata::V2(_deprecated_metadata)
|
||||
| RuntimeMetadata::V3(_deprecated_metadata)
|
||||
| RuntimeMetadata::V4(_deprecated_metadata)
|
||||
| RuntimeMetadata::V5(_deprecated_metadata)
|
||||
| RuntimeMetadata::V6(_deprecated_metadata)
|
||||
| RuntimeMetadata::V7(_deprecated_metadata) => {
|
||||
Box::new(core::iter::empty()) as Box<dyn Iterator<Item = StorageEntry<'_>>>
|
||||
}
|
||||
RuntimeMetadata::V8(m) => Box::new(m.storage_entries()),
|
||||
RuntimeMetadata::V9(m) => Box::new(m.storage_entries()),
|
||||
RuntimeMetadata::V10(m) => Box::new(m.storage_entries()),
|
||||
RuntimeMetadata::V11(m) => Box::new(m.storage_entries()),
|
||||
RuntimeMetadata::V12(m) => Box::new(m.storage_entries()),
|
||||
RuntimeMetadata::V13(m) => Box::new(m.storage_entries()),
|
||||
RuntimeMetadata::V14(m) => Box::new(m.storage_entries()),
|
||||
RuntimeMetadata::V15(m) => Box::new(m.storage_entries()),
|
||||
RuntimeMetadata::V16(m) => Box::new(m.storage_entries()),
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,19 +1,31 @@
|
||||
use super::storage_info::AnyStorageInfo;
|
||||
use super::storage_info::with_info;
|
||||
use crate::error::StorageValueError;
|
||||
use crate::utils::{AnyResolver, AnyTypeId};
|
||||
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]>,
|
||||
resolver: AnyResolver<'atblock>,
|
||||
}
|
||||
|
||||
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 {
|
||||
Self { info, bytes }
|
||||
pub(crate) fn new(info: Arc<AnyStorageInfo<'atblock>>, bytes: Cow<'atblock, [u8]>) -> Self {
|
||||
let resolver = match &*info {
|
||||
AnyStorageInfo::Current(info) => AnyResolver::A(info.resolver),
|
||||
AnyStorageInfo::Legacy(info) => AnyResolver::B(info.resolver),
|
||||
};
|
||||
|
||||
Self {
|
||||
info,
|
||||
bytes,
|
||||
resolver,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the raw bytes for this storage value.
|
||||
@@ -26,9 +38,25 @@ impl<'entry, 'atblock> StorageValue<'entry, 'atblock> {
|
||||
self.bytes.to_vec()
|
||||
}
|
||||
|
||||
/// Visit the given field with a [`scale_decode::visitor::Visitor`]. This is like a lower level
|
||||
/// version of [`StorageValue::decode_as`], as the visitor is able to preserve lifetimes
|
||||
/// and has access to more type information than is available via [`StorageValue::decode_as`].
|
||||
pub fn visit<V: scale_decode::visitor::Visitor<TypeResolver = AnyResolver<'atblock>>>(
|
||||
&self,
|
||||
visitor: V,
|
||||
) -> Result<V::Value<'_, '_>, V::Error> {
|
||||
let type_id = match &*self.info {
|
||||
AnyStorageInfo::Current(info) => AnyTypeId::A(info.info.value_id),
|
||||
AnyStorageInfo::Legacy(info) => AnyTypeId::B(info.info.value_id.clone()),
|
||||
};
|
||||
let cursor = &mut self.bytes();
|
||||
|
||||
scale_decode::visitor::decode_with_visitor(cursor, type_id, &self.resolver, visitor)
|
||||
}
|
||||
|
||||
/// 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(
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
mod any_resolver;
|
||||
mod either;
|
||||
mod range_map;
|
||||
|
||||
pub use any_resolver::{AnyResolver, AnyResolverError, AnyTypeId};
|
||||
pub use either::Either;
|
||||
pub use range_map::RangeMap;
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
use super::Either;
|
||||
use scale_info_legacy::LookupName;
|
||||
use scale_type_resolver::ResolvedTypeVisitor;
|
||||
|
||||
/// A type resolver which could either be for modern or historic resolving.
|
||||
pub type AnyResolver<'resolver> = Either<
|
||||
&'resolver scale_info::PortableRegistry,
|
||||
&'resolver scale_info_legacy::TypeRegistrySet<'resolver>,
|
||||
>;
|
||||
|
||||
/// A type ID which is either a modern or historic ID.
|
||||
pub type AnyTypeId = Either<u32, scale_info_legacy::LookupName>;
|
||||
|
||||
impl Default for AnyTypeId {
|
||||
fn default() -> Self {
|
||||
// Not a sensible default, but we don't need / can't provide a sensible one.
|
||||
AnyTypeId::A(u32::MAX)
|
||||
}
|
||||
}
|
||||
impl From<u32> for AnyTypeId {
|
||||
fn from(value: u32) -> Self {
|
||||
AnyTypeId::A(value)
|
||||
}
|
||||
}
|
||||
impl From<LookupName> for AnyTypeId {
|
||||
fn from(value: LookupName) -> Self {
|
||||
AnyTypeId::B(value)
|
||||
}
|
||||
}
|
||||
impl TryFrom<AnyTypeId> for u32 {
|
||||
type Error = ();
|
||||
fn try_from(value: AnyTypeId) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
AnyTypeId::A(v) => Ok(v),
|
||||
AnyTypeId::B(_) => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TryFrom<AnyTypeId> for LookupName {
|
||||
type Error = ();
|
||||
fn try_from(value: AnyTypeId) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
AnyTypeId::A(_) => Err(()),
|
||||
AnyTypeId::B(v) => Ok(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A resolve error that comes from using [`AnyResolver`] to resolve some [`AnyTypeId`] into a type.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AnyResolverError {
|
||||
#[error("got a {got} type ID but expected a {expected} type ID")]
|
||||
TypeIdMismatch {
|
||||
got: &'static str,
|
||||
expected: &'static str,
|
||||
},
|
||||
#[error("{0}")]
|
||||
ScaleInfo(scale_type_resolver::portable_registry::Error),
|
||||
#[error("{0}")]
|
||||
ScaleInfoLegacy(scale_info_legacy::type_registry::TypeRegistryResolveError),
|
||||
}
|
||||
|
||||
impl<'resolver> scale_type_resolver::TypeResolver for AnyResolver<'resolver> {
|
||||
type TypeId = AnyTypeId;
|
||||
type Error = AnyResolverError;
|
||||
|
||||
fn resolve_type<'this, V: ResolvedTypeVisitor<'this, TypeId = Self::TypeId>>(
|
||||
&'this self,
|
||||
type_id: Self::TypeId,
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error> {
|
||||
match (self, type_id) {
|
||||
(Either::A(resolver), Either::A(id)) => resolver
|
||||
.resolve_type(id, ModernVisitor(visitor))
|
||||
.map_err(AnyResolverError::ScaleInfo),
|
||||
(Either::B(resolver), Either::B(id)) => resolver
|
||||
.resolve_type(id, LegacyVisitor(visitor))
|
||||
.map_err(AnyResolverError::ScaleInfoLegacy),
|
||||
(Either::A(_), Either::B(_)) => Err(AnyResolverError::TypeIdMismatch {
|
||||
got: "LookupName",
|
||||
expected: "u32",
|
||||
}),
|
||||
(Either::B(_), Either::A(_)) => Err(AnyResolverError::TypeIdMismatch {
|
||||
got: "u32",
|
||||
expected: "LookupName",
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We need to have a visitor which understands only modern or legacy types, and can wrap the more generic visitor
|
||||
// that must be provided to AnyResolver::resolve_type. This then allows us to visit historic _or_ modern types
|
||||
// using the single visitor provided by the user.
|
||||
struct LegacyVisitor<V>(V);
|
||||
struct ModernVisitor<V>(V);
|
||||
|
||||
mod impls {
|
||||
use super::{AnyTypeId, LegacyVisitor, LookupName, ModernVisitor};
|
||||
use scale_type_resolver::*;
|
||||
|
||||
// An ugly implementation which maps from modern or legacy types into our AnyTypeId,
|
||||
// to make LegacyVisitor and ModernVisitor valid visitors when wrapping a generic "any" visitor.
|
||||
macro_rules! impl_visitor_mapper {
|
||||
($struc:ident, $type_id_ty:ident, $variant:ident) => {
|
||||
impl<'this, V> ResolvedTypeVisitor<'this> for $struc<V>
|
||||
where
|
||||
V: ResolvedTypeVisitor<'this, TypeId = AnyTypeId>,
|
||||
{
|
||||
type TypeId = $type_id_ty;
|
||||
type Value = V::Value;
|
||||
|
||||
fn visit_unhandled(self, kind: UnhandledKind) -> Self::Value {
|
||||
self.0.visit_unhandled(kind)
|
||||
}
|
||||
fn visit_array(self, type_id: Self::TypeId, len: usize) -> Self::Value {
|
||||
self.0.visit_array(AnyTypeId::$variant(type_id), len)
|
||||
}
|
||||
fn visit_not_found(self) -> Self::Value {
|
||||
self.0.visit_not_found()
|
||||
}
|
||||
fn visit_composite<Path, Fields>(self, path: Path, fields: Fields) -> Self::Value
|
||||
where
|
||||
Path: PathIter<'this>,
|
||||
Fields: FieldIter<'this, Self::TypeId>,
|
||||
{
|
||||
self.0.visit_composite(
|
||||
path,
|
||||
fields.map(|field| Field {
|
||||
name: field.name,
|
||||
id: AnyTypeId::$variant(field.id),
|
||||
}),
|
||||
)
|
||||
}
|
||||
fn visit_variant<Path, Fields, Var>(self, path: Path, variants: Var) -> Self::Value
|
||||
where
|
||||
Path: PathIter<'this>,
|
||||
Fields: FieldIter<'this, Self::TypeId>,
|
||||
Var: VariantIter<'this, Fields>,
|
||||
{
|
||||
self.0.visit_variant(
|
||||
path,
|
||||
variants.map(|variant| Variant {
|
||||
index: variant.index,
|
||||
name: variant.name,
|
||||
fields: variant.fields.map(|field| Field {
|
||||
name: field.name,
|
||||
id: AnyTypeId::$variant(field.id),
|
||||
}),
|
||||
}),
|
||||
)
|
||||
}
|
||||
fn visit_sequence<Path>(self, path: Path, type_id: Self::TypeId) -> Self::Value
|
||||
where
|
||||
Path: PathIter<'this>,
|
||||
{
|
||||
self.0.visit_sequence(path, AnyTypeId::$variant(type_id))
|
||||
}
|
||||
|
||||
fn visit_tuple<TypeIds>(self, type_ids: TypeIds) -> Self::Value
|
||||
where
|
||||
TypeIds: ExactSizeIterator<Item = Self::TypeId>,
|
||||
{
|
||||
self.0
|
||||
.visit_tuple(type_ids.map(|id| AnyTypeId::$variant(id)))
|
||||
}
|
||||
|
||||
fn visit_primitive(self, primitive: Primitive) -> Self::Value {
|
||||
self.0.visit_primitive(primitive)
|
||||
}
|
||||
|
||||
fn visit_compact(self, type_id: Self::TypeId) -> Self::Value {
|
||||
self.0.visit_compact(AnyTypeId::$variant(type_id))
|
||||
}
|
||||
|
||||
fn visit_bit_sequence(
|
||||
self,
|
||||
store_format: BitsStoreFormat,
|
||||
order_format: BitsOrderFormat,
|
||||
) -> Self::Value {
|
||||
self.0.visit_bit_sequence(store_format, order_format)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_visitor_mapper!(ModernVisitor, u32, A);
|
||||
impl_visitor_mapper!(LegacyVisitor, LookupName, B);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
macro_rules! either {
|
||||
($name:ident( $fst:ident, $($variant:ident),* )) => {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum $name<$fst, $($variant),*> {
|
||||
$fst($fst),
|
||||
$($variant($variant),)*
|
||||
|
||||
+15
-1
@@ -14,11 +14,23 @@ homepage.workspace = true
|
||||
description = "Command line utilities for checking metadata compatibility between nodes."
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
default = ["std", "legacy"]
|
||||
std = ["scale-info/std", "frame-metadata/std"]
|
||||
|
||||
# Enable decoding of legacy metadata, too.
|
||||
# std required by frame-metadata to decode <V14.
|
||||
legacy = [
|
||||
"std",
|
||||
"dep:scale-info-legacy",
|
||||
"dep:scale-type-resolver",
|
||||
"frame-decode/legacy",
|
||||
"frame-metadata/legacy"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
scale-info = { workspace = true, default-features = false }
|
||||
scale-info-legacy = { workspace = true, optional = true }
|
||||
scale-type-resolver = { workspace = true, optional = true }
|
||||
frame-decode = { workspace = true }
|
||||
frame-metadata = { workspace = true, default-features = false, features = ["current", "decode"] }
|
||||
codec = { package = "parity-scale-codec", workspace = true, default-features = false, features = ["derive"] }
|
||||
@@ -31,6 +43,8 @@ bitvec = { workspace = true, features = ["alloc"] }
|
||||
criterion = { workspace = true }
|
||||
scale-info = { workspace = true, features = ["bit-vec"] }
|
||||
subxt-utils-stripmetadata = { workspace = true }
|
||||
frame-decode = { workspace = true, features = ["legacy-types"] }
|
||||
subxt-codegen = { workspace = true }
|
||||
|
||||
[lib]
|
||||
# Without this, libtest cli opts interfere with criterion benches:
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,421 @@
|
||||
mod portable_registry_builder;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::Metadata;
|
||||
use crate::utils::ordered_map::OrderedMap;
|
||||
use crate::utils::variant_index::VariantIndex;
|
||||
use alloc::borrow::ToOwned;
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::format;
|
||||
use alloc::string::ToString;
|
||||
use alloc::vec::Vec;
|
||||
use frame_decode::constants::{ConstantEntryInfo, ConstantTypeInfo};
|
||||
use frame_decode::extrinsics::ExtrinsicTypeInfo;
|
||||
use frame_decode::runtime_apis::RuntimeApiTypeInfo;
|
||||
use frame_decode::storage::{StorageEntryInfo, StorageTypeInfo};
|
||||
use frame_metadata::v15;
|
||||
use portable_registry_builder::PortableRegistryBuilder;
|
||||
use scale_info_legacy::TypeRegistrySet;
|
||||
use scale_info_legacy::type_registry::RuntimeApiName;
|
||||
|
||||
/// Options to configure the legacy translating.
|
||||
pub(crate) struct Opts {
|
||||
pub sanitize_paths: bool,
|
||||
pub ignore_not_found: bool,
|
||||
}
|
||||
|
||||
impl Opts {
|
||||
/// Opts tuned for best compatibility translating.
|
||||
pub(crate) fn compat() -> Self {
|
||||
Opts {
|
||||
sanitize_paths: true,
|
||||
ignore_not_found: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! from_historic {
|
||||
($vis:vis fn $fn_name:ident($metadata:path $(, builtin_index: $builtin_index:ident)? )) => {
|
||||
$vis fn $fn_name(metadata: &$metadata, types: &TypeRegistrySet<'_>, opts: Opts) -> Result<Metadata, Error> {
|
||||
// This will be used to construct our `PortableRegistry` from old-style types.
|
||||
let mut portable_registry_builder = PortableRegistryBuilder::new(&types);
|
||||
portable_registry_builder.ignore_not_found(opts.ignore_not_found);
|
||||
portable_registry_builder.sanitize_paths(opts.sanitize_paths);
|
||||
|
||||
|
||||
// We use this type in a few places to denote that we don't know how to decode it.
|
||||
let unknown_type_id = portable_registry_builder.add_type_str("special::Unknown", None)
|
||||
.map_err(|e| Error::add_type("constructing 'Unknown' type", e))?;
|
||||
|
||||
// Pallet metadata
|
||||
let mut call_index = 0u8;
|
||||
let mut error_index = 0u8;
|
||||
let mut event_index = 0u8;
|
||||
|
||||
let new_pallets = as_decoded(&metadata.modules).iter().map(|pallet| {
|
||||
// In older metadatas, calls and event enums can have different indexes
|
||||
// in a given pallet. Pallets without calls or events don't increment
|
||||
// the respective index for them.
|
||||
//
|
||||
// We assume since errors are non optional, that the pallet index _always_
|
||||
// increments for errors (no `None`s to skip).
|
||||
let (call_index, event_index, error_index) = {
|
||||
let out = (call_index, event_index, error_index);
|
||||
if pallet.calls.is_some() {
|
||||
call_index += 1;
|
||||
}
|
||||
if pallet.event.is_some() {
|
||||
event_index += 1;
|
||||
}
|
||||
error_index += 1;
|
||||
|
||||
out
|
||||
};
|
||||
|
||||
// For v12 and v13 metadata, there is a builtin index for everything in a pallet.
|
||||
// We enable this logic for those metadatas to get the correct index.
|
||||
$(
|
||||
let $builtin_index = true;
|
||||
let (call_index, event_index, error_index) = if $builtin_index {
|
||||
(pallet.index, pallet.index, pallet.index)
|
||||
} else {
|
||||
(call_index, event_index, error_index)
|
||||
};
|
||||
)?
|
||||
|
||||
let pallet_name = as_decoded(&pallet.name).to_string();
|
||||
|
||||
// Storage entries:
|
||||
let storage = pallet.storage.as_ref().map(|s| {
|
||||
let storage = as_decoded(s);
|
||||
let prefix = as_decoded(&storage.prefix);
|
||||
let entries = metadata.storage_in_pallet(&pallet_name).map(|entry_name| {
|
||||
let info = metadata
|
||||
.storage_info(&pallet_name, &entry_name)
|
||||
.map_err(|e| Error::StorageInfoError(e.into_owned()))?;
|
||||
let entry_name = entry_name.into_owned();
|
||||
|
||||
let info = info.map_ids(|old_id| {
|
||||
portable_registry_builder.add_type(old_id)
|
||||
}).map_err(|e| {
|
||||
let ctx = format!("adding type used in storage entry {pallet_name}.{entry_name}");
|
||||
Error::add_type(ctx, e)
|
||||
})?;
|
||||
|
||||
let entry = crate::StorageEntryMetadata {
|
||||
name: entry_name.clone(),
|
||||
info: info.into_owned(),
|
||||
// We don't expose docs via our storage info yet.
|
||||
docs: Vec::new(),
|
||||
};
|
||||
|
||||
Ok((entry_name, entry))
|
||||
}).collect::<Result<OrderedMap<_, _>, _>>()?;
|
||||
Ok(crate::StorageMetadata {
|
||||
prefix: prefix.clone(),
|
||||
entries,
|
||||
})
|
||||
}).transpose()?;
|
||||
|
||||
// Pallet error type is just a builtin type:
|
||||
let error_ty = portable_registry_builder.add_type_str(&format!("builtin::module::error::{pallet_name}"), None)
|
||||
.map_err(|e| {
|
||||
let ctx = format!("converting the error enum for pallet {pallet_name}");
|
||||
Error::add_type(ctx, e)
|
||||
})?;
|
||||
|
||||
// Pallet calls also just a builtin type:
|
||||
let call_ty = pallet.calls.as_ref().map(|_| {
|
||||
portable_registry_builder.add_type_str(&format!("builtin::module::call::{pallet_name}"), None)
|
||||
.map_err(|e| {
|
||||
let ctx = format!("converting the call enum for pallet {pallet_name}");
|
||||
Error::add_type(ctx, e)
|
||||
})
|
||||
}).transpose()?;
|
||||
|
||||
// Pallet events also just a builtin type:
|
||||
let event_ty = pallet.event.as_ref().map(|_| {
|
||||
portable_registry_builder.add_type_str(&format!("builtin::module::event::{pallet_name}"), None)
|
||||
.map_err(|e| {
|
||||
let ctx = format!("converting the event enum for pallet {pallet_name}");
|
||||
Error::add_type(ctx, e)
|
||||
})
|
||||
}).transpose()?;
|
||||
|
||||
let call_variant_index =
|
||||
VariantIndex::build(call_ty, portable_registry_builder.types());
|
||||
let error_variant_index =
|
||||
VariantIndex::build(Some(error_ty), portable_registry_builder.types());
|
||||
let event_variant_index =
|
||||
VariantIndex::build(event_ty, portable_registry_builder.types());
|
||||
|
||||
let constants = metadata.constants_in_pallet(&pallet_name).map(|name| {
|
||||
let name = name.into_owned();
|
||||
let info = metadata.constant_info(&pallet_name, &name)
|
||||
.map_err(|e| Error::ConstantInfoError(e.into_owned()))?;
|
||||
let new_type_id = portable_registry_builder.add_type(info.type_id)
|
||||
.map_err(|e| {
|
||||
let ctx = format!("converting the constant {name} for pallet {pallet_name}");
|
||||
Error::add_type(ctx, e)
|
||||
})?;
|
||||
|
||||
let constant = crate::ConstantMetadata {
|
||||
name: name.clone(),
|
||||
ty: new_type_id,
|
||||
value: info.bytes.to_vec(),
|
||||
// We don't expose docs via our constant info yet.
|
||||
docs: Vec::new(),
|
||||
};
|
||||
|
||||
Ok((name, constant))
|
||||
}).collect::<Result<_,Error>>()?;
|
||||
|
||||
let pallet_metadata = crate::PalletMetadataInner {
|
||||
name: pallet_name.clone(),
|
||||
call_index,
|
||||
event_index,
|
||||
error_index,
|
||||
storage,
|
||||
error_ty: Some(error_ty),
|
||||
call_ty,
|
||||
event_ty,
|
||||
call_variant_index,
|
||||
error_variant_index,
|
||||
event_variant_index,
|
||||
constants,
|
||||
view_functions: Default::default(),
|
||||
associated_types: Default::default(),
|
||||
// Pallets did not have docs prior to V15.
|
||||
docs: Default::default(),
|
||||
};
|
||||
|
||||
Ok((pallet_name, pallet_metadata))
|
||||
}).collect::<Result<OrderedMap<_,_>,Error>>()?;
|
||||
|
||||
// Extrinsic metadata
|
||||
let new_extrinsic = {
|
||||
let signature_info = metadata
|
||||
.extrinsic_signature_info()
|
||||
.map_err(|e| Error::ExtrinsicInfoError(e.into_owned()))?;
|
||||
|
||||
let address_ty_id = portable_registry_builder.add_type(signature_info.address_id)
|
||||
.map_err(|_| Error::CannotFindAddressType)?;
|
||||
|
||||
let signature_ty_id = portable_registry_builder.add_type(signature_info.signature_id)
|
||||
.map_err(|_| Error::CannotFindCallType)?;
|
||||
|
||||
let transaction_extensions = metadata
|
||||
.extrinsic_extension_info(None)
|
||||
.map_err(|e| Error::ExtrinsicInfoError(e.into_owned()))?
|
||||
.extension_ids
|
||||
.into_iter()
|
||||
.map(|ext| {
|
||||
let ext_name = ext.name.into_owned();
|
||||
let ext_type = portable_registry_builder.add_type(ext.id)
|
||||
.map_err(|e| {
|
||||
let ctx = format!("converting the signed extension {ext_name}");
|
||||
Error::add_type(ctx, e)
|
||||
})?;
|
||||
|
||||
Ok(crate::TransactionExtensionMetadataInner {
|
||||
identifier: ext_name,
|
||||
extra_ty: ext_type,
|
||||
// This only started existing in V14+ metadata, but in any case,
|
||||
// we don't need to know how to decode the signed payload for
|
||||
// historic blocks (hopefully), so set to unknown.
|
||||
additional_ty: unknown_type_id.into()
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>,Error>>()?;
|
||||
|
||||
let transaction_extensions_by_version = BTreeMap::from_iter([(
|
||||
0,
|
||||
(0..transaction_extensions.len() as u32).collect()
|
||||
)]);
|
||||
|
||||
crate::ExtrinsicMetadata {
|
||||
address_ty: address_ty_id.into(),
|
||||
signature_ty: signature_ty_id.into(),
|
||||
supported_versions: Vec::from_iter([4]),
|
||||
transaction_extensions,
|
||||
transaction_extensions_by_version,
|
||||
}
|
||||
};
|
||||
|
||||
// Outer enum types
|
||||
let outer_enums = crate::OuterEnumsMetadata {
|
||||
call_enum_ty: portable_registry_builder.add_type_str("builtin::Call", None)
|
||||
.map_err(|e| {
|
||||
let ctx = format!("constructing the 'builtin::Call' type to put in the OuterEnums metadata");
|
||||
Error::add_type(ctx, e)
|
||||
})?,
|
||||
event_enum_ty: portable_registry_builder.add_type_str("builtin::Event", None)
|
||||
.map_err(|e| {
|
||||
let ctx = format!("constructing the 'builtin::Event' type to put in the OuterEnums metadata");
|
||||
Error::add_type(ctx, e)
|
||||
})?,
|
||||
error_enum_ty: portable_registry_builder.add_type_str("builtin::Error", None)
|
||||
.map_err(|e| {
|
||||
let ctx = format!("constructing the 'builtin::Error' type to put in the OuterEnums metadata");
|
||||
Error::add_type(ctx, e)
|
||||
})?,
|
||||
};
|
||||
|
||||
// These are all the same in V13, but be explicit anyway for clarity.
|
||||
let pallets_by_call_index = new_pallets
|
||||
.values()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx,p)| (p.call_index, idx))
|
||||
.collect();
|
||||
let pallets_by_error_index = new_pallets
|
||||
.values()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx,p)| (p.error_index, idx))
|
||||
.collect();
|
||||
let pallets_by_event_index = new_pallets
|
||||
.values()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx,p)| (p.event_index, idx))
|
||||
.collect();
|
||||
|
||||
// This is optional in the sense that Subxt will return an error if it needs to decode this type,
|
||||
// and I think for historic metadata we wouldn't end up down that path anyway. Historic metadata
|
||||
// tends to call it just "DispatchError" but search more specific paths first.
|
||||
let dispatch_error_ty = portable_registry_builder
|
||||
.try_add_type_str("hardcoded::DispatchError", None)
|
||||
.or_else(|| portable_registry_builder.try_add_type_str("sp_runtime::DispatchError", None))
|
||||
.or_else(|| portable_registry_builder.try_add_type_str("DispatchError", None))
|
||||
.transpose()
|
||||
.map_err(|e| Error::add_type("constructing DispatchError", e))?;
|
||||
|
||||
// Runtime API definitions live with type definitions.
|
||||
let apis = type_registry_to_runtime_apis(&types, &mut portable_registry_builder)?;
|
||||
|
||||
Ok(crate::Metadata {
|
||||
types: portable_registry_builder.finish(),
|
||||
pallets: new_pallets,
|
||||
pallets_by_call_index,
|
||||
pallets_by_error_index,
|
||||
pallets_by_event_index,
|
||||
extrinsic: new_extrinsic,
|
||||
outer_enums,
|
||||
dispatch_error_ty,
|
||||
apis,
|
||||
// Nothing custom existed in V13
|
||||
custom: v15::CustomMetadata { map: Default::default() },
|
||||
})
|
||||
}}
|
||||
}
|
||||
|
||||
from_historic!(pub fn from_v13(frame_metadata::v13::RuntimeMetadataV13, builtin_index: yes));
|
||||
from_historic!(pub fn from_v12(frame_metadata::v12::RuntimeMetadataV12, builtin_index: yes));
|
||||
from_historic!(pub fn from_v11(frame_metadata::v11::RuntimeMetadataV11));
|
||||
from_historic!(pub fn from_v10(frame_metadata::v10::RuntimeMetadataV10));
|
||||
from_historic!(pub fn from_v9(frame_metadata::v9::RuntimeMetadataV9));
|
||||
from_historic!(pub fn from_v8(frame_metadata::v8::RuntimeMetadataV8));
|
||||
|
||||
fn as_decoded<A, B>(item: &frame_metadata::decode_different::DecodeDifferent<A, B>) -> &B {
|
||||
match item {
|
||||
frame_metadata::decode_different::DecodeDifferent::Encode(_a) => {
|
||||
panic!("Expecting decoded data")
|
||||
}
|
||||
frame_metadata::decode_different::DecodeDifferent::Decoded(b) => b,
|
||||
}
|
||||
}
|
||||
|
||||
// Obtain Runtime API information from some type registry.
|
||||
pub fn type_registry_to_runtime_apis(
|
||||
types: &TypeRegistrySet<'_>,
|
||||
portable_registry_builder: &mut PortableRegistryBuilder,
|
||||
) -> Result<OrderedMap<String, crate::RuntimeApiMetadataInner>, Error> {
|
||||
let mut apis = OrderedMap::new();
|
||||
let mut trait_name = "";
|
||||
let mut trait_methods = OrderedMap::new();
|
||||
|
||||
for api in types.runtime_apis() {
|
||||
match api {
|
||||
RuntimeApiName::Trait(name) => {
|
||||
if !trait_methods.is_empty() {
|
||||
apis.push_insert(
|
||||
trait_name.into(),
|
||||
crate::RuntimeApiMetadataInner {
|
||||
name: trait_name.into(),
|
||||
methods: trait_methods,
|
||||
docs: Vec::new(),
|
||||
},
|
||||
);
|
||||
}
|
||||
trait_methods = OrderedMap::new();
|
||||
trait_name = name;
|
||||
}
|
||||
RuntimeApiName::Method(name) => {
|
||||
let info = types
|
||||
.runtime_api_info(trait_name, name)
|
||||
.map_err(|e| Error::RuntimeApiInfoError(e.into_owned()))?;
|
||||
|
||||
let info = info.map_ids(|id| {
|
||||
portable_registry_builder.add_type(id).map_err(|e| {
|
||||
let c = format!("converting type for runtime API {trait_name}.{name}");
|
||||
Error::add_type(c, e)
|
||||
})
|
||||
})?;
|
||||
|
||||
trait_methods.push_insert(
|
||||
name.to_owned(),
|
||||
crate::RuntimeApiMethodMetadataInner {
|
||||
name: name.into(),
|
||||
info,
|
||||
docs: Vec::new(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(apis)
|
||||
}
|
||||
|
||||
/// An error encountered converting some legacy metadata to our internal format.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// Cannot add a type.
|
||||
#[error("Cannot add type ({context}): {error}")]
|
||||
AddTypeError {
|
||||
context: String,
|
||||
error: portable_registry_builder::PortableRegistryAddTypeError,
|
||||
},
|
||||
#[error("Cannot find 'hardcoded::ExtrinsicAddress' type in legacy types")]
|
||||
CannotFindAddressType,
|
||||
#[error("Cannot find 'hardcoded::ExtrinsicSignature' type in legacy types")]
|
||||
CannotFindSignatureType,
|
||||
#[error(
|
||||
"Cannot find 'builtin::Call' type in legacy types (this should have been automatically added)"
|
||||
)]
|
||||
CannotFindCallType,
|
||||
#[error("Cannot obtain the storage information we need to convert storage entries")]
|
||||
StorageInfoError(frame_decode::storage::StorageInfoError<'static>),
|
||||
#[error("Cannot obtain the extrinsic information we need to convert transaction extensions")]
|
||||
ExtrinsicInfoError(frame_decode::extrinsics::ExtrinsicInfoError<'static>),
|
||||
#[error("Cannot obtain the Runtime API information we need")]
|
||||
RuntimeApiInfoError(frame_decode::runtime_apis::RuntimeApiInfoError<'static>),
|
||||
#[error("Cannot obtain the Constant information we need")]
|
||||
ConstantInfoError(frame_decode::constants::ConstantInfoError<'static>),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// A shorthand for the [`Error::AddTypeError`] variant.
|
||||
fn add_type(
|
||||
context: impl Into<String>,
|
||||
error: impl Into<portable_registry_builder::PortableRegistryAddTypeError>,
|
||||
) -> Self {
|
||||
Error::AddTypeError {
|
||||
context: context.into(),
|
||||
error: error.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,541 @@
|
||||
use alloc::borrow::ToOwned;
|
||||
use alloc::collections::{BTreeMap, BTreeSet};
|
||||
use alloc::string::ToString;
|
||||
use alloc::vec::Vec;
|
||||
use scale_info::PortableRegistry;
|
||||
use scale_info::{PortableType, form::PortableForm};
|
||||
use scale_info_legacy::type_registry::TypeRegistryResolveError;
|
||||
use scale_info_legacy::{LookupName, TypeRegistrySet};
|
||||
use scale_type_resolver::{
|
||||
BitsOrderFormat, BitsStoreFormat, FieldIter, PathIter, Primitive, ResolvedTypeVisitor,
|
||||
UnhandledKind, VariantIter,
|
||||
};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum PortableRegistryAddTypeError {
|
||||
#[error("Error resolving type: {0}")]
|
||||
ResolveError(#[from] TypeRegistryResolveError),
|
||||
#[error("Cannot find type '{0}'")]
|
||||
TypeNotFound(LookupName),
|
||||
}
|
||||
|
||||
/// the purpose of this is to convert a (subset of) [`scale_info_legacy::TypeRegistrySet`]
|
||||
/// into a [`scale_info::PortableRegistry`]. Type IDs from the former are passed in, and
|
||||
/// type IDs from the latter are handed back. Calling [`PortableRegistryBuilder::finish()`]
|
||||
/// then hands back a [`scale_info::PortableRegistry`] which these Ids can be used with.
|
||||
pub struct PortableRegistryBuilder<'info> {
|
||||
legacy_types: &'info TypeRegistrySet<'info>,
|
||||
scale_info_types: PortableRegistry,
|
||||
old_to_new: BTreeMap<LookupName, u32>,
|
||||
ignore_not_found: bool,
|
||||
sanitize_paths: bool,
|
||||
seen_names_in_default_path: BTreeSet<String>,
|
||||
}
|
||||
|
||||
impl<'info> PortableRegistryBuilder<'info> {
|
||||
/// Instantiate a new [`PortableRegistryBuilder`], providing the set of
|
||||
/// legacy types you wish to use to construct modern types from.
|
||||
pub fn new(legacy_types: &'info TypeRegistrySet<'info>) -> Self {
|
||||
PortableRegistryBuilder {
|
||||
legacy_types,
|
||||
scale_info_types: PortableRegistry {
|
||||
types: Default::default(),
|
||||
},
|
||||
old_to_new: Default::default(),
|
||||
ignore_not_found: false,
|
||||
sanitize_paths: false,
|
||||
seen_names_in_default_path: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// If this is enabled, any type that isn't found will be replaced by a "special::Unknown" type
|
||||
/// instead of a "type not found" error being emitted.
|
||||
///
|
||||
/// Default: false
|
||||
pub fn ignore_not_found(&mut self, ignore: bool) {
|
||||
self.ignore_not_found = ignore;
|
||||
}
|
||||
|
||||
/// Should type paths be sanitized to make them more amenable to things like codegen?
|
||||
///
|
||||
/// Default: false
|
||||
pub fn sanitize_paths(&mut self, sanitize: bool) {
|
||||
self.sanitize_paths = sanitize;
|
||||
}
|
||||
|
||||
/// Try adding a type, given its string name and optionally the pallet it's scoped to.
|
||||
pub fn try_add_type_str(
|
||||
&mut self,
|
||||
id: &str,
|
||||
pallet: Option<&str>,
|
||||
) -> Option<Result<u32, TypeRegistryResolveError>> {
|
||||
let mut id = match LookupName::parse(id) {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
return Some(Err(TypeRegistryResolveError::LookupNameInvalid(
|
||||
id.to_owned(),
|
||||
e,
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(pallet) = pallet {
|
||||
id = id.in_pallet(pallet);
|
||||
}
|
||||
|
||||
self.try_add_type(id)
|
||||
}
|
||||
|
||||
/// Try adding a type, returning `None` if the type doesn't exist.
|
||||
pub fn try_add_type(
|
||||
&mut self,
|
||||
id: LookupName,
|
||||
) -> Option<Result<u32, TypeRegistryResolveError>> {
|
||||
match self.add_type(id) {
|
||||
Ok(id) => Some(Ok(id)),
|
||||
Err(PortableRegistryAddTypeError::TypeNotFound(_)) => None,
|
||||
Err(PortableRegistryAddTypeError::ResolveError(e)) => Some(Err(e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a new legacy type, giving its string ID/name and, if applicable, the pallet that it's seen in,
|
||||
/// returning the corresponding "modern" type ID to use in its place, or an error if something does wrong.
|
||||
pub fn add_type_str(
|
||||
&mut self,
|
||||
id: &str,
|
||||
pallet: Option<&str>,
|
||||
) -> Result<u32, PortableRegistryAddTypeError> {
|
||||
let mut id = LookupName::parse(id)
|
||||
.map_err(|e| TypeRegistryResolveError::LookupNameInvalid(id.to_owned(), e))?;
|
||||
|
||||
if let Some(pallet) = pallet {
|
||||
id = id.in_pallet(pallet);
|
||||
}
|
||||
|
||||
self.add_type(id)
|
||||
}
|
||||
|
||||
/// Add a new legacy type, returning the corresponding "modern" type ID to use in
|
||||
/// its place, or an error if something does wrong.
|
||||
pub fn add_type(&mut self, id: LookupName) -> Result<u32, PortableRegistryAddTypeError> {
|
||||
if let Some(new_id) = self.old_to_new.get(&id) {
|
||||
return Ok(*new_id);
|
||||
}
|
||||
|
||||
// Assign a new ID immediately to prevent any recursion. If we don't do this, then
|
||||
// recursive types (ie types that contain themselves) will lead to a stack overflow.
|
||||
// with this, we assign IDs up front, so the ID is returned immediately on recursing.
|
||||
let new_id = self.scale_info_types.types.len() as u32;
|
||||
|
||||
// Add a placeholder type to "reserve" this ID.
|
||||
self.scale_info_types.types.push(PortableType {
|
||||
id: new_id,
|
||||
ty: scale_info::Type::new(
|
||||
scale_info::Path { segments: vec![] },
|
||||
core::iter::empty(),
|
||||
scale_info::TypeDef::Variant(scale_info::TypeDefVariant { variants: vec![] }),
|
||||
Default::default(),
|
||||
),
|
||||
});
|
||||
|
||||
// Cache the ID so that recursing calls bail early.
|
||||
self.old_to_new.insert(id.clone(), new_id);
|
||||
|
||||
let visitor = PortableRegistryVisitor {
|
||||
builder: &mut *self,
|
||||
current_type: &id,
|
||||
};
|
||||
|
||||
match visitor
|
||||
.builder
|
||||
.legacy_types
|
||||
.resolve_type(id.clone(), visitor)
|
||||
{
|
||||
Ok(Ok(ty)) => {
|
||||
self.scale_info_types.types[new_id as usize].ty = ty;
|
||||
Ok(new_id)
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
self.old_to_new.remove(&id);
|
||||
Err(e)
|
||||
}
|
||||
Err(e) => {
|
||||
self.old_to_new.remove(&id);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the current [`scale_info::PortableRegistry`].
|
||||
pub fn types(&self) -> &PortableRegistry {
|
||||
&self.scale_info_types
|
||||
}
|
||||
|
||||
/// Finish adding types and return the modern type registry.
|
||||
pub fn finish(self) -> PortableRegistry {
|
||||
self.scale_info_types
|
||||
}
|
||||
}
|
||||
|
||||
struct PortableRegistryVisitor<'a, 'info> {
|
||||
builder: &'a mut PortableRegistryBuilder<'info>,
|
||||
current_type: &'a LookupName,
|
||||
}
|
||||
|
||||
impl<'a, 'info> ResolvedTypeVisitor<'info> for PortableRegistryVisitor<'a, 'info> {
|
||||
type TypeId = LookupName;
|
||||
type Value = Result<scale_info::Type<PortableForm>, PortableRegistryAddTypeError>;
|
||||
|
||||
fn visit_unhandled(self, kind: UnhandledKind) -> Self::Value {
|
||||
panic!("A handler exists for every type, but visit_unhandled({kind:?}) was called");
|
||||
}
|
||||
|
||||
fn visit_not_found(self) -> Self::Value {
|
||||
if self.builder.ignore_not_found {
|
||||
// Return the "unknown" type if we're ignoring not found types:
|
||||
Ok(unknown_type())
|
||||
} else {
|
||||
// Otherwise just return an error at this point:
|
||||
Err(PortableRegistryAddTypeError::TypeNotFound(
|
||||
self.current_type.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_primitive(self, primitive: Primitive) -> Self::Value {
|
||||
let p = match primitive {
|
||||
Primitive::Bool => scale_info::TypeDefPrimitive::Bool,
|
||||
Primitive::Char => scale_info::TypeDefPrimitive::Char,
|
||||
Primitive::Str => scale_info::TypeDefPrimitive::Str,
|
||||
Primitive::U8 => scale_info::TypeDefPrimitive::U8,
|
||||
Primitive::U16 => scale_info::TypeDefPrimitive::U16,
|
||||
Primitive::U32 => scale_info::TypeDefPrimitive::U32,
|
||||
Primitive::U64 => scale_info::TypeDefPrimitive::U64,
|
||||
Primitive::U128 => scale_info::TypeDefPrimitive::U128,
|
||||
Primitive::U256 => scale_info::TypeDefPrimitive::U256,
|
||||
Primitive::I8 => scale_info::TypeDefPrimitive::I8,
|
||||
Primitive::I16 => scale_info::TypeDefPrimitive::I16,
|
||||
Primitive::I32 => scale_info::TypeDefPrimitive::I32,
|
||||
Primitive::I64 => scale_info::TypeDefPrimitive::I64,
|
||||
Primitive::I128 => scale_info::TypeDefPrimitive::I128,
|
||||
Primitive::I256 => scale_info::TypeDefPrimitive::I256,
|
||||
};
|
||||
|
||||
Ok(scale_info::Type::new(
|
||||
Default::default(),
|
||||
core::iter::empty(),
|
||||
scale_info::TypeDef::Primitive(p),
|
||||
Default::default(),
|
||||
))
|
||||
}
|
||||
|
||||
fn visit_sequence<Path: PathIter<'info>>(
|
||||
self,
|
||||
path: Path,
|
||||
inner_type_id: Self::TypeId,
|
||||
) -> Self::Value {
|
||||
let inner_id = self.builder.add_type(inner_type_id)?;
|
||||
let path = scale_info::Path {
|
||||
segments: prepare_path(path, self.builder),
|
||||
};
|
||||
|
||||
Ok(scale_info::Type::new(
|
||||
path,
|
||||
core::iter::empty(),
|
||||
scale_info::TypeDef::Sequence(scale_info::TypeDefSequence {
|
||||
type_param: inner_id.into(),
|
||||
}),
|
||||
Default::default(),
|
||||
))
|
||||
}
|
||||
|
||||
fn visit_composite<Path, Fields>(self, path: Path, fields: Fields) -> Self::Value
|
||||
where
|
||||
Path: PathIter<'info>,
|
||||
Fields: FieldIter<'info, Self::TypeId>,
|
||||
{
|
||||
let path = scale_info::Path {
|
||||
segments: prepare_path(path, self.builder),
|
||||
};
|
||||
|
||||
let mut scale_info_fields = Vec::<scale_info::Field<_>>::new();
|
||||
for field in fields {
|
||||
let type_name = field.id.to_string();
|
||||
let id = self.builder.add_type(field.id)?;
|
||||
scale_info_fields.push(scale_info::Field {
|
||||
name: field.name.map(Into::into),
|
||||
ty: id.into(),
|
||||
type_name: Some(type_name),
|
||||
docs: Default::default(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(scale_info::Type::new(
|
||||
path,
|
||||
core::iter::empty(),
|
||||
scale_info::TypeDef::Composite(scale_info::TypeDefComposite {
|
||||
fields: scale_info_fields,
|
||||
}),
|
||||
Default::default(),
|
||||
))
|
||||
}
|
||||
|
||||
fn visit_array(self, inner_type_id: LookupName, len: usize) -> Self::Value {
|
||||
let inner_id = self.builder.add_type(inner_type_id)?;
|
||||
|
||||
Ok(scale_info::Type::new(
|
||||
Default::default(),
|
||||
core::iter::empty(),
|
||||
scale_info::TypeDef::Array(scale_info::TypeDefArray {
|
||||
len: len as u32,
|
||||
type_param: inner_id.into(),
|
||||
}),
|
||||
Default::default(),
|
||||
))
|
||||
}
|
||||
|
||||
fn visit_tuple<TypeIds>(self, type_ids: TypeIds) -> Self::Value
|
||||
where
|
||||
TypeIds: ExactSizeIterator<Item = Self::TypeId>,
|
||||
{
|
||||
let mut scale_info_fields = Vec::new();
|
||||
for old_id in type_ids {
|
||||
let new_id = self.builder.add_type(old_id)?;
|
||||
scale_info_fields.push(new_id.into());
|
||||
}
|
||||
|
||||
Ok(scale_info::Type::new(
|
||||
Default::default(),
|
||||
core::iter::empty(),
|
||||
scale_info::TypeDef::Tuple(scale_info::TypeDefTuple {
|
||||
fields: scale_info_fields,
|
||||
}),
|
||||
Default::default(),
|
||||
))
|
||||
}
|
||||
|
||||
fn visit_variant<Path, Fields, Var>(self, path: Path, variants: Var) -> Self::Value
|
||||
where
|
||||
Path: PathIter<'info>,
|
||||
Fields: FieldIter<'info, Self::TypeId>,
|
||||
Var: VariantIter<'info, Fields>,
|
||||
{
|
||||
let path = scale_info::Path {
|
||||
segments: prepare_path(path, self.builder),
|
||||
};
|
||||
|
||||
let mut scale_info_variants = Vec::new();
|
||||
for variant in variants {
|
||||
let mut scale_info_variant_fields = Vec::<scale_info::Field<_>>::new();
|
||||
for field in variant.fields {
|
||||
let type_name = field.id.to_string();
|
||||
let id = self.builder.add_type(field.id)?;
|
||||
scale_info_variant_fields.push(scale_info::Field {
|
||||
name: field.name.map(Into::into),
|
||||
ty: id.into(),
|
||||
type_name: Some(type_name),
|
||||
docs: Default::default(),
|
||||
});
|
||||
}
|
||||
|
||||
scale_info_variants.push(scale_info::Variant {
|
||||
name: variant.name.to_owned(),
|
||||
index: variant.index,
|
||||
fields: scale_info_variant_fields,
|
||||
docs: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
Ok(scale_info::Type::new(
|
||||
path,
|
||||
core::iter::empty(),
|
||||
scale_info::TypeDef::Variant(scale_info::TypeDefVariant {
|
||||
variants: scale_info_variants,
|
||||
}),
|
||||
Default::default(),
|
||||
))
|
||||
}
|
||||
|
||||
fn visit_compact(self, inner_type_id: Self::TypeId) -> Self::Value {
|
||||
let inner_id = self.builder.add_type(inner_type_id)?;
|
||||
|
||||
// Configure the path and type params to maximise compat.
|
||||
let path = ["parity_scale_codec", "Compact"]
|
||||
.into_iter()
|
||||
.map(ToOwned::to_owned)
|
||||
.collect();
|
||||
let type_params = [scale_info::TypeParameter {
|
||||
name: "T".to_owned(),
|
||||
ty: Some(inner_id.into()),
|
||||
}];
|
||||
|
||||
Ok(scale_info::Type::new(
|
||||
scale_info::Path { segments: path },
|
||||
type_params,
|
||||
scale_info::TypeDef::Compact(scale_info::TypeDefCompact {
|
||||
type_param: inner_id.into(),
|
||||
}),
|
||||
Default::default(),
|
||||
))
|
||||
}
|
||||
|
||||
fn visit_bit_sequence(
|
||||
self,
|
||||
store_format: BitsStoreFormat,
|
||||
order_format: BitsOrderFormat,
|
||||
) -> Self::Value {
|
||||
// These order types are added by default into a `TypeRegistry`, so we
|
||||
// expect them to exist. Parsing should always succeed.
|
||||
let order_ty_str = match order_format {
|
||||
BitsOrderFormat::Lsb0 => "bitvec::order::Lsb0",
|
||||
BitsOrderFormat::Msb0 => "bitvec::order::Msb0",
|
||||
};
|
||||
let order_ty = LookupName::parse(order_ty_str).unwrap();
|
||||
let new_order_ty = self.builder.add_type(order_ty)?;
|
||||
|
||||
// The store types also exist by default. Parsing should always succeed.
|
||||
let store_ty_str = match store_format {
|
||||
BitsStoreFormat::U8 => "u8",
|
||||
BitsStoreFormat::U16 => "u16",
|
||||
BitsStoreFormat::U32 => "u32",
|
||||
BitsStoreFormat::U64 => "u64",
|
||||
};
|
||||
let store_ty = LookupName::parse(store_ty_str).unwrap();
|
||||
let new_store_ty = self.builder.add_type(store_ty)?;
|
||||
|
||||
// Configure the path and type params to look like BitVec's to try
|
||||
// and maximise compatibility.
|
||||
let path = ["bitvec", "vec", "BitVec"]
|
||||
.into_iter()
|
||||
.map(ToOwned::to_owned)
|
||||
.collect();
|
||||
let type_params = [
|
||||
scale_info::TypeParameter {
|
||||
name: "Store".to_owned(),
|
||||
ty: Some(new_store_ty.into()),
|
||||
},
|
||||
scale_info::TypeParameter {
|
||||
name: "Order".to_owned(),
|
||||
ty: Some(new_order_ty.into()),
|
||||
},
|
||||
];
|
||||
|
||||
Ok(scale_info::Type::new(
|
||||
scale_info::Path { segments: path },
|
||||
type_params,
|
||||
scale_info::TypeDef::BitSequence(scale_info::TypeDefBitSequence {
|
||||
bit_order_type: new_order_ty.into(),
|
||||
bit_store_type: new_store_ty.into(),
|
||||
}),
|
||||
Default::default(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_path<'info, Path: PathIter<'info>>(
|
||||
path: Path,
|
||||
builder: &mut PortableRegistryBuilder<'_>,
|
||||
) -> Vec<String> {
|
||||
// If no sanitizint, just return the path as-is.
|
||||
if !builder.sanitize_paths {
|
||||
return path.map(|p| p.to_owned()).collect();
|
||||
}
|
||||
|
||||
/// Names of prelude types. For codegen to work, any type that _isn't_ one of these must
|
||||
/// have a path that is sensible and can be converted to module names.
|
||||
static PRELUDE_TYPE_NAMES: [&str; 24] = [
|
||||
"Vec",
|
||||
"Option",
|
||||
"Result",
|
||||
"Cow",
|
||||
"BTreeMap",
|
||||
"BTreeSet",
|
||||
"BinaryHeap",
|
||||
"VecDeque",
|
||||
"LinkedList",
|
||||
"Range",
|
||||
"RangeInclusive",
|
||||
"NonZeroI8",
|
||||
"NonZeroU8",
|
||||
"NonZeroI16",
|
||||
"NonZeroU16",
|
||||
"NonZeroI32",
|
||||
"NonZeroU32",
|
||||
"NonZeroI64",
|
||||
"NonZeroU64",
|
||||
"NonZeroI128",
|
||||
"NonZeroU128",
|
||||
"NonZeroIsize",
|
||||
"NonZeroUsize",
|
||||
"Duration",
|
||||
];
|
||||
|
||||
let path: Vec<&str> = path.collect();
|
||||
|
||||
// No path should be empty; at least the type name should be present.
|
||||
if path.is_empty() {
|
||||
panic!(
|
||||
"Empty path is not expected when converting legacy type; type name expected at least"
|
||||
);
|
||||
}
|
||||
|
||||
// The special::Unknown type can be returned as is; dupe paths allowed.
|
||||
if path.len() == 2 && path[0] == "special" && path[1] == "Unknown" {
|
||||
return vec!["special".to_owned(), "Unknown".to_owned()];
|
||||
}
|
||||
|
||||
// If non-prelude type has no path, give it one.
|
||||
if path.len() == 1 && !PRELUDE_TYPE_NAMES.contains(&path[0]) {
|
||||
return vec![
|
||||
"other".to_owned(),
|
||||
prepare_ident(path[0], &mut builder.seen_names_in_default_path),
|
||||
];
|
||||
}
|
||||
|
||||
// Non-compliant paths are converted to our default path
|
||||
let non_compliant_path = path[0..path.len() - 1].iter().any(|&p| {
|
||||
p.is_empty()
|
||||
|| p.starts_with(|c: char| !c.is_ascii_alphabetic())
|
||||
|| p.contains(|c: char| !c.is_ascii_alphanumeric() || c.is_ascii_uppercase())
|
||||
});
|
||||
if non_compliant_path {
|
||||
let last = *path.last().unwrap();
|
||||
return vec![
|
||||
"other".to_owned(),
|
||||
prepare_ident(last, &mut builder.seen_names_in_default_path),
|
||||
];
|
||||
}
|
||||
|
||||
// If path happens by chance to be ["other", Foo] then ensure Foo isn't duped
|
||||
if path.len() == 2 && path[0] == "other" {
|
||||
return vec![
|
||||
"other".to_owned(),
|
||||
prepare_ident(path[1], &mut builder.seen_names_in_default_path),
|
||||
];
|
||||
}
|
||||
|
||||
path.iter().map(|&p| p.to_owned()).collect()
|
||||
}
|
||||
|
||||
fn prepare_ident(base_ident: &str, seen: &mut BTreeSet<String>) -> String {
|
||||
let mut n = 1;
|
||||
let mut ident = base_ident.to_owned();
|
||||
while !seen.insert(ident.clone()) {
|
||||
ident = format!("{base_ident}{n}");
|
||||
n += 1;
|
||||
}
|
||||
ident
|
||||
}
|
||||
|
||||
fn unknown_type() -> scale_info::Type<PortableForm> {
|
||||
scale_info::Type::new(
|
||||
scale_info::Path {
|
||||
segments: Vec::from_iter(["special".to_owned(), "Unknown".to_owned()]),
|
||||
},
|
||||
core::iter::empty(),
|
||||
scale_info::TypeDef::Variant(scale_info::TypeDefVariant {
|
||||
variants: Vec::new(),
|
||||
}),
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,477 @@
|
||||
use super::*;
|
||||
use alloc::collections::BTreeSet;
|
||||
use codec::Decode;
|
||||
use core::str::FromStr;
|
||||
use frame_decode::constants::ConstantTypeInfo;
|
||||
use frame_decode::runtime_apis::RuntimeApiEntryInfo;
|
||||
use frame_metadata::RuntimeMetadata;
|
||||
use scale_info_legacy::LookupName;
|
||||
use scale_type_resolver::TypeResolver;
|
||||
|
||||
/// Load some legacy kusama metadata from our artifacts.
|
||||
fn legacy_kusama_metadata(version: u8) -> (u64, RuntimeMetadata) {
|
||||
const VERSIONS: [(u8, u64, &str); 5] = [
|
||||
(9, 1021, "metadata_v9_1021.scale"),
|
||||
(10, 1038, "metadata_v10_1038.scale"),
|
||||
(11, 1045, "metadata_v11_1045.scale"),
|
||||
(12, 2025, "metadata_v12_2025.scale"),
|
||||
(13, 9030, "metadata_v13_9030.scale"),
|
||||
];
|
||||
|
||||
let (spec_version, filename) = VERSIONS
|
||||
.iter()
|
||||
.find(|(v, _spec_version, _filename)| *v == version)
|
||||
.map(|(_, spec_version, name)| (*spec_version, *name))
|
||||
.unwrap_or_else(|| panic!("v{version} metadata artifact does not exist"));
|
||||
|
||||
let mut path = std::path::PathBuf::from_str("../artifacts/kusama/").unwrap();
|
||||
path.push(filename);
|
||||
|
||||
let bytes = std::fs::read(path).expect("Could not read file");
|
||||
let metadata = RuntimeMetadata::decode(&mut &*bytes).expect("Could not SCALE decode metadata");
|
||||
|
||||
(spec_version, metadata)
|
||||
}
|
||||
|
||||
/// Load our kusama types.
|
||||
/// TODO: This is WRONG at the moment; change to point to kusama types when they exist:
|
||||
fn kusama_types() -> scale_info_legacy::ChainTypeRegistry {
|
||||
frame_decode::legacy_types::polkadot::relay_chain()
|
||||
}
|
||||
|
||||
/// Sanitizing paths changes things between old and new, so disable this in tests by default
|
||||
/// so that we can compare paths and check that by default things translate identically.
|
||||
/// Tests assume that ignore_not_found is enabled, which converts not found types to
|
||||
/// special::Unknown instead of returning an error.
|
||||
fn test_opts() -> super::Opts {
|
||||
super::Opts {
|
||||
sanitize_paths: false,
|
||||
ignore_not_found: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a pair of original metadata + converted subxt_metadata::Metadata
|
||||
fn metadata_pair(
|
||||
version: u8,
|
||||
opts: super::Opts,
|
||||
) -> (TypeRegistrySet<'static>, RuntimeMetadata, crate::Metadata) {
|
||||
let (spec_version, metadata) = legacy_kusama_metadata(version);
|
||||
let types = kusama_types();
|
||||
|
||||
// Extend the types with builtins.
|
||||
let types_for_spec = {
|
||||
let mut types_for_spec = types.for_spec_version(spec_version).to_owned();
|
||||
let extended_types =
|
||||
frame_decode::helpers::type_registry_from_metadata_any(&metadata).unwrap();
|
||||
types_for_spec.prepend(extended_types);
|
||||
types_for_spec
|
||||
};
|
||||
|
||||
let subxt_metadata = match &metadata {
|
||||
RuntimeMetadata::V9(m) => super::from_v9(m, &types_for_spec, opts),
|
||||
RuntimeMetadata::V10(m) => super::from_v10(m, &types_for_spec, opts),
|
||||
RuntimeMetadata::V11(m) => super::from_v11(m, &types_for_spec, opts),
|
||||
RuntimeMetadata::V12(m) => super::from_v12(m, &types_for_spec, opts),
|
||||
RuntimeMetadata::V13(m) => super::from_v13(m, &types_for_spec, opts),
|
||||
_ => panic!("Metadata version {} not expected", metadata.version()),
|
||||
}
|
||||
.expect("Could not convert to subxt_metadata::Metadata");
|
||||
|
||||
(types_for_spec, metadata, subxt_metadata)
|
||||
}
|
||||
|
||||
/// A representation of the shape of some type that we can compare across metadatas.
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
enum Shape {
|
||||
Array(Box<Shape>, usize),
|
||||
BitSequence(
|
||||
scale_type_resolver::BitsStoreFormat,
|
||||
scale_type_resolver::BitsOrderFormat,
|
||||
),
|
||||
Compact(Box<Shape>),
|
||||
Composite(Vec<String>, Vec<(Option<String>, Shape)>),
|
||||
Primitive(scale_type_resolver::Primitive),
|
||||
Sequence(Vec<String>, Box<Shape>),
|
||||
Tuple(Vec<Shape>),
|
||||
Variant(Vec<String>, Vec<Variant>),
|
||||
// This is very important for performance; if we've already seen a variant at some path,
|
||||
// we'll return just the variant path next time in this, to avoid duplicating lots of variants.
|
||||
// This also eliminates recursion, since variants allow for it.
|
||||
SeenVariant(Vec<String>),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
struct Variant {
|
||||
index: u8,
|
||||
name: String,
|
||||
fields: Vec<(Option<String>, Shape)>,
|
||||
}
|
||||
|
||||
impl Shape {
|
||||
/// convert some modern type definition into a [`Shape`].
|
||||
fn from_modern_type(id: u32, types: &scale_info::PortableRegistry) -> Shape {
|
||||
let mut seen_variants = BTreeSet::new();
|
||||
Shape::from_modern_type_inner(id, &mut seen_variants, types)
|
||||
}
|
||||
|
||||
fn from_modern_type_inner(
|
||||
id: u32,
|
||||
seen_variants: &mut BTreeSet<Vec<String>>,
|
||||
types: &scale_info::PortableRegistry,
|
||||
) -> Shape {
|
||||
let visitor =
|
||||
scale_type_resolver::visitor::new((seen_variants, types), |_, _| panic!("Unhandled"))
|
||||
.visit_array(|(seen_variants, types), type_id, len| {
|
||||
let inner = Shape::from_modern_type_inner(type_id, seen_variants, types);
|
||||
Shape::Array(Box::new(inner), len)
|
||||
})
|
||||
.visit_bit_sequence(|_, store, order| Shape::BitSequence(store, order))
|
||||
.visit_compact(|(seen_variants, types), type_id| {
|
||||
let inner = Shape::from_modern_type_inner(type_id, seen_variants, types);
|
||||
Shape::Compact(Box::new(inner))
|
||||
})
|
||||
.visit_composite(|(seen_variants, types), path, fields| {
|
||||
let path = path.map(|p| p.to_owned()).collect();
|
||||
let inners = fields
|
||||
.map(|field| {
|
||||
let name = field.name.map(|n| n.to_owned());
|
||||
let inner =
|
||||
Shape::from_modern_type_inner(field.id, seen_variants, types);
|
||||
(name, inner)
|
||||
})
|
||||
.collect();
|
||||
Shape::Composite(path, inners)
|
||||
})
|
||||
.visit_primitive(|_types, prim| Shape::Primitive(prim))
|
||||
.visit_sequence(|(seen_variants, types), path, type_id| {
|
||||
let path = path.map(|p| p.to_owned()).collect();
|
||||
let inner = Shape::from_modern_type_inner(type_id, seen_variants, types);
|
||||
Shape::Sequence(path, Box::new(inner))
|
||||
})
|
||||
.visit_tuple(|(seen_variants, types), fields| {
|
||||
let inners = fields
|
||||
.map(|field| Shape::from_modern_type_inner(field, seen_variants, types))
|
||||
.collect();
|
||||
Shape::Tuple(inners)
|
||||
})
|
||||
.visit_variant(|(seen_variants, types), path, variants| {
|
||||
let path: Vec<String> = path.map(|p| p.to_owned()).collect();
|
||||
// very important to avoid recursion and performance costs:
|
||||
if !seen_variants.insert(path.clone()) {
|
||||
return Shape::SeenVariant(path);
|
||||
}
|
||||
let variants = variants
|
||||
.map(|v| Variant {
|
||||
index: v.index,
|
||||
name: v.name.to_owned(),
|
||||
fields: v
|
||||
.fields
|
||||
.map(|field| {
|
||||
let name = field.name.map(|n| n.to_owned());
|
||||
let inner = Shape::from_modern_type_inner(
|
||||
field.id,
|
||||
seen_variants,
|
||||
types,
|
||||
);
|
||||
(name, inner)
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.collect();
|
||||
Shape::Variant(path, variants)
|
||||
})
|
||||
.visit_not_found(|_types| {
|
||||
panic!("PortableRegistry should not have a type which can't be found")
|
||||
});
|
||||
|
||||
types.resolve_type(id, visitor).unwrap()
|
||||
}
|
||||
|
||||
/// convert some historic type definition into a [`Shape`].
|
||||
fn from_legacy_type(name: &LookupName, types: &TypeRegistrySet<'_>) -> Shape {
|
||||
let mut seen_variants = BTreeSet::new();
|
||||
Shape::from_legacy_type_inner(name.clone(), &mut seen_variants, types)
|
||||
}
|
||||
|
||||
fn from_legacy_type_inner(
|
||||
id: LookupName,
|
||||
seen_variants: &mut BTreeSet<Vec<String>>,
|
||||
types: &TypeRegistrySet<'_>,
|
||||
) -> Shape {
|
||||
let visitor =
|
||||
scale_type_resolver::visitor::new((seen_variants, types), |_, _| panic!("Unhandled"))
|
||||
.visit_array(|(seen_variants, types), type_id, len| {
|
||||
let inner = Shape::from_legacy_type_inner(type_id, seen_variants, types);
|
||||
Shape::Array(Box::new(inner), len)
|
||||
})
|
||||
.visit_bit_sequence(|_types, store, order| Shape::BitSequence(store, order))
|
||||
.visit_compact(|(seen_variants, types), type_id| {
|
||||
let inner = Shape::from_legacy_type_inner(type_id, seen_variants, types);
|
||||
Shape::Compact(Box::new(inner))
|
||||
})
|
||||
.visit_composite(|(seen_variants, types), path, fields| {
|
||||
let path = path.map(|p| p.to_owned()).collect();
|
||||
let inners = fields
|
||||
.map(|field| {
|
||||
let name = field.name.map(|n| n.to_owned());
|
||||
let inner =
|
||||
Shape::from_legacy_type_inner(field.id, seen_variants, types);
|
||||
(name, inner)
|
||||
})
|
||||
.collect();
|
||||
Shape::Composite(path, inners)
|
||||
})
|
||||
.visit_primitive(|_types, prim| Shape::Primitive(prim))
|
||||
.visit_sequence(|(seen_variants, types), path, type_id| {
|
||||
let path = path.map(|p| p.to_owned()).collect();
|
||||
let inner = Shape::from_legacy_type_inner(type_id, seen_variants, types);
|
||||
Shape::Sequence(path, Box::new(inner))
|
||||
})
|
||||
.visit_tuple(|(seen_variants, types), fields| {
|
||||
let inners = fields
|
||||
.map(|field| Shape::from_legacy_type_inner(field, seen_variants, types))
|
||||
.collect();
|
||||
Shape::Tuple(inners)
|
||||
})
|
||||
.visit_variant(|(seen_variants, types), path, variants| {
|
||||
let path: Vec<String> = path.map(|p| p.to_owned()).collect();
|
||||
// very important to avoid recursion and performance costs:
|
||||
if !seen_variants.insert(path.clone()) {
|
||||
return Shape::SeenVariant(path);
|
||||
}
|
||||
let variants = variants
|
||||
.map(|v| Variant {
|
||||
index: v.index,
|
||||
name: v.name.to_owned(),
|
||||
fields: v
|
||||
.fields
|
||||
.map(|field| {
|
||||
let name = field.name.map(|n| n.to_owned());
|
||||
let inner = Shape::from_legacy_type_inner(
|
||||
field.id,
|
||||
seen_variants,
|
||||
types,
|
||||
);
|
||||
(name, inner)
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.collect();
|
||||
Shape::Variant(path, variants)
|
||||
})
|
||||
.visit_not_found(|(seen_variants, _)| {
|
||||
// When we convert legacy to modern types, any types we don't find
|
||||
// are replaced with empty variants (since we can't have dangling types
|
||||
// in our new PortableRegistry). Do the same here so they compare equal.
|
||||
Shape::from_legacy_type_inner(
|
||||
LookupName::parse("special::Unknown").unwrap(),
|
||||
seen_variants,
|
||||
types,
|
||||
)
|
||||
});
|
||||
|
||||
types.resolve_type(id, visitor).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
// Go over all of the constants listed via frame-decode and check that our old
|
||||
// and new metadatas both have identical output.
|
||||
macro_rules! constants_eq {
|
||||
($name:ident, $version:literal, $version_path:ident) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let (old_types, old_md, new_md) = metadata_pair($version, test_opts());
|
||||
let RuntimeMetadata::$version_path(old_md) = old_md else {
|
||||
panic!("Wrong version")
|
||||
};
|
||||
|
||||
let old: Vec<_> = old_md
|
||||
.constant_tuples()
|
||||
.map(|(p, n)| old_md.constant_info(&p, &n).unwrap())
|
||||
.map(|c| {
|
||||
(
|
||||
c.bytes.to_owned(),
|
||||
Shape::from_legacy_type(&c.type_id, &old_types),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let new: Vec<_> = new_md
|
||||
.constant_tuples()
|
||||
.map(|(p, n)| new_md.constant_info(&p, &n).unwrap())
|
||||
.map(|c| {
|
||||
(
|
||||
c.bytes.to_owned(),
|
||||
Shape::from_modern_type(c.type_id, new_md.types()),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
assert_eq!(old, new);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
constants_eq!(v9_constants_eq, 9, V9);
|
||||
constants_eq!(v10_constants_eq, 10, V10);
|
||||
constants_eq!(v11_constants_eq, 11, V11);
|
||||
constants_eq!(v12_constants_eq, 12, V12);
|
||||
constants_eq!(v13_constants_eq, 13, V13);
|
||||
|
||||
/// Make sure all Runtime APIs are the same once translated.
|
||||
#[test]
|
||||
fn runtime_apis() {
|
||||
for version in 9..=13 {
|
||||
let (old_types, _old_md, new_md) = metadata_pair(version, test_opts());
|
||||
|
||||
let old: Vec<_> = old_types
|
||||
.runtime_api_tuples()
|
||||
.map(|(p, n)| {
|
||||
old_types
|
||||
.runtime_api_info(&p, &n)
|
||||
.unwrap()
|
||||
.map_ids(|id| Ok::<_, ()>(Shape::from_legacy_type(&id, &old_types)))
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
let new: Vec<_> = new_md
|
||||
.runtime_api_tuples()
|
||||
.map(|(p, n)| {
|
||||
new_md
|
||||
.runtime_api_info(&p, &n)
|
||||
.unwrap()
|
||||
.map_ids(|id| Ok::<_, ()>(Shape::from_modern_type(id, new_md.types())))
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
|
||||
assert_eq!(old, new);
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! storage_eq {
|
||||
($name:ident, $version:literal, $version_path:ident) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let (old_types, old_md, new_md) = metadata_pair($version, test_opts());
|
||||
let RuntimeMetadata::$version_path(old_md) = old_md else {
|
||||
panic!("Wrong version")
|
||||
};
|
||||
|
||||
let old: Vec<_> = old_md
|
||||
.storage_tuples()
|
||||
.map(|(p, n)| {
|
||||
let info = old_md
|
||||
.storage_info(&p, &n)
|
||||
.unwrap()
|
||||
.map_ids(|id| Ok::<_, ()>(Shape::from_legacy_type(&id, &old_types)))
|
||||
.unwrap();
|
||||
(p.into_owned(), n.into_owned(), info)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let new: Vec<_> = new_md
|
||||
.storage_tuples()
|
||||
.map(|(p, n)| {
|
||||
let info = new_md
|
||||
.storage_info(&p, &n)
|
||||
.unwrap()
|
||||
.map_ids(|id| Ok::<_, ()>(Shape::from_modern_type(id, new_md.types())))
|
||||
.unwrap();
|
||||
(p.into_owned(), n.into_owned(), info)
|
||||
})
|
||||
.collect();
|
||||
|
||||
if old.len() != new.len() {
|
||||
panic!("Storage entries for version 9 metadata differ in length");
|
||||
}
|
||||
|
||||
for (old, new) in old.into_iter().zip(new.into_iter()) {
|
||||
assert_eq!((&old.0, &old.1), (&new.0, &new.1), "Storage entry mismatch");
|
||||
assert_eq!(
|
||||
old.2, new.2,
|
||||
"Storage entry {}.{} does not match!",
|
||||
old.0, old.1
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
storage_eq!(v9_storage_eq, 9, V9);
|
||||
storage_eq!(v10_storage_eq, 10, V10);
|
||||
storage_eq!(v11_storage_eq, 11, V11);
|
||||
storage_eq!(v12_storage_eq, 12, V12);
|
||||
storage_eq!(v13_storage_eq, 13, V13);
|
||||
|
||||
#[test]
|
||||
fn builtin_call() {
|
||||
for version in 9..=13 {
|
||||
let (old_types, _old_md, new_md) = metadata_pair(version, test_opts());
|
||||
|
||||
let old = Shape::from_legacy_type(&LookupName::parse("builtin::Call").unwrap(), &old_types);
|
||||
let new = Shape::from_modern_type(new_md.outer_enums.call_enum_ty, new_md.types());
|
||||
assert_eq!(old, new, "Call types do not match in metadata V{version}!");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_error() {
|
||||
for version in 9..=13 {
|
||||
let (old_types, _old_md, new_md) = metadata_pair(version, test_opts());
|
||||
|
||||
let old =
|
||||
Shape::from_legacy_type(&LookupName::parse("builtin::Error").unwrap(), &old_types);
|
||||
let new = Shape::from_modern_type(new_md.outer_enums.error_enum_ty, new_md.types());
|
||||
assert_eq!(old, new, "Error types do not match in metadata V{version}!");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_event() {
|
||||
for version in 9..=13 {
|
||||
let (old_types, _old_md, new_md) = metadata_pair(version, test_opts());
|
||||
|
||||
let old =
|
||||
Shape::from_legacy_type(&LookupName::parse("builtin::Event").unwrap(), &old_types);
|
||||
let new = Shape::from_modern_type(new_md.outer_enums.event_enum_ty, new_md.types());
|
||||
assert_eq!(old, new, "Event types do not match in metadata V{version}!");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codegen_works() {
|
||||
for version in 9..=13 {
|
||||
// We need to do this against `subxt_codegen::Metadata` and so cannot re-use our
|
||||
// test functions for it. This is because the compiler sees some difference between
|
||||
// `subxct_codegen::Metadata` and `crate::Metadata` even though they should be identical.
|
||||
let new_md = {
|
||||
let (spec_version, metadata) = legacy_kusama_metadata(version);
|
||||
let types = kusama_types();
|
||||
|
||||
let types_for_spec = {
|
||||
let mut types_for_spec = types.for_spec_version(spec_version).to_owned();
|
||||
let extended_types =
|
||||
frame_decode::helpers::type_registry_from_metadata_any(&metadata).unwrap();
|
||||
types_for_spec.prepend(extended_types);
|
||||
types_for_spec
|
||||
};
|
||||
|
||||
match &metadata {
|
||||
RuntimeMetadata::V9(m) => subxt_codegen::Metadata::from_v9(m, &types_for_spec),
|
||||
RuntimeMetadata::V10(m) => subxt_codegen::Metadata::from_v10(m, &types_for_spec),
|
||||
RuntimeMetadata::V11(m) => subxt_codegen::Metadata::from_v11(m, &types_for_spec),
|
||||
RuntimeMetadata::V12(m) => subxt_codegen::Metadata::from_v12(m, &types_for_spec),
|
||||
RuntimeMetadata::V13(m) => subxt_codegen::Metadata::from_v13(m, &types_for_spec),
|
||||
_ => panic!("Metadata version {} not expected", metadata.version()),
|
||||
}
|
||||
.expect("Could not convert to subxt_metadata::Metadata")
|
||||
};
|
||||
|
||||
// We only test that generation succeeds without any errors, not necessarily that it's 100% useful:
|
||||
let codegen = subxt_codegen::CodegenBuilder::new();
|
||||
let _ = codegen
|
||||
.generate(new_md)
|
||||
.map_err(|e| e.into_compile_error())
|
||||
.unwrap_or_else(|e| panic!("Codegen failed for metadata V{version}: {e}"));
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,10 @@ mod v14;
|
||||
mod v15;
|
||||
mod v16;
|
||||
|
||||
/// Legacy translation hidden behind the corresponding feature flag.
|
||||
#[cfg(feature = "legacy")]
|
||||
pub mod legacy;
|
||||
|
||||
/// The metadata versions that we support converting into [`crate::Metadata`].
|
||||
/// These are ordest from highest to lowest, so that the metadata we'd want to
|
||||
/// pick first is first in the array.
|
||||
@@ -33,6 +37,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 {
|
||||
|
||||
+45
-77
@@ -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,16 @@ impl TryFrom<v14::RuntimeMetadataV14> for Metadata {
|
||||
pallets.push_insert(
|
||||
name.clone(),
|
||||
PalletMetadataInner {
|
||||
name,
|
||||
index: p.index,
|
||||
name: name.clone(),
|
||||
call_index: p.index,
|
||||
event_index: p.index,
|
||||
error_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(),
|
||||
@@ -85,7 +101,9 @@ impl TryFrom<v14::RuntimeMetadataV14> for Metadata {
|
||||
Ok(Metadata {
|
||||
types: m.types,
|
||||
pallets,
|
||||
pallets_by_index,
|
||||
pallets_by_call_index: pallets_by_index.clone(),
|
||||
pallets_by_error_index: pallets_by_index.clone(),
|
||||
pallets_by_event_index: pallets_by_index,
|
||||
extrinsic: from_extrinsic_metadata(m.extrinsic, missing_extrinsic_type_ids),
|
||||
dispatch_error_ty,
|
||||
outer_enums: OuterEnumsMetadata {
|
||||
@@ -135,59 +153,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,
|
||||
|
||||
+77
-125
@@ -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 =
|
||||
@@ -54,26 +69,54 @@ impl TryFrom<v15::RuntimeMetadataV15> for Metadata {
|
||||
name.clone(),
|
||||
PalletMetadataInner {
|
||||
name,
|
||||
index: p.index,
|
||||
call_index: p.index,
|
||||
event_index: p.index,
|
||||
error_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
|
||||
@@ -85,10 +128,12 @@ impl TryFrom<v15::RuntimeMetadataV15> for Metadata {
|
||||
Ok(Metadata {
|
||||
types: m.types,
|
||||
pallets,
|
||||
pallets_by_index,
|
||||
pallets_by_call_index: pallets_by_index.clone(),
|
||||
pallets_by_error_index: pallets_by_index.clone(),
|
||||
pallets_by_event_index: 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 +175,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,
|
||||
}
|
||||
}
|
||||
|
||||
+102
-153
@@ -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);
|
||||
@@ -62,26 +88,54 @@ impl TryFrom<v16::RuntimeMetadataV16> for Metadata {
|
||||
name.clone(),
|
||||
PalletMetadataInner {
|
||||
name,
|
||||
index: p.index,
|
||||
call_index: p.index,
|
||||
event_index: p.index,
|
||||
error_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 +157,14 @@ impl TryFrom<v16::RuntimeMetadataV16> for Metadata {
|
||||
.map(|ty| ty.id);
|
||||
|
||||
Ok(Metadata {
|
||||
types,
|
||||
types: m.types,
|
||||
pallets,
|
||||
pallets_by_index,
|
||||
pallets_by_call_index: pallets_by_index.clone(),
|
||||
pallets_by_error_index: pallets_by_index.clone(),
|
||||
pallets_by_event_index: 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 +203,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,
|
||||
}
|
||||
}
|
||||
|
||||
+446
-168
@@ -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::{ConstantEntry, ConstantInfo, ConstantInfoError};
|
||||
use frame_decode::custom_values::{CustomValue, CustomValueInfo, CustomValueInfoError};
|
||||
use frame_decode::extrinsics::{
|
||||
ExtrinsicCallInfo, ExtrinsicExtensionInfo, ExtrinsicInfoArg, ExtrinsicInfoError,
|
||||
ExtrinsicSignatureInfo,
|
||||
};
|
||||
use frame_decode::runtime_apis::{
|
||||
RuntimeApiEntry, RuntimeApiInfo, RuntimeApiInfoError, RuntimeApiInput,
|
||||
};
|
||||
use frame_decode::storage::{StorageEntry, StorageInfo, StorageInfoError, StorageKeyInfo};
|
||||
use frame_decode::view_functions::{
|
||||
ViewFunctionEntry, ViewFunctionInfo, ViewFunctionInfoError, ViewFunctionInput,
|
||||
};
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use scale_info::{PortableRegistry, Variant, form::PortableForm};
|
||||
use utils::{
|
||||
@@ -39,12 +48,14 @@ 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;
|
||||
|
||||
#[cfg(feature = "legacy")]
|
||||
pub use from::legacy::Error as LegacyFromError;
|
||||
|
||||
type CustomMetadataInner = frame_metadata::v15::CustomMetadata<PortableForm>;
|
||||
|
||||
/// Node metadata. This can be constructed by providing some compatible [`frame_metadata`]
|
||||
@@ -55,9 +66,19 @@ pub struct Metadata {
|
||||
/// Type registry containing all types used in the metadata.
|
||||
types: PortableRegistry,
|
||||
/// Metadata of all the pallets.
|
||||
pallets: OrderedMap<ArcStr, PalletMetadataInner>,
|
||||
/// Find the location in the pallet Vec by pallet index.
|
||||
pallets_by_index: HashMap<u8, usize>,
|
||||
pallets: OrderedMap<String, PalletMetadataInner>,
|
||||
/// Find the pallet for a given call index.
|
||||
pallets_by_call_index: HashMap<u8, usize>,
|
||||
/// Find the pallet for a given event index.
|
||||
///
|
||||
/// for modern metadatas, this is the same as pallets_by_call_index,
|
||||
/// but for old metadatas this can vary.
|
||||
pallets_by_event_index: HashMap<u8, usize>,
|
||||
/// Find the pallet for a given error index.
|
||||
///
|
||||
/// for modern metadatas, this is the same as pallets_by_call_index,
|
||||
/// but for old metadatas this can vary.
|
||||
pallets_by_error_index: HashMap<u8, usize>,
|
||||
/// Metadata of the extrinsic.
|
||||
extrinsic: ExtrinsicMetadata,
|
||||
/// The types of the outer enums.
|
||||
@@ -65,7 +86,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,12 +96,12 @@ 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,
|
||||
) -> Result<ExtrinsicCallInfo<'_, Self::TypeId>, ExtrinsicInfoError<'_>> {
|
||||
let pallet = self.pallet_by_index(pallet_index).ok_or({
|
||||
let pallet = self.pallet_by_call_index(pallet_index).ok_or({
|
||||
ExtrinsicInfoError::PalletNotFound {
|
||||
index: pallet_index,
|
||||
}
|
||||
@@ -108,7 +129,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 +138,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 +163,292 @@ 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)
|
||||
}
|
||||
}
|
||||
impl frame_decode::storage::StorageEntryInfo for Metadata {
|
||||
fn storage_entries(&self) -> impl Iterator<Item = StorageEntry<'_>> {
|
||||
self.pallets().flat_map(|pallet| {
|
||||
let pallet_name = pallet.name();
|
||||
let pallet_iter = core::iter::once(StorageEntry::In(pallet_name.into()));
|
||||
let entries_iter = pallet.storage().into_iter().flat_map(|storage| {
|
||||
storage
|
||||
.entries()
|
||||
.iter()
|
||||
.map(|entry| StorageEntry::Name(entry.name().into()))
|
||||
});
|
||||
|
||||
pallet_iter.chain(entries_iter)
|
||||
})
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
impl frame_decode::runtime_apis::RuntimeApiEntryInfo for Metadata {
|
||||
fn runtime_api_entries(&self) -> impl Iterator<Item = RuntimeApiEntry<'_>> {
|
||||
self.runtime_api_traits().flat_map(|api_trait| {
|
||||
let trait_name = api_trait.name();
|
||||
let trait_iter = core::iter::once(RuntimeApiEntry::In(trait_name.into()));
|
||||
let method_iter = api_trait
|
||||
.methods()
|
||||
.map(|method| RuntimeApiEntry::Name(method.name().into()));
|
||||
|
||||
trait_iter.chain(method_iter)
|
||||
})
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
impl frame_decode::view_functions::ViewFunctionEntryInfo for Metadata {
|
||||
fn view_function_entries(&self) -> impl Iterator<Item = ViewFunctionEntry<'_>> {
|
||||
self.pallets().flat_map(|pallet| {
|
||||
let pallet_name = pallet.name();
|
||||
let pallet_iter = core::iter::once(ViewFunctionEntry::In(pallet_name.into()));
|
||||
let fn_iter = pallet
|
||||
.view_functions()
|
||||
.map(|function| ViewFunctionEntry::Name(function.name().into()));
|
||||
|
||||
pallet_iter.chain(fn_iter)
|
||||
})
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
impl frame_decode::constants::ConstantEntryInfo for Metadata {
|
||||
fn constant_entries(&self) -> impl Iterator<Item = ConstantEntry<'_>> {
|
||||
self.pallets().flat_map(|pallet| {
|
||||
let pallet_name = pallet.name();
|
||||
let pallet_iter = core::iter::once(ConstantEntry::In(pallet_name.into()));
|
||||
let constant_iter = pallet
|
||||
.constants()
|
||||
.map(|constant| ConstantEntry::Name(constant.name().into()));
|
||||
|
||||
pallet_iter.chain(constant_iter)
|
||||
})
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
impl frame_decode::custom_values::CustomValueEntryInfo for Metadata {
|
||||
fn custom_values(&self) -> impl Iterator<Item = CustomValue<'_>> {
|
||||
self.custom.map.keys().map(|name| CustomValue {
|
||||
name: Cow::Borrowed(name),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
/// This is essentially 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)
|
||||
}
|
||||
|
||||
/// Convert V16 metadata into [`Metadata`].
|
||||
pub fn from_v16(
|
||||
metadata: frame_metadata::v16::RuntimeMetadataV16,
|
||||
) -> Result<Self, TryFromError> {
|
||||
metadata.try_into()
|
||||
}
|
||||
|
||||
/// Convert V15 metadata into [`Metadata`].
|
||||
pub fn from_v15(
|
||||
metadata: frame_metadata::v15::RuntimeMetadataV15,
|
||||
) -> Result<Self, TryFromError> {
|
||||
metadata.try_into()
|
||||
}
|
||||
|
||||
/// Convert V14 metadata into [`Metadata`].
|
||||
pub fn from_v14(
|
||||
metadata: frame_metadata::v14::RuntimeMetadataV14,
|
||||
) -> Result<Self, TryFromError> {
|
||||
metadata.try_into()
|
||||
}
|
||||
|
||||
/// Convert V13 metadata into [`Metadata`], given the necessary extra type information.
|
||||
#[cfg(feature = "legacy")]
|
||||
pub fn from_v13(
|
||||
metadata: &frame_metadata::v13::RuntimeMetadataV13,
|
||||
types: &scale_info_legacy::TypeRegistrySet<'_>,
|
||||
) -> Result<Self, LegacyFromError> {
|
||||
from::legacy::from_v13(metadata, types, from::legacy::Opts::compat())
|
||||
}
|
||||
|
||||
/// Convert V12 metadata into [`Metadata`], given the necessary extra type information.
|
||||
#[cfg(feature = "legacy")]
|
||||
pub fn from_v12(
|
||||
metadata: &frame_metadata::v12::RuntimeMetadataV12,
|
||||
types: &scale_info_legacy::TypeRegistrySet<'_>,
|
||||
) -> Result<Self, LegacyFromError> {
|
||||
from::legacy::from_v12(metadata, types, from::legacy::Opts::compat())
|
||||
}
|
||||
|
||||
/// Convert V13 metadata into [`Metadata`], given the necessary extra type information.
|
||||
#[cfg(feature = "legacy")]
|
||||
pub fn from_v11(
|
||||
metadata: &frame_metadata::v11::RuntimeMetadataV11,
|
||||
types: &scale_info_legacy::TypeRegistrySet<'_>,
|
||||
) -> Result<Self, LegacyFromError> {
|
||||
from::legacy::from_v11(metadata, types, from::legacy::Opts::compat())
|
||||
}
|
||||
|
||||
/// Convert V13 metadata into [`Metadata`], given the necessary extra type information.
|
||||
#[cfg(feature = "legacy")]
|
||||
pub fn from_v10(
|
||||
metadata: &frame_metadata::v10::RuntimeMetadataV10,
|
||||
types: &scale_info_legacy::TypeRegistrySet<'_>,
|
||||
) -> Result<Self, LegacyFromError> {
|
||||
from::legacy::from_v10(metadata, types, from::legacy::Opts::compat())
|
||||
}
|
||||
|
||||
/// Convert V9 metadata into [`Metadata`], given the necessary extra type information.
|
||||
#[cfg(feature = "legacy")]
|
||||
pub fn from_v9(
|
||||
metadata: &frame_metadata::v9::RuntimeMetadataV9,
|
||||
types: &scale_info_legacy::TypeRegistrySet<'_>,
|
||||
) -> Result<Self, LegacyFromError> {
|
||||
from::legacy::from_v9(metadata, types, from::legacy::Opts::compat())
|
||||
}
|
||||
|
||||
/// Convert V8 metadata into [`Metadata`], given the necessary extra type information.
|
||||
#[cfg(feature = "legacy")]
|
||||
pub fn from_v8(
|
||||
metadata: &frame_metadata::v8::RuntimeMetadataV8,
|
||||
types: &scale_info_legacy::TypeRegistrySet<'_>,
|
||||
) -> Result<Self, LegacyFromError> {
|
||||
from::legacy::from_v8(metadata, types, from::legacy::Opts::compat())
|
||||
}
|
||||
|
||||
/// Access the underlying type registry.
|
||||
pub fn types(&self) -> &PortableRegistry {
|
||||
&self.types
|
||||
@@ -177,10 +482,36 @@ impl Metadata {
|
||||
})
|
||||
}
|
||||
|
||||
/// Access a pallet given its encoded variant index.
|
||||
pub fn pallet_by_index(&self, variant_index: u8) -> Option<PalletMetadata<'_>> {
|
||||
/// Access a pallet given some call/extrinsic pallet index byte
|
||||
pub fn pallet_by_call_index(&self, variant_index: u8) -> Option<PalletMetadata<'_>> {
|
||||
let inner = self
|
||||
.pallets_by_index
|
||||
.pallets_by_call_index
|
||||
.get(&variant_index)
|
||||
.and_then(|i| self.pallets.get_by_index(*i))?;
|
||||
|
||||
Some(PalletMetadata {
|
||||
inner,
|
||||
types: self.types(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Access a pallet given some event pallet index byte
|
||||
pub fn pallet_by_event_index(&self, variant_index: u8) -> Option<PalletMetadata<'_>> {
|
||||
let inner = self
|
||||
.pallets_by_event_index
|
||||
.get(&variant_index)
|
||||
.and_then(|i| self.pallets.get_by_index(*i))?;
|
||||
|
||||
Some(PalletMetadata {
|
||||
inner,
|
||||
types: self.types(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Access a pallet given some error pallet index byte
|
||||
pub fn pallet_by_error_index(&self, variant_index: u8) -> Option<PalletMetadata<'_>> {
|
||||
let inner = self
|
||||
.pallets_by_error_index
|
||||
.get(&variant_index)
|
||||
.and_then(|i| self.pallets.get_by_index(*i))?;
|
||||
|
||||
@@ -217,20 +548,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 {
|
||||
@@ -264,9 +581,19 @@ impl<'a> PalletMetadata<'a> {
|
||||
&self.inner.name
|
||||
}
|
||||
|
||||
/// The pallet index.
|
||||
pub fn index(&self) -> u8 {
|
||||
self.inner.index
|
||||
/// The index to use for calls in this pallet.
|
||||
pub fn call_index(&self) -> u8 {
|
||||
self.inner.call_index
|
||||
}
|
||||
|
||||
/// The index to use for events in this pallet.
|
||||
pub fn event_index(&self) -> u8 {
|
||||
self.inner.event_index
|
||||
}
|
||||
|
||||
/// The index to use for errors in this pallet.
|
||||
pub fn error_index(&self) -> u8 {
|
||||
self.inner.error_index
|
||||
}
|
||||
|
||||
/// The pallet docs.
|
||||
@@ -418,9 +745,19 @@ impl<'a> PalletMetadata<'a> {
|
||||
#[derive(Debug, Clone)]
|
||||
struct PalletMetadataInner {
|
||||
/// Pallet name.
|
||||
name: ArcStr,
|
||||
/// Pallet index.
|
||||
index: u8,
|
||||
name: String,
|
||||
/// The index for calls in the pallet.
|
||||
call_index: u8,
|
||||
/// The index for events in the pallet.
|
||||
///
|
||||
/// This is the same as `call_index` for modern metadatas,
|
||||
/// but can be different for older metadatas (pre-V12).
|
||||
event_index: u8,
|
||||
/// The index for errors in the pallet.
|
||||
///
|
||||
/// This is the same as `call_index` for modern metadatas,
|
||||
/// but can be different for older metadatas (pre-V12).
|
||||
error_index: u8,
|
||||
/// Pallet storage metadata.
|
||||
storage: Option<StorageMetadata>,
|
||||
/// Type ID for the pallet Call enum.
|
||||
@@ -436,9 +773,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 +788,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 +812,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 +824,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 +843,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 +1060,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 +1085,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 +1104,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 +1127,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 +1155,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>,
|
||||
}
|
||||
@@ -999,6 +1243,38 @@ impl<'a> CustomValueMetadata<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode SCALE encoded metadata.
|
||||
///
|
||||
/// - The default assumption is that metadata is encoded as [`frame_metadata::RuntimeMetadataPrefixed`]. This is the
|
||||
/// expected format that metadata is encoded into.
|
||||
/// - if this fails, we also try to decode as [`frame_metadata::RuntimeMetadata`].
|
||||
/// - If this all fails, we also try to decode as [`frame_metadata::OpaqueMetadata`].
|
||||
pub fn decode_runtime_metadata(
|
||||
input: &[u8],
|
||||
) -> Result<frame_metadata::RuntimeMetadata, codec::Error> {
|
||||
use codec::Decode;
|
||||
|
||||
let err = match frame_metadata::RuntimeMetadataPrefixed::decode(&mut &*input) {
|
||||
Ok(md) => return Ok(md.1),
|
||||
Err(e) => e,
|
||||
};
|
||||
|
||||
if let Ok(md) = frame_metadata::RuntimeMetadata::decode(&mut &*input) {
|
||||
return Ok(md);
|
||||
}
|
||||
|
||||
// frame_metadata::OpaqueMetadata is a vec of bytes. If we can decode the length, AND
|
||||
// the length definitely corresponds to the number of remaining bytes, then we try to
|
||||
// decode the inner bytes.
|
||||
if let Ok(len) = codec::Compact::<u64>::decode(&mut &*input) {
|
||||
if input.len() == len.0 as usize {
|
||||
return decode_runtime_metadata(input);
|
||||
}
|
||||
}
|
||||
|
||||
Err(err)
|
||||
}
|
||||
|
||||
// Support decoding metadata from the "wire" format directly into this.
|
||||
// Errors may be lost in the case that the metadata content is somehow invalid.
|
||||
impl codec::Decode for Metadata {
|
||||
@@ -1008,9 +1284,11 @@ impl codec::Decode for Metadata {
|
||||
frame_metadata::RuntimeMetadata::V14(md) => md.try_into(),
|
||||
frame_metadata::RuntimeMetadata::V15(md) => md.try_into(),
|
||||
frame_metadata::RuntimeMetadata::V16(md) => md.try_into(),
|
||||
_ => return Err("Cannot try_into() to Metadata: unsupported metadata version".into()),
|
||||
_ => {
|
||||
return Err("Metadata::decode failed: Cannot try_into() to Metadata: unsupported metadata version".into())
|
||||
},
|
||||
};
|
||||
|
||||
metadata.map_err(|_e| "Cannot try_into() to Metadata.".into())
|
||||
metadata.map_err(|_| "Metadata::decode failed: Cannot try_into() to Metadata".into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user