diff --git a/client/block-builder/src/lib.rs b/client/block-builder/src/lib.rs index a4c6f5aad2aeb..3f9fecbccbb9e 100644 --- a/client/block-builder/src/lib.rs +++ b/client/block-builder/src/lib.rs @@ -35,6 +35,7 @@ use sp_blockchain::{ApplyExtrinsicFailed, Error}; use sp_core::ExecutionContext; use sp_runtime::{ generic::BlockId, + legacy, traits::{Block as BlockT, Hash, HashFor, Header as HeaderT, NumberFor, One}, Digest, }; @@ -135,6 +136,7 @@ where pub struct BlockBuilder<'a, Block: BlockT, A: ProvideRuntimeApi, B> { extrinsics: Vec, api: ApiRef<'a, A::Api>, + version: u32, block_id: BlockId, parent_hash: Block::Hash, backend: &'a B, @@ -183,10 +185,15 @@ where api.initialize_block_with_context(&block_id, ExecutionContext::BlockConstruction, &header)?; + let version = api + .api_version::>(&block_id)? + .ok_or_else(|| Error::VersionInvalid("BlockBuilderApi".to_string()))?; + Ok(Self { parent_hash, extrinsics: Vec::new(), api, + version, block_id, backend, estimated_header_size, @@ -199,13 +206,26 @@ where pub fn push(&mut self, xt: ::Extrinsic) -> Result<(), Error> { let block_id = &self.block_id; let extrinsics = &mut self.extrinsics; + let version = self.version; self.api.execute_in_transaction(|api| { - match api.apply_extrinsic_with_context( - block_id, - ExecutionContext::BlockConstruction, - xt.clone(), - ) { + let res = if version < 6 { + #[allow(deprecated)] + api.apply_extrinsic_before_version_6_with_context( + block_id, + ExecutionContext::BlockConstruction, + xt.clone(), + ) + .map(legacy::byte_sized_error::convert_to_latest) + } else { + api.apply_extrinsic_with_context( + block_id, + ExecutionContext::BlockConstruction, + xt.clone(), + ) + }; + + match res { Ok(Ok(_)) => { extrinsics.push(xt); TransactionOutcome::Commit(Ok(())) diff --git a/client/rpc/src/state/tests.rs b/client/rpc/src/state/tests.rs index 9dbe02cdb7d64..287dfac8c6ba0 100644 --- a/client/rpc/src/state/tests.rs +++ b/client/rpc/src/state/tests.rs @@ -527,7 +527,7 @@ fn should_return_runtime_version() { let result = "{\"specName\":\"test\",\"implName\":\"parity-test\",\"authoringVersion\":1,\ \"specVersion\":2,\"implVersion\":2,\"apis\":[[\"0xdf6acb689907609b\",4],\ - [\"0x37e397fc7c91f5e4\",1],[\"0xd2bc9897eed08f15\",3],[\"0x40fe3ad401f8959a\",5],\ + [\"0x37e397fc7c91f5e4\",1],[\"0xd2bc9897eed08f15\",3],[\"0x40fe3ad401f8959a\",6],\ [\"0xc6e9a76309f39b09\",1],[\"0xdd718d5cc53262d4\",1],[\"0xcbca25e39f142387\",2],\ [\"0xf78b278be53f454c\",2],[\"0xab3c0572291feb8b\",1],[\"0xbc9d89904f5b923f\",1]],\ \"transactionVersion\":1,\"stateVersion\":1}"; diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index c502245409fdb..aa9f1c80dfdbc 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -70,7 +70,7 @@ pub mod mock; mod tests; pub mod weights; -pub use list::{notional_bag_for, Bag, Error, List, Node}; +pub use list::{notional_bag_for, Bag, List, ListError, Node}; pub use pallet::*; pub use weights::WeightInfo; @@ -270,7 +270,7 @@ impl, I: 'static> Pallet { } impl, I: 'static> SortedListProvider for Pallet { - type Error = Error; + type Error = ListError; type Score = T::Score; @@ -286,7 +286,7 @@ impl, I: 'static> SortedListProvider for Pallet List::::contains(id) } - fn on_insert(id: T::AccountId, score: T::Score) -> Result<(), Error> { + fn on_insert(id: T::AccountId, score: T::Score) -> Result<(), ListError> { List::::insert(id, score) } diff --git a/frame/bags-list/src/list/mod.rs b/frame/bags-list/src/list/mod.rs index 4921817c7e146..4e1287458bcb4 100644 --- a/frame/bags-list/src/list/mod.rs +++ b/frame/bags-list/src/list/mod.rs @@ -39,7 +39,7 @@ use sp_std::{ }; #[derive(Debug, PartialEq, Eq)] -pub enum Error { +pub enum ListError { /// A duplicate id has been detected. Duplicate, } @@ -266,9 +266,9 @@ impl, I: 'static> List { /// Insert a new id into the appropriate bag in the list. /// /// Returns an error if the list already contains `id`. - pub(crate) fn insert(id: T::AccountId, score: T::Score) -> Result<(), Error> { + pub(crate) fn insert(id: T::AccountId, score: T::Score) -> Result<(), ListError> { if Self::contains(&id) { - return Err(Error::Duplicate) + return Err(ListError::Duplicate) } let bag_score = notional_bag_for::(score); diff --git a/frame/bags-list/src/list/tests.rs b/frame/bags-list/src/list/tests.rs index 9b7a078b44284..c8e233f1e62cc 100644 --- a/frame/bags-list/src/list/tests.rs +++ b/frame/bags-list/src/list/tests.rs @@ -248,7 +248,7 @@ mod list { // then assert_storage_noop!(assert_eq!( List::::insert(3, 20).unwrap_err(), - Error::Duplicate + ListError::Duplicate )); }); } diff --git a/frame/bags-list/src/tests.rs b/frame/bags-list/src/tests.rs index 99396c9cbb3e3..0d6ba4721b9a2 100644 --- a/frame/bags-list/src/tests.rs +++ b/frame/bags-list/src/tests.rs @@ -518,7 +518,7 @@ mod sorted_list_provider { // then assert_storage_noop!(assert_eq!( BagsList::on_insert(3, 20).unwrap_err(), - Error::Duplicate + ListError::Duplicate )); }); } diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index b57d24d2d530c..ddc06ce0aecfd 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -1583,7 +1583,7 @@ impl ElectionProvider for Pallet { /// number. pub fn dispatch_error_to_invalid(error: DispatchError) -> InvalidTransaction { let error_number = match error { - DispatchError::Module(ModuleError { error, .. }) => error, + DispatchError::Module(ModuleError { error, .. }) => error[0], _ => 0, }; InvalidTransaction::Custom(error_number) diff --git a/frame/election-provider-multi-phase/src/unsigned.rs b/frame/election-provider-multi-phase/src/unsigned.rs index c52a4da22cb87..d210852bac19e 100644 --- a/frame/election-provider-multi-phase/src/unsigned.rs +++ b/frame/election-provider-multi-phase/src/unsigned.rs @@ -931,7 +931,7 @@ mod tests { #[test] #[should_panic(expected = "Invalid unsigned submission must produce invalid block and \ deprive validator from their authoring reward.: \ - Module(ModuleError { index: 2, error: 1, message: \ + Module(ModuleError { index: 2, error: [1, 0, 0, 0], message: \ Some(\"PreDispatchWrongWinnerCount\") })")] fn unfeasible_solution_panics() { ExtBuilder::default().build_and_execute(|| { @@ -1053,7 +1053,7 @@ mod tests { MultiPhase::basic_checks(&solution, "mined").unwrap_err(), MinerError::PreDispatchChecksFailed(DispatchError::Module(ModuleError { index: 2, - error: 1, + error: [1, 0, 0, 0], message: Some("PreDispatchWrongWinnerCount"), })), ); diff --git a/frame/scheduler/src/mock.rs b/frame/scheduler/src/mock.rs index 869425c2ff530..ecd04c3e48b52 100644 --- a/frame/scheduler/src/mock.rs +++ b/frame/scheduler/src/mock.rs @@ -38,7 +38,7 @@ use sp_runtime::{ // Logger module to track execution. #[frame_support::pallet] pub mod logger { - use super::*; + use super::{OriginCaller, OriginTrait}; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; use std::cell::RefCell; @@ -71,7 +71,7 @@ pub mod logger { #[pallet::call] impl Pallet where - ::Origin: OriginTrait, + ::Origin: OriginTrait, { #[pallet::weight(*weight)] pub fn log(origin: OriginFor, i: u32, weight: Weight) -> DispatchResult { diff --git a/frame/sudo/src/mock.rs b/frame/sudo/src/mock.rs index 410807789069b..2e2a4abafcd98 100644 --- a/frame/sudo/src/mock.rs +++ b/frame/sudo/src/mock.rs @@ -34,7 +34,6 @@ use sp_runtime::{ // Logger module to track execution. #[frame_support::pallet] pub mod logger { - use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; diff --git a/frame/support/procedural/src/construct_runtime/mod.rs b/frame/support/procedural/src/construct_runtime/mod.rs index 249eb072001c9..2a86869382c93 100644 --- a/frame/support/procedural/src/construct_runtime/mod.rs +++ b/frame/support/procedural/src/construct_runtime/mod.rs @@ -241,6 +241,7 @@ fn construct_runtime_final_expansion( expand::expand_outer_inherent(&name, &block, &unchecked_extrinsic, &pallets, &scrate); let validate_unsigned = expand::expand_outer_validate_unsigned(&name, &pallets, &scrate); let integrity_test = decl_integrity_test(&scrate); + let static_assertions = decl_static_assertions(&name, &pallets, &scrate); let res = quote!( #scrate_decl @@ -282,6 +283,8 @@ fn construct_runtime_final_expansion( #validate_unsigned #integrity_test + + #static_assertions ); Ok(res) @@ -471,3 +474,34 @@ fn decl_integrity_test(scrate: &TokenStream2) -> TokenStream2 { } ) } + +fn decl_static_assertions( + runtime: &Ident, + pallet_decls: &[Pallet], + scrate: &TokenStream2, +) -> TokenStream2 { + let error_encoded_size_check = pallet_decls.iter().map(|decl| { + let path = &decl.path; + let assert_message = format!( + "The maximum encoded size of the error type in the `{}` pallet exceeds \ + `MAX_MODULE_ERROR_ENCODED_SIZE`", + decl.name, + ); + + quote! { + #scrate::tt_call! { + macro = [{ #path::tt_error_token }] + frame_support = [{ #scrate }] + ~~> #scrate::assert_error_encoded_size! { + path = [{ #path }] + runtime = [{ #runtime }] + assert_message = [{ #assert_message }] + } + } + } + }); + + quote! { + #(#error_encoded_size_check)* + } +} diff --git a/frame/support/procedural/src/lib.rs b/frame/support/procedural/src/lib.rs index e2233fff72285..92564e94493c1 100644 --- a/frame/support/procedural/src/lib.rs +++ b/frame/support/procedural/src/lib.rs @@ -28,9 +28,11 @@ mod dummy_part_checker; mod key_prefix; mod match_and_insert; mod pallet; +mod pallet_error; mod partial_eq_no_bound; mod storage; mod transactional; +mod tt_macro; use proc_macro::TokenStream; use std::{cell::RefCell, str::FromStr}; @@ -41,9 +43,9 @@ thread_local! { static COUNTER: RefCell = RefCell::new(Counter(0)); } -/// Counter to generate a relatively unique identifier for macros querying for the existence of -/// pallet parts. This is necessary because declarative macros gets hoisted to the crate root, -/// which shares the namespace with other pallets containing the very same query macros. +/// Counter to generate a relatively unique identifier for macros. This is necessary because +/// declarative macros gets hoisted to the crate root, which shares the namespace with other pallets +/// containing the very same macros. struct Counter(u64); impl Counter { @@ -562,3 +564,14 @@ pub fn __generate_dummy_part_checker(input: TokenStream) -> TokenStream { pub fn match_and_insert(input: TokenStream) -> TokenStream { match_and_insert::match_and_insert(input) } + +#[proc_macro_derive(PalletError, attributes(codec))] +pub fn derive_pallet_error(input: TokenStream) -> TokenStream { + pallet_error::derive_pallet_error(input) +} + +/// Internal macro used by `frame_support` to create tt-call-compliant macros +#[proc_macro] +pub fn __create_tt_macro(input: TokenStream) -> TokenStream { + tt_macro::create_tt_return_macro(input) +} diff --git a/frame/support/procedural/src/pallet/expand/error.rs b/frame/support/procedural/src/pallet/expand/error.rs index 184fa86d3ae19..86b06d737decf 100644 --- a/frame/support/procedural/src/pallet/expand/error.rs +++ b/frame/support/procedural/src/pallet/expand/error.rs @@ -15,20 +15,48 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::pallet::Def; +use crate::{ + pallet::{parse::error::VariantField, Def}, + COUNTER, +}; use frame_support_procedural_tools::get_doc_literals; +use syn::spanned::Spanned; /// /// * impl various trait on Error pub fn expand_error(def: &mut Def) -> proc_macro2::TokenStream { - let error = if let Some(error) = &def.error { error } else { return Default::default() }; + let count = COUNTER.with(|counter| counter.borrow_mut().inc()); + let error_token_unique_id = + syn::Ident::new(&format!("__tt_error_token_{}", count), def.item.span()); - let error_ident = &error.error; let frame_support = &def.frame_support; let frame_system = &def.frame_system; + let config_where_clause = &def.config.where_clause; + + let error = if let Some(error) = &def.error { + error + } else { + return quote::quote! { + #[macro_export] + #[doc(hidden)] + macro_rules! #error_token_unique_id { + { + $caller:tt + frame_support = [{ $($frame_support:ident)::* }] + } => { + $($frame_support::)*tt_return! { + $caller + } + }; + } + + pub use #error_token_unique_id as tt_error_token; + } + }; + + let error_ident = &error.error; let type_impl_gen = &def.type_impl_generics(error.attr_span); let type_use_gen = &def.type_use_generics(error.attr_span); - let config_where_clause = &def.config.where_clause; let phantom_variant: syn::Variant = syn::parse_quote!( #[doc(hidden)] @@ -39,13 +67,19 @@ pub fn expand_error(def: &mut Def) -> proc_macro2::TokenStream { ) ); - let as_u8_matches = error.variants.iter().enumerate().map( - |(i, (variant, _))| quote::quote_spanned!(error.attr_span => Self::#variant => #i as u8,), - ); - - let as_str_matches = error.variants.iter().map(|(variant, _)| { - let variant_str = format!("{}", variant); - quote::quote_spanned!(error.attr_span => Self::#variant => #variant_str,) + let as_str_matches = error.variants.iter().map(|(variant, field_ty, _)| { + let variant_str = variant.to_string(); + match field_ty { + Some(VariantField { is_named: true }) => { + quote::quote_spanned!(error.attr_span => Self::#variant { .. } => #variant_str,) + }, + Some(VariantField { is_named: false }) => { + quote::quote_spanned!(error.attr_span => Self::#variant(..) => #variant_str,) + }, + None => { + quote::quote_spanned!(error.attr_span => Self::#variant => #variant_str,) + }, + } }); let error_item = { @@ -62,9 +96,14 @@ pub fn expand_error(def: &mut Def) -> proc_macro2::TokenStream { let capture_docs = if cfg!(feature = "no-metadata-docs") { "never" } else { "always" }; // derive TypeInfo for error metadata - error_item - .attrs - .push(syn::parse_quote!( #[derive(#frame_support::scale_info::TypeInfo)] )); + error_item.attrs.push(syn::parse_quote! { + #[derive( + #frame_support::codec::Encode, + #frame_support::codec::Decode, + #frame_support::scale_info::TypeInfo, + #frame_support::PalletError, + )] + }); error_item.attrs.push(syn::parse_quote!( #[scale_info(skip_type_params(#type_use_gen), capture_docs = #capture_docs)] )); @@ -90,14 +129,6 @@ pub fn expand_error(def: &mut Def) -> proc_macro2::TokenStream { } impl<#type_impl_gen> #error_ident<#type_use_gen> #config_where_clause { - #[doc(hidden)] - pub fn as_u8(&self) -> u8 { - match &self { - Self::__Ignore(_, _) => unreachable!("`__Ignore` can never be constructed"), - #( #as_u8_matches )* - } - } - #[doc(hidden)] pub fn as_str(&self) -> &'static str { match &self { @@ -120,18 +151,37 @@ pub fn expand_error(def: &mut Def) -> proc_macro2::TokenStream { #config_where_clause { fn from(err: #error_ident<#type_use_gen>) -> Self { + use #frame_support::codec::Encode; let index = < ::PalletInfo as #frame_support::traits::PalletInfo >::index::>() .expect("Every active module has an index in the runtime; qed") as u8; + let mut encoded = err.encode(); + encoded.resize(#frame_support::MAX_MODULE_ERROR_ENCODED_SIZE, 0); #frame_support::sp_runtime::DispatchError::Module(#frame_support::sp_runtime::ModuleError { index, - error: err.as_u8(), + error: core::convert::TryInto::try_into(encoded).expect("encoded error is resized to be equal to the maximum encoded error size; qed"), message: Some(err.as_str()), }) } } + + #[macro_export] + #[doc(hidden)] + macro_rules! #error_token_unique_id { + { + $caller:tt + frame_support = [{ $($frame_support:ident)::* }] + } => { + $($frame_support::)*tt_return! { + $caller + error = [{ #error_ident }] + } + }; + } + + pub use #error_token_unique_id as tt_error_token; ) } diff --git a/frame/support/procedural/src/pallet/parse/error.rs b/frame/support/procedural/src/pallet/parse/error.rs index 419770386bf69..0ec49aa0adb49 100644 --- a/frame/support/procedural/src/pallet/parse/error.rs +++ b/frame/support/procedural/src/pallet/parse/error.rs @@ -18,20 +18,26 @@ use super::helper; use frame_support_procedural_tools::get_doc_literals; use quote::ToTokens; -use syn::spanned::Spanned; +use syn::{spanned::Spanned, Fields}; /// List of additional token to be used for parsing. mod keyword { syn::custom_keyword!(Error); } +/// Records information about the error enum variants. +pub struct VariantField { + /// Whether or not the field is named, i.e. whether it is a tuple variant or struct variant. + pub is_named: bool, +} + /// This checks error declaration as a enum declaration with only variants without fields nor /// discriminant. pub struct ErrorDef { /// The index of error item in pallet module. pub index: usize, - /// Variants ident and doc literals (ordered as declaration order) - pub variants: Vec<(syn::Ident, Vec)>, + /// Variants ident, optional field and doc literals (ordered as declaration order) + pub variants: Vec<(syn::Ident, Option, Vec)>, /// A set of usage of instance, must be check for consistency with trait. pub instances: Vec, /// The keyword error used (contains span). @@ -70,18 +76,19 @@ impl ErrorDef { .variants .iter() .map(|variant| { - if !matches!(variant.fields, syn::Fields::Unit) { - let msg = "Invalid pallet::error, unexpected fields, must be `Unit`"; - return Err(syn::Error::new(variant.fields.span(), msg)) - } + let field_ty = match &variant.fields { + Fields::Unit => None, + Fields::Named(_) => Some(VariantField { is_named: true }), + Fields::Unnamed(_) => Some(VariantField { is_named: false }), + }; if variant.discriminant.is_some() { - let msg = "Invalid pallet::error, unexpected discriminant, discriminant \ + let msg = "Invalid pallet::error, unexpected discriminant, discriminants \ are not supported"; let span = variant.discriminant.as_ref().unwrap().0.span(); return Err(syn::Error::new(span, msg)) } - Ok((variant.ident.clone(), get_doc_literals(&variant.attrs))) + Ok((variant.ident.clone(), field_ty, get_doc_literals(&variant.attrs))) }) .collect::>()?; diff --git a/frame/support/procedural/src/pallet_error.rs b/frame/support/procedural/src/pallet_error.rs new file mode 100644 index 0000000000000..216168131e43d --- /dev/null +++ b/frame/support/procedural/src/pallet_error.rs @@ -0,0 +1,197 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use frame_support_procedural_tools::generate_crate_access_2018; +use quote::ToTokens; +use std::str::FromStr; + +// Derive `PalletError` +pub fn derive_pallet_error(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let syn::DeriveInput { ident: name, generics, data, .. } = match syn::parse(input) { + Ok(input) => input, + Err(e) => return e.to_compile_error().into(), + }; + + let frame_support = match generate_crate_access_2018("frame-support") { + Ok(c) => c, + Err(e) => return e.into_compile_error().into(), + }; + let frame_support = &frame_support; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let max_encoded_size = match data { + syn::Data::Struct(syn::DataStruct { fields, .. }) => match fields { + syn::Fields::Named(syn::FieldsNamed { named: fields, .. }) | + syn::Fields::Unnamed(syn::FieldsUnnamed { unnamed: fields, .. }) => { + let maybe_field_tys = fields + .iter() + .map(|f| generate_field_types(f, &frame_support)) + .collect::>>(); + let field_tys = match maybe_field_tys { + Ok(tys) => tys.into_iter().flatten(), + Err(e) => return e.into_compile_error().into(), + }; + quote::quote! { + 0_usize + #( + .saturating_add(< + #field_tys as #frame_support::traits::PalletError + >::MAX_ENCODED_SIZE) + )* + } + }, + syn::Fields::Unit => quote::quote!(0), + }, + syn::Data::Enum(syn::DataEnum { variants, .. }) => { + let field_tys = variants + .iter() + .map(|variant| generate_variant_field_types(variant, &frame_support)) + .collect::>>, syn::Error>>(); + + let field_tys = match field_tys { + Ok(tys) => tys.into_iter().flatten().collect::>(), + Err(e) => return e.to_compile_error().into(), + }; + + // We start with `1`, because the discriminant of an enum is stored as u8 + if field_tys.is_empty() { + quote::quote!(1) + } else { + let variant_sizes = field_tys.into_iter().map(|variant_field_tys| { + quote::quote! { + 1_usize + #(.saturating_add(< + #variant_field_tys as #frame_support::traits::PalletError + >::MAX_ENCODED_SIZE))* + } + }); + + quote::quote! {{ + let mut size = 1_usize; + let mut tmp = 0_usize; + #( + tmp = #variant_sizes; + size = if tmp > size { tmp } else { size }; + tmp = 0_usize; + )* + size + }} + } + }, + syn::Data::Union(syn::DataUnion { union_token, .. }) => { + let msg = "Cannot derive `PalletError` for union; please implement it directly"; + return syn::Error::new(union_token.span, msg).into_compile_error().into() + }, + }; + + quote::quote!( + const _: () = { + impl #impl_generics #frame_support::traits::PalletError + for #name #ty_generics #where_clause + { + const MAX_ENCODED_SIZE: usize = #max_encoded_size; + } + }; + ) + .into() +} + +fn generate_field_types( + field: &syn::Field, + scrate: &syn::Ident, +) -> syn::Result> { + let attrs = &field.attrs; + + for attr in attrs { + if attr.path.is_ident("codec") { + match attr.parse_meta()? { + syn::Meta::List(ref meta_list) if meta_list.nested.len() == 1 => { + match meta_list + .nested + .first() + .expect("Just checked that there is one item; qed") + { + syn::NestedMeta::Meta(syn::Meta::Path(path)) + if path.get_ident().map_or(false, |i| i == "skip") => + return Ok(None), + + syn::NestedMeta::Meta(syn::Meta::Path(path)) + if path.get_ident().map_or(false, |i| i == "compact") => + { + let field_ty = &field.ty; + return Ok(Some(quote::quote!(#scrate::codec::Compact<#field_ty>))) + }, + + syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue { + path, + lit: syn::Lit::Str(lit_str), + .. + })) if path.get_ident().map_or(false, |i| i == "encoded_as") => { + let ty = proc_macro2::TokenStream::from_str(&lit_str.value())?; + return Ok(Some(ty)) + }, + + _ => (), + } + }, + _ => (), + } + } + } + + Ok(Some(field.ty.to_token_stream())) +} + +fn generate_variant_field_types( + variant: &syn::Variant, + scrate: &syn::Ident, +) -> syn::Result>> { + let attrs = &variant.attrs; + + for attr in attrs { + if attr.path.is_ident("codec") { + match attr.parse_meta()? { + syn::Meta::List(ref meta_list) if meta_list.nested.len() == 1 => { + match meta_list + .nested + .first() + .expect("Just checked that there is one item; qed") + { + syn::NestedMeta::Meta(syn::Meta::Path(path)) + if path.get_ident().map_or(false, |i| i == "skip") => + return Ok(None), + + _ => (), + } + }, + _ => (), + } + } + } + + match &variant.fields { + syn::Fields::Named(syn::FieldsNamed { named: fields, .. }) | + syn::Fields::Unnamed(syn::FieldsUnnamed { unnamed: fields, .. }) => { + let field_tys = fields + .iter() + .map(|field| generate_field_types(field, scrate)) + .collect::>>()?; + Ok(Some(field_tys.into_iter().flatten().collect())) + }, + syn::Fields::Unit => Ok(None), + } +} diff --git a/frame/support/procedural/src/tt_macro.rs b/frame/support/procedural/src/tt_macro.rs new file mode 100644 index 0000000000000..0a270a7173cfc --- /dev/null +++ b/frame/support/procedural/src/tt_macro.rs @@ -0,0 +1,110 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Implementation of the `create_tt_return_macro` macro + +use crate::COUNTER; +use frame_support_procedural_tools::generate_crate_access_2018; +use proc_macro2::{Ident, TokenStream}; +use quote::format_ident; + +struct CreateTtReturnMacroDef { + name: Ident, + args: Vec<(Ident, TokenStream)>, +} + +impl syn::parse::Parse for CreateTtReturnMacroDef { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let name = input.parse()?; + let _ = input.parse::()?; + + let mut args = Vec::new(); + while !input.is_empty() { + let mut value; + let key: Ident = input.parse()?; + let _ = input.parse::()?; + let _: syn::token::Bracket = syn::bracketed!(value in input); + let _: syn::token::Brace = syn::braced!(value in value); + let value: TokenStream = value.parse()?; + + args.push((key, value)) + } + + Ok(Self { name, args }) + } +} + +/// A proc macro that accepts a name and any number of key-value pairs, to be used to create a +/// declarative macro that follows tt-call conventions and simply calls [`tt_call::tt_return`], +/// accepting an optional `frame-support` argument and returning the key-value pairs that were +/// supplied to the proc macro. +/// +/// # Example +/// ```ignore +/// __create_tt_macro! { +/// my_tt_macro, +/// foo = [{ bar }] +/// } +/// +/// // Creates the following declarative macro: +/// +/// macro_rules! my_tt_macro { +/// { +/// $caller:tt +/// $(frame_support = [{ $($frame_support:ident)::* }])? +/// } => { +/// frame_support::tt_return! { +/// $caller +/// foo = [{ bar }] +/// } +/// } +/// } +/// ``` +pub fn create_tt_return_macro(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let CreateTtReturnMacroDef { name, args } = + syn::parse_macro_input!(input as CreateTtReturnMacroDef); + + let frame_support = match generate_crate_access_2018("frame-support") { + Ok(i) => i, + Err(e) => return e.into_compile_error().into(), + }; + let (keys, values): (Vec<_>, Vec<_>) = args.into_iter().unzip(); + let count = COUNTER.with(|counter| counter.borrow_mut().inc()); + let unique_name = format_ident!("{}_{}", name, count); + + let decl_macro = quote::quote! { + #[macro_export] + #[doc(hidden)] + macro_rules! #unique_name { + { + $caller:tt + $(frame_support = [{ $($frame_support:ident)::* }])? + } => { + #frame_support::tt_return! { + $caller + #( + #keys = [{ #values }] + )* + } + } + } + + pub use #unique_name as #name; + }; + + decl_macro.into() +} diff --git a/frame/support/src/dispatch.rs b/frame/support/src/dispatch.rs index ece5173f2f4ca..7ccf796167297 100644 --- a/frame/support/src/dispatch.rs +++ b/frame/support/src/dispatch.rs @@ -2003,6 +2003,10 @@ macro_rules! decl_module { pub type Pallet<$trait_instance $(, $instance $( = $module_default_instance)?)?> = $mod_type<$trait_instance $(, $instance)?>; + $crate::__create_tt_macro! { + tt_error_token, + } + $crate::decl_module! { @impl_on_initialize { $system } diff --git a/frame/support/src/error.rs b/frame/support/src/error.rs index 4880bba5c5e97..764376a4e1dc2 100644 --- a/frame/support/src/error.rs +++ b/frame/support/src/error.rs @@ -85,7 +85,12 @@ macro_rules! decl_error { } ) => { $(#[$attr])* - #[derive($crate::scale_info::TypeInfo)] + #[derive( + $crate::codec::Encode, + $crate::codec::Decode, + $crate::scale_info::TypeInfo, + $crate::PalletError, + )] #[scale_info(skip_type_params($generic $(, $inst_generic)?), capture_docs = "always")] pub enum $error<$generic: $trait $(, $inst_generic: $instance)?> $( where $( $where_ty: $where_bound ),* )? @@ -114,17 +119,6 @@ macro_rules! decl_error { impl<$generic: $trait $(, $inst_generic: $instance)?> $error<$generic $(, $inst_generic)?> $( where $( $where_ty: $where_bound ),* )? { - fn as_u8(&self) -> u8 { - $crate::decl_error! { - @GENERATE_AS_U8 - self - $error - {} - 0, - $( $name ),* - } - } - fn as_str(&self) -> &'static str { match self { Self::__Ignore(_, _) => unreachable!("`__Ignore` can never be constructed"), @@ -149,47 +143,19 @@ macro_rules! decl_error { $( where $( $where_ty: $where_bound ),* )? { fn from(err: $error<$generic $(, $inst_generic)?>) -> Self { + use $crate::codec::Encode; let index = <$generic::PalletInfo as $crate::traits::PalletInfo> ::index::<$module<$generic $(, $inst_generic)?>>() .expect("Every active module has an index in the runtime; qed") as u8; + let mut error = err.encode(); + error.resize($crate::MAX_MODULE_ERROR_ENCODED_SIZE, 0); $crate::sp_runtime::DispatchError::Module($crate::sp_runtime::ModuleError { index, - error: err.as_u8(), + error: core::convert::TryInto::try_into(error).expect("encoded error is resized to be equal to the maximum encoded error size; qed"), message: Some(err.as_str()), }) } } }; - (@GENERATE_AS_U8 - $self:ident - $error:ident - { $( $generated:tt )* } - $index:expr, - $name:ident - $( , $rest:ident )* - ) => { - $crate::decl_error! { - @GENERATE_AS_U8 - $self - $error - { - $( $generated )* - $error::$name => $index, - } - $index + 1, - $( $rest ),* - } - }; - (@GENERATE_AS_U8 - $self:ident - $error:ident - { $( $generated:tt )* } - $index:expr, - ) => { - match $self { - $error::__Ignore(_, _) => unreachable!("`__Ignore` can never be constructed"), - $( $generated )* - } - } } diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 3988b5e9af219..f290b3ed9d533 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -93,7 +93,9 @@ pub use self::{ StorageMap, StorageNMap, StoragePrefixedMap, StorageValue, }, }; -pub use sp_runtime::{self, print, traits::Printable, ConsensusEngineId}; +pub use sp_runtime::{ + self, print, traits::Printable, ConsensusEngineId, MAX_MODULE_ERROR_ENCODED_SIZE, +}; use codec::{Decode, Encode}; use scale_info::TypeInfo; @@ -103,7 +105,7 @@ use sp_runtime::TypeId; pub const LOG_TARGET: &'static str = "runtime::frame-support"; /// A type that cannot be instantiated. -#[derive(Debug, PartialEq, Eq, Clone, TypeInfo)] +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] pub enum Never {} /// A pallet identifier. These are per pallet and should be stored in a registry somewhere. @@ -598,11 +600,12 @@ pub fn debug(data: &impl sp_std::fmt::Debug) { #[doc(inline)] pub use frame_support_procedural::{ - construct_runtime, decl_storage, match_and_insert, transactional, RuntimeDebugNoBound, + construct_runtime, decl_storage, match_and_insert, transactional, PalletError, + RuntimeDebugNoBound, }; #[doc(hidden)] -pub use frame_support_procedural::__generate_dummy_part_checker; +pub use frame_support_procedural::{__create_tt_macro, __generate_dummy_part_checker}; /// Derive [`Clone`] but do not bound any generic. /// @@ -847,6 +850,32 @@ macro_rules! assert_ok { }; } +/// Assert that the maximum encoding size does not exceed the value defined in +/// [`MAX_MODULE_ERROR_ENCODED_SIZE`] during compilation. +/// +/// This macro is intended to be used in conjunction with `tt_call!`. +#[macro_export] +macro_rules! assert_error_encoded_size { + { + path = [{ $($path:ident)::+ }] + runtime = [{ $runtime:ident }] + assert_message = [{ $assert_message:literal }] + error = [{ $error:ident }] + } => { + const _: () = assert!( + < + $($path::)+$error<$runtime> as $crate::traits::PalletError + >::MAX_ENCODED_SIZE <= $crate::MAX_MODULE_ERROR_ENCODED_SIZE, + $assert_message + ); + }; + { + path = [{ $($path:ident)::+ }] + runtime = [{ $runtime:ident }] + assert_message = [{ $assert_message:literal }] + } => {}; +} + #[cfg(feature = "std")] #[doc(hidden)] pub use serde::{Deserialize, Serialize}; @@ -1361,6 +1390,7 @@ pub mod pallet_prelude { TransactionTag, TransactionValidity, TransactionValidityError, UnknownTransaction, ValidTransaction, }, + MAX_MODULE_ERROR_ENCODED_SIZE, }; pub use sp_std::marker::PhantomData; } @@ -1638,10 +1668,25 @@ pub mod pallet_prelude { /// pub enum Error { /// /// $some_optional_doc /// $SomeFieldLessVariant, +/// /// $some_more_optional_doc +/// $SomeVariantWithOneField(FieldType), /// ... /// } /// ``` -/// I.e. a regular rust enum named `Error`, with generic `T` and fieldless variants. +/// I.e. a regular rust enum named `Error`, with generic `T` and fieldless or multiple-field +/// variants. +/// +/// Any field type in the enum variants must implement [`scale_info::TypeInfo`] in order to be +/// properly used in the metadata, and its encoded size should be as small as possible, +/// preferably 1 byte in size in order to reduce storage size. The error enum itself has an +/// absolute maximum encoded size specified by [`MAX_MODULE_ERROR_ENCODED_SIZE`]. +/// +/// Field types in enum variants must also implement [`PalletError`](traits::PalletError), +/// otherwise the pallet will fail to compile. Rust primitive types have already implemented +/// the [`PalletError`](traits::PalletError) trait along with some commonly used stdlib types +/// such as `Option` and `PhantomData`, and hence in most use cases, a manual implementation is +/// not necessary and is discouraged. +/// /// The generic `T` mustn't bound anything and where clause is not allowed. But bounds and /// where clause shouldn't be needed for any usecase. /// diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index a8ce78ae9dabc..40afc0d337f43 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -46,6 +46,9 @@ pub use validation::{ ValidatorSetWithIdentification, VerifySeal, }; +mod error; +pub use error::PalletError; + mod filter; pub use filter::{ClearFilterGuard, FilterStack, FilterStackGuard, InstanceFilter, IntegrityTest}; diff --git a/frame/support/src/traits/error.rs b/frame/support/src/traits/error.rs new file mode 100644 index 0000000000000..8e26891669e65 --- /dev/null +++ b/frame/support/src/traits/error.rs @@ -0,0 +1,95 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Traits for describing and constraining pallet error types. +use codec::{Compact, Decode, Encode}; +use sp_std::marker::PhantomData; + +/// Trait indicating that the implementing type is going to be included as a field in a variant of +/// the `#[pallet::error]` enum type. +/// +/// ## Notes +/// +/// The pallet error enum has a maximum encoded size as defined by +/// [`frame_support::MAX_MODULE_ERROR_ENCODED_SIZE`]. If the pallet error type exceeds this size +/// limit, a static assertion during compilation will fail. The compilation error will be in the +/// format of `error[E0080]: evaluation of constant value failed` due to the usage of +/// const assertions. +pub trait PalletError: Encode + Decode { + /// The maximum encoded size for the implementing type. + /// + /// This will be used to check whether the pallet error type is less than or equal to + /// [`frame_support::MAX_MODULE_ERROR_ENCODED_SIZE`], and if it is, a compilation error will be + /// thrown. + const MAX_ENCODED_SIZE: usize; +} + +macro_rules! impl_for_types { + (size: $size:expr, $($typ:ty),+) => { + $( + impl PalletError for $typ { + const MAX_ENCODED_SIZE: usize = $size; + } + )+ + }; +} + +impl_for_types!(size: 0, (), crate::Never); +impl_for_types!(size: 1, u8, i8, bool); +impl_for_types!(size: 2, u16, i16, Compact); +impl_for_types!(size: 4, u32, i32, Compact); +impl_for_types!(size: 5, Compact); +impl_for_types!(size: 8, u64, i64); +impl_for_types!(size: 9, Compact); +// Contains a u64 for secs and u32 for nanos, hence 12 bytes +impl_for_types!(size: 12, core::time::Duration); +impl_for_types!(size: 16, u128, i128); +impl_for_types!(size: 17, Compact); + +impl PalletError for PhantomData { + const MAX_ENCODED_SIZE: usize = 0; +} + +impl PalletError for core::ops::Range { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE.saturating_mul(2); +} + +impl PalletError for [T; N] { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE.saturating_mul(N); +} + +impl PalletError for Option { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE.saturating_add(1); +} + +impl PalletError for Result { + const MAX_ENCODED_SIZE: usize = if T::MAX_ENCODED_SIZE > E::MAX_ENCODED_SIZE { + T::MAX_ENCODED_SIZE + } else { + E::MAX_ENCODED_SIZE + } + .saturating_add(1); +} + +#[impl_trait_for_tuples::impl_for_tuples(1, 18)] +impl PalletError for Tuple { + const MAX_ENCODED_SIZE: usize = { + let mut size = 0_usize; + for_tuples!( #(size = size.saturating_add(Tuple::MAX_ENCODED_SIZE);)* ); + size + }; +} diff --git a/frame/support/test/tests/construct_runtime.rs b/frame/support/test/tests/construct_runtime.rs index b3f8feb8aa4b2..804deb08919a4 100644 --- a/frame/support/test/tests/construct_runtime.rs +++ b/frame/support/test/tests/construct_runtime.rs @@ -271,139 +271,95 @@ pub type Header = generic::Header; pub type Block = generic::Block; pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; -mod origin_test { - use super::{module3, nested, system, Block, UncheckedExtrinsic}; - use frame_support::traits::{Contains, OriginTrait}; - - impl nested::module3::Config for RuntimeOriginTest {} - impl module3::Config for RuntimeOriginTest {} - - pub struct BaseCallFilter; - impl Contains for BaseCallFilter { - fn contains(c: &Call) -> bool { - match c { - Call::NestedModule3(_) => true, - _ => false, - } - } - } - - impl system::Config for RuntimeOriginTest { - type BaseCallFilter = BaseCallFilter; - type Hash = super::H256; - type Origin = Origin; - type BlockNumber = super::BlockNumber; - type AccountId = u32; - type Event = Event; - type PalletInfo = PalletInfo; - type Call = Call; - type DbWeight = (); - } - - frame_support::construct_runtime!( - pub enum RuntimeOriginTest where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic - { - System: system::{Pallet, Event, Origin}, - NestedModule3: nested::module3::{Pallet, Origin, Call}, - Module3: module3::{Pallet, Origin, Call}, - } - ); - - #[test] - fn origin_default_filter() { - let accepted_call = nested::module3::Call::fail {}.into(); - let rejected_call = module3::Call::fail {}.into(); - - assert_eq!(Origin::root().filter_call(&accepted_call), true); - assert_eq!(Origin::root().filter_call(&rejected_call), true); - assert_eq!(Origin::none().filter_call(&accepted_call), true); - assert_eq!(Origin::none().filter_call(&rejected_call), false); - assert_eq!(Origin::signed(0).filter_call(&accepted_call), true); - assert_eq!(Origin::signed(0).filter_call(&rejected_call), false); - assert_eq!(Origin::from(Some(0)).filter_call(&accepted_call), true); - assert_eq!(Origin::from(Some(0)).filter_call(&rejected_call), false); - assert_eq!(Origin::from(None).filter_call(&accepted_call), true); - assert_eq!(Origin::from(None).filter_call(&rejected_call), false); - assert_eq!(Origin::from(super::nested::module3::Origin).filter_call(&accepted_call), true); - assert_eq!(Origin::from(super::nested::module3::Origin).filter_call(&rejected_call), false); - - let mut origin = Origin::from(Some(0)); - origin.add_filter(|c| matches!(c, Call::Module3(_))); - assert_eq!(origin.filter_call(&accepted_call), false); - assert_eq!(origin.filter_call(&rejected_call), false); - - // Now test for root origin and filters: - let mut origin = Origin::from(Some(0)); - origin.set_caller_from(Origin::root()); - assert!(matches!(origin.caller, OriginCaller::system(super::system::RawOrigin::Root))); - - // Root origin bypass all filter. - assert_eq!(origin.filter_call(&accepted_call), true); - assert_eq!(origin.filter_call(&rejected_call), true); - - origin.set_caller_from(Origin::from(Some(0))); - - // Back to another signed origin, the filtered are now effective again - assert_eq!(origin.filter_call(&accepted_call), true); - assert_eq!(origin.filter_call(&rejected_call), false); - - origin.set_caller_from(Origin::root()); - origin.reset_filter(); - - // Root origin bypass all filter, even when they are reset. - assert_eq!(origin.filter_call(&accepted_call), true); - assert_eq!(origin.filter_call(&rejected_call), true); - } -} - #[test] fn check_modules_error_type() { assert_eq!( Module1_1::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { index: 31, error: 0, message: Some("Something") })), + Err(DispatchError::Module(ModuleError { + index: 31, + error: [0; 4], + message: Some("Something") + })), ); assert_eq!( Module2::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { index: 32, error: 0, message: Some("Something") })), + Err(DispatchError::Module(ModuleError { + index: 32, + error: [0; 4], + message: Some("Something") + })), ); assert_eq!( Module1_2::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { index: 33, error: 0, message: Some("Something") })), + Err(DispatchError::Module(ModuleError { + index: 33, + error: [0; 4], + message: Some("Something") + })), ); assert_eq!( NestedModule3::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { index: 34, error: 0, message: Some("Something") })), + Err(DispatchError::Module(ModuleError { + index: 34, + error: [0; 4], + message: Some("Something") + })), ); assert_eq!( Module1_3::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { index: 6, error: 0, message: Some("Something") })), + Err(DispatchError::Module(ModuleError { + index: 6, + error: [0; 4], + message: Some("Something") + })), ); assert_eq!( Module1_4::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { index: 3, error: 0, message: Some("Something") })), + Err(DispatchError::Module(ModuleError { + index: 3, + error: [0; 4], + message: Some("Something") + })), ); assert_eq!( Module1_5::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { index: 4, error: 0, message: Some("Something") })), + Err(DispatchError::Module(ModuleError { + index: 4, + error: [0; 4], + message: Some("Something") + })), ); assert_eq!( Module1_6::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { index: 1, error: 0, message: Some("Something") })), + Err(DispatchError::Module(ModuleError { + index: 1, + error: [0; 4], + message: Some("Something") + })), ); assert_eq!( Module1_7::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { index: 2, error: 0, message: Some("Something") })), + Err(DispatchError::Module(ModuleError { + index: 2, + error: [0; 4], + message: Some("Something") + })), ); assert_eq!( Module1_8::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { index: 12, error: 0, message: Some("Something") })), + Err(DispatchError::Module(ModuleError { + index: 12, + error: [0; 4], + message: Some("Something") + })), ); assert_eq!( Module1_9::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { index: 13, error: 0, message: Some("Something") })), + Err(DispatchError::Module(ModuleError { + index: 13, + error: [0; 4], + message: Some("Something") + })), ); } diff --git a/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.rs b/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.rs new file mode 100644 index 0000000000000..827d8a58af733 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.rs @@ -0,0 +1,85 @@ +use frame_support::construct_runtime; +use sp_runtime::{generic, traits::BlakeTwo256}; +use sp_core::sr25519; + +#[frame_support::pallet] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::error] + pub enum Error { + MyError(crate::Nested1), + } +} + +#[derive(scale_info::TypeInfo, frame_support::PalletError, codec::Encode, codec::Decode)] +pub enum Nested1 { + Nested2(Nested2) +} + +#[derive(scale_info::TypeInfo, frame_support::PalletError, codec::Encode, codec::Decode)] +pub enum Nested2 { + Nested3(Nested3) +} + +#[derive(scale_info::TypeInfo, frame_support::PalletError, codec::Encode, codec::Decode)] +pub enum Nested3 { + Nested4(Nested4) +} + +#[derive(scale_info::TypeInfo, frame_support::PalletError, codec::Encode, codec::Decode)] +pub enum Nested4 { + Num(u8) +} + +pub type Signature = sr25519::Signature; +pub type BlockNumber = u32; +pub type Header = generic::Header; +pub type Block = generic::Block; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + +impl pallet::Config for Runtime {} + +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type Origin = Origin; + type Index = u64; + type BlockNumber = u32; + type Call = Call; + type Hash = sp_runtime::testing::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = sp_runtime::traits::IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = frame_support::traits::ConstU32<250>; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +construct_runtime! { + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system::{Pallet, Call, Storage, Config, Event}, + Pallet: pallet::{Pallet}, + } +} + +fn main() {} diff --git a/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.stderr b/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.stderr new file mode 100644 index 0000000000000..161873866b6f3 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.stderr @@ -0,0 +1,13 @@ +error[E0080]: evaluation of constant value failed + --> tests/construct_runtime_ui/pallet_error_too_large.rs:74:1 + | +74 | / construct_runtime! { +75 | | pub enum Runtime where +76 | | Block = Block, +77 | | NodeBlock = Block, +... | +82 | | } +83 | | } + | |_^ the evaluated program panicked at 'The maximum encoded size of the error type in the `Pallet` pallet exceeds `MAX_MODULE_ERROR_ENCODED_SIZE`', $DIR/tests/construct_runtime_ui/pallet_error_too_large.rs:74:1 + | + = note: this error originates in the macro `$crate::panic::panic_2021` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/origin.rs b/frame/support/test/tests/origin.rs new file mode 100644 index 0000000000000..1def44c15b48f --- /dev/null +++ b/frame/support/test/tests/origin.rs @@ -0,0 +1,214 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Origin tests for construct_runtime macro + +#![recursion_limit = "128"] + +use frame_support::traits::{Contains, OriginTrait}; +use scale_info::TypeInfo; +use sp_core::{sr25519, H256}; +use sp_runtime::{generic, traits::BlakeTwo256}; + +mod system; + +mod nested { + use super::*; + + pub mod module { + use super::*; + + pub trait Config: system::Config {} + + frame_support::decl_module! { + pub struct Module for enum Call + where origin: ::Origin, system=system + { + #[weight = 0] + pub fn fail(_origin) -> frame_support::dispatch::DispatchResult { + Err(Error::::Something.into()) + } + } + } + + #[derive(Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo)] + pub struct Origin; + + frame_support::decl_event! { + pub enum Event { + A, + } + } + + frame_support::decl_error! { + pub enum Error for Module { + Something + } + } + + frame_support::decl_storage! { + trait Store for Module as Module {} + add_extra_genesis { + build(|_config| {}) + } + } + } +} + +pub mod module { + use super::*; + + pub trait Config: system::Config {} + + frame_support::decl_module! { + pub struct Module for enum Call + where origin: ::Origin, system=system + { + #[weight = 0] + pub fn fail(_origin) -> frame_support::dispatch::DispatchResult { + Err(Error::::Something.into()) + } + #[weight = 0] + pub fn aux_1(_origin, #[compact] _data: u32) -> frame_support::dispatch::DispatchResult { + unreachable!() + } + #[weight = 0] + pub fn aux_2(_origin, _data: i32, #[compact] _data2: u32) -> frame_support::dispatch::DispatchResult { + unreachable!() + } + #[weight = 0] + fn aux_3(_origin, _data: i32, _data2: String) -> frame_support::dispatch::DispatchResult { + unreachable!() + } + #[weight = 3] + fn aux_4(_origin) -> frame_support::dispatch::DispatchResult { unreachable!() } + #[weight = (5, frame_support::weights::DispatchClass::Operational)] + fn operational(_origin) { unreachable!() } + } + } + + #[derive(Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo)] + pub struct Origin(pub core::marker::PhantomData); + + frame_support::decl_event! { + pub enum Event { + A, + } + } + + frame_support::decl_error! { + pub enum Error for Module { + Something + } + } + + frame_support::decl_storage! { + trait Store for Module as Module {} + add_extra_genesis { + build(|_config| {}) + } + } +} + +impl nested::module::Config for RuntimeOriginTest {} +impl module::Config for RuntimeOriginTest {} + +pub struct BaseCallFilter; +impl Contains for BaseCallFilter { + fn contains(c: &Call) -> bool { + match c { + Call::NestedModule(_) => true, + _ => false, + } + } +} + +impl system::Config for RuntimeOriginTest { + type BaseCallFilter = BaseCallFilter; + type Hash = H256; + type Origin = Origin; + type BlockNumber = BlockNumber; + type AccountId = u32; + type Event = Event; + type PalletInfo = PalletInfo; + type Call = Call; + type DbWeight = (); +} + +frame_support::construct_runtime!( + pub enum RuntimeOriginTest where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: system::{Pallet, Event, Origin}, + NestedModule: nested::module::{Pallet, Origin, Call}, + Module: module::{Pallet, Origin, Call}, + } +); + +pub type Signature = sr25519::Signature; +pub type BlockNumber = u64; +pub type Header = generic::Header; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +pub type Block = generic::Block; + +#[test] +fn origin_default_filter() { + let accepted_call = nested::module::Call::fail {}.into(); + let rejected_call = module::Call::fail {}.into(); + + assert_eq!(Origin::root().filter_call(&accepted_call), true); + assert_eq!(Origin::root().filter_call(&rejected_call), true); + assert_eq!(Origin::none().filter_call(&accepted_call), true); + assert_eq!(Origin::none().filter_call(&rejected_call), false); + assert_eq!(Origin::signed(0).filter_call(&accepted_call), true); + assert_eq!(Origin::signed(0).filter_call(&rejected_call), false); + assert_eq!(Origin::from(Some(0)).filter_call(&accepted_call), true); + assert_eq!(Origin::from(Some(0)).filter_call(&rejected_call), false); + assert_eq!(Origin::from(None).filter_call(&accepted_call), true); + assert_eq!(Origin::from(None).filter_call(&rejected_call), false); + assert_eq!(Origin::from(nested::module::Origin).filter_call(&accepted_call), true); + assert_eq!(Origin::from(nested::module::Origin).filter_call(&rejected_call), false); + + let mut origin = Origin::from(Some(0)); + origin.add_filter(|c| matches!(c, Call::Module(_))); + assert_eq!(origin.filter_call(&accepted_call), false); + assert_eq!(origin.filter_call(&rejected_call), false); + + // Now test for root origin and filters: + let mut origin = Origin::from(Some(0)); + origin.set_caller_from(Origin::root()); + assert!(matches!(origin.caller, OriginCaller::system(system::RawOrigin::Root))); + + // Root origin bypass all filter. + assert_eq!(origin.filter_call(&accepted_call), true); + assert_eq!(origin.filter_call(&rejected_call), true); + + origin.set_caller_from(Origin::from(Some(0))); + + // Back to another signed origin, the filtered are now effective again + assert_eq!(origin.filter_call(&accepted_call), true); + assert_eq!(origin.filter_call(&rejected_call), false); + + origin.set_caller_from(Origin::root()); + origin.reset_filter(); + + // Root origin bypass all filter, even when they are reset. + assert_eq!(origin.filter_call(&accepted_call), true); + assert_eq!(origin.filter_call(&rejected_call), true); +} diff --git a/frame/support/test/tests/pallet.rs b/frame/support/test/tests/pallet.rs index 451cb2e7b889e..83f6a722f93aa 100644 --- a/frame/support/test/tests/pallet.rs +++ b/frame/support/test/tests/pallet.rs @@ -20,7 +20,7 @@ use frame_support::{ storage::unhashed, traits::{ ConstU32, GetCallName, GetStorageVersion, OnFinalize, OnGenesis, OnInitialize, - OnRuntimeUpgrade, PalletInfoAccess, StorageVersion, + OnRuntimeUpgrade, PalletError, PalletInfoAccess, StorageVersion, }, weights::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays, RuntimeDbWeight}, }; @@ -229,9 +229,14 @@ pub mod pallet { } #[pallet::error] + #[derive(PartialEq, Eq)] pub enum Error { /// doc comment put into metadata InsufficientProposersBalance, + Code(u8), + #[codec(skip)] + Skipped(u128), + CompactU8(#[codec(compact)] u8), } #[pallet::event] @@ -656,10 +661,11 @@ fn error_expand() { DispatchError::from(pallet::Error::::InsufficientProposersBalance), DispatchError::Module(ModuleError { index: 1, - error: 0, + error: [0, 0, 0, 0], message: Some("InsufficientProposersBalance") }), ); + assert_eq!( as PalletError>::MAX_ENCODED_SIZE, 3); } #[test] diff --git a/frame/support/test/tests/pallet_instance.rs b/frame/support/test/tests/pallet_instance.rs index 30b9bcda88d1e..118794e2fa20a 100644 --- a/frame/support/test/tests/pallet_instance.rs +++ b/frame/support/test/tests/pallet_instance.rs @@ -343,7 +343,7 @@ fn error_expand() { DispatchError::from(pallet::Error::::InsufficientProposersBalance), DispatchError::Module(ModuleError { index: 1, - error: 0, + error: [0; 4], message: Some("InsufficientProposersBalance") }), ); @@ -364,7 +364,7 @@ fn error_expand() { ), DispatchError::Module(ModuleError { index: 2, - error: 0, + error: [0; 4], message: Some("InsufficientProposersBalance") }), ); diff --git a/frame/support/test/tests/pallet_ui/error_no_fieldless.rs b/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.rs similarity index 50% rename from frame/support/test/tests/pallet_ui/error_no_fieldless.rs rename to frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.rs index c9d444d6f90dd..254d65866774f 100644 --- a/frame/support/test/tests/pallet_ui/error_no_fieldless.rs +++ b/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.rs @@ -1,25 +1,19 @@ #[frame_support::pallet] mod pallet { - use frame_support::pallet_prelude::Hooks; - use frame_system::pallet_prelude::BlockNumberFor; - #[pallet::config] pub trait Config: frame_system::Config {} #[pallet::pallet] pub struct Pallet(core::marker::PhantomData); - #[pallet::hooks] - impl Hooks> for Pallet {} - - #[pallet::call] - impl Pallet {} - #[pallet::error] pub enum Error { - U8(u8), + CustomError(crate::MyError), } } +#[derive(scale_info::TypeInfo, codec::Encode, codec::Decode)] +enum MyError {} + fn main() { } diff --git a/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr b/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr new file mode 100644 index 0000000000000..2a8149e309ac1 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr @@ -0,0 +1,12 @@ +error[E0277]: the trait bound `MyError: PalletError` is not satisfied + --> tests/pallet_ui/error_does_not_derive_pallet_error.rs:1:1 + | +1 | #[frame_support::pallet] + | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `PalletError` is not implemented for `MyError` + | +note: required by `MAX_ENCODED_SIZE` + --> $WORKSPACE/frame/support/src/traits/error.rs + | + | const MAX_ENCODED_SIZE: usize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the derive macro `frame_support::PalletError` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/pallet_ui/error_no_fieldless.stderr b/frame/support/test/tests/pallet_ui/error_no_fieldless.stderr deleted file mode 100644 index 1d69fbeff9aac..0000000000000 --- a/frame/support/test/tests/pallet_ui/error_no_fieldless.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: Invalid pallet::error, unexpected fields, must be `Unit` - --> $DIR/error_no_fieldless.rs:20:5 - | -20 | U8(u8), - | ^^^^ diff --git a/frame/support/test/tests/pallet_ui/pass/error_nested_types.rs b/frame/support/test/tests/pallet_ui/pass/error_nested_types.rs new file mode 100644 index 0000000000000..1b6f584af23b9 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pass/error_nested_types.rs @@ -0,0 +1,41 @@ +use codec::{Decode, Encode}; +use frame_support::PalletError; + +#[frame_support::pallet] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::error] + pub enum Error { + CustomError(crate::MyError), + } +} + +#[derive(Encode, Decode, PalletError, scale_info::TypeInfo)] +pub enum MyError { + Foo, + Bar, + Baz(NestedError), + Struct(MyStruct), + Wrapper(Wrapper), +} + +#[derive(Encode, Decode, PalletError, scale_info::TypeInfo)] +pub enum NestedError { + Quux +} + +#[derive(Encode, Decode, PalletError, scale_info::TypeInfo)] +pub struct MyStruct { + field: u8, +} + +#[derive(Encode, Decode, PalletError, scale_info::TypeInfo)] +pub struct Wrapper(bool); + +fn main() { +} diff --git a/frame/utility/src/tests.rs b/frame/utility/src/tests.rs index 9a1e11f54d6e3..44b07f70db14c 100644 --- a/frame/utility/src/tests.rs +++ b/frame/utility/src/tests.rs @@ -38,7 +38,6 @@ use sp_runtime::{ // example module to test behaviors. #[frame_support::pallet] pub mod example { - use super::*; use frame_support::{dispatch::WithPostDispatchInfo, pallet_prelude::*}; use frame_system::pallet_prelude::*; diff --git a/primitives/block-builder/src/lib.rs b/primitives/block-builder/src/lib.rs index 229f115c6667f..1b74c27b7ae43 100644 --- a/primitives/block-builder/src/lib.rs +++ b/primitives/block-builder/src/lib.rs @@ -20,11 +20,14 @@ #![cfg_attr(not(feature = "std"), no_std)] use sp_inherents::{CheckInherentsResult, InherentData}; -use sp_runtime::{traits::Block as BlockT, ApplyExtrinsicResult}; +use sp_runtime::{ + legacy::byte_sized_error::ApplyExtrinsicResult as ApplyExtrinsicResultBeforeV6, + traits::Block as BlockT, ApplyExtrinsicResult, +}; sp_api::decl_runtime_apis! { /// The `BlockBuilder` api trait that provides the required functionality for building a block. - #[api_version(5)] + #[api_version(6)] pub trait BlockBuilder { /// Apply the given extrinsic. /// @@ -32,6 +35,9 @@ sp_api::decl_runtime_apis! { /// this block or not. fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult; + #[changed_in(6)] + fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResultBeforeV6; + /// Finish the current block. #[renamed("finalise_block", 3)] fn finalize_block() -> ::Header; diff --git a/primitives/runtime/src/legacy.rs b/primitives/runtime/src/legacy.rs new file mode 100644 index 0000000000000..7bc7c88a7e10d --- /dev/null +++ b/primitives/runtime/src/legacy.rs @@ -0,0 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Runtime types that existed in old API versions. + +pub mod byte_sized_error; diff --git a/primitives/runtime/src/legacy/byte_sized_error.rs b/primitives/runtime/src/legacy/byte_sized_error.rs new file mode 100644 index 0000000000000..049abff69ff1a --- /dev/null +++ b/primitives/runtime/src/legacy/byte_sized_error.rs @@ -0,0 +1,100 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Runtime types that existed prior to BlockBuilder API version 6. + +use crate::{ArithmeticError, TokenError}; +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; + +/// [`ModuleError`] type definition before BlockBuilder API version 6. +#[derive(Eq, Clone, Copy, Encode, Decode, Debug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct ModuleError { + /// Module index, matching the metadata module index. + pub index: u8, + /// Module specific error value. + pub error: u8, + /// Optional error message. + #[codec(skip)] + #[cfg_attr(feature = "std", serde(skip_deserializing))] + pub message: Option<&'static str>, +} + +impl PartialEq for ModuleError { + fn eq(&self, other: &Self) -> bool { + (self.index == other.index) && (self.error == other.error) + } +} + +/// [`DispatchError`] type definition before BlockBuilder API version 6. +#[derive(Eq, Clone, Copy, Encode, Decode, Debug, TypeInfo, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum DispatchError { + /// Some error occurred. + Other( + #[codec(skip)] + #[cfg_attr(feature = "std", serde(skip_deserializing))] + &'static str, + ), + /// Failed to lookup some data. + CannotLookup, + /// A bad origin. + BadOrigin, + /// A custom error in a module. + Module(ModuleError), + /// At least one consumer is remaining so the account cannot be destroyed. + ConsumerRemaining, + /// There are no providers so the account cannot be created. + NoProviders, + /// There are too many consumers so the account cannot be created. + TooManyConsumers, + /// An error to do with tokens. + Token(TokenError), + /// An arithmetic error. + Arithmetic(ArithmeticError), +} + +/// [`DispatchOutcome`] type definition before BlockBuilder API version 6. +pub type DispatchOutcome = Result<(), DispatchError>; + +/// [`ApplyExtrinsicResult`] type definition before BlockBuilder API version 6. +pub type ApplyExtrinsicResult = + Result; + +/// Convert the legacy `ApplyExtrinsicResult` type to the latest version. +pub fn convert_to_latest(old: ApplyExtrinsicResult) -> crate::ApplyExtrinsicResult { + old.map(|outcome| { + outcome.map_err(|e| match e { + DispatchError::Other(s) => crate::DispatchError::Other(s), + DispatchError::CannotLookup => crate::DispatchError::CannotLookup, + DispatchError::BadOrigin => crate::DispatchError::BadOrigin, + DispatchError::Module(err) => crate::DispatchError::Module(crate::ModuleError { + index: err.index, + error: [err.error, 0, 0, 0], + message: err.message, + }), + DispatchError::ConsumerRemaining => crate::DispatchError::ConsumerRemaining, + DispatchError::NoProviders => crate::DispatchError::NoProviders, + DispatchError::TooManyConsumers => crate::DispatchError::TooManyConsumers, + DispatchError::Token(err) => crate::DispatchError::Token(err), + DispatchError::Arithmetic(err) => crate::DispatchError::Arithmetic(err), + }) + }) +} diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index a428da59f6a0d..337fac5812aed 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -57,6 +57,7 @@ use scale_info::TypeInfo; pub mod curve; pub mod generic; +pub mod legacy; mod multiaddress; pub mod offchain; pub mod runtime_logger; @@ -97,6 +98,10 @@ pub use sp_arithmetic::{ pub use either::Either; +/// The number of bytes of the module-specific `error` field defined in [`ModuleError`]. +/// In FRAME, this is the maximum encoded size of a pallet error type. +pub const MAX_MODULE_ERROR_ENCODED_SIZE: usize = 4; + /// An abstraction over justification for a block's validity under a consensus algorithm. /// /// Essentially a finality proof. The exact formulation will vary between consensus @@ -468,7 +473,7 @@ pub struct ModuleError { /// Module index, matching the metadata module index. pub index: u8, /// Module specific error value. - pub error: u8, + pub error: [u8; MAX_MODULE_ERROR_ENCODED_SIZE], /// Optional error message. #[codec(skip)] #[cfg_attr(feature = "std", serde(skip_deserializing))] @@ -922,15 +927,15 @@ mod tests { fn dispatch_error_encoding() { let error = DispatchError::Module(ModuleError { index: 1, - error: 2, + error: [2, 0, 0, 0], message: Some("error message"), }); let encoded = error.encode(); let decoded = DispatchError::decode(&mut &encoded[..]).unwrap(); - assert_eq!(encoded, vec![3, 1, 2]); + assert_eq!(encoded, vec![3, 1, 2, 0, 0, 0]); assert_eq!( decoded, - DispatchError::Module(ModuleError { index: 1, error: 2, message: None }) + DispatchError::Module(ModuleError { index: 1, error: [2, 0, 0, 0], message: None }) ); } @@ -943,9 +948,9 @@ mod tests { Other("bar"), CannotLookup, BadOrigin, - Module(ModuleError { index: 1, error: 1, message: None }), - Module(ModuleError { index: 1, error: 2, message: None }), - Module(ModuleError { index: 2, error: 1, message: None }), + Module(ModuleError { index: 1, error: [1, 0, 0, 0], message: None }), + Module(ModuleError { index: 1, error: [2, 0, 0, 0], message: None }), + Module(ModuleError { index: 2, error: [1, 0, 0, 0], message: None }), ConsumerRemaining, NoProviders, Token(TokenError::NoFunds), @@ -970,8 +975,8 @@ mod tests { // Ignores `message` field in `Module` variant. assert_eq!( - Module(ModuleError { index: 1, error: 1, message: Some("foo") }), - Module(ModuleError { index: 1, error: 1, message: None }), + Module(ModuleError { index: 1, error: [1, 0, 0, 0], message: Some("foo") }), + Module(ModuleError { index: 1, error: [1, 0, 0, 0], message: None }), ); } diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index 1cea7c4e805c1..ba4ca790a9198 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -1554,6 +1554,12 @@ impl Printable for &[u8] { } } +impl Printable for [u8; N] { + fn print(&self) { + sp_io::misc::print_hex(&self[..]); + } +} + impl Printable for &str { fn print(&self) { sp_io::misc::print_utf8(self.as_bytes()); diff --git a/utils/frame/rpc/system/src/lib.rs b/utils/frame/rpc/system/src/lib.rs index eb1b258c97ec6..0eae4d061afbb 100644 --- a/utils/frame/rpc/system/src/lib.rs +++ b/utils/frame/rpc/system/src/lib.rs @@ -25,10 +25,11 @@ use jsonrpc_core::{Error as RpcError, ErrorCode}; use jsonrpc_derive::rpc; use sc_rpc_api::DenyUnsafe; use sc_transaction_pool_api::{InPoolTransaction, TransactionPool}; +use sp_api::ApiExt; use sp_block_builder::BlockBuilder; use sp_blockchain::HeaderBackend; use sp_core::{hexdisplay::HexDisplay, Bytes}; -use sp_runtime::{generic::BlockId, traits}; +use sp_runtime::{generic::BlockId, legacy, traits}; pub use self::gen_client::Client as SystemClient; pub use frame_system_rpc_runtime_api::AccountNonceApi; @@ -135,14 +136,40 @@ where .map_err(|e| RpcError { code: ErrorCode::ServerError(Error::DecodeError.into()), message: "Unable to dry run extrinsic.".into(), - data: Some(format!("{:?}", e).into()), + data: Some(e.to_string().into()), })?; - let result = api.apply_extrinsic(&at, uxt).map_err(|e| RpcError { - code: ErrorCode::ServerError(Error::RuntimeError.into()), - message: "Unable to dry run extrinsic.".into(), - data: Some(e.to_string().into()), - })?; + let api_version = api + .api_version::>(&at) + .map_err(|e| RpcError { + code: ErrorCode::ServerError(Error::RuntimeError.into()), + message: "Unable to dry run extrinsic.".into(), + data: Some(e.to_string().into()), + })? + .ok_or_else(|| RpcError { + code: ErrorCode::ServerError(Error::RuntimeError.into()), + message: "Unable to dry run extrinsic.".into(), + data: Some( + format!("Could not find `BlockBuilder` api for block `{:?}`.", at).into(), + ), + })?; + + let result = if api_version < 6 { + #[allow(deprecated)] + api.apply_extrinsic_before_version_6(&at, uxt) + .map(legacy::byte_sized_error::convert_to_latest) + .map_err(|e| RpcError { + code: ErrorCode::ServerError(Error::RuntimeError.into()), + message: "Unable to dry run extrinsic.".into(), + data: Some(e.to_string().into()), + })? + } else { + api.apply_extrinsic(&at, uxt).map_err(|e| RpcError { + code: ErrorCode::ServerError(Error::RuntimeError.into()), + message: "Unable to dry run extrinsic.".into(), + data: Some(e.to_string().into()), + })? + }; Ok(Encode::encode(&result).into()) };