Allow pallet error enum variants to contain fields (#10242)

* Allow pallet errors to contain at most one field

* Update docs on pallet::error

* Reword documentation

* cargo fmt

* Introduce CompactPalletError trait and require #[pallet::error] fields to implement them

* cargo fmt

* Do not assume tuple variants

* Add CompactPalletError derive macro

* Check for error type compactness in construct_runtime

* cargo fmt

* Derive CompactPalletError instead of implementing it directly during macro expansion

* Implement CompactPalletError on OptionBool instead of Option<bool>

* Check for type idents instead of variant ident

* Add doc comments for ErrorCompactnessTest

* Add an trait implementation of ErrorCompactnessTest for ()

* Convert the error field of DispatchError to a 4-element byte array

* Add static check for pallet error size

* Rename to MAX_PALLET_ERROR_ENCODED_SIZE

* Remove ErrorCompactnessTest trait

* Remove check_compactness

* Return only the most significant byte when constructing a custom InvalidTransaction

* Rename CompactPalletError to PalletError

* Use counter to generate unique idents for assert macros

* Make declarative pallet macros compile with pallet error size checks

* Remove unused doc comment

* Try and fix build errors

* Fix build errors

* Add macro_use for some test modules

* Test fix

* Fix compilation errors

* Remove unneeded #[macro_use]

* Resolve import ambiguity

* Make path to pallet Error enum more specific

* Fix test expectation

* Disambiguate imports

* Fix test expectations

* Revert appending pallet module name to path

* Rename bags_list::list::Error to BagError

* Fixes

* Fixes

* Fixes

* Fix test expectations

* Fix test expectation

* Add more implementations for PalletError

* Lift the 1-field requirement for nested pallet errors

* Fix UI test expectation

* Remove PalletError impl for OptionBool

* Use saturating operations

* cargo fmt

* Delete obsolete test

* Fix test expectation

* Try and use assert macro in const context

* Pull out the pallet error size check macro

* Fix UI test for const assertion

* cargo fmt

* Apply clippy suggestion

* Fix doc comment

* Docs for create_tt_return_macro

* Ensure TryInto is imported in earlier Rust editions

* Apply suggestions from code review

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Fix up comments and names

* Implement PalletError for Never

* cargo fmt

* Don't compile example code

* Bump API version for block builder

* Factor in codec attributes while derving PalletError

* Rename module and fix unit test

* Add missing attribute

* Check API version and convert ApplyExtrinsicResult accordingly

* Rename BagError to ListError

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Use codec crate re-exported from frame support

* Add links to types mentioned in doc comments

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* cargo fmt

* cargo fmt

* Re-add attribute for hidden docs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
This commit is contained in:
Keith Yeung
2022-03-24 09:11:14 +01:00
committed by GitHub
parent 5c9f23af13
commit 208be86934
38 changed files with 1263 additions and 241 deletions
+25 -5
View File
@@ -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<Block>, B> {
extrinsics: Vec<Block::Extrinsic>,
api: ApiRef<'a, A::Api>,
version: u32,
block_id: BlockId<Block>,
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::<dyn BlockBuilderApi<Block>>(&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: <Block as BlockT>::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(()))
+1 -1
View File
@@ -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}";
+3 -3
View File
@@ -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<T: Config<I>, I: 'static> Pallet<T, I> {
}
impl<T: Config<I>, I: 'static> SortedListProvider<T::AccountId> for Pallet<T, I> {
type Error = Error;
type Error = ListError;
type Score = T::Score;
@@ -286,7 +286,7 @@ impl<T: Config<I>, I: 'static> SortedListProvider<T::AccountId> for Pallet<T, I>
List::<T, I>::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::<T, I>::insert(id, score)
}
+3 -3
View File
@@ -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<T: Config<I>, I: 'static> List<T, I> {
/// 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::<T, I>(score);
+1 -1
View File
@@ -248,7 +248,7 @@ mod list {
// then
assert_storage_noop!(assert_eq!(
List::<Runtime>::insert(3, 20).unwrap_err(),
Error::Duplicate
ListError::Duplicate
));
});
}
+1 -1
View File
@@ -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
));
});
}
@@ -1583,7 +1583,7 @@ impl<T: Config> ElectionProvider for Pallet<T> {
/// 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)
@@ -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"),
})),
);
+2 -2
View File
@@ -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<T: Config> Pallet<T>
where
<T as system::Config>::Origin: OriginTrait<PalletsOrigin = OriginCaller>,
<T as frame_system::Config>::Origin: OriginTrait<PalletsOrigin = OriginCaller>,
{
#[pallet::weight(*weight)]
pub fn log(origin: OriginFor<T>, i: u32, weight: Weight) -> DispatchResult {
-1
View File
@@ -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::*;
@@ -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)*
}
}
+16 -3
View File
@@ -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<Counter> = 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)
}
@@ -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 = <
<T as #frame_system::Config>::PalletInfo
as #frame_support::traits::PalletInfo
>::index::<Pallet<#type_use_gen>>()
.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;
)
}
@@ -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<syn::Lit>)>,
/// Variants ident, optional field and doc literals (ordered as declaration order)
pub variants: Vec<(syn::Ident, Option<VariantField>, Vec<syn::Lit>)>,
/// A set of usage of instance, must be check for consistency with trait.
pub instances: Vec<helper::InstanceUsage>,
/// 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::<Result<_, _>>()?;
@@ -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::<syn::Result<Vec<_>>>();
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::<Result<Vec<Option<Vec<proc_macro2::TokenStream>>>, syn::Error>>();
let field_tys = match field_tys {
Ok(tys) => tys.into_iter().flatten().collect::<Vec<_>>(),
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<Option<proc_macro2::TokenStream>> {
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<Option<Vec<proc_macro2::TokenStream>>> {
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::<syn::Result<Vec<_>>>()?;
Ok(Some(field_tys.into_iter().flatten().collect()))
},
syn::Fields::Unit => Ok(None),
}
}
@@ -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<Self> {
let name = input.parse()?;
let _ = input.parse::<syn::Token![,]>()?;
let mut args = Vec::new();
while !input.is_empty() {
let mut value;
let key: Ident = input.parse()?;
let _ = input.parse::<syn::Token![=]>()?;
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()
}
+4
View File
@@ -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 }
+10 -44
View File
@@ -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 )*
}
}
}
+50 -5
View File
@@ -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};
@@ -1375,6 +1404,7 @@ pub mod pallet_prelude {
TransactionTag, TransactionValidity, TransactionValidityError, UnknownTransaction,
ValidTransaction,
},
MAX_MODULE_ERROR_ENCODED_SIZE,
};
pub use sp_std::marker::PhantomData;
}
@@ -1652,10 +1682,25 @@ pub mod pallet_prelude {
/// pub enum Error<T> {
/// /// $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.
///
+3
View File
@@ -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};
@@ -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<u8>);
impl_for_types!(size: 4, u32, i32, Compact<u16>);
impl_for_types!(size: 5, Compact<u32>);
impl_for_types!(size: 8, u64, i64);
impl_for_types!(size: 9, Compact<u64>);
// 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<u128>);
impl<T> PalletError for PhantomData<T> {
const MAX_ENCODED_SIZE: usize = 0;
}
impl<T: PalletError> PalletError for core::ops::Range<T> {
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE.saturating_mul(2);
}
impl<T: PalletError, const N: usize> PalletError for [T; N] {
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE.saturating_mul(N);
}
impl<T: PalletError> PalletError for Option<T> {
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE.saturating_add(1);
}
impl<T: PalletError, E: PalletError> PalletError for Result<T, E> {
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
};
}
@@ -271,139 +271,95 @@ pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
pub type Block = generic::Block<Header, UncheckedExtrinsic>;
pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<u32, Call, Signature, ()>;
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<Call> 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<T>, Origin<T>},
NestedModule3: nested::module3::{Pallet, Origin, Call},
Module3: module3::{Pallet, Origin<T>, 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::<Runtime>::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::<Runtime>::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::<Runtime>::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::<Runtime>::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::<Runtime>::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::<Runtime>::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::<Runtime>::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::<Runtime>::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::<Runtime>::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::<Runtime>::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::<Runtime>::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")
})),
);
}
@@ -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<T>(core::marker::PhantomData<T>);
#[pallet::error]
pub enum Error<T> {
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<BlockNumber, BlakeTwo256>;
pub type Block = generic::Block<Header, UncheckedExtrinsic>;
pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<u32, Call, Signature, ()>;
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<Self::AccountId>;
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<T>},
Pallet: pallet::{Pallet},
}
}
fn main() {}
@@ -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)
@@ -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<T: Config> for enum Call
where origin: <T as system::Config>::Origin, system=system
{
#[weight = 0]
pub fn fail(_origin) -> frame_support::dispatch::DispatchResult {
Err(Error::<T>::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<T: Config> {
Something
}
}
frame_support::decl_storage! {
trait Store for Module<T: Config> as Module {}
add_extra_genesis {
build(|_config| {})
}
}
}
}
pub mod module {
use super::*;
pub trait Config: system::Config {}
frame_support::decl_module! {
pub struct Module<T: Config> for enum Call
where origin: <T as system::Config>::Origin, system=system
{
#[weight = 0]
pub fn fail(_origin) -> frame_support::dispatch::DispatchResult {
Err(Error::<T>::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<T>(pub core::marker::PhantomData<T>);
frame_support::decl_event! {
pub enum Event {
A,
}
}
frame_support::decl_error! {
pub enum Error for Module<T: Config> {
Something
}
}
frame_support::decl_storage! {
trait Store for Module<T: Config> as Module {}
add_extra_genesis {
build(|_config| {})
}
}
}
impl nested::module::Config for RuntimeOriginTest {}
impl module::Config for RuntimeOriginTest {}
pub struct BaseCallFilter;
impl Contains<Call> 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<T>, Origin<T>},
NestedModule: nested::module::{Pallet, Origin, Call},
Module: module::{Pallet, Origin<T>, Call},
}
);
pub type Signature = sr25519::Signature;
pub type BlockNumber = u64;
pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<u32, Call, Signature, ()>;
pub type Block = generic::Block<Header, UncheckedExtrinsic>;
#[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);
}
+8 -2
View File
@@ -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<T> {
/// 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::<Runtime>::InsufficientProposersBalance),
DispatchError::Module(ModuleError {
index: 1,
error: 0,
error: [0, 0, 0, 0],
message: Some("InsufficientProposersBalance")
}),
);
assert_eq!(<pallet::Error::<Runtime> as PalletError>::MAX_ENCODED_SIZE, 3);
}
#[test]
@@ -343,7 +343,7 @@ fn error_expand() {
DispatchError::from(pallet::Error::<Runtime>::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")
}),
);
@@ -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<T>(core::marker::PhantomData<T>);
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
#[pallet::call]
impl<T: Config> Pallet<T> {}
#[pallet::error]
pub enum Error<T> {
U8(u8),
CustomError(crate::MyError),
}
}
#[derive(scale_info::TypeInfo, codec::Encode, codec::Decode)]
enum MyError {}
fn main() {
}
@@ -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)
@@ -1,5 +0,0 @@
error: Invalid pallet::error, unexpected fields, must be `Unit`
--> $DIR/error_no_fieldless.rs:20:5
|
20 | U8(u8),
| ^^^^
@@ -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<T>(core::marker::PhantomData<T>);
#[pallet::error]
pub enum Error<T> {
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() {
}
-1
View File
@@ -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::*;
@@ -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: <Block as BlockT>::Extrinsic) -> ApplyExtrinsicResult;
#[changed_in(6)]
fn apply_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> ApplyExtrinsicResultBeforeV6;
/// Finish the current block.
#[renamed("finalise_block", 3)]
fn finalize_block() -> <Block as BlockT>::Header;
@@ -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;
@@ -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<DispatchOutcome, crate::transaction_validity::TransactionValidityError>;
/// 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),
})
})
}
+14 -9
View File
@@ -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 }),
);
}
@@ -1554,6 +1554,12 @@ impl Printable for &[u8] {
}
}
impl<const N: usize> 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());
+34 -7
View File
@@ -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::<dyn BlockBuilder<Block>>(&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())
};