diff --git a/polkadot/runtime/common/src/auctions.rs b/polkadot/runtime/common/src/auctions.rs index 6f5b05cd388f35734bc2c5ddba03d17117a36397..2619fa519ce06d03a60687319875f4fc05dce911 100644 --- a/polkadot/runtime/common/src/auctions.rs +++ b/polkadot/runtime/common/src/auctions.rs @@ -26,7 +26,7 @@ use frame_support::{ weights::{DispatchClass, Weight}, }; use primitives::v1::Id as ParaId; -use frame_system::ensure_signed; +use frame_system::{ensure_signed, ensure_root}; use crate::slot_range::{SlotRange, SLOT_RANGE_COUNT}; use crate::traits::{Leaser, LeaseError, Auctioneer}; use parity_scale_codec::Decode; @@ -37,6 +37,7 @@ type BalanceOf<T> = <<<T as Config>::Leaser as Leaser>::Currency as Currency<<T pub trait WeightInfo { fn new_auction() -> Weight; fn bid() -> Weight; + fn cancel_auction() -> Weight; fn on_initialize() -> Weight; } @@ -44,6 +45,7 @@ pub struct TestWeightInfo; impl WeightInfo for TestWeightInfo { fn new_auction() -> Weight { 0 } fn bid() -> Weight { 0 } + fn cancel_auction() -> Weight { 0 } fn on_initialize() -> Weight { 0 } } @@ -264,6 +266,20 @@ decl_module! { let who = ensure_signed(origin)?; Self::handle_bid(who, para, auction_index, first_slot, last_slot, amount)?; } + + /// Cancel an in-progress auction. + /// + /// Can only be called by Root origin. + #[weight = T::WeightInfo::cancel_auction()] + pub fn cancel_auction(origin) { + ensure_root(origin)?; + // Unreserve all bids. + for ((bidder, _), amount) in ReservedAmounts::<T>::drain() { + CurrencyOf::<T>::unreserve(&bidder, amount); + } + Winning::<T>::remove_all(); + AuctionInfo::<T>::kill(); + } } } @@ -490,8 +506,7 @@ impl<T: Config> Module<T> { // First, unreserve all amounts that were reserved for the bids. We will later re-reserve the // amounts from the bidders that ended up being assigned the slot so there's no need to // special-case them here. - for ((bidder, para), amount) in ReservedAmounts::<T>::iter() { - ReservedAmounts::<T>::take((bidder.clone(), para)); + for ((bidder, _), amount) in ReservedAmounts::<T>::drain() { CurrencyOf::<T>::unreserve(&bidder, amount); } @@ -1311,6 +1326,25 @@ mod tests { ])); }); } + + #[test] + fn can_cancel_auction() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Auctions::new_auction(Origin::signed(6), 5, 1)); + assert_ok!(Auctions::bid(Origin::signed(1), 0.into(), 1, 1, 4, 1)); + assert_eq!(Balances::reserved_balance(1), 1); + assert_eq!(Balances::free_balance(1), 9); + + assert_noop!(Auctions::cancel_auction(Origin::signed(6)), BadOrigin); + assert_ok!(Auctions::cancel_auction(Origin::root())); + + assert!(AuctionInfo::<Test>::get().is_none()); + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(ReservedAmounts::<Test>::iter().count(), 0); + assert_eq!(Winning::<Test>::iter().count(), 0); + }); + } } #[cfg(feature = "runtime-benchmarks")] @@ -1318,7 +1352,7 @@ mod benchmarking { use super::{*, Module as Auctions}; use frame_system::RawOrigin; use frame_support::traits::OnInitialize; - use sp_runtime::traits::Bounded; + use sp_runtime::{traits::Bounded, SaturatedConversion}; use frame_benchmarking::{benchmarks, whitelisted_caller, account, impl_benchmark_test_suite}; @@ -1330,6 +1364,39 @@ mod benchmarking { assert_eq!(event, &system_event); } + fn fill_winners<T: Config>(lease_period_index: LeasePeriodOf<T>) { + let auction_index = AuctionCounter::get(); + let minimum_balance = CurrencyOf::<T>::minimum_balance(); + + for n in 1 ..= SLOT_RANGE_COUNT as u32 { + let bidder = account("bidder", n, 0); + CurrencyOf::<T>::make_free_balance_be(&bidder, BalanceOf::<T>::max_value()); + + let (start, end) = match n { + 1 => (0u32, 0u32), + 2 => (0, 1), + 3 => (0, 2), + 4 => (0, 3), + 5 => (1, 1), + 6 => (1, 2), + 7 => (1, 3), + 8 => (2, 2), + 9 => (2, 3), + 10 => (3, 3), + _ => panic!("test not meant for this"), + }; + + assert!(Auctions::<T>::bid( + RawOrigin::Signed(bidder).into(), + ParaId::from(n), + auction_index, + lease_period_index + start.into(), // First Slot + lease_period_index + end.into(), // Last slot + minimum_balance.saturating_mul(n.into()), // Amount + ).is_ok()); + } + } + benchmarks! { where_clause { where T: pallet_babe::Config } @@ -1388,37 +1455,8 @@ mod benchmarking { let lease_period_index = LeasePeriodOf::<T>::zero(); let now = frame_system::Pallet::<T>::block_number(); Auctions::<T>::new_auction(RawOrigin::Root.into(), duration, lease_period_index)?; - let auction_index = AuctionCounter::get(); - let minimum_balance = CurrencyOf::<T>::minimum_balance(); - - for n in 1 ..= SLOT_RANGE_COUNT as u32 { - let bidder = account("bidder", n, 0); - CurrencyOf::<T>::make_free_balance_be(&bidder, BalanceOf::<T>::max_value()); - - let (start, end) = match n { - 1 => (0u32, 0u32), - 2 => (0, 1), - 3 => (0, 2), - 4 => (0, 3), - 5 => (1, 1), - 6 => (1, 2), - 7 => (1, 3), - 8 => (2, 2), - 9 => (2, 3), - 10 => (3, 3), - _ => panic!("test not meant for this"), - }; - - Auctions::<T>::bid( - RawOrigin::Signed(bidder).into(), - ParaId::from(n), - auction_index, - lease_period_index + start.into(), // First Slot - lease_period_index + end.into(), // Last slot - minimum_balance.saturating_mul(n.into()), // Amount - )?; - } + fill_winners::<T>(lease_period_index); for winner in Winning::<T>::get(T::BlockNumber::from(0u32)).unwrap().iter() { assert!(winner.is_some()); @@ -1438,8 +1476,34 @@ mod benchmarking { }: { Auctions::<T>::on_initialize(duration + now + T::EndingPeriod::get()); } verify { + let auction_index = AuctionCounter::get(); assert_last_event::<T>(RawEvent::AuctionClosed(auction_index).into()); } + + // Worst case: 10 bidders taking all wining spots, and winning data is full. + cancel_auction { + // Create a new auction + let duration: T::BlockNumber = 99u32.into(); + let lease_period_index = LeasePeriodOf::<T>::zero(); + let now = frame_system::Pallet::<T>::block_number(); + Auctions::<T>::new_auction(RawOrigin::Root.into(), duration, lease_period_index)?; + + fill_winners::<T>(lease_period_index); + + let winning_data = Winning::<T>::get(T::BlockNumber::from(0u32)).unwrap(); + for winner in winning_data.iter() { + assert!(winner.is_some()); + } + + // Make winning map full + for i in 0u32 .. T::EndingPeriod::get().saturated_into() { + Winning::<T>::insert(T::BlockNumber::from(i), winning_data.clone()); + } + assert!(AuctionInfo::<T>::get().is_some()); + }: _(RawOrigin::Root) + verify { + assert!(AuctionInfo::<T>::get().is_none()); + } } impl_benchmark_test_suite!( diff --git a/polkadot/runtime/common/src/crowdloan.rs b/polkadot/runtime/common/src/crowdloan.rs index fb63e1e966a081b611c292a0deb9b50d7f9742da..b7973882b5e446ef4f6ee593b377252c5ed9e5e8 100644 --- a/polkadot/runtime/common/src/crowdloan.rs +++ b/polkadot/runtime/common/src/crowdloan.rs @@ -468,12 +468,15 @@ decl_module! { /// This places any deposits that were not withdrawn into the treasury. #[weight = T::WeightInfo::dissolve(T::RemoveKeysLimit::get())] pub fn dissolve(origin, #[compact] index: ParaId) -> DispatchResultWithPostInfo { - ensure_signed(origin)?; + let who = ensure_signed(origin)?; let fund = Self::funds(index).ok_or(Error::<T>::InvalidParaId)?; let now = frame_system::Pallet::<T>::block_number(); let dissolution = fund.end.saturating_add(T::RetirementPeriod::get()); - ensure!((fund.retiring && now >= dissolution) || fund.raised.is_zero(), Error::<T>::NotReadyToDissolve); + + let can_dissolve = (fund.retiring && now >= dissolution) || + (fund.raised.is_zero() && who == fund.depositor); + ensure!(can_dissolve, Error::<T>::NotReadyToDissolve); // Try killing the crowdloan child trie match Self::crowdloan_kill(fund.trie_index) { @@ -1166,7 +1169,7 @@ mod tests { new_test_ext().execute_with(|| { let para = new_para(); - // Set up two crowdloans + // Set up a crowdloan assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, 1, 1, 9, None)); // Fund crowdloans. assert_ok!(Crowdloan::contribute(Origin::signed(2), para, 100, None)); @@ -1175,6 +1178,8 @@ mod tests { assert_noop!(Crowdloan::dissolve(Origin::signed(2), para), Error::<Test>::NotReadyToDissolve); assert_ok!(Crowdloan::withdraw(Origin::signed(2), 2, para)); + // Only crowdloan creator can dissolve when raised is zero. + assert_noop!(Crowdloan::dissolve(Origin::signed(2), para), Error::<Test>::NotReadyToDissolve); assert_ok!(Crowdloan::dissolve(Origin::signed(1), para)); assert_eq!(Balances::free_balance(1), 1000); }); diff --git a/polkadot/runtime/common/src/integration_tests.rs b/polkadot/runtime/common/src/integration_tests.rs index 30e330dbd3d9dfbb723cfd4bdb2f98c639aa0474..82c35b85a67f5858ebd2e1ae41cdb718130961c6 100644 --- a/polkadot/runtime/common/src/integration_tests.rs +++ b/polkadot/runtime/common/src/integration_tests.rs @@ -759,7 +759,7 @@ fn basic_swap_works() { assert_eq!(Balances::free_balance(&crowdloan_account), 0); // Dissolve returns the balance of the person who put a deposit for crowdloan - assert_ok!(Crowdloan::dissolve(Origin::signed(2), ParaId::from(2))); + assert_ok!(Crowdloan::dissolve(Origin::signed(1), ParaId::from(2))); assert_eq!(Balances::reserved_balance(&1), 0); assert_eq!(Balances::reserved_balance(&2), 500 + 20 * 2 * 1); diff --git a/polkadot/runtime/westend/src/weights/auctions.rs b/polkadot/runtime/westend/src/weights/auctions.rs index 612d3e6b9b215373783510d1915c8b1d149ccea6..54f7335b4c0018b638759b7b3329a3492f1a1628 100644 --- a/polkadot/runtime/westend/src/weights/auctions.rs +++ b/polkadot/runtime/westend/src/weights/auctions.rs @@ -16,7 +16,7 @@ //! Autogenerated weights for auctions //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 -//! DATE: 2021-03-14, STEPS: `[50, ]`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2021-03-23, STEPS: `[50, ]`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("westend-dev"), DB CACHE: 128 // Executed Command: @@ -44,18 +44,23 @@ use sp_std::marker::PhantomData; pub struct WeightInfo<T>(PhantomData<T>); impl<T: frame_system::Config> auctions::WeightInfo for WeightInfo<T> { fn new_auction() -> Weight { - (24_105_000 as Weight) + (24_619_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn bid() -> Weight { - (110_317_000 as Weight) + (113_354_000 as Weight) .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn on_initialize() -> Weight { - (602_940_000 as Weight) - .saturating_add(T::DbWeight::get().reads(75 as Weight)) - .saturating_add(T::DbWeight::get().writes(71 as Weight)) + (3_095_173_000 as Weight) + .saturating_add(T::DbWeight::get().reads(625 as Weight)) + .saturating_add(T::DbWeight::get().writes(621 as Weight)) + } + fn cancel_auction() -> Weight { + (841_845_000 as Weight) + .saturating_add(T::DbWeight::get().reads(21 as Weight)) + .saturating_add(T::DbWeight::get().writes(621 as Weight)) } }