From bc95a7397c6beecc52e171d02bfee9b121820ae1 Mon Sep 17 00:00:00 2001 From: Tadeo hepperle Date: Mon, 25 Sep 2023 16:48:48 +0200 Subject: [PATCH 1/5] add web feature to subxt codegen --- codegen/Cargo.toml | 1 + macro/Cargo.toml | 3 +++ subxt/Cargo.toml | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index a336f7fa7c..47aee80003 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -15,6 +15,7 @@ description = "Generate an API for interacting with a substrate node from FRAME [features] default = ["fetch-metadata"] fetch-metadata = ["dep:jsonrpsee", "dep:tokio", "dep:frame-metadata"] +web = ["jsonrpsee?/async-wasm-client", "jsonrpsee?/client-web-transport", "getrandom/js"] [dependencies] codec = { package = "parity-scale-codec", workspace = true, features = ["derive"] } diff --git a/macro/Cargo.toml b/macro/Cargo.toml index eeafea2872..2845e0f3c0 100644 --- a/macro/Cargo.toml +++ b/macro/Cargo.toml @@ -13,6 +13,9 @@ documentation.workspace = true homepage.workspace = true description = "Generate types and helpers for interacting with Substrate runtimes." +[features] +web = ["subxt-codegen/web"] + [lib] proc-macro = true diff --git a/subxt/Cargo.toml b/subxt/Cargo.toml index ac64287cc5..35fd8e634c 100644 --- a/subxt/Cargo.toml +++ b/subxt/Cargo.toml @@ -29,7 +29,7 @@ native = [ # Enable this for web/wasm builds. # Exactly 1 of "web" and "native" is expected. -web = ["jsonrpsee?/async-wasm-client", "jsonrpsee?/client-web-transport", "getrandom/js", "subxt-lightclient?/web"] +web = ["jsonrpsee?/async-wasm-client", "jsonrpsee?/client-web-transport", "getrandom/js", "subxt-lightclient?/web", "subxt-macro/web"] # Enable this to use jsonrpsee (allowing for example `OnlineClient::from_url`). jsonrpsee = ["dep:jsonrpsee"] From 7cfa7d3a7d75e0fe11c6c86f31025bf583fa9659 Mon Sep 17 00:00:00 2001 From: Tadeo hepperle Date: Mon, 25 Sep 2023 16:57:25 +0200 Subject: [PATCH 2/5] add getrandom --- Cargo.lock | 1 + codegen/Cargo.toml | 3 +++ 2 files changed, 4 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index d92e45e0ff..588f46425a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4255,6 +4255,7 @@ version = "0.31.0" dependencies = [ "bitvec", "frame-metadata 16.0.0", + "getrandom 0.2.10", "heck", "hex", "jsonrpsee", diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 47aee80003..a48b3d546d 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -31,6 +31,9 @@ hex = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread"], optional = true } thiserror = { workspace = true } +# Included if "web" feature is enabled, to enable its js feature. +getrandom = { workspace = true, optional = true } + [dev-dependencies] bitvec = { workspace = true } scale-info = { workspace = true, features = ["bit-vec"] } From 6e00e197f97a4da990e6c9629f7b380553d111a4 Mon Sep 17 00:00:00 2001 From: Tadeo hepperle Date: Tue, 26 Sep 2023 09:48:54 +0200 Subject: [PATCH 3/5] remove compile error --- codegen/src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 5af5a244b9..2f89da2689 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -53,9 +53,6 @@ mod types; #[cfg(feature = "fetch-metadata")] pub mod utils; -#[cfg(all(feature = "web", feature = "fetch-metadata"))] -compile_error!("subxt-codegen: the features 'web' and 'fetch_metadata' cannot be used together."); - pub use self::{ api::{GenerateRuntimeApi, RuntimeGenerator}, error::{CodegenError, TypeSubstitutionError}, From 2ad8291e0b791a6138e2cfd9c84b528e0188bbd6 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Mon, 25 Sep 2023 18:37:38 +0300 Subject: [PATCH 4/5] metadata: Generate runtime outer enums if not present in V14 (#1174) * metadata: Extend outer enum generation for V14 Signed-off-by: Alexandru Vasile * metadata: Generate outer enums if not present Signed-off-by: Alexandru Vasile * metadata: Porpagate v14 error instead of panic Signed-off-by: Alexandru Vasile * metadata: Try to find `RuntimeCall` then `Call` enums Signed-off-by: Alexandru Vasile * metadata: Ensure the returned type is variant for outer enums Signed-off-by: Alexandru Vasile * metadata: Replace or with or_else Signed-off-by: Alexandru Vasile * metadata: Apply clippy Signed-off-by: Alexandru Vasile * metadata: Return error and generate only `RuntimeError` Signed-off-by: Alexandru Vasile * metadata: Remove modified path Signed-off-by: Alexandru Vasile * metadata/tests: Check missing runtime types Signed-off-by: Alexandru Vasile --------- Signed-off-by: Alexandru Vasile --- metadata/src/from_into/mod.rs | 10 +- metadata/src/from_into/v14.rs | 249 +++++++++++++++++++++++++--------- 2 files changed, 196 insertions(+), 63 deletions(-) diff --git a/metadata/src/from_into/mod.rs b/metadata/src/from_into/mod.rs index a88ca56fc7..8170bc6133 100644 --- a/metadata/src/from_into/mod.rs +++ b/metadata/src/from_into/mod.rs @@ -7,11 +7,11 @@ mod v15; /// An error emitted if something goes wrong converting [`frame_metadata`] /// types into [`crate::Metadata`]. -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, PartialEq, Eq)] #[non_exhaustive] pub enum TryFromError { /// Type missing from type registry - #[error("Type {0} is expected but not found in the type registry")] + #[error("Type id {0} is expected but not found in the type registry")] TypeNotFound(u32), /// Type was not a variant/enum type #[error("Type {0} was not a variant/enum type, but is expected to be one")] @@ -19,6 +19,12 @@ pub enum TryFromError { /// An unsupported metadata version was provided. #[error("Cannot convert v{0} metadata into Metadata type")] UnsupportedMetadataVersion(u32), + /// Type name missing from type registry + #[error("Type name {0} is expected but not found in the type registry")] + TypeNameNotFound(String), + /// Invalid type path. + #[error("Type has an invalid path {0}")] + InvalidTypePath(String), } impl From for frame_metadata::RuntimeMetadataPrefixed { diff --git a/metadata/src/from_into/v14.rs b/metadata/src/from_into/v14.rs index 025b051ee8..7df5e95347 100644 --- a/metadata/src/from_into/v14.rs +++ b/metadata/src/from_into/v14.rs @@ -7,12 +7,13 @@ use std::collections::HashMap; use super::TryFromError; use crate::Metadata; use frame_metadata::{v14, v15}; +use scale_info::TypeDef; impl TryFrom for Metadata { type Error = TryFromError; fn try_from(value: v14::RuntimeMetadataV14) -> Result { // Convert to v15 and then convert that into Metadata. - v14_to_v15(value).try_into() + v14_to_v15(value)?.try_into() } } @@ -148,14 +149,15 @@ fn v15_to_v14(mut metadata: v15::RuntimeMetadataV15) -> v14::RuntimeMetadataV14 } } -fn v14_to_v15(mut metadata: v14::RuntimeMetadataV14) -> v15::RuntimeMetadataV15 { +fn v14_to_v15( + mut metadata: v14::RuntimeMetadataV14, +) -> Result { // Find the extrinsic types. - let extrinsic_parts = ExtrinsicPartTypeIds::new(&metadata) - .expect("Extrinsic types are always present on V14; qed"); + let extrinsic_parts = ExtrinsicPartTypeIds::new(&metadata)?; - let outer_enums = generate_outer_enums(&mut metadata); + let outer_enums = generate_outer_enums(&mut metadata)?; - v15::RuntimeMetadataV15 { + Ok(v15::RuntimeMetadataV15 { types: metadata.types, pallets: metadata .pallets @@ -245,7 +247,7 @@ fn v14_to_v15(mut metadata: v14::RuntimeMetadataV14) -> v15::RuntimeMetadataV15 custom: v15::CustomMetadata { map: Default::default(), }, - } + }) } /// The type IDs extracted from the metadata that represent the @@ -260,7 +262,7 @@ struct ExtrinsicPartTypeIds { impl ExtrinsicPartTypeIds { /// Extract the generic type parameters IDs from the extrinsic type. - fn new(metadata: &v14::RuntimeMetadataV14) -> Result { + fn new(metadata: &v14::RuntimeMetadataV14) -> Result { const ADDRESS: &str = "Address"; const CALL: &str = "Call"; const SIGNATURE: &str = "Signature"; @@ -268,7 +270,7 @@ impl ExtrinsicPartTypeIds { let extrinsic_id = metadata.extrinsic.ty.id; let Some(extrinsic_ty) = metadata.types.resolve(extrinsic_id) else { - return Err("Missing extrinsic type".into()); + return Err(TryFromError::TypeNotFound(extrinsic_id)); }; let params: HashMap<_, _> = extrinsic_ty @@ -276,7 +278,7 @@ impl ExtrinsicPartTypeIds { .iter() .map(|ty_param| { let Some(ty) = ty_param.ty else { - return Err("Missing type param type from extrinsic".to_string()); + return Err(TryFromError::TypeNameNotFound(ty_param.name.clone())); }; Ok((ty_param.name.as_str(), ty.id)) @@ -284,16 +286,16 @@ impl ExtrinsicPartTypeIds { .collect::>()?; let Some(address) = params.get(ADDRESS) else { - return Err("Missing address type from extrinsic".into()); + return Err(TryFromError::TypeNameNotFound(ADDRESS.into())); }; let Some(call) = params.get(CALL) else { - return Err("Missing call type from extrinsic".into()); + return Err(TryFromError::TypeNameNotFound(CALL.into())); }; let Some(signature) = params.get(SIGNATURE) else { - return Err("Missing signature type from extrinsic".into()); + return Err(TryFromError::TypeNameNotFound(SIGNATURE.into())); }; let Some(extra) = params.get(EXTRA) else { - return Err("Missing extra type from extrinsic".into()); + return Err(TryFromError::TypeNameNotFound(EXTRA.into())); }; Ok(ExtrinsicPartTypeIds { @@ -307,53 +309,54 @@ impl ExtrinsicPartTypeIds { fn generate_outer_enums( metadata: &mut v14::RuntimeMetadataV14, -) -> v15::OuterEnums { - let call_enum = metadata - .types - .types - .iter() - .find(|ty| { +) -> Result, TryFromError> { + let find_type = |name: &str| { + metadata.types.types.iter().find_map(|ty| { let Some(ident) = ty.ty.path.ident() else { - return false; + return None; }; - 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; + if ident != name { + return None; + } + + let TypeDef::Variant(_) = &ty.ty.type_def else { + return None; }; - ident == "RuntimeEvent" + + Some((ty.id, ty.ty.path.segments.clone())) }) - .expect("RuntimeEvent exists in V14; qed"); + }; - let call_ty = call_enum.id.into(); - let event_ty = event_enum.id.into(); + let Some((call_enum, mut call_path)) = find_type("RuntimeCall") else { + return Err(TryFromError::TypeNameNotFound("RuntimeCall".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 Some((event_enum, _)) = find_type("RuntimeEvent") else { + return Err(TryFromError::TypeNameNotFound("RuntimeEvent".into())); + }; - let error_ty_id = generate_runtime_error_type(metadata, path_segments); + let error_enum = if let Some((error_enum, _)) = find_type("RuntimeError") { + error_enum + } else { + let Some(last) = call_path.last_mut() else { + return Err(TryFromError::InvalidTypePath("RuntimeCall".into())); + }; + *last = "RuntimeError".to_string(); + generate_outer_error_enum_type(metadata, call_path) + }; - v15::OuterEnums { - call_enum_ty: call_ty, - event_enum_ty: event_ty, - error_enum_ty: error_ty_id.into(), - } + Ok(v15::OuterEnums { + call_enum_ty: call_enum.into(), + event_enum_ty: event_enum.into(), + error_enum_ty: error_enum.into(), + }) } -/// Generate the `RuntimeError` type and add it to the metadata. +/// Generates an outer `RuntimeError` enum type and adds it to the metadata. /// -/// Returns the `RuntimeError` Id from the registry. -fn generate_runtime_error_type( +/// Returns the id of the generated type from the registry. +fn generate_outer_error_enum_type( metadata: &mut v14::RuntimeMetadataV14, path_segments: Vec, ) -> u32 { @@ -361,16 +364,18 @@ fn generate_runtime_error_type( .pallets .iter() .filter_map(|pallet| { - let Some(pallet_error) = &pallet.error else { + let Some(error) = &pallet.error else { return None; }; + let path = format!("{}Error", pallet.name); + let ty = error.ty.id.into(); Some(scale_info::Variant { name: pallet.name.clone(), fields: vec![scale_info::Field { name: None, - ty: pallet_error.ty.id.into(), + ty, type_name: Some(path), docs: vec![], }], @@ -380,7 +385,7 @@ fn generate_runtime_error_type( }) .collect(); - let error_type = scale_info::Type { + let enum_type = scale_info::Type { path: scale_info::Path { segments: path_segments, }, @@ -389,23 +394,25 @@ fn generate_runtime_error_type( docs: vec![], }; - let error_type_id = metadata.types.types.len() as u32; + let enum_type_id = metadata.types.types.len() as u32; metadata.types.types.push(scale_info::PortableType { - id: error_type_id, - ty: error_type, + id: enum_type_id, + ty: enum_type, }); - error_type_id + enum_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}; + use frame_metadata::{ + v14::ExtrinsicMetadata, v15::RuntimeMetadataV15, RuntimeMetadata, RuntimeMetadataPrefixed, + }; + use scale_info::{meta_type, IntoPortable, TypeDef, TypeInfo}; + use std::{fs, marker::PhantomData, path::Path}; fn load_v15_metadata() -> RuntimeMetadataV15 { let bytes = fs::read(Path::new("../artifacts/polkadot_metadata_full.scale")) @@ -493,7 +500,7 @@ mod tests { 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 converted_v15 = v14_to_v15(v14).unwrap(); let v15_addr = v15.types.resolve(v15.extrinsic.address_ty.id).unwrap(); let converted_v15_addr = converted_v15 @@ -530,7 +537,7 @@ mod tests { 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); + let converted_v15 = v14_to_v15(v14).unwrap(); // RuntimeCall and RuntimeEvent were already present in the metadata v14. let v15_call = v15.types.resolve(v15.outer_enums.call_enum_ty.id).unwrap(); @@ -593,4 +600,124 @@ mod tests { } } } + + #[test] + fn test_missing_extrinsic_types() { + #[derive(TypeInfo)] + struct Runtime; + + let generate_metadata = |extrinsic_ty| { + let mut registry = scale_info::Registry::new(); + + let ty = registry.register_type(&meta_type::()); + + let extrinsic = ExtrinsicMetadata { + ty: extrinsic_ty, + version: 0, + signed_extensions: vec![], + } + .into_portable(&mut registry); + + v14::RuntimeMetadataV14 { + types: registry.into(), + pallets: Vec::new(), + extrinsic, + ty, + } + }; + + let metadata = generate_metadata(meta_type::<()>()); + let err = v14_to_v15(metadata).unwrap_err(); + assert_eq!(err, TryFromError::TypeNameNotFound("Address".into())); + + #[derive(TypeInfo)] + struct ExtrinsicNoCall { + _phantom: PhantomData<(Address, Signature, Extra)>, + } + let metadata = generate_metadata(meta_type::>()); + let err = v14_to_v15(metadata).unwrap_err(); + assert_eq!(err, TryFromError::TypeNameNotFound("Call".into())); + + #[derive(TypeInfo)] + struct ExtrinsicNoSign { + _phantom: PhantomData<(Call, Address, Extra)>, + } + let metadata = generate_metadata(meta_type::>()); + let err = v14_to_v15(metadata).unwrap_err(); + assert_eq!(err, TryFromError::TypeNameNotFound("Signature".into())); + + #[derive(TypeInfo)] + struct ExtrinsicNoExtra { + _phantom: PhantomData<(Call, Address, Signature)>, + } + let metadata = generate_metadata(meta_type::>()); + let err = v14_to_v15(metadata).unwrap_err(); + assert_eq!(err, TryFromError::TypeNameNotFound("Extra".into())); + } + + #[test] + fn test_missing_outer_enum_types() { + #[derive(TypeInfo)] + struct Runtime; + + #[derive(TypeInfo)] + enum RuntimeCall {} + #[derive(TypeInfo)] + enum RuntimeEvent {} + + #[allow(unused)] + #[derive(TypeInfo)] + struct ExtrinsicType { + pub signature: Option<(Address, Signature, Extra)>, + pub function: Call, + } + + // Missing runtime call. + { + let mut registry = scale_info::Registry::new(); + let ty = registry.register_type(&meta_type::()); + registry.register_type(&meta_type::()); + + let extrinsic = ExtrinsicMetadata { + ty: meta_type::>(), + version: 0, + signed_extensions: vec![], + } + .into_portable(&mut registry); + + let metadata = v14::RuntimeMetadataV14 { + types: registry.into(), + pallets: Vec::new(), + extrinsic, + ty, + }; + + let err = v14_to_v15(metadata).unwrap_err(); + assert_eq!(err, TryFromError::TypeNameNotFound("RuntimeCall".into())); + } + + // Missing runtime event. + { + let mut registry = scale_info::Registry::new(); + let ty = registry.register_type(&meta_type::()); + registry.register_type(&meta_type::()); + + let extrinsic = ExtrinsicMetadata { + ty: meta_type::>(), + version: 0, + signed_extensions: vec![], + } + .into_portable(&mut registry); + + let metadata = v14::RuntimeMetadataV14 { + types: registry.into(), + pallets: Vec::new(), + extrinsic, + ty, + }; + + let err = v14_to_v15(metadata).unwrap_err(); + assert_eq!(err, TryFromError::TypeNameNotFound("RuntimeEvent".into())); + } + } } From 05fe4de681f0720d005198e97d018e3be8a9bcea Mon Sep 17 00:00:00 2001 From: Tadeo hepperle Date: Tue, 26 Sep 2023 10:01:02 +0200 Subject: [PATCH 5/5] add empty use of getrandom --- codegen/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 2f89da2689..8171c2ee49 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -53,6 +53,9 @@ mod types; #[cfg(feature = "fetch-metadata")] pub mod utils; +#[cfg(feature = "web")] +use getrandom as _; + pub use self::{ api::{GenerateRuntimeApi, RuntimeGenerator}, error::{CodegenError, TypeSubstitutionError},