From 4990b09430e789ff53c64d4011dde19564329a3c Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 14 Jan 2025 20:32:38 +1000 Subject: [PATCH 01/13] Expand an unclear comment --- crates/subspace-runtime/src/signed_extensions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/subspace-runtime/src/signed_extensions.rs b/crates/subspace-runtime/src/signed_extensions.rs index 5d42cca058..ab9079b183 100644 --- a/crates/subspace-runtime/src/signed_extensions.rs +++ b/crates/subspace-runtime/src/signed_extensions.rs @@ -7,7 +7,7 @@ use sp_runtime::transaction_validity::{ }; use sp_std::prelude::*; -/// Disable specific pallets. +/// Disable balance transfers, if configured in the runtime. #[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, Default, TypeInfo)] pub struct DisablePallets; From cd72ab126e6339c48f3302d3c3d86bc8ea02cfea Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 15 Jan 2025 11:51:10 +1000 Subject: [PATCH 02/13] Use consistent syntax for Call::transact --- domains/runtime/evm/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/domains/runtime/evm/src/lib.rs b/domains/runtime/evm/src/lib.rs index 965573b918..64d57c0825 100644 --- a/domains/runtime/evm/src/lib.rs +++ b/domains/runtime/evm/src/lib.rs @@ -41,9 +41,9 @@ use frame_support::weights::{ConstantMultiplier, Weight}; use frame_support::{construct_runtime, parameter_types}; use frame_system::limits::{BlockLength, BlockWeights}; use pallet_block_fees::fees::OnChargeDomainTransaction; -use pallet_ethereum::Call::transact; use pallet_ethereum::{ - Call, PostLogContent, Transaction as EthereumTransaction, TransactionData, TransactionStatus, + PostLogContent, Transaction as EthereumTransaction, TransactionAction, TransactionData, + TransactionStatus, }; use pallet_evm::{ Account as EVMAccount, EnsureAddressNever, EnsureAddressRoot, FeeCalculator, @@ -946,7 +946,7 @@ fn pre_dispatch_evm_transaction( { let _ = transaction_validity?; - let Call::transact { transaction } = call; + let pallet_ethereum::Call::transact { transaction } = call; domain_check_weight::CheckWeight::::do_pre_dispatch(dispatch_info, len)?; let transaction_data: TransactionData = (&transaction).into(); @@ -1526,7 +1526,7 @@ impl_runtime_apis! { xts: Vec<::Extrinsic>, ) -> Vec { xts.into_iter().filter_map(|xt| match xt.0.function { - RuntimeCall::Ethereum(transact { transaction }) => Some(transaction), + RuntimeCall::Ethereum(pallet_ethereum::Call::transact { transaction }) => Some(transaction), _ => None }).collect::>() } @@ -1560,7 +1560,7 @@ impl_runtime_apis! { impl fp_rpc::ConvertTransactionRuntimeApi for Runtime { fn convert_transaction(transaction: EthereumTransaction) -> ::Extrinsic { UncheckedExtrinsic::new_unsigned( - pallet_ethereum::Call::::transact { transaction }.into(), + pallet_ethereum::Call::transact { transaction }.into(), ) } } From dad99ffbe9ce006c68fdbe21beb13efeec10e989 Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 14 Jan 2025 20:32:38 +1000 Subject: [PATCH 03/13] Add a contract creation filter to pallet-evm-nonce-tracker --- Cargo.lock | 1 + domains/pallets/evm_nonce_tracker/Cargo.toml | 4 +- domains/pallets/evm_nonce_tracker/src/lib.rs | 54 +++++++++++++++++++- 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 24779abeb8..9f7c9770c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7777,6 +7777,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-core", + "sp-domains", "sp-runtime", ] diff --git a/domains/pallets/evm_nonce_tracker/Cargo.toml b/domains/pallets/evm_nonce_tracker/Cargo.toml index cc8d3ab223..2ba104e25f 100644 --- a/domains/pallets/evm_nonce_tracker/Cargo.toml +++ b/domains/pallets/evm_nonce_tracker/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" license = "Apache-2.0" homepage = "https://subspace.network" repository = "https://github.com/autonomys/subspace" -description = "Subspace node pallet for EVM account nonce tracker." +description = "Subspace node pallet for EVM account nonce tracker and contract creation allow list." include = [ "/src", "/Cargo.toml", @@ -18,6 +18,7 @@ frame-support = { default-features = false, git = "https://github.com/subspace/p frame-system = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "94a1a8143a89bbe9f938c1939ff68abc1519a305" } scale-info = { version = "2.11.2", default-features = false, features = ["derive"] } sp-core = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "94a1a8143a89bbe9f938c1939ff68abc1519a305" } +sp-domains = { version = "0.1.0", default-features = false, path = "../../../crates/sp-domains" } sp-runtime = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "94a1a8143a89bbe9f938c1939ff68abc1519a305" } [features] @@ -28,5 +29,6 @@ std = [ "frame-system/std", "scale-info/std", "sp-core/std", + "sp-domains/std", "sp-runtime/std", ] diff --git a/domains/pallets/evm_nonce_tracker/src/lib.rs b/domains/pallets/evm_nonce_tracker/src/lib.rs index 4033115be8..8f5edf1da2 100644 --- a/domains/pallets/evm_nonce_tracker/src/lib.rs +++ b/domains/pallets/evm_nonce_tracker/src/lib.rs @@ -28,8 +28,9 @@ use sp_core::U256; #[frame_support::pallet] mod pallet { use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::BlockNumberFor; + use frame_system::pallet_prelude::*; use sp_core::U256; + use sp_domains::PermissionedActionAllowedBy; #[pallet::config] pub trait Config: frame_system::Config {} @@ -41,11 +42,55 @@ mod pallet { pub(super) type AccountNonce = StorageMap<_, Identity, T::AccountId, U256, OptionQuery>; + /// Storage to hold EVM contract creation allow list accounts. + #[pallet::storage] + pub(super) type ContractCreationAllowedBy = + StorageValue<_, PermissionedActionAllowedBy, OptionQuery>; + /// Pallet EVM account nonce tracker. #[pallet::pallet] #[pallet::without_storage_info] pub struct Pallet(_); + #[pallet::call] + impl Pallet { + /// Update ContractCreationAllowedBy storage by Sudo. + #[pallet::call_index(0)] + #[pallet::weight(::DbWeight::get().reads_writes(0, 1))] + pub fn set_contract_creation_allowed_by( + origin: OriginFor, + contract_creation_allowed_by: PermissionedActionAllowedBy, + ) -> DispatchResult { + ensure_root(origin)?; + ContractCreationAllowedBy::::put(contract_creation_allowed_by); + Ok(()) + } + } + + #[pallet::genesis_config] + pub struct GenesisConfig { + pub contract_creation_allowed_by: PermissionedActionAllowedBy, + } + + impl Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { + // If the allow listed accounts are not configured, maintain compatibility with + // legacy chainspecs, by allowing anyone to create contracts. + contract_creation_allowed_by: PermissionedActionAllowedBy::Anyone, + } + } + } + + // In the genesis config, the allow list should be set to the domain sudo for the private EVM + // domain. + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + ContractCreationAllowedBy::::put(self.contract_creation_allowed_by.clone()); + } + } + #[pallet::hooks] impl Hooks> for Pallet { fn on_finalize(_now: BlockNumberFor) { @@ -66,4 +111,11 @@ impl Pallet { pub fn set_account_nonce(account: T::AccountId, nonce: U256) { AccountNonce::::set(account, Some(nonce)) } + + /// Returns true if the account is allowed to create contracts. + pub fn is_allowed_to_create_contracts(signer: &T::AccountId) -> bool { + ContractCreationAllowedBy::::get() + .map(|allowed_by| allowed_by.is_allowed(signer)) + .unwrap_or_default() + } } From fa8967da8769096d667cdc0568ecfa1f2c78e6e3 Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 14 Jan 2025 20:32:38 +1000 Subject: [PATCH 04/13] Add a contract creation filter to the EVM domain --- domains/pallets/evm_nonce_tracker/src/lib.rs | 24 ----- domains/runtime/evm/src/lib.rs | 103 +++++++++++++++++++ 2 files changed, 103 insertions(+), 24 deletions(-) diff --git a/domains/pallets/evm_nonce_tracker/src/lib.rs b/domains/pallets/evm_nonce_tracker/src/lib.rs index 8f5edf1da2..8209dc060b 100644 --- a/domains/pallets/evm_nonce_tracker/src/lib.rs +++ b/domains/pallets/evm_nonce_tracker/src/lib.rs @@ -67,30 +67,6 @@ mod pallet { } } - #[pallet::genesis_config] - pub struct GenesisConfig { - pub contract_creation_allowed_by: PermissionedActionAllowedBy, - } - - impl Default for GenesisConfig { - fn default() -> Self { - GenesisConfig { - // If the allow listed accounts are not configured, maintain compatibility with - // legacy chainspecs, by allowing anyone to create contracts. - contract_creation_allowed_by: PermissionedActionAllowedBy::Anyone, - } - } - } - - // In the genesis config, the allow list should be set to the domain sudo for the private EVM - // domain. - #[pallet::genesis_build] - impl BuildGenesisConfig for GenesisConfig { - fn build(&self) { - ContractCreationAllowedBy::::put(self.contract_creation_allowed_by.clone()); - } - } - #[pallet::hooks] impl Hooks> for Pallet { fn on_finalize(_now: BlockNumberFor) { diff --git a/domains/runtime/evm/src/lib.rs b/domains/runtime/evm/src/lib.rs index 64d57c0825..f5b32ed38b 100644 --- a/domains/runtime/evm/src/lib.rs +++ b/domains/runtime/evm/src/lib.rs @@ -69,6 +69,7 @@ use sp_runtime::traits::{ }; use sp_runtime::transaction_validity::{ InvalidTransaction, TransactionSource, TransactionValidity, TransactionValidityError, + ValidTransaction, }; use sp_runtime::{ generic, impl_opaque_keys, ApplyExtrinsicResult, ConsensusEngineId, Digest, @@ -121,6 +122,7 @@ pub type SignedExtra = ( frame_system::CheckNonce, domain_check_weight::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, + CheckContractCreation, ); /// Custom signed extra for check_and_pre_dispatch. @@ -134,6 +136,7 @@ type CustomSignedExtra = ( pallet_evm_nonce_tracker::CheckNonce, domain_check_weight::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, + CheckContractCreation, ); /// Unchecked extrinsic type as expected by this runtime. @@ -152,6 +155,95 @@ pub type Executive = domain_pallet_executive::Executive< AllPalletsWithSystem, >; +/// 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 { + if is_create_contract(call) + && !pallet_evm_nonce_tracker::Pallet::::is_allowed_to_create_contracts(signer) + { + return false; + } + + // If it's not a contract call, or the account is allowed to create contracts, return true. + true +} + +/// Returns true if the call is a contract creation call. +pub fn is_create_contract(call: &RuntimeCall) -> bool { + 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, + // TODO: does this need a recursion limit? + 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(is_create_contract), + pallet_utility::Call::as_derivative { call, .. } + | pallet_utility::Call::dispatch_as { call, .. } + | pallet_utility::Call::with_weight { call, .. } => is_create_contract(call), + pallet_utility::Call::__Ignore(..) => false, + }, + _ => false, + } +} + +/// Reject contract creation, unless the account is in the current evm contract allow list. +#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, Default, TypeInfo)] +pub struct CheckContractCreation; + +impl SignedExtension for CheckContractCreation { + const IDENTIFIER: &'static str = "CheckContractCreation"; + type AccountId = ::AccountId; + type Call = ::RuntimeCall; + type AdditionalSigned = (); + type Pre = (); + + fn additional_signed(&self) -> Result { + Ok(()) + } + + fn validate( + &self, + who: &Self::AccountId, + call: &Self::Call, + _info: &DispatchInfoOf, + _len: usize, + ) -> TransactionValidity { + // Reject contract creation unless the account is in the allow list. + if !is_create_contract_allowed(call, who) { + InvalidTransaction::Call.into() + } else { + Ok(ValidTransaction::default()) + } + } + + fn pre_dispatch( + self, + who: &Self::AccountId, + call: &Self::Call, + info: &DispatchInfoOf, + len: usize, + ) -> Result { + self.validate(who, call, info, len)?; + Ok(()) + } + + // TODO: can unsigned calls create contracts? +} + impl fp_self_contained::SelfContainedCall for RuntimeCall { type SignedInfo = H160; @@ -175,6 +267,11 @@ impl fp_self_contained::SelfContainedCall for RuntimeCall { dispatch_info: &DispatchInfoOf, len: usize, ) -> Option { + if !is_create_contract_allowed(self, &(*info).into()) { + // TODO: should this be Custom() instead? + return Some(Err(InvalidTransaction::Call.into())); + } + match self { RuntimeCall::Ethereum(call) => { // Ensure the caller can pay for the consensus chain storage fee @@ -199,6 +296,11 @@ impl fp_self_contained::SelfContainedCall for RuntimeCall { dispatch_info: &DispatchInfoOf, len: usize, ) -> Option> { + if !is_create_contract_allowed(self, &(*info).into()) { + // TODO: should this be Custom() instead? + return Some(Err(InvalidTransaction::Call.into())); + } + match self { RuntimeCall::Ethereum(call) => { // Withdraw the consensus chain storage fee from the caller and record @@ -1009,6 +1111,7 @@ fn check_transaction_and_do_pre_dispatch_inner( pallet_evm_nonce_tracker::CheckNonce::from(extra.5 .0), extra.6, extra.7, + extra.8, ); custom_extra From e66788f93f62901c1ed025cf36445cf0c1675651 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 17 Jan 2025 11:08:06 +1000 Subject: [PATCH 05/13] Fix grammar --- crates/sp-domains/src/lib.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/sp-domains/src/lib.rs b/crates/sp-domains/src/lib.rs index b87f5824f3..5ef9664de2 100644 --- a/crates/sp-domains/src/lib.rs +++ b/crates/sp-domains/src/lib.rs @@ -983,17 +983,17 @@ impl DomainsDigestItem for DigestItem { /// EVM chain Id storage key. /// -/// This and next function should ideally use Host function to fetch the storage key +/// This function should ideally use a Host function to fetch the storage key /// from the domain runtime. But since the Host function is not available at Genesis, we have to /// assume the storage keys. /// TODO: once the chain is launched in mainnet, we should use the Host function for all domain instances. pub(crate) fn evm_chain_id_storage_key() -> StorageKey { StorageKey( storage_prefix( - // This is the name used for the `pallet_evm_chain_id` in the `construct_runtime` macro + // This is the name used for `pallet_evm_chain_id` in the `construct_runtime` macro // i.e. `EVMChainId: pallet_evm_chain_id = 82,` "EVMChainId".as_bytes(), - // This is the storage item name used inside the `pallet_evm_chain_id` + // This is the storage item name used inside `pallet_evm_chain_id` "ChainId".as_bytes(), ) .to_vec(), @@ -1009,9 +1009,9 @@ pub(crate) fn evm_chain_id_storage_key() -> StorageKey { pub fn domain_total_issuance_storage_key() -> StorageKey { StorageKey( storage_prefix( - // This is the name used for the `pallet_balances` in the `construct_runtime` macro + // This is the name used for `pallet_balances` in the `construct_runtime` macro "Balances".as_bytes(), - // This is the storage item name used inside the `pallet_balances` + // This is the storage item name used inside `pallet_balances` "TotalIssuance".as_bytes(), ) .to_vec(), @@ -1020,7 +1020,7 @@ pub fn domain_total_issuance_storage_key() -> StorageKey { /// Account info on frame_system on Domains /// -/// This function should ideally use Host function to fetch the storage key +/// This function should ideally use a Host function to fetch the storage key /// from the domain runtime. But since the Host function is not available at Genesis, we have to /// assume the storage keys. /// TODO: once the chain is launched in mainnet, we should use the Host function for all domain instances. @@ -1036,17 +1036,17 @@ pub fn domain_account_storage_key(who: AccountId) -> StorageK StorageKey(final_key) } -/// The storage key of the `SelfDomainId` storage item in the `pallet-domain-id` +/// The storage key of the `SelfDomainId` storage item in `pallet-domain-id` /// -/// Any change to the storage item name or the `pallet-domain-id` name used in the `construct_runtime` +/// Any change to the storage item name or `pallet-domain-id` name used in the `construct_runtime` /// macro must be reflected here. pub fn self_domain_id_storage_key() -> StorageKey { StorageKey( frame_support::storage::storage_prefix( - // This is the name used for the `pallet-domain-id` in the `construct_runtime` macro + // This is the name used for `pallet-domain-id` in the `construct_runtime` macro // i.e. `SelfDomainId: pallet_domain_id = 90` "SelfDomainId".as_bytes(), - // This is the storage item name used inside the `pallet-domain-id` + // This is the storage item name used inside `pallet-domain-id` "SelfDomainId".as_bytes(), ) .to_vec(), From 830694c4ffbb08c356b53a7a46d578ac7274c312 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 17 Jan 2025 11:11:56 +1000 Subject: [PATCH 06/13] Move some ethereum types to domain primitives --- domains/primitives/runtime/src/lib.rs | 9 ++++++++- domains/runtime/evm/src/lib.rs | 17 +++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/domains/primitives/runtime/src/lib.rs b/domains/primitives/runtime/src/lib.rs index e8989b499f..8dd03603db 100644 --- a/domains/primitives/runtime/src/lib.rs +++ b/domains/primitives/runtime/src/lib.rs @@ -24,7 +24,7 @@ extern crate alloc; use alloc::string::String; #[cfg(not(feature = "std"))] use alloc::vec::Vec; -pub use fp_account::AccountId20; +pub use fp_account::{AccountId20, EthereumSignature as EVMSignature}; use frame_support::dispatch::DispatchClass; use frame_support::weights::constants::{BlockExecutionWeight, ExtrinsicBaseWeight}; use frame_system::limits::{BlockLength, BlockWeights}; @@ -68,6 +68,13 @@ pub const SLOT_DURATION: u64 = 1000; /// The EVM chain Id type pub type EVMChainId = u64; +/// Alias to 512-bit hash when used in the context of a transaction signature on the EVM chain. +pub type EthereumSignature = EVMSignature; + +/// Some way of identifying an account on the EVM chain. We intentionally make it equivalent +/// to the public key of the EVM transaction signing scheme. +pub type EthereumAccountId = <::Signer as IdentifyAccount>::AccountId; + /// Dispatch ratio for domains pub const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(65); diff --git a/domains/runtime/evm/src/lib.rs b/domains/runtime/evm/src/lib.rs index f5b32ed38b..1eeffae3a3 100644 --- a/domains/runtime/evm/src/lib.rs +++ b/domains/runtime/evm/src/lib.rs @@ -19,13 +19,13 @@ use core::mem; use domain_runtime_primitives::opaque::Header; pub use domain_runtime_primitives::{ block_weights, maximum_block_length, maximum_domain_block_weight, opaque, Balance, BlockNumber, - Hash, Nonce, EXISTENTIAL_DEPOSIT, + EthereumAccountId as AccountId, EthereumSignature as Signature, Hash, Nonce, + EXISTENTIAL_DEPOSIT, }; use domain_runtime_primitives::{ CheckExtrinsicsValidityError, DecodeExtrinsicError, HoldIdentifier, ERR_BALANCE_OVERFLOW, ERR_NONCE_OVERFLOW, SLOT_DURATION, }; -use fp_account::EthereumSignature; use fp_self_contained::{CheckedSignature, SelfContainedCall}; use frame_support::dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo}; use frame_support::genesis_builder_helper::{build_state, get_preset}; @@ -63,9 +63,9 @@ use sp_messenger_host_functions::{get_storage_key, StorageKeyRequest}; use sp_mmr_primitives::EncodableOpaqueLeaf; use sp_runtime::generic::Era; use sp_runtime::traits::{ - BlakeTwo256, Block as BlockT, Checkable, DispatchInfoOf, Dispatchable, IdentifyAccount, - IdentityLookup, Keccak256, NumberFor, One, PostDispatchInfoOf, SignedExtension, - UniqueSaturatedInto, ValidateUnsigned, Verify, Zero, + BlakeTwo256, Block as BlockT, Checkable, DispatchInfoOf, Dispatchable, IdentityLookup, + Keccak256, NumberFor, One, PostDispatchInfoOf, SignedExtension, UniqueSaturatedInto, + ValidateUnsigned, Zero, }; use sp_runtime::transaction_validity::{ InvalidTransaction, TransactionSource, TransactionValidity, TransactionValidityError, @@ -90,13 +90,6 @@ use subspace_runtime_primitives::{ SlowAdjustingFeeUpdate, SHANNON, SSC, }; -/// Alias to 512-bit hash when used in the context of a transaction signature on the chain. -pub type Signature = EthereumSignature; - -/// Some way of identifying an account on the chain. We intentionally make it equivalent -/// to the public key of our transaction signing scheme. -pub type AccountId = <::Signer as IdentifyAccount>::AccountId; - /// The address format for describing accounts. pub type Address = AccountId; From 7fc8db5e9750dd2f04d07b4497eef1f26bc1c44b Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 17 Jan 2025 11:12:25 +1000 Subject: [PATCH 07/13] Allow contract creation to be set per domain instance --- crates/sp-domains/src/lib.rs | 23 +++++++++++++++++++++-- crates/sp-domains/src/storage.rs | 17 +++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/crates/sp-domains/src/lib.rs b/crates/sp-domains/src/lib.rs index 5ef9664de2..9cb478016b 100644 --- a/crates/sp-domains/src/lib.rs +++ b/crates/sp-domains/src/lib.rs @@ -1000,9 +1000,28 @@ pub(crate) fn evm_chain_id_storage_key() -> StorageKey { ) } -/// Total issuance storage for Domains. +/// EVM contract creation allow list storage key. /// -/// This function should ideally use Host function to fetch the storage key +/// This function should ideally use a Host function to fetch the storage key +/// from the domain runtime. But since the Host function is not available at Genesis, we have to +/// assume the storage keys. +/// TODO: once the chain is launched in mainnet, we should use the Host function for all domain instances. +pub(crate) fn evm_contract_creation_allowed_by_storage_key() -> StorageKey { + StorageKey( + storage_prefix( + // This is the name used for `pallet_evm_nonce_tracker` in the `construct_runtime` macro + // i.e. `EVMNoncetracker: pallet_evm_nonce_tracker = 84,` + "EVMNoncetracker".as_bytes(), + // This is the storage item name used inside `pallet_evm_nonce_tracker` + "ContractCreationAllowedBy".as_bytes(), + ) + .to_vec(), + ) +} + +/// Total issuance storage key for Domains. +/// +/// This function should ideally use a Host function to fetch the storage key /// from the domain runtime. But since the Host function is not available at Genesis, we have to /// assume the storage keys. /// TODO: once the chain is launched in mainnet, we should use the Host function for all domain instances. diff --git a/crates/sp-domains/src/storage.rs b/crates/sp-domains/src/storage.rs index 2618bab093..00e45635f1 100644 --- a/crates/sp-domains/src/storage.rs +++ b/crates/sp-domains/src/storage.rs @@ -1,10 +1,13 @@ #[cfg(not(feature = "std"))] extern crate alloc; -use crate::{evm_chain_id_storage_key, self_domain_id_storage_key, DomainId}; +use crate::{ + evm_chain_id_storage_key, evm_contract_creation_allowed_by_storage_key, + self_domain_id_storage_key, DomainId, PermissionedActionAllowedBy, +}; #[cfg(not(feature = "std"))] use alloc::vec::Vec; -use domain_runtime_primitives::EVMChainId; +use domain_runtime_primitives::{EVMChainId, EthereumAccountId}; use hash_db::Hasher; use parity_scale_codec::{Codec, Decode, Encode}; use scale_info::TypeInfo; @@ -61,6 +64,16 @@ impl RawGenesis { .insert(evm_chain_id_storage_key(), StorageData(chain_id.encode())); } + pub fn set_evm_contract_creation_allowed_by( + &mut self, + contract_creation_allowed_by: PermissionedActionAllowedBy, + ) { + let _ = self.top.insert( + evm_contract_creation_allowed_by_storage_key(), + StorageData(contract_creation_allowed_by.encode()), + ); + } + pub fn set_top_storages(&mut self, storages: Vec<(StorageKey, StorageData)>) { for (k, v) in storages { let _ = self.top.insert(k, v); From 8273214e25d5c9cfc8e59fa28329f8411f4b052b Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 17 Jan 2025 12:05:09 +1000 Subject: [PATCH 08/13] Add contract creation to domain params --- crates/pallet-domains/src/benchmarking.rs | 2 + crates/pallet-domains/src/domain_registry.rs | 36 +++++++--- crates/pallet-domains/src/lib.rs | 7 +- crates/pallet-domains/src/runtime_registry.rs | 41 ++++++++--- crates/pallet-domains/src/staking.rs | 2 + crates/pallet-domains/src/tests.rs | 2 + crates/sp-domains/src/lib.rs | 71 ++++++++++++++++++- crates/sp-domains/src/storage.rs | 2 +- .../src/chain_spec.rs | 8 ++- crates/subspace-node/src/chain_spec.rs | 1 + .../src/domain/auto_id_chain_spec.rs | 3 +- crates/subspace-node/src/domain/cli.rs | 5 +- .../src/domain/evm_chain_spec.rs | 6 +- domains/pallets/evm_nonce_tracker/src/lib.rs | 11 ++- domains/primitives/runtime/Cargo.toml | 2 +- .../src/auto_id_domain_chain_spec.rs | 6 +- .../src/evm_domain_chain_spec.rs | 6 +- 17 files changed, 177 insertions(+), 34 deletions(-) diff --git a/crates/pallet-domains/src/benchmarking.rs b/crates/pallet-domains/src/benchmarking.rs index cee5e4cc4f..70f0af8bf3 100644 --- a/crates/pallet-domains/src/benchmarking.rs +++ b/crates/pallet-domains/src/benchmarking.rs @@ -540,6 +540,7 @@ mod benchmarks { bundle_slot_probability: (1, 1), operator_allow_list: OperatorAllowList::Anyone, initial_balances: Default::default(), + domain_runtime_config: Default::default(), }; assert_ok!(Domains::::set_permissioned_action_allowed_by( @@ -910,6 +911,7 @@ mod benchmarks { bundle_slot_probability: (1, 1), operator_allow_list: OperatorAllowList::Anyone, initial_balances: Default::default(), + domain_runtime_config: Default::default(), }; assert_ok!(Domains::::set_permissioned_action_allowed_by( diff --git a/crates/pallet-domains/src/domain_registry.rs b/crates/pallet-domains/src/domain_registry.rs index 0bf554c271..09cd03883e 100644 --- a/crates/pallet-domains/src/domain_registry.rs +++ b/crates/pallet-domains/src/domain_registry.rs @@ -26,8 +26,8 @@ use scale_info::TypeInfo; use sp_core::Get; use sp_domains::{ calculate_max_bundle_weight_and_size, derive_domain_block_hash, DomainBundleLimit, DomainId, - DomainSudoCall, DomainsDigestItem, DomainsTransfersTracker, OnDomainInstantiated, - OperatorAllowList, RuntimeId, RuntimeType, + DomainRuntimeConfig, DomainSudoCall, DomainsDigestItem, DomainsTransfersTracker, + OnDomainInstantiated, OperatorAllowList, RuntimeId, RuntimeType, }; use sp_runtime::traits::{CheckedAdd, Zero}; use sp_runtime::DigestItem; @@ -56,6 +56,7 @@ pub enum Error { DuplicateInitialAccounts, FailedToGenerateRawGenesis(crate::runtime_registry::Error), BundleLimitCalculationOverflow, + InvalidConfigForRuntimeType, } #[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)] @@ -71,10 +72,12 @@ pub struct DomainConfig { /// The probability of successful bundle in a slot (active slots coefficient). This defines the /// expected bundle production rate, must be `> 0` and `≤ 1`. pub bundle_slot_probability: (u64, u64), - /// Allowed operators to operate for this domain. + /// Accounts allowed to operate on this domain. pub operator_allow_list: OperatorAllowList, - // Initial balances for Domain. + // Initial balances for this domain. pub initial_balances: Vec<(MultiAccountId, Balance)>, + /// Configurations for a specific type of domain runtime, for example, EVM. + pub domain_runtime_config: DomainRuntimeConfig, } /// Parameters of the `instantiate_domain` call, it is similar to `DomainConfig` except the `max_bundle_size/weight` @@ -90,6 +93,7 @@ pub struct DomainConfigParams { pub bundle_slot_probability: (u64, u64), pub operator_allow_list: OperatorAllowList, pub initial_balances: Vec<(MultiAccountId, Balance)>, + pub domain_runtime_config: DomainRuntimeConfig, } pub fn into_domain_config( @@ -102,6 +106,7 @@ pub fn into_domain_config( bundle_slot_probability, operator_allow_list, initial_balances, + domain_runtime_config, } = domain_config_params; let DomainBundleLimit { @@ -126,6 +131,7 @@ pub fn into_domain_config( bundle_slot_probability, operator_allow_list, initial_balances, + domain_runtime_config, }) } @@ -251,16 +257,26 @@ pub(crate) fn do_instantiate_domain( runtime_object }); - let domain_runtime_info = match runtime_obj.runtime_type { - RuntimeType::Evm => { + let domain_runtime_info = match ( + runtime_obj.runtime_type, + &domain_config.domain_runtime_config, + ) { + (RuntimeType::Evm, DomainRuntimeConfig::Evm(domain_runtime_config)) => { let evm_chain_id = NextEVMChainId::::get(); let next_evm_chain_id = evm_chain_id.checked_add(1).ok_or(Error::MaxEVMChainId)?; NextEVMChainId::::set(next_evm_chain_id); - DomainRuntimeInfo::EVM { + + DomainRuntimeInfo::Evm { chain_id: evm_chain_id, + domain_runtime_config: domain_runtime_config.clone(), + } + } + (RuntimeType::AutoId, DomainRuntimeConfig::AutoId(domain_runtime_config)) => { + DomainRuntimeInfo::AutoId { + domain_runtime_config: domain_runtime_config.clone(), } } - RuntimeType::AutoId => DomainRuntimeInfo::AutoId, + _ => return Err(Error::InvalidConfigForRuntimeType), }; // burn total issuance on domain from owners account and track the domain balance @@ -285,7 +301,7 @@ pub(crate) fn do_instantiate_domain( let raw_genesis = into_complete_raw_genesis::( runtime_obj, domain_id, - domain_runtime_info, + &domain_runtime_info, total_issuance, domain_config.initial_balances.clone(), ) @@ -398,6 +414,7 @@ mod tests { bundle_slot_probability: (0, 0), operator_allow_list: OperatorAllowList::Anyone, initial_balances: Default::default(), + domain_runtime_config: Default::default(), }; let mut ext = new_test_ext(); @@ -555,6 +572,7 @@ mod tests { bundle_slot_probability: (1, 1), operator_allow_list: OperatorAllowList::Anyone, initial_balances: vec![(MultiAccountId::Raw(vec![0, 1, 2, 3, 4, 5]), 1_000_000 * SSC)], + domain_runtime_config: Default::default(), }; let mut ext = new_test_ext(); diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index 9b9b9c5089..fbf91ba2ac 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -1312,7 +1312,7 @@ mod pallet { let block_number = frame_system::Pallet::::current_block_number(); let runtime_id = do_register_runtime::( runtime_name, - runtime_type.clone(), + runtime_type, raw_genesis_storage, block_number, ) @@ -1825,6 +1825,7 @@ mod pallet { bundle_slot_probability: genesis_domain.bundle_slot_probability, operator_allow_list: genesis_domain.operator_allow_list, initial_balances: genesis_domain.initial_balances, + domain_runtime_config: genesis_domain.domain_runtime_config, }; let domain_owner = genesis_domain.owner_account_id; let domain_id = do_instantiate_domain::( @@ -2111,12 +2112,12 @@ impl Pallet { ) -> Option<(DomainInstanceData, BlockNumberFor)> { let domain_obj = DomainRegistry::::get(domain_id)?; let runtime_object = RuntimeRegistry::::get(domain_obj.domain_config.runtime_id)?; - let runtime_type = runtime_object.runtime_type.clone(); + let runtime_type = runtime_object.runtime_type; let total_issuance = domain_obj.domain_config.total_issuance()?; let raw_genesis = into_complete_raw_genesis::( runtime_object, domain_id, - domain_obj.domain_runtime_info, + &domain_obj.domain_runtime_info, total_issuance, domain_obj.domain_config.initial_balances, ) diff --git a/crates/pallet-domains/src/runtime_registry.rs b/crates/pallet-domains/src/runtime_registry.rs index f1e22056b2..47d12e9665 100644 --- a/crates/pallet-domains/src/runtime_registry.rs +++ b/crates/pallet-domains/src/runtime_registry.rs @@ -20,7 +20,10 @@ use scale_info::TypeInfo; use sp_core::crypto::AccountId32; use sp_core::Hasher; use sp_domains::storage::{RawGenesis, StorageData, StorageKey}; -use sp_domains::{DomainId, DomainsDigestItem, RuntimeId, RuntimeObject, RuntimeType}; +use sp_domains::{ + AutoIdDomainRuntimeConfig, DomainId, DomainsDigestItem, EvmDomainRuntimeConfig, RuntimeId, + RuntimeObject, RuntimeType, +}; use sp_runtime::traits::{CheckedAdd, Get, Zero}; use sp_runtime::DigestItem; use sp_std::vec; @@ -42,15 +45,26 @@ pub enum Error { } /// Domain runtime specific information to create domain raw genesis. -#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq, Copy)] +#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)] pub enum DomainRuntimeInfo { - EVM { chain_id: EVMChainId }, - AutoId, + Evm { + /// The dynamic EVM chain id for this domain. + chain_id: EVMChainId, + /// The EVM-specific domain runtime config. + domain_runtime_config: EvmDomainRuntimeConfig, + }, + AutoId { + /// The AutoId-specific domain runtime config. + domain_runtime_config: AutoIdDomainRuntimeConfig, + }, } impl Default for DomainRuntimeInfo { fn default() -> Self { - Self::EVM { chain_id: 0 } + Self::Evm { + chain_id: 0, + domain_runtime_config: EvmDomainRuntimeConfig::default(), + } } } @@ -93,7 +107,7 @@ fn derive_initial_balances_storages( pub fn into_complete_raw_genesis( runtime_obj: RuntimeObject, T::Hash>, domain_id: DomainId, - domain_runtime_info: DomainRuntimeInfo, + domain_runtime_info: &DomainRuntimeInfo, total_issuance: BalanceOf, initial_balances: Vec<(MultiAccountId, BalanceOf)>, ) -> Result { @@ -102,8 +116,15 @@ pub fn into_complete_raw_genesis( } = runtime_obj; raw_genesis.set_domain_id(domain_id); match domain_runtime_info { - DomainRuntimeInfo::EVM { chain_id } => { - raw_genesis.set_evm_chain_id(chain_id); + DomainRuntimeInfo::Evm { + chain_id, + domain_runtime_config, + } => { + raw_genesis.set_evm_chain_id(*chain_id); + raw_genesis.set_evm_contract_creation_allowed_by( + &domain_runtime_config.initial_contract_creation_allow_list, + ); + let initial_balances = initial_balances.into_iter().try_fold( Vec::<(AccountId20, BalanceOf)>::new(), |mut balances, (account_id, balance)| { @@ -122,7 +143,9 @@ pub fn into_complete_raw_genesis( initial_balances, )); } - DomainRuntimeInfo::AutoId => { + DomainRuntimeInfo::AutoId { + domain_runtime_config: _, + } => { let initial_balances = initial_balances.into_iter().try_fold( Vec::<(AccountId32, BalanceOf)>::new(), |mut balances, (account_id, balance)| { diff --git a/crates/pallet-domains/src/staking.rs b/crates/pallet-domains/src/staking.rs index 8e650c6bc3..ea06db4be1 100644 --- a/crates/pallet-domains/src/staking.rs +++ b/crates/pallet-domains/src/staking.rs @@ -1454,6 +1454,7 @@ pub(crate) mod tests { bundle_slot_probability: (0, 0), operator_allow_list: OperatorAllowList::Anyone, initial_balances: Default::default(), + domain_runtime_config: Default::default(), }; let domain_obj = DomainObject { @@ -1817,6 +1818,7 @@ pub(crate) mod tests { bundle_slot_probability: (0, 0), operator_allow_list: OperatorAllowList::Anyone, initial_balances: Default::default(), + domain_runtime_config: Default::default(), }; let domain_obj = DomainObject { diff --git a/crates/pallet-domains/src/tests.rs b/crates/pallet-domains/src/tests.rs index 60ac88303d..d572d41daf 100644 --- a/crates/pallet-domains/src/tests.rs +++ b/crates/pallet-domains/src/tests.rs @@ -474,6 +474,7 @@ pub(crate) fn register_genesis_domain(creator: u128, operator_ids: Vec OperatorAllowList { } /// Permissioned actions allowed by either specific accounts or anyone. -#[derive(TypeInfo, Encode, Decode, Debug, PartialEq, Clone, Serialize, Deserialize)] +#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum PermissionedActionAllowedBy { Accounts(Vec), Anyone, @@ -894,6 +894,69 @@ impl PermissionedActionAllowedBy true, } } + + pub fn is_anyone_allowed(&self) -> bool { + matches!(self, PermissionedActionAllowedBy::Anyone) + } +} + +/// EVM-specific domain runtime config. +#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct EvmDomainRuntimeConfig { + /// Accounts initially allowed to create contracts on an EVM domain. + /// The domain owner can update this list using a sudo call. + pub initial_contract_creation_allow_list: PermissionedActionAllowedBy, +} + +impl Default for EvmDomainRuntimeConfig { + fn default() -> Self { + EvmDomainRuntimeConfig { + initial_contract_creation_allow_list: PermissionedActionAllowedBy::Anyone, + } + } +} + +/// AutoId-specific domain runtime config. +#[derive( + TypeInfo, Debug, Default, Encode, Decode, Clone, PartialEq, Eq, Serialize, Deserialize, +)] +pub struct AutoIdDomainRuntimeConfig { + // Currently, there is no specific configuration for AutoId. +} + +/// Configrations for specific domain runtime kinds. +#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum DomainRuntimeConfig { + Evm(EvmDomainRuntimeConfig), + AutoId(AutoIdDomainRuntimeConfig), +} + +impl Default for DomainRuntimeConfig { + fn default() -> Self { + Self::default_evm() + } +} + +impl From for DomainRuntimeConfig { + fn from(evm_config: EvmDomainRuntimeConfig) -> Self { + DomainRuntimeConfig::Evm(evm_config) + } +} + +impl From for DomainRuntimeConfig { + fn from(auto_id_config: AutoIdDomainRuntimeConfig) -> Self { + DomainRuntimeConfig::AutoId(auto_id_config) + } +} + +impl DomainRuntimeConfig { + pub fn default_evm() -> Self { + DomainRuntimeConfig::Evm(EvmDomainRuntimeConfig::default()) + } + + pub fn default_auto_id() -> Self { + DomainRuntimeConfig::AutoId(AutoIdDomainRuntimeConfig::default()) + } } #[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -909,6 +972,8 @@ pub struct GenesisDomain { pub domain_name: String, pub bundle_slot_probability: (u64, u64), pub operator_allow_list: OperatorAllowList, + /// Configurations for a specific type of domain runtime, for example, EVM. + pub domain_runtime_config: DomainRuntimeConfig, // Genesis operator pub signing_key: OperatorPublicKey, @@ -921,7 +986,7 @@ pub struct GenesisDomain { /// Types of runtime pallet domains currently supports #[derive( - Debug, Default, Encode, Decode, TypeInfo, Clone, PartialEq, Eq, Serialize, Deserialize, + Debug, Default, Encode, Decode, TypeInfo, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, )] pub enum RuntimeType { #[default] diff --git a/crates/sp-domains/src/storage.rs b/crates/sp-domains/src/storage.rs index 00e45635f1..d317cf8254 100644 --- a/crates/sp-domains/src/storage.rs +++ b/crates/sp-domains/src/storage.rs @@ -66,7 +66,7 @@ impl RawGenesis { pub fn set_evm_contract_creation_allowed_by( &mut self, - contract_creation_allowed_by: PermissionedActionAllowedBy, + contract_creation_allowed_by: &PermissionedActionAllowedBy, ) { let _ = self.top.insert( evm_contract_creation_allowed_by_storage_key(), diff --git a/crates/subspace-malicious-operator/src/chain_spec.rs b/crates/subspace-malicious-operator/src/chain_spec.rs index cc1df3e2e5..79a634fbd0 100644 --- a/crates/subspace-malicious-operator/src/chain_spec.rs +++ b/crates/subspace-malicious-operator/src/chain_spec.rs @@ -7,7 +7,10 @@ use sc_service::{ChainSpec, ChainType}; use sp_core::crypto::AccountId32; use sp_core::{sr25519, Pair, Public}; use sp_domains::storage::RawGenesis; -use sp_domains::{OperatorAllowList, OperatorPublicKey, PermissionedActionAllowedBy, RuntimeType}; +use sp_domains::{ + DomainRuntimeConfig, OperatorAllowList, OperatorPublicKey, PermissionedActionAllowedBy, + RuntimeType, +}; use sp_runtime::traits::{Convert, IdentifyAccount}; use sp_runtime::{BuildStorage, MultiSigner, Percent}; use std::marker::PhantomData; @@ -140,6 +143,7 @@ struct GenesisDomainParams { raw_genesis_storage: Vec, initial_balances: Vec<(MultiAccountId, Balance)>, permissioned_action_allowed_by: PermissionedActionAllowedBy, + domain_runtime_config: DomainRuntimeConfig, } pub fn dev_config() -> Result { @@ -191,6 +195,7 @@ pub fn dev_config() -> Result { raw_genesis_storage: raw_genesis_storage.clone(), initial_balances: endowed_accounts(), permissioned_action_allowed_by: PermissionedActionAllowedBy::Anyone, + domain_runtime_config: DomainRuntimeConfig::default_evm(), }, )) .map_err(|error| format!("Failed to serialize genesis config: {error}"))?, @@ -260,6 +265,7 @@ fn subspace_genesis_config( nomination_tax: Percent::from_percent(5), minimum_nominator_stake: 100 * SSC, initial_balances: genesis_domain_params.initial_balances, + domain_runtime_config: genesis_domain_params.domain_runtime_config, }], }, } diff --git a/crates/subspace-node/src/chain_spec.rs b/crates/subspace-node/src/chain_spec.rs index 8cf80065b8..1fa521682a 100644 --- a/crates/subspace-node/src/chain_spec.rs +++ b/crates/subspace-node/src/chain_spec.rs @@ -425,6 +425,7 @@ fn subspace_genesis_config( nomination_tax: Percent::from_percent(5), minimum_nominator_stake: 100 * SSC, initial_balances: genesis_domain.initial_balances, + domain_runtime_config: genesis_domain.domain_runtime_config, } }) .collect() diff --git a/crates/subspace-node/src/domain/auto_id_chain_spec.rs b/crates/subspace-node/src/domain/auto_id_chain_spec.rs index 38c64422fe..5cd7b27f8e 100644 --- a/crates/subspace-node/src/domain/auto_id_chain_spec.rs +++ b/crates/subspace-node/src/domain/auto_id_chain_spec.rs @@ -28,7 +28,7 @@ use sc_chain_spec::GenericChainSpec; use sc_service::ChainType; use sp_core::crypto::{AccountId32, UncheckedFrom}; use sp_domains::storage::RawGenesis; -use sp_domains::{OperatorAllowList, OperatorPublicKey, RuntimeType}; +use sp_domains::{DomainRuntimeConfig, OperatorAllowList, OperatorPublicKey, RuntimeType}; use sp_runtime::traits::Convert; use sp_runtime::BuildStorage; use std::collections::BTreeSet; @@ -195,5 +195,6 @@ pub fn get_genesis_domain( initial_balances: get_testnet_endowed_accounts_by_spec_id(spec_id), operator_allow_list, operator_signing_key, + domain_runtime_config: DomainRuntimeConfig::default_auto_id(), }) } diff --git a/crates/subspace-node/src/domain/cli.rs b/crates/subspace-node/src/domain/cli.rs index e057764d9b..0d7fb1143b 100644 --- a/crates/subspace-node/src/domain/cli.rs +++ b/crates/subspace-node/src/domain/cli.rs @@ -32,7 +32,9 @@ use sc_service::{BasePath, Configuration, DatabaseSource}; use sp_blockchain::HeaderBackend; use sp_domain_digests::AsPredigest; use sp_domains::storage::RawGenesis; -use sp_domains::{DomainId, OperatorAllowList, OperatorId, OperatorPublicKey, RuntimeType}; +use sp_domains::{ + DomainId, DomainRuntimeConfig, OperatorAllowList, OperatorId, OperatorPublicKey, RuntimeType, +}; use sp_runtime::generic::BlockId; use sp_runtime::traits::Header; use sp_runtime::DigestItem; @@ -478,6 +480,7 @@ pub struct GenesisDomain { pub initial_balances: Vec<(MultiAccountId, Balance)>, pub operator_allow_list: OperatorAllowList, pub operator_signing_key: OperatorPublicKey, + pub domain_runtime_config: DomainRuntimeConfig, } /// Genesis Operator list params diff --git a/crates/subspace-node/src/domain/evm_chain_spec.rs b/crates/subspace-node/src/domain/evm_chain_spec.rs index 7759be4370..3b402ee3d9 100644 --- a/crates/subspace-node/src/domain/evm_chain_spec.rs +++ b/crates/subspace-node/src/domain/evm_chain_spec.rs @@ -29,7 +29,7 @@ use sc_chain_spec::GenericChainSpec; use sc_service::ChainType; use sp_core::crypto::UncheckedFrom; use sp_domains::storage::RawGenesis; -use sp_domains::{OperatorAllowList, OperatorPublicKey, RuntimeType}; +use sp_domains::{EvmDomainRuntimeConfig, OperatorAllowList, OperatorPublicKey, RuntimeType}; use sp_runtime::traits::Convert; use sp_runtime::BuildStorage; use std::collections::BTreeSet; @@ -223,5 +223,9 @@ pub fn get_genesis_domain( initial_balances: get_testnet_endowed_accounts_by_spec_id(spec_id), operator_allow_list, operator_signing_key, + domain_runtime_config: EvmDomainRuntimeConfig { + initial_contract_creation_allow_list: sp_domains::PermissionedActionAllowedBy::Anyone, + } + .into(), }) } diff --git a/domains/pallets/evm_nonce_tracker/src/lib.rs b/domains/pallets/evm_nonce_tracker/src/lib.rs index 8209dc060b..4d2784a887 100644 --- a/domains/pallets/evm_nonce_tracker/src/lib.rs +++ b/domains/pallets/evm_nonce_tracker/src/lib.rs @@ -43,6 +43,12 @@ mod pallet { StorageMap<_, Identity, T::AccountId, U256, OptionQuery>; /// Storage to hold EVM contract creation allow list accounts. + /// Unlike domain instantiation, no storage value means "anyone can create contracts". + /// + /// At genesis, this is set via DomainConfigParams, DomainRuntimeInfo::Evm, and + /// RawGenesis::set_evm_contract_creation_allowed_by(). + // When this type name is changed, evm_contract_creation_allowed_by_storage_key() also needs to + // be updated. #[pallet::storage] pub(super) type ContractCreationAllowedBy = StorageValue<_, PermissionedActionAllowedBy, OptionQuery>; @@ -54,7 +60,7 @@ mod pallet { #[pallet::call] impl Pallet { - /// Update ContractCreationAllowedBy storage by Sudo. + /// Replace ContractCreationAllowedBy setting in storage, as a domain sudo call. #[pallet::call_index(0)] #[pallet::weight(::DbWeight::get().reads_writes(0, 1))] pub fn set_contract_creation_allowed_by( @@ -90,8 +96,9 @@ impl Pallet { /// Returns true if the account is allowed to create contracts. pub fn is_allowed_to_create_contracts(signer: &T::AccountId) -> bool { + // Unlike domain instantiation, no storage value means "anyone can create contracts". ContractCreationAllowedBy::::get() .map(|allowed_by| allowed_by.is_allowed(signer)) - .unwrap_or_default() + .unwrap_or(true) } } diff --git a/domains/primitives/runtime/Cargo.toml b/domains/primitives/runtime/Cargo.toml index 77348e0659..d026f0735b 100644 --- a/domains/primitives/runtime/Cargo.toml +++ b/domains/primitives/runtime/Cargo.toml @@ -12,7 +12,7 @@ description = "Common primitives of subspace domain runtime" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -fp-account = { version = "1.0.0-dev", default-features = false, git = "https://github.com/autonomys/frontier", rev = "f80f9e2bad338f3bf3854b256b3c4edea23e5968" } +fp-account = { version = "1.0.0-dev", default-features = false, features = ["serde"], git = "https://github.com/autonomys/frontier", rev = "f80f9e2bad338f3bf3854b256b3c4edea23e5968" } 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" } parity-scale-codec = { version = "3.6.12", default-features = false, features = ["derive"] } diff --git a/test/subspace-test-client/src/auto_id_domain_chain_spec.rs b/test/subspace-test-client/src/auto_id_domain_chain_spec.rs index 4f7a228aec..dd61ed4185 100644 --- a/test/subspace-test-client/src/auto_id_domain_chain_spec.rs +++ b/test/subspace-test-client/src/auto_id_domain_chain_spec.rs @@ -8,7 +8,9 @@ use sc_chain_spec::{ChainType, GenericChainSpec, NoExtension}; use sp_core::crypto::AccountId32; use sp_core::{sr25519, Pair, Public}; use sp_domains::storage::RawGenesis; -use sp_domains::{GenesisDomain, OperatorAllowList, OperatorPublicKey, RuntimeType}; +use sp_domains::{ + DomainRuntimeConfig, GenesisDomain, OperatorAllowList, OperatorPublicKey, RuntimeType, +}; use sp_runtime::traits::{Convert, IdentifyAccount}; use sp_runtime::{BuildStorage, MultiSigner, Percent}; use subspace_runtime_primitives::{AccountId, Balance, SSC}; @@ -94,5 +96,7 @@ pub fn get_genesis_domain( .cloned() .map(|k| (AccountIdConverter::convert(k), 2_000_000 * SSC)) .collect(), + + domain_runtime_config: DomainRuntimeConfig::default_auto_id(), }) } diff --git a/test/subspace-test-client/src/evm_domain_chain_spec.rs b/test/subspace-test-client/src/evm_domain_chain_spec.rs index eead3243f8..92b1346133 100644 --- a/test/subspace-test-client/src/evm_domain_chain_spec.rs +++ b/test/subspace-test-client/src/evm_domain_chain_spec.rs @@ -9,7 +9,9 @@ use evm_domain_test_runtime::{ use sc_chain_spec::{ChainType, GenericChainSpec, NoExtension}; use sp_core::{ecdsa, Pair, Public}; use sp_domains::storage::RawGenesis; -use sp_domains::{DomainId, GenesisDomain, OperatorAllowList, OperatorPublicKey, RuntimeType}; +use sp_domains::{ + DomainId, DomainRuntimeConfig, GenesisDomain, OperatorAllowList, OperatorPublicKey, RuntimeType, +}; use sp_runtime::traits::{Convert, IdentifyAccount, Verify}; use sp_runtime::{BuildStorage, Percent}; use subspace_runtime_primitives::{AccountId, Balance, SSC}; @@ -132,5 +134,7 @@ pub fn get_genesis_domain( .cloned() .map(|k| (AccountId20Converter::convert(k), 2_000_000 * SSC)) .collect(), + + domain_runtime_config: DomainRuntimeConfig::default_evm(), }) } From 1ac35d60e3b1345571f48712d1b6f71ea6834090 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 17 Jan 2025 12:22:13 +1000 Subject: [PATCH 09/13] Limit recursion when checking for balance transfers or contract creation --- .../subspace-runtime/src/signed_extensions.rs | 25 ++++++++++++++----- domains/runtime/evm/src/lib.rs | 25 ++++++++++++++----- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/crates/subspace-runtime/src/signed_extensions.rs b/crates/subspace-runtime/src/signed_extensions.rs index ab9079b183..cb741e10ee 100644 --- a/crates/subspace-runtime/src/signed_extensions.rs +++ b/crates/subspace-runtime/src/signed_extensions.rs @@ -7,6 +7,8 @@ use sp_runtime::transaction_validity::{ }; use sp_std::prelude::*; +const MAX_BALANCE_RECURSION_DEPTH: u16 = 5; + /// Disable balance transfers, if configured in the runtime. #[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, Default, TypeInfo)] pub struct DisablePallets; @@ -30,7 +32,9 @@ impl SignedExtension for DisablePallets { _len: usize, ) -> TransactionValidity { // Disable normal balance transfers. - if !RuntimeConfigs::enable_balance_transfers() && contains_balance_transfer(call) { + if !RuntimeConfigs::enable_balance_transfers() + && contains_balance_transfer(call, MAX_BALANCE_RECURSION_DEPTH) + { InvalidTransaction::Call.into() } else { Ok(ValidTransaction::default()) @@ -61,7 +65,14 @@ impl SignedExtension for DisablePallets { } } -fn contains_balance_transfer(call: &RuntimeCall) -> bool { +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( pallet_balances::Call::transfer_allow_death { .. } @@ -71,12 +82,14 @@ fn contains_balance_transfer(call: &RuntimeCall) -> bool { 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(contains_balance_transfer) - } + | 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), + | pallet_utility::Call::with_weight { call, .. } => { + contains_balance_transfer(call, recursion_depth_left) + } pallet_utility::Call::__Ignore(..) => false, }, _ => false, diff --git a/domains/runtime/evm/src/lib.rs b/domains/runtime/evm/src/lib.rs index 1eeffae3a3..0adaf95026 100644 --- a/domains/runtime/evm/src/lib.rs +++ b/domains/runtime/evm/src/lib.rs @@ -148,12 +148,15 @@ 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 { - if is_create_contract(call) - && !pallet_evm_nonce_tracker::Pallet::::is_allowed_to_create_contracts(signer) + // Only enter recursive code if this account can't create contracts + if !pallet_evm_nonce_tracker::Pallet::::is_allowed_to_create_contracts(signer) + && is_create_contract(call, MAX_CONTRACT_RECURSION_DEPTH) { return false; } @@ -163,7 +166,14 @@ pub fn is_create_contract_allowed(call: &RuntimeCall, signer: &AccountId) -> boo } /// Returns true if the call is a contract creation call. -pub fn is_create_contract(call: &RuntimeCall) -> bool { +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, @@ -179,14 +189,17 @@ pub fn is_create_contract(call: &RuntimeCall) -> bool { transaction: EthereumTransaction::EIP1559(transaction), .. }) => transaction.action == TransactionAction::Create, - // TODO: does this need a recursion limit? 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(is_create_contract), + | 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), + | pallet_utility::Call::with_weight { call, .. } => { + is_create_contract(call, recursion_depth_left) + } pallet_utility::Call::__Ignore(..) => false, }, _ => false, From 01110265e92305dd614af0db6a77460de0a76fc5 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 17 Jan 2025 12:27:47 +1000 Subject: [PATCH 10/13] Fix balance overflow and invalid messenger channel error code clash --- crates/sp-domains-fraud-proof/src/lib.rs | 3 +++ domains/pallets/messenger/src/lib.rs | 6 +++++- domains/primitives/runtime/src/lib.rs | 7 +++++++ domains/runtime/evm/src/lib.rs | 21 +++++++++++++-------- 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/crates/sp-domains-fraud-proof/src/lib.rs b/crates/sp-domains-fraud-proof/src/lib.rs index e62b88e220..769c602f56 100644 --- a/crates/sp-domains-fraud-proof/src/lib.rs +++ b/crates/sp-domains-fraud-proof/src/lib.rs @@ -63,6 +63,9 @@ use subspace_core_primitives::U256; use subspace_runtime_primitives::{Balance, Moment}; /// Custom invalid validity code for the extrinsics in pallet-domains. +// When updating these error codes, check for clashes between: +// +// #[repr(u8)] pub enum InvalidTransactionCode { TransactionProof = 101, diff --git a/domains/pallets/messenger/src/lib.rs b/domains/pallets/messenger/src/lib.rs index 4fa397b3c1..c7fac2515c 100644 --- a/domains/pallets/messenger/src/lib.rs +++ b/domains/pallets/messenger/src/lib.rs @@ -47,9 +47,13 @@ use sp_runtime::traits::{Extrinsic, Hash}; use sp_runtime::DispatchError; pub(crate) mod verification_errors { - pub(crate) const INVALID_CHANNEL: u8 = 200; + // When updating these error codes, check for clashes between: + // + // pub(crate) const INVALID_NONCE: u8 = 201; pub(crate) const NONCE_OVERFLOW: u8 = 202; + // This error code was previously 200, but that clashed with ERR_BALANCE_OVERFLOW. + pub(crate) const INVALID_CHANNEL: u8 = 203; } #[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo, Copy)] diff --git a/domains/primitives/runtime/src/lib.rs b/domains/primitives/runtime/src/lib.rs index 8dd03603db..69215fa9d9 100644 --- a/domains/primitives/runtime/src/lib.rs +++ b/domains/primitives/runtime/src/lib.rs @@ -89,10 +89,17 @@ pub fn maximum_domain_block_weight() -> Weight { weight.set_proof_size(consensus_maximum_normal_block_length) } +// When updating these error codes, check for clashes between: +// +// + /// Custom error when nonce overflow occurs. pub const ERR_NONCE_OVERFLOW: u8 = 100; /// Custom error when balance overflow occurs. pub const ERR_BALANCE_OVERFLOW: u8 = 200; +/// Custom error when a user tries to create a contract, but their account is not on the allow +/// list. +pub const ERR_CONTRACT_CREATION_NOT_ALLOWED: u8 = 210; /// Maximum block length for all dispatches. /// This is set to 3.75 MiB since consensus chain supports on 3.75 MiB for normal diff --git a/domains/runtime/evm/src/lib.rs b/domains/runtime/evm/src/lib.rs index 0adaf95026..23f4889750 100644 --- a/domains/runtime/evm/src/lib.rs +++ b/domains/runtime/evm/src/lib.rs @@ -24,7 +24,7 @@ pub use domain_runtime_primitives::{ }; use domain_runtime_primitives::{ CheckExtrinsicsValidityError, DecodeExtrinsicError, HoldIdentifier, ERR_BALANCE_OVERFLOW, - ERR_NONCE_OVERFLOW, SLOT_DURATION, + ERR_CONTRACT_CREATION_NOT_ALLOWED, ERR_NONCE_OVERFLOW, SLOT_DURATION, }; use fp_self_contained::{CheckedSignature, SelfContainedCall}; use frame_support::dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo}; @@ -210,6 +210,9 @@ pub fn is_create_contract(call: &RuntimeCall, mut recursion_depth_left: u16) -> #[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, Default, TypeInfo)] pub struct CheckContractCreation; +// Unsigned calls can't create contracts. Only pallet-evm and pallet-ethereum can create contracts. +// For pallet-evm all contracts are signed extrinsics, for pallet-ethereum there is only one +// extrinsic that is self-contained. impl SignedExtension for CheckContractCreation { const IDENTIFIER: &'static str = "CheckContractCreation"; type AccountId = ::AccountId; @@ -230,7 +233,7 @@ impl SignedExtension for CheckContractCreation { ) -> TransactionValidity { // Reject contract creation unless the account is in the allow list. if !is_create_contract_allowed(call, who) { - InvalidTransaction::Call.into() + InvalidTransaction::Custom(ERR_CONTRACT_CREATION_NOT_ALLOWED).into() } else { Ok(ValidTransaction::default()) } @@ -246,8 +249,6 @@ impl SignedExtension for CheckContractCreation { self.validate(who, call, info, len)?; Ok(()) } - - // TODO: can unsigned calls create contracts? } impl fp_self_contained::SelfContainedCall for RuntimeCall { @@ -274,8 +275,10 @@ impl fp_self_contained::SelfContainedCall for RuntimeCall { len: usize, ) -> Option { if !is_create_contract_allowed(self, &(*info).into()) { - // TODO: should this be Custom() instead? - return Some(Err(InvalidTransaction::Call.into())); + return Some(Err(InvalidTransaction::Custom( + ERR_CONTRACT_CREATION_NOT_ALLOWED, + ) + .into())); } match self { @@ -303,8 +306,10 @@ impl fp_self_contained::SelfContainedCall for RuntimeCall { len: usize, ) -> Option> { if !is_create_contract_allowed(self, &(*info).into()) { - // TODO: should this be Custom() instead? - return Some(Err(InvalidTransaction::Call.into())); + return Some(Err(InvalidTransaction::Custom( + ERR_CONTRACT_CREATION_NOT_ALLOWED, + ) + .into())); } match self { From c3b4bd3a4682817f5588312546f948a78ec392bc Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 20 Jan 2025 14:47:25 +1000 Subject: [PATCH 11/13] Reject unsigned contract calls unless anyone is allowed --- domains/pallets/evm_nonce_tracker/src/lib.rs | 8 +++++ domains/runtime/evm/src/lib.rs | 37 ++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/domains/pallets/evm_nonce_tracker/src/lib.rs b/domains/pallets/evm_nonce_tracker/src/lib.rs index 4d2784a887..93e1f9bebc 100644 --- a/domains/pallets/evm_nonce_tracker/src/lib.rs +++ b/domains/pallets/evm_nonce_tracker/src/lib.rs @@ -101,4 +101,12 @@ impl Pallet { .map(|allowed_by| allowed_by.is_allowed(signer)) .unwrap_or(true) } + + /// Returns true if the account is allowed to create contracts. + pub fn is_allowed_to_create_unsigned_contracts() -> bool { + // Unlike domain instantiation, no storage value means "anyone can create contracts". + ContractCreationAllowedBy::::get() + .map(|allowed_by| allowed_by.is_anyone_allowed()) + .unwrap_or(true) + } } diff --git a/domains/runtime/evm/src/lib.rs b/domains/runtime/evm/src/lib.rs index 23f4889750..f675d9398c 100644 --- a/domains/runtime/evm/src/lib.rs +++ b/domains/runtime/evm/src/lib.rs @@ -165,6 +165,21 @@ pub fn is_create_contract_allowed(call: &RuntimeCall, signer: &AccountId) -> boo true } +/// If anyone is allowed to create contracts, allows contracts. Otherwise, rejects contracts. +/// 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 + if !pallet_evm_nonce_tracker::Pallet::::is_allowed_to_create_unsigned_contracts() + && is_create_contract(call, MAX_CONTRACT_RECURSION_DEPTH) + { + return false; + } + + // If it's not a contract call, or anyone is allowed to create contracts, return true. + true +} + /// 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 { @@ -249,6 +264,28 @@ impl SignedExtension for CheckContractCreation { self.validate(who, call, info, len)?; Ok(()) } + + fn validate_unsigned( + call: &Self::Call, + _info: &DispatchInfoOf, + _len: usize, + ) -> TransactionValidity { + // Reject unsigned contract creation unless anyone is allowed to create them. + if !is_create_unsigned_contract_allowed(call) { + InvalidTransaction::Custom(ERR_CONTRACT_CREATION_NOT_ALLOWED).into() + } else { + Ok(ValidTransaction::default()) + } + } + + fn pre_dispatch_unsigned( + call: &Self::Call, + info: &DispatchInfoOf, + len: usize, + ) -> Result<(), TransactionValidityError> { + Self::validate_unsigned(call, info, len)?; + Ok(()) + } } impl fp_self_contained::SelfContainedCall for RuntimeCall { From 27ca2d039cf97f4089afdb06eb717dd25d011e57 Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 20 Jan 2025 14:50:45 +1000 Subject: [PATCH 12/13] Rename pallet_evm_nonce_tracker to pallet-evm-tracker --- Cargo.lock | 30 +++++++++---------- crates/sp-domains/src/lib.rs | 6 ++-- .../Cargo.toml | 2 +- .../src/check_nonce.rs | 0 .../src/lib.rs | 0 domains/runtime/evm/Cargo.toml | 4 +-- domains/runtime/evm/src/lib.rs | 12 ++++---- domains/test/runtime/evm/Cargo.toml | 4 +-- domains/test/runtime/evm/src/lib.rs | 8 ++--- 9 files changed, 33 insertions(+), 33 deletions(-) rename domains/pallets/{evm_nonce_tracker => evm-tracker}/Cargo.toml (97%) rename domains/pallets/{evm_nonce_tracker => evm-tracker}/src/check_nonce.rs (100%) rename domains/pallets/{evm_nonce_tracker => evm-tracker}/src/lib.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 9f7c9770c4..e3322e8479 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3255,10 +3255,10 @@ dependencies = [ "pallet-ethereum", "pallet-evm", "pallet-evm-chain-id", - "pallet-evm-nonce-tracker", "pallet-evm-precompile-modexp", "pallet-evm-precompile-sha3fips", "pallet-evm-precompile-simple", + "pallet-evm-tracker", "pallet-messenger", "pallet-timestamp", "pallet-transaction-payment", @@ -3312,10 +3312,10 @@ dependencies = [ "pallet-ethereum", "pallet-evm", "pallet-evm-chain-id", - "pallet-evm-nonce-tracker", "pallet-evm-precompile-modexp", "pallet-evm-precompile-sha3fips", "pallet-evm-precompile-simple", + "pallet-evm-tracker", "pallet-messenger", "pallet-timestamp", "pallet-transaction-payment", @@ -7768,19 +7768,6 @@ dependencies = [ "scale-info", ] -[[package]] -name = "pallet-evm-nonce-tracker" -version = "0.1.0" -dependencies = [ - "frame-support", - "frame-system", - "parity-scale-codec", - "scale-info", - "sp-core", - "sp-domains", - "sp-runtime", -] - [[package]] name = "pallet-evm-precompile-modexp" version = "2.0.0-dev" @@ -7809,6 +7796,19 @@ dependencies = [ "sp-io", ] +[[package]] +name = "pallet-evm-tracker" +version = "0.1.0" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-domains", + "sp-runtime", +] + [[package]] name = "pallet-messenger" version = "0.1.0" diff --git a/crates/sp-domains/src/lib.rs b/crates/sp-domains/src/lib.rs index 2a58743fb3..a132f68c5e 100644 --- a/crates/sp-domains/src/lib.rs +++ b/crates/sp-domains/src/lib.rs @@ -1074,10 +1074,10 @@ pub(crate) fn evm_chain_id_storage_key() -> StorageKey { pub(crate) fn evm_contract_creation_allowed_by_storage_key() -> StorageKey { StorageKey( storage_prefix( - // This is the name used for `pallet_evm_nonce_tracker` in the `construct_runtime` macro - // i.e. `EVMNoncetracker: pallet_evm_nonce_tracker = 84,` + // This is the name used for `pallet_evm_tracker` in the `construct_runtime` macro + // i.e. `EVMNoncetracker: pallet_evm_tracker = 84,` "EVMNoncetracker".as_bytes(), - // This is the storage item name used inside `pallet_evm_nonce_tracker` + // This is the storage item name used inside `pallet_evm_tracker` "ContractCreationAllowedBy".as_bytes(), ) .to_vec(), diff --git a/domains/pallets/evm_nonce_tracker/Cargo.toml b/domains/pallets/evm-tracker/Cargo.toml similarity index 97% rename from domains/pallets/evm_nonce_tracker/Cargo.toml rename to domains/pallets/evm-tracker/Cargo.toml index 2ba104e25f..98aa4bd251 100644 --- a/domains/pallets/evm_nonce_tracker/Cargo.toml +++ b/domains/pallets/evm-tracker/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "pallet-evm-nonce-tracker" +name = "pallet-evm-tracker" version = "0.1.0" authors = ["Subspace Labs "] edition = "2021" diff --git a/domains/pallets/evm_nonce_tracker/src/check_nonce.rs b/domains/pallets/evm-tracker/src/check_nonce.rs similarity index 100% rename from domains/pallets/evm_nonce_tracker/src/check_nonce.rs rename to domains/pallets/evm-tracker/src/check_nonce.rs diff --git a/domains/pallets/evm_nonce_tracker/src/lib.rs b/domains/pallets/evm-tracker/src/lib.rs similarity index 100% rename from domains/pallets/evm_nonce_tracker/src/lib.rs rename to domains/pallets/evm-tracker/src/lib.rs diff --git a/domains/runtime/evm/Cargo.toml b/domains/runtime/evm/Cargo.toml index 6fecb2f63f..5040adb3c0 100644 --- a/domains/runtime/evm/Cargo.toml +++ b/domains/runtime/evm/Cargo.toml @@ -37,7 +37,7 @@ pallet-domain-sudo = { version = "0.1.0", path = "../../pallets/domain-sudo", de pallet-ethereum = { default-features = false, git = "https://github.com/autonomys/frontier", rev = "f80f9e2bad338f3bf3854b256b3c4edea23e5968" } pallet-evm = { version = "6.0.0-dev", default-features = false, git = "https://github.com/autonomys/frontier", rev = "f80f9e2bad338f3bf3854b256b3c4edea23e5968" } pallet-evm-chain-id = { version = "1.0.0-dev", default-features = false, git = "https://github.com/autonomys/frontier", rev = "f80f9e2bad338f3bf3854b256b3c4edea23e5968" } -pallet-evm-nonce-tracker = { version = "0.1.0", path = "../../pallets/evm_nonce_tracker", default-features = false } +pallet-evm-tracker = { version = "0.1.0", path = "../../pallets/evm-tracker", default-features = false } pallet-evm-precompile-modexp = { version = "2.0.0-dev", default-features = false, git = "https://github.com/autonomys/frontier", rev = "f80f9e2bad338f3bf3854b256b3c4edea23e5968" } pallet-evm-precompile-sha3fips = { version = "2.0.0-dev", default-features = false, git = "https://github.com/autonomys/frontier", rev = "f80f9e2bad338f3bf3854b256b3c4edea23e5968" } pallet-evm-precompile-simple = { version = "2.0.0-dev", default-features = false, git = "https://github.com/autonomys/frontier", rev = "f80f9e2bad338f3bf3854b256b3c4edea23e5968" } @@ -99,7 +99,7 @@ std = [ "pallet-ethereum/std", "pallet-evm/std", "pallet-evm-chain-id/std", - "pallet-evm-nonce-tracker/std", + "pallet-evm-tracker/std", "pallet-evm-precompile-modexp/std", "pallet-evm-precompile-sha3fips/std", "pallet-evm-precompile-simple/std", diff --git a/domains/runtime/evm/src/lib.rs b/domains/runtime/evm/src/lib.rs index f675d9398c..19f735b292 100644 --- a/domains/runtime/evm/src/lib.rs +++ b/domains/runtime/evm/src/lib.rs @@ -126,7 +126,7 @@ type CustomSignedExtra = ( frame_system::CheckTxVersion, frame_system::CheckGenesis, frame_system::CheckMortality, - pallet_evm_nonce_tracker::CheckNonce, + pallet_evm_tracker::CheckNonce, domain_check_weight::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, CheckContractCreation, @@ -155,7 +155,7 @@ const MAX_CONTRACT_RECURSION_DEPTH: u16 = 5; /// 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 - if !pallet_evm_nonce_tracker::Pallet::::is_allowed_to_create_contracts(signer) + if !pallet_evm_tracker::Pallet::::is_allowed_to_create_contracts(signer) && is_create_contract(call, MAX_CONTRACT_RECURSION_DEPTH) { return false; @@ -170,7 +170,7 @@ pub fn is_create_contract_allowed(call: &RuntimeCall, signer: &AccountId) -> boo /// 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 - if !pallet_evm_nonce_tracker::Pallet::::is_allowed_to_create_unsigned_contracts() + if !pallet_evm_tracker::Pallet::::is_allowed_to_create_unsigned_contracts() && is_create_contract(call, MAX_CONTRACT_RECURSION_DEPTH) { return false; @@ -835,7 +835,7 @@ impl pallet_evm::Config for Runtime { type WeightInfo = pallet_evm::weights::SubstrateWeight; } -impl pallet_evm_nonce_tracker::Config for Runtime {} +impl pallet_evm_tracker::Config for Runtime {} parameter_types! { pub const PostOnlyBlockHash: PostLogContent = PostLogContent::OnlyBlockHash; @@ -933,7 +933,7 @@ construct_runtime!( EVM: pallet_evm = 81, EVMChainId: pallet_evm_chain_id = 82, BaseFee: pallet_base_fee = 83, - EVMNoncetracker: pallet_evm_nonce_tracker = 84, + EVMNoncetracker: pallet_evm_tracker = 84, // domain instance stuff SelfDomainId: pallet_domain_id = 90, @@ -1156,7 +1156,7 @@ fn check_transaction_and_do_pre_dispatch_inner( extra.2, extra.3, extra.4, - pallet_evm_nonce_tracker::CheckNonce::from(extra.5 .0), + pallet_evm_tracker::CheckNonce::from(extra.5 .0), extra.6, extra.7, extra.8, diff --git a/domains/test/runtime/evm/Cargo.toml b/domains/test/runtime/evm/Cargo.toml index 2f26410460..ce8a6e8fb9 100644 --- a/domains/test/runtime/evm/Cargo.toml +++ b/domains/test/runtime/evm/Cargo.toml @@ -36,7 +36,7 @@ pallet-domain-sudo = { version = "0.1.0", path = "../../../pallets/domain-sudo", pallet-ethereum = { default-features = false, git = "https://github.com/autonomys/frontier", rev = "f80f9e2bad338f3bf3854b256b3c4edea23e5968" } pallet-evm = { version = "6.0.0-dev", default-features = false, git = "https://github.com/autonomys/frontier", rev = "f80f9e2bad338f3bf3854b256b3c4edea23e5968" } pallet-evm-chain-id = { version = "1.0.0-dev", default-features = false, git = "https://github.com/autonomys/frontier", rev = "f80f9e2bad338f3bf3854b256b3c4edea23e5968" } -pallet-evm-nonce-tracker = { version = "0.1.0", path = "../../../pallets/evm_nonce_tracker", default-features = false } +pallet-evm-tracker = { version = "0.1.0", path = "../../../pallets/evm-tracker", default-features = false } pallet-evm-precompile-modexp = { version = "2.0.0-dev", default-features = false, git = "https://github.com/autonomys/frontier", rev = "f80f9e2bad338f3bf3854b256b3c4edea23e5968" } pallet-evm-precompile-sha3fips = { version = "2.0.0-dev", default-features = false, git = "https://github.com/autonomys/frontier", rev = "f80f9e2bad338f3bf3854b256b3c4edea23e5968" } pallet-evm-precompile-simple = { version = "2.0.0-dev", default-features = false, git = "https://github.com/autonomys/frontier", rev = "f80f9e2bad338f3bf3854b256b3c4edea23e5968" } @@ -93,7 +93,7 @@ std = [ "pallet-ethereum/std", "pallet-evm/std", "pallet-evm-chain-id/std", - "pallet-evm-nonce-tracker/std", + "pallet-evm-tracker/std", "pallet-evm-precompile-modexp/std", "pallet-evm-precompile-sha3fips/std", "pallet-evm-precompile-simple/std", diff --git a/domains/test/runtime/evm/src/lib.rs b/domains/test/runtime/evm/src/lib.rs index a46ad36a43..bc93dddb52 100644 --- a/domains/test/runtime/evm/src/lib.rs +++ b/domains/test/runtime/evm/src/lib.rs @@ -130,7 +130,7 @@ type CustomSignedExtra = ( frame_system::CheckTxVersion, frame_system::CheckGenesis, frame_system::CheckMortality, - pallet_evm_nonce_tracker::CheckNonce, + pallet_evm_tracker::CheckNonce, domain_check_weight::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, ); @@ -645,7 +645,7 @@ impl pallet_evm::OnChargeEVMTransaction for EVMCurrencyAdapter { } } -impl pallet_evm_nonce_tracker::Config for Runtime {} +impl pallet_evm_tracker::Config for Runtime {} impl pallet_evm::Config for Runtime { type FeeCalculator = BaseFee; @@ -756,7 +756,7 @@ construct_runtime!( EVM: pallet_evm = 81, EVMChainId: pallet_evm_chain_id = 82, BaseFee: pallet_base_fee = 83, - EVMNoncetracker: pallet_evm_nonce_tracker = 84, + EVMNoncetracker: pallet_evm_tracker = 84, // domain instance stuff SelfDomainId: pallet_domain_id = 90, @@ -969,7 +969,7 @@ fn check_transaction_and_do_pre_dispatch_inner( extra.2, extra.3, extra.4, - pallet_evm_nonce_tracker::CheckNonce::from(extra.5 .0), + pallet_evm_tracker::CheckNonce::from(extra.5 .0), extra.6, extra.7, ); From 7497b5c1daaef008ff80b042d8c76d9c756beb74 Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 20 Jan 2025 16:10:25 +1000 Subject: [PATCH 13/13] Add storage migration for new domain registry fields --- crates/pallet-domains/src/lib.rs | 3 +- .../pallet-domains/src/migration_v2_to_v3.rs | 299 ++++++++++++++++++ crates/subspace-runtime/src/lib.rs | 4 +- 3 files changed, 304 insertions(+), 2 deletions(-) create mode 100644 crates/pallet-domains/src/migration_v2_to_v3.rs diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index fbf91ba2ac..ff00b60e80 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -27,6 +27,7 @@ mod tests; pub mod block_tree; mod bundle_storage_fund; pub mod domain_registry; +pub mod migration_v2_to_v3; pub mod runtime_registry; mod staking; mod staking_epoch; @@ -161,7 +162,7 @@ pub type BlockTreeNodeFor = crate::block_tree::BlockTreeNode< >; /// The current storage version. -const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); +const STORAGE_VERSION: StorageVersion = StorageVersion::new(3); /// The number of bundle of a particular domain to be included in the block is probabilistic /// and based on the consensus chain slot probability and domain bundle slot probability, usually diff --git a/crates/pallet-domains/src/migration_v2_to_v3.rs b/crates/pallet-domains/src/migration_v2_to_v3.rs new file mode 100644 index 0000000000..ee32a51404 --- /dev/null +++ b/crates/pallet-domains/src/migration_v2_to_v3.rs @@ -0,0 +1,299 @@ +//! Migration module for pallet-domains storage version v2 to v3. +//! +//! TODO: remove this module after it has been deployed to Taurus. + +use crate::{Config, Pallet}; +use frame_support::migrations::VersionedMigration; +use frame_support::traits::UncheckedOnRuntimeUpgrade; +use frame_support::weights::Weight; + +pub type VersionCheckedMigrateDomainsV2ToV3 = VersionedMigration< + 2, + 3, + VersionUncheckedMigrateV2ToV3, + Pallet, + ::DbWeight, +>; + +pub struct VersionUncheckedMigrateV2ToV3(sp_std::marker::PhantomData); +impl UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateV2ToV3 { + fn on_runtime_upgrade() -> Weight { + domain_registry_structure_migration::migrate_domain_registry_structure::() + } +} + +mod domain_registry_structure_migration { + use crate::domain_registry::{DomainConfig as DomainConfigV3, DomainObject as DomainObjectV3}; + use crate::pallet::DomainRegistry as DomainRegistryV3; + use crate::runtime_registry::DomainRuntimeInfo as DomainRuntimeInfoV3; + use crate::{BalanceOf, BlockNumberFor, Config, Pallet, ReceiptHashFor}; + use codec::{Decode, Encode}; + use domain_runtime_primitives::{EVMChainId, MultiAccountId}; + use frame_support::pallet_prelude::{OptionQuery, TypeInfo, Weight}; + use frame_support::{storage_alias, Identity}; + use scale_info::prelude::string::String; + use sp_core::Get; + use sp_domains::{ + AutoIdDomainRuntimeConfig, + DomainId, + // changed in V3, but we use Into to convert to it + // DomainRuntimeConfig as DomainRuntimeConfigV3, + EvmDomainRuntimeConfig, + OperatorAllowList, + RuntimeId, + }; + use sp_runtime::Vec; + + #[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)] + pub struct DomainConfigV2 { + /// A user defined name for this domain, should be a human-readable UTF-8 encoded string. + pub domain_name: String, + /// A pointer to the `RuntimeRegistry` entry for this domain. + pub runtime_id: RuntimeId, + /// The max bundle size for this domain, may not exceed the system-wide `MaxDomainBlockSize` limit. + pub max_bundle_size: u32, + /// The max bundle weight for this domain, may not exceed the system-wide `MaxDomainBlockWeight` limit. + pub max_bundle_weight: Weight, + /// The probability of successful bundle in a slot (active slots coefficient). This defines the + /// expected bundle production rate, must be `> 0` and `≤ 1`. + pub bundle_slot_probability: (u64, u64), + /// Allowed operators to operate for this domain. + pub operator_allow_list: OperatorAllowList, + // Initial balances for Domain. + pub initial_balances: Vec<(MultiAccountId, Balance)>, + } + + /// Domain runtime specific information to create domain raw genesis. + #[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq, Copy)] + #[allow(clippy::upper_case_acronyms)] + pub enum DomainRuntimeInfoV2 { + EVM { chain_id: EVMChainId }, + AutoId, + } + + #[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)] + pub struct DomainObjectV2 { + /// The address of the domain creator, used to validate updating the domain config. + pub owner_account_id: AccountId, + /// The consensus chain block number when the domain first instantiated. + pub created_at: Number, + /// The hash of the genesis execution receipt for this domain. + pub genesis_receipt_hash: ReceiptHash, + /// The domain config. + pub domain_config: DomainConfigV2, + /// Domain runtime specific information. + pub domain_runtime_info: DomainRuntimeInfoV2, + /// The amount of balance hold on the domain owner account + pub domain_instantiation_deposit: Balance, + } + + #[storage_alias] + pub(super) type DomainRegistry = StorageMap< + Pallet, + Identity, + DomainId, + DomainObjectV2< + BlockNumberFor, + ReceiptHashFor, + ::AccountId, + BalanceOf, + >, + OptionQuery, + >; + + pub(super) fn migrate_domain_registry_structure() -> Weight { + let mut domain_count = 0; + + DomainRegistryV3::::translate_values::< + DomainObjectV2, ReceiptHashFor, T::AccountId, BalanceOf>, + _, + >(|domain_object_v2| { + domain_count += 1; + + let (domain_runtime_config, domain_runtime_info) = + match domain_object_v2.domain_runtime_info { + DomainRuntimeInfoV2::EVM { chain_id } => { + // Added in V3 + let domain_runtime_config = EvmDomainRuntimeConfig::default(); + ( + domain_runtime_config.clone().into(), + DomainRuntimeInfoV3::Evm { + chain_id, + domain_runtime_config, + }, + ) + } + DomainRuntimeInfoV2::AutoId => { + // Added in V3 + let domain_runtime_config = AutoIdDomainRuntimeConfig::default(); + ( + domain_runtime_config.clone().into(), + DomainRuntimeInfoV3::AutoId { + domain_runtime_config, + }, + ) + } + }; + + Some(DomainObjectV3 { + owner_account_id: domain_object_v2.owner_account_id, + created_at: domain_object_v2.created_at, + genesis_receipt_hash: domain_object_v2.genesis_receipt_hash, + domain_config: DomainConfigV3 { + domain_name: domain_object_v2.domain_config.domain_name, + runtime_id: domain_object_v2.domain_config.runtime_id, + max_bundle_size: domain_object_v2.domain_config.max_bundle_size, + max_bundle_weight: domain_object_v2.domain_config.max_bundle_weight, + bundle_slot_probability: domain_object_v2.domain_config.bundle_slot_probability, + operator_allow_list: domain_object_v2.domain_config.operator_allow_list, + initial_balances: domain_object_v2.domain_config.initial_balances, + // Added in V3 + domain_runtime_config, + }, + domain_runtime_info, + domain_instantiation_deposit: domain_object_v2.domain_instantiation_deposit, + }) + }); + + // 1 read and 1 write per domain + T::DbWeight::get().reads_writes(domain_count, domain_count) + } +} + +#[cfg(test)] +mod tests { + use super::domain_registry_structure_migration::{ + migrate_domain_registry_structure, DomainConfigV2, DomainObjectV2, DomainRegistry, + DomainRuntimeInfoV2, + }; + use crate::domain_registry::{DomainConfig as DomainConfigV3, DomainObject as DomainObjectV3}; + use crate::pallet::DomainRegistry as DomainRegistryV3; + use crate::runtime_registry::DomainRuntimeInfo as DomainRuntimeInfoV3; + use crate::tests::{new_test_ext, Test}; + use sp_domains::{ + AutoIdDomainRuntimeConfig, DomainId, DomainRuntimeConfig as DomainRuntimeConfigV3, + EvmDomainRuntimeConfig, OperatorAllowList, PermissionedActionAllowedBy, + }; + + #[test] + fn test_domain_registry_structure_migration_evm() { + let mut ext = new_test_ext(); + let domain_id: DomainId = 0.into(); + let chain_id = 8u32.into(); + let domain = DomainObjectV2 { + owner_account_id: 1u32.into(), + created_at: 2u32.into(), + genesis_receipt_hash: Default::default(), + domain_config: DomainConfigV2 { + domain_name: "test-evm-migrate".to_string(), + runtime_id: 3u32, + max_bundle_size: 4, + max_bundle_weight: 5.into(), + bundle_slot_probability: (6, 7), + operator_allow_list: OperatorAllowList::Anyone, + initial_balances: vec![], + }, + domain_runtime_info: DomainRuntimeInfoV2::EVM { chain_id }, + domain_instantiation_deposit: 9u32.into(), + }; + + ext.execute_with(|| DomainRegistry::::set(domain_id, Some(domain.clone()))); + + ext.commit_all().unwrap(); + + ext.execute_with(|| { + let weights = migrate_domain_registry_structure::(); + assert_eq!( + weights, + ::DbWeight::get().reads_writes(1, 1), + ); + assert_eq!( + DomainRegistryV3::::get(domain_id), + Some(DomainObjectV3 { + owner_account_id: domain.owner_account_id, + created_at: domain.created_at, + genesis_receipt_hash: domain.genesis_receipt_hash, + domain_config: DomainConfigV3 { + domain_name: domain.domain_config.domain_name, + runtime_id: domain.domain_config.runtime_id, + max_bundle_size: domain.domain_config.max_bundle_size, + max_bundle_weight: domain.domain_config.max_bundle_weight, + bundle_slot_probability: domain.domain_config.bundle_slot_probability, + operator_allow_list: domain.domain_config.operator_allow_list, + initial_balances: domain.domain_config.initial_balances, + domain_runtime_config: DomainRuntimeConfigV3::Evm(EvmDomainRuntimeConfig { + initial_contract_creation_allow_list: + PermissionedActionAllowedBy::Anyone + }), + }, + domain_runtime_info: DomainRuntimeInfoV3::Evm { + chain_id, + domain_runtime_config: EvmDomainRuntimeConfig { + initial_contract_creation_allow_list: + PermissionedActionAllowedBy::Anyone + }, + }, + domain_instantiation_deposit: domain.domain_instantiation_deposit, + }) + ); + }); + } + + #[test] + fn test_domain_registry_structure_migration_auto_id() { + let mut ext = new_test_ext(); + let domain_id: DomainId = 10.into(); + let domain = DomainObjectV2 { + owner_account_id: 11u32.into(), + created_at: 12u32.into(), + genesis_receipt_hash: Default::default(), + domain_config: DomainConfigV2 { + domain_name: "test-auto-id-migrate".to_string(), + runtime_id: 13u32, + max_bundle_size: 14, + max_bundle_weight: 15.into(), + bundle_slot_probability: (16, 17), + operator_allow_list: OperatorAllowList::Anyone, + initial_balances: vec![], + }, + domain_runtime_info: DomainRuntimeInfoV2::AutoId, + domain_instantiation_deposit: 19u32.into(), + }; + + ext.execute_with(|| DomainRegistry::::set(domain_id, Some(domain.clone()))); + + ext.commit_all().unwrap(); + + ext.execute_with(|| { + let weights = migrate_domain_registry_structure::(); + assert_eq!( + weights, + ::DbWeight::get().reads_writes(1, 1), + ); + assert_eq!( + DomainRegistryV3::::get(domain_id), + Some(DomainObjectV3 { + owner_account_id: domain.owner_account_id, + created_at: domain.created_at, + genesis_receipt_hash: domain.genesis_receipt_hash, + domain_config: DomainConfigV3 { + domain_name: domain.domain_config.domain_name, + runtime_id: domain.domain_config.runtime_id, + max_bundle_size: domain.domain_config.max_bundle_size, + max_bundle_weight: domain.domain_config.max_bundle_weight, + bundle_slot_probability: domain.domain_config.bundle_slot_probability, + operator_allow_list: domain.domain_config.operator_allow_list, + initial_balances: domain.domain_config.initial_balances, + domain_runtime_config: DomainRuntimeConfigV3::AutoId( + AutoIdDomainRuntimeConfig {} + ), + }, + domain_runtime_info: DomainRuntimeInfoV3::AutoId { + domain_runtime_config: AutoIdDomainRuntimeConfig {} + }, + domain_instantiation_deposit: domain.domain_instantiation_deposit, + }) + ); + }); + } +} diff --git a/crates/subspace-runtime/src/lib.rs b/crates/subspace-runtime/src/lib.rs index ed930abdf7..a766fc98b4 100644 --- a/crates/subspace-runtime/src/lib.rs +++ b/crates/subspace-runtime/src/lib.rs @@ -136,7 +136,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: Cow::Borrowed("subspace"), impl_name: Cow::Borrowed("subspace"), authoring_version: 0, - spec_version: 2, + spec_version: 12, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 0, @@ -978,6 +978,8 @@ pub type Executive = frame_executive::Executive< frame_system::ChainContext, Runtime, AllPalletsWithSystem, + // TODO: remove once migration has been deployed to Taurus + pallet_domains::migration_v2_to_v3::VersionCheckedMigrateDomainsV2ToV3, >; fn extract_segment_headers(ext: &UncheckedExtrinsic) -> Option> {