Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 32 additions & 36 deletions pallets/transaction-storage/src/extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,55 +45,51 @@ struct TraverseResult {
/// Maximum recursion depth for inspecting wrapper calls.
pub const MAX_WRAPPER_DEPTH: u32 = 8;

/// Returns `true` if `call` is a storage-mutating TransactionStorage call (store,
/// store_with_cid_config, renew) — either directly or nested inside wrappers.
///
/// Intended for use in XCM `SafeCallFilter` implementations. The runtime's
/// [`CallInspector`] provides the wrapper-recursion logic, so this function
/// works for any runtime without duplicating the blocked-call list.
pub fn is_storage_mutating_call<T: Config, I: CallInspector<RuntimeCallOf<T>>>(
call: &RuntimeCallOf<T>,
depth: u32,
) -> bool
where
RuntimeCallOf<T>: IsSubType<Call<T>>,
{
if depth >= MAX_WRAPPER_DEPTH {
return true;
}
if let Some(inner_call) = call.is_sub_type() {
return matches!(
inner_call,
Call::store { .. } | Call::store_with_cid_config { .. } | Call::renew { .. }
);
}
if let Some((inner_calls, _)) = I::inspect_wrapper(call) {
return inner_calls
.into_iter()
.any(|inner| is_storage_mutating_call::<T, I>(inner, depth + 1));
}
false
}

/// Tells [`ValidateStorageCalls`] how to find storage calls inside wrapper
/// extrinsics (e.g. `Utility::batch`, `Sudo::sudo_as`).
///
/// The runtime implements this for its `RuntimeCall` type, allowing the pallet extension
/// to recursively validate and consume storage authorization in wrapped calls, and to
/// transform the origin to [`Origin::Authorized`] for origin-preserving wrappers.
pub trait CallInspector<Call>: Clone + PartialEq + Eq + Default {
pub trait CallInspector<T: Config>: Clone + PartialEq + Eq + Default
where
RuntimeCallOf<T>: IsSubType<Call<T>>,
{
/// If `call` is a wrapper, return:
/// - The inner calls to inspect for storage authorization
/// - `true` if the wrapper passes origin through to inner calls (e.g. batch), `false` if it
/// changes the origin (e.g. sudo_as)
///
/// Returns `None` for non-wrapper calls.
fn inspect_wrapper(call: &Call) -> Option<(Vec<&Call>, bool)>;
fn inspect_wrapper(call: &RuntimeCallOf<T>) -> Option<(Vec<&RuntimeCallOf<T>>, bool)>;

/// Returns `true` if `call` is a storage-mutating TransactionStorage call (store,
/// store_with_cid_config, renew) — either directly or nested inside wrappers.
fn is_storage_mutating_call(call: &RuntimeCallOf<T>, depth: u32) -> bool {
if depth >= MAX_WRAPPER_DEPTH {
return true;
}
if let Some(inner_call) = call.is_sub_type() {
return matches!(
inner_call,
Call::store { .. } | Call::store_with_cid_config { .. } | Call::renew { .. }
);
}
if let Some((inner_calls, _)) = Self::inspect_wrapper(call) {
return inner_calls
.into_iter()
.any(|inner| Self::is_storage_mutating_call(inner, depth + 1));
}
false
}
}

/// No-op implementation — no wrapper inspection. Direct storage calls still work.
impl<Call> CallInspector<Call> for () {
fn inspect_wrapper(_: &Call) -> Option<(Vec<&Call>, bool)> {
impl<T: Config> CallInspector<T> for ()
where
RuntimeCallOf<T>: IsSubType<Call<T>>,
{
fn inspect_wrapper(_: &RuntimeCallOf<T>) -> Option<(Vec<&RuntimeCallOf<T>>, bool)> {
None
}
}
Expand Down Expand Up @@ -145,7 +141,7 @@ impl<T: Config + Send + Sync, I> fmt::Debug for ValidateStorageCalls<T, I> {
}
}

impl<T: Config + Send + Sync, I: CallInspector<RuntimeCallOf<T>>> ValidateStorageCalls<T, I>
impl<T: Config + Send + Sync, I: CallInspector<T>> ValidateStorageCalls<T, I>
where
RuntimeCallOf<T>: IsSubType<Call<T>>,
{
Expand Down Expand Up @@ -184,7 +180,7 @@ where
}
}

impl<T: Config + Send + Sync, I: CallInspector<RuntimeCallOf<T>> + Send + Sync + 'static>
impl<T: Config + Send + Sync, I: CallInspector<T> + Send + Sync + 'static>
TransactionExtension<RuntimeCallOf<T>> for ValidateStorageCalls<T, I>
where
RuntimeCallOf<T>: IsSubType<Call<T>>,
Expand Down
2 changes: 1 addition & 1 deletion pallets/transaction-storage/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ pub enum AuthorizedCaller<AccountId> {
/// Convenience alias for [`AuthorizedCaller`] bound to a runtime's `AccountId`.
pub type AuthorizedCallerFor<T> = AuthorizedCaller<<T as frame_system::Config>::AccountId>;

pub use extension::{is_storage_mutating_call, CallInspector, MAX_WRAPPER_DEPTH};
pub use extension::{CallInspector, MAX_WRAPPER_DEPTH};

/// An authorization to store data.
#[derive(Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
Expand Down
6 changes: 3 additions & 3 deletions runtimes/bulletin-polkadot/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ impl SortedMembers<AccountId> for TestAccounts {
#[derive(Clone, PartialEq, Eq, Default)]
pub struct StorageCallInspector;

impl pallet_transaction_storage::CallInspector<RuntimeCall> for StorageCallInspector {
impl pallet_transaction_storage::CallInspector<Runtime> for StorageCallInspector {
fn inspect_wrapper(call: &RuntimeCall) -> Option<(alloc::vec::Vec<&RuntimeCall>, bool)> {
match call {
RuntimeCall::Utility(c) => inspect_utility_wrapper(c),
Expand All @@ -393,7 +393,7 @@ impl pallet_transaction_storage::CallInspector<RuntimeCall> for StorageCallInspe
/// nesting. Used with `EverythingBut` as the XCM `SafeCallFilter`.
impl frame_support::traits::Contains<RuntimeCall> for StorageCallInspector {
fn contains(call: &RuntimeCall) -> bool {
pallet_transaction_storage::is_storage_mutating_call::<Runtime, Self>(call, 0)
Self::is_storage_mutating_call(call, 0)
}
}

Expand Down Expand Up @@ -555,7 +555,7 @@ fn validate_purge_keys(who: &AccountId) -> TransactionValidity {
}
}

use pallet_transaction_storage::MAX_WRAPPER_DEPTH;
use pallet_transaction_storage::{CallInspector, MAX_WRAPPER_DEPTH};
use pallets_common::{
inspect_proxy_wrapper, inspect_sudo_wrapper, inspect_utility_wrapper, proxy_inner_calls,
utility_inner_calls,
Expand Down
5 changes: 3 additions & 2 deletions runtimes/bulletin-westend/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use frame_support::{
traits::{Contains, EitherOfDiverse, Equals, SortedMembers},
};
use frame_system::EnsureSignedBy;
use pallet_transaction_storage::CallInspector;
use pallet_xcm::EnsureXcm;
use pallets_common::{inspect_sudo_wrapper, inspect_utility_wrapper, NoCurrency};
use sp_keyring::Sr25519Keyring;
Expand Down Expand Up @@ -58,7 +59,7 @@ parameter_types! {
#[derive(Clone, PartialEq, Eq, Default)]
pub struct StorageCallInspector;

impl pallet_transaction_storage::CallInspector<RuntimeCall> for StorageCallInspector {
impl pallet_transaction_storage::CallInspector<Runtime> for StorageCallInspector {
fn inspect_wrapper(call: &RuntimeCall) -> Option<(Vec<&RuntimeCall>, bool)> {
match call {
RuntimeCall::Utility(c) => inspect_utility_wrapper(c),
Expand All @@ -73,7 +74,7 @@ impl pallet_transaction_storage::CallInspector<RuntimeCall> for StorageCallInspe
/// Used with `EverythingBut` as the XCM `SafeCallFilter`.
impl Contains<RuntimeCall> for StorageCallInspector {
fn contains(call: &RuntimeCall) -> bool {
pallet_transaction_storage::is_storage_mutating_call::<Runtime, Self>(call, 0)
Self::is_storage_mutating_call(call, 0)
}
}

Expand Down