diff --git a/polkadot/runtime/common/src/paras_sudo_wrapper.rs b/polkadot/runtime/common/src/paras_sudo_wrapper.rs index 313d3b0963..80bf0b3e35 100644 --- a/polkadot/runtime/common/src/paras_sudo_wrapper.rs +++ b/polkadot/runtime/common/src/paras_sudo_wrapper.rs @@ -25,7 +25,9 @@ use frame_support::{ }; use frame_system::ensure_root; use runtime_parachains::{ - configuration, dmp, ump, hrmp, paras::{self, ParaGenesisArgs}, + configuration, dmp, ump, hrmp, + ParaLifecycle, + paras::{self, ParaGenesisArgs}, }; use primitives::v1::Id as ParaId; use parity_scale_codec::Encode; @@ -49,6 +51,14 @@ decl_error! { DefinitelyNotWasm, /// Could not schedule para cleanup. CouldntCleanup, + /// Not a parathread. + NotParathread, + /// Not a parachain. + NotParachain, + /// Cannot upgrade parathread. + CannotUpgrade, + /// Cannot downgrade parachain. + CannotDowngrade, } } @@ -78,6 +88,26 @@ decl_module! { Ok(()) } + /// Upgrade a parathread to a parachain + #[weight = (1_000, DispatchClass::Operational)] + pub fn sudo_schedule_parathread_upgrade(origin, id: ParaId) -> DispatchResult { + ensure_root(origin)?; + // Para backend should think this is a parathread... + ensure!(paras::Module::::lifecycle(id) == Some(ParaLifecycle::Parathread), Error::::NotParathread); + runtime_parachains::schedule_parathread_upgrade::(id).map_err(|_| Error::::CannotUpgrade)?; + Ok(()) + } + + /// Downgrade a parachain to a parathread + #[weight = (1_000, DispatchClass::Operational)] + pub fn sudo_schedule_parachain_downgrade(origin, id: ParaId) -> DispatchResult { + ensure_root(origin)?; + // Para backend should think this is a parachain... + ensure!(paras::Module::::lifecycle(id) == Some(ParaLifecycle::Parachain), Error::::NotParachain); + runtime_parachains::schedule_parachain_downgrade::(id).map_err(|_| Error::::CannotDowngrade)?; + Ok(()) + } + /// Send a downward XCM to the given para. /// /// The given parachain should exist and the payload should not exceed the preconfigured size diff --git a/polkadot/runtime/common/src/slots.rs b/polkadot/runtime/common/src/slots.rs index f1a5a5199c..b38c36e902 100644 --- a/polkadot/runtime/common/src/slots.rs +++ b/polkadot/runtime/common/src/slots.rs @@ -154,6 +154,8 @@ decl_module! { /// Just a hotwire into the `lease_out` call, in case Root wants to force some lease to happen /// independently of any other on-chain mechanism to use it. + /// + /// Can only be called by the Root origin. #[weight = T::WeightInfo::force_lease()] fn force_lease(origin, para: ParaId, @@ -167,6 +169,26 @@ decl_module! { .map_err(|_| Error::::LeaseError)?; Ok(()) } + + /// Clear all leases for a Para Id, refunding any deposits back to the original owners. + /// + /// Can only be called by the Root origin. + #[weight = 0] // TODO: Benchmarks + fn clear_all_leases(origin, + para: ParaId, + ) -> DispatchResult { + ensure_root(origin)?; + let deposits = Self::all_deposits_held(para); + + // Refund any deposits for these leases + for (who, deposit) in deposits { + let err_amount = T::Currency::unreserve(&who, deposit); + debug_assert!(err_amount.is_zero()); + } + + Leases::::remove(para); + Ok(()) + } } } @@ -216,7 +238,7 @@ impl Module { // If this is less than what we were holding for this leaser's now-ended lease, then // unreserve it. if let Some(rebate) = ended_lease.1.checked_sub(&now_held) { - T::Currency::unreserve( &ended_lease.0, rebate); + T::Currency::unreserve(&ended_lease.0, rebate); } } @@ -249,6 +271,34 @@ impl Module { parachains.len() as u32, ) } + + // Return a vector of (user, balance) for all deposits for a parachain. + // Useful when trying to clean up a parachain leases, as this would tell + // you all the balances you need to unreserve. + fn all_deposits_held(para: ParaId) -> Vec<(T::AccountId, BalanceOf)> { + let mut tracker = sp_std::collections::btree_map::BTreeMap::new(); + Leases::::get(para) + .into_iter() + .for_each(|lease| { + match lease { + Some((who, amount)) => { + match tracker.get(&who) { + Some(prev_amount) => { + if amount > *prev_amount { + tracker.insert(who, amount); + } + }, + None => { + tracker.insert(who, amount); + } + } + }, + None => {}, + } + }); + + tracker.into_iter().collect() + } } impl crate::traits::OnSwap for Module { @@ -653,6 +703,37 @@ mod tests { ]); }); } + + #[test] + fn clear_all_leases_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + + assert_ok!(TestRegistrar::::register(1, ParaId::from(1), Default::default(), Default::default())); + + let max_num = 5u32; + + // max_num different people are reserved for leases to Para ID 1 + for i in 1u32 ..= max_num { + let j: u64 = i.into(); + assert_ok!(Slots::lease_out(1.into(), &j, j * 10, i * i, i)); + assert_eq!(Slots::deposit_held(1.into(), &j), j * 10); + assert_eq!(Balances::reserved_balance(j), j * 10); + } + + assert_ok!(Slots::clear_all_leases(Origin::root(), 1.into())); + + // Balances cleaned up correctly + for i in 1u32 ..= max_num { + let j: u64 = i.into(); + assert_eq!(Slots::deposit_held(1.into(), &j), 0); + assert_eq!(Balances::reserved_balance(j), 0); + } + + // Leases is empty. + assert!(Leases::::get(ParaId::from(1)).is_empty()); + }); + } } #[cfg(feature = "runtime-benchmarks")] diff --git a/polkadot/runtime/common/src/traits.rs b/polkadot/runtime/common/src/traits.rs index a866156a52..e483b2345c 100644 --- a/polkadot/runtime/common/src/traits.rs +++ b/polkadot/runtime/common/src/traits.rs @@ -78,6 +78,7 @@ pub trait Registrar { } /// Error type for something that went wrong with leasing. +#[derive(Debug)] pub enum LeaseError { /// Unable to reserve the funds in the leaser's account. ReserveFailed,