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

Handle sp_runtime::ModuleError substrate updates #492

Merged
merged 15 commits into from
Mar 31, 2022
Merged
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
162 changes: 117 additions & 45 deletions codegen/src/api/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,104 @@ use frame_metadata::v14::RuntimeMetadataV14;
use proc_macro2::TokenStream as TokenStream2;
use proc_macro_error::abort_call_site;
use quote::quote;
use scale_info::TypeDef;
use scale_info::{
form::PortableForm,
Field,
TypeDef,
};

/// Different substrate versions will have a different `DispatchError::Module`.
/// The following cases are ordered by versions.
enum ModuleErrorType {
/// Case 1: `DispatchError::Module { index: u8, error: u8 }`
///
/// This is the first supported `DispatchError::Module` format.
NamedField,
/// Case 2: `DispatchError::Module ( sp_runtime::ModuleError { index: u8, error: u8 } )`
///
/// Substrate introduced `sp_runtime::ModuleError`, while keeping the error `u8`.
LegacyError,
/// Case 3: `DispatchError::Module ( sp_runtime::ModuleError { index: u8, error: [u8; 4] } )`
///
/// The substrate error evolved into `[u8; 4]`.
ArrayError,
}

impl quote::ToTokens for ModuleErrorType {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let trait_fn_body = match self {
ModuleErrorType::NamedField => {
quote! {
if let &Self::Module { index, error } = self {
Some((index, error))
} else {
None
}
}
}
ModuleErrorType::LegacyError => {
quote! {
if let Self::Module (module_error) = self {
Some((module_error.index, module_error.error))
} else {
None
}
}
}
ModuleErrorType::ArrayError => {
quote! {
if let Self::Module (module_error) = self {
Some((module_error.index, module_error.error[0]))
} else {
None
}
}
}
};

tokens.extend(trait_fn_body);
}
}

/// Determine the `ModuleError` type for the `ModuleErrorType::LegacyError` and
/// `ModuleErrorType::ErrorArray` cases.
fn module_error_type(
module_field: &Field<PortableForm>,
metadata: &RuntimeMetadataV14,
) -> ModuleErrorType {
// Fields are named.
if module_field.name().is_some() {
return ModuleErrorType::NamedField
}

// Get the `sp_runtime::ModuleError` structure.
let module_err = metadata
.types
.resolve(module_field.ty().id())
.unwrap_or_else(|| {
abort_call_site!("sp_runtime::ModuleError type expected in metadata")
});

let error_type_def = match module_err.type_def() {
TypeDef::Composite(composite) => composite,
_ => abort_call_site!("sp_runtime::ModuleError type should be a composite type"),
};

// Get the error field from the `sp_runtime::ModuleError` structure.
let error_field = error_type_def
.fields()
.iter()
.find(|field| field.name() == Some(&"error".to_string()))
.unwrap_or_else(|| {
abort_call_site!("sp_runtime::ModuleError expected to contain error field")
});

if error_field.type_name() == Some(&"u8".to_string()) {
ascjones marked this conversation as resolved.
Show resolved Hide resolved
ModuleErrorType::LegacyError
} else {
ascjones marked this conversation as resolved.
Show resolved Hide resolved
ModuleErrorType::ArrayError
}
}

/// The aim of this is to implement the `::subxt::HasModuleError` trait for
/// the generated `DispatchError`, so that we can obtain the module error details,
Expand All @@ -27,7 +124,7 @@ pub fn generate_has_module_error_impl(
metadata: &RuntimeMetadataV14,
types_mod_ident: &syn::Ident,
) -> TokenStream2 {
let dispatch_error_def = metadata
let dispatch_error = metadata
.types
.types()
.iter()
Expand All @@ -38,55 +135,30 @@ pub fn generate_has_module_error_impl(
.ty()
.type_def();

// Slightly older versions of substrate have a `DispatchError::Module { index, error }`
// variant. Newer versions have something like a `DispatchError::Module (Details)` variant.
// We check to see which type of variant we're dealing with based on the metadata, and
// generate the correct code to handle either older or newer substrate versions.
let module_variant_is_struct = if let TypeDef::Variant(details) = dispatch_error_def {
let module_variant = details
.variants()
.iter()
.find(|variant| variant.name() == "Module")
.unwrap_or_else(|| {
abort_call_site!("DispatchError::Module variant expected in metadata")
});
let are_fields_named = module_variant
.fields()
.get(0)
.unwrap_or_else(|| {
abort_call_site!(
"DispatchError::Module expected to contain 1 or more fields"
)
})
.name()
.is_some();
are_fields_named
} else {
false
};

let trait_fn_body = if module_variant_is_struct {
quote! {
if let &Self::Module { index, error } = self {
Some((index, error))
} else {
None
}
}
} else {
quote! {
if let Self::Module (module_error) = self {
Some((module_error.index, module_error.error))
} else {
None
}
// Get the `DispatchError::Module` variant (either struct or named fields).
let module_variant = match dispatch_error {
TypeDef::Variant(variant) => {
variant
.variants()
.iter()
.find(|variant| variant.name() == "Module")
.unwrap_or_else(|| {
abort_call_site!("DispatchError::Module variant expected in metadata")
})
}
_ => abort_call_site!("DispatchError expected to contain variant in metadata"),
};

let module_field = module_variant.fields().get(0).unwrap_or_else(|| {
abort_call_site!("DispatchError::Module expected to contain 1 or more fields")
});

let error_type = module_error_type(module_field, metadata);

quote! {
impl ::subxt::HasModuleError for #types_mod_ident::sp_runtime::DispatchError {
fn module_error_indices(&self) -> Option<(u8,u8)> {
#trait_fn_body
#error_type
}
}
}
Expand Down