use crate::{
slot_range::SlotRange,
traits::{AuctionStatus, Auctioneer, LeaseError, Leaser, Registrar},
};
use alloc::{vec, vec::Vec};
use codec::Decode;
use core::mem::swap;
use frame_support::{
dispatch::DispatchResult,
ensure,
traits::{Currency, Get, Randomness, ReservableCurrency},
weights::Weight,
};
use frame_system::pallet_prelude::BlockNumberFor;
pub use pallet::*;
use polkadot_primitives::Id as ParaId;
use sp_runtime::traits::{CheckedSub, One, Saturating, Zero};
type CurrencyOf<T> = <<T as Config>::Leaser as Leaser<BlockNumberFor<T>>>::Currency;
type BalanceOf<T> = <<<T as Config>::Leaser as Leaser<BlockNumberFor<T>>>::Currency as Currency<
<T as frame_system::Config>::AccountId,
>>::Balance;
pub trait WeightInfo {
fn new_auction() -> Weight;
fn bid() -> Weight;
fn cancel_auction() -> Weight;
fn on_initialize() -> Weight;
}
pub struct TestWeightInfo;
impl WeightInfo for TestWeightInfo {
fn new_auction() -> Weight {
Weight::zero()
}
fn bid() -> Weight {
Weight::zero()
}
fn cancel_auction() -> Weight {
Weight::zero()
}
fn on_initialize() -> Weight {
Weight::zero()
}
}
pub type AuctionIndex = u32;
type LeasePeriodOf<T> = <<T as Config>::Leaser as Leaser<BlockNumberFor<T>>>::LeasePeriod;
type WinningData<T> = [Option<(<T as frame_system::Config>::AccountId, ParaId, BalanceOf<T>)>;
SlotRange::SLOT_RANGE_COUNT];
type WinnersData<T> =
Vec<(<T as frame_system::Config>::AccountId, ParaId, BalanceOf<T>, SlotRange)>;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::{dispatch::DispatchClass, pallet_prelude::*, traits::EnsureOrigin};
use frame_system::{ensure_root, ensure_signed, pallet_prelude::*};
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type Leaser: Leaser<
BlockNumberFor<Self>,
AccountId = Self::AccountId,
LeasePeriod = BlockNumberFor<Self>,
>;
type Registrar: Registrar<AccountId = Self::AccountId>;
#[pallet::constant]
type EndingPeriod: Get<BlockNumberFor<Self>>;
#[pallet::constant]
type SampleLength: Get<BlockNumberFor<Self>>;
type Randomness: Randomness<Self::Hash, BlockNumberFor<Self>>;
type InitiateOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type WeightInfo: WeightInfo;
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
AuctionStarted {
auction_index: AuctionIndex,
lease_period: LeasePeriodOf<T>,
ending: BlockNumberFor<T>,
},
AuctionClosed { auction_index: AuctionIndex },
Reserved { bidder: T::AccountId, extra_reserved: BalanceOf<T>, total_amount: BalanceOf<T> },
Unreserved { bidder: T::AccountId, amount: BalanceOf<T> },
ReserveConfiscated { para_id: ParaId, leaser: T::AccountId, amount: BalanceOf<T> },
BidAccepted {
bidder: T::AccountId,
para_id: ParaId,
amount: BalanceOf<T>,
first_slot: LeasePeriodOf<T>,
last_slot: LeasePeriodOf<T>,
},
WinningOffset { auction_index: AuctionIndex, block_number: BlockNumberFor<T> },
}
#[pallet::error]
pub enum Error<T> {
AuctionInProgress,
LeasePeriodInPast,
ParaNotRegistered,
NotCurrentAuction,
NotAuction,
AuctionEnded,
AlreadyLeasedOut,
}
#[pallet::storage]
pub type AuctionCounter<T> = StorageValue<_, AuctionIndex, ValueQuery>;
#[pallet::storage]
pub type AuctionInfo<T: Config> = StorageValue<_, (LeasePeriodOf<T>, BlockNumberFor<T>)>;
#[pallet::storage]
pub type ReservedAmounts<T: Config> =
StorageMap<_, Twox64Concat, (T::AccountId, ParaId), BalanceOf<T>>;
#[pallet::storage]
pub type Winning<T: Config> = StorageMap<_, Twox64Concat, BlockNumberFor<T>, WinningData<T>>;
#[pallet::extra_constants]
impl<T: Config> Pallet<T> {
#[pallet::constant_name(SlotRangeCount)]
fn slot_range_count() -> u32 {
SlotRange::SLOT_RANGE_COUNT as u32
}
#[pallet::constant_name(LeasePeriodsPerSlot)]
fn lease_periods_per_slot() -> u32 {
SlotRange::LEASE_PERIODS_PER_SLOT as u32
}
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(n: BlockNumberFor<T>) -> Weight {
let mut weight = T::DbWeight::get().reads(1);
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));
let winning_data = offset
.checked_sub(&One::one())
.and_then(Winning::<T>::get)
.unwrap_or([Self::EMPTY; SlotRange::SLOT_RANGE_COUNT]);
Winning::<T>::insert(offset, winning_data);
}
}
if let Some((winning_ranges, auction_lease_period_index)) = Self::check_auction_end(n) {
Self::manage_auction_end(auction_lease_period_index, winning_ranges);
weight = weight.saturating_add(T::WeightInfo::on_initialize());
}
weight
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight((T::WeightInfo::new_auction(), DispatchClass::Operational))]
pub fn new_auction(
origin: OriginFor<T>,
#[pallet::compact] duration: BlockNumberFor<T>,
#[pallet::compact] lease_period_index: LeasePeriodOf<T>,
) -> DispatchResult {
T::InitiateOrigin::ensure_origin(origin)?;
Self::do_new_auction(duration, lease_period_index)
}
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::bid())]
pub fn bid(
origin: OriginFor<T>,
#[pallet::compact] para: ParaId,
#[pallet::compact] auction_index: AuctionIndex,
#[pallet::compact] first_slot: LeasePeriodOf<T>,
#[pallet::compact] last_slot: LeasePeriodOf<T>,
#[pallet::compact] amount: BalanceOf<T>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
Self::handle_bid(who, para, auction_index, first_slot, last_slot, amount)?;
Ok(())
}
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::cancel_auction())]
pub fn cancel_auction(origin: OriginFor<T>) -> DispatchResult {
ensure_root(origin)?;
for ((bidder, _), amount) in ReservedAmounts::<T>::drain() {
CurrencyOf::<T>::unreserve(&bidder, amount);
}
#[allow(deprecated)]
Winning::<T>::remove_all(None);
AuctionInfo::<T>::kill();
Ok(())
}
}
}
impl<T: Config> Auctioneer<BlockNumberFor<T>> for Pallet<T> {
type AccountId = T::AccountId;
type LeasePeriod = BlockNumberFor<T>;
type Currency = CurrencyOf<T>;
fn new_auction(
duration: BlockNumberFor<T>,
lease_period_index: LeasePeriodOf<T>,
) -> DispatchResult {
Self::do_new_auction(duration, lease_period_index)
}
fn auction_status(now: BlockNumberFor<T>) -> AuctionStatus<BlockNumberFor<T>> {
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 {
return AuctionStatus::VrfDelay(after_early_end - ending_period)
}
}
fn place_bid(
bidder: T::AccountId,
para: ParaId,
first_slot: LeasePeriodOf<T>,
last_slot: LeasePeriodOf<T>,
amount: BalanceOf<T>,
) -> DispatchResult {
Self::handle_bid(bidder, para, AuctionCounter::<T>::get(), first_slot, last_slot, amount)
}
fn lease_period_index(b: BlockNumberFor<T>) -> Option<(Self::LeasePeriod, bool)> {
T::Leaser::lease_period_index(b)
}
#[cfg(any(feature = "runtime-benchmarks", test))]
fn lease_period_length() -> (BlockNumberFor<T>, BlockNumberFor<T>) {
T::Leaser::lease_period_length()
}
fn has_won_an_auction(para: ParaId, bidder: &T::AccountId) -> bool {
!T::Leaser::deposit_held(para, bidder).is_zero()
}
}
impl<T: Config> Pallet<T> {
const EMPTY: Option<(<T as frame_system::Config>::AccountId, ParaId, BalanceOf<T>)> = None;
fn do_new_auction(
duration: BlockNumberFor<T>,
lease_period_index: LeasePeriodOf<T>,
) -> DispatchResult {
let maybe_auction = AuctionInfo::<T>::get();
ensure!(maybe_auction.is_none(), Error::<T>::AuctionInProgress);
let now = frame_system::Pallet::<T>::block_number();
if let Some((current_lease_period, _)) = T::Leaser::lease_period_index(now) {
ensure!(lease_period_index >= current_lease_period, Error::<T>::LeasePeriodInPast);
}
let n = AuctionCounter::<T>::mutate(|n| {
*n += 1;
*n
});
let ending = frame_system::Pallet::<T>::block_number().saturating_add(duration);
AuctionInfo::<T>::put((lease_period_index, ending));
Self::deposit_event(Event::<T>::AuctionStarted {
auction_index: n,
lease_period: lease_period_index,
ending,
});
Ok(())
}
pub fn handle_bid(
bidder: T::AccountId,
para: ParaId,
auction_index: u32,
first_slot: LeasePeriodOf<T>,
last_slot: LeasePeriodOf<T>,
amount: BalanceOf<T>,
) -> DispatchResult {
ensure!(T::Registrar::is_registered(para), Error::<T>::ParaNotRegistered);
ensure!(auction_index == AuctionCounter::<T>::get(), Error::<T>::NotCurrentAuction);
let (first_lease_period, _) = AuctionInfo::<T>::get().ok_or(Error::<T>::NotAuction)?;
let auction_status = Self::auction_status(frame_system::Pallet::<T>::block_number());
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()),
};
ensure!(
!T::Leaser::already_leased(para, first_slot, last_slot),
Error::<T>::AlreadyLeasedOut
);
let range = SlotRange::new_bounded(first_lease_period, first_slot, last_slot)?;
let range_index = range as u8 as usize;
let mut current_winning = Winning::<T>::get(offset)
.or_else(|| offset.checked_sub(&One::one()).and_then(Winning::<T>::get))
.unwrap_or([Self::EMPTY; SlotRange::SLOT_RANGE_COUNT]);
if current_winning[range_index].as_ref().map_or(true, |last| amount > last.2) {
let existing_lease_deposit = T::Leaser::deposit_held(para, &bidder);
let reserve_required = amount.saturating_sub(existing_lease_deposit);
let bidder_para = (bidder.clone(), para);
let already_reserved = ReservedAmounts::<T>::get(&bidder_para).unwrap_or_default();
if let Some(additional) = reserve_required.checked_sub(&already_reserved) {
CurrencyOf::<T>::reserve(&bidder, additional)?;
ReservedAmounts::<T>::insert(&bidder_para, reserve_required);
Self::deposit_event(Event::<T>::Reserved {
bidder: bidder.clone(),
extra_reserved: additional,
total_amount: reserve_required,
});
}
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 auction_status.is_starting() &&
current_winning
.iter()
.filter_map(Option::as_ref)
.all(|&(ref other, other_para, _)| other != &who || other_para != para)
{
if let Some(amount) = ReservedAmounts::<T>::take(&(who.clone(), para)) {
let err_amt = CurrencyOf::<T>::unreserve(&who, amount);
debug_assert!(err_amt.is_zero());
Self::deposit_event(Event::<T>::Unreserved { bidder: who, amount });
}
}
}
Winning::<T>::insert(offset, ¤t_winning);
Self::deposit_event(Event::<T>::BidAccepted {
bidder,
para_id: para,
amount,
first_slot,
last_slot,
});
}
Ok(())
}
fn check_auction_end(now: BlockNumberFor<T>) -> Option<(WinningData<T>, LeasePeriodOf<T>)> {
if let Some((lease_period_index, early_end)) = AuctionInfo::<T>::get() {
let ending_period = T::EndingPeriod::get();
let late_end = early_end.saturating_add(ending_period);
let is_ended = now >= late_end;
if is_ended {
let (raw_offset, known_since) = T::Randomness::random(&b"para_auction"[..]);
if late_end <= known_since {
let raw_offset_block_number = <BlockNumberFor<T>>::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().max(One::one());
let auction_counter = AuctionCounter::<T>::get();
Self::deposit_event(Event::<T>::WinningOffset {
auction_index: auction_counter,
block_number: offset,
});
let res = Winning::<T>::get(offset)
.unwrap_or([Self::EMPTY; SlotRange::SLOT_RANGE_COUNT]);
#[allow(deprecated)]
Winning::<T>::remove_all(None);
AuctionInfo::<T>::kill();
return Some((res, lease_period_index))
}
}
}
None
}
fn manage_auction_end(
auction_lease_period_index: LeasePeriodOf<T>,
winning_ranges: WinningData<T>,
) {
for ((bidder, _), amount) in ReservedAmounts::<T>::drain() {
CurrencyOf::<T>::unreserve(&bidder, amount);
}
let winners = Self::calculate_winners(winning_ranges);
for (leaser, para, amount, range) in winners.into_iter() {
let begin_offset = LeasePeriodOf::<T>::from(range.as_pair().0 as u32);
let period_begin = auction_lease_period_index + begin_offset;
let period_count = LeasePeriodOf::<T>::from(range.len() as u32);
match T::Leaser::lease_out(para, &leaser, amount, period_begin, period_count) {
Err(LeaseError::ReserveFailed) |
Err(LeaseError::AlreadyEnded) |
Err(LeaseError::NoLeasePeriod) => {
},
Err(LeaseError::AlreadyLeased) => {
if CurrencyOf::<T>::reserve(&leaser, amount).is_ok() {
Self::deposit_event(Event::<T>::ReserveConfiscated {
para_id: para,
leaser,
amount,
});
}
},
Ok(()) => {}, }
}
Self::deposit_event(Event::<T>::AuctionClosed {
auction_index: AuctionCounter::<T>::get(),
});
}
fn calculate_winners(mut winning: WinningData<T>) -> WinnersData<T> {
let winning_ranges = {
let mut best_winners_ending_at: [(Vec<SlotRange>, BalanceOf<T>);
SlotRange::LEASE_PERIODS_PER_SLOT] = Default::default();
let best_bid = |range: SlotRange| {
winning[range as u8 as usize]
.as_ref()
.map(|(_, _, amount)| *amount * (range.len() as u32).into())
};
for i in 0..SlotRange::LEASE_PERIODS_PER_SLOT {
let r = SlotRange::new_bounded(0, 0, i as u32).expect("`i < LPPS`; qed");
if let Some(bid) = best_bid(r) {
best_winners_ending_at[i] = (vec![r], bid);
}
for j in 0..i {
let r = SlotRange::new_bounded(0, j as u32 + 1, i as u32)
.expect("`i < LPPS`; `j < i`; `j + 1 < LPPS`; qed");
if let Some(mut bid) = best_bid(r) {
bid += best_winners_ending_at[j].1;
if bid > best_winners_ending_at[i].1 {
let mut new_winners = best_winners_ending_at[j].0.clone();
new_winners.push(r);
best_winners_ending_at[i] = (new_winners, bid);
}
} else {
if best_winners_ending_at[j].1 > best_winners_ending_at[i].1 {
best_winners_ending_at[i] = best_winners_ending_at[j].clone();
}
}
}
}
best_winners_ending_at[SlotRange::LEASE_PERIODS_PER_SLOT - 1].0.clone()
};
winning_ranges
.into_iter()
.filter_map(|range| {
winning[range as u8 as usize]
.take()
.map(|(bidder, para, amount)| (bidder, para, amount, range))
})
.collect::<Vec<_>>()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{auctions, mock::TestRegistrar};
use frame_support::{
assert_noop, assert_ok, assert_storage_noop, derive_impl, ord_parameter_types,
parameter_types,
traits::{EitherOfDiverse, OnFinalize, OnInitialize},
};
use frame_system::{EnsureRoot, EnsureSignedBy};
use pallet_balances;
use polkadot_primitives::{BlockNumber, Id as ParaId};
use polkadot_primitives_test_helpers::{dummy_hash, dummy_head_data, dummy_validation_code};
use sp_core::H256;
use sp_runtime::{
traits::{BlakeTwo256, IdentityLookup},
BuildStorage,
DispatchError::BadOrigin,
};
use std::{cell::RefCell, collections::BTreeMap};
type Block = frame_system::mocking::MockBlockU32<Test>;
frame_support::construct_runtime!(
pub enum Test
{
System: frame_system,
Balances: pallet_balances,
Auctions: auctions,
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
type Nonce = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Block = Block;
type RuntimeEvent = RuntimeEvent;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for Test {
type AccountStore = System;
}
#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug)]
pub struct LeaseData {
leaser: u64,
amount: u64,
}
thread_local! {
pub static LEASES:
RefCell<BTreeMap<(ParaId, BlockNumber), LeaseData>> = RefCell::new(BTreeMap::new());
}
fn leases() -> Vec<((ParaId, BlockNumber), LeaseData)> {
LEASES.with(|p| (&*p.borrow()).clone().into_iter().collect::<Vec<_>>())
}
pub struct TestLeaser;
impl Leaser<BlockNumber> for TestLeaser {
type AccountId = u64;
type LeasePeriod = BlockNumber;
type Currency = Balances;
fn lease_out(
para: ParaId,
leaser: &Self::AccountId,
amount: <Self::Currency as Currency<Self::AccountId>>::Balance,
period_begin: Self::LeasePeriod,
period_count: Self::LeasePeriod,
) -> Result<(), LeaseError> {
LEASES.with(|l| {
let mut leases = l.borrow_mut();
let now = System::block_number();
let (current_lease_period, _) =
Self::lease_period_index(now).ok_or(LeaseError::NoLeasePeriod)?;
if period_begin < current_lease_period {
return Err(LeaseError::AlreadyEnded)
}
for period in period_begin..(period_begin + period_count) {
if leases.contains_key(&(para, period)) {
return Err(LeaseError::AlreadyLeased)
}
leases.insert((para, period), LeaseData { leaser: *leaser, amount });
}
Ok(())
})
}
fn deposit_held(
para: ParaId,
leaser: &Self::AccountId,
) -> <Self::Currency as Currency<Self::AccountId>>::Balance {
leases()
.iter()
.filter_map(|((id, _period), data)| {
if id == ¶ && &data.leaser == leaser {
Some(data.amount)
} else {
None
}
})
.max()
.unwrap_or_default()
}
fn lease_period_length() -> (BlockNumber, BlockNumber) {
(10, 0)
}
fn lease_period_index(b: BlockNumber) -> Option<(Self::LeasePeriod, bool)> {
let (lease_period_length, offset) = Self::lease_period_length();
let b = b.checked_sub(offset)?;
let lease_period = b / lease_period_length;
let first_block = (b % lease_period_length).is_zero();
Some((lease_period, first_block))
}
fn already_leased(
para_id: ParaId,
first_period: Self::LeasePeriod,
last_period: Self::LeasePeriod,
) -> bool {
leases().into_iter().any(|((para, period), _data)| {
para == para_id && first_period <= period && period <= last_period
})
}
}
ord_parameter_types! {
pub const Six: u64 = 6;
}
type RootOrSix = EitherOfDiverse<EnsureRoot<u64>, EnsureSignedBy<Six, u64>>;
thread_local! {
pub static LAST_RANDOM: RefCell<Option<(H256, u32)>> = RefCell::new(None);
}
fn set_last_random(output: H256, known_since: u32) {
LAST_RANDOM.with(|p| *p.borrow_mut() = Some((output, known_since)))
}
pub struct TestPastRandomness;
impl Randomness<H256, BlockNumber> for TestPastRandomness {
fn random(_subject: &[u8]) -> (H256, u32) {
LAST_RANDOM.with(|p| {
if let Some((output, known_since)) = &*p.borrow() {
(*output, *known_since)
} else {
(H256::zero(), frame_system::Pallet::<Test>::block_number())
}
})
}
}
parameter_types! {
pub static EndingPeriod: BlockNumber = 3;
pub static SampleLength: BlockNumber = 1;
}
impl Config for Test {
type RuntimeEvent = RuntimeEvent;
type Leaser = TestLeaser;
type Registrar = TestRegistrar<Self>;
type EndingPeriod = EndingPeriod;
type SampleLength = SampleLength;
type Randomness = TestPastRandomness;
type InitiateOrigin = RootOrSix;
type WeightInfo = crate::auctions::TestWeightInfo;
}
pub fn new_test_ext() -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
pallet_balances::GenesisConfig::<Test> {
balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)],
}
.assimilate_storage(&mut t)
.unwrap();
let mut ext: sp_io::TestExternalities = t.into();
ext.execute_with(|| {
assert_ok!(TestRegistrar::<Test>::register(
1,
0.into(),
dummy_head_data(),
dummy_validation_code()
));
assert_ok!(TestRegistrar::<Test>::register(
1,
1.into(),
dummy_head_data(),
dummy_validation_code()
));
assert_ok!(TestRegistrar::<Test>::register(
1,
2.into(),
dummy_head_data(),
dummy_validation_code()
));
assert_ok!(TestRegistrar::<Test>::register(
1,
3.into(),
dummy_head_data(),
dummy_validation_code()
));
});
ext
}
fn run_to_block(n: BlockNumber) {
while System::block_number() < n {
Auctions::on_finalize(System::block_number());
Balances::on_finalize(System::block_number());
System::on_finalize(System::block_number());
System::set_block_number(System::block_number() + 1);
System::on_initialize(System::block_number());
Balances::on_initialize(System::block_number());
Auctions::on_initialize(System::block_number());
}
}
#[test]
fn basic_setup_works() {
new_test_ext().execute_with(|| {
assert_eq!(AuctionCounter::<Test>::get(), 0);
assert_eq!(TestLeaser::deposit_held(0u32.into(), &1), 0);
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::auction_status(System::block_number()),
AuctionStatus::<u32>::NotStarted
);
});
}
#[test]
fn can_start_auction() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_noop!(Auctions::new_auction(RuntimeOrigin::signed(1), 5, 1), BadOrigin);
assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1));
assert_eq!(AuctionCounter::<Test>::get(), 1);
assert_eq!(
Auctions::auction_status(System::block_number()),
AuctionStatus::<u32>::StartingPeriod
);
});
}
#[test]
fn bidding_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1));
assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 5));
assert_eq!(Balances::reserved_balance(1), 5);
assert_eq!(Balances::free_balance(1), 5);
assert_eq!(
Winning::<Test>::get(0).unwrap()[SlotRange::ZeroThree as u8 as usize],
Some((1, 0.into(), 5))
);
});
}
#[test]
fn under_bidding_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1));
assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 5));
assert_storage_noop!({
assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), 0.into(), 1, 1, 4, 1));
});
});
}
#[test]
fn over_bidding_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1));
assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 5));
assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), 0.into(), 1, 1, 4, 6));
assert_eq!(Balances::reserved_balance(1), 0);
assert_eq!(Balances::free_balance(1), 10);
assert_eq!(Balances::reserved_balance(2), 6);
assert_eq!(Balances::free_balance(2), 14);
assert_eq!(
Winning::<Test>::get(0).unwrap()[SlotRange::ZeroThree as u8 as usize],
Some((2, 0.into(), 6))
);
});
}
#[test]
fn auction_proceeds_correctly() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1));
assert_eq!(AuctionCounter::<Test>::get(), 1);
assert_eq!(
Auctions::auction_status(System::block_number()),
AuctionStatus::<u32>::StartingPeriod
);
run_to_block(2);
assert_eq!(
Auctions::auction_status(System::block_number()),
AuctionStatus::<u32>::StartingPeriod
);
run_to_block(3);
assert_eq!(
Auctions::auction_status(System::block_number()),
AuctionStatus::<u32>::StartingPeriod
);
run_to_block(4);
assert_eq!(
Auctions::auction_status(System::block_number()),
AuctionStatus::<u32>::StartingPeriod
);
run_to_block(5);
assert_eq!(
Auctions::auction_status(System::block_number()),
AuctionStatus::<u32>::StartingPeriod
);
run_to_block(6);
assert_eq!(
Auctions::auction_status(System::block_number()),
AuctionStatus::<u32>::EndingPeriod(0, 0)
);
run_to_block(7);
assert_eq!(
Auctions::auction_status(System::block_number()),
AuctionStatus::<u32>::EndingPeriod(1, 0)
);
run_to_block(8);
assert_eq!(
Auctions::auction_status(System::block_number()),
AuctionStatus::<u32>::EndingPeriod(2, 0)
);
run_to_block(9);
assert_eq!(
Auctions::auction_status(System::block_number()),
AuctionStatus::<u32>::NotStarted
);
});
}
#[test]
fn can_win_auction() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1));
assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 1));
assert_eq!(Balances::reserved_balance(1), 1);
assert_eq!(Balances::free_balance(1), 9);
run_to_block(9);
assert_eq!(
leases(),
vec![
((0.into(), 1), LeaseData { leaser: 1, amount: 1 }),
((0.into(), 2), LeaseData { leaser: 1, amount: 1 }),
((0.into(), 3), LeaseData { leaser: 1, amount: 1 }),
((0.into(), 4), LeaseData { leaser: 1, amount: 1 }),
]
);
assert_eq!(TestLeaser::deposit_held(0.into(), &1), 1);
});
}
#[test]
fn can_win_auction_with_late_randomness() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1));
assert_ok!(Auctions::bid(RuntimeOrigin::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);
assert_eq!(leases(), vec![]);
assert_eq!(
Auctions::auction_status(System::block_number()),
AuctionStatus::<u32>::EndingPeriod(2, 0)
);
set_last_random(H256::zero(), 8);
run_to_block(9);
assert_eq!(
Auctions::auction_status(System::block_number()),
AuctionStatus::<u32>::VrfDelay(0)
);
assert_eq!(leases(), vec![]);
set_last_random(H256::zero(), 9);
run_to_block(10);
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 }),
((0.into(), 3), LeaseData { leaser: 1, amount: 1 }),
((0.into(), 4), LeaseData { leaser: 1, amount: 1 }),
]
);
assert_eq!(TestLeaser::deposit_held(0.into(), &1), 1);
});
}
#[test]
fn can_win_incomplete_auction() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1));
assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 4, 4, 5));
run_to_block(9);
assert_eq!(leases(), vec![((0.into(), 4), LeaseData { leaser: 1, amount: 5 }),]);
assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5);
});
}
#[test]
fn should_choose_best_combination() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1));
assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 1));
assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), 0.into(), 1, 2, 3, 4));
assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), 0.into(), 1, 4, 4, 2));
assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 1.into(), 1, 1, 4, 2));
run_to_block(9);
assert_eq!(
leases(),
vec![
((0.into(), 1), LeaseData { leaser: 1, amount: 1 }),
((0.into(), 2), LeaseData { leaser: 2, amount: 4 }),
((0.into(), 3), LeaseData { leaser: 2, amount: 4 }),
((0.into(), 4), LeaseData { leaser: 3, amount: 2 }),
]
);
assert_eq!(TestLeaser::deposit_held(0.into(), &1), 1);
assert_eq!(TestLeaser::deposit_held(1.into(), &1), 0);
assert_eq!(TestLeaser::deposit_held(0.into(), &2), 4);
assert_eq!(TestLeaser::deposit_held(0.into(), &3), 2);
});
}
#[test]
fn gap_bid_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1));
assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 1));
assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 4, 4, 4));
assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), 1.into(), 1, 2, 2, 2));
assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), 1.into(), 1, 3, 3, 3));
assert_eq!(Balances::reserved_balance(1), 4);
assert_eq!(Balances::reserved_balance(2), 2);
assert_eq!(Balances::reserved_balance(3), 3);
run_to_block(9);
assert_eq!(
leases(),
vec![
((0.into(), 1), LeaseData { leaser: 1, amount: 1 }),
((0.into(), 4), LeaseData { leaser: 1, amount: 4 }),
((1.into(), 2), LeaseData { leaser: 2, amount: 2 }),
((1.into(), 3), LeaseData { leaser: 3, amount: 3 }),
]
);
assert_eq!(TestLeaser::deposit_held(0.into(), &1), 4);
assert_eq!(TestLeaser::deposit_held(1.into(), &2), 2);
assert_eq!(TestLeaser::deposit_held(1.into(), &3), 3);
});
}
#[test]
fn deposit_credit_should_work() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1));
assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 5));
assert_eq!(Balances::reserved_balance(1), 5);
run_to_block(10);
assert_eq!(leases(), vec![((0.into(), 1), LeaseData { leaser: 1, amount: 5 }),]);
assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5);
assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 2));
assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 2, 2, 6));
assert_eq!(Balances::reserved_balance(1), 1);
run_to_block(20);
assert_eq!(
leases(),
vec![
((0.into(), 1), LeaseData { leaser: 1, amount: 5 }),
((0.into(), 2), LeaseData { leaser: 1, amount: 6 }),
]
);
assert_eq!(TestLeaser::deposit_held(0.into(), &1), 6);
});
}
#[test]
fn deposit_credit_on_alt_para_should_not_count() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1));
assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 5));
assert_eq!(Balances::reserved_balance(1), 5);
run_to_block(10);
assert_eq!(leases(), vec![((0.into(), 1), LeaseData { leaser: 1, amount: 5 }),]);
assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5);
assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 2));
assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 1.into(), 2, 2, 2, 6));
assert_eq!(Balances::reserved_balance(1), 6);
run_to_block(20);
assert_eq!(
leases(),
vec![
((0.into(), 1), LeaseData { leaser: 1, amount: 5 }),
((1.into(), 2), LeaseData { leaser: 1, amount: 6 }),
]
);
assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5);
assert_eq!(TestLeaser::deposit_held(1.into(), &1), 6);
});
}
#[test]
fn multiple_bids_work_pre_ending() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1));
for i in 1..6u64 {
run_to_block(i as _);
assert_ok!(Auctions::bid(RuntimeOrigin::signed(i), 0.into(), 1, 1, 4, i));
for j in 1..6 {
assert_eq!(Balances::reserved_balance(j), if j == i { j } else { 0 });
assert_eq!(Balances::free_balance(j), if j == i { j * 9 } else { j * 10 });
}
}
run_to_block(9);
assert_eq!(
leases(),
vec![
((0.into(), 1), LeaseData { leaser: 5, amount: 5 }),
((0.into(), 2), LeaseData { leaser: 5, amount: 5 }),
((0.into(), 3), LeaseData { leaser: 5, amount: 5 }),
((0.into(), 4), LeaseData { leaser: 5, amount: 5 }),
]
);
});
}
#[test]
fn multiple_bids_work_post_ending() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 0, 1));
for i in 1..6u64 {
run_to_block(((i - 1) / 2 + 1) as _);
assert_ok!(Auctions::bid(RuntimeOrigin::signed(i), 0.into(), 1, 1, 4, i));
for j in 1..6 {
assert_eq!(Balances::reserved_balance(j), if j <= i { j } else { 0 });
assert_eq!(Balances::free_balance(j), if j <= i { j * 9 } else { j * 10 });
}
}
for i in 1..6u64 {
assert_eq!(ReservedAmounts::<Test>::get((i, ParaId::from(0))).unwrap(), i);
}
run_to_block(5);
assert_eq!(
leases(),
(1..=4)
.map(|i| ((0.into(), i), LeaseData { leaser: 2, amount: 2 }))
.collect::<Vec<_>>()
);
});
}
#[test]
fn incomplete_calculate_winners_works() {
let mut winning = [None; SlotRange::SLOT_RANGE_COUNT];
winning[SlotRange::ThreeThree as u8 as usize] = Some((1, 0.into(), 1));
let winners = vec![(1, 0.into(), 1, SlotRange::ThreeThree)];
assert_eq!(Auctions::calculate_winners(winning), winners);
}
#[test]
fn first_incomplete_calculate_winners_works() {
let mut winning = [None; SlotRange::SLOT_RANGE_COUNT];
winning[0] = Some((1, 0.into(), 1));
let winners = vec![(1, 0.into(), 1, SlotRange::ZeroZero)];
assert_eq!(Auctions::calculate_winners(winning), winners);
}
#[test]
fn calculate_winners_works() {
let mut winning = [None; SlotRange::SLOT_RANGE_COUNT];
winning[SlotRange::ZeroZero as u8 as usize] = Some((2, 0.into(), 2));
winning[SlotRange::ZeroThree as u8 as usize] = Some((1, 100.into(), 1));
winning[SlotRange::OneOne as u8 as usize] = Some((3, 1.into(), 1));
winning[SlotRange::TwoTwo as u8 as usize] = Some((1, 2.into(), 53));
winning[SlotRange::ThreeThree as u8 as usize] = Some((5, 3.into(), 1));
let winners = vec![
(2, 0.into(), 2, SlotRange::ZeroZero),
(3, 1.into(), 1, SlotRange::OneOne),
(1, 2.into(), 53, SlotRange::TwoTwo),
(5, 3.into(), 1, SlotRange::ThreeThree),
];
assert_eq!(Auctions::calculate_winners(winning), winners);
winning[SlotRange::ZeroOne as u8 as usize] = Some((4, 10.into(), 3));
let winners = vec![
(4, 10.into(), 3, SlotRange::ZeroOne),
(1, 2.into(), 53, SlotRange::TwoTwo),
(5, 3.into(), 1, SlotRange::ThreeThree),
];
assert_eq!(Auctions::calculate_winners(winning), winners);
winning[SlotRange::ZeroThree as u8 as usize] = Some((1, 100.into(), 100));
let winners = vec![(1, 100.into(), 100, SlotRange::ZeroThree)];
assert_eq!(Auctions::calculate_winners(winning), winners);
}
#[test]
fn lower_bids_are_correctly_refunded() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 1, 1));
let para_1 = ParaId::from(1_u32);
let para_2 = ParaId::from(2_u32);
assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), para_1, 1, 1, 4, 9));
assert_eq!(Balances::reserved_balance(1), 9);
assert_eq!(ReservedAmounts::<Test>::get((1, para_1)), Some(9));
assert_eq!(Balances::reserved_balance(2), 0);
assert_eq!(ReservedAmounts::<Test>::get((2, para_2)), None);
assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), para_2, 1, 1, 4, 19));
assert_eq!(Balances::reserved_balance(1), 0);
assert_eq!(ReservedAmounts::<Test>::get((1, para_1)), None);
assert_eq!(Balances::reserved_balance(2), 19);
assert_eq!(ReservedAmounts::<Test>::get((2, para_2)), Some(19));
});
}
#[test]
fn initialize_winners_in_ending_period_works() {
new_test_ext().execute_with(|| {
let ed: u64 = <Test as pallet_balances::Config>::ExistentialDeposit::get();
assert_eq!(ed, 1);
run_to_block(1);
assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 9, 1));
let para_1 = ParaId::from(1_u32);
let para_2 = ParaId::from(2_u32);
let para_3 = ParaId::from(3_u32);
assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), para_1, 1, 1, 4, 9));
assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), para_2, 1, 3, 4, 19));
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, 9));
winning[SlotRange::TwoThree as u8 as usize] = Some((2, para_2, 19));
assert_eq!(Winning::<Test>::get(0), Some(winning));
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)
);
assert_eq!(Winning::<Test>::get(0), Some(winning));
run_to_block(11);
assert_eq!(
Auctions::auction_status(System::block_number()),
AuctionStatus::<u32>::EndingPeriod(1, 0)
);
assert_eq!(Winning::<Test>::get(1), Some(winning));
assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), para_3, 1, 3, 4, 29));
run_to_block(12);
assert_eq!(
Auctions::auction_status(System::block_number()),
AuctionStatus::<u32>::EndingPeriod(2, 0)
);
winning[SlotRange::TwoThree as u8 as usize] = Some((3, para_3, 29));
assert_eq!(Winning::<Test>::get(2), Some(winning));
});
}
#[test]
fn handle_bid_requires_registered_para() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1));
assert_noop!(
Auctions::bid(RuntimeOrigin::signed(1), 1337.into(), 1, 1, 4, 1),
Error::<Test>::ParaNotRegistered
);
assert_ok!(TestRegistrar::<Test>::register(
1,
1337.into(),
dummy_head_data(),
dummy_validation_code()
));
assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 1337.into(), 1, 1, 4, 1));
});
}
#[test]
fn handle_bid_checks_existing_lease_periods() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1));
assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 2, 3, 1));
assert_eq!(Balances::reserved_balance(1), 1);
assert_eq!(Balances::free_balance(1), 9);
run_to_block(9);
assert_eq!(
leases(),
vec![
((0.into(), 2), LeaseData { leaser: 1, amount: 1 }),
((0.into(), 3), LeaseData { leaser: 1, amount: 1 }),
]
);
assert_eq!(TestLeaser::deposit_held(0.into(), &1), 1);
assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1));
assert_noop!(
Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 1, 4, 1),
Error::<Test>::AlreadyLeasedOut,
);
assert_noop!(
Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 1, 2, 1),
Error::<Test>::AlreadyLeasedOut,
);
assert_noop!(
Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 3, 4, 1),
Error::<Test>::AlreadyLeasedOut,
);
assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 1, 1, 1));
});
}
#[test]
fn less_winning_samples_work() {
new_test_ext().execute_with(|| {
let ed: u64 = <Test as pallet_balances::Config>::ExistentialDeposit::get();
assert_eq!(ed, 1);
EndingPeriod::set(30);
SampleLength::set(10);
run_to_block(1);
assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 9, 11));
let para_1 = ParaId::from(1_u32);
let para_2 = ParaId::from(2_u32);
let para_3 = ParaId::from(3_u32);
assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), para_1, 1, 11, 14, 9));
assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), para_2, 1, 13, 14, 19));
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, 9));
winning[SlotRange::TwoThree as u8 as usize] = Some((2, para_2, 19));
assert_eq!(Winning::<Test>::get(0), Some(winning));
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)
);
assert_eq!(Winning::<Test>::get(0), Some(winning));
assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), para_3, 1, 14, 14, 29));
winning[SlotRange::ThreeThree as u8 as usize] = Some((3, para_3, 29));
assert_eq!(Winning::<Test>::get(0), Some(winning));
run_to_block(20);
assert_eq!(
Auctions::auction_status(System::block_number()),
AuctionStatus::<u32>::EndingPeriod(1, 0)
);
assert_eq!(Winning::<Test>::get(1), Some(winning));
run_to_block(25);
assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), para_3, 1, 13, 14, 29));
winning[SlotRange::TwoThree as u8 as usize] = Some((3, para_3, 29));
assert_eq!(Winning::<Test>::get(1), Some(winning));
run_to_block(30);
assert_eq!(
Auctions::auction_status(System::block_number()),
AuctionStatus::<u32>::EndingPeriod(2, 0)
);
assert_eq!(Winning::<Test>::get(2), Some(winning));
set_last_random(H256::from([254; 32]), 40);
run_to_block(40);
assert_eq!(
Auctions::auction_status(System::block_number()),
AuctionStatus::<u32>::NotStarted
);
assert_eq!(
leases(),
vec![
((3.into(), 13), LeaseData { leaser: 3, amount: 29 }),
((3.into(), 14), LeaseData { leaser: 3, amount: 29 }),
]
);
});
}
#[test]
fn auction_status_works() {
new_test_ext().execute_with(|| {
EndingPeriod::set(30);
SampleLength::set(10);
set_last_random(dummy_hash(), 0);
assert_eq!(
Auctions::auction_status(System::block_number()),
AuctionStatus::<u32>::NotStarted
);
run_to_block(1);
assert_ok!(Auctions::new_auction(RuntimeOrigin::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(dummy_hash(), 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(|| {
run_to_block(1);
assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1));
assert_ok!(Auctions::bid(RuntimeOrigin::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(RuntimeOrigin::signed(6)), BadOrigin);
assert_ok!(Auctions::cancel_auction(RuntimeOrigin::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")]
mod benchmarking {
use super::{Pallet as Auctions, *};
use frame_support::{
assert_ok,
traits::{EnsureOrigin, OnInitialize},
};
use frame_system::RawOrigin;
use polkadot_runtime_parachains::paras;
use sp_runtime::{traits::Bounded, SaturatedConversion};
use frame_benchmarking::{account, benchmarks, whitelisted_caller, BenchmarkError};
fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
let events = frame_system::Pallet::<T>::events();
let system_event: <T as frame_system::Config>::RuntimeEvent = generic_event.into();
let frame_system::EventRecord { event, .. } = &events[events.len() - 1];
assert_eq!(event, &system_event);
}
fn fill_winners<T: Config + paras::Config>(lease_period_index: LeasePeriodOf<T>) {
let auction_index = AuctionCounter::<T>::get();
let minimum_balance = CurrencyOf::<T>::minimum_balance();
for n in 1..=SlotRange::SLOT_RANGE_COUNT as u32 {
let owner = account("owner", n, 0);
let worst_validation_code = T::Registrar::worst_validation_code();
let worst_head_data = T::Registrar::worst_head_data();
CurrencyOf::<T>::make_free_balance_be(&owner, BalanceOf::<T>::max_value());
assert!(T::Registrar::register(
owner,
ParaId::from(n),
worst_head_data,
worst_validation_code
)
.is_ok());
}
assert_ok!(paras::Pallet::<T>::add_trusted_validation_code(
frame_system::Origin::<T>::Root.into(),
T::Registrar::worst_validation_code(),
));
T::Registrar::execute_pending_transitions();
for n in 1..=SlotRange::SLOT_RANGE_COUNT as u32 {
let bidder = account("bidder", n, 0);
CurrencyOf::<T>::make_free_balance_be(&bidder, BalanceOf::<T>::max_value());
let slot_range = SlotRange::n((n - 1) as u8).unwrap();
let (start, end) = slot_range.as_pair();
assert!(Auctions::<T>::bid(
RawOrigin::Signed(bidder).into(),
ParaId::from(n),
auction_index,
lease_period_index + start.into(), lease_period_index + end.into(), minimum_balance.saturating_mul(n.into()), )
.is_ok());
}
}
benchmarks! {
where_clause { where T: pallet_babe::Config + paras::Config }
new_auction {
let duration = BlockNumberFor::<T>::max_value();
let lease_period_index = LeasePeriodOf::<T>::max_value();
let origin =
T::InitiateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
}: _<T::RuntimeOrigin>(origin, duration, lease_period_index)
verify {
assert_last_event::<T>(Event::<T>::AuctionStarted {
auction_index: AuctionCounter::<T>::get(),
lease_period: LeasePeriodOf::<T>::max_value(),
ending: BlockNumberFor::<T>::max_value(),
}.into());
}
bid {
let (_, offset) = T::Leaser::lease_period_length();
frame_system::Pallet::<T>::set_block_number(offset + One::one());
let duration = BlockNumberFor::<T>::max_value();
let lease_period_index = LeasePeriodOf::<T>::zero();
let origin = T::InitiateOrigin::try_successful_origin()
.expect("InitiateOrigin has no successful origin required for the benchmark");
Auctions::<T>::new_auction(origin, duration, lease_period_index)?;
let para = ParaId::from(0);
let new_para = ParaId::from(1_u32);
let owner = account("owner", 0, 0);
CurrencyOf::<T>::make_free_balance_be(&owner, BalanceOf::<T>::max_value());
let worst_head_data = T::Registrar::worst_head_data();
let worst_validation_code = T::Registrar::worst_validation_code();
T::Registrar::register(owner.clone(), para, worst_head_data.clone(), worst_validation_code.clone())?;
T::Registrar::register(owner, new_para, worst_head_data, worst_validation_code.clone())?;
assert_ok!(paras::Pallet::<T>::add_trusted_validation_code(
frame_system::Origin::<T>::Root.into(),
worst_validation_code,
));
T::Registrar::execute_pending_transitions();
let auction_index = AuctionCounter::<T>::get();
let first_slot = AuctionInfo::<T>::get().unwrap().0;
let last_slot = first_slot + 3u32.into();
let first_amount = CurrencyOf::<T>::minimum_balance();
let first_bidder: T::AccountId = account("first_bidder", 0, 0);
CurrencyOf::<T>::make_free_balance_be(&first_bidder, BalanceOf::<T>::max_value());
Auctions::<T>::bid(
RawOrigin::Signed(first_bidder.clone()).into(),
para,
auction_index,
first_slot,
last_slot,
first_amount,
)?;
let caller: T::AccountId = whitelisted_caller();
CurrencyOf::<T>::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
let bigger_amount = CurrencyOf::<T>::minimum_balance().saturating_mul(10u32.into());
assert_eq!(CurrencyOf::<T>::reserved_balance(&first_bidder), first_amount);
}: _(RawOrigin::Signed(caller.clone()), new_para, auction_index, first_slot, last_slot, bigger_amount)
verify {
assert_eq!(CurrencyOf::<T>::reserved_balance(&caller), bigger_amount);
}
on_initialize {
let (lease_length, offset) = T::Leaser::lease_period_length();
frame_system::Pallet::<T>::set_block_number(offset + One::one());
let duration: BlockNumberFor<T> = lease_length / 2u32.into();
let lease_period_index = LeasePeriodOf::<T>::zero();
let now = frame_system::Pallet::<T>::block_number();
let origin = T::InitiateOrigin::try_successful_origin()
.expect("InitiateOrigin has no successful origin required for the benchmark");
Auctions::<T>::new_auction(origin, duration, lease_period_index)?;
fill_winners::<T>(lease_period_index);
for winner in Winning::<T>::get(BlockNumberFor::<T>::from(0u32)).unwrap().iter() {
assert!(winner.is_some());
}
let winning_data = Winning::<T>::get(BlockNumberFor::<T>::from(0u32)).unwrap();
for i in 0u32 .. (T::EndingPeriod::get() / T::SampleLength::get()).saturated_into() {
Winning::<T>::insert(BlockNumberFor::<T>::from(i), winning_data.clone());
}
frame_system::Pallet::<T>::set_block_number(duration + now + T::EndingPeriod::get());
{
pallet_babe::EpochStart::<T>::set((Zero::zero(), u32::MAX.into()));
pallet_babe::Pallet::<T>::on_initialize(duration + now + T::EndingPeriod::get());
let authorities = pallet_babe::Pallet::<T>::authorities();
if !authorities.is_empty() {
pallet_babe::Pallet::<T>::enact_epoch_change(authorities.clone(), authorities, None);
}
}
}: {
Auctions::<T>::on_initialize(duration + now + T::EndingPeriod::get());
} verify {
let auction_index = AuctionCounter::<T>::get();
assert_last_event::<T>(Event::<T>::AuctionClosed { auction_index }.into());
assert!(Winning::<T>::iter().count().is_zero());
}
cancel_auction {
let (lease_length, offset) = T::Leaser::lease_period_length();
frame_system::Pallet::<T>::set_block_number(offset + One::one());
let duration: BlockNumberFor<T> = lease_length / 2u32.into();
let lease_period_index = LeasePeriodOf::<T>::zero();
let now = frame_system::Pallet::<T>::block_number();
let origin = T::InitiateOrigin::try_successful_origin()
.expect("InitiateOrigin has no successful origin required for the benchmark");
Auctions::<T>::new_auction(origin, duration, lease_period_index)?;
fill_winners::<T>(lease_period_index);
let winning_data = Winning::<T>::get(BlockNumberFor::<T>::from(0u32)).unwrap();
for winner in winning_data.iter() {
assert!(winner.is_some());
}
for i in 0u32 .. (T::EndingPeriod::get() / T::SampleLength::get()).saturated_into() {
Winning::<T>::insert(BlockNumberFor::<T>::from(i), winning_data.clone());
}
assert!(AuctionInfo::<T>::get().is_some());
}: _(RawOrigin::Root)
verify {
assert!(AuctionInfo::<T>::get().is_none());
}
impl_benchmark_test_suite!(
Auctions,
crate::integration_tests::new_test_ext(),
crate::integration_tests::Test,
);
}
}