diff --git a/Cargo.lock b/Cargo.lock index f29528f2e0..e15d1b9183 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -784,6 +784,7 @@ dependencies = [ "kusama-runtime-constants", "log", "pallet-ah-migrator", + "pallet-ah-ops", "pallet-asset-conversion", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", @@ -2282,9 +2283,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -2292,7 +2293,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.48.5", + "windows-targets 0.52.0", ] [[package]] @@ -7646,12 +7647,15 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" name = "pallet-ah-migrator" version = "0.1.0" dependencies = [ + "chrono", + "cumulus-primitives-core", "frame-benchmarking", "frame-support", "frame-system", "hex", "hex-literal", "log", + "pallet-ah-ops", "pallet-asset-rate", "pallet-bags-list", "pallet-balances", @@ -7668,6 +7672,7 @@ dependencies = [ "pallet-scheduler", "pallet-staking", "pallet-state-trie-migration", + "pallet-timestamp", "pallet-treasury", "pallet-vesting", "parity-scale-codec", @@ -7683,6 +7688,22 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-ah-ops" +version = "0.1.0" +dependencies = [ + "cumulus-primitives-core", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-runtime 39.0.5", + "sp-std", +] + [[package]] name = "pallet-alliance" version = "37.0.0" @@ -10079,9 +10100,11 @@ dependencies = [ "frame-system", "log", "pallet-ah-migrator", + "pallet-balances", "pallet-indices", "pallet-message-queue", "pallet-rc-migrator", + "pallet-timestamp", "parachains-common", "parity-scale-codec", "polkadot-parachain-primitives", diff --git a/Cargo.toml b/Cargo.toml index 764768758c..a8d94acc18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,8 +8,10 @@ license = "GPL-3.0-only" # TODO ::post_check(()); + pallet_ah_migrator::crowdloan::CrowdloanMigrationCheck::::post_check(); + //pallet_ah_migrator::preimage::PreimageMigrationCheck::::post_check(()); // NOTE that the DMP queue is probably not empty because the snapshot that we use contains // some overweight ones. }); } +#[tokio::test] +async fn num_leases_to_ending_block_works_simple() { + let mut rc = remote_ext_test_setup::("SNAP_RC").await.unwrap(); + let f = |now: BlockNumberFor, num_leases: u32| { + frame_system::Pallet::::set_block_number(now); + pallet_rc_migrator::crowdloan::num_leases_to_ending_block::(num_leases) + }; + + rc.execute_with(|| { + let p = ::LeasePeriod::get(); + let o = ::LeaseOffset::get(); + + // Sanity check: + assert!(f(1000, 0).is_err()); + assert!(f(1000, 10).is_err()); + // Overflow check: + assert!(f(o, u32::MAX).is_err()); + + // In period 0: + assert_eq!(f(o, 0), Ok(o)); + assert_eq!(f(o, 1), Ok(o + p)); + assert_eq!(f(o, 2), Ok(o + 2 * p)); + + // In period 1: + assert_eq!(f(o + p, 0), Ok(o + p)); + assert_eq!(f(o + p, 1), Ok(o + 2 * p)); + assert_eq!(f(o + p, 2), Ok(o + 3 * p)); + + // In period 19 with 5 remaining: + assert_eq!(f(o + 19 * p, 1), Ok(o + 20 * p)); + assert_eq!(f(o + 19 * p, 5), Ok(o + 24 * p)); + }); +} + #[test] fn sovereign_account_translation() { let good_cases = [ diff --git a/pallets/ah-migrator/Cargo.toml b/pallets/ah-migrator/Cargo.toml index 93ada94d27..bfdc0b1a9d 100644 --- a/pallets/ah-migrator/Cargo.toml +++ b/pallets/ah-migrator/Cargo.toml @@ -9,6 +9,8 @@ repository.workspace = true [dependencies] codec = { workspace = true, features = ["max-encoded-len"] } +cumulus-primitives-core = { workspace = true } +chrono = { workspace = true } frame-benchmarking = { workspace = true, optional = true } frame-support = { workspace = true } frame-system = { workspace = true } @@ -31,6 +33,7 @@ pallet-staking = { workspace = true } pallet-state-trie-migration = { workspace = true } pallet-vesting = { workspace = true } pallet-treasury = { workspace = true } +pallet-timestamp = { workspace = true } polkadot-parachain-primitives = { workspace = true } polkadot-runtime-common = { workspace = true } runtime-parachains = { workspace = true } @@ -43,16 +46,20 @@ sp-runtime = { workspace = true } sp-std = { workspace = true } hex-literal = { workspace = true } hex = { workspace = true } +pallet-ah-ops = { workspace = true } [features] default = ["std"] std = [ + "chrono/std", "codec/std", + "cumulus-primitives-core/std", "frame-benchmarking?/std", "frame-support/std", "frame-system/std", "hex/std", "log/std", + "pallet-ah-ops/std", "pallet-asset-rate/std", "pallet-bags-list/std", "pallet-balances/std", @@ -69,6 +76,7 @@ std = [ "pallet-scheduler/std", "pallet-staking/std", "pallet-state-trie-migration/std", + "pallet-timestamp/std", "pallet-treasury/std", "pallet-vesting/std", "polkadot-parachain-primitives/std", @@ -83,9 +91,11 @@ std = [ "sp-std/std", ] runtime-benchmarks = [ + "cumulus-primitives-core/runtime-benchmarks", "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", + "pallet-ah-ops/runtime-benchmarks", "pallet-asset-rate/runtime-benchmarks", "pallet-bags-list/runtime-benchmarks", "pallet-balances/runtime-benchmarks", @@ -102,6 +112,7 @@ runtime-benchmarks = [ "pallet-scheduler/runtime-benchmarks", "pallet-staking/runtime-benchmarks", "pallet-state-trie-migration/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", "pallet-treasury/runtime-benchmarks", "pallet-vesting/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", @@ -112,6 +123,7 @@ runtime-benchmarks = [ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", + "pallet-ah-ops/try-runtime", "pallet-asset-rate/try-runtime", "pallet-bags-list/try-runtime", "pallet-balances/try-runtime", @@ -128,6 +140,7 @@ try-runtime = [ "pallet-scheduler/try-runtime", "pallet-staking/try-runtime", "pallet-state-trie-migration/try-runtime", + "pallet-timestamp/try-runtime", "pallet-treasury/try-runtime", "pallet-vesting/try-runtime", "polkadot-runtime-common/try-runtime", diff --git a/pallets/ah-migrator/src/crowdloan.rs b/pallets/ah-migrator/src/crowdloan.rs new file mode 100644 index 0000000000..7dfa76e5de --- /dev/null +++ b/pallets/ah-migrator/src/crowdloan.rs @@ -0,0 +1,167 @@ +// 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 . + +use crate::*; +use chrono::TimeZone; +use pallet_rc_migrator::crowdloan::RcCrowdloanMessage; + +impl Pallet { + pub fn do_receive_crowdloan_messages( + messages: Vec>, + ) -> Result<(), Error> { + let (mut good, mut bad) = (0, 0); + Self::deposit_event(Event::BatchReceived { + pallet: PalletEventName::Crowdloan, + count: messages.len() as u32, + }); + log::info!(target: LOG_TARGET, "Received {} crowdloan messages", messages.len()); + + for message in messages { + match Self::do_process_crowdloan_message(message) { + Ok(()) => good += 1, + Err(e) => { + bad += 1; + log::error!(target: LOG_TARGET, "Error while integrating crowdloan message: {:?}", e); + }, + } + } + + Self::deposit_event(Event::BatchProcessed { + pallet: PalletEventName::Crowdloan, + count_good: good, + count_bad: bad, + }); + + Ok(()) + } + + pub fn do_process_crowdloan_message(message: RcCrowdloanMessageOf) -> Result<(), Error> { + match message { + RcCrowdloanMessage::LeaseReserve { unreserve_block, account, para_id, amount } => { + log::info!(target: LOG_TARGET, "Integrating lease reserve for para_id: {:?}, account: {:?}, amount: {:?}, unreserve_block: {:?}", ¶_id, &account, &amount, &unreserve_block); + defensive_assert!(!pallet_ah_ops::RcLeaseReserve::::contains_key(( + unreserve_block, + para_id, + &account + ))); + + pallet_ah_ops::RcLeaseReserve::::insert( + (unreserve_block, para_id, &account), + amount, + ); + }, + RcCrowdloanMessage::CrowdloanContribution { + withdraw_block, + contributor, + para_id, + amount, + crowdloan_account, + } => { + log::info!(target: LOG_TARGET, "Integrating crowdloan contribution for para_id: {:?}, contributor: {:?}, amount: {:?}, crowdloan_account: {:?}, withdraw_block: {:?}", ¶_id, &contributor, &amount, &crowdloan_account, &withdraw_block); + defensive_assert!(!pallet_ah_ops::RcCrowdloanContribution::::contains_key(( + withdraw_block, + para_id, + &contributor + ))); + + pallet_ah_ops::RcCrowdloanContribution::::insert( + (withdraw_block, para_id, &contributor), + (crowdloan_account, amount), + ); + }, + RcCrowdloanMessage::CrowdloanReserve { + unreserve_block, + para_id, + amount, + depositor, + } => { + log::info!(target: LOG_TARGET, "Integrating crowdloan reserve for para_id: {:?}, amount: {:?}, depositor: {:?}", ¶_id, &amount, &depositor); + defensive_assert!(!pallet_ah_ops::RcCrowdloanReserve::::contains_key(( + unreserve_block, + para_id, + &depositor + ))); + + pallet_ah_ops::RcCrowdloanReserve::::insert( + (unreserve_block, para_id, &depositor), + amount, + ); + }, + } + + Ok(()) + } +} + +pub struct CrowdloanMigrationCheck(pub PhantomData); + +#[cfg(feature = "std")] +impl CrowdloanMigrationCheck +where + BlockNumberFor: Into, +{ + pub fn post_check() { + println!("Lease reserve info"); + let lease_reserves = pallet_ah_ops::RcLeaseReserve::::iter().collect::>(); + for ((unlock_block, para_id, who), value) in &lease_reserves { + println!( + "Lease Reserve [{unlock_block}] {para_id} {who}: {} ({})", + value / 10_000_000_000, + Self::block_to_date(*unlock_block) + ); + } + + let total_reserved = lease_reserves.iter().map(|((_, _, _), value)| value).sum::(); + println!( + "Num lease reserves: {}, total reserved amount: {}", + lease_reserves.len(), + total_reserved / 10_000_000_000 + ); + + println!("Crowdloan reserve info"); + let crowdloan_reserves = pallet_ah_ops::RcCrowdloanReserve::::iter().collect::>(); + for ((unlock_block, para_id, who), value) in &crowdloan_reserves { + println!( + "Crowdloan Reserve [{unlock_block}] {para_id} {who}: {} ({})", + value / 10_000_000_000, + Self::block_to_date(*unlock_block) + ); + } + + let total_reserved = + crowdloan_reserves.iter().map(|((_, _, _), value)| value).sum::(); + println!( + "Num crowdloan reserves: {}, total reserved amount: {}", + crowdloan_reserves.len(), + total_reserved / 10_000_000_000 + ); + } + + #[cfg(feature = "std")] + fn block_to_date(block: BlockNumberFor) -> chrono::DateTime { + let anchor_block: u64 = + ::RcBlockNumberProvider::current_block_number().into(); + // We are using the time from AH here, not relay. But the snapshots are taken together. + let anchor_timestamp: u64 = pallet_timestamp::Now::::get().into(); + + let block_diff: u64 = (block.into() - anchor_block).into(); + let add_time_ms: i64 = (block_diff * 6_000) as i64; + + // convert anchor_timestamp to chrono timestamp + let anchor_timestamp = chrono::Utc.timestamp_millis_opt(anchor_timestamp as i64).unwrap(); + let block_timestamp = anchor_timestamp + chrono::Duration::milliseconds(add_time_ms); + block_timestamp + } +} diff --git a/pallets/ah-migrator/src/lib.rs b/pallets/ah-migrator/src/lib.rs index c56c513bfe..e9542ada32 100644 --- a/pallets/ah-migrator/src/lib.rs +++ b/pallets/ah-migrator/src/lib.rs @@ -37,6 +37,7 @@ pub mod bounties; pub mod call; pub mod claims; pub mod conviction_voting; +pub mod crowdloan; pub mod indices; pub mod multisig; pub mod preimage; @@ -54,7 +55,7 @@ use frame_support::{ pallet_prelude::*, storage::{transactional::with_transaction_opaque_err, TransactionOutcome}, traits::{ - fungible::{InspectFreeze, Mutate, MutateFreeze, MutateHold}, + fungible::{InspectFreeze, Mutate, MutateFreeze, MutateHold, Unbalanced}, Defensive, DefensiveTruncateFrom, LockableCurrency, OriginTrait, QueryPreimage, ReservableCurrency, StorePreimage, WithdrawReasons as LockWithdrawReasons, }, @@ -65,6 +66,7 @@ use pallet_rc_migrator::{ accounts::Account as RcAccount, claims::RcClaimsMessageOf, conviction_voting::RcConvictionVotingMessageOf, + crowdloan::RcCrowdloanMessageOf, indices::RcIndicesIndexOf, multisig::*, preimage::*, @@ -101,11 +103,14 @@ type RcAccountFor = RcAccount< pub enum PalletEventName { Indices, FastUnstake, + Crowdloan, BagsList, Vesting, Bounties, } +pub type BalanceOf = ::Balance; + #[frame_support::pallet(dev_mode)] pub mod pallet { use super::*; @@ -131,6 +136,8 @@ pub mod pallet { + pallet_bounties::Config + pallet_treasury::Config + pallet_asset_rate::Config + + pallet_timestamp::Config // Needed for testing + + pallet_ah_ops::Config { /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -139,6 +146,7 @@ pub mod pallet { + MutateHold + InspectFreeze + MutateFreeze + + Unbalanced + ReservableCurrency + LockableCurrency; /// XCM check account. @@ -351,21 +359,6 @@ pub mod pallet { /// How many scheduler messages failed to integrate. count_bad: u32, }, - /// Should not happen. Manual intervention by the Fellowship required. - /// - /// Can happen when existing AH and incoming RC vesting schedules have more combined - /// entries than allowed. This triggers the merging logic which has henceforth failed - /// with the given inner pallet-vesting error. - FailedToMergeVestingSchedules { - /// The account that failed to merge the schedules. - who: AccountId32, - /// The first schedule index that failed to merge. - schedule1: u32, - /// The second schedule index that failed to merge. - schedule2: u32, - /// The index of the pallet-vesting error that occurred. - pallet_vesting_error_index: Option, - }, ConvictionVotingMessagesReceived { /// How many conviction voting messages are in the batch. count: u32, @@ -602,6 +595,16 @@ pub mod pallet { Self::do_receive_asset_rates(rates).map_err(Into::into) } + + #[pallet::call_index(19)] + pub fn receive_crowdloan_messages( + origin: OriginFor, + messages: Vec>, + ) -> DispatchResult { + ensure_root(origin)?; + + Self::do_receive_crowdloan_messages(messages).map_err(Into::into) + } } #[pallet::hooks] diff --git a/pallets/ah-migrator/src/staking/nom_pools.rs b/pallets/ah-migrator/src/staking/nom_pools.rs index 088e57db44..2c830a86f6 100644 --- a/pallets/ah-migrator/src/staking/nom_pools.rs +++ b/pallets/ah-migrator/src/staking/nom_pools.rs @@ -125,7 +125,7 @@ impl Pallet { /// - RC time point: 150 /// - Result: 75 + (150 - 100) / 2 = 100 pub fn rc_to_ah_timepoint(rc_timepoint: BlockNumberFor) -> BlockNumberFor { - let rc_now = T::RcBlockNumberProvider::current_block_number(); + let rc_now = ::RcBlockNumberProvider::current_block_number(); let ah_now = frame_system::Pallet::::block_number(); if let Some(rc_since) = rc_now.checked_sub(&rc_timepoint) { diff --git a/pallets/ah-ops/Cargo.toml b/pallets/ah-ops/Cargo.toml new file mode 100644 index 0000000000..f950e5a2a7 --- /dev/null +++ b/pallets/ah-ops/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "pallet-ah-ops" +description = "Operations cleanup pallet for the post-migration Asset Hub" +license = "Apache-2.0" +version = "0.1.0" +edition.workspace = true +authors.workspace = true +repository.workspace = true + +[dependencies] +codec = { workspace = true, features = ["max-encoded-len"] } +cumulus-primitives-core = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +log = { workspace = true } +pallet-balances = { workspace = true } +pallet-timestamp = { workspace = true } +scale-info = { workspace = true, features = ["derive"] } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "cumulus-primitives-core/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-balances/std", + "pallet-timestamp/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", + +] +runtime-benchmarks = [ + "cumulus-primitives-core/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-balances/try-runtime", + "pallet-timestamp/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/pallets/ah-ops/src/lib.rs b/pallets/ah-ops/src/lib.rs new file mode 100644 index 0000000000..7eba5947e4 --- /dev/null +++ b/pallets/ah-ops/src/lib.rs @@ -0,0 +1,345 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The operational pallet for the Asset Hub, designed to manage and facilitate the migration of +//! subsystems such as Governance, Staking, Balances from the Relay Chain to the Asset Hub. This +//! pallet works alongside its counterpart, `pallet_rc_migrator`, which handles migration +//! processes on the Relay Chain side. +//! +//! This pallet is responsible for controlling the initiation, progression, and completion of the +//! migration process, including managing its various stages and transferring the necessary data. +//! The pallet directly accesses the storage of other pallets for read/write operations while +//! maintaining compatibility with their existing APIs. +//! +//! To simplify development and avoid the need to edit the original pallets, this pallet may +//! duplicate private items such as storage entries from the original pallets. This ensures that the +//! migration logic can be implemented without altering the original implementations. + +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; + +use cumulus_primitives_core::ParaId; +use frame_support::{ + pallet_prelude::*, + traits::{ + fungible::{InspectFreeze, Mutate, MutateFreeze, MutateHold, Unbalanced}, + tokens::Preservation, + Defensive, LockableCurrency, ReservableCurrency, + }, +}; +use frame_system::pallet_prelude::*; +use pallet_balances::AccountData; +use sp_runtime::{traits::BlockNumberProvider, AccountId32}; +use sp_std::prelude::*; + +/// The log target of this pallet. +pub const LOG_TARGET: &str = "runtime::ah-migrator"; + +pub type BalanceOf = ::Balance; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::config] + pub trait Config: + frame_system::Config, AccountId = AccountId32> + + pallet_balances::Config + + pallet_timestamp::Config // Needed for testing + { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Native asset type. + type Currency: Mutate + + MutateHold + + InspectFreeze + + MutateFreeze + + Unbalanced + + ReservableCurrency + + LockableCurrency; + + /// Access the block number of the Relay Chain. + type RcBlockNumberProvider: BlockNumberProvider>; + } + + /// Amount of balance that was reserved for winning a lease auction. + /// + /// `unreserve_lease_deposit` can be permissionlessly called once the block number passed to + /// unreserve the deposit. It is implicitly called by `withdraw_crowdloan_contribution`. + /// + /// The account here can either be a crowdloan account or a solo bidder. If it is a crowdloan + /// account, then the summed up contributions for it in the contributions map will equate the + /// reserved balance here. + /// + /// The keys are as follows: + /// - Block number after which the deposit can be unreserved. + /// - The para_id of the lease slot. + /// - The account that will have the balance unreserved. + /// - The balance to be unreserved. + #[pallet::storage] + pub type RcLeaseReserve = StorageNMap< + _, + ( + NMapKey>, + NMapKey, + NMapKey, + ), + BalanceOf, + OptionQuery, + >; + + /// Amount of balance that a contributor made towards a crowdloan. + /// + /// `withdraw_crowdloan_contribution` can be permissionlessly called once the block number + /// passed to unlock the balance for a specific account. + /// + /// The keys are as follows: + /// - Block number after which the balance can be unlocked. + /// - The para_id of the crowdloan. + /// - The account that made the contribution. + /// + /// The value is (fund_pot, balance). The contribution pot is the second key in the + /// `RcCrowdloanContribution` storage. + #[pallet::storage] + pub type RcCrowdloanContribution = StorageNMap< + _, + ( + NMapKey>, + NMapKey, + NMapKey, + ), + (T::AccountId, BalanceOf), + OptionQuery, + >; + + /// The reserve that was taken to create a crowdloan. + /// + /// This is normally 500 DOT and can be refunded as last step after all + /// `RcCrowdloanContribution`s of this loan have been withdrawn. + /// + /// Keys: + /// - Block number after which this can be unreserved + /// - The para_id of the crowdloan + /// - The account that will have the balance unreserved + #[pallet::storage] + pub type RcCrowdloanReserve = StorageNMap< + _, + ( + NMapKey>, + NMapKey, + NMapKey, + ), + BalanceOf, + OptionQuery, + >; + + #[pallet::error] + pub enum Error { + /// Either no lease deposit or already unreserved. + NoLeaseReserve, + /// Either no crowdloan contribution or already withdrawn. + NoCrowdloanContribution, + /// Either no crowdloan reserve or already unreserved. + NoCrowdloanReserve, + /// Failed to withdraw crowdloan contribution. + FailedToWithdrawCrowdloanContribution, + /// Block number is not yet reached. + NotYet, + /// Not all contributions are withdrawn. + ContributionsRemaining, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// Some lease reserve could not be unreserved and needs manual cleanup. + LeaseUnreserveRemaining { + depositor: T::AccountId, + para_id: ParaId, + remaining: BalanceOf, + }, + + /// Some amount for a crowdloan reserve could not be unreserved and needs manual cleanup. + CrowdloanUnreserveRemaining { + depositor: T::AccountId, + para_id: ParaId, + remaining: BalanceOf, + }, + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::call] + impl Pallet { + /// Unreserve the deposit that was taken for creating a crowdloan. + /// + /// This can be called by any signed origin. It unreserves the lease deposit on the account + /// that won the lease auction. It can be unreserved once all leases expired. Note that it + /// will be called automatically from `withdraw_crowdloan_contribution` for the matching + /// crowdloan account. + /// + /// Solo bidder accounts that won lease auctions can use this to unreserve their amount. + #[pallet::call_index(0)] + #[pallet::weight(0)] + pub fn unreserve_lease_deposit( + origin: OriginFor, + block: BlockNumberFor, + depositor: Option, + para_id: ParaId, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + let depositor = depositor.unwrap_or(sender); + + Self::do_unreserve_lease_deposit(block, depositor, para_id).map_err(Into::into) + } + + /// Withdraw the contribution of a finished crowdloan. + /// + /// A crowdloan contribution can be withdrawn if either: + /// - The crowdloan failed to in an auction and timed out + /// - Won an auction and all leases expired + /// + /// Can be called by any signed origin. + #[pallet::call_index(1)] + #[pallet::weight(0)] + pub fn withdraw_crowdloan_contribution( + origin: OriginFor, + block: BlockNumberFor, + depositor: Option, + para_id: ParaId, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + let depositor = depositor.unwrap_or(sender); + + Self::do_withdraw_crowdloan_contribution(block, depositor, para_id).map_err(Into::into) + } + + /// Unreserve the deposit that was taken for creating a crowdloan. + /// + /// This can be called once either: + /// - The crowdloan failed to win an auction and timed out + /// - Won an auction, all leases expired and all contributions are withdrawn + /// + /// Can be called by any signed origin. The condition that all contributions are withdrawn + /// is in place since the reserve acts as a storage deposit. + #[pallet::call_index(2)] + #[pallet::weight(0)] + pub fn unreserve_crowdloan_reserve( + origin: OriginFor, + block: BlockNumberFor, + depositor: Option, + para_id: ParaId, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + let depositor = depositor.unwrap_or(sender); + + Self::do_unreserve_crowdloan_reserve(block, depositor, para_id).map_err(Into::into) + } + } + + impl Pallet { + pub fn do_unreserve_lease_deposit( + block: BlockNumberFor, + depositor: T::AccountId, + para_id: ParaId, + ) -> Result<(), Error> { + ensure!(block <= T::RcBlockNumberProvider::current_block_number(), Error::::NotYet); + let balance = RcLeaseReserve::::take((block, para_id, &depositor)) + .ok_or(Error::::NoLeaseReserve)?; + + let remaining = ::Currency::unreserve(&depositor, balance); + if remaining > 0 { + defensive!("Should be able to unreserve all"); + Self::deposit_event(Event::LeaseUnreserveRemaining { + depositor, + remaining, + para_id, + }); + } + + Ok(()) + } + + pub fn do_withdraw_crowdloan_contribution( + block: BlockNumberFor, + depositor: T::AccountId, + para_id: ParaId, + ) -> Result<(), Error> { + ensure!(block <= T::RcBlockNumberProvider::current_block_number(), Error::::NotYet); + let (pot, contribution) = + RcCrowdloanContribution::::take((block, para_id, &depositor)) + .ok_or(Error::::NoCrowdloanContribution)?; + + // Maybe this is the first one to withdraw and we need to unreserve it from the pot + match Self::do_unreserve_lease_deposit(block, pot.clone(), para_id) { + Ok(()) => (), + Err(Error::::NoLeaseReserve) => (), // fine + Err(e) => return Err(e), + } + + // Ideally this does not fail. But if it does, then we keep it for manual inspection. + let transferred = ::Currency::transfer( + &pot, + &depositor, + contribution, + Preservation::Preserve, + ) + .defensive() + .map_err(|_| Error::::FailedToWithdrawCrowdloanContribution)?; + defensive_assert!(transferred == contribution); + // Need to reactivate since we deactivated it here https://github.com/paritytech/polkadot-sdk/blob/04847d515ef56da4d0801c9b89a4241dfa827b33/polkadot/runtime/common/src/crowdloan/mod.rs#L793 + ::Currency::reactivate(transferred); + + Ok(()) + } + + pub fn do_unreserve_crowdloan_reserve( + block: BlockNumberFor, + depositor: T::AccountId, + para_id: ParaId, + ) -> Result<(), Error> { + ensure!(block <= T::RcBlockNumberProvider::current_block_number(), Error::::NotYet); + ensure!( + Self::contributions_withdrawn(block, para_id), + Error::::ContributionsRemaining + ); + let amount = RcCrowdloanReserve::::take((block, para_id, &depositor)) + .ok_or(Error::::NoCrowdloanReserve)?; + + let remaining = ::Currency::unreserve(&depositor, amount); + if remaining > 0 { + defensive!("Should be able to unreserve all"); + Self::deposit_event(Event::CrowdloanUnreserveRemaining { + depositor, + remaining, + para_id, + }); + } + + Ok(()) + } + + // TODO Test this + fn contributions_withdrawn(block: BlockNumberFor, para_id: ParaId) -> bool { + let mut contrib_iter = RcCrowdloanContribution::::iter_prefix((block, para_id)); + contrib_iter.next().is_none() + } + } +} diff --git a/pallets/rc-migrator/src/accounts.rs b/pallets/rc-migrator/src/accounts.rs index 87d3cbfba5..d5890c719e 100644 --- a/pallets/rc-migrator/src/accounts.rs +++ b/pallets/rc-migrator/src/accounts.rs @@ -122,7 +122,7 @@ pub struct Account { } /// The state for the Relay Chain accounts. -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum AccountState { /// The account should be migrated to AH and removed on RC. Migrate, diff --git a/pallets/rc-migrator/src/crowdloan.rs b/pallets/rc-migrator/src/crowdloan.rs new file mode 100644 index 0000000000..9be763a8a0 --- /dev/null +++ b/pallets/rc-migrator/src/crowdloan.rs @@ -0,0 +1,299 @@ +// 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 . + +use crate::{types::AccountIdOf, *}; +use sp_runtime::traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub}; + +pub struct CrowdloanMigrator { + _marker: sp_std::marker::PhantomData, +} + +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug, Clone, PartialEq, Eq)] +pub enum RcCrowdloanMessage { + /// Reserve for some slot leases. + LeaseReserve { + /// The block number at which this deposit can be unreserved. + unreserve_block: BlockNumber, + /// Account that has `amount` reserved. + account: AccountId, + /// Parachain ID that this crowdloan belongs to. + /// + /// Note that Bifrost ID 3356 is now 2030. + para_id: ParaId, + /// Amount that was reserved for the lease. + /// + /// This is not necessarily the same as the full crowdloan contribution amount, since there + /// can be contributions after the lease candle auction ended. But it is the same for solo + /// bidders. The amount that was contributed after the cutoff will be held as *free* by the + /// crowdloan pot account. + amount: Balance, + }, + /// Contribute to a crowdloan. + CrowdloanContribution { + /// The block number at which this contribution can be withdrawn. + withdraw_block: BlockNumber, + /// The contributor that will have `amount` deposited. + contributor: AccountId, + /// Parachain ID that this crowdloan belongs to. + /// + /// Note that Bifrost ID 3356 is now 2030. + para_id: ParaId, + /// Amount that was loaned to the crowdloan. + amount: Balance, + /// The crowdloan pot account that will have `amount` removed. + crowdloan_account: AccountId, + }, + /// Reserve amount on a crowdloan pot account. + CrowdloanReserve { + /// The block number at which this deposit can be unreserved. + unreserve_block: BlockNumber, + /// The account that has `amount` reserved. + /// + /// This is often the parachain manager or some multisig account from the parachain team + /// who initiated the crowdloan. + depositor: AccountId, + /// Parachain ID that this crowdloan belongs to. + /// + /// Note that Bifrost ID 3356 is now 2030. + para_id: ParaId, + /// Amount that was reserved to create the crowdloan. + /// + /// Normally this is 500 DOT. TODO: Should sanity check. + amount: Balance, + }, +} + +pub type RcCrowdloanMessageOf = + RcCrowdloanMessage, AccountIdOf, crate::BalanceOf>; + +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug, Clone, PartialEq, Eq)] +pub enum CrowdloanStage { + Setup, + LeaseReserve { last_key: Option }, + CrowdloanContribution { last_key: Option }, + CrowdloanReserve, + Finished, +} + +impl PalletMigration for CrowdloanMigrator + where + crate::BalanceOf: + From<<::Currency as frame_support::traits::Currency>::Balance>, + crate::BalanceOf: + From<<<::Auctioneer as polkadot_runtime_common::traits::Auctioneer<<<::Block as sp_runtime::traits::Block>::Header as sp_runtime::traits::Header>::Number>>::Currency as frame_support::traits::Currency>::Balance>, +{ + type Key = CrowdloanStage; + type Error = Error; + + fn migrate_many( + current_key: Option, + weight_counter: &mut WeightMeter, + ) -> Result, Self::Error> { + let mut inner_key = current_key.unwrap_or(CrowdloanStage::Setup); + let mut messages = Vec::new(); + + loop { + if weight_counter + .try_consume(::DbWeight::get().reads_writes(1, 1)) + .is_err() + { + if messages.is_empty() { + return Err(Error::OutOfWeight); + } else { + break; + } + } + + if messages.len() > 10_000 { + log::warn!("Weight allowed very big batch, stopping"); + break; + } + + inner_key = match inner_key { + CrowdloanStage::Setup => { + inner_key = CrowdloanStage::LeaseReserve { last_key: None }; + + // Only thing to do here is to re-map the bifrost crowdloan: https://polkadot.subsquare.io/referenda/524 + let leases = pallet_slots::Leases::::take(ParaId::from(2030)); + if leases.is_empty() { + defensive!("Bifrost fund maybe already ended, remove this"); + continue; + } + + // It would be better if we can re-map all contributions to the new para id, but + // that requires to iterate them all, so we go the other way around; changing + // the leases to the old Bifrost Crowdloan. + pallet_slots::Leases::::insert(ParaId::from(3356), leases); + log::info!(target: LOG_TARGET, "Migrated Bifrost Leases from crowdloan 2030 to 3356"); + + inner_key + }, + CrowdloanStage::LeaseReserve { last_key } => { + let mut iter = match last_key.clone() { + Some(last_key) => pallet_slots::Leases::::iter_from_key(last_key), + None => pallet_slots::Leases::::iter(), + }; + + match iter.next() { + Some((para_id, leases)) => { + inner_key = CrowdloanStage::LeaseReserve { last_key: Some(para_id) }; + + let Some(last_lease) = leases.last() else { + // This seems to be fine, but i don't know why it happens, see https://github.com/paritytech/polkadot-sdk/blob/db3ff60b5af2a9017cb968a4727835f3d00340f0/polkadot/runtime/common/src/slots/mod.rs#L108-L109 + log::warn!(target: LOG_TARGET, "Empty leases for para_id: {:?}", para_id); + continue; + }; + + let Some((lease_acc, lease_amount)) = last_lease else { + // See https://github.com/paritytech/polkadot-sdk/blob/db3ff60b5af2a9017cb968a4727835f3d00340f0/polkadot/runtime/common/src/slots/mod.rs#L115 + defensive!("Last lease cannot be None"); + continue; + }; + + // Sanity check that all leases have the same account and amount: + #[cfg(feature = "std")] + for (acc, amount) in leases.iter().flatten() { + defensive_assert!(acc == lease_acc, "All leases should have the same account"); + defensive_assert!(amount == lease_amount, "All leases should have the same amount"); + } + + // NOTE: Max instead of sum, see https://github.com/paritytech/polkadot-sdk/blob/db3ff60b5af2a9017cb968a4727835f3d00340f0/polkadot/runtime/common/src/slots/mod.rs#L102-L103 + let amount: crate::BalanceOf = leases.iter().flatten().map(|(_acc, amount)| amount).max().cloned().unwrap_or_default().into(); + + if amount == 0u32.into() { + defensive_assert!(para_id < ParaId::from(2000), "Only system chains are allowed to have zero lease reserve"); + continue; + } + + let unreserve_block = num_leases_to_ending_block::(leases.len() as u32).defensive().map_err(|_| Error::::Unreachable)?; + + log::debug!(target: LOG_TARGET, "Migrating out lease reserve for para_id: {:?}, account: {:?}, amount: {:?}, unreserve_block: {:?}", ¶_id, &lease_acc, &amount, &unreserve_block); + messages.push(RcCrowdloanMessage::LeaseReserve { unreserve_block, account: lease_acc.clone(), para_id, amount }); + inner_key + }, + None => CrowdloanStage::CrowdloanContribution { last_key: None }, + } + }, + CrowdloanStage::CrowdloanContribution { last_key } => { + let mut funds_iter = match last_key.clone() { + Some(last_key) => pallet_crowdloan::Funds::::iter_from_key(last_key), + None => pallet_crowdloan::Funds::::iter(), + }; + + let (para_id, fund) = match funds_iter.next() { + Some((para_id, fund)) => (para_id, fund), + None => { + inner_key = CrowdloanStage::CrowdloanReserve; + continue; + }, + }; + + let mut contributions_iter = pallet_crowdloan::Pallet::::contribution_iterator(fund.fund_index); + + match contributions_iter.next() { + Some((contributor, (amount, memo))) => { + inner_key = CrowdloanStage::CrowdloanContribution { last_key: Some(para_id) }; + // Dont really care about memos, but we can add them, if needed. + if !memo.is_empty() { + log::warn!(target: LOG_TARGET, "Discarding crowdloan memo of length: {}", &memo.len()); + } + + let leases = pallet_slots::Leases::::get(para_id); + if leases.is_empty() { + defensive_assert!(fund.raised == 0u32.into(), "Crowdloan should be empty if there are no leases"); + } + + let crowdloan_account = pallet_crowdloan::Pallet::::fund_account_id(fund.fund_index); + let withdraw_block = num_leases_to_ending_block::(leases.len() as u32).defensive().map_err(|_| Error::::Unreachable)?; + // We directly remove so that we dont have to store a cursor: + pallet_crowdloan::Pallet::::contribution_kill(fund.fund_index, &contributor); + + log::debug!(target: LOG_TARGET, "Migrating out crowdloan contribution for para_id: {:?}, contributor: {:?}, amount: {:?}, withdraw_block: {:?}", ¶_id, &contributor, &amount, &withdraw_block); + + messages.push(RcCrowdloanMessage::CrowdloanContribution { withdraw_block, contributor, para_id, amount: amount.into(), crowdloan_account }); + + inner_key // does not change since we deleted the contribution + }, + None => CrowdloanStage::CrowdloanContribution { last_key: Some(para_id) }, + } + }, + CrowdloanStage::CrowdloanReserve => { + match pallet_crowdloan::Funds::::iter().next() { + Some((para_id, fund)) => { + inner_key = CrowdloanStage::CrowdloanReserve; + pallet_crowdloan::Funds::::take(para_id); + + let leases = pallet_slots::Leases::::get(para_id); + if leases.is_empty() { + defensive_assert!(fund.raised == 0u32.into(), "Fund should be empty"); + continue; + } + let unreserve_block = num_leases_to_ending_block::(leases.len() as u32).defensive().map_err(|_| Error::::Unreachable)?; + + log::debug!(target: LOG_TARGET, "Migrating out crowdloan deposit for para_id: {:?}, fund_index: {:?}, amount: {:?}, depositor: {:?}", ¶_id, &fund.fund_index, &fund.deposit, &fund.depositor); + + messages.push(RcCrowdloanMessage::CrowdloanReserve { unreserve_block, para_id, amount: fund.deposit.into(), depositor: fund.depositor }); + inner_key + }, + None => CrowdloanStage::Finished, + } + }, + CrowdloanStage::Finished => break, + } + } + + if !messages.is_empty() { + Pallet::::send_chunked_xcm(messages, |messages| { + types::AhMigratorCall::::ReceiveCrowdloanMessages { messages } + })?; + } + + if inner_key == CrowdloanStage::Finished { + Ok(None) + } else { + Ok(Some(inner_key)) + } + } +} + +/// Calculate the lease ending block from the number of remaining leases (including the current). +/// +/// # Example +/// +/// We are in the middle of period 3 and there are 2 leases left: +/// |-0-|-1-|-2-|-3-|-4-|-5-| +/// ^-----^ +/// Then this function returns the end block number of period 4 (start block of period 5). +pub fn num_leases_to_ending_block(num_leases: u32) -> Result, ()> { + let now = frame_system::Pallet::::block_number(); + let num_leases: BlockNumberFor = num_leases.into(); + let offset = ::LeaseOffset::get(); + let period = ::LeasePeriod::get(); + + // Sanity check: + if now < offset { + return Err(()); + } + + // The current period: (now - offset) / period + let current_period = now.checked_sub(&offset).and_then(|x| x.checked_div(&period)).ok_or(())?; + // (current_period + num_leases) * period + offset + let last_period_end_block = current_period + .checked_add(&num_leases) + .and_then(|x| x.checked_mul(&period)) + .and_then(|x| x.checked_add(&offset)) + .ok_or(())?; + Ok(last_period_end_block) +} diff --git a/pallets/rc-migrator/src/lib.rs b/pallets/rc-migrator/src/lib.rs index ff2633a945..8031d38145 100644 --- a/pallets/rc-migrator/src/lib.rs +++ b/pallets/rc-migrator/src/lib.rs @@ -33,6 +33,7 @@ pub mod accounts; pub mod claims; +pub mod crowdloan; pub mod indices; pub mod multisig; pub mod preimage; @@ -66,7 +67,9 @@ use indices::IndicesMigrator; use multisig::MultisigMigrator; use pallet_balances::AccountData; use polkadot_parachain_primitives::primitives::Id as ParaId; -use polkadot_runtime_common::{claims as pallet_claims, paras_registrar}; +use polkadot_runtime_common::{ + claims as pallet_claims, crowdloan as pallet_crowdloan, paras_registrar, slots as pallet_slots, +}; use preimage::{ PreimageChunkMigrator, PreimageLegacyRequestStatusMigrator, PreimageRequestStatusMigrator, }; @@ -121,6 +124,8 @@ pub enum PalletEventName { BagsList, } +pub type BalanceOf = ::Balance; + #[derive(Encode, Decode, Clone, Default, RuntimeDebug, TypeInfo, MaxEncodedLen, PartialEq, Eq)] pub enum MigrationStage { @@ -225,16 +230,25 @@ pub enum MigrationStage>, }, ConvictionVotingMigrationDone, + BountiesMigrationInit, BountiesMigrationOngoing { last_key: Option, }, BountiesMigrationDone, + AssetRateMigrationInit, AssetRateMigrationOngoing { last_key: Option, }, AssetRateMigrationDone, + + CrowdloanMigrationInit, + CrowdloanMigrationOngoing { + last_key: Option, + }, + CrowdloanMigrationDone, + MigrationDone, } @@ -265,6 +279,7 @@ impl Result { Ok(match s { "skip-accounts" => MigrationStage::AccountsMigrationDone, + "crowdloan" => MigrationStage::CrowdloanMigrationInit, "preimage" => MigrationStage::PreimageMigrationInit, "referenda" => MigrationStage::ReferendaMigrationInit, "multisig" => MigrationStage::MultisigMigrationInit, @@ -279,7 +294,7 @@ impl = AccountInfo<::Nonce, ::AccountData>; -#[frame_support::pallet(dev_mode)] +#[frame_support::pallet] pub mod pallet { use super::*; @@ -309,6 +324,8 @@ pub mod pallet { + pallet_bounties::Config + pallet_treasury::Config + pallet_asset_rate::Config + + pallet_slots::Config + + pallet_crowdloan::Config { /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -343,8 +360,7 @@ pub mod pallet { #[pallet::error] pub enum Error { - /// The error that should to be replaced by something meaningful. - TODO, + Unreachable, OutOfWeight, /// Failed to send XCM message to AH. XcmError, @@ -393,7 +409,13 @@ pub mod pallet { pub struct Pallet(_); #[pallet::hooks] - impl Hooks> for Pallet { + impl Hooks> for Pallet + where + crate::BalanceOf: + From<<::Currency as frame_support::traits::Currency>::Balance>, + crate::BalanceOf: + From<<<::Auctioneer as polkadot_runtime_common::traits::Auctioneer<<<::Block as sp_runtime::traits::Block>::Header as sp_runtime::traits::Header>::Number>>::Currency as frame_support::traits::Currency>::Balance>, + { fn on_initialize(_: BlockNumberFor) -> Weight { let mut weight_counter = WeightMeter::with_limit(T::MaxRcWeight::get()); let stage = RcMigrationStage::::get(); @@ -989,9 +1011,40 @@ pub mod pallet { } }, MigrationStage::AssetRateMigrationDone => { - Self::transition(MigrationStage::MigrationDone); + Self::transition(MigrationStage::CrowdloanMigrationInit); + }, + MigrationStage::CrowdloanMigrationInit => { + Self::transition(MigrationStage::CrowdloanMigrationOngoing { last_key: None }); }, + MigrationStage::CrowdloanMigrationOngoing { last_key } => { + let res = with_transaction_opaque_err::, Error, _>(|| { + match crowdloan::CrowdloanMigrator::::migrate_many( + last_key, + &mut weight_counter, + ) { + Ok(last_key) => TransactionOutcome::Commit(Ok(last_key)), + Err(e) => TransactionOutcome::Rollback(Err(e)), + } + }) + .expect("Always returning Ok; qed"); + match res { + Ok(None) => { + Self::transition(MigrationStage::CrowdloanMigrationDone); + }, + Ok(Some(last_key)) => { + Self::transition(MigrationStage::CrowdloanMigrationOngoing { + last_key: Some(last_key), + }); + }, + e => { + defensive!("Error while migrating crowdloan: {:?}", e); + }, + } + }, + MigrationStage::CrowdloanMigrationDone => { + Self::transition(MigrationStage::MigrationDone); + }, MigrationStage::MigrationDone => (), }; diff --git a/pallets/rc-migrator/src/types.rs b/pallets/rc-migrator/src/types.rs index 98bb8e2e99..4c37abc563 100644 --- a/pallets/rc-migrator/src/types.rs +++ b/pallets/rc-migrator/src/types.rs @@ -76,6 +76,8 @@ pub enum AhMigratorCall { ReceiveBountiesMessages { messages: Vec> }, #[codec(index = 18)] ReceiveAssetRates { asset_rates: Vec<(::AssetKind, FixedU128)> }, + #[codec(index = 19)] + ReceiveCrowdloanMessages { messages: Vec> }, } /// Copy of `ParaInfo` type from `paras_registrar` pallet. diff --git a/system-parachains/asset-hubs/asset-hub-polkadot/Cargo.toml b/system-parachains/asset-hubs/asset-hub-polkadot/Cargo.toml index 02d0ea0f0b..2456c2b9a2 100644 --- a/system-parachains/asset-hubs/asset-hub-polkadot/Cargo.toml +++ b/system-parachains/asset-hubs/asset-hub-polkadot/Cargo.toml @@ -10,6 +10,7 @@ version.workspace = true [dependencies] pallet-ah-migrator = { workspace = true } +pallet-ah-ops = { workspace = true } codec = { features = ["derive", "max-encoded-len"], workspace = true } hex-literal = { optional = true, workspace = true } @@ -148,6 +149,7 @@ runtime-benchmarks = [ "frame-system-benchmarking/runtime-benchmarks", "frame-system/runtime-benchmarks", "hex-literal", + "pallet-ah-ops/runtime-benchmarks", "pallet-asset-conversion/runtime-benchmarks", "pallet-asset-rate/runtime-benchmarks", "pallet-assets/runtime-benchmarks", @@ -199,6 +201,7 @@ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "frame-try-runtime/try-runtime", + "pallet-ah-ops/try-runtime", "pallet-asset-conversion-tx-payment/try-runtime", "pallet-asset-conversion/try-runtime", "pallet-asset-rate/try-runtime", @@ -264,6 +267,7 @@ std = [ "frame-try-runtime?/std", "kusama-runtime-constants/std", "log/std", + "pallet-ah-ops/std", "pallet-asset-conversion-tx-payment/std", "pallet-asset-conversion/std", "pallet-asset-rate/std", diff --git a/system-parachains/asset-hubs/asset-hub-polkadot/src/lib.rs b/system-parachains/asset-hubs/asset-hub-polkadot/src/lib.rs index a0704070a8..f516de142e 100644 --- a/system-parachains/asset-hubs/asset-hub-polkadot/src/lib.rs +++ b/system-parachains/asset-hubs/asset-hub-polkadot/src/lib.rs @@ -1118,6 +1118,12 @@ impl pallet_claims::Config for Runtime { type WeightInfo = pallet_claims::TestWeightInfo; // TODOweights::polkadot_runtime_common_claims::WeightInfo; } +impl pallet_ah_ops::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type RcBlockNumberProvider = RelaychainDataProvider; +} + impl pallet_ah_migrator::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Balances; @@ -1184,7 +1190,7 @@ construct_runtime!( PoolAssets: pallet_assets:: = 54, AssetConversion: pallet_asset_conversion = 55, - // OpenGov stuff. + // OpenGov stuff Treasury: pallet_treasury = 60, ConvictionVoting: pallet_conviction_voting = 61, Referenda: pallet_referenda = 62, @@ -1199,7 +1205,8 @@ construct_runtime!( FastUnstake: pallet_fast_unstake = 71, VoterList: pallet_bags_list:: = 72, - // Asset Hub Migrator + // Asset Hub Migration in the 250s + AhOps: pallet_ah_ops = 254, AhMigrator: pallet_ah_migrator = 255, } );