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