diff --git a/Cargo.lock b/Cargo.lock index 34e0aa7101..e1b66c6a5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10039,6 +10039,8 @@ dependencies = [ "frame-system", "log", "pallet-ah-migrator", + "pallet-indices", + "pallet-message-queue", "pallet-rc-migrator", "parachains-common", "polkadot-parachain-primitives", diff --git a/integration-tests/ahm/Cargo.toml b/integration-tests/ahm/Cargo.toml index de3ea2e5be..c69ea5c468 100644 --- a/integration-tests/ahm/Cargo.toml +++ b/integration-tests/ahm/Cargo.toml @@ -20,6 +20,8 @@ grandpa = { workspace = true } log = { workspace = true, default-features = true } pallet-rc-migrator = { workspace = true, default-features = true } pallet-ah-migrator = { workspace = true, default-features = true } +pallet-indices = { workspace = true, default-features = true } +pallet-message-queue = { workspace = true, default-features = true } parachains-common = { workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } diff --git a/pallets/rc-migrator/src/lib.rs b/pallets/rc-migrator/src/lib.rs index ce135e4d53..82fd042da0 100644 --- a/pallets/rc-migrator/src/lib.rs +++ b/pallets/rc-migrator/src/lib.rs @@ -46,7 +46,7 @@ use frame_support::{ traits::{ fungible::{Inspect, InspectFreeze, Mutate, MutateFreeze, MutateHold}, tokens::{Fortitude, Precision, Preservation}, - Defensive, LockableCurrency, ReservableCurrency, + Contains, Defensive, LockableCurrency, ReservableCurrency, }, weights::WeightMeter, }; @@ -96,7 +96,19 @@ impl From for Error { } } -#[derive(Encode, Decode, Clone, PartialEq, Eq, Default, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[derive( + Encode, + Decode, + Clone, + PartialEq, + Eq, + Default, + RuntimeDebug, + TypeInfo, + MaxEncodedLen, + PartialOrd, + Ord, +)] pub enum MigrationStage { /// The migration has not yet started but will start in the next block. #[default] @@ -196,6 +208,12 @@ pub mod pallet { type AhWeightInfo: AhWeightInfo; /// The existential deposit on the Asset Hub. type AhExistentialDeposit: Get<::Balance>; + /// Contains all calls that are allowed during the migration. + /// + /// The calls in here will be available again after the migration. + type RcIntraMigrationCalls: Contains<::RuntimeCall>; + /// Contains all calls that are allowed after the migration finished. + type RcPostMigrationCalls: Contains<::RuntimeCall>; } #[pallet::error] @@ -250,16 +268,6 @@ pub mod pallet { #[pallet::pallet] pub struct Pallet(_); - #[pallet::call] - impl Pallet { - /// TODO - #[pallet::call_index(0)] - #[pallet::weight({1})] - pub fn do_something(_origin: OriginFor) -> DispatchResultWithPostInfo { - Ok(().into()) - } - } - #[pallet::hooks] impl Hooks> for Pallet { fn on_initialize(_: BlockNumberFor) -> Weight { @@ -593,3 +601,25 @@ pub mod pallet { } } } + +impl Contains<::RuntimeCall> for Pallet { + fn contains(call: &::RuntimeCall) -> bool { + let stage = RcMigrationStage::::get(); + let is_finished = stage >= MigrationStage::MigrationDone; + let is_ongoing = stage > MigrationStage::Pending && !is_finished; + + // We have to return whether the call is allowed: + const ALLOWED: bool = true; + const FORBIDDEN: bool = false; + + if is_finished && !T::RcIntraMigrationCalls::contains(call) { + return FORBIDDEN; + } + + if is_ongoing && !T::RcPostMigrationCalls::contains(call) { + return FORBIDDEN; + } + + ALLOWED + } +} diff --git a/relay/polkadot/src/ah_migration/mod.rs b/relay/polkadot/src/ah_migration/mod.rs new file mode 100644 index 0000000000..2a8cd25096 --- /dev/null +++ b/relay/polkadot/src/ah_migration/mod.rs @@ -0,0 +1,19 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Asset Hub Migration. + +pub mod phase1; diff --git a/relay/polkadot/src/ah_migration/phase1.rs b/relay/polkadot/src/ah_migration/phase1.rs new file mode 100644 index 0000000000..6fd2d60f79 --- /dev/null +++ b/relay/polkadot/src/ah_migration/phase1.rs @@ -0,0 +1,131 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! First phase of the Asset Hub Migration. + +use crate::*; + +/// Contains all calls that are enabled during the migration. +pub struct CallsEnabledDuringMigration; +impl Contains<::RuntimeCall> for CallsEnabledDuringMigration { + fn contains(call: &::RuntimeCall) -> bool { + let (during, _after) = call_allowed_status(call); + during + } +} + +/// Contains all calls that are enabled after the migration. +pub struct CallsEnabledAfterMigration; +impl Contains<::RuntimeCall> for CallsEnabledAfterMigration { + fn contains(call: &::RuntimeCall) -> bool { + let (_during, after) = call_allowed_status(call); + after + } +} + +/// Return whether a call should be enabled during and/or after the migration. +/// +/// Time line of the migration looks like this: +/// +/// --------|-----------|---------> +/// Start End +/// +/// We now define 2 periods: +/// +/// 1. During the migration: [Start, End] +/// 2. After the migration: (End, ∞) +/// +/// Visually: +/// +/// |-----1-----| +/// |---2----> +/// --------|-----------|---------> +/// Start End +/// +/// This call returns a 2-tuple to indicate whether a call is enabled during these periods. +pub fn call_allowed_status(call: &::RuntimeCall) -> (bool, bool) { + use RuntimeCall::*; + const ON: bool = true; + const OFF: bool = false; + + match call { + System(..) => (ON, ON), + Scheduler(..) => (OFF, OFF), + Preimage(..) => (OFF, OFF), + Babe(..) => (ON, ON), // TODO double check + Timestamp(..) => (OFF, OFF), + Indices(..) => (OFF, OFF), + Balances(..) => (OFF, ON), + // TransactionPayment has no calls + // Authorship has no calls + Staking(..) => (OFF, OFF), + // Offences has no calls + // Historical has no calls + Session(..) => (OFF, OFF), + Grandpa(..) => (ON, ON), // TODO double check + // AuthorityDiscovery has no calls + Treasury(..) => (OFF, OFF), + ConvictionVoting(..) => (OFF, OFF), + Referenda(..) => (OFF, OFF), + // Origins has no calls + Whitelist(..) => (OFF, OFF), + Claims(..) => (OFF, OFF), + Vesting(..) => (OFF, OFF), + Utility(..) => (OFF, ON), // batching etc + Proxy(..) => (OFF, ON), + Multisig(..) => (OFF, ON), + Bounties(..) => (OFF, OFF), + ChildBounties(..) => (OFF, OFF), + ElectionProviderMultiPhase(..) => (OFF, OFF), + VoterList(..) => (OFF, OFF), + NominationPools(..) => (OFF, OFF), + FastUnstake(..) => (OFF, OFF), + // DelegatedStaking has on calls + // ParachainsOrigin has no calls + Configuration(..) => (ON, ON), /* TODO allow this to be called by fellow origin during the migration https://github.com/polkadot-fellows/runtimes/pull/559#discussion_r1928794490 */ + ParasShared(..) => (OFF, OFF), /* Has no calls but a call enum https://github.com/paritytech/polkadot-sdk/blob/ee803b74056fac5101c06ec5998586fa6eaac470/polkadot/runtime/parachains/src/shared.rs#L185-L186 */ + ParaInclusion(..) => (OFF, OFF), /* Has no calls but a call enum https://github.com/paritytech/polkadot-sdk/blob/74ec1ee226ace087748f38dfeffc869cd5534ac8/polkadot/runtime/parachains/src/inclusion/mod.rs#L352-L353 */ + ParaInherent(..) => (ON, ON), // only inherents + // ParaScheduler has no calls + Paras(..) => (ON, ON), + Initializer(..) => (ON, ON), + // Dmp has no calls and deprecated + Hrmp(..) => (OFF, OFF), + // ParaSessionInfo has no calls + ParasDisputes(..) => (OFF, ON), // TODO check with security + ParasSlashing(..) => (OFF, ON), // TODO check with security + OnDemand(..) => (OFF, ON), + // CoretimeAssignmentProvider has no calls + Registrar(..) => (OFF, ON), + Slots(..) => (OFF, OFF), + Auctions(..) => (OFF, OFF), + Crowdloan( + crowdloan::Call::::dissolve { .. } | + crowdloan::Call::::refund { .. } | + crowdloan::Call::::dissolve { .. }, + ) => (OFF, ON), + Crowdloan(..) => (OFF, OFF), + Coretime(coretime::Call::::request_revenue_at { .. }) => (OFF, ON), + Coretime(..) => (ON, ON), + StateTrieMigration(..) => (OFF, OFF), // Deprecated + XcmPallet(..) => (OFF, ON), /* TODO allow para origins and root to call this during the migration, see https://github.com/polkadot-fellows/runtimes/pull/559#discussion_r1928789463 */ + MessageQueue(..) => (ON, ON), // TODO think about this + AssetRate(..) => (OFF, OFF), + Beefy(..) => (OFF, ON), /* TODO @claravanstaden @bkontur + * RcMigrator has no calls currently + * Exhaustive match. Compiler ensures that we did not miss any. */ + } +} diff --git a/relay/polkadot/src/lib.rs b/relay/polkadot/src/lib.rs index a60d4910dc..5ea3810df6 100644 --- a/relay/polkadot/src/lib.rs +++ b/relay/polkadot/src/lib.rs @@ -50,6 +50,7 @@ use runtime_parachains::{ shared as parachains_shared, }; +use ah_migration::phase1 as ahm_phase1; use authority_discovery_primitives::AuthorityId as AuthorityDiscoveryId; use beefy_primitives::{ ecdsa_crypto::{AuthorityId as BeefyId, Signature as BeefySignature}, @@ -67,7 +68,7 @@ use frame_support::{ traits::{ fungible::HoldConsideration, tokens::{imbalance::ResolveTo, UnityOrOuterConversion}, - ConstU32, ConstU8, EitherOf, EitherOfDiverse, Everything, FromContains, Get, + ConstU32, ConstU8, Contains, EitherOf, EitherOfDiverse, Everything, FromContains, Get, InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, OnRuntimeUpgrade, PrivilegeCmp, ProcessMessage, ProcessMessageError, WithdrawReasons, }, @@ -138,6 +139,7 @@ mod bag_thresholds; // Genesis preset configurations. pub mod genesis_config_presets; // Governance configurations. +pub mod ah_migration; pub mod governance; use governance::{ pallet_custom_origins, AuctionAdmin, FellowshipAdmin, GeneralAdmin, LeaseAdmin, StakingAdmin, @@ -190,7 +192,7 @@ parameter_types! { } impl frame_system::Config for Runtime { - type BaseCallFilter = Everything; + type BaseCallFilter = RcMigrator; type BlockWeights = BlockWeights; type BlockLength = BlockLength; type RuntimeOrigin = RuntimeOrigin; @@ -1576,6 +1578,8 @@ impl pallet_rc_migrator::Config for Runtime { type AhExistentialDeposit = AhExistentialDeposit; type RcWeightInfo = (); type AhWeightInfo = (); + type RcPostMigrationCalls = ahm_phase1::CallsEnabledDuringMigration; + type RcIntraMigrationCalls = ahm_phase1::CallsEnabledAfterMigration; } construct_runtime! { diff --git a/relay/polkadot/tests/ahm/mod.rs b/relay/polkadot/tests/ahm/mod.rs index 31b6c6d474..24618e8461 100644 --- a/relay/polkadot/tests/ahm/mod.rs +++ b/relay/polkadot/tests/ahm/mod.rs @@ -14,16 +14,17 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! Asset Hub Migrator tests. +//! Asset Hub Migration tests. mod accounts; -// Runtime specific imports -// Use general aliases for the imports to make it easier to copy&paste the tests for other runtimes. -use polkadot_runtime::{Block, RcMigrator, Runtime as T, System, *}; - -// General imports +use frame_support::{sp_runtime::traits::Dispatchable, traits::Contains}; +use pallet_rc_migrator::*; +use polkadot_primitives::Id as ParaId; +use polkadot_runtime::{Block, BuildStorage, RcMigrator, Runtime as T, RuntimeOrigin, System, *}; use remote_externalities::{Builder, Mode, OfflineConfig, RemoteExternalities}; +use runtime_parachains::inclusion::AggregateMessageOrigin; +use sp_runtime::AccountId32; /// Create externalities that have their state initialized from a snapshot. /// @@ -48,3 +49,105 @@ async fn remote_ext_test_setup() -> Option> { Some(ext) } + +/// Check that the call filtering mechanism works. +#[test] +fn call_filter_works() { + let mut t: sp_io::TestExternalities = + frame_system::GenesisConfig::::default().build_storage().unwrap().into(); + + // MQ calls are never filtered: + let mq_call = + polkadot_runtime::RuntimeCall::MessageQueue(pallet_message_queue::Call::::reap_page { + message_origin: AggregateMessageOrigin::Ump( + runtime_parachains::inclusion::UmpQueueId::Para(ParaId::from(1000)), + ), + page_index: 0, + }); + // Balances calls are filtered during the migration: + let balances_call = + polkadot_runtime::RuntimeCall::Balances(pallet_balances::Call::::transfer_all { + dest: AccountId32::from([0; 32]).into(), + keep_alive: false, + }); + // Indices calls are filtered during and after the migration: + let indices_call = + polkadot_runtime::RuntimeCall::Indices(pallet_indices::Call::::claim { index: 0 }); + + let is_allowed = |call: &polkadot_runtime::RuntimeCall| Pallet::::contains(call); + + // Try the BaseCallFilter + t.execute_with(|| { + // Before the migration starts + { + RcMigrationStage::::put(MigrationStage::Pending); + + assert!(is_allowed(&mq_call)); + assert!(is_allowed(&balances_call)); + assert!(is_allowed(&indices_call)); + } + + // During the migration + { + RcMigrationStage::::put(MigrationStage::ProxyMigrationInit); + + assert!(is_allowed(&mq_call)); + assert!(!is_allowed(&balances_call)); + assert!(!is_allowed(&indices_call)); + } + + // After the migration + { + RcMigrationStage::::put(MigrationStage::MigrationDone); + + assert!(is_allowed(&mq_call)); + assert!(is_allowed(&balances_call)); + assert!(!is_allowed(&indices_call)); + } + }); + + // Try to actually dispatch the calls + t.execute_with(|| { + as frame_support::traits::Currency<_>>::deposit_creating( + &AccountId32::from([0; 32]), + u64::MAX.into(), + ); + + // Before the migration starts + { + RcMigrationStage::::put(MigrationStage::Pending); + + assert!(!is_forbidden(&mq_call)); + assert!(!is_forbidden(&balances_call)); + assert!(!is_forbidden(&indices_call)); + } + + // During the migration + { + RcMigrationStage::::put(MigrationStage::ProxyMigrationInit); + + assert!(!is_forbidden(&mq_call)); + assert!(is_forbidden(&balances_call)); + assert!(is_forbidden(&indices_call)); + } + + // After the migration + { + RcMigrationStage::::put(MigrationStage::MigrationDone); + + assert!(!is_forbidden(&mq_call)); + assert!(!is_forbidden(&balances_call)); + assert!(is_forbidden(&indices_call)); + } + }); +} + +/// Whether a call is forbidden by the call filter. +fn is_forbidden(call: &polkadot_runtime::RuntimeCall) -> bool { + let Err(err) = call.clone().dispatch(RuntimeOrigin::signed(AccountId32::from([0; 32]))) else { + return false; + }; + + let filtered_err: sp_runtime::DispatchError = frame_system::Error::::CallFiltered.into(); + err.error == filtered_err +}