Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use scale-encode and scale-decode to encode and decode based on metadata #842

Merged
merged 42 commits into from
Mar 21, 2023
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
4b0b734
WIP EncodeAsType and DecodeAsType
jsdw Feb 13, 2023
8b5c0d8
Merge branch 'master' into jsdw-scale-encode-decode
jsdw Feb 13, 2023
74628ec
remove silly cli experiment code
jsdw Feb 14, 2023
0c90a80
Get things finally compiling with EncodeAsType and DecodeAsType
jsdw Feb 14, 2023
ee69471
update codegen test and WrapperKeepOpaque proper impl (in case it sho…
jsdw Feb 14, 2023
9e3ca60
fix tests
jsdw Feb 15, 2023
a116517
Merge branch 'master' into jsdw-scale-encode-decode
jsdw Feb 21, 2023
f3dab46
accomodate scale-value changes
jsdw Feb 21, 2023
7ccfe92
starting to migrate to EncodeAsType/DecodeAsType
jsdw Feb 23, 2023
083d271
static event decoding and tx encoding to use DecodeAsFields/EncodeAsF…
jsdw Feb 24, 2023
550a314
some tidy up and add decode(skip) attrs where needed
jsdw Feb 27, 2023
3258e83
fix root event decoding
jsdw Feb 28, 2023
4bbec39
#[codec(skip)] will do, and combine map_key stuff into storage_addres…
jsdw Feb 28, 2023
4767097
fmt and clippy
jsdw Feb 28, 2023
7087c35
Merge branch 'master' into jsdw-scale-encode-decode
jsdw Feb 28, 2023
1d4f3ee
update Cargo.lock
jsdw Feb 28, 2023
ee88037
remove patched scale-encode
jsdw Feb 28, 2023
6f70c29
bump scale-encode to 0.1 and remove unused dep in testing crate
jsdw Feb 28, 2023
e5b538c
update deps and use released scale-decode
jsdw Mar 13, 2023
4260492
update scale-value to latest to remove git branch
jsdw Mar 13, 2023
fbd5443
Apply suggestions from code review
jsdw Mar 13, 2023
7c0168f
Merge branch 'master' into jsdw-scale-encode-decode
jsdw Mar 13, 2023
d9849b3
Merge branch 'jsdw-scale-encode-decode' of github.com:paritytech/subx…
jsdw Mar 13, 2023
891800b
remove sorting in derives/attr generation; spit them out in order given
jsdw Mar 15, 2023
f5d0c58
Merge branch 'master' into jsdw-scale-encode-decode
jsdw Mar 15, 2023
53f2687
Merge branch 'master' into jsdw-scale-encode-decode
jsdw Mar 15, 2023
a252b67
re-add derive sorting; it's a hashmap
jsdw Mar 16, 2023
b38ae8e
StaticTxPayload and DynamicTxPayload rolled into single Payload struct
jsdw Mar 15, 2023
0131eda
StaticStorageAddress and DynamicStorageAddress into single Address st…
jsdw Mar 16, 2023
9b8f846
Fix storage address byte retrieval
jsdw Mar 17, 2023
6c25474
StaticConstantAddress and DynamicConstantAddress => Address
jsdw Mar 17, 2023
1b1ab02
Simplify storage codegen to fix test
jsdw Mar 17, 2023
756cad2
Add comments
jsdw Mar 17, 2023
c377ff7
Alias to RuntimeEvent rather than making another, and prep for substi…
jsdw Mar 17, 2023
02e4b93
remove unnecessary clone
jsdw Mar 17, 2023
7e4ea8d
Fix docs and failing UI test
jsdw Mar 18, 2023
902ec2a
Merge branch 'master' into jsdw-scale-encode-decode
jsdw Mar 20, 2023
bf6a01c
root_bytes -> to_root_bytes
jsdw Mar 20, 2023
e94c0b7
document error case in StorageClient::address_bytes()
jsdw Mar 21, 2023
c92d3aa
Merge branch 'master' into jsdw-scale-encode-decode
jsdw Mar 21, 2023
f78f27a
Merge branch 'master' into jsdw-scale-encode-decode
jsdw Mar 21, 2023
5c62777
Merge branch 'master' into jsdw-scale-encode-decode
jsdw Mar 21, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
272 changes: 135 additions & 137 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions codegen/src/api/calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ pub fn generate_calls(
pub fn #fn_name(
&self,
#( #call_fn_args, )*
) -> #crate_path::tx::StaticTxPayload<#struct_name> {
#crate_path::tx::StaticTxPayload::new(
) -> #crate_path::tx::Payload<#struct_name> {
#crate_path::tx::Payload::new_static(
#pallet_name,
#call_name,
#struct_name { #( #call_args, )* },
Expand Down
4 changes: 2 additions & 2 deletions codegen/src/api/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ pub fn generate_constants(

Ok(quote! {
#docs
pub fn #fn_name(&self) -> #crate_path::constants::StaticConstantAddress<#crate_path::metadata::DecodeStaticType<#return_ty>> {
#crate_path::constants::StaticConstantAddress::new(
pub fn #fn_name(&self) -> #crate_path::constants::Address<#return_ty> {
#crate_path::constants::Address::new_static(
#pallet_name,
#constant_name,
[#(#constant_hash,)*]
Expand Down
97 changes: 65 additions & 32 deletions codegen/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ use syn::parse_quote;
/// Error returned when the Codegen cannot generate the runtime API.
#[derive(Debug, thiserror::Error)]
pub enum CodegenError {
/// The given metadata type could not be found.
#[error("Could not find type with ID {0} in the type registry; please raise a support issue.")]
TypeNotFound(u32),
/// Cannot fetch the metadata bytes.
#[error("Failed to fetch metadata, make sure that you're pointing at a node which is providing V14 metadata: {0}")]
Fetch(#[from] FetchMetadataError),
Expand Down Expand Up @@ -80,12 +83,6 @@ pub enum CodegenError {
/// Metadata for storage could not be found.
#[error("Metadata for storage entry {0}_{1} could not be found. Make sure you are providing a valid metadata V14")]
MissingStorageMetadata(String, String),
/// StorageNMap should have N hashers.
#[error("Number of hashers ({0}) does not equal 1 for StorageMap, or match number of fields ({1}) for StorageNMap. Make sure you are providing a valid metadata V14")]
MismatchHashers(usize, usize),
/// Expected to find one hasher for StorageMap.
#[error("No hasher found for single key. Make sure you are providing a valid metadata V14")]
MissingHasher,
/// Metadata for call could not be found.
#[error("Metadata for call entry {0}_{1} could not be found. Make sure you are providing a valid metadata V14")]
MissingCallMetadata(String, String),
Expand Down Expand Up @@ -250,13 +247,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,
);
Expand All @@ -274,6 +270,28 @@ impl RuntimeGenerator {
})
.collect::<Vec<_>>();

// Get the path to the `Runtime` struct. We assume that the same path contains
// RuntimeCall and RuntimeEvent.
let runtime_type_id = self.metadata.ty.id();
let runtime_path_segments = self
.metadata
.types
.resolve(runtime_type_id)
.ok_or(CodegenError::TypeNotFound(runtime_type_id))?
.path()
.namespace()
.iter()
.map(|part| syn::PathSegment::from(format_ident!("{}", part)));
let runtime_path_suffix = syn::Path {
leading_colon: None,
segments: syn::punctuated::Punctuated::from_iter(runtime_path_segments),
};
let runtime_path = if runtime_path_suffix.segments.is_empty() {
quote!(#types_mod_ident)
} else {
quote!(#types_mod_ident::#runtime_path_suffix)
};

// Pallet names and their length are used to create PALLETS array.
// The array is used to identify the pallets composing the metadata for
// validation of just those pallets.
Expand Down Expand Up @@ -338,26 +356,24 @@ impl RuntimeGenerator {
})
.collect::<Result<Vec<_>, CodegenError>>()?;

let outer_event_variants = self.metadata.pallets.iter().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);

let root_event_if_arms = self.metadata.pallets.iter().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.as_ref().map(|_| {
// An 'if' arm for the RootEvent impl to match this variant name:
quote! {
#[codec(index = #index)]
#variant_name(#mod_name::Event),
if pallet_name == #variant_name_str {
ascjones marked this conversation as resolved.
Show resolved Hide resolved
return Ok(Event::#variant_name(#mod_name::Event::decode_with_metadata(
&mut &*pallet_bytes,
pallet_ty,
metadata
)?));
}
}
})
});

let outer_event = quote! {
#default_derives
pub enum Event {
#( #outer_event_variants )*
}
};

let mod_ident = &item_mod_ir.ident;
let pallets_with_constants: Vec<_> = pallets_with_mod_names
.iter()
Expand Down Expand Up @@ -387,22 +403,36 @@ impl RuntimeGenerator {
#[allow(dead_code, unused_imports, non_camel_case_types)]
#[allow(clippy::all)]
pub mod #mod_ident {
// Preserve any Rust items that were previously defined in the adorned module
// Preserve any Rust items that were previously defined in the adorned module.
#( #rust_items ) *

// Make it easy to access the root via `root_mod` at different levels:
use super::#mod_ident as root_mod;
// Make it easy to access the root items via `root_mod` at different levels
// without reaching out of this module.
#[allow(unused_imports)]
mod root_mod {
pub use super::*;
}

// Identify the pallets composing the static metadata by name.
pub static PALLETS: [&str; #pallet_names_len] = [ #(#pallet_names,)* ];

#outer_event
#( #modules )*
#types_mod
/// The statically generated runtime call type.
pub type Call = #runtime_path::RuntimeCall;

/// The default error type returned when there is a runtime issue,
/// exposed here for ease of use.
/// The error type returned when there is a runtime issue.
pub type DispatchError = #types_mod_ident::sp_runtime::DispatchError;

// Make the runtime event type easily accessible, and impl RootEvent to help decode into it.
pub type Event = #runtime_path::RuntimeEvent;

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())
}
}

pub fn constants() -> ConstantsApi {
ConstantsApi
}
Expand Down Expand Up @@ -443,14 +473,17 @@ impl RuntimeGenerator {
}

/// check whether the Client you are using is aligned with the statically generated codegen.
pub fn validate_codegen<T: ::subxt::Config, C: ::subxt::client::OfflineClientT<T>>(client: &C) -> Result<(), ::subxt::error::MetadataError> {
pub fn validate_codegen<T: #crate_path::Config, C: #crate_path::client::OfflineClientT<T>>(client: &C) -> Result<(), #crate_path::error::MetadataError> {
let runtime_metadata_hash = client.metadata().metadata_hash(&PALLETS);
if runtime_metadata_hash != [ #(#metadata_hash,)* ] {
Err(::subxt::error::MetadataError::IncompatibleMetadata)
Err(#crate_path::error::MetadataError::IncompatibleMetadata)
} else {
Ok(())
}
}

#( #modules )*
#types_mod
}
})
}
Expand Down
87 changes: 28 additions & 59 deletions codegen/src/api/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ use frame_metadata::{
StorageEntryMetadata,
StorageEntryModifier,
StorageEntryType,
StorageHasher,
};
use heck::ToSnakeCase as _;
use proc_macro2::TokenStream as TokenStream2;
Expand Down Expand Up @@ -84,31 +83,12 @@ fn generate_storage_entry_fns(
crate_path: &CratePath,
should_gen_docs: bool,
) -> Result<TokenStream2, CodegenError> {
let (fields, key_impl) = match storage_entry.ty {
let (fields, key_impl) = match &storage_entry.ty {
StorageEntryType::Plain(_) => (vec![], quote!(vec![])),
StorageEntryType::Map {
ref key,
ref hashers,
..
} => {
StorageEntryType::Map { key, .. } => {
let key_ty = type_gen.resolve_type(key.id());
let hashers = hashers
.iter()
.map(|hasher| {
let hasher = match hasher {
StorageHasher::Blake2_128 => "Blake2_128",
StorageHasher::Blake2_256 => "Blake2_256",
StorageHasher::Blake2_128Concat => "Blake2_128Concat",
StorageHasher::Twox128 => "Twox128",
StorageHasher::Twox256 => "Twox256",
StorageHasher::Twox64Concat => "Twox64Concat",
StorageHasher::Identity => "Identity",
};
let hasher = format_ident!("{}", hasher);
quote!( #crate_path::storage::address::StorageHasher::#hasher )
})
.collect::<Vec<_>>();
match key_ty.type_def() {
// An N-map; return each of the keys separately.
TypeDef::Tuple(tuple) => {
let fields = tuple
.fields()
Expand All @@ -121,46 +101,23 @@ fn generate_storage_entry_fns(
})
.collect::<Vec<_>>();

let key_impl = if hashers.len() == fields.len() {
// If the number of hashers matches the number of fields, we're dealing with
// something shaped like a StorageNMap, and each field should be hashed separately
// according to the corresponding hasher.
let keys = hashers
.into_iter()
.zip(&fields)
.map(|(hasher, (field_name, _))| {
quote!( #crate_path::storage::address::StorageMapKey::new(#field_name.borrow(), #hasher) )
});
quote! {
vec![ #( #keys ),* ]
}
} else if hashers.len() == 1 {
// If there is one hasher, then however many fields we have, we want to hash a
// tuple of them using the one hasher we're told about. This corresponds to a
// StorageMap.
let hasher = hashers.get(0).expect("checked for 1 hasher");
let items =
fields.iter().map(|(field_name, _)| quote!( #field_name ));
quote! {
vec![ #crate_path::storage::address::StorageMapKey::new(&(#( #items.borrow() ),*), #hasher) ]
}
} else {
return Err(CodegenError::MismatchHashers(
hashers.len(),
fields.len(),
))
let keys = fields
.iter()
.map(|(field_name, _)| {
quote!( #crate_path::storage::address::StaticStorageMapKey::new(#field_name.borrow()) )
});
let key_impl = quote! {
vec![ #( #keys ),* ]
};

(fields, key_impl)
}
// A map with a single key; return the single key.
_ => {
let ty_path = type_gen.resolve_type_path(key.id());
let fields = vec![(format_ident!("_0"), ty_path)];
let Some(hasher) = hashers.get(0) else {
return Err(CodegenError::MissingHasher)
};
let key_impl = quote! {
vec![ #crate_path::storage::address::StorageMapKey::new(_0.borrow(), #hasher) ]
vec![ #crate_path::storage::address::StaticStorageMapKey::new(_0.borrow()) ]
};
(fields, key_impl)
}
Expand Down Expand Up @@ -233,8 +190,14 @@ fn generate_storage_entry_fns(
#docs
pub fn #fn_name_root(
&self,
) -> #crate_path::storage::address::StaticStorageAddress::<#crate_path::metadata::DecodeStaticType<#storage_entry_value_ty>, (), #is_defaultable_type, #is_iterable_type> {
#crate_path::storage::address::StaticStorageAddress::new(
) -> #crate_path::storage::address::Address::<
#crate_path::storage::address::StaticStorageMapKey,
#storage_entry_value_ty,
(),
#is_defaultable_type,
#is_iterable_type
> {
#crate_path::storage::address::Address::new_static(
#pallet_name,
#storage_name,
Vec::new(),
Expand All @@ -252,8 +215,14 @@ fn generate_storage_entry_fns(
pub fn #fn_name(
&self,
#( #key_args, )*
) -> #crate_path::storage::address::StaticStorageAddress::<#crate_path::metadata::DecodeStaticType<#storage_entry_value_ty>, #crate_path::storage::address::Yes, #is_defaultable_type, #is_iterable_type> {
#crate_path::storage::address::StaticStorageAddress::new(
) -> #crate_path::storage::address::Address::<
#crate_path::storage::address::StaticStorageMapKey,
#storage_entry_value_ty,
#crate_path::storage::address::Yes,
#is_defaultable_type,
#is_iterable_type
> {
#crate_path::storage::address::Address::new_static(
#pallet_name,
#storage_name,
#key_impl,
Expand Down
Loading