diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 766bc9515b8ab..d0f4d675f471a 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -249,6 +249,7 @@ impl pallet_utility::Config for Runtime { type RuntimeCall = RuntimeCall; type PalletsOrigin = OriginCaller; type WeightInfo = pallet_utility::weights::SubstrateWeight; + type CallFilter = Everything; } parameter_types! { diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index a07176e9f67b9..665ce6c22be4e 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -40,6 +40,8 @@ use frame_support::{ traits::{ ConstU32, ConstU64, Contains, Currency, ExistenceRequirement, LockableCurrency, OnIdle, OnInitialize, WithdrawReasons, + ConstU32, ConstU64, Contains, Currency, Everything, ExistenceRequirement, Get, + LockableCurrency, OnIdle, OnInitialize, WithdrawReasons, }, weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, }; @@ -337,6 +339,7 @@ impl pallet_utility::Config for Test { type RuntimeCall = RuntimeCall; type PalletsOrigin = OriginCaller; type WeightInfo = (); + type CallFilter = Everything; } impl pallet_proxy::Config for Test { diff --git a/frame/proxy/src/tests.rs b/frame/proxy/src/tests.rs index f3771083c4dd4..79627418f45e8 100644 --- a/frame/proxy/src/tests.rs +++ b/frame/proxy/src/tests.rs @@ -26,7 +26,7 @@ use codec::{Decode, Encode}; use frame_support::{ assert_noop, assert_ok, dispatch::DispatchError, - traits::{ConstU32, ConstU64, Contains}, + traits::{ConstU32, ConstU64, Contains, Everything}, RuntimeDebug, }; use sp_core::H256; @@ -98,6 +98,7 @@ impl pallet_utility::Config for Test { type RuntimeCall = RuntimeCall; type PalletsOrigin = OriginCaller; type WeightInfo = (); + type CallFilter = Everything; } #[derive( diff --git a/frame/treasury/src/tests.rs b/frame/treasury/src/tests.rs index 67b21ff6252a8..663006b766066 100644 --- a/frame/treasury/src/tests.rs +++ b/frame/treasury/src/tests.rs @@ -29,7 +29,7 @@ use frame_support::{ assert_err_ignore_postinfo, assert_noop, assert_ok, pallet_prelude::GenesisBuild, parameter_types, - traits::{ConstU32, ConstU64, OnInitialize}, + traits::{ConstU32, ConstU64, Everything, OnInitialize}, PalletId, }; @@ -101,6 +101,7 @@ impl pallet_utility::Config for Test { type RuntimeCall = RuntimeCall; type PalletsOrigin = OriginCaller; type WeightInfo = (); + type CallFilter = Everything; } parameter_types! { diff --git a/frame/utility/src/lib.rs b/frame/utility/src/lib.rs index af212a31eb971..da78ac2d6c25b 100644 --- a/frame/utility/src/lib.rs +++ b/frame/utility/src/lib.rs @@ -58,9 +58,14 @@ pub mod weights; use codec::{Decode, Encode}; use frame_support::{ - dispatch::{extract_actual_weight, GetDispatchInfo, PostDispatchInfo}, - traits::{IsSubType, OriginTrait, UnfilteredDispatchable}, + dispatch::{ + extract_actual_weight, DispatchErrorWithPostInfo, GetDispatchInfo, PostDispatchInfo, + }, + pallet_prelude::DispatchResultWithPostInfo, + traits::{Contains, IsSubType, OriginTrait, UnfilteredDispatchable}, + weights::Weight, }; +use frame_system::pallet_prelude::OriginFor; use sp_core::TypeId; use sp_io::hashing::blake2_256; use sp_runtime::traits::{BadOrigin, Dispatchable, TrailingZeroInput}; @@ -100,6 +105,9 @@ pub mod pallet { /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; + + /// Filtering calls. + type CallFilter: Contains<::RuntimeCall>; } #[pallet::event] @@ -168,8 +176,9 @@ pub mod pallet { /// - `calls`: The calls to be dispatched from the same origin. The number of call must not /// exceed the constant: `batched_calls_limit` (available in constant metadata). /// - /// If origin is root then the calls are dispatched without checking origin filter. (This - /// includes bypassing `frame_system::Config::BaseCallFilter`). + /// If the source is root, then calls are sent without checking the origin and call filters. + /// (This involves bypassing `frame_system::Config::BaseCallFilter` and + /// `Config::CallFilter`). /// /// ## Complexity /// - O(C) where C is the number of calls to be batched. @@ -215,12 +224,14 @@ pub mod pallet { let mut weight = Weight::zero(); for (index, call) in calls.into_iter().enumerate() { let info = call.get_dispatch_info(); + // If origin is root, don't apply any dispatch filters; root can call anything. let result = if is_root { call.dispatch_bypass_filter(origin.clone()) } else { - call.dispatch(origin.clone()) + Self::dispatch_filtered(origin.clone(), call) }; + // Add the weight of this call. weight = weight.saturating_add(extract_actual_weight(&result, &info)); if let Err(e) = result { @@ -274,6 +285,7 @@ pub mod pallet { let pseudonym = Self::derivative_account_id(who, index); origin.set_caller_from(frame_system::RawOrigin::Signed(pseudonym)); let info = call.get_dispatch_info(); + let result = call.dispatch(origin); // Always take into account the base weight of this call. let mut weight = T::WeightInfo::as_derivative() @@ -296,8 +308,9 @@ pub mod pallet { /// - `calls`: The calls to be dispatched from the same origin. The number of call must not /// exceed the constant: `batched_calls_limit` (available in constant metadata). /// - /// If origin is root then the calls are dispatched without checking origin filter. (This - /// includes bypassing `frame_system::Config::BaseCallFilter`). + /// If the source is root, then calls are sent without checking the origin and call filters. + /// (This involves bypassing `frame_system::Config::BaseCallFilter` and + /// `Config::CallFilter`). /// /// ## Complexity /// - O(C) where C is the number of calls to be batched. @@ -337,6 +350,7 @@ pub mod pallet { let mut weight = Weight::zero(); for (index, call) in calls.into_iter().enumerate() { let info = call.get_dispatch_info(); + // If origin is root, bypass any dispatch filter; root can call anything. let result = if is_root { call.dispatch_bypass_filter(origin.clone()) @@ -349,7 +363,7 @@ pub mod pallet { !matches!(c.is_sub_type(), Some(Call::batch_all { .. })) }, ); - call.dispatch(filtered_origin) + Self::dispatch_filtered(filtered_origin, call) }; // Add the weight of this call. weight = weight.saturating_add(extract_actual_weight(&result, &info)); @@ -405,8 +419,9 @@ pub mod pallet { /// - `calls`: The calls to be dispatched from the same origin. The number of call must not /// exceed the constant: `batched_calls_limit` (available in constant metadata). /// - /// If origin is root then the calls are dispatch without checking origin filter. (This - /// includes bypassing `frame_system::Config::BaseCallFilter`). + /// If the source is root, then calls are sent without checking the origin and call filters. + /// (This involves bypassing `frame_system::Config::BaseCallFilter` and + /// `Config::CallFilter`). /// /// ## Complexity /// - O(C) where C is the number of calls to be batched. @@ -448,12 +463,14 @@ pub mod pallet { let mut has_error: bool = false; for call in calls.into_iter() { let info = call.get_dispatch_info(); + // If origin is root, don't apply any dispatch filters; root can call anything. let result = if is_root { call.dispatch_bypass_filter(origin.clone()) } else { - call.dispatch(origin.clone()) + Self::dispatch_filtered(origin.clone(), call) }; + // Add the weight of this call. weight = weight.saturating_add(extract_actual_weight(&result, &info)); if let Err(e) = result { @@ -507,4 +524,18 @@ impl Pallet { Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref())) .expect("infinite length input; no invalid inputs for type; qed") } + + fn dispatch_filtered( + origin: OriginFor, + call: ::RuntimeCall, + ) -> DispatchResultWithPostInfo { + if ::CallFilter::contains(&call) { + return call.dispatch(origin) + } + + Err(DispatchErrorWithPostInfo { + post_info: Some(Weight::default()).into(), + error: >::CallFiltered.into(), + }) + } } diff --git a/frame/utility/src/tests.rs b/frame/utility/src/tests.rs index 6f156e318c8cf..76c8620f0a323 100644 --- a/frame/utility/src/tests.rs +++ b/frame/utility/src/tests.rs @@ -86,6 +86,12 @@ pub mod example { pub fn big_variant(_origin: OriginFor, _arg: [u8; 400]) -> DispatchResult { Ok(()) } + + #[pallet::call_index(3)] + #[pallet::weight(0)] + pub fn not_batchable(_origin: OriginFor, _arg: u8) -> DispatchResult { + Ok(()) + } } } @@ -245,6 +251,17 @@ impl Contains for TestBaseCallFilter { } } } + +pub struct TestCallFilter; +impl Contains for TestCallFilter { + fn contains(c: &RuntimeCall) -> bool { + match *c { + RuntimeCall::Example(example::Call::not_batchable { .. }) => false, + _ => true, + } + } +} + impl mock_democracy::Config for Test { type RuntimeEvent = RuntimeEvent; type ExternalMajorityOrigin = EnsureProportionAtLeast; @@ -254,6 +271,7 @@ impl Config for Test { type RuntimeCall = RuntimeCall; type PalletsOrigin = OriginCaller; type WeightInfo = (); + type CallFilter = TestCallFilter; } type ExampleCall = example::Call; @@ -384,7 +402,7 @@ fn as_derivative_handles_weight_refund() { } #[test] -fn as_derivative_filters() { +fn as_derivative_basic_filters() { new_test_ext().execute_with(|| { assert_err_ignore_postinfo!( Utility::as_derivative( @@ -447,7 +465,7 @@ fn batch_with_signed_works() { } #[test] -fn batch_with_signed_filters() { +fn batch_with_signed_base_filters() { new_test_ext().execute_with(|| { assert_ok!(Utility::batch( RuntimeOrigin::signed(1), @@ -466,6 +484,34 @@ fn batch_with_signed_filters() { }); } +#[test] +fn batch_with_signed_call_filters() { + new_test_ext().execute_with(|| { + assert_ok!(Utility::batch( + RuntimeOrigin::signed(1), + vec![RuntimeCall::Example(example::Call::not_batchable { arg: 0 })] + ),); + System::assert_last_event( + utility::Event::BatchInterrupted { + index: 0, + error: frame_system::Error::::CallFiltered.into(), + } + .into(), + ); + }); +} + +#[test] +fn batch_with_root_call_filters() { + new_test_ext().execute_with(|| { + assert_ok!(Utility::batch( + RuntimeOrigin::root(), + vec![RuntimeCall::Example(example::Call::not_batchable { arg: 0 })] + ),); + System::assert_last_event(utility::Event::BatchCompleted.into()); + }); +} + #[test] fn batch_early_exit_works() { new_test_ext().execute_with(|| { @@ -721,6 +767,30 @@ fn batch_all_does_not_nest() { }); } +#[test] +fn batch_all_with_signed_call_filters() { + new_test_ext().execute_with(|| { + assert_err_ignore_postinfo!( + Utility::batch_all( + RuntimeOrigin::signed(1), + vec![RuntimeCall::Example(example::Call::not_batchable { arg: 0 })] + ), + DispatchError::from(frame_system::Error::::CallFiltered) + ); + }); +} + +#[test] +fn batch_all_with_root_call_filters() { + new_test_ext().execute_with(|| { + assert_ok!(Utility::batch_all( + RuntimeOrigin::root(), + vec![RuntimeCall::Example(example::Call::not_batchable { arg: 0 })] + ),); + System::assert_last_event(utility::Event::BatchCompleted.into()); + }); +} + #[test] fn batch_limit() { new_test_ext().execute_with(|| { @@ -768,6 +838,28 @@ fn force_batch_works() { }); } +#[test] +fn force_batch_with_signed_call_filters() { + new_test_ext().execute_with(|| { + assert_ok!(Utility::force_batch( + RuntimeOrigin::signed(1), + vec![RuntimeCall::Example(example::Call::not_batchable { arg: 0 })] + ),); + System::assert_last_event(utility::Event::BatchCompletedWithErrors.into()); + }); +} + +#[test] +fn force_batch_with_root_call_filters() { + new_test_ext().execute_with(|| { + assert_ok!(Utility::force_batch( + RuntimeOrigin::root(), + vec![RuntimeCall::Example(example::Call::not_batchable { arg: 0 })] + ),); + System::assert_last_event(utility::Event::BatchCompleted.into()); + }); +} + #[test] fn none_origin_does_not_work() { new_test_ext().execute_with(|| {