Skip to content

Commit

Permalink
Add a pallet-utility Call iterator
Browse files Browse the repository at this point in the history
  • Loading branch information
teor2345 committed Jan 23, 2025
1 parent 94c9f9a commit c5c1880
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 72 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/subspace-runtime-primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ codec = { package = "parity-scale-codec", version = "3.6.12", default-features =
frame-support = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "94a1a8143a89bbe9f938c1939ff68abc1519a305" }
frame-system = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "94a1a8143a89bbe9f938c1939ff68abc1519a305" }
pallet-transaction-payment = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "94a1a8143a89bbe9f938c1939ff68abc1519a305" }
pallet-utility = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "94a1a8143a89bbe9f938c1939ff68abc1519a305" }
scale-info = { version = "2.11.1", default-features = false, features = ["derive"] }
serde = { version = "1.0.203", default-features = false, features = ["alloc", "derive"] }
sp-core = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "94a1a8143a89bbe9f938c1939ff68abc1519a305" }
Expand All @@ -34,6 +35,7 @@ std = [
"frame-support/std",
"frame-system/std",
"pallet-transaction-payment/std",
"pallet-utility/std",
"scale-info/std",
"serde/std",
"sp-core/std",
Expand Down
2 changes: 2 additions & 0 deletions crates/subspace-runtime-primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#![cfg_attr(not(feature = "std"), no_std)]

pub mod utility;

#[cfg(not(feature = "std"))]
extern crate alloc;

Expand Down
54 changes: 54 additions & 0 deletions crates/subspace-runtime-primitives/src/utility.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//! Runtime primitives for pallet-utility.
use scale_info::prelude::collections::VecDeque;

/// Type alias used to get the runtime call type.
pub type RuntimeCallFor<Runtime> = <Runtime as pallet_utility::Config>::RuntimeCall;

/// Trait used to convert from a specific `RuntimeCall` type to `pallet-utility::Call<Runtime>`.
pub trait MaybeIntoUtilityCall<Runtime>
where
Runtime: pallet_utility::Config,
{
/// If this call is a `pallet_utility::Call<Runtime>` call, returns the inner call.
fn maybe_into_utility_call(&self) -> Option<&pallet_utility::Call<Runtime>>;
}

/// Returns an interator over `call`, and any calls nested within it using `pallet-utility`.
///
/// The iterator yields all calls in depth-first order, including calls which contain other calls.
/// `pallet_utility::Call::__Ignore` calls are yielded themsevles, but their contents are not.
// This function doesn't use stack recursion, so there's no need to check the recursion depth.
pub fn nested_utility_call_iter<Runtime>(
call: &RuntimeCallFor<Runtime>,
) -> impl Iterator<Item = &RuntimeCallFor<Runtime>>
where
Runtime: pallet_utility::Config,
RuntimeCallFor<Runtime>: MaybeIntoUtilityCall<Runtime>,
{
// Instead of using recursion, we allocate references to each call on the heap.
// TODO: re-use the same memory with an enum for a call ref, a boxed call, or a vec of calls
let mut new_calls = VecDeque::from([call]);

core::iter::from_fn(move || {
let call = new_calls.pop_front()?;

if let Some(call) = call.maybe_into_utility_call() {
match call {
pallet_utility::Call::batch { calls }
| pallet_utility::Call::batch_all { calls }
| pallet_utility::Call::force_batch { calls } => calls.iter().for_each(|call| {
new_calls.push_front(call);
}),
pallet_utility::Call::as_derivative { call, .. }
| pallet_utility::Call::dispatch_as { call, .. }
| pallet_utility::Call::with_weight { call, .. } => {
new_calls.push_front(call.as_ref())
}
pallet_utility::Call::__Ignore(..) => {}
}
}

Some(call)
})
}
11 changes: 11 additions & 0 deletions crates/subspace-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ use subspace_core_primitives::solutions::{
pieces_to_solution_range, solution_range_to_pieces, SolutionRange,
};
use subspace_core_primitives::{PublicKey, Randomness, SlotNumber, U256};
use subspace_runtime_primitives::utility::MaybeIntoUtilityCall;
use subspace_runtime_primitives::{
maximum_normal_block_length, AccountId, Balance, BlockNumber, FindBlockRewardAddress, Hash,
HoldIdentifier, Moment, Nonce, Signature, SlowAdjustingFeeUpdate, BLOCK_WEIGHT_FOR_2_SEC,
Expand Down Expand Up @@ -419,6 +420,16 @@ impl pallet_utility::Config for Runtime {
type WeightInfo = pallet_utility::weights::SubstrateWeight<Runtime>;
}

impl MaybeIntoUtilityCall<Runtime> for RuntimeCall {
/// If this call is a `pallet_utility::Call<Runtime>` call, returns the inner call.
fn maybe_into_utility_call(&self) -> Option<&pallet_utility::Call<Runtime>> {
match self {
RuntimeCall::Utility(call) => Some(call),
_ => None,
}
}
}

impl pallet_sudo::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type RuntimeCall = RuntimeCall;
Expand Down
10 changes: 10 additions & 0 deletions crates/subspace-runtime/src/object_mapping.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//! Object mapping extraction code.
//! This code must be kept in sync with `test/subspace-test-runtime/src/lib.rs`.
use crate::{Block, Runtime, RuntimeCall};
use codec::{Compact, CompactLen, Encode};
use sp_std::prelude::*;
Expand All @@ -6,6 +9,11 @@ use subspace_core_primitives::objects::{BlockObject, BlockObjectMapping};

const MAX_OBJECT_MAPPING_RECURSION_DEPTH: u16 = 5;

/// Extract the nested object mappings from `call`.
// TODO:
// - add start and end nesting closures to nested_utility_call_iter(), so we can modify and restore
// the base offset
// - convert this code to use nested_utility_call_iter(), and remove the recursion depth limit
pub(crate) fn extract_utility_block_object_mapping(
mut base_offset: u32,
objects: &mut Vec<BlockObject>,
Expand Down Expand Up @@ -67,6 +75,7 @@ pub(crate) fn extract_utility_block_object_mapping(
}
}

/// Extract the object mappings from `call`.
pub(crate) fn extract_call_block_object_mapping(
mut base_offset: u32,
objects: &mut Vec<BlockObject>,
Expand Down Expand Up @@ -102,6 +111,7 @@ pub(crate) fn extract_call_block_object_mapping(
}
}

/// Extract the object mappings from `block`.
pub(crate) fn extract_block_object_mapping(block: Block) -> BlockObjectMapping {
let mut block_object_mapping = BlockObjectMapping::default();
let mut base_offset =
Expand Down
42 changes: 12 additions & 30 deletions crates/subspace-runtime/src/signed_extensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ use sp_runtime::transaction_validity::{
InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,
};
use sp_std::prelude::*;

const MAX_BALANCE_RECURSION_DEPTH: u16 = 5;
use subspace_runtime_primitives::utility::nested_utility_call_iter;

/// Disable balance transfers, if configured in the runtime.
#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, Default, TypeInfo)]
Expand All @@ -32,9 +31,7 @@ impl SignedExtension for DisablePallets {
_len: usize,
) -> TransactionValidity {
// Disable normal balance transfers.
if !RuntimeConfigs::enable_balance_transfers()
&& contains_balance_transfer(call, MAX_BALANCE_RECURSION_DEPTH)
{
if !RuntimeConfigs::enable_balance_transfers() && contains_balance_transfer(call) {
InvalidTransaction::Call.into()
} else {
Ok(ValidTransaction::default())
Expand Down Expand Up @@ -65,33 +62,18 @@ impl SignedExtension for DisablePallets {
}
}

fn contains_balance_transfer(call: &RuntimeCall, mut recursion_depth_left: u16) -> bool {
if recursion_depth_left == 0 {
// If the recursion depth is reached, we assume the call contains a balance transfer.
return true;
}

recursion_depth_left -= 1;

match call {
RuntimeCall::Balances(
fn contains_balance_transfer(call: &RuntimeCall) -> bool {
for call in nested_utility_call_iter::<Runtime>(call) {
// Other calls are inconclusive, they might contain nested calls
if let RuntimeCall::Balances(
pallet_balances::Call::transfer_allow_death { .. }
| pallet_balances::Call::transfer_keep_alive { .. }
| pallet_balances::Call::transfer_all { .. },
) => true,
RuntimeCall::Utility(utility_call) => match utility_call {
pallet_utility::Call::batch { calls }
| pallet_utility::Call::batch_all { calls }
| pallet_utility::Call::force_batch { calls } => calls
.iter()
.any(|call| contains_balance_transfer(call, recursion_depth_left)),
pallet_utility::Call::as_derivative { call, .. }
| pallet_utility::Call::dispatch_as { call, .. }
| pallet_utility::Call::with_weight { call, .. } => {
contains_balance_transfer(call, recursion_depth_left)
}
pallet_utility::Call::__Ignore(..) => false,
},
_ => false,
) = call
{
return true;
}
}

false
}
90 changes: 48 additions & 42 deletions domains/runtime/evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ use sp_subspace_mmr::domain_mmr_runtime_interface::{
};
use sp_subspace_mmr::{ConsensusChainMmrLeafProof, MmrLeaf};
use sp_version::RuntimeVersion;
use subspace_runtime_primitives::utility::{nested_utility_call_iter, MaybeIntoUtilityCall};
use subspace_runtime_primitives::{
BlockNumber as ConsensusBlockNumber, Hash as ConsensusBlockHash, Moment,
SlowAdjustingFeeUpdate, SHANNON, SSC,
Expand Down Expand Up @@ -148,15 +149,13 @@ pub type Executive = domain_pallet_executive::Executive<
AllPalletsWithSystem,
>;

const MAX_CONTRACT_RECURSION_DEPTH: u16 = 5;

/// Rejects contracts that can't be created under the current allow list.
/// Returns false if the call is a contract call, and the account is *not* allowed to call it.
/// Otherwise, returns true.
pub fn is_create_contract_allowed(call: &RuntimeCall, signer: &AccountId) -> bool {
// Only enter recursive code if this account can't create contracts
// Only enter allocating code if this account can't create contracts
if !pallet_evm_tracker::Pallet::<Runtime>::is_allowed_to_create_contracts(signer)
&& is_create_contract(call, MAX_CONTRACT_RECURSION_DEPTH)
&& is_create_contract(call)
{
return false;
}
Expand All @@ -169,9 +168,9 @@ pub fn is_create_contract_allowed(call: &RuntimeCall, signer: &AccountId) -> boo
/// Returns false if the call is a contract call, and there is a specific (possibly empty) allow
/// list. Otherwise, returns true.
pub fn is_create_unsigned_contract_allowed(call: &RuntimeCall) -> bool {
// Only enter recursive code if unsigned contracts can't be created
// Only enter allocating code if unsigned contracts can't be created
if !pallet_evm_tracker::Pallet::<Runtime>::is_allowed_to_create_unsigned_contracts()
&& is_create_contract(call, MAX_CONTRACT_RECURSION_DEPTH)
&& is_create_contract(call)
{
return false;
}
Expand All @@ -181,44 +180,41 @@ pub fn is_create_unsigned_contract_allowed(call: &RuntimeCall) -> bool {
}

/// Returns true if the call is a contract creation call.
pub fn is_create_contract(call: &RuntimeCall, mut recursion_depth_left: u16) -> bool {
if recursion_depth_left == 0 {
// If the recursion depth is reached, we assume the call contains a contract.
return true;
}

recursion_depth_left -= 1;

match call {
RuntimeCall::EVM(pallet_evm::Call::create { .. })
| RuntimeCall::EVM(pallet_evm::Call::create2 { .. }) => true,
RuntimeCall::Ethereum(pallet_ethereum::Call::transact {
transaction: EthereumTransaction::Legacy(transaction),
..
}) => transaction.action == TransactionAction::Create,
RuntimeCall::Ethereum(pallet_ethereum::Call::transact {
transaction: EthereumTransaction::EIP2930(transaction),
..
}) => transaction.action == TransactionAction::Create,
RuntimeCall::Ethereum(pallet_ethereum::Call::transact {
transaction: EthereumTransaction::EIP1559(transaction),
..
}) => transaction.action == TransactionAction::Create,
RuntimeCall::Utility(utility_call) => match utility_call {
pallet_utility::Call::batch { calls }
| pallet_utility::Call::batch_all { calls }
| pallet_utility::Call::force_batch { calls } => calls
.iter()
.any(|call| is_create_contract(call, recursion_depth_left)),
pallet_utility::Call::as_derivative { call, .. }
| pallet_utility::Call::dispatch_as { call, .. }
| pallet_utility::Call::with_weight { call, .. } => {
is_create_contract(call, recursion_depth_left)
pub fn is_create_contract(call: &RuntimeCall) -> bool {
for call in nested_utility_call_iter::<Runtime>(call) {
match call {
RuntimeCall::EVM(pallet_evm::Call::create { .. })
| RuntimeCall::EVM(pallet_evm::Call::create2 { .. }) => return true,
RuntimeCall::Ethereum(pallet_ethereum::Call::transact {
transaction: EthereumTransaction::Legacy(transaction),
..
}) => {
if transaction.action == TransactionAction::Create {
return true;
}
}
RuntimeCall::Ethereum(pallet_ethereum::Call::transact {
transaction: EthereumTransaction::EIP2930(transaction),
..
}) => {
if transaction.action == TransactionAction::Create {
return true;
}
}
RuntimeCall::Ethereum(pallet_ethereum::Call::transact {
transaction: EthereumTransaction::EIP1559(transaction),
..
}) => {
if transaction.action == TransactionAction::Create {
return true;
}
}
pallet_utility::Call::__Ignore(..) => false,
},
_ => false,
// Inconclusive, might contain nested calls
_ => {}
}
}

false
}

/// Reject contract creation, unless the account is in the current evm contract allow list.
Expand Down Expand Up @@ -905,6 +901,16 @@ impl pallet_utility::Config for Runtime {
type WeightInfo = pallet_utility::weights::SubstrateWeight<Runtime>;
}

impl MaybeIntoUtilityCall<Runtime> for RuntimeCall {
/// If this call is a `pallet_utility::Call<Runtime>` call, returns the inner call.
fn maybe_into_utility_call(&self) -> Option<&pallet_utility::Call<Runtime>> {
match self {
RuntimeCall::Utility(call) => Some(call),
_ => None,
}
}
}

// Create the runtime by composing the FRAME pallets that were previously configured.
//
// NOTE: Currently domain runtime does not naturally support the pallets with inherent extrinsics.
Expand Down
1 change: 1 addition & 0 deletions test/subspace-test-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,7 @@ fn is_xdm_mmr_proof_valid(ext: &<Block as BlockT>::Extrinsic) -> Option<bool> {
}
}

// This code must be kept in sync with `crates/subspace-runtime/src/object_mapping.rs`.
fn extract_utility_block_object_mapping(
mut base_offset: u32,
objects: &mut Vec<BlockObject>,
Expand Down

0 comments on commit c5c1880

Please sign in to comment.