diff --git a/pallets/rc-migrator/src/lib.rs b/pallets/rc-migrator/src/lib.rs index cb11aa58bf..c4735867c9 100644 --- a/pallets/rc-migrator/src/lib.rs +++ b/pallets/rc-migrator/src/lib.rs @@ -195,6 +195,8 @@ pub enum MigrationStage< /// The migration has not yet started but will start in the future. #[default] Pending, + /// The migration was paused. + MigrationPaused, /// The migration has been scheduled to start at the given block number. Scheduled { /// The block number at which the migration will start. @@ -652,6 +654,8 @@ pub mod pallet { BadXcmVersion, /// The origin is invalid. InvalidOrigin, + /// The stage transition is invalid. + InvalidStageTransition, } #[pallet::event] @@ -717,15 +721,9 @@ pub mod pallet { new: AhUmpQueuePriority>, }, /// The total issuance was recorded. - MigratedBalanceRecordSet { - kept: T::Balance, - migrated: T::Balance, - }, + MigratedBalanceRecordSet { kept: T::Balance, migrated: T::Balance }, /// The RC kept balance was consumed. - MigratedBalanceConsumed { - kept: T::Balance, - migrated: T::Balance, - }, + MigratedBalanceConsumed { kept: T::Balance, migrated: T::Balance }, /// The manager account id was set. ManagerSet { /// The old manager account id. @@ -734,15 +732,31 @@ pub mod pallet { new: Option, }, /// An XCM message was sent. - XcmSent { - origin: Location, - destination: Location, - message: Xcm<()>, - message_id: XcmHash, - }, + XcmSent { origin: Location, destination: Location, message: Xcm<()>, message_id: XcmHash }, /// The staking elections were paused. StakingElectionsPaused, + /// The accounts to be preserved on Relay Chain were set. + AccountsPreserved { + /// The accounts that will be preserved. + accounts: Vec, + }, + /// The canceller account id was set. + CancellerSet { + /// The old canceller account id. + old: Option, + /// The new canceller account id. + new: Option, + }, + /// The migration was paused. + MigrationPaused { + /// The stage at which the migration was paused. + pause_stage: MigrationStageOf, + }, + /// The migration was cancelled. + MigrationCancelled, + /// Some pure accounts were indexed for possibly receiving free `Any` proxies. PureAccountsIndexed { + /// The number of indexed pure accounts. num_pure_accounts: u32, }, } @@ -816,6 +830,12 @@ pub mod pallet { #[pallet::storage] pub type Manager = StorageValue<_, T::AccountId, OptionQuery>; + /// An optional account id of a canceller. + /// + /// This account id can only stop scheduled migration. + #[pallet::storage] + pub type Canceller = StorageValue<_, T::AccountId, OptionQuery>; + /// The block number at which the migration began and the pallet's extrinsics were locked. /// /// This value is set when entering the `WaitingForAh` stage, i.e., when @@ -1186,6 +1206,90 @@ pub mod pallet { }); Ok(()) } + + /// Set the accounts to be preserved on Relay Chain during the migration. + /// + /// The accounts must have no consumers references. + #[pallet::call_index(9)] + #[pallet::weight({ + Weight::from_parts(10_000_000, 0) + .saturating_add(T::DbWeight::get().writes(accounts.len() as u64)) + })] + pub fn preserve_accounts( + origin: OriginFor, + accounts: Vec, + ) -> DispatchResultWithPostInfo { + Self::ensure_admin_or_manager(origin.clone())?; + for account in &accounts { + ensure!( + frame_system::Pallet::::consumers(account) == 0, + Error::::AccountReferenced + ); + RcAccounts::::insert(account, accounts::AccountState::Preserve); + } + Self::deposit_event(Event::AccountsPreserved { accounts }); + + Ok(Pays::No.into()) + } + + /// Set the canceller account id. + /// + /// The canceller can only stop scheduled migration. + #[pallet::call_index(10)] + #[pallet::weight(T::RcWeightInfo::set_manager())] // same as `set_manager` + pub fn set_canceller( + origin: OriginFor, + new: Option, + ) -> DispatchResultWithPostInfo { + Self::ensure_admin_or_manager(origin.clone())?; + if let Some(ref who) = new { + ensure!( + frame_system::Pallet::::consumers(who) == 0, + Error::::AccountReferenced + ); + RcAccounts::::insert(who, accounts::AccountState::Preserve); + } + let old = Canceller::::get(); + Canceller::::set(new.clone()); + Self::deposit_event(Event::CancellerSet { old, new }); + + Ok(Pays::No.into()) + } + + /// Pause the migration. + #[pallet::call_index(11)] + #[pallet::weight({ Weight::from_parts(10_000_000, 1000) })] + pub fn pause_migration(origin: OriginFor) -> DispatchResultWithPostInfo { + Self::ensure_admin_or_manager(origin.clone())?; + + let pause_stage = RcMigrationStage::::get(); + Self::transition(MigrationStage::MigrationPaused); + + Self::deposit_event(Event::MigrationPaused { pause_stage }); + + Ok(Pays::No.into()) + } + + /// Cancel the migration. + /// + /// Migration can only be cancelled if it is in the [`MigrationStage::Scheduled`] state. + #[pallet::call_index(12)] + #[pallet::weight({ Weight::from_parts(10_000_000, 1000) })] + pub fn cancel_migration(origin: OriginFor) -> DispatchResultWithPostInfo { + Self::ensure_privileged_origin(origin)?; + + let current_stage = RcMigrationStage::::get(); + ensure!( + matches!(current_stage, MigrationStage::Scheduled { .. }), + Error::::InvalidStageTransition + ); + + Self::transition(MigrationStage::Pending); + + Self::deposit_event(Event::MigrationCancelled); + + Ok(Pays::No.into()) + } } #[pallet::hooks] @@ -1228,7 +1332,7 @@ pub mod pallet { } match stage { - MigrationStage::Pending => { + MigrationStage::Pending | MigrationStage::MigrationPaused { .. } => { return weight_counter.consumed(); }, MigrationStage::Scheduled { start } => { @@ -2158,6 +2262,21 @@ pub mod pallet { Ok(()) } + /// Ensure that the origin is [`Config::AdminOrigin`], signed by [`Manager`] account id or + /// [`Canceller`] account id. + fn ensure_privileged_origin(origin: OriginFor) -> DispatchResult { + if let Ok(account_id) = ensure_signed(origin.clone()) { + if Manager::::get().map_or(false, |manager_id| manager_id == account_id) { + return Ok(()); + } + if Canceller::::get().map_or(false, |canceller_id| canceller_id == account_id) { + return Ok(()); + } + } + ::AdminOrigin::ensure_origin(origin)?; + Ok(()) + } + /// Returns `true` if the migration is ongoing and the Asset Hub has not confirmed /// processing the same number of XCM messages as we have sent to it. fn has_excess_unconfirmed_dmp(current: &MigrationStageOf) -> bool {