mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-14 02:51:08 +00:00
Do not allow any crowdloan contributions during the VRF period (#3346)
* patch `is_ending` logic for sampling * Create AuctionStatus abstraction * clean up apis * fix docs * Add check for contributions during vrf * custom error for this * fix benchmark * opening -> starting * Apply suggestions from code review Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Update runtime/common/src/auctions.rs * avoid divide by zero stuff Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
This commit is contained in:
@@ -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::<T>::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<T>,
|
||||
) -> DispatchResult {
|
||||
T::InitiateOrigin::ensure_origin(origin)?;
|
||||
|
||||
ensure!(!Self::is_in_progress(), Error::<T>::AuctionInProgress);
|
||||
ensure!(lease_period_index >= T::Leaser::lease_period_index(), Error::<T>::LeasePeriodInPast);
|
||||
|
||||
// Bump the counter.
|
||||
let n = AuctionCounter::<T>::mutate(|n| { *n += 1; *n });
|
||||
|
||||
// Set the information.
|
||||
let ending = frame_system::Pallet::<T>::block_number().saturating_add(duration);
|
||||
AuctionInfo::<T>::put((lease_period_index, ending));
|
||||
|
||||
Self::deposit_event(Event::<T>::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<T: Config> Auctioneer for Pallet<T> {
|
||||
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<Self::BlockNumber> {
|
||||
if let Some((_, early_end)) = AuctionInfo::<T>::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<Self::BlockNumber> {
|
||||
let early_end = match AuctionInfo::<T>::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<T: Config> Pallet<T> {
|
||||
// A trick to allow me to initialize large arrays with nothing in them.
|
||||
const EMPTY: Option<(<T as frame_system::Config>::AccountId, ParaId, BalanceOf<T>)> = None;
|
||||
|
||||
/// True if an auction is in progress.
|
||||
pub fn is_in_progress() -> bool {
|
||||
AuctionInfo::<T>::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::<T>::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<T: Config> Pallet<T> {
|
||||
duration: T::BlockNumber,
|
||||
lease_period_index: LeasePeriodOf<T>,
|
||||
) -> DispatchResult {
|
||||
ensure!(!Self::is_in_progress(), Error::<T>::AuctionInProgress);
|
||||
let maybe_auction = AuctionInfo::<T>::get();
|
||||
ensure!(maybe_auction.is_none(), Error::<T>::AuctionInProgress);
|
||||
ensure!(lease_period_index >= T::Leaser::lease_period_index(), Error::<T>::LeasePeriodInPast);
|
||||
|
||||
// Bump the counter.
|
||||
@@ -410,21 +400,24 @@ impl<T: Config> Pallet<T> {
|
||||
// Bidding on latest auction.
|
||||
ensure!(auction_index == AuctionCounter::<T>::get(), Error::<T>::NotCurrentAuction);
|
||||
// Assume it's actually an auction (this should never fail because of above).
|
||||
let (first_lease_period, early_end) = AuctionInfo::<T>::get().ok_or(Error::<T>::NotAuction)?;
|
||||
let late_end = early_end.saturating_add(T::EndingPeriod::get());
|
||||
let (first_lease_period, _) = AuctionInfo::<T>::get().ok_or(Error::<T>::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::<T>::block_number();
|
||||
ensure!(now < late_end, Error::<T>::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::<T>::block_number());
|
||||
// The offset into the ending samples of the auction.
|
||||
let offset = match auction_status {
|
||||
AuctionStatus::NotStarted => return Err(Error::<T>::AuctionEnded.into()),
|
||||
AuctionStatus::StartingPeriod => Zero::zero(),
|
||||
AuctionStatus::EndingPeriod(o, _) => o,
|
||||
AuctionStatus::VrfDelay(_) => return Err(Error::<T>::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::<T>::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::<T>::get(offset)
|
||||
.or_else(|| offset.checked_sub(&One::one()).and_then(Winning::<T>::get))
|
||||
@@ -463,7 +456,7 @@ impl<T: Config> Pallet<T> {
|
||||
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<T: Config> Pallet<T> {
|
||||
// Our random seed was known only after the auction ended. Good to use.
|
||||
let raw_offset_block_number = <T::BlockNumber>::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::<T>::get();
|
||||
Self::deposit_event(Event::<T>::WinningOffset(auction_counter, offset));
|
||||
@@ -835,15 +828,13 @@ mod tests {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(AuctionCounter::<Test>::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::<u32>::NotStarted);
|
||||
|
||||
run_to_block(10);
|
||||
|
||||
assert_eq!(AuctionCounter::<Test>::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::<u32>::NotStarted);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -856,8 +847,7 @@ mod tests {
|
||||
assert_ok!(Auctions::new_auction(Origin::signed(6), 5, 1));
|
||||
|
||||
assert_eq!(AuctionCounter::<Test>::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::<u32>::StartingPeriod);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -918,40 +908,31 @@ mod tests {
|
||||
assert_ok!(Auctions::new_auction(Origin::signed(6), 5, 1));
|
||||
|
||||
assert_eq!(AuctionCounter::<Test>::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::<u32>::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::<u32>::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::<u32>::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::<u32>::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::<u32>::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::<u32>::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::<u32>::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::<u32>::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::<u32>::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::<u32>::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::<u32>::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::<u32>::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::<u32>::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::<u32>::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::<u32>::StartingPeriod);
|
||||
|
||||
run_to_block(10);
|
||||
assert_eq!(Auctions::is_ending(System::block_number()), Some(0));
|
||||
assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::<u32>::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::<u32>::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::<u32>::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::<u32>::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::<u32>::StartingPeriod);
|
||||
|
||||
run_to_block(10);
|
||||
assert_eq!(Auctions::is_ending(System::block_number()), Some(0));
|
||||
assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::<u32>::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::<u32>::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::<u32>::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::<u32>::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::<u32>::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::<u32>::StartingPeriod);
|
||||
|
||||
run_to_block(10);
|
||||
assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::<u32>::EndingPeriod(0, 0));
|
||||
|
||||
run_to_block(11);
|
||||
assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::<u32>::EndingPeriod(0, 1));
|
||||
|
||||
run_to_block(19);
|
||||
assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::<u32>::EndingPeriod(0, 9));
|
||||
|
||||
run_to_block(20);
|
||||
assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::<u32>::EndingPeriod(1, 0));
|
||||
|
||||
run_to_block(25);
|
||||
assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::<u32>::EndingPeriod(1, 5));
|
||||
|
||||
run_to_block(30);
|
||||
assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::<u32>::EndingPeriod(2, 0));
|
||||
|
||||
run_to_block(39);
|
||||
assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::<u32>::EndingPeriod(2, 9));
|
||||
|
||||
run_to_block(40);
|
||||
assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::<u32>::VrfDelay(0));
|
||||
|
||||
run_to_block(44);
|
||||
assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::<u32>::VrfDelay(4));
|
||||
|
||||
set_last_random(Default::default(), 45);
|
||||
run_to_block(45);
|
||||
assert_eq!(Auctions::auction_status(System::block_number()), AuctionStatus::<u32>::NotStarted);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_cancel_auction() {
|
||||
new_test_ext().execute_with(|| {
|
||||
|
||||
@@ -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<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
||||
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::<T>::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::<T>::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::<T>::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<Option<(u64, u64)>> = RefCell::new(None);
|
||||
static VRF_DELAY: RefCell<u64> = RefCell::new(0);
|
||||
static ENDING_PERIOD: RefCell<u64> = RefCell::new(5);
|
||||
static BIDS_PLACED: RefCell<Vec<BidPlaced>> = RefCell::new(Vec::new());
|
||||
static HAS_WON: RefCell<BTreeMap<(ParaId, u64), bool>> = RefCell::new(BTreeMap::new());
|
||||
@@ -855,6 +863,12 @@ mod tests {
|
||||
fn bids() -> Vec<BidPlaced> {
|
||||
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<u64> {
|
||||
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<u64> {
|
||||
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::<u64>::StartingPeriod);
|
||||
assert_eq!(TestAuctioneer::auction_status(5), AuctionStatus::<u64>::EndingPeriod(0, 0));
|
||||
assert_eq!(TestAuctioneer::auction_status(9), AuctionStatus::<u64>::EndingPeriod(4, 0));
|
||||
assert_eq!(TestAuctioneer::auction_status(11), AuctionStatus::<u64>::NotStarted);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1209,11 +1237,41 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
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::<Test>::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::<T>::get().len(), n as usize);
|
||||
let old_endings_count = EndingsCount::<T>::get();
|
||||
}: {
|
||||
|
||||
@@ -41,7 +41,7 @@ use crate::{
|
||||
auctions, crowdloan, slots, paras_registrar,
|
||||
slot_range::SlotRange,
|
||||
traits::{
|
||||
Registrar as RegistrarT, Auctioneer,
|
||||
Registrar as RegistrarT, Auctioneer, AuctionStatus,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -868,7 +868,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::<u32>::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));
|
||||
|
||||
@@ -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<BlockNumber> {
|
||||
/// 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<BlockNumber> AuctionStatus<BlockNumber> {
|
||||
/// 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<Self::BlockNumber>;
|
||||
/// Given the current block number, return the current auction status.
|
||||
fn auction_status(now: Self::BlockNumber) -> AuctionStatus<Self::BlockNumber>;
|
||||
|
||||
/// Place a bid in the current auction.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user