mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-22 04:28:00 +00:00
codegen: Generate type aliases for better API ergonomics (#1249)
* codegen: Generate type alias for storage return types
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* codegen: Generate type alias for call function arguments
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* testing: Update polkadot.rs code from commit 2e2a75ff81
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* codegen: Type aliases for runtime API parameters
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* codegen: Type alias for runtime apis output
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* storage: Change path of the aliased module
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* codegen: Adjust module indentation
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* codegen: Do not alias for api::runtime_types with unresolved generics
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* codegen: Fix and document runtime API alias generation
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* Update artifacts
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* Update cargo.lock file
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* codegen: Generate composite structs with alias unnamed fields
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* testing: Update polkadot.rs file
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* codegen: Alias storage unnamed parameters
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* Update polkadot.rs
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* examples: Change polkadot to rococo runtime
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* codegen: Fix compiling tests in the codegen crate
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* codegen: Extend storage test with alias module
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* cli/tests: Adjust exepcted commands to the latest metadata
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* codegen: Remove missleading comment and docs
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* codegen: Ensure unique names for generated runtime API types
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* codegen/tests: Test expected runtime type generation
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* codegen/tests: Check duplicate params in runtime APIs
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* codegen/tests: Test colliding names of type aliases and parameters
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* Fix clippy
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* codegen: Separate alias module from struct definition
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* Update polkadot.rs
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* codegen: Remove outdated docs from composite def
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
---------
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -180,7 +180,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 ParaInherent\n Staking\n System\n Timestamp\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 System\n Timestamp\n");
|
||||
// if incorrect pallet, error:
|
||||
let output = simulate_run("abc123").await;
|
||||
assert!(output.is_err());
|
||||
@@ -198,19 +198,20 @@ pub mod tests {
|
||||
let output = simulate_run("Balances abc123").await;
|
||||
assert!(output.is_err());
|
||||
// check that we can explore a certain call:
|
||||
let output = simulate_run("Balances calls transfer").await;
|
||||
assert!(output.unwrap().starts_with("Usage:\n subxt explore Balances calls transfer <SCALE_VALUE>\n construct the call by providing a valid argument\n\nThe call expect expects a <SCALE_VALUE> with this shape:\n {\n dest: enum MultiAddress"));
|
||||
let output = simulate_run("Balances calls transfer_allow_death").await;
|
||||
assert!(output.unwrap().starts_with("Usage:\n subxt explore Balances calls transfer_allow_death <SCALE_VALUE>\n construct the call by providing a valid argument\n\nThe call expect expects a <SCALE_VALUE> with this shape:\n {\n dest: enum MultiAddress"));
|
||||
// check that unsigned extrinsic can be constructed:
|
||||
let output =
|
||||
simulate_run("Balances calls transfer {\"dest\":v\"Raw\"((255,255, 255)),\"value\":0}")
|
||||
.await;
|
||||
let output = simulate_run(
|
||||
"Balances calls transfer_allow_death {\"dest\":v\"Raw\"((255,255, 255)),\"value\":0}",
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
output.unwrap(),
|
||||
"Encoded call data:\n 0x24040507020cffffff00\n"
|
||||
"Encoded call data:\n 0x24040400020cffffff00\n"
|
||||
);
|
||||
// check that we can explore a certain constant:
|
||||
let output = simulate_run("Balances constants ExistentialDeposit").await;
|
||||
assert_eq!(output.unwrap(), "Description:\n The minimum amount required to keep an account open. MUST BE GREATER THAN ZERO!\n\nThe constant has the following shape:\n u128\n\nThe value of the constant is:\n 10000000000\n");
|
||||
assert_eq!(output.unwrap(), "Description:\n The minimum amount required to keep an account open. MUST BE GREATER THAN ZERO!\n\nThe constant has the following shape:\n u128\n\nThe value of the constant is:\n 33333333\n");
|
||||
// check that we can explore a certain storage entry:
|
||||
let output = simulate_run("System storage Account").await;
|
||||
assert!(output.unwrap().starts_with("Usage:\n subxt explore System storage Account <KEY_VALUE>\n\nDescription:\n The full account information for a particular account ID."));
|
||||
|
||||
+2
-1
@@ -38,4 +38,5 @@ getrandom = { workspace = true, optional = true }
|
||||
[dev-dependencies]
|
||||
bitvec = { workspace = true }
|
||||
scale-info = { workspace = true, features = ["bit-vec"] }
|
||||
pretty_assertions = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
frame-metadata = { workspace = true }
|
||||
|
||||
+23
-10
@@ -32,34 +32,44 @@ pub fn generate_calls(
|
||||
|
||||
let mut struct_defs = super::generate_structs_from_variants(
|
||||
type_gen,
|
||||
types_mod_ident,
|
||||
call_ty,
|
||||
|name| name.to_upper_camel_case().into(),
|
||||
"Call",
|
||||
crate_path,
|
||||
should_gen_docs,
|
||||
)?;
|
||||
let (call_structs, call_fns): (Vec<_>, Vec<_>) = struct_defs
|
||||
|
||||
let result = struct_defs
|
||||
.iter_mut()
|
||||
.map(|(variant_name, struct_def)| {
|
||||
let (call_fn_args, call_args): (Vec<_>, Vec<_>) = match struct_def.fields {
|
||||
.map(|(variant_name, struct_def, aliases)| {
|
||||
let fn_name = format_ident!("{}", variant_name.to_snake_case());
|
||||
|
||||
let result: Vec<_> = match struct_def.fields {
|
||||
CompositeDefFields::Named(ref named_fields) => named_fields
|
||||
.iter()
|
||||
.map(|(name, field)| {
|
||||
let fn_arg_type = &field.type_path;
|
||||
let call_arg = if field.is_boxed() {
|
||||
quote! { #name: ::std::boxed::Box::new(#name) }
|
||||
} else {
|
||||
quote! { #name }
|
||||
};
|
||||
(quote!( #name: #fn_arg_type ), call_arg)
|
||||
|
||||
let alias_name =
|
||||
format_ident!("{}", name.to_string().to_upper_camel_case());
|
||||
|
||||
(quote!( #name: types::#fn_name::#alias_name ), call_arg)
|
||||
})
|
||||
.unzip(),
|
||||
.collect(),
|
||||
CompositeDefFields::NoFields => Default::default(),
|
||||
CompositeDefFields::Unnamed(_) => {
|
||||
return Err(CodegenError::InvalidCallVariant(call_ty))
|
||||
}
|
||||
};
|
||||
|
||||
let call_fn_args = result.iter().map(|(call_fn_arg, _)| call_fn_arg);
|
||||
let call_args = result.iter().map(|(_, call_arg)| call_arg);
|
||||
|
||||
let pallet_name = pallet.name();
|
||||
let call_name = &variant_name;
|
||||
let struct_name = &struct_def.name;
|
||||
@@ -69,7 +79,7 @@ pub fn generate_calls(
|
||||
call_name.to_string(),
|
||||
));
|
||||
};
|
||||
let fn_name = format_ident!("{}", variant_name.to_snake_case());
|
||||
|
||||
// Propagate the documentation just to `TransactionApi` methods, while
|
||||
// draining the documentation of inner call structures.
|
||||
let docs = should_gen_docs.then_some(struct_def.docs.take()).flatten();
|
||||
@@ -78,6 +88,8 @@ pub fn generate_calls(
|
||||
let call_struct = quote! {
|
||||
#struct_def
|
||||
|
||||
#aliases
|
||||
|
||||
impl #crate_path::blocks::StaticExtrinsic for #struct_name {
|
||||
const PALLET: &'static str = #pallet_name;
|
||||
const CALL: &'static str = #call_name;
|
||||
@@ -101,9 +113,10 @@ pub fn generate_calls(
|
||||
|
||||
Ok((call_struct, client_fn))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.into_iter()
|
||||
.unzip();
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let call_structs = result.iter().map(|(call_struct, _)| call_struct);
|
||||
let call_fns = result.iter().map(|(_, client_fn)| client_fn);
|
||||
|
||||
let call_type = type_gen.resolve_type_path(call_ty);
|
||||
let call_ty = type_gen.resolve_type(call_ty);
|
||||
|
||||
+16
-11
@@ -52,6 +52,7 @@ pub fn generate_events(
|
||||
|
||||
let struct_defs = super::generate_structs_from_variants(
|
||||
type_gen,
|
||||
types_mod_ident,
|
||||
event_ty,
|
||||
|name| name.into(),
|
||||
"Event",
|
||||
@@ -59,20 +60,24 @@ pub fn generate_events(
|
||||
should_gen_docs,
|
||||
)?;
|
||||
|
||||
let event_structs = struct_defs.iter().map(|(variant_name, struct_def)| {
|
||||
let pallet_name = pallet.name();
|
||||
let event_struct = &struct_def.name;
|
||||
let event_name = variant_name;
|
||||
let event_structs = struct_defs
|
||||
.iter()
|
||||
.map(|(variant_name, struct_def, aliases)| {
|
||||
let pallet_name = pallet.name();
|
||||
let event_struct = &struct_def.name;
|
||||
let event_name = variant_name;
|
||||
|
||||
quote! {
|
||||
#struct_def
|
||||
quote! {
|
||||
#struct_def
|
||||
|
||||
impl #crate_path::events::StaticEvent for #event_struct {
|
||||
const PALLET: &'static str = #pallet_name;
|
||||
const EVENT: &'static str = #event_name;
|
||||
#aliases
|
||||
|
||||
impl #crate_path::events::StaticEvent for #event_struct {
|
||||
const PALLET: &'static str = #pallet_name;
|
||||
const EVENT: &'static str = #event_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
let event_type = type_gen.resolve_type_path(event_ty);
|
||||
let event_ty = type_gen.resolve_type(event_ty);
|
||||
let docs = &event_ty.docs;
|
||||
|
||||
+60
-3
@@ -358,12 +358,13 @@ impl RuntimeGenerator {
|
||||
/// Return a vector of tuples of variant names and corresponding struct definitions.
|
||||
pub fn generate_structs_from_variants<F>(
|
||||
type_gen: &TypeGenerator,
|
||||
types_mod_ident: &syn::Ident,
|
||||
type_id: u32,
|
||||
variant_to_struct_name: F,
|
||||
error_message_type_name: &str,
|
||||
crate_path: &syn::Path,
|
||||
should_gen_docs: bool,
|
||||
) -> Result<Vec<(String, CompositeDef)>, CodegenError>
|
||||
) -> Result<Vec<(String, CompositeDef, TypeAliases)>, CodegenError>
|
||||
where
|
||||
F: Fn(&str) -> std::borrow::Cow<str>,
|
||||
{
|
||||
@@ -386,19 +387,75 @@ where
|
||||
type_gen,
|
||||
)?;
|
||||
|
||||
let alias_module_name = format_ident!("{}", var.name.to_snake_case());
|
||||
|
||||
let docs = should_gen_docs.then_some(&*var.docs).unwrap_or_default();
|
||||
let struct_def = CompositeDef::struct_def(
|
||||
&ty,
|
||||
struct_name.as_ref(),
|
||||
Default::default(),
|
||||
fields,
|
||||
fields.clone(),
|
||||
Some(parse_quote!(pub)),
|
||||
type_gen,
|
||||
docs,
|
||||
crate_path,
|
||||
Some(alias_module_name.clone()),
|
||||
)?;
|
||||
|
||||
Ok((var.name.to_string(), struct_def))
|
||||
let type_aliases = TypeAliases::new(fields, types_mod_ident.clone(), alias_module_name);
|
||||
|
||||
Ok((var.name.to_string(), struct_def, type_aliases))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Generate the type aliases from a set of enum / struct definitions.
|
||||
///
|
||||
/// The type aliases are used to make the generated code more readable.
|
||||
#[derive(Debug)]
|
||||
pub struct TypeAliases {
|
||||
fields: CompositeDefFields,
|
||||
types_mod_ident: syn::Ident,
|
||||
mod_name: syn::Ident,
|
||||
}
|
||||
|
||||
impl TypeAliases {
|
||||
pub fn new(
|
||||
fields: CompositeDefFields,
|
||||
types_mod_ident: syn::Ident,
|
||||
mod_name: syn::Ident,
|
||||
) -> Self {
|
||||
TypeAliases {
|
||||
fields,
|
||||
types_mod_ident,
|
||||
mod_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl quote::ToTokens for TypeAliases {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
let has_fields = matches!(&self.fields, CompositeDefFields::Named(fields) if !fields.is_empty())
|
||||
|| matches!(&self.fields, CompositeDefFields::Unnamed(fields) if !fields.is_empty());
|
||||
if !has_fields {
|
||||
return;
|
||||
}
|
||||
|
||||
let visibility: syn::Visibility = parse_quote!(pub);
|
||||
|
||||
let aliases = self
|
||||
.fields
|
||||
.to_type_aliases_tokens(Some(visibility).as_ref());
|
||||
|
||||
let mod_name = &self.mod_name;
|
||||
let types_mod_ident = &self.types_mod_ident;
|
||||
|
||||
tokens.extend(quote! {
|
||||
pub mod #mod_name {
|
||||
use super::#types_mod_ident;
|
||||
|
||||
#aliases
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+280
-14
@@ -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::HashSet;
|
||||
|
||||
use crate::{types::TypeGenerator, CodegenError};
|
||||
use heck::ToSnakeCase as _;
|
||||
use heck::ToUpperCamelCase as _;
|
||||
@@ -36,38 +38,80 @@ fn generate_runtime_api(
|
||||
.then_some(quote! { #( #[doc = #docs ] )* })
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut unique_names = HashSet::new();
|
||||
let mut unique_aliases = HashSet::new();
|
||||
|
||||
let inputs: Vec<_> = method.inputs().enumerate().map(|(idx, input)| {
|
||||
// These are method names, which can just be '_', but struct field names can't
|
||||
// just be an underscore, so fix any such names we find to work in structs.
|
||||
let name = if input.name == "_" {
|
||||
format_ident!("_{}", idx)
|
||||
} else {
|
||||
format_ident!("{}", &input.name)
|
||||
};
|
||||
let ty = type_gen.resolve_type_path(input.ty);
|
||||
|
||||
let param = quote!(#name: #ty);
|
||||
(param, name)
|
||||
let mut name = input.name.trim_start_matches('_').to_string();
|
||||
if name.is_empty() {
|
||||
name = format!("_{}", idx);
|
||||
}
|
||||
while !unique_names.insert(name.clone()) {
|
||||
// Name is already used, append the index until it is unique.
|
||||
name = format!("{}_param{}", name, idx);
|
||||
}
|
||||
|
||||
let mut alias = name.to_upper_camel_case();
|
||||
// Note: name is not empty.
|
||||
if alias.as_bytes()[0].is_ascii_digit() {
|
||||
alias = format!("Param{}", alias);
|
||||
}
|
||||
while !unique_aliases.insert(alias.clone()) {
|
||||
alias = format!("{}Param{}", alias, idx);
|
||||
}
|
||||
|
||||
let (alias_name, name) = (format_ident!("{alias}"), format_ident!("{name}"));
|
||||
|
||||
// Generate alias for runtime type.
|
||||
let ty = type_gen.resolve_type_path(input.ty);
|
||||
let aliased_param = quote!( pub type #alias_name = #ty; );
|
||||
|
||||
// Structures are placed on the same level as the alias module.
|
||||
let struct_ty_path = quote!( #method_name::#alias_name );
|
||||
let struct_param = quote!(#name: #struct_ty_path);
|
||||
|
||||
// Function parameters must be indented by `types`.
|
||||
let fn_param = quote!(#name: types::#struct_ty_path);
|
||||
(fn_param, struct_param, name, aliased_param)
|
||||
}).collect();
|
||||
|
||||
let params = inputs.iter().map(|(param, _)| param);
|
||||
let param_names = inputs.iter().map(|(_, name)| name);
|
||||
let fn_params = inputs.iter().map(|(fn_param, _, _, _)| fn_param);
|
||||
let struct_params = inputs.iter().map(|(_, struct_param, _, _)| struct_param);
|
||||
let param_names = inputs.iter().map(|(_, _, name, _,)| name);
|
||||
let type_aliases = inputs.iter().map(|(_, _, _, aliased_param)| aliased_param);
|
||||
|
||||
let output = type_gen.resolve_type_path(method.output_ty());
|
||||
let aliased_module = quote!(
|
||||
pub mod #method_name {
|
||||
use super::#types_mod_ident;
|
||||
|
||||
#( #type_aliases )*
|
||||
|
||||
// Guard the `Output` name against collisions by placing it in a dedicated module.
|
||||
pub mod output {
|
||||
use super::#types_mod_ident;
|
||||
pub type Output = #output;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// From the method metadata generate a structure that holds
|
||||
// all parameter types. This structure is used with metadata
|
||||
// to encode parameters to the call via `encode_as_fields_to`.
|
||||
let derives = type_gen.default_derives();
|
||||
let struct_name = format_ident!("{}", method.name().to_upper_camel_case());
|
||||
let struct_params = params.clone();
|
||||
let struct_input = quote!(
|
||||
#aliased_module
|
||||
|
||||
#derives
|
||||
pub struct #struct_name {
|
||||
#( pub #struct_params, )*
|
||||
}
|
||||
);
|
||||
|
||||
let output = type_gen.resolve_type_path(method.output_ty());
|
||||
|
||||
let Some(call_hash) = api.method_hash(method.name()) else {
|
||||
return Err(CodegenError::MissingRuntimeApiMetadata(
|
||||
trait_name_str.to_owned(),
|
||||
@@ -77,7 +121,7 @@ fn generate_runtime_api(
|
||||
|
||||
let method = quote!(
|
||||
#docs
|
||||
pub fn #method_name(&self, #( #params, )* ) -> #crate_path::runtime_api::Payload<types::#struct_name, #output> {
|
||||
pub fn #method_name(&self, #( #fn_params, )* ) -> #crate_path::runtime_api::Payload<types::#struct_name, types::#method_name::output::Output> {
|
||||
#crate_path::runtime_api::Payload::new_static(
|
||||
#trait_name_str,
|
||||
#method_name_str,
|
||||
@@ -160,3 +204,225 @@ pub fn generate_runtime_apis(
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::RuntimeGenerator;
|
||||
use frame_metadata::v15::{
|
||||
self, RuntimeApiMetadata, RuntimeApiMethodMetadata, RuntimeApiMethodParamMetadata,
|
||||
};
|
||||
use quote::quote;
|
||||
use scale_info::meta_type;
|
||||
use subxt_metadata::Metadata;
|
||||
|
||||
fn metadata_with_runtime_apis(runtime_apis: Vec<RuntimeApiMetadata>) -> Metadata {
|
||||
let extrinsic_metadata = v15::ExtrinsicMetadata {
|
||||
version: 0,
|
||||
signed_extensions: vec![],
|
||||
address_ty: meta_type::<()>(),
|
||||
call_ty: meta_type::<()>(),
|
||||
signature_ty: meta_type::<()>(),
|
||||
extra_ty: meta_type::<()>(),
|
||||
};
|
||||
|
||||
let metadata: Metadata = v15::RuntimeMetadataV15::new(
|
||||
vec![],
|
||||
extrinsic_metadata,
|
||||
meta_type::<()>(),
|
||||
runtime_apis,
|
||||
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");
|
||||
metadata
|
||||
}
|
||||
|
||||
fn generate_code(runtime_apis: Vec<RuntimeApiMetadata>) -> String {
|
||||
let metadata = metadata_with_runtime_apis(runtime_apis);
|
||||
let item_mod = syn::parse_quote!(
|
||||
pub mod api {}
|
||||
);
|
||||
let generator = RuntimeGenerator::new(metadata);
|
||||
let generated = generator
|
||||
.generate_runtime(
|
||||
item_mod,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
syn::parse_str("::subxt_path").unwrap(),
|
||||
false,
|
||||
)
|
||||
.expect("should be able to generate runtime");
|
||||
generated.to_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unique_param_names() {
|
||||
let runtime_apis = vec![RuntimeApiMetadata {
|
||||
name: "Test",
|
||||
methods: vec![RuntimeApiMethodMetadata {
|
||||
name: "test",
|
||||
inputs: vec![
|
||||
RuntimeApiMethodParamMetadata {
|
||||
name: "foo",
|
||||
ty: meta_type::<bool>(),
|
||||
},
|
||||
RuntimeApiMethodParamMetadata {
|
||||
name: "bar",
|
||||
ty: meta_type::<bool>(),
|
||||
},
|
||||
],
|
||||
output: meta_type::<bool>(),
|
||||
docs: vec![],
|
||||
}],
|
||||
|
||||
docs: vec![],
|
||||
}];
|
||||
|
||||
let code = generate_code(runtime_apis);
|
||||
|
||||
let structure = quote! {
|
||||
pub struct Test {
|
||||
pub foo: test::Foo,
|
||||
pub bar: test::Bar,
|
||||
}
|
||||
};
|
||||
let expected_alias = quote!(
|
||||
pub mod test {
|
||||
use super::runtime_types;
|
||||
pub type Foo = ::core::primitive::bool;
|
||||
pub type Bar = ::core::primitive::bool;
|
||||
pub mod output {
|
||||
use super::runtime_types;
|
||||
pub type Output = ::core::primitive::bool;
|
||||
}
|
||||
}
|
||||
);
|
||||
assert!(code.contains(&structure.to_string()));
|
||||
assert!(code.contains(&expected_alias.to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_param_names() {
|
||||
let runtime_apis = vec![RuntimeApiMetadata {
|
||||
name: "Test",
|
||||
methods: vec![RuntimeApiMethodMetadata {
|
||||
name: "test",
|
||||
inputs: vec![
|
||||
RuntimeApiMethodParamMetadata {
|
||||
name: "_a",
|
||||
ty: meta_type::<bool>(),
|
||||
},
|
||||
RuntimeApiMethodParamMetadata {
|
||||
name: "a",
|
||||
ty: meta_type::<bool>(),
|
||||
},
|
||||
RuntimeApiMethodParamMetadata {
|
||||
name: "__a",
|
||||
ty: meta_type::<bool>(),
|
||||
},
|
||||
],
|
||||
output: meta_type::<bool>(),
|
||||
docs: vec![],
|
||||
}],
|
||||
|
||||
docs: vec![],
|
||||
}];
|
||||
|
||||
let code = generate_code(runtime_apis);
|
||||
|
||||
let structure = quote! {
|
||||
pub struct Test {
|
||||
pub a: test::A,
|
||||
pub a_param1: test::AParam1,
|
||||
pub a_param2: test::AParam2,
|
||||
}
|
||||
};
|
||||
let expected_alias = quote!(
|
||||
pub mod test {
|
||||
use super::runtime_types;
|
||||
pub type A = ::core::primitive::bool;
|
||||
pub type AParam1 = ::core::primitive::bool;
|
||||
pub type AParam2 = ::core::primitive::bool;
|
||||
pub mod output {
|
||||
use super::runtime_types;
|
||||
pub type Output = ::core::primitive::bool;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
assert!(code.contains(&structure.to_string()));
|
||||
assert!(code.contains(&expected_alias.to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_param_and_alias_names() {
|
||||
let runtime_apis = vec![RuntimeApiMetadata {
|
||||
name: "Test",
|
||||
methods: vec![RuntimeApiMethodMetadata {
|
||||
name: "test",
|
||||
inputs: vec![
|
||||
RuntimeApiMethodParamMetadata {
|
||||
name: "_",
|
||||
ty: meta_type::<bool>(),
|
||||
},
|
||||
RuntimeApiMethodParamMetadata {
|
||||
name: "_a",
|
||||
ty: meta_type::<bool>(),
|
||||
},
|
||||
RuntimeApiMethodParamMetadata {
|
||||
name: "_param_0",
|
||||
ty: meta_type::<bool>(),
|
||||
},
|
||||
RuntimeApiMethodParamMetadata {
|
||||
name: "__",
|
||||
ty: meta_type::<bool>(),
|
||||
},
|
||||
RuntimeApiMethodParamMetadata {
|
||||
name: "___param_0_param_2",
|
||||
ty: meta_type::<bool>(),
|
||||
},
|
||||
],
|
||||
output: meta_type::<bool>(),
|
||||
docs: vec![],
|
||||
}],
|
||||
|
||||
docs: vec![],
|
||||
}];
|
||||
|
||||
let code = generate_code(runtime_apis);
|
||||
|
||||
let structure = quote! {
|
||||
pub struct Test {
|
||||
pub _0: test::Param0,
|
||||
pub a: test::A,
|
||||
pub param_0: test::Param0Param2,
|
||||
pub _3: test::Param3,
|
||||
pub param_0_param_2: test::Param0Param2Param4,
|
||||
}
|
||||
};
|
||||
let expected_alias = quote!(
|
||||
pub mod test {
|
||||
use super::runtime_types;
|
||||
pub type Param0 = ::core::primitive::bool;
|
||||
pub type A = ::core::primitive::bool;
|
||||
pub type Param0Param2 = ::core::primitive::bool;
|
||||
pub type Param3 = ::core::primitive::bool;
|
||||
pub type Param0Param2Param4 = ::core::primitive::bool;
|
||||
pub mod output {
|
||||
use super::runtime_types;
|
||||
pub type Output = ::core::primitive::bool;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
assert!(code.contains(&structure.to_string()));
|
||||
assert!(code.contains(&expected_alias.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
+82
-26
@@ -5,6 +5,7 @@
|
||||
use crate::types::TypeGenerator;
|
||||
use crate::types::TypePath;
|
||||
use heck::ToSnakeCase as _;
|
||||
use heck::ToUpperCamelCase as _;
|
||||
use proc_macro2::{Ident, TokenStream as TokenStream2, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
use scale_info::TypeDef;
|
||||
@@ -34,18 +35,33 @@ pub fn generate_storage(
|
||||
return Ok(quote!());
|
||||
};
|
||||
|
||||
let storage_fns = storage
|
||||
let (storage_fns, alias_modules): (Vec<_>, Vec<_>) = storage
|
||||
.entries()
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
generate_storage_entry_fns(type_gen, pallet, entry, crate_path, should_gen_docs)
|
||||
generate_storage_entry_fns(
|
||||
type_gen,
|
||||
pallet,
|
||||
entry,
|
||||
crate_path,
|
||||
should_gen_docs,
|
||||
types_mod_ident,
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<_>, CodegenError>>()?;
|
||||
.collect::<Result<Vec<_>, CodegenError>>()?
|
||||
.into_iter()
|
||||
.unzip();
|
||||
|
||||
Ok(quote! {
|
||||
pub mod storage {
|
||||
use super::#types_mod_ident;
|
||||
|
||||
pub mod types {
|
||||
use super::#types_mod_ident;
|
||||
|
||||
#( #alias_modules )*
|
||||
}
|
||||
|
||||
pub struct StorageApi;
|
||||
|
||||
impl StorageApi {
|
||||
@@ -61,8 +77,30 @@ fn generate_storage_entry_fns(
|
||||
storage_entry: &StorageEntryMetadata,
|
||||
crate_path: &syn::Path,
|
||||
should_gen_docs: bool,
|
||||
) -> Result<TokenStream2, CodegenError> {
|
||||
let keys: Vec<(Ident, TypePath)> = match storage_entry.entry_type() {
|
||||
types_mod_ident: &syn::Ident,
|
||||
) -> Result<(TokenStream2, TokenStream2), CodegenError> {
|
||||
let snake_case_name = storage_entry.name().to_snake_case();
|
||||
let storage_entry_ty = storage_entry.entry_type().value_ty();
|
||||
let storage_entry_value_ty = type_gen.resolve_type_path(storage_entry_ty);
|
||||
|
||||
let alias_name = format_ident!("{}", storage_entry.name().to_upper_camel_case());
|
||||
let alias_module_name = format_ident!("{snake_case_name}");
|
||||
let alias_storage_path = quote!( types::#alias_module_name::#alias_name );
|
||||
|
||||
let storage_entry_map = |idx, id| {
|
||||
let ident: Ident = format_ident!("_{}", idx);
|
||||
let ty_path = type_gen.resolve_type_path(id);
|
||||
|
||||
let alias_name = format_ident!("Param{}", idx);
|
||||
let alias_type = primitive_type_alias(&ty_path);
|
||||
|
||||
let alias_type = quote!( pub type #alias_name = #alias_type; );
|
||||
let path_to_alias = quote!( types::#alias_module_name::#alias_name );
|
||||
|
||||
(ident, alias_type, path_to_alias)
|
||||
};
|
||||
|
||||
let keys: Vec<(Ident, TokenStream, TokenStream)> = match storage_entry.entry_type() {
|
||||
StorageEntryType::Plain(_) => vec![],
|
||||
StorageEntryType::Map { key_ty, .. } => {
|
||||
match &type_gen.resolve_type(*key_ty).type_def {
|
||||
@@ -71,17 +109,11 @@ fn generate_storage_entry_fns(
|
||||
.fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, f)| {
|
||||
let ident: Ident = format_ident!("_{}", syn::Index::from(i));
|
||||
let ty_path = type_gen.resolve_type_path(f.id);
|
||||
(ident, ty_path)
|
||||
})
|
||||
.map(|(idx, f)| storage_entry_map(idx, f.id))
|
||||
.collect::<Vec<_>>(),
|
||||
// A map with a single key; return the single key.
|
||||
_ => {
|
||||
let ident = format_ident!("_0");
|
||||
let ty_path = type_gen.resolve_type_path(*key_ty);
|
||||
vec![(ident, ty_path)]
|
||||
vec![storage_entry_map(0, *key_ty)]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,9 +127,6 @@ fn generate_storage_entry_fns(
|
||||
));
|
||||
};
|
||||
|
||||
let snake_case_name = storage_entry.name().to_snake_case();
|
||||
let storage_entry_ty = storage_entry.entry_type().value_ty();
|
||||
let storage_entry_value_ty = type_gen.resolve_type_path(storage_entry_ty);
|
||||
let docs = storage_entry.docs();
|
||||
let docs = should_gen_docs
|
||||
.then_some(quote! { #( #[doc = #docs ] )* })
|
||||
@@ -123,10 +152,9 @@ fn generate_storage_entry_fns(
|
||||
};
|
||||
let is_fetchable_type = is_fetchable.then_some(quote!(#crate_path::storage::address::Yes)).unwrap_or(quote!(()));
|
||||
let is_iterable_type = is_iterable.then_some(quote!(#crate_path::storage::address::Yes)).unwrap_or(quote!(()));
|
||||
let key_impls = keys_slice.iter().map(|(field_name, _)| quote!( #crate_path::storage::address::make_static_storage_map_key(#field_name.borrow()) ));
|
||||
let key_args = keys_slice.iter().map(|(field_name, field_type)| {
|
||||
let field_ty = primitive_type_alias(field_type);
|
||||
quote!( #field_name: impl ::std::borrow::Borrow<#field_ty> )
|
||||
let key_impls = keys_slice.iter().map(|(field_name, _, _)| quote!( #crate_path::storage::address::make_static_storage_map_key(#field_name.borrow()) ));
|
||||
let key_args = keys_slice.iter().map(|(field_name, _, path_to_alias )| {
|
||||
quote!( #field_name: impl ::std::borrow::Borrow<#path_to_alias> )
|
||||
});
|
||||
|
||||
quote!(
|
||||
@@ -136,7 +164,7 @@ fn generate_storage_entry_fns(
|
||||
#(#key_args,)*
|
||||
) -> #crate_path::storage::address::Address::<
|
||||
#crate_path::storage::address::StaticStorageMapKey,
|
||||
#storage_entry_value_ty,
|
||||
#alias_storage_path,
|
||||
#is_fetchable_type,
|
||||
#is_defaultable_type,
|
||||
#is_iterable_type
|
||||
@@ -151,11 +179,26 @@ fn generate_storage_entry_fns(
|
||||
)
|
||||
});
|
||||
|
||||
Ok(quote! {
|
||||
#( #all_fns
|
||||
let alias_types = keys.iter().map(|(_, alias_type, _)| alias_type);
|
||||
|
||||
)*
|
||||
})
|
||||
// Generate type alias for the return type only, since
|
||||
// the keys of the storage entry are not explicitly named.
|
||||
let alias_module = quote! {
|
||||
pub mod #alias_module_name {
|
||||
use super::#types_mod_ident;
|
||||
|
||||
pub type #alias_name = #storage_entry_value_ty;
|
||||
|
||||
#( #alias_types )*
|
||||
}
|
||||
};
|
||||
|
||||
Ok((
|
||||
quote! {
|
||||
#( #all_fns )*
|
||||
},
|
||||
alias_module,
|
||||
))
|
||||
}
|
||||
|
||||
fn primitive_type_alias(type_path: &TypePath) -> TokenStream {
|
||||
@@ -174,6 +217,7 @@ fn primitive_type_alias(type_path: &TypePath) -> TokenStream {
|
||||
mod tests {
|
||||
use crate::RuntimeGenerator;
|
||||
use frame_metadata::v15;
|
||||
use heck::ToUpperCamelCase as _;
|
||||
use quote::{format_ident, quote};
|
||||
use scale_info::{meta_type, MetaType};
|
||||
use std::borrow::Cow;
|
||||
@@ -283,10 +327,22 @@ mod tests {
|
||||
let expected_storage_constructor = quote!(
|
||||
fn #name_ident(
|
||||
&self,
|
||||
_0: impl ::std::borrow::Borrow<#expected_type>,
|
||||
_0: impl ::std::borrow::Borrow<types::#name_ident::Param0>,
|
||||
)
|
||||
);
|
||||
assert!(generated_str.contains(&expected_storage_constructor.to_string()));
|
||||
|
||||
let alias_name = format_ident!("{}", name.to_upper_camel_case());
|
||||
let expected_alias_module = quote!(
|
||||
pub mod #name_ident {
|
||||
use super::runtime_types;
|
||||
|
||||
pub type #alias_name = ::core::primitive::bool;
|
||||
pub type Param0 = #expected_type;
|
||||
}
|
||||
);
|
||||
|
||||
assert!(generated_str.contains(&expected_alias_module.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
use crate::error::CodegenError;
|
||||
|
||||
use super::{Derives, Field, TypeDefParameters, TypeGenerator, TypeParameter, TypePath};
|
||||
use heck::ToUpperCamelCase as _;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use scale_info::{form::PortableForm, Type, TypeDef, TypeDefPrimitive};
|
||||
@@ -27,6 +28,9 @@ pub struct CompositeDef {
|
||||
|
||||
impl CompositeDef {
|
||||
/// Construct a definition which will generate code for a standalone `struct`.
|
||||
///
|
||||
/// This is useful for generating structures from call and enum metadata variants;
|
||||
/// and from all the runtime types of the metadata.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn struct_def(
|
||||
ty: &Type<PortableForm>,
|
||||
@@ -37,6 +41,7 @@ impl CompositeDef {
|
||||
type_gen: &TypeGenerator,
|
||||
docs: &[String],
|
||||
crate_path: &syn::Path,
|
||||
alias_module_name: Option<syn::Ident>,
|
||||
) -> Result<Self, CodegenError> {
|
||||
let mut derives = type_gen.type_derives(ty)?;
|
||||
let fields: Vec<_> = fields_def.field_types().collect();
|
||||
@@ -75,6 +80,7 @@ impl CompositeDef {
|
||||
derives,
|
||||
type_params,
|
||||
field_visibility,
|
||||
alias_module_name,
|
||||
},
|
||||
fields: fields_def,
|
||||
docs: docs_token,
|
||||
@@ -104,11 +110,15 @@ impl quote::ToTokens for CompositeDef {
|
||||
derives,
|
||||
type_params,
|
||||
field_visibility,
|
||||
alias_module_name,
|
||||
} => {
|
||||
let phantom_data = type_params.unused_params_phantom_data();
|
||||
let fields = self
|
||||
.fields
|
||||
.to_struct_field_tokens(phantom_data, field_visibility.as_ref());
|
||||
|
||||
let fields: TokenStream = self.fields.to_struct_field_tokens(
|
||||
phantom_data,
|
||||
field_visibility.as_ref(),
|
||||
alias_module_name.as_ref(),
|
||||
);
|
||||
let trailing_semicolon = matches!(
|
||||
self.fields,
|
||||
CompositeDefFields::NoFields | CompositeDefFields::Unnamed(_)
|
||||
@@ -143,6 +153,7 @@ pub enum CompositeDefKind {
|
||||
derives: Derives,
|
||||
type_params: TypeDefParameters,
|
||||
field_visibility: Option<syn::Visibility>,
|
||||
alias_module_name: Option<syn::Ident>,
|
||||
},
|
||||
/// Comprises a variant of a Rust `enum`.
|
||||
EnumVariant,
|
||||
@@ -150,7 +161,7 @@ pub enum CompositeDefKind {
|
||||
|
||||
/// Encapsulates the composite fields, keeping the invariant that all fields are either named or
|
||||
/// unnamed.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CompositeDefFields {
|
||||
NoFields,
|
||||
Named(Vec<(syn::Ident, CompositeDefFieldType)>),
|
||||
@@ -210,11 +221,38 @@ impl CompositeDefFields {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate the code for type aliases which will be used to construct the `struct` or `enum`.
|
||||
pub fn to_type_aliases_tokens(&self, visibility: Option<&syn::Visibility>) -> TokenStream {
|
||||
match self {
|
||||
Self::NoFields => {
|
||||
quote!()
|
||||
}
|
||||
Self::Named(ref fields) => {
|
||||
let fields = fields.iter().map(|(name, ty)| {
|
||||
let alias_name = format_ident!("{}", name.to_string().to_upper_camel_case());
|
||||
// Alias without boxing to have a cleaner call interface.
|
||||
let ty_path = &ty.type_path;
|
||||
quote! ( #visibility type #alias_name = #ty_path; )
|
||||
});
|
||||
quote!( #( #fields )* )
|
||||
}
|
||||
Self::Unnamed(ref fields) => {
|
||||
let fields = fields.iter().enumerate().map(|(idx, ty)| {
|
||||
let alias_name = format_ident!("Field{}", idx);
|
||||
quote! ( #visibility type #alias_name = #ty; )
|
||||
});
|
||||
|
||||
quote!( #( #fields )* )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate the code for fields which will compose a `struct`.
|
||||
pub fn to_struct_field_tokens(
|
||||
&self,
|
||||
phantom_data: Option<syn::TypePath>,
|
||||
visibility: Option<&syn::Visibility>,
|
||||
alias_module_name: Option<&syn::Ident>,
|
||||
) -> TokenStream {
|
||||
match self {
|
||||
Self::NoFields => {
|
||||
@@ -227,7 +265,20 @@ impl CompositeDefFields {
|
||||
Self::Named(ref fields) => {
|
||||
let fields = fields.iter().map(|(name, ty)| {
|
||||
let compact_attr = ty.compact_attr();
|
||||
quote! { #compact_attr #visibility #name: #ty }
|
||||
|
||||
if let Some(alias_module_name) = alias_module_name {
|
||||
let alias_name =
|
||||
format_ident!("{}", name.to_string().to_upper_camel_case());
|
||||
|
||||
let mut path = quote!( #alias_module_name::#alias_name);
|
||||
if ty.is_boxed() {
|
||||
path = quote!( ::std::boxed::Box<#path> );
|
||||
}
|
||||
|
||||
quote! { #compact_attr #visibility #name: #path }
|
||||
} else {
|
||||
quote! { #compact_attr #visibility #name: #ty }
|
||||
}
|
||||
});
|
||||
let marker = phantom_data.map(|phantom_data| {
|
||||
quote!(
|
||||
@@ -243,9 +294,21 @@ impl CompositeDefFields {
|
||||
)
|
||||
}
|
||||
Self::Unnamed(ref fields) => {
|
||||
let fields = fields.iter().map(|ty| {
|
||||
let fields = fields.iter().enumerate().map(|(idx, ty)| {
|
||||
let compact_attr = ty.compact_attr();
|
||||
quote! { #compact_attr #visibility #ty }
|
||||
|
||||
if let Some(alias_module_name) = alias_module_name {
|
||||
let alias_name = format_ident!("Field{}", idx);
|
||||
|
||||
let mut path = quote!( #alias_module_name::#alias_name);
|
||||
if ty.is_boxed() {
|
||||
path = quote!( ::std::boxed::Box<#path> );
|
||||
}
|
||||
|
||||
quote! { #compact_attr #visibility #path }
|
||||
} else {
|
||||
quote! { #compact_attr #visibility #ty }
|
||||
}
|
||||
});
|
||||
let marker = phantom_data.map(|phantom_data| {
|
||||
quote!(
|
||||
@@ -286,7 +349,7 @@ impl CompositeDefFields {
|
||||
}
|
||||
|
||||
/// Represents a field of a composite type to be generated.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CompositeDefFieldType {
|
||||
pub type_id: u32,
|
||||
pub type_path: TypePath,
|
||||
|
||||
@@ -98,10 +98,9 @@ impl<'a> TypeGenerator<'a> {
|
||||
.or_insert_with(|| Module::new(ident, root_mod_ident.clone()))
|
||||
});
|
||||
|
||||
innermost_module.types.insert(
|
||||
path.clone(),
|
||||
TypeDefGen::from_type(&ty.ty, self, &self.crate_path, self.should_gen_docs)?,
|
||||
);
|
||||
innermost_module
|
||||
.types
|
||||
.insert(path.clone(), TypeDefGen::from_type(&ty.ty, self)?);
|
||||
}
|
||||
|
||||
Ok(root_mod)
|
||||
@@ -274,6 +273,21 @@ impl<'a> TypeGenerator<'a> {
|
||||
.map_err(|e| CodegenError::InvalidTypePath(joined_path, e))?;
|
||||
Ok(self.derives.resolve(&ty_path))
|
||||
}
|
||||
|
||||
/// The name of the module which will contain the generated types.
|
||||
pub fn types_mod_ident(&self) -> &Ident {
|
||||
&self.types_mod_ident
|
||||
}
|
||||
|
||||
/// The `subxt` crate access path in the generated code.
|
||||
pub fn crate_path(&self) -> &syn::Path {
|
||||
&self.crate_path
|
||||
}
|
||||
|
||||
/// True if codegen should generate the documentation for the API.
|
||||
pub fn should_gen_docs(&self) -> bool {
|
||||
self.should_gen_docs
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a Rust `mod`, containing generated types and child `mod`s.
|
||||
|
||||
@@ -33,10 +33,10 @@ impl TypeDefGen {
|
||||
pub fn from_type(
|
||||
ty: &Type<PortableForm>,
|
||||
type_gen: &TypeGenerator,
|
||||
crate_path: &syn::Path,
|
||||
should_gen_docs: bool,
|
||||
) -> Result<Self, CodegenError> {
|
||||
let derives = type_gen.type_derives(ty)?;
|
||||
let crate_path = type_gen.crate_path();
|
||||
let should_gen_docs = type_gen.should_gen_docs();
|
||||
|
||||
let type_params = ty
|
||||
.type_params
|
||||
@@ -77,6 +77,7 @@ impl TypeDefGen {
|
||||
type_gen,
|
||||
docs,
|
||||
crate_path,
|
||||
None,
|
||||
)?;
|
||||
TypeDefGenKind::Struct(composite_def)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![allow(missing_docs)]
|
||||
use polkadot::multisig::events::NewMultisig;
|
||||
use polkadot::runtime_types::{
|
||||
frame_system::pallet::Call, polkadot_runtime::RuntimeCall, sp_weights::weight_v2::Weight,
|
||||
frame_system::pallet::Call, rococo_runtime::RuntimeCall, sp_weights::weight_v2::Weight,
|
||||
};
|
||||
use subxt::utils::AccountId32;
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user