diff --git a/Cargo.lock b/Cargo.lock index 0353bf239a..427ff7be4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -789,7 +789,10 @@ dependencies = [ "pallet-aura", "pallet-authorship", "pallet-balances", + "pallet-bounties", + "pallet-child-bounties", "pallet-collator-selection", + "pallet-conviction-voting", "pallet-message-queue", "pallet-multisig", "pallet-nfts", @@ -797,13 +800,17 @@ dependencies = [ "pallet-nomination-pools", "pallet-preimage", "pallet-proxy", + "pallet-referenda", + "pallet-scheduler", "pallet-session", "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", + "pallet-treasury", "pallet-uniques", "pallet-utility", "pallet-vesting", + "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub-router", @@ -819,6 +826,7 @@ dependencies = [ "serde_json", "snowbridge-router-primitives", "sp-api", + "sp-arithmetic 26.0.0", "sp-block-builder", "sp-consensus-aura", "sp-core 34.0.0", @@ -839,6 +847,7 @@ dependencies = [ "staging-xcm-builder", "staging-xcm-executor", "substrate-wasm-builder", + "system-parachains-common", "system-parachains-constants", "xcm-runtime-apis", ] @@ -7642,6 +7651,8 @@ dependencies = [ "pallet-preimage", "pallet-proxy", "pallet-rc-migrator", + "pallet-referenda", + "pallet-scheduler", "pallet-staking", "pallet-state-trie-migration", "parity-scale-codec", @@ -8870,6 +8881,7 @@ dependencies = [ "pallet-nomination-pools", "pallet-preimage", "pallet-proxy", + "pallet-referenda", "pallet-staking", "parity-scale-codec", "polkadot-parachain-primitives", @@ -15089,6 +15101,20 @@ dependencies = [ "libc", ] +[[package]] +name = "system-parachains-common" +version = "1.0.0" +dependencies = [ + "frame-support", + "parity-scale-codec", + "polkadot-runtime-common", + "scale-info", + "sp-api", + "sp-runtime 39.0.5", + "staging-xcm", + "staging-xcm-executor", +] + [[package]] name = "system-parachains-constants" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index d87b50899e..698043f186 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -241,6 +241,7 @@ sp-weights = { version = "31.0.0", default-features = false } static_assertions = { version = "1.1.0" } substrate-wasm-builder = { version = "24.0.1" } system-parachains-constants = { path = "system-parachains/constants", default-features = false } +system-parachains-common = { path = "system-parachains/common", default-features = false } tokio = { version = "1.36.0" } xcm = { version = "14.2.0", default-features = false, package = "staging-xcm" } xcm-builder = { version = "17.0.1", default-features = false, package = "staging-xcm-builder" } diff --git a/integration-tests/ahm/src/tests.rs b/integration-tests/ahm/src/tests.rs index 684dbc3788..2f0ae4a36e 100644 --- a/integration-tests/ahm/src/tests.rs +++ b/integration-tests/ahm/src/tests.rs @@ -50,6 +50,12 @@ async fn account_migration_works() { // Simulate relay blocks and grab the DMP messages let (dmp_messages, pre_check_payload) = rc.execute_with(|| { let mut dmps = Vec::new(); + + if let Ok(stage) = std::env::var("START_STAGE") { + let stage = state_from_str::(&stage); + RcMigrationStage::::put(stage); + } + let pre_check_payload = pallet_rc_migrator::preimage::PreimageChunkMigrator::::pre_check(); @@ -96,13 +102,24 @@ async fn account_migration_works() { next_block_ah(); } - pallet_rc_migrator::preimage::PreimageChunkMigrator::::post_check( - pre_check_payload, - ); - pallet_ah_migrator::preimage::PreimageMigrationCheck::::post_check( - ah_pre_check_payload, - ); + // pallet_rc_migrator::preimage::PreimageChunkMigrator::::post_check( + // pre_check_payload, + // ); + // pallet_ah_migrator::preimage::PreimageMigrationCheck::::post_check( + // ah_pre_check_payload, + // ); // NOTE that the DMP queue is probably not empty because the snapshot that we use contains // some overweight ones. }); } + +pub fn state_from_str( + s: &str, +) -> pallet_rc_migrator::MigrationStageOf { + use pallet_rc_migrator::MigrationStage; + match s { + "preimage" => MigrationStage::PreimageMigrationInit, + "referenda" => MigrationStage::ReferendaMigrationInit, + _ => MigrationStage::Pending, + } +} diff --git a/pallets/ah-migrator/Cargo.toml b/pallets/ah-migrator/Cargo.toml index 0629a6cd4e..c17cd603ac 100644 --- a/pallets/ah-migrator/Cargo.toml +++ b/pallets/ah-migrator/Cargo.toml @@ -19,6 +19,8 @@ pallet-nomination-pools = { workspace = true } pallet-preimage = { workspace = true } pallet-proxy = { workspace = true } pallet-rc-migrator = { workspace = true } +pallet-referenda = { workspace = true } +pallet-scheduler = { workspace = true } pallet-staking = { workspace = true } pallet-state-trie-migration = { workspace = true } polkadot-parachain-primitives = { workspace = true } @@ -47,6 +49,8 @@ std = [ "pallet-preimage/std", "pallet-proxy/std", "pallet-rc-migrator/std", + "pallet-referenda/std", + "pallet-scheduler/std", "pallet-staking/std", "pallet-state-trie-migration/std", "polkadot-parachain-primitives/std", @@ -70,6 +74,8 @@ runtime-benchmarks = [ "pallet-preimage/runtime-benchmarks", "pallet-proxy/runtime-benchmarks", "pallet-rc-migrator/runtime-benchmarks", + "pallet-referenda/runtime-benchmarks", + "pallet-scheduler/runtime-benchmarks", "pallet-staking/runtime-benchmarks", "pallet-state-trie-migration/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", @@ -86,6 +92,8 @@ try-runtime = [ "pallet-preimage/try-runtime", "pallet-proxy/try-runtime", "pallet-rc-migrator/try-runtime", + "pallet-referenda/try-runtime", + "pallet-scheduler/try-runtime", "pallet-staking/try-runtime", "pallet-state-trie-migration/try-runtime", "polkadot-runtime-common/try-runtime", diff --git a/pallets/ah-migrator/src/lib.rs b/pallets/ah-migrator/src/lib.rs index 4839178610..84a8a027cf 100644 --- a/pallets/ah-migrator/src/lib.rs +++ b/pallets/ah-migrator/src/lib.rs @@ -35,6 +35,7 @@ pub mod account; pub mod multisig; pub mod preimage; pub mod proxy; +pub mod referenda; pub mod staking; pub mod types; @@ -45,7 +46,8 @@ use frame_support::{ storage::{transactional::with_transaction_opaque_err, TransactionOutcome}, traits::{ fungible::{InspectFreeze, Mutate, MutateFreeze, MutateHold}, - Defensive, LockableCurrency, ReservableCurrency, WithdrawReasons as LockWithdrawReasons, + Defensive, DefensiveResult, LockableCurrency, OriginTrait, QueryPreimage, + ReservableCurrency, WithdrawReasons as LockWithdrawReasons, }, }; use frame_system::pallet_prelude::*; @@ -53,6 +55,8 @@ use pallet_balances::{AccountData, Reasons as LockReasons}; use pallet_rc_migrator::{ accounts::Account as RcAccount, multisig::*, preimage::*, proxy::*, staking::nom_pools::*, }; +use pallet_referenda::TrackIdOf; +use referenda::RcReferendumInfoOf; use sp_application_crypto::Ss58Codec; use sp_core::H256; use sp_runtime::{ @@ -84,6 +88,7 @@ pub mod pallet { + pallet_multisig::Config + pallet_proxy::Config + pallet_preimage::Config + + pallet_referenda::Config + pallet_nomination_pools::Config { /// The overarching event type. @@ -116,6 +121,17 @@ pub mod pallet { type RcToAhDelay: Convert, BlockNumberFor>; /// Access the block number of the Relay Chain. type RcBlockNumberProvider: BlockNumberProvider>; + /// Some part of the Relay Chain origins used in Governance. + type RcPalletsOrigin: Parameter; + /// Convert a Relay Chain origin to an Asset Hub one. + type RcToAhPalletsOrigin: TryConvert< + Self::RcPalletsOrigin, + ::PalletsOrigin, + >; + /// Preimage registry. + type Preimage: QueryPreimage::Hashing>; + /// Convert a Relay Chain Call to a local AH one. + type RcToAhCall: for<'a> TryConvert<&'a [u8], ::RuntimeCall>; } /// RC accounts that failed to migrate when were received on the Asset Hub. @@ -132,6 +148,10 @@ pub mod pallet { FailedToUnreserveDeposit, /// Failed to process an account data from RC. FailedToProcessAccount, + /// Failed to convert RC type to AH type. + FailedToConvertType, + /// Failed to fetch preimage. + PreimageNotFound, } #[pallet::event] @@ -234,6 +254,19 @@ pub mod pallet { /// How many nom pools messages failed to integrate. count_bad: u32, }, + /// We received a batch of referendums that we are going to integrate. + ReferendumsBatchReceived { + /// How many referendums are in the batch. + count: u32, + }, + /// We processed a batch of referendums that we received. + ReferendumsBatchProcessed { + /// How many referendums were successfully integrated. + count_good: u32, + /// How many referendums failed to integrate. + count_bad: u32, + }, + ReferendaProcessed, } #[pallet::pallet] @@ -335,6 +368,33 @@ pub mod pallet { Self::do_receive_nom_pools_messages(messages).map_err(Into::into) } + + /// Receive referendum counts, deciding counts, votes for the track queue. + #[pallet::call_index(8)] + pub fn receive_referenda_values( + origin: OriginFor, + referendum_count: u32, + // track_id, count + deciding_count: Vec<(TrackIdOf, u32)>, + // referendum_id, votes + track_queue: Vec<(TrackIdOf, Vec<(u32, u128)>)>, + ) -> DispatchResult { + ensure_root(origin)?; + + Self::do_receive_referenda_values(referendum_count, deciding_count, track_queue) + .map_err(Into::into) + } + + /// Receive referendums from the Relay Chain. + #[pallet::call_index(9)] + pub fn receive_referendums( + origin: OriginFor, + referendums: Vec<(u32, RcReferendumInfoOf)>, + ) -> DispatchResult { + ensure_root(origin)?; + + Self::do_receive_referendums(referendums).map_err(Into::into) + } } #[pallet::hooks] diff --git a/pallets/ah-migrator/src/referenda.rs b/pallets/ah-migrator/src/referenda.rs new file mode 100644 index 0000000000..facab33709 --- /dev/null +++ b/pallets/ah-migrator/src/referenda.rs @@ -0,0 +1,224 @@ +// 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 . + +use crate::*; +use frame_support::traits::{schedule::v3::Anon, Bounded, BoundedInline, DefensiveTruncateFrom}; +use pallet_referenda::{ + BalanceOf, BoundedCallOf, DecidingCount, MetadataOf, ReferendumCount, ReferendumInfo, + ReferendumInfoFor, ReferendumInfoOf, ReferendumStatus, ReferendumStatusOf, ScheduleAddressOf, + TallyOf, TrackIdOf, TrackQueue, +}; + +/// ReferendumInfoOf for RC. +/// +/// The `RuntimeOrigin` is a type argument that needs to be mapped to AH `RuntimeOrigin`. +/// Inline `proposal`s and the ones stored by `Preimage` pallet should also be mapped to get the +/// final local `pallet_referenda::ReferendumInfoFor::`. +/// +/// Reflects: `pallet_referenda::ReferendumInfoOf::`. +pub type RcReferendumInfoOf = ReferendumInfo< + TrackIdOf, + ::RcPalletsOrigin, + BlockNumberFor, + BoundedCallOf, + BalanceOf, + TallyOf, + ::AccountId, + ScheduleAddressOf, +>; + +/// RcReferendumStatusOf for RC. +/// +/// Reflects: `pallet_referenda::ReferendumStatusOf::`. +pub type RcReferendumStatusOf = ReferendumStatus< + TrackIdOf, + ::RcPalletsOrigin, + BlockNumberFor, + BoundedCallOf, + BalanceOf, + TallyOf, + ::AccountId, + ScheduleAddressOf, +>; + +impl Pallet { + pub fn do_receive_referendums( + referendums: Vec<(u32, RcReferendumInfoOf)>, + ) -> Result<(), Error> { + log::info!(target: LOG_TARGET, "Integrating {} referendums", referendums.len()); + Self::deposit_event(Event::ReferendumsBatchReceived { count: referendums.len() as u32 }); + let (mut count_good, mut count_bad) = (0, 0); + + for (id, referendum) in referendums { + match Self::do_receive_referendum(id, referendum) { + Ok(()) => count_good += 1, + Err(_) => count_bad += 1, + } + } + + Self::deposit_event(Event::ReferendumsBatchProcessed { count_good, count_bad }); + log::info!(target: LOG_TARGET, "Processed {} referendums", count_good); + + Ok(()) + } + + pub fn do_receive_referendum( + id: u32, + referendum: RcReferendumInfoOf, + ) -> Result<(), Error> { + log::debug!(target: LOG_TARGET, "Integrating referendum {}", id); + + let referendum: ReferendumInfoOf = match referendum { + ReferendumInfo::Ongoing(status) => { + let cancel_referendum = |id, status: RcReferendumStatusOf| { + if let Some((_, last_alarm)) = status.alarm { + // TODO: scheduler migrated first? + let _ = T::Scheduler::cancel(last_alarm); + } + // TODO: use referenda block provider + let now = frame_system::Pallet::::block_number(); + ReferendumInfoFor::::insert( + id, + ReferendumInfo::Cancelled( + now, + Some(status.submission_deposit), + status.decision_deposit, + ), + ); + log::info!("Referendum {} cancelled", id); + }; + + let origin = match T::RcToAhPalletsOrigin::try_convert(status.origin.clone()) { + Ok(origin) => origin, + Err(_) => { + defensive!( + "Failed to convert RC origin to AH origin for referendum {}", + id + ); + cancel_referendum(id, status); + return Ok(()); + }, + }; + + let encoded_call = if let Ok(e) = Self::fetch_preimage(&status.proposal) { + e + } else { + log::warn!("Failed to fetch preimage for referendum {}", id); + cancel_referendum(id, status); + return Ok(()); + }; + + let call = if let Ok(call) = T::RcToAhCall::try_convert(&encoded_call) { + call + } else { + // TODO: replace with defensive if we expect all referendum calls to be mapped. + log::warn!("Failed to convert RC call to AH call for referendum {}", id); + cancel_referendum(id, status); + return Ok(()); + }; + + let inline = if let Ok(i) = BoundedInline::try_from(call.encode()) { + i + } else { + defensive!("Call encoded length is too large for inline encoding"); + // TODO: if we have such a case we would need to dispatch two call on behalf of + // the original preimage submitter to release the funds for the new preimage + // deposit and dispatch the call to note a new preimage. or we provide a + // better sdk for this case. + cancel_referendum(id, status); + return Ok(()); + }; + + let status = ReferendumStatusOf:: { + track: status.track, + origin, + proposal: Bounded::Inline(inline), + enactment: status.enactment, + submitted: status.submitted, + submission_deposit: status.submission_deposit, + decision_deposit: status.decision_deposit, + deciding: status.deciding, + tally: status.tally, + in_queue: status.in_queue, + alarm: status.alarm, + }; + + ReferendumInfo::Ongoing(status) + }, + ReferendumInfo::Approved(a, b, c) => ReferendumInfo::Approved(a, b, c), + ReferendumInfo::Rejected(a, b, c) => ReferendumInfo::Rejected(a, b, c), + ReferendumInfo::Cancelled(a, b, c) => ReferendumInfo::Cancelled(a, b, c), + ReferendumInfo::TimedOut(a, b, c) => ReferendumInfo::TimedOut(a, b, c), + ReferendumInfo::Killed(a) => ReferendumInfo::Killed(a), + }; + + ReferendumInfoFor::::insert(id, referendum); + + log::debug!(target: LOG_TARGET, "Referendum {} integrated", id); + + Ok(()) + } + + pub fn do_receive_referenda_values( + referendum_count: u32, + deciding_count: Vec<(TrackIdOf, u32)>, + track_queue: Vec<(TrackIdOf, Vec<(u32, u128)>)>, + ) -> Result<(), Error> { + log::info!(target: LOG_TARGET, "Integrating referenda pallet"); + + ReferendumCount::::put(referendum_count); + deciding_count.iter().for_each(|(track_id, count)| { + DecidingCount::::insert(track_id, count); + }); + track_queue.into_iter().for_each(|(track_id, queue)| { + let queue = BoundedVec::<_, T::MaxQueued>::defensive_truncate_from(queue); + TrackQueue::::insert(track_id, queue); + }); + + Self::deposit_event(Event::ReferendaProcessed); + log::info!(target: LOG_TARGET, "Referenda pallet integrated"); + Ok(()) + } + + fn fetch_preimage(bounded_call: &BoundedCallOf) -> Result, Error> { + match bounded_call { + Bounded::Inline(encoded) => Ok(encoded.clone().into_inner()), + Bounded::Legacy { hash, .. } => { + let encoded = if let Ok(encoded) = T::Preimage::fetch(hash, None) { + encoded + } else { + // not an error since a submitter can delete the preimage for ongoing referendum + log::warn!("No preimage found for call hash: {:?}", hash); + return Err(Error::::PreimageNotFound); + }; + Ok(encoded.into_owned()) + }, + Bounded::Lookup { hash, len } => { + let encoded = if let Ok(encoded) = T::Preimage::fetch(hash, Some(*len)) { + encoded + } else { + // not an error since a submitter can delete the preimage for ongoing referendum + log::warn!("No preimage found for call hash: {:?}", (hash, len)); + return Err(Error::::PreimageNotFound); + }; + Ok(encoded.into_owned()) + }, + } + } +} + +// TODO: shift referendums' time block by the time of the migration +// TODO: schedule `one_fewer_deciding` for referendums canceled during migration diff --git a/pallets/rc-migrator/Cargo.toml b/pallets/rc-migrator/Cargo.toml index 71c0df6393..9b71fc51d3 100644 --- a/pallets/rc-migrator/Cargo.toml +++ b/pallets/rc-migrator/Cargo.toml @@ -24,6 +24,7 @@ pallet-staking = { workspace = true } pallet-proxy = { workspace = true } pallet-multisig = { workspace = true } pallet-preimage = { workspace = true } +pallet-referenda = { workspace = true } pallet-nomination-pools = { workspace = true } polkadot-runtime-common = { workspace = true } runtime-parachains = { workspace = true } @@ -45,6 +46,7 @@ std = [ "pallet-nomination-pools/std", "pallet-preimage/std", "pallet-proxy/std", + "pallet-referenda/std", "pallet-staking/std", "polkadot-parachain-primitives/std", "polkadot-runtime-common/std", @@ -67,6 +69,7 @@ runtime-benchmarks = [ "pallet-nomination-pools/runtime-benchmarks", "pallet-preimage/runtime-benchmarks", "pallet-proxy/runtime-benchmarks", + "pallet-referenda/runtime-benchmarks", "pallet-staking/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-runtime-common/runtime-benchmarks", @@ -82,6 +85,7 @@ try-runtime = [ "pallet-nomination-pools/try-runtime", "pallet-preimage/try-runtime", "pallet-proxy/try-runtime", + "pallet-referenda/try-runtime", "pallet-staking/try-runtime", "polkadot-runtime-common/try-runtime", "runtime-parachains/try-runtime", diff --git a/pallets/rc-migrator/src/lib.rs b/pallets/rc-migrator/src/lib.rs index ea81c7c432..c9d5417afe 100644 --- a/pallets/rc-migrator/src/lib.rs +++ b/pallets/rc-migrator/src/lib.rs @@ -35,6 +35,7 @@ pub mod accounts; pub mod multisig; pub mod preimage; pub mod proxy; +pub mod referenda; pub mod staking; pub mod types; mod weights; @@ -70,6 +71,7 @@ use preimage::{ PreimageChunkMigrator, PreimageLegacyRequestStatusMigrator, PreimageRequestStatusMigrator, }; use proxy::*; +use referenda::ReferendaStage; use staking::nom_pools::{NomPoolsMigrator, NomPoolsStage}; use types::PalletMigration; @@ -98,6 +100,8 @@ impl From for Error { } } +pub type MigrationStageOf = MigrationStage<::AccountId>; + #[derive(Encode, Decode, Clone, Default, RuntimeDebug, TypeInfo, MaxEncodedLen, PartialEq, Eq)] pub enum MigrationStage { /// The migration has not yet started but will start in the next block. @@ -153,9 +157,16 @@ pub enum MigrationStage { next_key: Option>, }, NomPoolsMigrationDone, + ReferendaMigrationInit, + ReferendaMigrationOngoing { + last_key: Option, + }, + ReferendaMigrationDone, MigrationDone, } +pub type MigrationStageFor = MigrationStage<::AccountId>; + impl MigrationStage { /// Whether the migration is finished. /// @@ -193,6 +204,7 @@ pub mod pallet { + pallet_multisig::Config + pallet_proxy::Config + pallet_preimage::Config + + pallet_referenda::Config + pallet_nomination_pools::Config { /// The overarching event type. @@ -569,6 +581,42 @@ pub mod pallet { } }, MigrationStage::NomPoolsMigrationDone => { + Self::transition(MigrationStage::ReferendaMigrationInit); + }, + MigrationStage::ReferendaMigrationInit => { + Self::transition(MigrationStage::ReferendaMigrationOngoing { + last_key: Some(Default::default()), + }); + }, + MigrationStage::ReferendaMigrationOngoing { last_key } => { + let res = + with_transaction_opaque_err::, Error, _>(|| { + match referenda::ReferendaMigrator::::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::ReferendaMigrationDone); + }, + Ok(Some(last_key)) => { + Self::transition(MigrationStage::ReferendaMigrationOngoing { + last_key: Some(last_key), + }); + }, + Err(err) => { + defensive!("Error while migrating referenda: {:?}", err); + log::error!(target: LOG_TARGET, "Error while migrating referenda: {:?}", err); + }, + } + }, + MigrationStage::ReferendaMigrationDone => { Self::transition(MigrationStage::MigrationDone); }, MigrationStage::MigrationDone => (), @@ -641,6 +689,35 @@ pub mod pallet { Ok(()) } + + /// Send a single XCM message. + pub fn send_xcm(call: types::AhMigratorCall) -> Result<(), Error> { + log::info!(target: LOG_TARGET, "Sending XCM message"); + + let call = types::AssetHubPalletConfig::::AhmController(call); + + let message = Xcm(vec![ + Instruction::UnpaidExecution { + weight_limit: WeightLimit::Unlimited, + check_origin: None, + }, + Instruction::Transact { + origin_kind: OriginKind::Superuser, + require_weight_at_most: Weight::from_all(1), // TODO + call: call.encode().into(), + }, + ]); + + if let Err(err) = send_xcm::( + Location::new(0, [Junction::Parachain(1000)]), + message.clone(), + ) { + log::error!(target: LOG_TARGET, "Error while sending XCM message: {:?}", err); + return Err(Error::XcmError); + }; + + Ok(()) + } } } diff --git a/pallets/rc-migrator/src/referenda.rs b/pallets/rc-migrator/src/referenda.rs new file mode 100644 index 0000000000..0ce33e3496 --- /dev/null +++ b/pallets/rc-migrator/src/referenda.rs @@ -0,0 +1,157 @@ +// 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 . + +use crate::*; +use pallet_referenda::{ + DecidingCount, MetadataOf, ReferendumCount, ReferendumInfoFor, TrackIdOf, TrackQueue, +}; + +/// The stages of the referenda pallet migration. +#[derive(Encode, Decode, Clone, Default, RuntimeDebug, TypeInfo, MaxEncodedLen, PartialEq, Eq)] +pub enum ReferendaStage { + #[default] + StorageValues, + ReferendumInfo(Option), +} + +pub struct ReferendaMigrator { + _phantom: sp_std::marker::PhantomData, +} + +impl PalletMigration for ReferendaMigrator { + type Key = ReferendaStage; + type Error = Error; + + fn migrate_many( + mut last_key: Option, + weight_counter: &mut WeightMeter, + ) -> Result, Self::Error> { + let stage = match last_key { + None | Some(ReferendaStage::StorageValues) => { + let _ = Self::migrate_values(weight_counter)?; + Some(ReferendaStage::ReferendumInfo(None)) + }, + Some(ReferendaStage::ReferendumInfo(last_key)) => + match Self::migrate_many_referendum_info(last_key, weight_counter)? { + Some(last_key) => Some(ReferendaStage::ReferendumInfo(Some(last_key))), + None => None, + }, + }; + Ok(stage) + } +} + +impl ReferendaMigrator { + fn migrate_values(_weight_counter: &mut WeightMeter) -> Result<(), Error> { + defensive_assert!( + MetadataOf::::iter_keys().next().is_none(), + "Referenda metadata is not empty" + ); + + let referendum_count = ReferendumCount::::take(); + + const TRACKS_COUNT: usize = 16; + + // track_id, count + let deciding_count = DecidingCount::::iter().drain().collect::>(); + defensive_assert!( + deciding_count.len() <= TRACKS_COUNT, + "Deciding count unexpectedly large" + ); + + // (track_id, vec<(referendum_id, votes)>) + let track_queue = TrackQueue::::iter() + .drain() + .map(|(track_id, queue)| (track_id, queue.into_inner())) + .collect::>(); + defensive_assert!(track_queue.len() <= TRACKS_COUNT, "Track queue unexpectedly large"); + + Pallet::::send_xcm(types::AhMigratorCall::::ReceiveReferendaValues { + referendum_count, + deciding_count, + track_queue, + })?; + + Ok(()) + } + + fn migrate_many_referendum_info( + mut last_key: Option, + weight_counter: &mut WeightMeter, + ) -> Result, Error> { + // we should not send more than AH can handle within the block. + let mut ah_weight_counter = WeightMeter::with_limit(T::MaxAhWeight::get()); + + let mut batch = Vec::new(); + + // TODO: account transport/XCM weight. + + let last_key = loop { + if weight_counter.try_consume(T::DbWeight::get().reads_writes(1, 1)).is_err() { + if batch.is_empty() { + defensive!("Out of weight too early"); + return Err(Error::OutOfWeight); + } else { + break last_key; + } + } + + // TODO: replace by the actual weight. + if ah_weight_counter.try_consume(Weight::from_all(1)).is_err() { + if batch.is_empty() { + defensive!("Out of weight too early"); + return Err(Error::OutOfWeight); + } else { + break last_key; + } + } + + let next_key = match last_key { + Some(last_key) => { + let Some(next_key) = + ReferendumInfoFor::::iter_keys_from_key(last_key).next() + else { + break None; + }; + next_key + }, + None => { + let Some(next_key) = ReferendumInfoFor::::iter_keys().next() else { + break None; + }; + next_key + }, + }; + + let Some(info) = pallet_referenda::ReferendumInfoFor::::take(&next_key) else { + defensive!("ReferendumInfoFor is empty"); + last_key = ReferendumInfoFor::::iter_keys_from_key(next_key).next(); + continue; + }; + + batch.push((next_key, info)); + last_key = Some(next_key); + }; + + if !batch.is_empty() { + Pallet::::send_chunked_xcm(batch, |batch| { + types::AhMigratorCall::::ReceiveReferendums { referendums: batch } + })?; + } + + Ok(last_key) + } +} diff --git a/pallets/rc-migrator/src/types.rs b/pallets/rc-migrator/src/types.rs index ddbd6a8135..a8d3947351 100644 --- a/pallets/rc-migrator/src/types.rs +++ b/pallets/rc-migrator/src/types.rs @@ -17,6 +17,7 @@ //! Types use super::*; +use pallet_referenda::{ReferendumInfoOf, TrackIdOf}; pub type AccountIdOf = ::AccountId; @@ -46,6 +47,14 @@ pub enum AhMigratorCall { ReceivePreimageLegacyStatus { legacy_status: Vec> }, #[codec(index = 7)] ReceiveNomPoolsMessages { messages: Vec> }, + #[codec(index = 8)] + ReceiveReferendaValues { + referendum_count: u32, + deciding_count: Vec<(TrackIdOf, u32)>, + track_queue: Vec<(TrackIdOf, Vec<(u32, u128)>)>, + }, + #[codec(index = 9)] + ReceiveReferendums { referendums: Vec<(u32, ReferendumInfoOf)> }, } /// 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 4d8050a7ee..618c0c5cf4 100644 --- a/system-parachains/asset-hubs/asset-hub-polkadot/Cargo.toml +++ b/system-parachains/asset-hubs/asset-hub-polkadot/Cargo.toml @@ -25,6 +25,7 @@ bp-bridge-hub-polkadot = { workspace = true } collectives-polkadot-runtime-constants = { workspace = true } kusama-runtime-constants = { workspace = true } polkadot-runtime-constants = { workspace = true } +system-parachains-constants = { workspace = true } # Substrate frame-benchmarking = { optional = true, workspace = true } @@ -43,20 +44,28 @@ pallet-assets = { workspace = true } pallet-aura = { workspace = true } pallet-authorship = { workspace = true } pallet-balances = { workspace = true } +pallet-bounties = { workspace = true } +pallet-child-bounties = { workspace = true } +pallet-conviction-voting = { workspace = true } pallet-message-queue = { workspace = true } pallet-multisig = { workspace = true } pallet-nfts = { workspace = true } pallet-nfts-runtime-api = { workspace = true } pallet-preimage = { workspace = true } pallet-proxy = { workspace = true } +pallet-referenda = { workspace = true } +pallet-scheduler = { workspace = true } pallet-session = { workspace = true } pallet-timestamp = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true } +pallet-treasury = { workspace = true } pallet-uniques = { workspace = true } pallet-utility = { workspace = true } pallet-vesting = { workspace = true } +pallet-whitelist = { workspace = true } sp-api = { workspace = true } +sp-arithmetic = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } sp-core = { workspace = true } @@ -101,7 +110,7 @@ cumulus-primitives-utility = { workspace = true } pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } parachains-common = { workspace = true } -system-parachains-constants = { workspace = true } +system-parachains-common = { workspace = true } assets-common = { workspace = true } # Bridges @@ -135,23 +144,31 @@ runtime-benchmarks = [ "pallet-asset-conversion/runtime-benchmarks", "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", + "pallet-bounties/runtime-benchmarks", + "pallet-child-bounties/runtime-benchmarks", "pallet-collator-selection/runtime-benchmarks", + "pallet-conviction-voting/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", "pallet-nfts/runtime-benchmarks", "pallet-nomination-pools/runtime-benchmarks", "pallet-preimage/runtime-benchmarks", "pallet-proxy/runtime-benchmarks", + "pallet-referenda/runtime-benchmarks", + "pallet-scheduler/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", + "pallet-treasury/runtime-benchmarks", "pallet-uniques/runtime-benchmarks", "pallet-utility/runtime-benchmarks", "pallet-vesting/runtime-benchmarks", + "pallet-whitelist/runtime-benchmarks", "pallet-xcm-benchmarks/runtime-benchmarks", "pallet-xcm-bridge-hub-router/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "parachains-common/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-runtime-common/runtime-benchmarks", + "system-parachains-common/runtime-benchmarks", "snowbridge-router-primitives/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "sp-staking/runtime-benchmarks", @@ -176,23 +193,31 @@ try-runtime = [ "pallet-aura/try-runtime", "pallet-authorship/try-runtime", "pallet-balances/try-runtime", + "pallet-bounties/try-runtime", + "pallet-child-bounties/try-runtime", "pallet-collator-selection/try-runtime", + "pallet-conviction-voting/try-runtime", "pallet-message-queue/try-runtime", "pallet-multisig/try-runtime", "pallet-nfts/try-runtime", "pallet-nomination-pools/try-runtime", "pallet-preimage/try-runtime", "pallet-proxy/try-runtime", + "pallet-referenda/try-runtime", + "pallet-scheduler/try-runtime", "pallet-session/try-runtime", "pallet-timestamp/try-runtime", "pallet-transaction-payment/try-runtime", + "pallet-treasury/try-runtime", "pallet-uniques/try-runtime", "pallet-utility/try-runtime", "pallet-vesting/try-runtime", + "pallet-whitelist/try-runtime", "pallet-xcm-bridge-hub-router/try-runtime", "pallet-xcm/try-runtime", "parachain-info/try-runtime", "polkadot-runtime-common/try-runtime", + "system-parachains-common/try-runtime", "sp-runtime/try-runtime", ] std = [ @@ -228,7 +253,10 @@ std = [ "pallet-aura/std", "pallet-authorship/std", "pallet-balances/std", + "pallet-bounties/std", + "pallet-child-bounties/std", "pallet-collator-selection/std", + "pallet-conviction-voting/std", "pallet-message-queue/std", "pallet-multisig/std", "pallet-nfts-runtime-api/std", @@ -236,13 +264,17 @@ std = [ "pallet-nomination-pools/std", "pallet-preimage/std", "pallet-proxy/std", + "pallet-referenda/std", + "pallet-scheduler/std", "pallet-session/std", "pallet-timestamp/std", "pallet-transaction-payment-rpc-runtime-api/std", "pallet-transaction-payment/std", + "pallet-treasury/std", "pallet-uniques/std", "pallet-utility/std", "pallet-vesting/std", + "pallet-whitelist/std", "pallet-xcm-benchmarks?/std", "pallet-xcm-bridge-hub-router/std", "pallet-xcm/std", @@ -257,6 +289,7 @@ std = [ "serde_json/std", "snowbridge-router-primitives/std", "sp-api/std", + "sp-arithmetic/std", "sp-block-builder/std", "sp-consensus-aura/std", "sp-core/std", @@ -274,6 +307,7 @@ std = [ "sp-weights/std", "substrate-wasm-builder", "system-parachains-constants/std", + "system-parachains-common/std", "xcm-builder/std", "xcm-executor/std", "xcm-runtime-apis/std", diff --git a/system-parachains/asset-hubs/asset-hub-polkadot/src/governance/mod.rs b/system-parachains/asset-hubs/asset-hub-polkadot/src/governance/mod.rs new file mode 100644 index 0000000000..92b9202d5e --- /dev/null +++ b/system-parachains/asset-hubs/asset-hub-polkadot/src/governance/mod.rs @@ -0,0 +1,97 @@ +// 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 . + +//! Governance configurations for the Asset Hub runtime. + +use super::*; +use crate::xcm_config::FellowshipLocation; +use frame_system::EnsureRootWithSuccess; +use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; +use xcm::latest::BodyId; + +mod origins; +pub use origins::{ + pallet_custom_origins, AuctionAdmin, FellowshipAdmin, GeneralAdmin, LeaseAdmin, + ReferendumCanceller, ReferendumKiller, Spender, StakingAdmin, Treasurer, WhitelistedCaller, +}; +mod tracks; +pub use tracks::TracksInfo; + +parameter_types! { + pub const VoteLockingPeriod: BlockNumber = prod_or_fast!(7 * RC_DAYS, 1); +} + +impl pallet_conviction_voting::Config for Runtime { + type WeightInfo = (); // TODO: weights::pallet_conviction_voting::WeightInfo; + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type VoteLockingPeriod = VoteLockingPeriod; + type MaxVotes = ConstU32<512>; // TODO check with weight + // TODO: review + type MaxTurnout = + frame_support::traits::tokens::currency::ActiveIssuanceOf; + type Polls = Referenda; +} + +parameter_types! { + pub const AlarmInterval: BlockNumber = 1; + pub const SubmissionDeposit: Balance = DOLLARS; + pub const UndecidingTimeout: BlockNumber = 14 * RC_DAYS; +} + +parameter_types! { + pub const MaxBalance: Balance = Balance::MAX; +} +pub type TreasurySpender = EitherOf, Spender>; + +impl origins::pallet_custom_origins::Config for Runtime {} + +parameter_types! { + // Fellows pluralistic body. + pub const FellowsBodyId: BodyId = BodyId::Technical; +} + +impl pallet_whitelist::Config for Runtime { + type WeightInfo = (); // TODO: weights::pallet_whitelist::WeightInfo; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type WhitelistOrigin = EitherOfDiverse< + EnsureRoot, + EnsureXcm>, + >; + type DispatchWhitelistedOrigin = EitherOf, WhitelistedCaller>; + type Preimages = Preimage; +} + +impl pallet_referenda::Config for Runtime { + type WeightInfo = (); // TODO: weights::pallet_referenda::WeightInfo; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type Scheduler = Scheduler; + type Currency = Balances; + type SubmitOrigin = frame_system::EnsureSigned; + type CancelOrigin = EitherOf, ReferendumCanceller>; + type KillOrigin = EitherOf, ReferendumKiller>; + type Slash = (); // TODO: Treasury; + type Votes = pallet_conviction_voting::VotesOf; + type Tally = pallet_conviction_voting::TallyOf; + type SubmissionDeposit = SubmissionDeposit; + type MaxQueued = ConstU32<100>; + type UndecidingTimeout = UndecidingTimeout; + type AlarmInterval = AlarmInterval; + type Tracks = TracksInfo; + type Preimages = Preimage; +} diff --git a/system-parachains/asset-hubs/asset-hub-polkadot/src/governance/origins.rs b/system-parachains/asset-hubs/asset-hub-polkadot/src/governance/origins.rs new file mode 100644 index 0000000000..d069c356e8 --- /dev/null +++ b/system-parachains/asset-hubs/asset-hub-polkadot/src/governance/origins.rs @@ -0,0 +1,155 @@ +// 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 . + +//! Custom origins for governance interventions. + +pub use pallet_custom_origins::*; + +// From https://github.com/polkadot-fellows/runtimes/blob/7bbf00566d86d51fcd5582779e7e9c37a814405e/relay/polkadot/src/governance/origins.rs#L21-L154 +#[frame_support::pallet] +pub mod pallet_custom_origins { + use crate::{Balance, DOLLARS, GRAND}; + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[derive(PartialEq, Eq, Clone, MaxEncodedLen, Encode, Decode, TypeInfo, RuntimeDebug)] + #[pallet::origin] + pub enum Origin { + /// Origin able to cancel slashes and manage minimum commission. + StakingAdmin, + /// Origin for spending up to $10,000,000 DOT from the treasury as well as generally + /// administering it. + Treasurer, + /// Origin for managing the composition of the fellowship. + FellowshipAdmin, + /// Origin for managing the registrar and permissioned HRMP channel operations. + GeneralAdmin, + /// Origin for starting auctions. + AuctionAdmin, + /// Origin able to force slot leases. + LeaseAdmin, + /// Origin able to cancel referenda. + ReferendumCanceller, + /// Origin able to kill referenda. + ReferendumKiller, + /// Origin able to spend around $250 from the treasury at once. + SmallTipper, + /// Origin able to spend around $1,000 from the treasury at once. + BigTipper, + /// Origin able to spend around $10,000 from the treasury at once. + SmallSpender, + /// Origin able to spend around $100,000 from the treasury at once. + MediumSpender, + /// Origin able to spend up to $1,000,000 DOT from the treasury at once. + BigSpender, + /// Origin able to dispatch a whitelisted call. + WhitelistedCaller, + /// Origin for signaling that the network wishes for some change. + WishForChange, + } + + macro_rules! decl_unit_ensures { + ( $name:ident: $success_type:ty = $success:expr ) => { + pub struct $name; + impl> + From> + EnsureOrigin for $name + { + type Success = $success_type; + fn try_origin(o: O) -> Result { + o.into().and_then(|o| match o { + Origin::$name => Ok($success), + r => Err(O::from(r)), + }) + } + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Ok(O::from(Origin::$name)) + } + } + }; + ( $name:ident ) => { decl_unit_ensures! { $name : () = () } }; + ( $name:ident: $success_type:ty = $success:expr, $( $rest:tt )* ) => { + decl_unit_ensures! { $name: $success_type = $success } + decl_unit_ensures! { $( $rest )* } + }; + ( $name:ident, $( $rest:tt )* ) => { + decl_unit_ensures! { $name } + decl_unit_ensures! { $( $rest )* } + }; + () => {} + } + decl_unit_ensures!( + StakingAdmin, + Treasurer, + FellowshipAdmin, + GeneralAdmin, + AuctionAdmin, + LeaseAdmin, + ReferendumCanceller, + ReferendumKiller, + WhitelistedCaller, + WishForChange, + ); + + macro_rules! decl_ensure { + ( + $vis:vis type $name:ident: EnsureOrigin { + $( $item:ident = $success:expr, )* + } + ) => { + $vis struct $name; + impl> + From> + EnsureOrigin for $name + { + type Success = $success_type; + fn try_origin(o: O) -> Result { + o.into().and_then(|o| match o { + $( + Origin::$item => Ok($success), + )* + r => Err(O::from(r)), + }) + } + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + // By convention the more privileged origins go later, so for greatest chance + // of success, we want the last one. + let _result: Result = Err(()); + $( + let _result: Result = Ok(O::from(Origin::$item)); + )* + _result + } + } + } + } + + decl_ensure! { + pub type Spender: EnsureOrigin { + SmallTipper = 250 * DOLLARS, + BigTipper = GRAND, + SmallSpender = 10 * GRAND, + MediumSpender = 100 * GRAND, + BigSpender = 1_000 * GRAND, + Treasurer = 10_000 * GRAND, + } + } +} diff --git a/system-parachains/asset-hubs/asset-hub-polkadot/src/governance/tracks.rs b/system-parachains/asset-hubs/asset-hub-polkadot/src/governance/tracks.rs new file mode 100644 index 0000000000..702ce90d6e --- /dev/null +++ b/system-parachains/asset-hubs/asset-hub-polkadot/src/governance/tracks.rs @@ -0,0 +1,337 @@ +// 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 . + +//! Track configurations for governance. + +// TODO: RC_DAYS, RC_MINUTES should be RC_RC_DAYS, RC_MINUTES, etc. + +use super::*; + +// From https://github.com/polkadot-fellows/runtimes/blob/f29ef4bf7fc7039a9a62f0fd4e9abd95bfee6436/relay/polkadot/src/governance/tracks.rs#L21-L334 +const fn percent(x: i32) -> sp_arithmetic::FixedI64 { + sp_arithmetic::FixedI64::from_rational(x as u128, 100) +} +use pallet_referenda::Curve; +const APP_ROOT: Curve = Curve::make_reciprocal(4, 28, percent(80), percent(50), percent(100)); +const SUP_ROOT: Curve = Curve::make_linear(28, 28, percent(0), percent(50)); +const APP_STAKING_ADMIN: Curve = Curve::make_linear(17, 28, percent(50), percent(100)); +const SUP_STAKING_ADMIN: Curve = + Curve::make_reciprocal(12, 28, percent(1), percent(0), percent(50)); +const APP_TREASURER: Curve = Curve::make_reciprocal(4, 28, percent(80), percent(50), percent(100)); +const SUP_TREASURER: Curve = Curve::make_linear(28, 28, percent(0), percent(50)); +const APP_FELLOWSHIP_ADMIN: Curve = Curve::make_linear(17, 28, percent(50), percent(100)); +const SUP_FELLOWSHIP_ADMIN: Curve = + Curve::make_reciprocal(12, 28, percent(1), percent(0), percent(50)); +const APP_GENERAL_ADMIN: Curve = + Curve::make_reciprocal(4, 28, percent(80), percent(50), percent(100)); +const SUP_GENERAL_ADMIN: Curve = + Curve::make_reciprocal(7, 28, percent(10), percent(0), percent(50)); +const APP_AUCTION_ADMIN: Curve = + Curve::make_reciprocal(4, 28, percent(80), percent(50), percent(100)); +const SUP_AUCTION_ADMIN: Curve = + Curve::make_reciprocal(7, 28, percent(10), percent(0), percent(50)); +const APP_LEASE_ADMIN: Curve = Curve::make_linear(17, 28, percent(50), percent(100)); +const SUP_LEASE_ADMIN: Curve = Curve::make_reciprocal(12, 28, percent(1), percent(0), percent(50)); +const APP_REFERENDUM_CANCELLER: Curve = Curve::make_linear(17, 28, percent(50), percent(100)); +const SUP_REFERENDUM_CANCELLER: Curve = + Curve::make_reciprocal(12, 28, percent(1), percent(0), percent(50)); +const APP_REFERENDUM_KILLER: Curve = Curve::make_linear(17, 28, percent(50), percent(100)); +const SUP_REFERENDUM_KILLER: Curve = + Curve::make_reciprocal(12, 28, percent(1), percent(0), percent(50)); +const APP_SMALL_TIPPER: Curve = Curve::make_linear(10, 28, percent(50), percent(100)); +const SUP_SMALL_TIPPER: Curve = Curve::make_reciprocal(1, 28, percent(4), percent(0), percent(50)); +const APP_BIG_TIPPER: Curve = Curve::make_linear(10, 28, percent(50), percent(100)); +const SUP_BIG_TIPPER: Curve = Curve::make_reciprocal(8, 28, percent(1), percent(0), percent(50)); +const APP_SMALL_SPENDER: Curve = Curve::make_linear(17, 28, percent(50), percent(100)); +const SUP_SMALL_SPENDER: Curve = + Curve::make_reciprocal(12, 28, percent(1), percent(0), percent(50)); +const APP_MEDIUM_SPENDER: Curve = Curve::make_linear(23, 28, percent(50), percent(100)); +const SUP_MEDIUM_SPENDER: Curve = + Curve::make_reciprocal(16, 28, percent(1), percent(0), percent(50)); +const APP_BIG_SPENDER: Curve = Curve::make_linear(28, 28, percent(50), percent(100)); +const SUP_BIG_SPENDER: Curve = Curve::make_reciprocal(20, 28, percent(1), percent(0), percent(50)); +const APP_WHITELISTED_CALLER: Curve = + Curve::make_reciprocal(16, 28 * 24, percent(96), percent(50), percent(100)); +const SUP_WHITELISTED_CALLER: Curve = + Curve::make_reciprocal(1, 28, percent(20), percent(5), percent(50)); + +const TRACKS_DATA: [(u16, pallet_referenda::TrackInfo); 16] = [ + ( + 0, + pallet_referenda::TrackInfo { + name: "root", + max_deciding: 1, + decision_deposit: 100 * GRAND, + prepare_period: 2 * RC_HOURS, + decision_period: 28 * RC_DAYS, + confirm_period: 24 * RC_HOURS, + min_enactment_period: 24 * RC_HOURS, + min_approval: APP_ROOT, + min_support: SUP_ROOT, + }, + ), + ( + 1, + pallet_referenda::TrackInfo { + name: "whitelisted_caller", + max_deciding: 100, + decision_deposit: 10 * GRAND, + prepare_period: 30 * RC_MINUTES, + decision_period: 28 * RC_DAYS, + confirm_period: 10 * RC_MINUTES, + min_enactment_period: 10 * RC_MINUTES, + min_approval: APP_WHITELISTED_CALLER, + min_support: SUP_WHITELISTED_CALLER, + }, + ), + ( + 2, + pallet_referenda::TrackInfo { + name: "wish_for_change", + max_deciding: 10, + decision_deposit: 20 * GRAND, + prepare_period: 2 * RC_HOURS, + decision_period: 28 * RC_DAYS, + confirm_period: 24 * RC_HOURS, + min_enactment_period: 10 * RC_MINUTES, + min_approval: APP_ROOT, + min_support: SUP_ROOT, + }, + ), + ( + 10, + pallet_referenda::TrackInfo { + name: "staking_admin", + max_deciding: 10, + decision_deposit: 5 * GRAND, + prepare_period: 2 * RC_HOURS, + decision_period: 28 * RC_DAYS, + confirm_period: 3 * RC_HOURS, + min_enactment_period: 10 * RC_MINUTES, + min_approval: APP_STAKING_ADMIN, + min_support: SUP_STAKING_ADMIN, + }, + ), + ( + 11, + pallet_referenda::TrackInfo { + name: "treasurer", + max_deciding: 10, + decision_deposit: GRAND, + prepare_period: 2 * RC_HOURS, + decision_period: 28 * RC_DAYS, + confirm_period: 7 * RC_DAYS, + min_enactment_period: 24 * RC_HOURS, + min_approval: APP_TREASURER, + min_support: SUP_TREASURER, + }, + ), + ( + 12, + pallet_referenda::TrackInfo { + name: "lease_admin", + max_deciding: 10, + decision_deposit: 5 * GRAND, + prepare_period: 2 * RC_HOURS, + decision_period: 28 * RC_DAYS, + confirm_period: 3 * RC_HOURS, + min_enactment_period: 10 * RC_MINUTES, + min_approval: APP_LEASE_ADMIN, + min_support: SUP_LEASE_ADMIN, + }, + ), + ( + 13, + pallet_referenda::TrackInfo { + name: "fellowship_admin", + max_deciding: 10, + decision_deposit: 5 * GRAND, + prepare_period: 2 * RC_HOURS, + decision_period: 28 * RC_DAYS, + confirm_period: 3 * RC_HOURS, + min_enactment_period: 10 * RC_MINUTES, + min_approval: APP_FELLOWSHIP_ADMIN, + min_support: SUP_FELLOWSHIP_ADMIN, + }, + ), + ( + 14, + pallet_referenda::TrackInfo { + name: "general_admin", + max_deciding: 10, + decision_deposit: 5 * GRAND, + prepare_period: 2 * RC_HOURS, + decision_period: 28 * RC_DAYS, + confirm_period: 3 * RC_HOURS, + min_enactment_period: 10 * RC_MINUTES, + min_approval: APP_GENERAL_ADMIN, + min_support: SUP_GENERAL_ADMIN, + }, + ), + ( + 15, + pallet_referenda::TrackInfo { + name: "auction_admin", + max_deciding: 10, + decision_deposit: 5 * GRAND, + prepare_period: 2 * RC_HOURS, + decision_period: 28 * RC_DAYS, + confirm_period: 3 * RC_HOURS, + min_enactment_period: 10 * RC_MINUTES, + min_approval: APP_AUCTION_ADMIN, + min_support: SUP_AUCTION_ADMIN, + }, + ), + ( + 20, + pallet_referenda::TrackInfo { + name: "referendum_canceller", + max_deciding: 1_000, + decision_deposit: 10 * GRAND, + prepare_period: 2 * RC_HOURS, + decision_period: 7 * RC_DAYS, + confirm_period: 3 * RC_HOURS, + min_enactment_period: 10 * RC_MINUTES, + min_approval: APP_REFERENDUM_CANCELLER, + min_support: SUP_REFERENDUM_CANCELLER, + }, + ), + ( + 21, + pallet_referenda::TrackInfo { + name: "referendum_killer", + max_deciding: 1_000, + decision_deposit: 50 * GRAND, + prepare_period: 2 * RC_HOURS, + decision_period: 28 * RC_DAYS, + confirm_period: 3 * RC_HOURS, + min_enactment_period: 10 * RC_MINUTES, + min_approval: APP_REFERENDUM_KILLER, + min_support: SUP_REFERENDUM_KILLER, + }, + ), + ( + 30, + pallet_referenda::TrackInfo { + name: "small_tipper", + max_deciding: 200, + decision_deposit: DOLLARS, + prepare_period: RC_MINUTES, + decision_period: 7 * RC_DAYS, + confirm_period: 10 * RC_MINUTES, + min_enactment_period: RC_MINUTES, + min_approval: APP_SMALL_TIPPER, + min_support: SUP_SMALL_TIPPER, + }, + ), + ( + 31, + pallet_referenda::TrackInfo { + name: "big_tipper", + max_deciding: 100, + decision_deposit: 10 * DOLLARS, + prepare_period: 10 * RC_MINUTES, + decision_period: 7 * RC_DAYS, + confirm_period: RC_HOURS, + min_enactment_period: 10 * RC_MINUTES, + min_approval: APP_BIG_TIPPER, + min_support: SUP_BIG_TIPPER, + }, + ), + ( + 32, + pallet_referenda::TrackInfo { + name: "small_spender", + max_deciding: 50, + decision_deposit: 100 * DOLLARS, + prepare_period: 4 * RC_HOURS, + decision_period: 28 * RC_DAYS, + confirm_period: 2 * RC_DAYS, + min_enactment_period: 24 * RC_HOURS, + min_approval: APP_SMALL_SPENDER, + min_support: SUP_SMALL_SPENDER, + }, + ), + ( + 33, + pallet_referenda::TrackInfo { + name: "medium_spender", + max_deciding: 50, + decision_deposit: 200 * DOLLARS, + prepare_period: 4 * RC_HOURS, + decision_period: 28 * RC_DAYS, + confirm_period: 4 * RC_DAYS, + min_enactment_period: 24 * RC_HOURS, + min_approval: APP_MEDIUM_SPENDER, + min_support: SUP_MEDIUM_SPENDER, + }, + ), + ( + 34, + pallet_referenda::TrackInfo { + name: "big_spender", + max_deciding: 50, + decision_deposit: 400 * DOLLARS, + prepare_period: 4 * RC_HOURS, + decision_period: 28 * RC_DAYS, + confirm_period: 7 * RC_DAYS, + min_enactment_period: 24 * RC_HOURS, + min_approval: APP_BIG_SPENDER, + min_support: SUP_BIG_SPENDER, + }, + ), +]; + +pub struct TracksInfo; +impl pallet_referenda::TracksInfo for TracksInfo { + type Id = u16; + type RuntimeOrigin = ::PalletsOrigin; + fn tracks() -> &'static [(Self::Id, pallet_referenda::TrackInfo)] { + &TRACKS_DATA[..] + } + fn track_for(id: &Self::RuntimeOrigin) -> Result { + if let Ok(system_origin) = frame_system::RawOrigin::try_from(id.clone()) { + match system_origin { + frame_system::RawOrigin::Root => Ok(0), + _ => Err(()), + } + } else if let Ok(custom_origin) = origins::Origin::try_from(id.clone()) { + match custom_origin { + origins::Origin::WhitelistedCaller => Ok(1), + origins::Origin::WishForChange => Ok(2), + // General admin + origins::Origin::StakingAdmin => Ok(10), + origins::Origin::Treasurer => Ok(11), + origins::Origin::LeaseAdmin => Ok(12), + origins::Origin::FellowshipAdmin => Ok(13), + origins::Origin::GeneralAdmin => Ok(14), + origins::Origin::AuctionAdmin => Ok(15), + // Referendum admins + origins::Origin::ReferendumCanceller => Ok(20), + origins::Origin::ReferendumKiller => Ok(21), + // Limited treasury spenders + origins::Origin::SmallTipper => Ok(30), + origins::Origin::BigTipper => Ok(31), + origins::Origin::SmallSpender => Ok(32), + origins::Origin::MediumSpender => Ok(33), + origins::Origin::BigSpender => Ok(34), + } + } else { + Err(()) + } + } +} +pallet_referenda::impl_tracksinfo_get!(TracksInfo, Balance, BlockNumber); 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 e7fbb2d86d..96a66b9b7f 100644 --- a/system-parachains/asset-hubs/asset-hub-polkadot/src/lib.rs +++ b/system-parachains/asset-hubs/asset-hub-polkadot/src/lib.rs @@ -61,12 +61,16 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); // Genesis preset configurations. pub mod genesis_config_presets; +pub mod governance; mod impls; mod migration; pub mod staking; +pub mod treasury; mod weights; pub mod xcm_config; +use core::cmp::Ordering; + use assets_common::{ foreign_creators::ForeignCreators, local_and_foreign_assets::{LocalFromLeft, TargetFromLeft}, @@ -75,12 +79,14 @@ use assets_common::{ }; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; +use governance::{pallet_custom_origins, Treasurer, TreasurySpender}; use migration::{RcToAhFreezeReason, RcToAhHoldReason}; +use polkadot_runtime_constants::time::{DAYS as RC_DAYS, HOURS as RC_HOURS, MINUTES as RC_MINUTES}; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, ConstU128, OpaqueMetadata}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, - traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, ConvertInto, Verify}, + traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, ConvertInto, IdentityLookup, Verify}, transaction_validity::{TransactionSource, TransactionValidity}, ApplyExtrinsicResult, Perbill, Permill, }; @@ -104,9 +110,13 @@ use frame_support::{ traits::{ fungible::{self, HoldConsideration}, fungibles, - tokens::imbalance::ResolveAssetTo, - AsEnsureOriginWithArg, ConstBool, ConstU32, ConstU64, ConstU8, EitherOfDiverse, Equals, - InstanceFilter, LinearStoragePrice, NeverEnsureOrigin, TransformOrigin, WithdrawReasons, + tokens::{ + imbalance::ResolveAssetTo, pay::PayAssetFromAccount, PayFromAccount, + UnityAssetBalanceConversion, + }, + AsEnsureOriginWithArg, ConstBool, ConstU32, ConstU64, ConstU8, EitherOf, EitherOfDiverse, + Equals, InstanceFilter, LinearStoragePrice, NeverEnsureOrigin, PrivilegeCmp, + TransformOrigin, WithdrawReasons, }, weights::{ConstantMultiplier, Weight, WeightToFee as _}, PalletId, @@ -143,7 +153,7 @@ pub use sp_runtime::BuildStorage; // Polkadot imports use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; -use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; +use polkadot_runtime_common::{prod_or_fast, BlockHashCount, SlowAdjustingFeeUpdate}; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; @@ -500,9 +510,16 @@ impl InstanceFilter for ProxyType { RuntimeCall::Assets { .. } | RuntimeCall::Nfts { .. } | RuntimeCall::Uniques { .. } | + RuntimeCall::Scheduler(..) | + RuntimeCall::Treasury(..) | + RuntimeCall::Bounties(..) | + RuntimeCall::ChildBounties(..) | // We allow calling `vest` and merging vesting schedules, but obviously not // vested transfers. - RuntimeCall::Vesting(pallet_vesting::Call::vested_transfer { .. }) + RuntimeCall::Vesting(pallet_vesting::Call::vested_transfer { .. }) | + RuntimeCall::ConvictionVoting(..) | + RuntimeCall::Referenda(..) | + RuntimeCall::Whitelist(..) ), ProxyType::CancelProxy => matches!( c, @@ -595,12 +612,13 @@ impl InstanceFilter for ProxyType { // TODO: Uncomment once all these pallets are deployed. ProxyType::Governance => matches!( c, - //RuntimeCall::Treasury(..) | - //RuntimeCall::Bounties(..) | - RuntimeCall::Utility(..) /*RuntimeCall::ChildBounties(..) | - *RuntimeCall::ConvictionVoting(..) | - *RuntimeCall::Referenda(..) | - *RuntimeCall::Whitelist(..) */ + RuntimeCall::Treasury(..) | + RuntimeCall::Bounties(..) | + RuntimeCall::Utility(..) | + RuntimeCall::ChildBounties(..) | + RuntimeCall::ConvictionVoting(..) | + RuntimeCall::Referenda(..) | + RuntimeCall::Whitelist(..) ), ProxyType::Staking => { matches!( @@ -683,8 +701,8 @@ type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< impl parachain_info::Config for Runtime {} parameter_types! { - pub MessageQueueServiceWeight: Weight = Perbill::from_percent(35) * RuntimeBlockWeights::get().max_block; - pub MessageQueueIdleServiceWeight: Weight = Perbill::from_percent(20) * RuntimeBlockWeights::get().max_block; + pub MessageQueueServiceWeight: Weight = Perbill::from_percent(50) * RuntimeBlockWeights::get().max_block; + pub MessageQueueIdleServiceWeight: Weight = Perbill::from_percent(50) * RuntimeBlockWeights::get().max_block; } impl pallet_message_queue::Config for Runtime { @@ -1031,6 +1049,45 @@ impl pallet_preimage::Config for Runtime { >; } +parameter_types! { + pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * + RuntimeBlockWeights::get().max_block; + pub const MaxScheduledPerBlock: u32 = 50; + pub const NoPreimagePostponement: Option = Some(10); +} + +/// Used the compare the privilege of an origin inside the scheduler. +pub struct OriginPrivilegeCmp; + +impl PrivilegeCmp for OriginPrivilegeCmp { + fn cmp_privilege(left: &OriginCaller, right: &OriginCaller) -> Option { + if left == right { + return Some(Ordering::Equal) + } + + match (left, right) { + // Root is greater than anything. + (OriginCaller::system(frame_system::RawOrigin::Root), _) => Some(Ordering::Greater), + // For every other origin we don't care, as they are not used for `ScheduleOrigin`. + _ => None, + } + } +} + +impl pallet_scheduler::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeEvent = RuntimeEvent; + type PalletsOrigin = OriginCaller; + type RuntimeCall = RuntimeCall; + type MaximumWeight = MaximumSchedulerWeight; + // Also allow Treasurer to schedule recurring payments. + type ScheduleOrigin = EitherOf, Treasurer>; + type MaxScheduledPerBlock = MaxScheduledPerBlock; + type WeightInfo = (); // TODO: weights::pallet_scheduler::WeightInfo; + type OriginPrivilegeCmp = OriginPrivilegeCmp; + type Preimages = Preimage; +} + impl pallet_ah_migrator::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Balances; @@ -1043,6 +1100,10 @@ impl pallet_ah_migrator::Config for Runtime { type RcToProxyType = migration::RcToProxyType; type RcToAhDelay = migration::RcToAhDelay; type RcBlockNumberProvider = RelaychainDataProvider; + type RcToAhCall = migration::RcToAhCall; + type RcPalletsOrigin = migration::RcPalletsOrigin; + type RcToAhPalletsOrigin = migration::RcToAhPalletsOrigin; + type Preimage = Preimage; } // Create the runtime by composing the FRAME pallets that were previously configured. @@ -1056,6 +1117,7 @@ construct_runtime!( Timestamp: pallet_timestamp = 3, ParachainInfo: parachain_info = 4, Preimage: pallet_preimage = 5, + Scheduler: pallet_scheduler = 6, // Monetary stuff. Balances: pallet_balances = 10, @@ -1090,6 +1152,15 @@ construct_runtime!( PoolAssets: pallet_assets:: = 54, AssetConversion: pallet_asset_conversion = 55, + // OpenGov stuff. + Treasury: pallet_treasury = 60, + ConvictionVoting: pallet_conviction_voting = 61, + Referenda: pallet_referenda = 62, + Origins: pallet_custom_origins = 63, + Whitelist: pallet_whitelist = 64, + Bounties: pallet_bounties = 65, + ChildBounties: pallet_child_bounties = 66, + // Staking in the 70s NominationPools: pallet_nomination_pools = 70, @@ -1157,15 +1228,23 @@ mod benches { [pallet_message_queue, MessageQueue] [pallet_multisig, Multisig] [pallet_nfts, Nfts] + [pallet_preimage, Preimage] [pallet_proxy, Proxy] + [pallet_scheduler, Scheduler] [pallet_session, SessionBench::] [pallet_uniques, Uniques] [pallet_utility, Utility] [pallet_vesting, Vesting] [pallet_timestamp, Timestamp] + [pallet_treasury, Treasury] [pallet_collator_selection, CollatorSelection] [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] + [pallet_conviction_voting, ConvictionVoting] + [pallet_referenda, Referenda] + [pallet_whitelist, Whitelist] + [pallet_bounties, Bounties] + [pallet_child_bounties, ChildBounties] // XCM [pallet_xcm, PalletXcmExtrinsiscsBenchmark::] // Bridges diff --git a/system-parachains/asset-hubs/asset-hub-polkadot/src/migration.rs b/system-parachains/asset-hubs/asset-hub-polkadot/src/migration.rs index 12fb0250e9..3191570d41 100644 --- a/system-parachains/asset-hubs/asset-hub-polkadot/src/migration.rs +++ b/system-parachains/asset-hubs/asset-hub-polkadot/src/migration.rs @@ -14,9 +14,19 @@ // limitations under the License. use super::*; -use frame_support::pallet_prelude::TypeInfo; +use codec::DecodeAll; +use frame_support::{ + defensive, + pallet_prelude::TypeInfo, + traits::{Bounded, QueryPreimage}, +}; use frame_system::pallet_prelude::BlockNumberFor; +use pallet_referenda::BoundedCallOf; +use polkadot_runtime_common::impls::{LocatableAssetConverter, VersionedLocatableAsset}; +use sp_core::H256; use sp_runtime::traits::{Convert, TryConvert}; +use system_parachains_common::pay::VersionedLocatableAccount; +use xcm::latest::prelude::*; /// Relay Chain Hold Reason #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] @@ -93,3 +103,221 @@ impl Convert, BlockNumberFor> for RcToAhDelay { rc / 2 } } + +/// A subset of Relay Chain origins. +/// +/// These origins are utilized in Governance and mapped to Asset Hub origins for active referendums. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub enum RcPalletsOrigin { + #[codec(index = 0u8)] + system(frame_system::Origin), + #[codec(index = 22u8)] + Origins(pallet_custom_origins::Origin), +} + +/// Convert a Relay Chain origin to an Asset Hub one. +pub struct RcToAhPalletsOrigin; +impl TryConvert for RcToAhPalletsOrigin { + fn try_convert(a: RcPalletsOrigin) -> Result { + match a { + RcPalletsOrigin::system(a) => Ok(OriginCaller::system(a)), + RcPalletsOrigin::Origins(a) => Ok(OriginCaller::Origins(a)), + } + } +} + +/// Relay Chain Runtime Call. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub enum RcRuntimeCall { + // TODO: variant set code for Relay Chain + // TODO: variant set code for Parachains + // TODO: whitelisted caller + #[codec(index = 19u8)] + Treasury(RcTreasuryCall), + #[codec(index = 21u8)] + Referenda(pallet_referenda::Call), + #[codec(index = 26u8)] + Utility(RcUtilityCall), + #[codec(index = 34u8)] + Bounties(pallet_bounties::Call), + #[codec(index = 38u8)] + ChildBounties(pallet_child_bounties::Call), +} + +/// Relay Chain Treasury Call obtained from cargo expand. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub enum RcTreasuryCall { + /// Propose and approve a spend of treasury funds. + #[codec(index = 3u8)] + spend_local { + #[codec(compact)] + amount: Balance, + beneficiary: Address, + }, + /// Force a previously approved proposal to be removed from the approval queue. + #[codec(index = 4u8)] + remove_approval { + #[codec(compact)] + proposal_id: pallet_treasury::ProposalIndex, + }, + /// Propose and approve a spend of treasury funds. + #[codec(index = 5u8)] + spend { + asset_kind: Box, + #[codec(compact)] + amount: Balance, + beneficiary: VersionedLocation, + valid_from: Option, + }, + /// Claim a spend. + #[codec(index = 6u8)] + payout { index: pallet_treasury::SpendIndex }, + #[codec(index = 7u8)] + check_status { index: pallet_treasury::SpendIndex }, + #[codec(index = 8u8)] + void_spend { index: pallet_treasury::SpendIndex }, +} + +/// Relay Chain Utility Call obtained from cargo expand. +/// +/// The variants that are not generally used in Governance are not included. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub enum RcUtilityCall { + /// Send a batch of dispatch calls. + #[codec(index = 0u8)] + batch { calls: Vec }, + /// Send a batch of dispatch calls and atomically execute them. + #[codec(index = 2u8)] + batch_all { calls: Vec }, + /// Dispatches a function call with a provided origin. + #[codec(index = 3u8)] + dispatch_as { as_origin: Box, call: Box }, + /// Send a batch of dispatch calls. + /// Unlike `batch`, it allows errors and won't interrupt. + #[codec(index = 4u8)] + force_batch { calls: Vec }, +} + +/// Convert an encoded Relay Chain Call to a local AH one. +pub struct RcToAhCall; +impl<'a> TryConvert<&'a [u8], RuntimeCall> for RcToAhCall { + fn try_convert(mut a: &'a [u8]) -> Result { + let rc_call = match RcRuntimeCall::decode_all(&mut a) { + Ok(rc_call) => rc_call, + Err(e) => { + log::error!("Failed to decode RC call with error: {:?}", e); + return Err(a) + }, + }; + Self::map(rc_call).map_err(|_| a) + } +} +impl RcToAhCall { + fn map(rc_call: RcRuntimeCall) -> Result { + match rc_call { + RcRuntimeCall::Utility(RcUtilityCall::dispatch_as { as_origin, call }) => { + let origin = RcToAhPalletsOrigin::try_convert(*as_origin).map_err(|err| { + log::error!("Failed to decode RC dispatch_as origin: {:?}", err); + })?; + Ok(RuntimeCall::Utility(pallet_utility::Call::::dispatch_as { + as_origin: Box::new(origin), + call: Box::new(Self::map(*call)?), + })) + }, + RcRuntimeCall::Utility(RcUtilityCall::batch { calls }) => + Ok(RuntimeCall::Utility(pallet_utility::Call::::batch { + calls: calls + .into_iter() + .map(|c| Self::map(c)) + .collect::, _>>()?, + })), + RcRuntimeCall::Utility(RcUtilityCall::batch_all { calls }) => + Ok(RuntimeCall::Utility(pallet_utility::Call::::batch_all { + calls: calls + .into_iter() + .map(|c| Self::map(c)) + .collect::, _>>()?, + })), + RcRuntimeCall::Utility(RcUtilityCall::force_batch { calls }) => + Ok(RuntimeCall::Utility(pallet_utility::Call::::force_batch { + calls: calls + .into_iter() + .map(|c| Self::map(c)) + .collect::, _>>()?, + })), + RcRuntimeCall::Treasury(RcTreasuryCall::spend { + asset_kind, + amount, + beneficiary, + valid_from, + }) => { + let asset_kind = + LocatableAssetConverter::try_convert(*asset_kind).map_err(|_| { + log::error!("Failed to convert RC asset kind to latest version"); + })?; + if asset_kind.location != Location::new(0, Parachain(1000)) { + log::error!("Unsupported RC asset kind location: {:?}", asset_kind.location); + return Err(()); + }; + let asset_kind = VersionedLocatableAsset::V4 { + location: Location::here(), + asset_id: asset_kind.asset_id, + }; + let beneficiary = beneficiary.try_into().map_err(|_| { + log::error!("Failed to convert RC beneficiary type to the latest version"); + })?; + let beneficiary = VersionedLocatableAccount::V4 { + location: Location::here(), + account_id: beneficiary, + }; + Ok(RuntimeCall::Treasury(pallet_treasury::Call::::spend { + asset_kind: Box::new(asset_kind), + amount, + beneficiary: Box::new(beneficiary), + valid_from, + })) + }, + RcRuntimeCall::Treasury(inner_call) => { + let call = + inner_call.using_encoded(|mut e| Decode::decode(&mut e)).map_err(|err| { + log::error!("Failed to decode inner RC call into inner AH call: {:?}", err); + () + })?; + Ok(RuntimeCall::Treasury(call)) + }, + RcRuntimeCall::Referenda(inner_call) => { + let call = + inner_call.using_encoded(|mut e| Decode::decode(&mut e)).map_err(|err| { + log::error!( + "Failed to decode RC Referenda call to AH Referenda call: {:?}", + err + ); + () + })?; + Ok(RuntimeCall::Referenda(call)) + }, + RcRuntimeCall::Bounties(inner_call) => { + let call = + inner_call.using_encoded(|mut e| Decode::decode(&mut e)).map_err(|err| { + log::error!( + "Failed to decode RC Bounties call to AH Bounties call: {:?}", + err + ); + () + })?; + Ok(RuntimeCall::Bounties(call)) + }, + RcRuntimeCall::ChildBounties(inner_call) => { + let call = + inner_call.using_encoded(|mut e| Decode::decode(&mut e)).map_err(|err| { + log::error!( + "Failed to decode RC ChildBounties call to AH ChildBounties call: {:?}", + err + ); + () + })?; + Ok(RuntimeCall::ChildBounties(call)) + }, + } + } +} diff --git a/system-parachains/asset-hubs/asset-hub-polkadot/src/treasury.rs b/system-parachains/asset-hubs/asset-hub-polkadot/src/treasury.rs new file mode 100644 index 0000000000..b162f29265 --- /dev/null +++ b/system-parachains/asset-hubs/asset-hub-polkadot/src/treasury.rs @@ -0,0 +1,98 @@ +// 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. + +use crate::*; +use polkadot_runtime_common::impls::VersionedLocatableAsset; +use system_parachains_common::pay::{LocalPay, VersionedLocatableAccount}; + +parameter_types! { + pub const SpendPeriod: BlockNumber = 24 * RC_DAYS; + pub const Burn: Permill = Permill::from_percent(1); + pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); + pub const PayoutSpendPeriod: BlockNumber = 30 * RC_DAYS; + pub const MaxApprovals: u32 = 100; + pub TreasuryAccount: AccountId = Treasury::account_id(); +} + +type BlaBla = PayAssetFromAccount; + +impl pallet_treasury::Config for Runtime { + type PalletId = TreasuryPalletId; + type Currency = Balances; + type RejectOrigin = EitherOfDiverse, Treasurer>; + type RuntimeEvent = RuntimeEvent; + type SpendPeriod = SpendPeriod; + type Burn = Burn; + type BurnDestination = (); + type SpendFunds = Bounties; + type MaxApprovals = MaxApprovals; + type WeightInfo = (); // TODO: weights::pallet_treasury::WeightInfo; + type SpendOrigin = TreasurySpender; + type AssetKind = VersionedLocatableAsset; + type Beneficiary = VersionedLocatableAccount; + type BeneficiaryLookup = IdentityLookup; + // TODO: should we continue using `RelayTreasuryPalletAccount` or we move all assets to + // `Treasury::account_id()`` + type Paymaster = LocalPay< + NativeAndAssets, + xcm_config::RelayTreasuryPalletAccount, + xcm_config::LocationToAccountId, + >; + type BalanceConverter = UnityAssetBalanceConversion; // TODO: use actual conversion + type PayoutPeriod = PayoutSpendPeriod; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); // TODO: polkadot_runtime_common::impls::benchmarks::TreasuryArguments; +} + +parameter_types! { + pub const BountyDepositBase: Balance = DOLLARS; + pub const BountyDepositPayoutDelay: BlockNumber = 0; + pub const BountyUpdatePeriod: BlockNumber = 90 * RC_DAYS; + pub const MaximumReasonLength: u32 = 16384; + // TODO: update deposits + pub const DataDepositPerByte: Balance = system_para_deposit(0, 1); + pub const CuratorDepositMultiplier: Permill = Permill::from_percent(50); + pub const CuratorDepositMin: Balance = 10 * DOLLARS; + pub const CuratorDepositMax: Balance = 200 * DOLLARS; + pub const BountyValueMinimum: Balance = 10 * DOLLARS; +} + +impl pallet_bounties::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type BountyDepositBase = BountyDepositBase; + type BountyDepositPayoutDelay = BountyDepositPayoutDelay; + type BountyUpdatePeriod = BountyUpdatePeriod; + type CuratorDepositMultiplier = CuratorDepositMultiplier; + type CuratorDepositMin = CuratorDepositMin; + type CuratorDepositMax = CuratorDepositMax; + type BountyValueMinimum = BountyValueMinimum; + type ChildBountyManager = ChildBounties; + type DataDepositPerByte = DataDepositPerByte; + type MaximumReasonLength = MaximumReasonLength; + type OnSlash = Treasury; + type WeightInfo = (); // TODO: weights::pallet_bounties::WeightInfo; +} + +parameter_types! { + pub const MaxActiveChildBountyCount: u32 = 100; + pub const ChildBountyValueMinimum: Balance = BountyValueMinimum::get() / 10; +} + +impl pallet_child_bounties::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type MaxActiveChildBountyCount = MaxActiveChildBountyCount; + type ChildBountyValueMinimum = ChildBountyValueMinimum; + type WeightInfo = (); // TODO: weights::pallet_child_bounties::WeightInfo; +} diff --git a/system-parachains/common/Cargo.toml b/system-parachains/common/Cargo.toml new file mode 100644 index 0000000000..8e53150b7e --- /dev/null +++ b/system-parachains/common/Cargo.toml @@ -0,0 +1,48 @@ +[package] +authors.workspace = true +description = "Shared utilities between system-parachains runtimes" +edition.workspace = true +license.workspace = true +name = "system-parachains-common" +repository.workspace = true +version.workspace = true + +[dependencies] +codec = { features = ["derive", "max-encoded-len"], workspace = true } +scale-info = { features = ["derive"], workspace = true } + +sp-api = { workspace = true } +sp-runtime = { workspace = true } + +frame-support = { workspace = true } + +polkadot-runtime-common = { workspace = true } +xcm = { workspace = true } +xcm-executor = { workspace = true } + + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "sp-api/std", + "sp-runtime/std", + "frame-support/std", + "polkadot-runtime-common/std", + "xcm/std", + "xcm-executor/std", +] + +runtime-benchmarks = [ + "sp-runtime/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "polkadot-runtime-common/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", +] + +try-runtime = [ + "sp-runtime/try-runtime", + "frame-support/try-runtime", + "polkadot-runtime-common/try-runtime", +] diff --git a/system-parachains/common/src/lib.rs b/system-parachains/common/src/lib.rs new file mode 100644 index 0000000000..7fac39777d --- /dev/null +++ b/system-parachains/common/src/lib.rs @@ -0,0 +1,21 @@ +// 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 . + +//! Shared types between system-parachains runtimes. + +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod pay; diff --git a/system-parachains/common/src/pay.rs b/system-parachains/common/src/pay.rs new file mode 100644 index 0000000000..048f4cabae --- /dev/null +++ b/system-parachains/common/src/pay.rs @@ -0,0 +1,114 @@ +// 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 . + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::traits::{ + fungibles, + tokens::{PaymentStatus, Preservation}, +}; +use polkadot_runtime_common::impls::VersionedLocatableAsset; +use sp_runtime::{traits::TypedGet, DispatchError, RuntimeDebug}; +use xcm::latest::prelude::*; +use xcm_executor::traits::ConvertLocation; + +/// Versioned locatable account type which contains both an XCM `location` and `account_id` to +/// identify an account which exists on some chain. +#[derive( + Encode, Decode, Eq, PartialEq, Clone, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen, +)] +pub enum VersionedLocatableAccount { + // TODO: remove the V3 variant + #[codec(index = 3)] + V3 { location: xcm::v3::Location, account_id: xcm::v3::Location }, + #[codec(index = 4)] + V4 { location: xcm::v4::Location, account_id: xcm::v4::Location }, +} + +/// Pay on the local chain with `fungibles` implementation if the beneficiary and the asset are both +/// local. +pub struct LocalPay(core::marker::PhantomData<(F, A, C)>); +impl frame_support::traits::tokens::Pay for LocalPay +where + A: TypedGet, + F: fungibles::Mutate + fungibles::Create, + C: ConvertLocation, + A::Type: Eq + Clone, +{ + type Balance = F::Balance; + type Beneficiary = VersionedLocatableAccount; + type AssetKind = VersionedLocatableAsset; + type Id = QueryId; + type Error = DispatchError; + fn pay( + who: &Self::Beneficiary, + asset: Self::AssetKind, + amount: Self::Balance, + ) -> Result { + let who = Self::match_location(who).map_err(|_| DispatchError::Unavailable)?; + let asset = Self::match_asset(&asset).map_err(|_| DispatchError::Unavailable)?; + >::transfer( + asset, + &A::get(), + &who, + amount, + Preservation::Expendable, + )?; + // We use `QueryId::MAX` as a constant identifier for these payments since they are always + // processed immediately and successfully on the local chain. The `QueryId` type is used to + // maintain compatibility with XCM payment implementations. + Ok(Self::Id::MAX) + } + fn check_payment(_: Self::Id) -> PaymentStatus { + PaymentStatus::Success + } + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful(_: &Self::Beneficiary, asset: Self::AssetKind, amount: Self::Balance) { + let asset = Self::match_asset(&asset).expect("invalid asset"); + >::create(asset.clone(), A::get(), true, amount).unwrap(); + >::mint_into(asset, &A::get(), amount).unwrap(); + } + #[cfg(feature = "runtime-benchmarks")] + fn ensure_concluded(_: Self::Id) {} +} + +impl LocalPay +where + A: TypedGet, + F: fungibles::Mutate + fungibles::Create, + C: ConvertLocation, + A::Type: Eq + Clone, +{ + fn match_location(who: &VersionedLocatableAccount) -> Result { + // only applicable for the local accounts + let account_id = match who { + VersionedLocatableAccount::V3 { location, account_id } if location.is_here() => + &account_id.clone().try_into().map_err(|_| ())?, + VersionedLocatableAccount::V4 { location, account_id } if location.is_here() => + account_id, + _ => return Err(()), + }; + C::convert_location(account_id).map(|a| a.clone()).ok_or(()) + } + fn match_asset(asset: &VersionedLocatableAsset) -> Result { + match asset { + VersionedLocatableAsset::V3 { location, asset_id } if location.is_here() => + asset_id.clone().try_into().map(|a: xcm::v4::AssetId| a.0).map_err(|_| ()), + VersionedLocatableAsset::V4 { location, asset_id } if location.is_here() => + Ok(asset_id.clone().0), + _ => return Err(()), + } + } +}