Utilize Metadata V15 (#1041)

* Update frame-metadata to the latest branch

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Add outer enum types

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Extend the extrinsic with address,call,sign,extra types

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Codegen test Event, Error and Call for outer enums

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Revert "Codegen test Event, Error and Call for outer enums"

This reverts commit db542dca0369eedd257a7ec031d5b5549bc46a88.

* Update frame-metadata from the latest release

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Update scale-info

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen/error: Support v15 message

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Convert v14 to v15

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata/retain: Adjust to extrinsic type for V15

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata/validation: Adjust hashing for extrinsic types V15

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* scripts: Fetch V15 and output codegen for full_client only

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* subxt/blocks: Use extrinsic types directly

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* testing: Fetch V15 for build script

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* artifacts: Generate from latest polkadot version

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen: Fetch legacy with old API for v14 only

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* rpc: Fetch metadata versions

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* client: Fetch latest unstable then V15 then V14

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* testing: Adjust testing API to latest interface

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Adjust clippy

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Generate the `RuntimeError` type for V14

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Remove testing files

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* testing/staking: Remove controller account from bond

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata/validation: Use specific variants for hashing RuntimeCall

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* XXX: Custom Substrate binary: must revert with next release

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* XXX: To revert: CI use hardcoded substrate

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen: Use v15 outer enum types

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Retain outer enum types

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen: Use outer enum types instead of generating them

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Update artifacts

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Revert "XXX: Custom Substrate binary: must revert with next release"

This reverts commit e9705298661919f5769720b35030759fb8a7b01d.

Revert "XXX: To revert: CI use hardcoded substrate"

This reverts commit b18a5a0985a56ee4ad99bc9a1c0f9cd733cf4271.

* testing: Include env for dummy wat contracts

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Adjsut clippy

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* ci: Use new link for fetching latest substrate binary

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* tests: Include dummy RuntimeEvent into test metadata

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* ci: Bump light-client timeout tests to 25min

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata/validation: Use specific pallets as provided

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* testing: Rename metadata constant

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Use call_ty instead of signature_ty

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Rename retaining variant function

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Use Option<&[&str]>

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* online_client: Fetch V15 metadata explicitely

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata/validation: Include the hash of the outer enum types

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Fix sign typo

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* artifacts: Update the artifacts

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* subxt: Remove RootError RootEvent and RootExtrinsic traits

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Update polkadot.rs

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata/tests: Ensure outer enum variants are retained

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* scripts: Include multiple pallets for our decoding purposes

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* subxt: Apply clippy

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* artifacts: Update small metadata

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* error: Keep raw bytes for the ModuleError representation

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* error: Modify docs to not include links

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* subxt/tests: Propagate `RuntimeCall` to outer enums

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* subxt: Provide proper byte slice for decoding

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Update artifacts

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cli/tests: Adjust expected pallets message

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Test conversion from v14 to v15

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata: Fix typo

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Update subxt/src/blocks/extrinsic_types.rs

Co-authored-by: James Wilson <james@jsdw.me>

* metadata: Simplify type path for RuntimeError

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* metadata/validation: Use visited ids per outer enum

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* error: Remove RawModuleError

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Fix new clippy error from updated rust version

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Co-authored-by: James Wilson <james@jsdw.me>
This commit is contained in:
Alexandru Vasile
2023-07-17 12:52:02 +03:00
committed by GitHub
parent 4fb051f233
commit 78a106f059
33 changed files with 3283 additions and 9056 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ on:
env:
CARGO_TERM_COLOR: always
# Use latest substrate for nightly runs:
SUBSTRATE_URL: https://releases.parity.io/substrate/x86_64-debian:stretch/latest/substrate/substrate
SUBSTRATE_URL: https://releases.parity.io/substrate/x86_64-debian:bullseye/latest/substrate/substrate
jobs:
tests:
+1 -1
View File
@@ -19,7 +19,7 @@ concurrency:
env:
CARGO_TERM_COLOR: always
# TODO: Currently pointing at latest substrate; is there a suitable binary we can pin to here?
SUBSTRATE_URL: https://releases.parity.io/substrate/x86_64-debian:stretch/latest/substrate/substrate
SUBSTRATE_URL: https://releases.parity.io/substrate/x86_64-debian:bullseye/latest/substrate/substrate
jobs:
build:
Generated
+23 -11
View File
@@ -1373,6 +1373,17 @@ name = "frame-metadata"
version = "15.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "878babb0b136e731cc77ec2fd883ff02745ff21e6fb662729953d44923df009c"
dependencies = [
"cfg-if",
"parity-scale-codec",
"scale-info",
]
[[package]]
name = "frame-metadata"
version = "16.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cf1549fba25a6fcac22785b61698317d958e96cac72a59102ea45b9ae64692"
dependencies = [
"cfg-if",
"parity-scale-codec",
@@ -1953,7 +1964,7 @@ name = "integration-tests"
version = "0.29.0"
dependencies = [
"assert_matches",
"frame-metadata",
"frame-metadata 16.0.0",
"futures",
"hex",
"parity-scale-codec",
@@ -3200,9 +3211,9 @@ dependencies = [
[[package]]
name = "scale-info"
version = "2.8.0"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad560913365790f17cbf12479491169f01b9d46d29cfc7422bf8c64bdc61b731"
checksum = "35c0a159d0c45c12b20c5a844feb1fe4bea86e28f17b92a5f0c42193634d3782"
dependencies = [
"bitvec",
"cfg-if",
@@ -3214,9 +3225,9 @@ dependencies = [
[[package]]
name = "scale-info-derive"
version = "2.8.0"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19df9bd9ace6cc2fe19387c96ce677e823e07d017ceed253e7bb3d1d1bd9c73b"
checksum = "912e55f6d20e0e80d63733872b40e1227c0bce1e1ab81ba67d696339bfd7fd29"
dependencies = [
"proc-macro-crate",
"proc-macro2",
@@ -3233,7 +3244,7 @@ dependencies = [
"base58",
"blake2",
"either",
"frame-metadata",
"frame-metadata 15.1.0",
"parity-scale-codec",
"scale-bits",
"scale-decode",
@@ -4157,7 +4168,7 @@ dependencies = [
"blake2",
"derivative",
"either",
"frame-metadata",
"frame-metadata 16.0.0",
"futures",
"futures-util",
"getrandom 0.2.10",
@@ -4195,7 +4206,7 @@ version = "0.29.0"
dependencies = [
"clap 4.3.11",
"color-eyre",
"frame-metadata",
"frame-metadata 16.0.0",
"hex",
"jsonrpsee",
"parity-scale-codec",
@@ -4215,7 +4226,7 @@ name = "subxt-codegen"
version = "0.29.0"
dependencies = [
"bitvec",
"frame-metadata",
"frame-metadata 16.0.0",
"heck",
"hex",
"jsonrpsee",
@@ -4244,9 +4255,10 @@ dependencies = [
name = "subxt-metadata"
version = "0.29.0"
dependencies = [
"assert_matches",
"bitvec",
"criterion",
"frame-metadata",
"frame-metadata 16.0.0",
"parity-scale-codec",
"scale-info",
"sp-core-hashing",
@@ -4696,7 +4708,7 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
name = "ui-tests"
version = "0.29.0"
dependencies = [
"frame-metadata",
"frame-metadata 16.0.0",
"parity-scale-codec",
"scale-info",
"subxt",
+2 -2
View File
@@ -45,7 +45,7 @@ console_error_panic_hook = "0.1.7"
darling = "0.20.0"
derivative = "2.2.0"
either = "1.8.1"
frame-metadata = { version = "15.1.0", features = ["v14", "v15-unstable", "std"] }
frame-metadata = { version = "16.0.0", default-features = false, features = ["current", "std"] }
futures = { version = "0.3.27", default-features = false, features = ["std"] }
getrandom = { version = "0.2", default-features = false }
hex = "0.4.3"
@@ -58,7 +58,7 @@ proc-macro-error = "1.0.4"
proc-macro2 = "1.0.63"
quote = "1.0.29"
regex = "1.9.1"
scale-info = "2.8.0"
scale-info = "2.9.0"
scale-value = "0.10.0"
scale-bits = "0.3"
scale-decode = "0.7.0"
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -167,7 +167,7 @@ pub mod tests {
async fn test_commands() {
// show pallets:
let output = simulate_run("").await;
assert_eq!(output.unwrap(), "Usage:\n subxt explore <PALLET>\n explore a specific pallet\n\nAvailable <PALLET> values are:\n Balances\n Multisig\n Staking\n System\n");
assert_eq!(output.unwrap(), "Usage:\n subxt explore <PALLET>\n explore a specific pallet\n\nAvailable <PALLET> values are:\n Balances\n Multisig\n ParaInherent\n Staking\n System\n Timestamp\n");
// if incorrect pallet, error:
let output = simulate_run("abc123").await;
assert!(output.is_err());
+13 -142
View File
@@ -289,13 +289,12 @@ impl RuntimeGenerator {
) -> Result<TokenStream2, CodegenError> {
let item_mod_attrs = item_mod.attrs.clone();
let item_mod_ir = ir::ItemMod::try_from(item_mod)?;
let default_derives = derives.default_derives();
let type_gen = TypeGenerator::new(
self.metadata.types(),
"runtime_types",
type_substitutes,
derives.clone(),
derives,
crate_path.clone(),
should_gen_docs,
);
@@ -382,118 +381,6 @@ impl RuntimeGenerator {
})
.collect::<Result<Vec<_>, CodegenError>>()?;
let outer_event_variants = self.metadata.pallets().filter_map(|p| {
let variant_name = format_ident!("{}", p.name());
let mod_name = format_ident!("{}", p.name().to_string().to_snake_case());
let index = proc_macro2::Literal::u8_unsuffixed(p.index());
p.event_ty_id().map(|_| {
quote! {
#[codec(index = #index)]
#variant_name(#mod_name::Event),
}
})
});
let outer_event = quote! {
#default_derives
pub enum Event {
#( #outer_event_variants )*
}
};
let outer_extrinsic_variants = self.metadata.pallets().filter_map(|p| {
let variant_name = format_ident!("{}", p.name());
let mod_name = format_ident!("{}", p.name().to_string().to_snake_case());
let index = proc_macro2::Literal::u8_unsuffixed(p.index());
p.call_ty_id().map(|_| {
quote! {
#[codec(index = #index)]
#variant_name(#mod_name::Call),
}
})
});
let outer_extrinsic = quote! {
#default_derives
pub enum Call {
#( #outer_extrinsic_variants )*
}
};
let root_event_if_arms = self.metadata.pallets().filter_map(|p| {
let variant_name_str = &p.name();
let variant_name = format_ident!("{}", variant_name_str);
let mod_name = format_ident!("{}", variant_name_str.to_string().to_snake_case());
p.event_ty_id().map(|_| {
// An 'if' arm for the RootEvent impl to match this variant name:
quote! {
if pallet_name == #variant_name_str {
return Ok(Event::#variant_name(#mod_name::Event::decode_with_metadata(
&mut &*pallet_bytes,
pallet_ty,
metadata
)?));
}
}
})
});
let root_extrinsic_if_arms = self.metadata.pallets().filter_map(|p| {
let variant_name_str = p.name();
let variant_name = format_ident!("{}", variant_name_str);
let mod_name = format_ident!("{}", variant_name_str.to_string().to_snake_case());
p.call_ty_id().map(|_| {
// An 'if' arm for the RootExtrinsic impl to match this variant name:
quote! {
if pallet_name == #variant_name_str {
return Ok(Call::#variant_name(#mod_name::Call::decode_with_metadata(
&mut &*pallet_bytes,
pallet_ty,
metadata
)?));
}
}
})
});
let outer_error_variants = self.metadata.pallets().filter_map(|p| {
let variant_name = format_ident!("{}", p.name());
let mod_name = format_ident!("{}", p.name().to_string().to_snake_case());
let index = proc_macro2::Literal::u8_unsuffixed(p.index());
p.error_ty_id().map(|_| {
quote! {
#[codec(index = #index)]
#variant_name(#mod_name::Error),
}
})
});
let outer_error = quote! {
#default_derives
pub enum Error {
#( #outer_error_variants )*
}
};
let root_error_if_arms = self.metadata.pallets().filter_map(|p| {
let variant_name_str = &p.name();
let variant_name = format_ident!("{}", variant_name_str);
let mod_name = format_ident!("{}", variant_name_str.to_string().to_snake_case());
p.error_ty_id().map(|type_id| {
quote! {
if pallet_name == #variant_name_str {
let variant_error = #mod_name::Error::decode_with_metadata(cursor, #type_id, metadata)?;
return Ok(Error::#variant_name(variant_error));
}
}
})
});
let mod_ident = &item_mod_ir.ident;
let pallets_with_constants: Vec<_> = pallets_with_mod_names
.iter()
@@ -526,6 +413,12 @@ impl RuntimeGenerator {
should_gen_docs,
)?;
// Fetch the paths of the outer enums.
// Substrate exposes those under `kitchensink_runtime`, while Polkadot under `polkadot_runtime`.
let call_path = type_gen.resolve_type_path(self.metadata.outer_enums().call_enum_ty());
let event_path = type_gen.resolve_type_path(self.metadata.outer_enums().event_enum_ty());
let error_path = type_gen.resolve_type_path(self.metadata.outer_enums().error_enum_ty());
Ok(quote! {
#( #item_mod_attrs )*
#[allow(dead_code, unused_imports, non_camel_case_types)]
@@ -551,36 +444,14 @@ impl RuntimeGenerator {
/// The error type returned when there is a runtime issue.
pub type DispatchError = #types_mod_ident::sp_runtime::DispatchError;
#outer_event
/// The outer event enum.
pub type Event = #event_path;
impl #crate_path::events::RootEvent for Event {
fn root_event(pallet_bytes: &[u8], pallet_name: &str, pallet_ty: u32, metadata: &#crate_path::Metadata) -> Result<Self, #crate_path::Error> {
use #crate_path::metadata::DecodeWithMetadata;
#( #root_event_if_arms )*
Err(#crate_path::ext::scale_decode::Error::custom(format!("Pallet name '{}' not found in root Event enum", pallet_name)).into())
}
}
/// The outer extrinsic enum.
pub type Call = #call_path;
#outer_extrinsic
impl #crate_path::blocks::RootExtrinsic for Call {
fn root_extrinsic(pallet_bytes: &[u8], pallet_name: &str, pallet_ty: u32, metadata: &#crate_path::Metadata) -> Result<Self, #crate_path::Error> {
use #crate_path::metadata::DecodeWithMetadata;
#( #root_extrinsic_if_arms )*
Err(#crate_path::ext::scale_decode::Error::custom(format!("Pallet name '{}' not found in root Call enum", pallet_name)).into())
}
}
#outer_error
impl #crate_path::error::RootError for Error {
fn root_error(pallet_bytes: &[u8], pallet_name: &str, metadata: &#crate_path::Metadata) -> Result<Self, #crate_path::Error> {
use #crate_path::metadata::DecodeWithMetadata;
let cursor = &mut &pallet_bytes[..];
#( #root_error_if_arms )*
Err(#crate_path::ext::scale_decode::Error::custom(format!("Pallet name '{}' not found in root Error enum", pallet_name)).into())
}
}
/// The outer error enum representing the DispatchError's Module variant.
pub type Error = #error_path;
pub fn constants() -> ConstantsApi {
ConstantsApi
+1 -1
View File
@@ -19,7 +19,7 @@ pub enum CodegenError {
#[error("Failed IO for {0}, make sure that you are providing the correct file path for metadata: {1}")]
Io(String, std::io::Error),
/// Cannot decode the metadata bytes.
#[error("Could not decode metadata, only V14 metadata is supported: {0}")]
#[error("Could not decode metadata, only V14 and V15 metadata are supported: {0}")]
Decode(#[from] codec::Error),
/// Out of line modules are not supported.
#[error("Out-of-line subxt modules are not supported, make sure you are providing a body to your module: pub mod polkadot {{ ... }}")]
+2 -5
View File
@@ -204,12 +204,9 @@ async fn fetch_metadata(
client: &impl ClientT,
version: MetadataVersion,
) -> Result<Vec<u8>, FetchMetadataError> {
if !matches!(
version,
MetadataVersion::Latest | MetadataVersion::Version(14)
) {
if !matches!(version, MetadataVersion::Version(14)) {
return Err(FetchMetadataError::Other(
"The node can only return version 14 metadata but you've asked for something else"
"The node can only return version 14 metadata using the legacy API but you've asked for something else"
.to_string(),
));
}
+1
View File
@@ -24,6 +24,7 @@ thiserror = { workspace = true }
bitvec = { workspace = true, features = ["alloc"] }
criterion = { workspace = true }
scale-info = { workspace = true, features = ["bit-vec"] }
assert_matches = { workspace = true }
[lib]
# Without this, libtest cli opts interfere with criteron benches:
+401 -5
View File
@@ -2,6 +2,8 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use std::collections::HashMap;
use super::TryFromError;
use crate::Metadata;
use frame_metadata::{v14, v15};
@@ -21,7 +23,47 @@ impl From<Metadata> for v14::RuntimeMetadataV14 {
}
}
fn v15_to_v14(metadata: v15::RuntimeMetadataV15) -> v14::RuntimeMetadataV14 {
fn v15_to_v14(mut metadata: v15::RuntimeMetadataV15) -> v14::RuntimeMetadataV14 {
let types = &mut metadata.types;
// In subxt we care about the `Address`, `Call`, `Signature` and `Extra` types.
let extrinsic_type = scale_info::Type {
path: scale_info::Path {
segments: vec![
"primitives".to_string(),
"runtime".to_string(),
"generic".to_string(),
"UncheckedExtrinsic".to_string(),
],
},
type_params: vec![
scale_info::TypeParameter::<scale_info::form::PortableForm> {
name: "Address".to_string(),
ty: Some(metadata.extrinsic.address_ty),
},
scale_info::TypeParameter::<scale_info::form::PortableForm> {
name: "Call".to_string(),
ty: Some(metadata.extrinsic.call_ty),
},
scale_info::TypeParameter::<scale_info::form::PortableForm> {
name: "Signature".to_string(),
ty: Some(metadata.extrinsic.signature_ty),
},
scale_info::TypeParameter::<scale_info::form::PortableForm> {
name: "Extra".to_string(),
ty: Some(metadata.extrinsic.extra_ty),
},
],
type_def: scale_info::TypeDef::Composite(scale_info::TypeDefComposite { fields: vec![] }),
docs: vec![],
};
let extrinsic_type_id = types.types.len() as u32;
types.types.push(scale_info::PortableType {
id: extrinsic_type_id,
ty: extrinsic_type,
});
v14::RuntimeMetadataV14 {
types: metadata.types,
pallets: metadata
@@ -92,7 +134,7 @@ fn v15_to_v14(metadata: v15::RuntimeMetadataV15) -> v14::RuntimeMetadataV14 {
})
.collect(),
extrinsic: frame_metadata::v14::ExtrinsicMetadata {
ty: metadata.extrinsic.ty,
ty: extrinsic_type_id.into(),
version: metadata.extrinsic.version,
signed_extensions: metadata.extrinsic.signed_extensions.into_iter().map(|ext| {
frame_metadata::v14::SignedExtensionMetadata {
@@ -106,7 +148,13 @@ fn v15_to_v14(metadata: v15::RuntimeMetadataV15) -> v14::RuntimeMetadataV14 {
}
}
fn v14_to_v15(metadata: v14::RuntimeMetadataV14) -> v15::RuntimeMetadataV15 {
fn v14_to_v15(mut metadata: v14::RuntimeMetadataV14) -> v15::RuntimeMetadataV15 {
// Find the extrinsic types.
let extrinsic_parts = ExtrinsicPartTypeIds::new(&metadata)
.expect("Extrinsic types are always present on V14; qed");
let outer_enums = generate_outer_enums(&mut metadata);
v15::RuntimeMetadataV15 {
types: metadata.types,
pallets: metadata
@@ -178,7 +226,6 @@ fn v14_to_v15(metadata: v14::RuntimeMetadataV14) -> v15::RuntimeMetadataV15 {
})
.collect(),
extrinsic: frame_metadata::v15::ExtrinsicMetadata {
ty: metadata.extrinsic.ty,
version: metadata.extrinsic.version,
signed_extensions: metadata.extrinsic.signed_extensions.into_iter().map(|ext| {
frame_metadata::v15::SignedExtensionMetadata {
@@ -186,9 +233,358 @@ fn v14_to_v15(metadata: v14::RuntimeMetadataV14) -> v15::RuntimeMetadataV15 {
ty: ext.ty,
additional_signed: ext.additional_signed,
}
}).collect()
}).collect(),
address_ty: extrinsic_parts.address.into(),
call_ty: extrinsic_parts.call.into(),
signature_ty: extrinsic_parts.signature.into(),
extra_ty: extrinsic_parts.extra.into(),
},
ty: metadata.ty,
apis: Default::default(),
outer_enums,
custom: v15::CustomMetadata {
map: Default::default(),
},
}
}
/// The type IDs extracted from the metadata that represent the
/// generic type parameters passed to the `UncheckedExtrinsic` from
/// the substrate-based chain.
struct ExtrinsicPartTypeIds {
address: u32,
call: u32,
signature: u32,
extra: u32,
}
impl ExtrinsicPartTypeIds {
/// Extract the generic type parameters IDs from the extrinsic type.
fn new(metadata: &v14::RuntimeMetadataV14) -> Result<Self, String> {
const ADDRESS: &str = "Address";
const CALL: &str = "Call";
const SIGNATURE: &str = "Signature";
const EXTRA: &str = "Extra";
let extrinsic_id = metadata.extrinsic.ty.id;
let Some(extrinsic_ty) = metadata.types.resolve(extrinsic_id) else {
return Err("Missing extrinsic type".into())
};
let params: HashMap<_, _> = extrinsic_ty
.type_params
.iter()
.map(|ty_param| {
let Some(ty) = ty_param.ty else {
return Err("Missing type param type from extrinsic".to_string());
};
Ok((ty_param.name.as_str(), ty.id))
})
.collect::<Result<_, _>>()?;
let Some(address) = params.get(ADDRESS) else {
return Err("Missing address type from extrinsic".into());
};
let Some(call) = params.get(CALL) else {
return Err("Missing call type from extrinsic".into());
};
let Some(signature) = params.get(SIGNATURE) else {
return Err("Missing signature type from extrinsic".into());
};
let Some(extra) = params.get(EXTRA) else {
return Err("Missing extra type from extrinsic".into());
};
Ok(ExtrinsicPartTypeIds {
address: *address,
call: *call,
signature: *signature,
extra: *extra,
})
}
}
fn generate_outer_enums(
metadata: &mut v14::RuntimeMetadataV14,
) -> v15::OuterEnums<scale_info::form::PortableForm> {
let call_enum = metadata
.types
.types
.iter()
.find(|ty| {
let Some(ident) = ty.ty.path.ident() else { return false };
ident == "RuntimeCall"
})
.expect("RuntimeCall exists in V14; qed");
let event_enum = metadata
.types
.types
.iter()
.find(|ty| {
let Some(ident) = ty.ty.path.ident() else { return false };
ident == "RuntimeEvent"
})
.expect("RuntimeEvent exists in V14; qed");
let call_ty = call_enum.id.into();
let event_ty = event_enum.id.into();
let mut path_segments = call_enum.ty.path.segments.clone();
let last = path_segments
.last_mut()
.expect("Should have at least one segment checked above; qed");
*last = "RuntimeError".to_string();
let error_ty_id = generate_runtime_error_type(metadata, path_segments);
v15::OuterEnums {
call_enum_ty: call_ty,
event_enum_ty: event_ty,
error_enum_ty: error_ty_id.into(),
}
}
/// Generate the `RuntimeError` type and add it to the metadata.
///
/// Returns the `RuntimeError` Id from the registry.
fn generate_runtime_error_type(
metadata: &mut v14::RuntimeMetadataV14,
path_segments: Vec<String>,
) -> u32 {
let variants: Vec<_> = metadata
.pallets
.iter()
.filter_map(|pallet| {
let Some(pallet_error) = &pallet.error else { return None };
let path = format!("{}Error", pallet.name);
Some(scale_info::Variant {
name: pallet.name.clone(),
fields: vec![scale_info::Field {
name: None,
ty: pallet_error.ty.id.into(),
type_name: Some(path),
docs: vec![],
}],
index: pallet.index,
docs: vec![],
})
})
.collect();
let error_type = scale_info::Type {
path: scale_info::Path {
segments: path_segments,
},
type_params: vec![],
type_def: scale_info::TypeDef::Variant(scale_info::TypeDefVariant { variants }),
docs: vec![],
};
let error_type_id = metadata.types.types.len() as u32;
metadata.types.types.push(scale_info::PortableType {
id: error_type_id,
ty: error_type,
});
error_type_id
}
#[cfg(test)]
mod tests {
use super::*;
use codec::Decode;
use frame_metadata::{v15::RuntimeMetadataV15, RuntimeMetadata, RuntimeMetadataPrefixed};
use scale_info::TypeDef;
use std::{fs, path::Path};
fn load_v15_metadata() -> RuntimeMetadataV15 {
let bytes = fs::read(Path::new("../artifacts/polkadot_metadata_full.scale"))
.expect("Cannot read metadata blob");
let meta: RuntimeMetadataPrefixed =
Decode::decode(&mut &*bytes).expect("Cannot decode scale metadata");
match meta.1 {
RuntimeMetadata::V15(v15) => v15,
_ => panic!("Unsupported metadata version {:?}", meta.1),
}
}
#[test]
fn test_extrinsic_id_generation() {
let v15 = load_v15_metadata();
let v14 = v15_to_v14(v15.clone());
let ext_ty = v14.types.resolve(v14.extrinsic.ty.id).unwrap();
let addr_id = ext_ty
.type_params
.iter()
.find_map(|ty| {
if ty.name == "Address" {
Some(ty.ty.unwrap().id)
} else {
None
}
})
.unwrap();
let call_id = ext_ty
.type_params
.iter()
.find_map(|ty| {
if ty.name == "Call" {
Some(ty.ty.unwrap().id)
} else {
None
}
})
.unwrap();
let extra_id = ext_ty
.type_params
.iter()
.find_map(|ty| {
if ty.name == "Extra" {
Some(ty.ty.unwrap().id)
} else {
None
}
})
.unwrap();
let signature_id = ext_ty
.type_params
.iter()
.find_map(|ty| {
if ty.name == "Signature" {
Some(ty.ty.unwrap().id)
} else {
None
}
})
.unwrap();
// Position in type registry shouldn't change.
assert_eq!(v15.extrinsic.address_ty.id, addr_id);
assert_eq!(v15.extrinsic.call_ty.id, call_id);
assert_eq!(v15.extrinsic.extra_ty.id, extra_id);
assert_eq!(v15.extrinsic.signature_ty.id, signature_id);
let v15_addr = v15.types.resolve(v15.extrinsic.address_ty.id).unwrap();
let v14_addr = v14.types.resolve(addr_id).unwrap();
assert_eq!(v15_addr, v14_addr);
let v15_call = v15.types.resolve(v15.extrinsic.call_ty.id).unwrap();
let v14_call = v14.types.resolve(call_id).unwrap();
assert_eq!(v15_call, v14_call);
let v15_extra = v15.types.resolve(v15.extrinsic.extra_ty.id).unwrap();
let v14_extra = v14.types.resolve(extra_id).unwrap();
assert_eq!(v15_extra, v14_extra);
let v15_sign = v15.types.resolve(v15.extrinsic.signature_ty.id).unwrap();
let v14_sign = v14.types.resolve(signature_id).unwrap();
assert_eq!(v15_sign, v14_sign);
// Ensure we don't lose the information when converting back to v15.
let converted_v15 = v14_to_v15(v14);
let v15_addr = v15.types.resolve(v15.extrinsic.address_ty.id).unwrap();
let converted_v15_addr = converted_v15
.types
.resolve(converted_v15.extrinsic.address_ty.id)
.unwrap();
assert_eq!(v15_addr, converted_v15_addr);
let v15_call = v15.types.resolve(v15.extrinsic.call_ty.id).unwrap();
let converted_v15_call = converted_v15
.types
.resolve(converted_v15.extrinsic.call_ty.id)
.unwrap();
assert_eq!(v15_call, converted_v15_call);
let v15_extra = v15.types.resolve(v15.extrinsic.extra_ty.id).unwrap();
let converted_v15_extra = converted_v15
.types
.resolve(converted_v15.extrinsic.extra_ty.id)
.unwrap();
assert_eq!(v15_extra, converted_v15_extra);
let v15_sign = v15.types.resolve(v15.extrinsic.signature_ty.id).unwrap();
let converted_v15_sign = converted_v15
.types
.resolve(converted_v15.extrinsic.signature_ty.id)
.unwrap();
assert_eq!(v15_sign, converted_v15_sign);
}
#[test]
fn test_outer_enums_generation() {
let v15 = load_v15_metadata();
let v14 = v15_to_v14(v15.clone());
// Convert back to v15 and expect to have the enum types properly generated.
let converted_v15 = v14_to_v15(v14);
// RuntimeCall and RuntimeEvent were already present in the metadata v14.
let v15_call = v15.types.resolve(v15.outer_enums.call_enum_ty.id).unwrap();
let converted_v15_call = converted_v15
.types
.resolve(converted_v15.outer_enums.call_enum_ty.id)
.unwrap();
assert_eq!(v15_call, converted_v15_call);
let v15_event = v15.types.resolve(v15.outer_enums.event_enum_ty.id).unwrap();
let converted_v15_event = converted_v15
.types
.resolve(converted_v15.outer_enums.event_enum_ty.id)
.unwrap();
assert_eq!(v15_event, converted_v15_event);
let v15_error = v15.types.resolve(v15.outer_enums.error_enum_ty.id).unwrap();
let converted_v15_error = converted_v15
.types
.resolve(converted_v15.outer_enums.error_enum_ty.id)
.unwrap();
// Ensure they match in terms of variants and fields ids.
assert_eq!(v15_error.path, converted_v15_error.path);
let TypeDef::Variant(v15_variant) = &v15_error.type_def else {
panic!("V15 error must be a variant");
};
let TypeDef::Variant(converted_v15_variant) = &converted_v15_error.type_def else {
panic!("Converted V15 error must be a variant");
};
assert_eq!(
v15_variant.variants.len(),
converted_v15_variant.variants.len()
);
for (v15_var, converted_v15_var) in v15_variant
.variants
.iter()
.zip(converted_v15_variant.variants.iter())
{
// Variant name must match.
assert_eq!(v15_var.name, converted_v15_var.name);
assert_eq!(v15_var.fields.len(), converted_v15_var.fields.len());
// Fields must have the same type.
for (v15_field, converted_v15_field) in
v15_var.fields.iter().zip(converted_v15_var.fields.iter())
{
assert_eq!(v15_field.ty.id, converted_v15_field.ty.id);
let ty = v15.types.resolve(v15_field.ty.id).unwrap();
let converted_ty = converted_v15
.types
.resolve(converted_v15_field.ty.id)
.unwrap();
assert_eq!(ty, converted_ty);
}
}
}
}
+22 -3
View File
@@ -6,7 +6,7 @@ use super::TryFromError;
use crate::utils::variant_index::VariantIndex;
use crate::{
utils::ordered_map::OrderedMap, ArcStr, ConstantMetadata, ExtrinsicMetadata, Metadata,
PalletMetadataInner, RuntimeApiMetadataInner, RuntimeApiMethodMetadata,
OuterEnumsMetadata, PalletMetadataInner, RuntimeApiMetadataInner, RuntimeApiMethodMetadata,
RuntimeApiMethodParamMetadata, SignedExtensionMetadata, StorageEntryMetadata,
StorageEntryModifier, StorageEntryType, StorageHasher, StorageMetadata,
};
@@ -88,6 +88,11 @@ mod from_v15 {
runtime_ty: m.ty.id,
dispatch_error_ty,
apis: apis.collect(),
outer_enums: OuterEnumsMetadata {
call_enum_ty: m.outer_enums.call_enum_ty.id,
event_enum_ty: m.outer_enums.event_enum_ty.id,
error_enum_ty: m.outer_enums.error_enum_ty.id,
},
})
}
}
@@ -104,13 +109,16 @@ mod from_v15 {
fn from_extrinsic_metadata(value: v15::ExtrinsicMetadata<PortableForm>) -> ExtrinsicMetadata {
ExtrinsicMetadata {
ty: value.ty.id,
version: value.version,
signed_extensions: value
.signed_extensions
.into_iter()
.map(from_signed_extension_metadata)
.collect(),
address_ty: value.address_ty.id,
call_ty: value.call_ty.id,
signature_ty: value.signature_ty.id,
extra_ty: value.extra_ty.id,
}
}
@@ -268,6 +276,14 @@ mod into_v15 {
.into_iter()
.map(from_runtime_api_metadata)
.collect(),
outer_enums: v15::OuterEnums {
call_enum_ty: m.outer_enums.call_enum_ty.into(),
event_enum_ty: m.outer_enums.event_enum_ty.into(),
error_enum_ty: m.outer_enums.error_enum_ty.into(),
},
custom: v15::CustomMetadata {
map: Default::default(),
},
}
}
}
@@ -313,13 +329,16 @@ mod into_v15 {
fn from_extrinsic_metadata(e: ExtrinsicMetadata) -> v15::ExtrinsicMetadata<PortableForm> {
v15::ExtrinsicMetadata {
ty: e.ty.into(),
version: e.version,
signed_extensions: e
.signed_extensions
.into_iter()
.map(from_signed_extension_metadata)
.collect(),
address_ty: e.address_ty.into(),
call_ty: e.call_ty.into(),
signature_ty: e.signature_ty.into(),
extra_ty: e.extra_ty.into(),
}
}
+59 -5
View File
@@ -45,6 +45,8 @@ pub struct Metadata {
extrinsic: ExtrinsicMetadata,
/// The type ID of the `Runtime` type.
runtime_ty: u32,
/// The types of the outer enums.
outer_enums: OuterEnumsMetadata,
/// 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.
@@ -77,6 +79,11 @@ impl Metadata {
&self.extrinsic
}
/// Return details about the outer enums.
pub fn outer_enums(&self) -> OuterEnumsMetadata {
self.outer_enums
}
/// An iterator over all of the available pallets.
pub fn pallets(&self) -> impl ExactSizeIterator<Item = PalletMetadata<'_>> {
self.pallets.values().iter().map(|inner| PalletMetadata {
@@ -440,8 +447,14 @@ impl ConstantMetadata {
/// Metadata for the extrinsic type.
#[derive(Debug, Clone)]
pub struct ExtrinsicMetadata {
/// The type of the extrinsic.
ty: u32,
/// The type of the address that signs the extrinsic
address_ty: u32,
/// The type of the outermost Call enum.
call_ty: u32,
/// The type of the extrinsic's signature.
signature_ty: u32,
/// The type of the outermost Extra enum.
extra_ty: u32,
/// Extrinsic version.
version: u8,
/// The signed extensions in the order they appear in the extrinsic.
@@ -449,9 +462,22 @@ pub struct ExtrinsicMetadata {
}
impl ExtrinsicMetadata {
/// Type of the extrinsic.
pub fn ty(&self) -> u32 {
self.ty
/// The type of the address that signs the extrinsic
pub fn address_ty(&self) -> u32 {
self.address_ty
}
/// The type of the outermost Call enum.
pub fn call_ty(&self) -> u32 {
self.call_ty
}
/// The type of the extrinsic's signature.
pub fn signature_ty(&self) -> u32 {
self.signature_ty
}
/// The type of the outermost Extra enum.
pub fn extra_ty(&self) -> u32 {
self.extra_ty
}
/// Extrinsic version.
@@ -491,6 +517,34 @@ impl SignedExtensionMetadata {
}
}
/// Metadata for the outer enums.
#[derive(Debug, Clone, Copy)]
pub struct OuterEnumsMetadata {
/// The type of the outer call enum.
call_enum_ty: u32,
/// The type of the outer event enum.
event_enum_ty: u32,
/// The type of the outer error enum.
error_enum_ty: u32,
}
impl OuterEnumsMetadata {
/// The type of the outer call enum.
pub fn call_enum_ty(&self) -> u32 {
self.call_enum_ty
}
/// The type of the outer event enum.
pub fn event_enum_ty(&self) -> u32 {
self.event_enum_ty
}
/// The type of the outer error enum.
pub fn error_enum_ty(&self) -> u32 {
self.error_enum_ty
}
}
/// Metadata for the available runtime APIs.
#[derive(Debug, Clone, Copy)]
pub struct RuntimeApiMetadata<'a> {
+66 -26
View File
@@ -5,7 +5,8 @@
//! Utility functions to generate a subset of the metadata.
use crate::{
ExtrinsicMetadata, Metadata, PalletMetadataInner, RuntimeApiMetadataInner, StorageEntryType,
ExtrinsicMetadata, Metadata, OuterEnumsMetadata, PalletMetadataInner, RuntimeApiMetadataInner,
StorageEntryType,
};
use scale_info::TypeDef;
use std::collections::{BTreeMap, HashSet};
@@ -82,7 +83,10 @@ fn update_pallet_types(pallet: &mut PalletMetadataInner, map_ids: &BTreeMap<u32,
/// Collect all type IDs needed to represent the extrinsic metadata.
fn collect_extrinsic_types(extrinsic: &ExtrinsicMetadata, type_ids: &mut HashSet<u32>) {
type_ids.insert(extrinsic.ty);
type_ids.insert(extrinsic.address_ty);
type_ids.insert(extrinsic.call_ty);
type_ids.insert(extrinsic.signature_ty);
type_ids.insert(extrinsic.extra_ty);
for signed in &extrinsic.signed_extensions {
type_ids.insert(signed.extra_ty);
@@ -92,7 +96,10 @@ fn collect_extrinsic_types(extrinsic: &ExtrinsicMetadata, type_ids: &mut HashSet
/// Update all type IDs of the provided extrinsic metadata using the new type IDs from the portable registry.
fn update_extrinsic_types(extrinsic: &mut ExtrinsicMetadata, map_ids: &BTreeMap<u32, u32>) {
update_type(&mut extrinsic.ty, map_ids);
update_type(&mut extrinsic.address_ty, map_ids);
update_type(&mut extrinsic.call_ty, map_ids);
update_type(&mut extrinsic.signature_ty, map_ids);
update_type(&mut extrinsic.extra_ty, map_ids);
for signed in &mut extrinsic.signed_extensions {
update_type(&mut signed.extra_ty, map_ids);
@@ -122,6 +129,20 @@ fn update_runtime_api_types(apis: &mut [RuntimeApiMetadataInner], map_ids: &BTre
}
}
/// Collect the outer enums type IDs.
fn collect_outer_enums(enums: &OuterEnumsMetadata, type_ids: &mut HashSet<u32>) {
type_ids.insert(enums.call_enum_ty);
type_ids.insert(enums.event_enum_ty);
type_ids.insert(enums.error_enum_ty);
}
/// Update all the type IDs for outer enums.
fn update_outer_enums(enums: &mut OuterEnumsMetadata, map_ids: &BTreeMap<u32, u32>) {
update_type(&mut enums.call_enum_ty, map_ids);
update_type(&mut enums.event_enum_ty, map_ids);
update_type(&mut enums.error_enum_ty, map_ids);
}
/// Update the given type using the new type ID from the portable registry.
///
/// # Panics
@@ -136,38 +157,36 @@ fn update_type(ty: &mut u32, map_ids: &BTreeMap<u32, u32>) {
*ty = new_id;
}
/// Strip any pallets out of the RuntimeCall type that aren't the ones we want to keep.
/// The RuntimeCall type is referenced in a bunch of places, so doing this prevents us from
/// holding on to stuff in pallets we've asked not to keep.
fn retain_pallets_in_runtime_call_type<F>(metadata: &mut Metadata, mut filter: F)
/// Retain the enum type identified by ID and keep only the variants that
/// match the provided filter.
fn retain_variants_in_enum_type<F>(metadata: &mut Metadata, id: u32, mut filter: F)
where
F: FnMut(&str) -> bool,
{
let extrinsic_ty = metadata
let ty = metadata
.types
.types
.get_mut(metadata.extrinsic.ty as usize)
.expect("Metadata should contain extrinsic type in registry");
.get_mut(id as usize)
.expect("Metadata should contain enum type in registry");
let Some(call_ty) = extrinsic_ty.ty.type_params
.iter_mut()
.find(|ty| ty.name == "Call")
.and_then(|ty| ty.ty) else { return; };
let call_ty = metadata
.types
.types
.get_mut(call_ty.id as usize)
.expect("Metadata should contain Call type information");
let TypeDef::Variant(variant) = &mut call_ty.ty.type_def else {
panic!("Metadata Call type is expected to be a variant type");
let TypeDef::Variant(variant) = &mut ty.ty.type_def else {
panic!("Metadata type is expected to be a variant type");
};
// Remove all variants from the call type that aren't the pallet(s) we want to keep.
// Remove all variants from the type that aren't the pallet(s) we want to keep.
variant.variants.retain(|v| filter(&v.name));
}
/// Strip any pallets out of the outer enum types that aren't the ones we want to keep.
fn retain_pallets_in_runtime_outer_types<F>(metadata: &mut Metadata, mut filter: F)
where
F: FnMut(&str) -> bool,
{
retain_variants_in_enum_type(metadata, metadata.outer_enums.call_enum_ty, &mut filter);
retain_variants_in_enum_type(metadata, metadata.outer_enums.event_enum_ty, &mut filter);
retain_variants_in_enum_type(metadata, metadata.outer_enums.error_enum_ty, &mut filter);
}
/// Generate a subset of the metadata that contains only the
/// types needed to represent the provided pallets and runtime APIs.
///
@@ -190,10 +209,13 @@ pub fn retain_metadata<F, G>(
{
let mut type_ids = HashSet::new();
// There is a special RuntimeCall type which points to all pallets and call types by default.
// There are special outer enum types that point to all pallets types (call, error, event) by default.
// This brings in a significant chunk of types. We trim this down to only include variants
// for the pallets we're retaining, to avoid this.
retain_pallets_in_runtime_call_type(metadata, &mut pallets_filter);
retain_pallets_in_runtime_outer_types(metadata, &mut pallets_filter);
// Collect the stripped outer enums.
collect_outer_enums(&metadata.outer_enums, &mut type_ids);
// Filter our pallet list to only those pallets we want to keep. Keep hold of all
// type IDs in the pallets we're keeping. Retain all, if no filter specified.
@@ -245,6 +267,7 @@ pub fn retain_metadata<F, G>(
let map_ids = metadata.types.retain(|id| type_ids.contains(&id));
// And finally, we can go and update all of our type IDs in the metadata as a result of this:
update_outer_enums(&mut metadata.outer_enums, &map_ids);
for pallets in metadata.pallets.values_mut() {
update_pallet_types(pallets, &map_ids);
}
@@ -257,6 +280,7 @@ pub fn retain_metadata<F, G>(
mod tests {
use super::*;
use crate::Metadata;
use assert_matches::assert_matches;
use codec::Decode;
use frame_metadata::{RuntimeMetadata, RuntimeMetadataPrefixed};
use std::{fs, path::Path};
@@ -281,6 +305,7 @@ mod tests {
// Retain one pallet at a time ensuring the test does not panic.
for pallet in metadata_cache.pallets() {
let mut metadata = metadata_cache.clone();
retain_metadata(
&mut metadata,
|pallet_name| pallet_name == pallet.name(),
@@ -292,6 +317,21 @@ mod tests {
&*metadata.pallets.get_by_index(0).unwrap().name,
pallet.name()
);
let id = metadata.outer_enums().call_enum_ty;
let ty = metadata.types.resolve(id).unwrap();
let num_variants = if pallet.call_ty_id().is_some() { 1 } else { 0 };
assert_matches!(&ty.type_def, TypeDef::Variant(variant) if variant.variants.len() == num_variants);
let id = metadata.outer_enums().error_enum_ty;
let ty = metadata.types.resolve(id).unwrap();
let num_variants = if pallet.error_ty_id().is_some() { 1 } else { 0 };
assert_matches!(&ty.type_def, TypeDef::Variant(variant) if variant.variants.len() == num_variants);
let id = metadata.outer_enums().event_enum_ty;
let ty = metadata.types.resolve(id).unwrap();
let num_variants = if pallet.event_ty_id().is_some() { 1 } else { 0 };
assert_matches!(&ty.type_def, TypeDef::Variant(variant) if variant.variants.len() == num_variants);
}
}
+97 -18
View File
@@ -5,10 +5,10 @@
//! Utility functions for metadata validation.
use crate::{
ExtrinsicMetadata, Metadata, PalletMetadata, RuntimeApiMetadata, RuntimeApiMethodMetadata,
StorageEntryMetadata, StorageEntryType,
ExtrinsicMetadata, Metadata, OuterEnumsMetadata, PalletMetadata, RuntimeApiMetadata,
RuntimeApiMethodMetadata, StorageEntryMetadata, StorageEntryType,
};
use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef, Variant};
use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef, TypeDefVariant, Variant};
use std::collections::HashSet;
/// Predefined value to be returned when we already visited a type.
@@ -104,6 +104,30 @@ fn get_variant_hash(
concat_and_hash2(&variant_name_bytes, &variant_field_bytes)
}
fn get_type_def_variant_hash(
registry: &PortableRegistry,
variant: &TypeDefVariant<PortableForm>,
only_these_variants: Option<&[&str]>,
visited_ids: &mut HashSet<u32>,
) -> [u8; HASH_LEN] {
let variant_id_bytes = [TypeBeingHashed::Variant as u8; HASH_LEN];
let variant_field_bytes = variant.variants.iter().fold([0u8; HASH_LEN], |bytes, var| {
// With EncodeAsType and DecodeAsType we no longer care which order the variants are in,
// as long as all of the names+types are there. XOR to not care about ordering.
let should_hash = only_these_variants
.as_ref()
.map(|only_these_variants| only_these_variants.contains(&var.name.as_str()))
.unwrap_or(true);
if should_hash {
xor(bytes, get_variant_hash(registry, var, visited_ids))
} else {
bytes
}
});
concat_and_hash2(&variant_id_bytes, &variant_field_bytes)
}
/// Obtain the hash representation of a `scale_info::TypeDef`.
fn get_type_def_hash(
registry: &PortableRegistry,
@@ -125,14 +149,7 @@ fn get_type_def_hash(
concat_and_hash2(&composite_id_bytes, &composite_field_bytes)
}
TypeDef::Variant(variant) => {
let variant_id_bytes = [TypeBeingHashed::Variant as u8; HASH_LEN];
let variant_field_bytes =
variant.variants.iter().fold([0u8; HASH_LEN], |bytes, var| {
// With EncodeAsType and DecodeAsType we no longer care which order the variants are in,
// as long as all of the names+types are there. XOR to not care about ordering.
xor(bytes, get_variant_hash(registry, var, visited_ids))
});
concat_and_hash2(&variant_id_bytes, &variant_field_bytes)
get_type_def_variant_hash(registry, variant, None, visited_ids)
}
TypeDef::Sequence(sequence) => concat_and_hash2(
&[TypeBeingHashed::Sequence as u8; HASH_LEN],
@@ -198,8 +215,16 @@ fn get_extrinsic_hash(
) -> [u8; HASH_LEN] {
let mut visited_ids = HashSet::<u32>::new();
let mut bytes = concat_and_hash2(
&get_type_hash(registry, extrinsic.ty, &mut visited_ids),
// Get the hashes of the extrinsic type.
let address_hash = get_type_hash(registry, extrinsic.address_ty, &mut visited_ids);
// The `RuntimeCall` type is intentionally omitted and hashed by the outer enums instead.
let signature_hash = get_type_hash(registry, extrinsic.signature_ty, &mut visited_ids);
let extra_hash = get_type_hash(registry, extrinsic.extra_ty, &mut visited_ids);
let mut bytes = concat_and_hash4(
&address_hash,
&signature_hash,
&extra_hash,
&[extrinsic.version; 32],
);
@@ -215,6 +240,39 @@ fn get_extrinsic_hash(
bytes
}
/// Obtain the hash representation of the `frame_metadata::v15::OuterEnums`.
fn get_outer_enums_hash(
registry: &PortableRegistry,
enums: &OuterEnumsMetadata,
only_these_variants: Option<&[&str]>,
) -> [u8; HASH_LEN] {
/// Hash the provided enum type.
fn get_enum_hash(
registry: &PortableRegistry,
id: u32,
only_these_variants: Option<&[&str]>,
) -> [u8; HASH_LEN] {
let ty = registry
.types
.get(id as usize)
.expect("Metadata should contain enum type in registry");
if let TypeDef::Variant(variant) = &ty.ty.type_def {
get_type_def_variant_hash(registry, variant, only_these_variants, &mut HashSet::new())
} else {
get_type_hash(registry, id, &mut HashSet::new())
}
}
let call_hash = get_enum_hash(registry, enums.call_enum_ty, only_these_variants);
let event_hash = get_enum_hash(registry, enums.event_enum_ty, only_these_variants);
let error_hash = get_enum_hash(registry, enums.error_enum_ty, only_these_variants);
concat_and_hash3(&call_hash, &event_hash, &error_hash)
}
/// Get the hash corresponding to a single storage entry.
fn get_storage_entry_hash(
registry: &PortableRegistry,
@@ -441,8 +499,6 @@ impl<'a> MetadataHasher<'a> {
/// Hash the given metadata.
pub fn hash(&self) -> [u8; HASH_LEN] {
let mut visited_ids = HashSet::<u32>::new();
let metadata = self.metadata;
let pallet_hash = metadata.pallets().fold([0u8; HASH_LEN], |bytes, pallet| {
@@ -480,9 +536,21 @@ impl<'a> MetadataHasher<'a> {
});
let extrinsic_hash = get_extrinsic_hash(&metadata.types, &metadata.extrinsic);
let runtime_hash = get_type_hash(&metadata.types, metadata.runtime_ty(), &mut visited_ids);
let runtime_hash =
get_type_hash(&metadata.types, metadata.runtime_ty(), &mut HashSet::new());
let outer_enums_hash = get_outer_enums_hash(
&metadata.types,
&metadata.outer_enums(),
self.specific_pallets.as_deref(),
);
concat_and_hash4(&pallet_hash, &apis_hash, &extrinsic_hash, &runtime_hash)
concat_and_hash5(
&pallet_hash,
&apis_hash,
&extrinsic_hash,
&runtime_hash,
&outer_enums_hash,
)
}
}
@@ -552,9 +620,12 @@ mod tests {
fn build_default_extrinsic() -> v15::ExtrinsicMetadata {
v15::ExtrinsicMetadata {
ty: meta_type::<()>(),
version: 0,
signed_extensions: vec![],
address_ty: meta_type::<()>(),
call_ty: meta_type::<()>(),
signature_ty: meta_type::<()>(),
extra_ty: meta_type::<()>(),
}
}
@@ -597,6 +668,14 @@ mod tests {
build_default_extrinsic(),
meta_type::<()>(),
vec![],
v15::OuterEnums {
call_enum_ty: meta_type::<()>(),
event_enum_ty: meta_type::<()>(),
error_enum_ty: meta_type::<()>(),
},
v15::CustomMetadata {
map: Default::default(),
},
)
.try_into()
.expect("can build valid metadata")
+4 -4
View File
@@ -12,10 +12,10 @@
# ```
# get the full metadata
cargo run --bin subxt metadata --version unstable > artifacts/polkadot_metadata_full.scale
cargo run --bin subxt metadata --version 15 > artifacts/polkadot_metadata_full.scale
# use it to generate polkadot.rs
cargo run --bin subxt codegen --file artifacts/polkadot_metadata_full.scale | rustfmt > testing/integration-tests/src/codegen/polkadot.rs
# generate a metadata file that only contains the pallets Balances, Staking, System and Multisig
cargo run --bin subxt metadata --file artifacts/polkadot_metadata_full.scale --pallets "Balances,Staking,System,Multisig" > artifacts/polkadot_metadata_small.scale
cargo run --bin subxt codegen --file artifacts/polkadot_metadata_full.scale | rustfmt > testing/integration-tests/src/full_client/codegen/polkadot.rs
# generate a metadata file that only contains a few pallets that we need for our examples.
cargo run --bin subxt metadata --file artifacts/polkadot_metadata_full.scale --pallets "Balances,Staking,System,Multisig,Timestamp,ParaInherent" > artifacts/polkadot_metadata_small.scale
# generate a metadata file that only contains no pallets
cargo run --bin subxt metadata --file artifacts/polkadot_metadata_full.scale --pallets "" > artifacts/polkadot_metadata_tiny.scale
+33 -96
View File
@@ -16,8 +16,8 @@ use crate::{
use crate::utils::strip_compact_prefix;
use codec::Decode;
use derivative::Derivative;
use scale_decode::DecodeAsFields;
use std::{collections::HashMap, sync::Arc};
use scale_decode::{DecodeAsFields, DecodeAsType};
use std::sync::Arc;
/// Trait to uniquely identify the extrinsic's identity from the runtime metadata.
///
@@ -37,23 +37,6 @@ pub trait StaticExtrinsic: DecodeAsFields {
}
}
/// This trait is implemented on the statically generated root extrinsic type, so that we're able
/// to decode it properly via a pallet that impls `DecodeAsMetadata`. This is necessary
/// because the "root extrinsic" type is generated using pallet info but doesn't actually exist in the
/// metadata types, so we have no easy way to decode things into it via type information and need a
/// little help via codegen.
#[doc(hidden)]
pub trait RootExtrinsic: Sized {
/// Given details of the pallet extrinsic we want to decode, and the name of the pallet, try to hand
/// back a "root extrinsic".
fn root_extrinsic(
pallet_bytes: &[u8],
pallet_name: &str,
pallet_extrinsic_ty: u32,
metadata: &Metadata,
) -> Result<Self, Error>;
}
/// The body of a block.
pub struct Extrinsics<T: Config, C> {
client: C,
@@ -420,22 +403,17 @@ where
}
}
/// Attempt to decode these [`ExtrinsicDetails`] into a root extrinsic type (which includes
/// 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: RootExtrinsic>(&self) -> Result<E, Error> {
let md = self.extrinsic_metadata()?;
let pallet_extrinsic_ty = md.pallet.call_ty_id().ok_or_else(|| {
Error::Metadata(MetadataError::CallTypeNotFoundInPallet(md.pallet.index()))
})?;
pub fn as_root_extrinsic<E: DecodeAsType>(&self) -> Result<E, Error> {
let decoded = E::decode_as_type(
&mut &self.call_bytes()[..],
self.metadata.outer_enums().call_enum_ty(),
self.metadata.types(),
)?;
// Ignore root enum index.
E::root_extrinsic(
&self.call_bytes()[1..],
md.pallet.name(),
pallet_extrinsic_ty,
&self.metadata,
)
Ok(decoded)
}
}
@@ -478,47 +456,11 @@ pub(crate) struct ExtrinsicPartTypeIds {
impl ExtrinsicPartTypeIds {
/// Extract the generic type parameters IDs from the extrinsic type.
pub(crate) fn new(metadata: &Metadata) -> Result<Self, BlockError> {
const ADDRESS: &str = "Address";
const CALL: &str = "Call";
const SIGNATURE: &str = "Signature";
const EXTRA: &str = "Extra";
let id = metadata.extrinsic().ty();
let Some(ty) = metadata.types().resolve(id) else {
return Err(BlockError::MissingType);
};
let params: HashMap<_, _> = ty
.type_params
.iter()
.map(|ty_param| {
let Some(ty) = ty_param.ty else {
return Err(BlockError::MissingType);
};
Ok((ty_param.name.as_str(), ty.id))
})
.collect::<Result<_, _>>()?;
let Some(address) = params.get(ADDRESS) else {
return Err(BlockError::MissingType);
};
let Some(call) = params.get(CALL) else {
return Err(BlockError::MissingType);
};
let Some(signature) = params.get(SIGNATURE) else {
return Err(BlockError::MissingType);
};
let Some(extra) = params.get(EXTRA) else {
return Err(BlockError::MissingType);
};
Ok(ExtrinsicPartTypeIds {
address: *address,
_call: *call,
signature: *signature,
extra: *extra,
address: metadata.extrinsic().address_ty(),
_call: metadata.extrinsic().call_ty(),
signature: metadata.extrinsic().signature_ty(),
extra: metadata.extrinsic().extra_ty(),
})
}
}
@@ -620,10 +562,10 @@ impl<T: Config> ExtrinsicEvents<T> {
#[cfg(test)]
mod tests {
use super::*;
use crate::metadata::DecodeWithMetadata;
use crate::{rpc::types::RuntimeVersion, OfflineClient, PolkadotConfig};
use assert_matches::assert_matches;
use codec::{Decode, Encode};
use frame_metadata::v15::{CustomMetadata, OuterEnums};
use frame_metadata::{
v15::{ExtrinsicMetadata, PalletCallMetadata, PalletMetadata, RuntimeMetadataV15},
RuntimeMetadataPrefixed,
@@ -660,27 +602,6 @@ mod tests {
Test(Pallet),
}
// We need this in order to be able to decode into a root extrinsic type:
impl RootExtrinsic for RuntimeCall {
fn root_extrinsic(
mut pallet_bytes: &[u8],
pallet_name: &str,
pallet_extrinsic_ty: u32,
metadata: &Metadata,
) -> Result<Self, Error> {
if pallet_name == "Test" {
return Ok(RuntimeCall::Test(Pallet::decode_with_metadata(
&mut pallet_bytes,
pallet_extrinsic_ty,
metadata,
)?));
}
panic!(
"Asked for pallet name '{pallet_name}', which isn't in our test RuntimeCall type"
)
}
}
// The calls of the pallet.
#[allow(unused)]
#[derive(
@@ -743,12 +664,28 @@ mod tests {
}];
let extrinsic = ExtrinsicMetadata {
ty: meta_type::<ExtrinsicType<(), RuntimeCall, (), ()>>(),
version: 4,
signed_extensions: vec![],
address_ty: meta_type::<()>(),
call_ty: meta_type::<RuntimeCall>(),
signature_ty: meta_type::<()>(),
extra_ty: meta_type::<()>(),
};
let meta = RuntimeMetadataV15::new(pallets, extrinsic, meta_type::<()>(), vec![]);
let meta = RuntimeMetadataV15::new(
pallets,
extrinsic,
meta_type::<()>(),
vec![],
OuterEnums {
call_enum_ty: meta_type::<RuntimeCall>(),
event_enum_ty: meta_type::<()>(),
error_enum_ty: meta_type::<()>(),
},
CustomMetadata {
map: Default::default(),
},
);
let runtime_metadata: RuntimeMetadataPrefixed = meta.into();
Metadata::new(runtime_metadata.try_into().unwrap())
+1 -3
View File
@@ -10,6 +10,4 @@ mod extrinsic_types;
pub use block_types::{Block, BlockBody};
pub use blocks_client::{subscribe_to_block_headers_filling_in_gaps, BlocksClient};
pub use extrinsic_types::{
ExtrinsicDetails, ExtrinsicEvents, Extrinsics, RootExtrinsic, StaticExtrinsic,
};
pub use extrinsic_types::{ExtrinsicDetails, ExtrinsicEvents, Extrinsics, StaticExtrinsic};
+19 -3
View File
@@ -129,16 +129,32 @@ impl<T: Config> OnlineClient<T> {
async fn fetch_metadata(rpc: &Rpc<T>) -> Result<Metadata, Error> {
#[cfg(feature = "unstable-metadata")]
{
/// The unstable metadata version number.
const UNSTABLE_METADATA_VERSION: u32 = u32::MAX;
// Try to fetch the latest unstable metadata, if that fails fall back to
// fetching the latest stable metadata.
const V15_METADATA_VERSION: u32 = u32::MAX;
match rpc.metadata_at_version(V15_METADATA_VERSION).await {
match rpc.metadata_at_version(UNSTABLE_METADATA_VERSION).await {
Ok(bytes) => Ok(bytes),
Err(_) => rpc.metadata().await,
Err(_) => OnlineClient::fetch_latest_stable_metadata(rpc).await,
}
}
#[cfg(not(feature = "unstable-metadata"))]
OnlineClient::fetch_latest_stable_metadata(rpc).await
}
/// Fetch the latest stable metadata from the node.
async fn fetch_latest_stable_metadata(rpc: &Rpc<T>) -> Result<Metadata, Error> {
// This is the latest stable metadata that subxt can utilize.
const V15_METADATA_VERSION: u32 = 15;
// Try to fetch the metadata version.
if let Ok(bytes) = rpc.metadata_at_version(V15_METADATA_VERSION).await {
return Ok(bytes);
}
// If that fails, fetch the metadata V14 using the old API.
rpc.metadata().await
}
+41 -51
View File
@@ -7,11 +7,10 @@
use crate::metadata::{DecodeWithMetadata, Metadata};
use core::fmt::Debug;
use scale_decode::visitor::DecodeAsTypeResult;
use scale_decode::{visitor::DecodeAsTypeResult, DecodeAsType};
use std::borrow::Cow;
use super::{Error, MetadataError};
use crate::error::RootError;
/// An error dispatching a transaction.
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
@@ -127,13 +126,17 @@ pub enum TransactionalError {
#[non_exhaustive]
pub struct ModuleError {
metadata: Metadata,
raw: RawModuleError,
/// Bytes representation:
/// - `bytes[0]`: pallet index
/// - `bytes[1]`: error index
/// - `bytes[2..]`: 3 bytes specific for the module error
bytes: [u8; 5],
}
impl PartialEq for ModuleError {
fn eq(&self, other: &Self) -> bool {
// A module error is the same if the raw underlying details are the same.
self.raw == other.raw
self.bytes == other.bytes
}
}
@@ -154,27 +157,38 @@ impl std::fmt::Display for ModuleError {
impl ModuleError {
/// Return more details about this error.
pub fn details(&self) -> Result<ModuleErrorDetails, MetadataError> {
let pallet = self.metadata.pallet_by_index_err(self.raw.pallet_index)?;
let pallet = self.metadata.pallet_by_index_err(self.pallet_index())?;
let variant = pallet
.error_variant_by_index(self.raw.error[0])
.ok_or_else(|| MetadataError::VariantIndexNotFound(self.raw.error[0]))?;
.error_variant_by_index(self.error_index())
.ok_or_else(|| MetadataError::VariantIndexNotFound(self.error_index()))?;
Ok(ModuleErrorDetails { pallet, variant })
}
/// Return the underlying module error data that was decoded.
pub fn raw(&self) -> RawModuleError {
self.raw
pub fn bytes(&self) -> [u8; 5] {
self.bytes
}
/// Attempts to decode the ModuleError into a value implementing the trait `RootError`
/// where the actual type of value is the generated top level enum `Error`.
pub fn as_root_error<E: RootError>(&self) -> Result<E, Error> {
E::root_error(
&self.raw.error,
self.details()?.pallet.name(),
&self.metadata,
)
/// Obtain the pallet index from the underlying byte data.
pub fn pallet_index(&self) -> u8 {
self.bytes[0]
}
/// Obtain the error index from the underlying byte data.
pub fn error_index(&self) -> u8 {
self.bytes[1]
}
/// Attempts to decode the ModuleError into the top outer Error enum.
pub fn as_root_error<E: DecodeAsType>(&self) -> Result<E, Error> {
let decoded = E::decode_as_type(
&mut &self.bytes[..],
self.metadata.outer_enums().error_enum_ty(),
self.metadata.types(),
)?;
Ok(decoded)
}
}
@@ -186,25 +200,6 @@ pub struct ModuleErrorDetails<'a> {
pub variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
}
/// The error details about a module error that has occurred.
///
/// **Note**: Structure used to obtain the underlying bytes of a ModuleError.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RawModuleError {
/// Index of the pallet that the error came from.
pub pallet_index: u8,
/// Raw error bytes.
pub error: [u8; 4],
}
impl RawModuleError {
/// Obtain the error index from the underlying byte data.
pub fn error_index(&self) -> u8 {
// Error index is utilized as the first byte from the error array.
self.error[0]
}
}
impl DispatchError {
/// Attempt to decode a runtime [`DispatchError`].
#[doc(hidden)]
@@ -290,21 +285,16 @@ impl DispatchError {
// The old version is 2 bytes; a pallet and error index.
// The new version is 5 bytes; a pallet and error index and then 3 extra bytes.
let raw = if module_bytes.len() == 2 {
RawModuleError {
pallet_index: module_bytes[0],
error: [module_bytes[1], 0, 0, 0],
}
let bytes = if module_bytes.len() == 2 {
[module_bytes[0], module_bytes[1], 0, 0, 0]
} else if module_bytes.len() == 5 {
RawModuleError {
pallet_index: module_bytes[0],
error: [
module_bytes[1],
module_bytes[2],
module_bytes[3],
module_bytes[4],
],
}
[
module_bytes[0],
module_bytes[1],
module_bytes[2],
module_bytes[3],
module_bytes[4],
]
} else {
tracing::warn!("Can't decode error sp_runtime::DispatchError: bytes do not match known shapes");
// Return _all_ of the bytes; every "unknown" return should be consistent.
@@ -312,7 +302,7 @@ impl DispatchError {
};
// And return our outward-facing version:
DispatchError::Module(ModuleError { metadata, raw })
DispatchError::Module(ModuleError { metadata, bytes })
}
};
+1 -13
View File
@@ -13,7 +13,7 @@ pub use crate::client::LightClientError;
// Re-export dispatch error types:
pub use dispatch_error::{
ArithmeticError, DispatchError, ModuleError, RawModuleError, TokenError, TransactionalError,
ArithmeticError, DispatchError, ModuleError, TokenError, TransactionalError,
};
// Re-expose the errors we use from other crates here:
@@ -225,15 +225,3 @@ pub enum MetadataError {
#[error("The generated code is not compatible with the node")]
IncompatibleCodegen,
}
/// This trait is implemented on the statically generated root ModuleError type
#[doc(hidden)]
pub trait RootError: Sized {
/// Given details of the pallet error we want to decode
fn root_error(
// typically a [u8; 4] encodes the error of a pallet
pallet_bytes: &[u8],
pallet_name: &str,
metadata: &Metadata,
) -> Result<Self, Error>;
}
+32 -53
View File
@@ -14,6 +14,7 @@ use crate::{
};
use codec::{Compact, Decode};
use derivative::Derivative;
use scale_decode::DecodeAsType;
use std::sync::Arc;
/// A collection of events obtained from a block, bundled with the necessary
@@ -386,20 +387,16 @@ 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: RootEvent>(&self) -> Result<E, Error> {
let ev_metadata = self.event_metadata();
let pallet_bytes = &self.all_bytes[self.event_start_idx + 1..self.event_fields_end_idx];
let pallet_event_ty = ev_metadata
.pallet
.event_ty_id()
.ok_or_else(|| MetadataError::EventTypeNotFoundInPallet(ev_metadata.pallet.index()))?;
pub fn as_root_event<E: DecodeAsType>(&self) -> Result<E, Error> {
let bytes = &self.all_bytes[self.event_start_idx..self.event_fields_end_idx];
E::root_event(
pallet_bytes,
self.pallet_name(),
pallet_event_ty,
&self.metadata,
)
let decoded = E::decode_as_type(
&mut &bytes[..],
self.metadata.outer_enums().event_enum_ty(),
self.metadata.types(),
)?;
Ok(decoded)
}
/// Return the topics associated with this event.
@@ -414,32 +411,17 @@ pub struct EventMetadataDetails<'a> {
pub variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
}
/// This trait is implemented on the statically generated root event type, so that we're able
/// to decode it properly via a pallet event that impls `DecodeAsMetadata`. This is necessary
/// becasue the "root event" type is generated using pallet info but doesn't actually exist in the
/// metadata types, so we have no easy way to decode things into it via type information and need a
/// little help via codegen.
#[doc(hidden)]
pub trait RootEvent: Sized {
/// Given details of the pallet event we want to decode, and the name of the pallet, try to hand
/// back a "root event".
fn root_event(
pallet_bytes: &[u8],
pallet_name: &str,
pallet_event_ty: u32,
metadata: &Metadata,
) -> Result<Self, Error>;
}
/// Event related test utilities used outside this module.
#[cfg(test)]
pub(crate) mod test_utils {
use super::*;
use crate::metadata::DecodeWithMetadata;
use crate::{Config, SubstrateConfig};
use codec::Encode;
use frame_metadata::{
v15::{ExtrinsicMetadata, PalletEventMetadata, PalletMetadata, RuntimeMetadataV15},
v15::{
CustomMetadata, ExtrinsicMetadata, OuterEnums, PalletEventMetadata, PalletMetadata,
RuntimeMetadataV15,
},
RuntimeMetadataPrefixed,
};
use scale_info::{meta_type, TypeInfo};
@@ -460,25 +442,6 @@ pub(crate) mod test_utils {
Test(Ev),
}
// We need this in order to be able to decode into a root event type:
impl<Ev: DecodeWithMetadata> RootEvent for AllEvents<Ev> {
fn root_event(
mut bytes: &[u8],
pallet_name: &str,
pallet_event_ty: u32,
metadata: &Metadata,
) -> Result<Self, Error> {
if pallet_name == "Test" {
return Ok(AllEvents::Test(Ev::decode_with_metadata(
&mut bytes,
pallet_event_ty,
metadata,
)?));
}
panic!("Asked for pallet name '{pallet_name}', which isn't in our test AllEvents type")
}
}
/// This encodes to the same format an event is expected to encode to
/// in node System.Events storage.
#[derive(Encode)]
@@ -546,12 +509,28 @@ pub(crate) mod test_utils {
}];
let extrinsic = ExtrinsicMetadata {
ty: meta_type::<ExtrinsicType<RuntimeCall>>(),
version: 0,
signed_extensions: vec![],
address_ty: meta_type::<()>(),
call_ty: meta_type::<RuntimeCall>(),
signature_ty: meta_type::<()>(),
extra_ty: meta_type::<()>(),
};
let meta = RuntimeMetadataV15::new(pallets, extrinsic, meta_type::<()>(), vec![]);
let meta = RuntimeMetadataV15::new(
pallets,
extrinsic,
meta_type::<()>(),
vec![],
OuterEnums {
call_enum_ty: meta_type::<()>(),
event_enum_ty: meta_type::<AllEvents<E>>(),
error_enum_ty: meta_type::<()>(),
},
CustomMetadata {
map: Default::default(),
},
);
let runtime_metadata: RuntimeMetadataPrefixed = meta.into();
Metadata::new(runtime_metadata.try_into().unwrap())
+1 -6
View File
@@ -11,12 +11,7 @@ mod events_type;
use codec::{Decode, Encode};
pub use events_client::EventsClient;
pub use events_type::{
EventDetails,
Events,
// Used in codegen but hidden from docs:
RootEvent,
};
pub use events_type::{EventDetails, Events};
use scale_decode::DecodeAsFields;
/// Trait to uniquely identify the events's identity from the runtime metadata.
+9
View File
@@ -365,6 +365,15 @@ impl<T: Config> Rpc<T> {
Ok(res)
}
/// Provide a list of the supported metadata versions of the node.
pub async fn metadata_versions(&self) -> Result<Vec<u32>, Error> {
let versions = self
.state_call("Metadata_metadata_versions", None, None)
.await?;
Ok(versions)
}
/// Execute runtime API call and return the specified runtime metadata version.
pub async fn metadata_at_version(&self, version: u32) -> Result<Metadata, Error> {
let param = version.encode();
File diff suppressed because one or more lines are too long
@@ -269,12 +269,10 @@ async fn storage_total_issuance() {
async fn storage_balance_lock() -> Result<(), subxt::Error> {
let bob_signer = dev::bob();
let bob: AccountId32 = dev::bob().public_key().into();
let charlie: AccountId32 = dev::charlie().public_key().into();
let ctx = test_context().await;
let api = ctx.client();
let tx = node_runtime::tx().staking().bond(
charlie.into(),
100_000_000_000_000,
runtime_types::pallet_staking::RewardDestination::Stash,
);
@@ -22,10 +22,12 @@ struct ContractsTestContext {
type Hash = <SubstrateConfig as Config>::Hash;
type AccountId = <SubstrateConfig as Config>::AccountId;
/// A dummy contract which does nothing at all.
const CONTRACT: &str = r#"
(module
(func (export "call"))
(import "env" "memory" (memory 1 1))
(func (export "deploy"))
(func (export "call"))
)
"#;
@@ -35,39 +35,39 @@ fn default_validator_prefs() -> ValidatorPrefs {
}
#[tokio::test]
async fn validate_with_controller_account() {
let ctx = test_context().await;
let api = ctx.client();
let alice = dev::alice();
let tx = node_runtime::tx()
.staking()
.validate(default_validator_prefs());
api.tx()
.sign_and_submit_then_watch_default(&tx, &alice)
.await
.unwrap()
.wait_for_finalized_success()
.await
.expect("should be successful");
}
#[tokio::test]
async fn validate_not_possible_for_stash_account() -> Result<(), Error> {
async fn validate_with_stash_account() {
let ctx = test_context().await;
let api = ctx.client();
let alice_stash = get_from_seed("Alice//stash");
let tx = node_runtime::tx()
.staking()
.validate(default_validator_prefs());
api.tx()
.sign_and_submit_then_watch_default(&tx, &alice_stash)
.await
.unwrap()
.wait_for_finalized_success()
.await
.expect("should be successful");
}
#[tokio::test]
async fn validate_not_possible_for_controller_account() -> Result<(), Error> {
let ctx = test_context().await;
let api = ctx.client();
let alice = dev::alice();
let tx = node_runtime::tx()
.staking()
.validate(default_validator_prefs());
let announce_validator = api
.tx()
.sign_and_submit_then_watch_default(&tx, &alice_stash)
.sign_and_submit_then_watch_default(&tx, &alice)
.await?
.wait_for_finalized_success()
.await;
@@ -80,41 +80,41 @@ async fn validate_not_possible_for_stash_account() -> Result<(), Error> {
}
#[tokio::test]
async fn nominate_with_controller_account() {
let ctx = test_context().await;
let api = ctx.client();
let alice = dev::alice();
let bob = dev::bob();
let tx = node_runtime::tx()
.staking()
.nominate(vec![bob.public_key().to_address()]);
api.tx()
.sign_and_submit_then_watch_default(&tx, &alice)
.await
.unwrap()
.wait_for_finalized_success()
.await
.expect("should be successful");
}
#[tokio::test]
async fn nominate_not_possible_for_stash_account() -> Result<(), Error> {
async fn nominate_with_stash_account() {
let ctx = test_context().await;
let api = ctx.client();
let alice_stash = get_from_seed("Alice//stash");
let bob = dev::bob();
let tx = node_runtime::tx()
.staking()
.nominate(vec![bob.public_key().to_address()]);
api.tx()
.sign_and_submit_then_watch_default(&tx, &alice_stash)
.await
.unwrap()
.wait_for_finalized_success()
.await
.expect("should be successful");
}
#[tokio::test]
async fn nominate_not_possible_for_controller_account() -> Result<(), Error> {
let ctx = test_context().await;
let api = ctx.client();
let alice = dev::alice();
let bob = dev::bob();
let tx = node_runtime::tx()
.staking()
.nominate(vec![bob.public_key().to_address()]);
let nomination = api
.tx()
.sign_and_submit_then_watch_default(&tx, &alice_stash)
.sign_and_submit_then_watch_default(&tx, &alice)
.await
.unwrap()
.wait_for_finalized_success()
@@ -129,7 +129,7 @@ async fn nominate_not_possible_for_stash_account() -> Result<(), Error> {
}
#[tokio::test]
async fn chill_works_for_controller_only() -> Result<(), Error> {
async fn chill_works_for_stash_only() -> Result<(), Error> {
let ctx = test_context().await;
let api = ctx.client();
@@ -142,14 +142,14 @@ async fn chill_works_for_controller_only() -> Result<(), Error> {
.staking()
.nominate(vec![bob_stash.public_key().to_address()]);
api.tx()
.sign_and_submit_then_watch_default(&nominate_tx, &alice)
.sign_and_submit_then_watch_default(&nominate_tx, &alice_stash)
.await?
.wait_for_finalized_success()
.await?;
let ledger_addr = node_runtime::storage()
.staking()
.ledger(alice.public_key().to_account_id());
.ledger(alice_stash.public_key().to_account_id());
let ledger = api
.storage()
.at_latest()
@@ -163,7 +163,7 @@ async fn chill_works_for_controller_only() -> Result<(), Error> {
let chill = api
.tx()
.sign_and_submit_then_watch_default(&chill_tx, &alice_stash)
.sign_and_submit_then_watch_default(&chill_tx, &alice)
.await?
.wait_for_finalized_success()
.await;
@@ -176,7 +176,7 @@ async fn chill_works_for_controller_only() -> Result<(), Error> {
let is_chilled = api
.tx()
.sign_and_submit_then_watch_default(&chill_tx, &alice)
.sign_and_submit_then_watch_default(&chill_tx, &alice_stash)
.await?
.wait_for_finalized_success()
.await?
@@ -193,11 +193,9 @@ async fn tx_bond() -> Result<(), Error> {
let alice = dev::alice();
let bond_tx = node_runtime::tx().staking().bond(
dev::bob().public_key().into(),
100_000_000_000_000,
RewardDestination::Stash,
);
let bond_tx = node_runtime::tx()
.staking()
.bond(100_000_000_000_000, RewardDestination::Stash);
let bond = api
.tx()
@@ -4,8 +4,9 @@
use crate::{node_runtime, test_context, TestContext};
use frame_metadata::v15::{
ExtrinsicMetadata, PalletCallMetadata, PalletMetadata, PalletStorageMetadata,
RuntimeMetadataV15, StorageEntryMetadata, StorageEntryModifier, StorageEntryType,
CustomMetadata, ExtrinsicMetadata, OuterEnums, PalletCallMetadata, PalletMetadata,
PalletStorageMetadata, RuntimeMetadataV15, StorageEntryMetadata, StorageEntryModifier,
StorageEntryType,
};
use scale_info::{
build::{Fields, Variants},
@@ -76,12 +77,23 @@ fn pallets_to_metadata(pallets: Vec<PalletMetadata>) -> Metadata {
v15_to_metadata(RuntimeMetadataV15::new(
pallets,
ExtrinsicMetadata {
ty: meta_type::<ExtrinsicType<RuntimeCall>>(),
version: 0,
signed_extensions: vec![],
address_ty: meta_type::<()>(),
call_ty: meta_type::<RuntimeCall>(),
signature_ty: meta_type::<()>(),
extra_ty: meta_type::<()>(),
},
meta_type::<()>(),
vec![],
OuterEnums {
call_enum_ty: meta_type::<()>(),
event_enum_ty: meta_type::<()>(),
error_enum_ty: meta_type::<()>(),
},
CustomMetadata {
map: Default::default(),
},
))
}
@@ -114,7 +126,7 @@ async fn constant_values_are_not_validated() {
// Modify the metadata.
let metadata = modified_metadata(api.metadata(), |md| {
let mut existential = md
let existential = md
.pallets
.iter_mut()
.find(|pallet| pallet.name == "Balances")
+1 -1
View File
@@ -37,7 +37,7 @@ async fn run() {
// Download metadata from binary. Avoid Subxt dep on `subxt::rpc::types::Bytes`and just impl here.
// This may at least prevent this script from running so often (ie whenever we change Subxt).
const V15_METADATA_VERSION: u32 = u32::MAX;
const V15_METADATA_VERSION: u32 = 15;
let bytes = V15_METADATA_VERSION.encode();
let version: String = format!("0x{}", hex::encode(&bytes));
let raw: String = {
+19 -5
View File
@@ -7,8 +7,8 @@ mod metadata_test_runner;
use frame_metadata::{
v15::{
ExtrinsicMetadata, PalletMetadata, PalletStorageMetadata, RuntimeMetadataV15,
StorageEntryMetadata,
CustomMetadata, ExtrinsicMetadata, OuterEnums, PalletMetadata, PalletStorageMetadata,
RuntimeMetadataV15, StorageEntryMetadata,
},
RuntimeMetadataPrefixed,
};
@@ -24,9 +24,12 @@ pub fn generate_metadata_from_pallets_custom_dispatch_error<DispatchError: TypeI
) -> RuntimeMetadataPrefixed {
// We don't care about the extrinsic type.
let extrinsic = ExtrinsicMetadata {
ty: meta_type::<()>(),
version: 0,
signed_extensions: vec![],
address_ty: meta_type::<()>(),
call_ty: meta_type::<()>(),
signature_ty: meta_type::<()>(),
extra_ty: meta_type::<()>(),
};
// Construct metadata manually from our types (See `RuntimeMetadataV15::new()`).
@@ -41,10 +44,13 @@ pub fn generate_metadata_from_pallets_custom_dispatch_error<DispatchError: TypeI
enum RuntimeCall {}
#[derive(TypeInfo)]
enum RuntimeEvent {}
#[derive(TypeInfo)]
enum RuntimeError {}
let ty = registry.register_type(&meta_type::<Runtime>());
registry.register_type(&meta_type::<RuntimeCall>());
registry.register_type(&meta_type::<RuntimeEvent>());
let runtime_call = registry.register_type(&meta_type::<RuntimeCall>());
let runtime_event = registry.register_type(&meta_type::<RuntimeEvent>());
let runtime_error = registry.register_type(&meta_type::<RuntimeError>());
// Metadata needs to contain this DispatchError, since codegen looks for it.
registry.register_type(&meta_type::<DispatchError>());
@@ -55,6 +61,14 @@ pub fn generate_metadata_from_pallets_custom_dispatch_error<DispatchError: TypeI
extrinsic,
ty,
apis: vec![],
outer_enums: OuterEnums {
call_enum_ty: runtime_call,
event_enum_ty: runtime_event,
error_enum_ty: runtime_error,
},
custom: CustomMetadata {
map: Default::default(),
},
};
RuntimeMetadataPrefixed::from(metadata)