diff --git a/runtime/common/src/auctions.rs b/runtime/common/src/auctions.rs index 07a59c650f73..cd730c4c6fe4 100644 --- a/runtime/common/src/auctions.rs +++ b/runtime/common/src/auctions.rs @@ -27,7 +27,7 @@ use frame_support::{ }; use primitives::v1::Id as ParaId; use crate::slot_range::SlotRange; -use crate::traits::{Leaser, LeaseError, Auctioneer, Registrar}; +use crate::traits::{Leaser, LeaseError, Auctioneer, Registrar, AuctionStatus}; use parity_scale_codec::Decode; pub use pallet::*; @@ -201,7 +201,7 @@ pub mod pallet { // If the current auction was in its ending period last block, then ensure that the (sub-)range // winner information is duplicated from the previous block in case no bids happened in the // last block. - if let Some(offset) = Self::is_ending(n) { + if let AuctionStatus::EndingPeriod(offset, _sub_sample) = Self::auction_status(n) { weight = weight.saturating_add(T::DbWeight::get().reads(1)); if !Winning::::contains_key(&offset) { weight = weight.saturating_add(T::DbWeight::get().writes(1)); @@ -241,19 +241,7 @@ pub mod pallet { #[pallet::compact] lease_period_index: LeasePeriodOf, ) -> DispatchResult { T::InitiateOrigin::ensure_origin(origin)?; - - ensure!(!Self::is_in_progress(), Error::::AuctionInProgress); - ensure!(lease_period_index >= T::Leaser::lease_period_index(), Error::::LeasePeriodInPast); - - // Bump the counter. - let n = AuctionCounter::::mutate(|n| { *n += 1; *n }); - - // Set the information. - let ending = frame_system::Pallet::::block_number().saturating_add(duration); - AuctionInfo::::put((lease_period_index, ending)); - - Self::deposit_event(Event::::AuctionStarted(n, lease_period_index, ending)); - Ok(()) + Self::do_new_auction(duration, lease_period_index) } /// Make a new bid from an account (including a parachain account) for deploying a new @@ -316,16 +304,28 @@ impl Auctioneer for Pallet { Self::do_new_auction(duration, lease_period_index) } - // Returns whether the auction is ending, and which sample number we are on. - fn is_ending(now: Self::BlockNumber) -> Option { - if let Some((_, early_end)) = AuctionInfo::::get() { - if let Some(after_early_end) = now.checked_sub(&early_end) { - if after_early_end < T::EndingPeriod::get() { - return Some(after_early_end / T::SampleLength::get()) - } - } + // Returns the status of the auction given the current block number. + fn auction_status(now: Self::BlockNumber) -> AuctionStatus { + let early_end = match AuctionInfo::::get() { + Some((_, early_end)) => early_end, + None => return AuctionStatus::NotStarted, + }; + + let after_early_end = match now.checked_sub(&early_end) { + Some(after_early_end) => after_early_end, + None => return AuctionStatus::StartingPeriod, + }; + + let ending_period = T::EndingPeriod::get(); + if after_early_end < ending_period { + let sample_length = T::SampleLength::get().max(One::one()); + let sample = after_early_end / sample_length; + let sub_sample = after_early_end % sample_length; + return AuctionStatus::EndingPeriod(sample, sub_sample) + } else { + // This is safe because of the comparison operator above + return AuctionStatus::VrfDelay(after_early_end - ending_period) } - None } fn place_bid( @@ -355,17 +355,6 @@ impl Pallet { // A trick to allow me to initialize large arrays with nothing in them. const EMPTY: Option<(::AccountId, ParaId, BalanceOf)> = None; - /// True if an auction is in progress. - pub fn is_in_progress() -> bool { - AuctionInfo::::get().map_or(false, |(_, early_end)| { - let late_end = early_end.saturating_add(T::EndingPeriod::get()); - // We need to check that the auction isn't in the period where it has definitely ended, but yeah we keep the - // info around because we haven't yet decided *exactly* when in the `EndingPeriod` that it ended. - let now = frame_system::Pallet::::block_number(); - now < late_end - }) - } - /// Create a new auction. /// /// This can only happen when there isn't already an auction in progress. Accepts the `duration` @@ -375,7 +364,8 @@ impl Pallet { duration: T::BlockNumber, lease_period_index: LeasePeriodOf, ) -> DispatchResult { - ensure!(!Self::is_in_progress(), Error::::AuctionInProgress); + let maybe_auction = AuctionInfo::::get(); + ensure!(maybe_auction.is_none(), Error::::AuctionInProgress); ensure!(lease_period_index >= T::Leaser::lease_period_index(), Error::::LeasePeriodInPast); // Bump the counter. @@ -410,21 +400,24 @@ impl Pallet { // Bidding on latest auction. ensure!(auction_index == AuctionCounter::::get(), Error::::NotCurrentAuction); // Assume it's actually an auction (this should never fail because of above). - let (first_lease_period, early_end) = AuctionInfo::::get().ok_or(Error::::NotAuction)?; - let late_end = early_end.saturating_add(T::EndingPeriod::get()); + let (first_lease_period, _) = AuctionInfo::::get().ok_or(Error::::NotAuction)?; - // We need to check that the auction isn't in the period where it has definitely ended, but yeah we keep the - // info around because we haven't yet decided *exactly* when in the `EndingPeriod` that it ended. - let now = frame_system::Pallet::::block_number(); - ensure!(now < late_end, Error::::AuctionEnded); + // Get the auction status and the current sample block. For the starting period, the sample + // block is zero. + let auction_status = Self::auction_status(frame_system::Pallet::::block_number()); + // The offset into the ending samples of the auction. + let offset = match auction_status { + AuctionStatus::NotStarted => return Err(Error::::AuctionEnded.into()), + AuctionStatus::StartingPeriod => Zero::zero(), + AuctionStatus::EndingPeriod(o, _) => o, + AuctionStatus::VrfDelay(_) => return Err(Error::::AuctionEnded.into()), + }; // Our range. let range = SlotRange::new_bounded(first_lease_period, first_slot, last_slot)?; // Range as an array index. let range_index = range as u8 as usize; - let is_ending = Self::is_ending(frame_system::Pallet::::block_number()); - // The offset into the ending samples of the auction. - let offset = is_ending.unwrap_or_default(); + // The current winning ranges. let mut current_winning = Winning::::get(offset) .or_else(|| offset.checked_sub(&One::one()).and_then(Winning::::get)) @@ -463,7 +456,7 @@ impl Pallet { let mut outgoing_winner = Some((bidder.clone(), para, amount)); swap(&mut current_winning[range_index], &mut outgoing_winner); if let Some((who, para, _amount)) = outgoing_winner { - if !is_ending.is_some() && current_winning.iter() + if auction_status.is_starting() && current_winning.iter() .filter_map(Option::as_ref) .all(|&(ref other, other_para, _)| other != &who || other_para != para) { @@ -504,7 +497,7 @@ impl Pallet { // Our random seed was known only after the auction ended. Good to use. let raw_offset_block_number = ::decode(&mut raw_offset.as_ref()) .expect("secure hashes should always be bigger than the block number; qed"); - let offset = (raw_offset_block_number % ending_period) / T::SampleLength::get(); + let offset = (raw_offset_block_number % ending_period) / T::SampleLength::get().max(One::one()); let auction_counter = AuctionCounter::::get(); Self::deposit_event(Event::::WinningOffset(auction_counter, offset)); @@ -835,15 +828,13 @@ mod tests { new_test_ext().execute_with(|| { assert_eq!(AuctionCounter::::get(), 0); assert_eq!(TestLeaser::deposit_held(0u32.into(), &1), 0); - assert_eq!(Auctions::is_in_progress(), false); - assert_eq!(Auctions::is_ending(System::block_number()), None); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::NotStarted); run_to_block(10); assert_eq!(AuctionCounter::::get(), 0); assert_eq!(TestLeaser::deposit_held(0u32.into(), &1), 0); - assert_eq!(Auctions::is_in_progress(), false); - assert_eq!(Auctions::is_ending(System::block_number()), None); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::NotStarted); }); } @@ -856,8 +847,7 @@ mod tests { assert_ok!(Auctions::new_auction(Origin::signed(6), 5, 1)); assert_eq!(AuctionCounter::::get(), 1); - assert_eq!(Auctions::is_in_progress(), true); - assert_eq!(Auctions::is_ending(System::block_number()), None); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod); }); } @@ -918,40 +908,31 @@ mod tests { assert_ok!(Auctions::new_auction(Origin::signed(6), 5, 1)); assert_eq!(AuctionCounter::::get(), 1); - assert_eq!(Auctions::is_in_progress(), true); - assert_eq!(Auctions::is_ending(System::block_number()), None); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod); run_to_block(2); - assert_eq!(Auctions::is_in_progress(), true); - assert_eq!(Auctions::is_ending(System::block_number()), None); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod); run_to_block(3); - assert_eq!(Auctions::is_in_progress(), true); - assert_eq!(Auctions::is_ending(System::block_number()), None); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod); run_to_block(4); - assert_eq!(Auctions::is_in_progress(), true); - assert_eq!(Auctions::is_ending(System::block_number()), None); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod); run_to_block(5); - assert_eq!(Auctions::is_in_progress(), true); - assert_eq!(Auctions::is_ending(System::block_number()), None); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod); run_to_block(6); - assert_eq!(Auctions::is_in_progress(), true); - assert_eq!(Auctions::is_ending(System::block_number()), Some(0)); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(0, 0)); run_to_block(7); - assert_eq!(Auctions::is_in_progress(), true); - assert_eq!(Auctions::is_ending(System::block_number()), Some(1)); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(1, 0)); run_to_block(8); - assert_eq!(Auctions::is_in_progress(), true); - assert_eq!(Auctions::is_ending(System::block_number()), Some(2)); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(2, 0)); run_to_block(9); - assert_eq!(Auctions::is_in_progress(), false); - assert_eq!(Auctions::is_ending(System::block_number()), None); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::NotStarted); }); } @@ -983,19 +964,19 @@ mod tests { 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_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod); run_to_block(8); // Auction has not yet ended. assert_eq!(leases(), vec![]); - assert!(Auctions::is_in_progress()); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(2, 0)); // This will prevent the auction's winner from being decided in the next block, since the random // seed was known before the final bids were made. set_last_random(H256::zero(), 8); // Auction definitely ended now, but we don't know exactly when in the last 3 blocks yet since // no randomness available yet. run_to_block(9); - // Auction has now ended... - assert!(!Auctions::is_in_progress()); - // ...But auction winner still not yet decided, so no leases yet. + // Auction has now ended... But auction winner still not yet decided, so no leases yet. + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::VrfDelay(0)); assert_eq!(leases(), vec![]); // Random seed now updated to a value known at block 9, when the auction ended. This means @@ -1003,7 +984,7 @@ mod tests { set_last_random(H256::zero(), 9); run_to_block(10); // Auction ended and winner selected - assert!(!Auctions::is_in_progress()); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::NotStarted); assert_eq!(leases(), vec![ ((0.into(), 1), LeaseData { leaser: 1, amount: 1 }), ((0.into(), 2), LeaseData { leaser: 1, amount: 1 }), @@ -1289,26 +1270,26 @@ mod tests { assert_ok!(Auctions::bid(Origin::signed(1), para_1, 1, 1, 4, 10)); assert_ok!(Auctions::bid(Origin::signed(2), para_2, 1, 3, 4, 20)); - assert_eq!(Auctions::is_ending(System::block_number()), None); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod); let mut winning = [None; SlotRange::SLOT_RANGE_COUNT]; winning[SlotRange::ZeroThree as u8 as usize] = Some((1, para_1, 10)); winning[SlotRange::TwoThree as u8 as usize] = Some((2, para_2, 20)); assert_eq!(Auctions::winning(0), Some(winning)); run_to_block(9); - assert_eq!(Auctions::is_ending(System::block_number()), None); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod); run_to_block(10); - assert_eq!(Auctions::is_ending(System::block_number()), Some(0)); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(0, 0)); assert_eq!(Auctions::winning(0), Some(winning)); run_to_block(11); - assert_eq!(Auctions::is_ending(System::block_number()), Some(1)); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(1, 0)); assert_eq!(Auctions::winning(1), Some(winning)); assert_ok!(Auctions::bid(Origin::signed(3), para_3, 1, 3, 4, 30)); run_to_block(12); - assert_eq!(Auctions::is_ending(System::block_number()), Some(2)); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(2, 0)); winning[SlotRange::TwoThree as u8 as usize] = Some((3, para_3, 30)); assert_eq!(Auctions::winning(2), Some(winning)); }); @@ -1342,17 +1323,17 @@ mod tests { assert_ok!(Auctions::bid(Origin::signed(1), para_1, 1, 11, 14, 10)); assert_ok!(Auctions::bid(Origin::signed(2), para_2, 1, 13, 14, 20)); - assert_eq!(Auctions::is_ending(System::block_number()), None); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod); let mut winning = [None; SlotRange::SLOT_RANGE_COUNT]; winning[SlotRange::ZeroThree as u8 as usize] = Some((1, para_1, 10)); winning[SlotRange::TwoThree as u8 as usize] = Some((2, para_2, 20)); assert_eq!(Auctions::winning(0), Some(winning)); run_to_block(9); - assert_eq!(Auctions::is_ending(System::block_number()), None); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod); run_to_block(10); - assert_eq!(Auctions::is_ending(System::block_number()), Some(0)); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(0, 0)); assert_eq!(Auctions::winning(0), Some(winning)); // New bids update the current winning @@ -1361,7 +1342,7 @@ mod tests { assert_eq!(Auctions::winning(0), Some(winning)); run_to_block(20); - assert_eq!(Auctions::is_ending(System::block_number()), Some(1)); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(1, 0)); assert_eq!(Auctions::winning(1), Some(winning)); run_to_block(25); // Overbid mid sample @@ -1370,13 +1351,13 @@ mod tests { assert_eq!(Auctions::winning(1), Some(winning)); run_to_block(30); - assert_eq!(Auctions::is_ending(System::block_number()), Some(2)); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(2, 0)); assert_eq!(Auctions::winning(2), Some(winning)); set_last_random(H256::from([254; 32]), 40); run_to_block(40); // Auction ended and winner selected - assert!(!Auctions::is_in_progress()); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::NotStarted); assert_eq!(leases(), vec![ ((3.into(), 13), LeaseData { leaser: 3, amount: 30 }), ((3.into(), 14), LeaseData { leaser: 3, amount: 30 }), @@ -1384,6 +1365,54 @@ mod tests { }); } + #[test] + fn auction_status_works() { + new_test_ext().execute_with(|| { + EndingPeriod::set(30); + SampleLength::set(10); + set_last_random(Default::default(), 0); + + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::NotStarted); + + run_to_block(1); + assert_ok!(Auctions::new_auction(Origin::signed(6), 9, 11)); + + run_to_block(9); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod); + + run_to_block(10); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(0, 0)); + + run_to_block(11); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(0, 1)); + + run_to_block(19); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(0, 9)); + + run_to_block(20); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(1, 0)); + + run_to_block(25); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(1, 5)); + + run_to_block(30); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(2, 0)); + + run_to_block(39); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(2, 9)); + + run_to_block(40); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::VrfDelay(0)); + + run_to_block(44); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::VrfDelay(4)); + + set_last_random(Default::default(), 45); + run_to_block(45); + assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::::NotStarted); + }); + } + #[test] fn can_cancel_auction() { new_test_ext().execute_with(|| { diff --git a/runtime/common/src/crowdloan.rs b/runtime/common/src/crowdloan.rs index cfd0ed7320b2..74a80121e85c 100644 --- a/runtime/common/src/crowdloan.rs +++ b/runtime/common/src/crowdloan.rs @@ -289,14 +289,17 @@ pub mod pallet { /// The provided memo is too large. MemoTooLarge, /// The fund is already in NewRaise - AlreadyInNewRaise + AlreadyInNewRaise, + /// No contributions allowed during the VRF delay + VrfDelayInProgress, } #[pallet::hooks] impl Hooks> for Pallet { - fn on_initialize(n: T::BlockNumber) -> frame_support::weights::Weight { - if let Some(n) = T::Auctioneer::is_ending(n) { - if n.is_zero() { + fn on_initialize(num: T::BlockNumber) -> frame_support::weights::Weight { + if let Some((sample, sub_sample)) = T::Auctioneer::auction_status(num).is_ending() { + // This is the very first block in the ending period + if sample.is_zero() && sub_sample.is_zero() { // first block of ending period. EndingsCount::::mutate(|c| *c += 1); } @@ -413,6 +416,10 @@ pub mod pallet { let fund_account = Self::fund_account_id(index); ensure!(!T::Auctioneer::has_won_an_auction(index, &fund_account), Error::::BidOrLeaseActive); + // We disallow any crowdloan contributions during the VRF Period, so that people do not sneak their + // contributions into the auction when it would not impact the outcome. + ensure!(!T::Auctioneer::auction_status(now).is_vrf(), Error::::VrfDelayInProgress); + let (old_balance, memo) = Self::contribution_get(fund.trie_index, &who); if let Some(ref verifier) = fund.verifier { @@ -427,7 +434,7 @@ pub mod pallet { let balance = old_balance.saturating_add(value); Self::contribution_put(fund.trie_index, &who, &balance, &memo); - if T::Auctioneer::is_ending(now).is_some() { + if T::Auctioneer::auction_status(now).is_ending().is_some() { match fund.last_contribution { // In ending period; must ensure that we are in NewRaise. LastContribution::Ending(n) if n == now => { @@ -758,7 +765,7 @@ mod tests { }; use crate::{ mock::TestRegistrar, - traits::OnSwap, + traits::{OnSwap, AuctionStatus}, crowdloan, }; use sp_keystore::{KeystoreExt, testing::KeyStore}; @@ -837,6 +844,7 @@ mod tests { } thread_local! { static AUCTION: RefCell> = RefCell::new(None); + static VRF_DELAY: RefCell = RefCell::new(0); static ENDING_PERIOD: RefCell = RefCell::new(5); static BIDS_PLACED: RefCell> = RefCell::new(Vec::new()); static HAS_WON: RefCell> = RefCell::new(BTreeMap::new()); @@ -855,6 +863,12 @@ mod tests { fn bids() -> Vec { BIDS_PLACED.with(|p| p.borrow().clone()) } + fn vrf_delay() -> u64 { + VRF_DELAY.with(|p| p.borrow().clone()) + } + fn set_vrf_delay(delay: u64) { + VRF_DELAY.with(|p| *p.borrow_mut() = delay); + } // Emulate what would happen if we won an auction: // balance is reserved and a deposit_held is recorded fn set_winner(para: ParaId, who: u64, winner: bool) { @@ -884,15 +898,29 @@ mod tests { Ok(()) } - fn is_ending(now: u64) -> Option { - if let Some((_, early_end)) = auction() { - if let Some(after_early_end) = now.checked_sub(early_end) { - if after_early_end < ending_period() { - return Some(after_early_end) - } + fn auction_status(now: u64) -> AuctionStatus { + let early_end = match auction() { + Some((_, early_end)) => early_end, + None => return AuctionStatus::NotStarted, + }; + let after_early_end = match now.checked_sub(early_end) { + Some(after_early_end) => after_early_end, + None => return AuctionStatus::StartingPeriod, + }; + + let ending_period = ending_period(); + if after_early_end < ending_period { + return AuctionStatus::EndingPeriod(after_early_end, 0) + } else { + let after_end = after_early_end - ending_period; + // Optional VRF delay + if after_end < vrf_delay() { + return AuctionStatus::VrfDelay(after_end); + } else { + // VRF delay is done, so we just end the auction + return AuctionStatus::NotStarted; } } - None } fn place_bid( @@ -997,10 +1025,10 @@ mod tests { assert_ok!(TestAuctioneer::place_bid(1, 2.into(), 0, 3, 6)); let b = BidPlaced { height: 0, bidder: 1, para: 2.into(), first_period: 0, last_period: 3, amount: 6 }; assert_eq!(bids(), vec![b]); - assert_eq!(TestAuctioneer::is_ending(4), None); - assert_eq!(TestAuctioneer::is_ending(5), Some(0)); - assert_eq!(TestAuctioneer::is_ending(9), Some(4)); - assert_eq!(TestAuctioneer::is_ending(11), None); + assert_eq!(TestAuctioneer::auction_status(4), AuctionStatus::::StartingPeriod); + assert_eq!(TestAuctioneer::auction_status(5), AuctionStatus::::EndingPeriod(0, 0)); + assert_eq!(TestAuctioneer::auction_status(9), AuctionStatus::::EndingPeriod(4, 0)); + assert_eq!(TestAuctioneer::auction_status(11), AuctionStatus::::NotStarted); }); } @@ -1210,10 +1238,40 @@ mod tests { } #[test] - fn bidding_works() { + fn cannot_contribute_during_vrf() { new_test_ext().execute_with(|| { + set_vrf_delay(5); + let para = new_para(); + let first_period = 1; + let last_period = 4; + + assert_ok!(TestAuctioneer::new_auction(5, 0)); + // Set up a crowdloan + assert_ok!(Crowdloan::create(Origin::signed(1), para, 1000, first_period, last_period, 20, None)); + + run_to_block(8); + // Can def contribute when auction is running. + assert!(TestAuctioneer::auction_status(System::block_number()).is_ending().is_some()); + assert_ok!(Crowdloan::contribute(Origin::signed(2), para, 250, None)); + + run_to_block(10); + // Can't contribute when auction is in the VRF delay period. + assert!(TestAuctioneer::auction_status(System::block_number()).is_vrf()); + assert_noop!(Crowdloan::contribute(Origin::signed(2), para, 250, None), Error::::VrfDelayInProgress); + + run_to_block(15); + // Its fine to contribute when no auction is running. + assert!(!TestAuctioneer::auction_status(System::block_number()).is_in_progress()); + assert_ok!(Crowdloan::contribute(Origin::signed(2), para, 250, None)); + }) + } + + #[test] + fn bidding_works() { + new_test_ext().execute_with(|| { + let para = new_para(); let first_period = 1; let last_period = 4; @@ -1811,7 +1869,7 @@ mod benchmarking { .ok_or("duration of auction less than zero")?; T::Auctioneer::new_auction(duration, lease_period_index)?; - assert_eq!(T::Auctioneer::is_ending(end_block), Some(0u32.into())); + assert_eq!(T::Auctioneer::auction_status(end_block).is_ending(), Some((0u32.into(), 0u32.into()))); assert_eq!(NewRaise::::get().len(), n as usize); let old_endings_count = EndingsCount::::get(); }: { diff --git a/runtime/common/src/integration_tests.rs b/runtime/common/src/integration_tests.rs index 28e404e04665..5063e2efe48c 100644 --- a/runtime/common/src/integration_tests.rs +++ b/runtime/common/src/integration_tests.rs @@ -41,7 +41,7 @@ use crate::{ auctions, crowdloan, slots, paras_registrar, slot_range::SlotRange, traits::{ - Registrar as RegistrarT, Auctioneer, + Registrar as RegistrarT, Auctioneer, AuctionStatus, }, }; @@ -865,7 +865,7 @@ fn crowdloan_ending_period_bid() { // Go to beginning of ending period run_to_block(100); - assert_eq!(Auctions::is_ending(100), Some(0)); + assert_eq!(Auctions::auction_status(100), AuctionStatus::::EndingPeriod(0, 0)); let mut winning = [None; SlotRange::SLOT_RANGE_COUNT]; winning[SlotRange::ZeroOne as u8 as usize] = Some((2, ParaId::from(2001), 900)); winning[SlotRange::ZeroThree as u8 as usize] = Some((crowdloan_account, ParaId::from(2000), total)); diff --git a/runtime/common/src/traits.rs b/runtime/common/src/traits.rs index fb157cbf841c..49da7e4fe4e6 100644 --- a/runtime/common/src/traits.rs +++ b/runtime/common/src/traits.rs @@ -137,6 +137,47 @@ pub trait Leaser { fn lease_period_index() -> Self::LeasePeriod; } +/// An enum which tracks the status of the auction system, and which phase it is in. +#[derive(PartialEq, Debug)] +pub enum AuctionStatus { + /// An auction has not started yet. + NotStarted, + /// We are in the starting period of the auction, collecting initial bids. + StartingPeriod, + /// We are in the ending period of the auction, where we are taking snapshots of the winning + /// bids. This state supports "sampling", where we may only take a snapshot every N blocks. + /// In this case, the first number is the current sample number, and the second number + /// is the sub-sample. i.e. for sampling every 20 blocks, the 25th block in the ending period + /// will be `EndingPeriod(1, 5)`. + EndingPeriod(BlockNumber, BlockNumber), + /// We have completed the bidding process and are waiting for the VRF to return some acceptable + /// randomness to select the winner. The number represents how many blocks we have been waiting. + VrfDelay(BlockNumber), +} + +impl AuctionStatus { + /// Returns true if the auction is in any state other than `NotStarted`. + pub fn is_in_progress(&self) -> bool { + !matches!(self, Self::NotStarted) + } + /// Return true if the auction is in the starting period. + pub fn is_starting(&self) -> bool { + matches!(self, Self::StartingPeriod) + } + /// Returns `Some(sample, sub_sample)` if the auction is in the `EndingPeriod`, + /// otherwise returns `None`. + pub fn is_ending(self) -> Option<(BlockNumber, BlockNumber)> { + match self { + Self::EndingPeriod(sample, sub_sample) => Some((sample, sub_sample)), + _ => None, + } + } + /// Returns true if the auction is in the `VrfDelay` period. + pub fn is_vrf(&self) -> bool { + matches!(self, Self::VrfDelay(_)) + } +} + pub trait Auctioneer { /// An account identifier for a leaser. type AccountId; @@ -157,9 +198,8 @@ pub trait Auctioneer { /// are to be auctioned. fn new_auction(duration: Self::BlockNumber, lease_period_index: Self::LeasePeriod) -> DispatchResult; - /// Returns `Some(n)` if the `now` block is part of the ending period of an auction, where `n` - /// represents how far into the ending period this block is. Otherwise, returns `None`. - fn is_ending(now: Self::BlockNumber) -> Option; + /// Given the current block number, return the current auction status. + fn auction_status(now: Self::BlockNumber) -> AuctionStatus; /// Place a bid in the current auction. ///