From b1fb6aeb76f76e8c209e800a3ce4cd20904ff03b Mon Sep 17 00:00:00 2001 From: Krayt78 Date: Fri, 29 Nov 2024 00:15:55 +0100 Subject: [PATCH] moved tests and benchmark to seperate files --- polkadot/runtime/common/src/auctions.rs | 1264 +---------------- .../common/src/auctions/benchmarking.rs | 222 +++ polkadot/runtime/common/src/auctions/tests.rs | 1034 ++++++++++++++ 3 files changed, 1258 insertions(+), 1262 deletions(-) create mode 100644 polkadot/runtime/common/src/auctions/benchmarking.rs create mode 100644 polkadot/runtime/common/src/auctions/tests.rs diff --git a/polkadot/runtime/common/src/auctions.rs b/polkadot/runtime/common/src/auctions.rs index 78f20d918bab..2578ac01d688 100644 --- a/polkadot/runtime/common/src/auctions.rs +++ b/polkadot/runtime/common/src/auctions.rs @@ -667,1268 +667,8 @@ impl Pallet { } } -/// tests for this module #[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; - - 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; - type Block = Block; - type RuntimeEvent = RuntimeEvent; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - 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> = RefCell::new(BTreeMap::new()); - } - - fn leases() -> Vec<((ParaId, BlockNumber), LeaseData)> { - LEASES.with(|p| (&*p.borrow()).clone().into_iter().collect::>()) - } - - pub struct TestLeaser; - impl Leaser for TestLeaser { - type AccountId = u64; - type LeasePeriod = BlockNumber; - type Currency = Balances; - - fn lease_out( - para: ParaId, - leaser: &Self::AccountId, - amount: >::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, - ) -> >::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, EnsureSignedBy>; - - thread_local! { - pub static LAST_RANDOM: RefCell> = 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 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::::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; - type EndingPeriod = EndingPeriod; - type SampleLength = SampleLength; - type Randomness = TestPastRandomness; - type InitiateOrigin = RootOrSix; - type WeightInfo = crate::auctions::TestWeightInfo; - } - - // This function basically just builds a genesis storage key/value store according to - // our desired mock up. - pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { - 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(|| { - // Register para 0, 1, 2, and 3 for tests - assert_ok!(TestRegistrar::::register( - 1, - 0.into(), - dummy_head_data(), - dummy_validation_code() - )); - assert_ok!(TestRegistrar::::register( - 1, - 1.into(), - dummy_head_data(), - dummy_validation_code() - )); - assert_ok!(TestRegistrar::::register( - 1, - 2.into(), - dummy_head_data(), - dummy_validation_code() - )); - assert_ok!(TestRegistrar::::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::::get(), 0); - assert_eq!(TestLeaser::deposit_held(0u32.into(), &1), 0); - 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::auction_status(System::block_number()), - AuctionStatus::::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::::get(), 1); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::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::::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::::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::::get(), 1); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::StartingPeriod - ); - - run_to_block(2); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::StartingPeriod - ); - - run_to_block(3); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::StartingPeriod - ); - - run_to_block(4); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::StartingPeriod - ); - - run_to_block(5); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::StartingPeriod - ); - - run_to_block(6); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::EndingPeriod(0, 0) - ); - - run_to_block(7); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::EndingPeriod(1, 0) - ); - - run_to_block(8); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::EndingPeriod(2, 0) - ); - - run_to_block(9); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::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::::StartingPeriod - ); - run_to_block(8); - // Auction has not yet ended. - assert_eq!(leases(), vec![]); - 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... 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 that the winner can now be chosen. - set_last_random(H256::zero(), 9); - run_to_block(10); - // Auction ended and winner selected - 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 }), - ((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)); - - // User 1 will make a bid for period 1 and 4 for the same Para 0 - 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)); - - // User 2 and 3 will make a bid for para 1 on period 2 and 3 respectively - 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)); - - // Total reserved should be the max of the two - assert_eq!(Balances::reserved_balance(1), 4); - - // Other people are reserved correctly too - assert_eq!(Balances::reserved_balance(2), 2); - assert_eq!(Balances::reserved_balance(3), 3); - - // End the auction. - 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)); - // Only 1 reserved since we have a deposit credit of 5. - 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)); - // 6 reserved since we are bidding on a new para; only works because we don't - 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::::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::>() - ); - }); - } - - #[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); - - // Make a bid and reserve a balance - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), para_1, 1, 1, 4, 9)); - assert_eq!(Balances::reserved_balance(1), 9); - assert_eq!(ReservedAmounts::::get((1, para_1)), Some(9)); - assert_eq!(Balances::reserved_balance(2), 0); - assert_eq!(ReservedAmounts::::get((2, para_2)), None); - - // Bigger bid, reserves new balance and returns funds - assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), para_2, 1, 1, 4, 19)); - assert_eq!(Balances::reserved_balance(1), 0); - assert_eq!(ReservedAmounts::::get((1, para_1)), None); - assert_eq!(Balances::reserved_balance(2), 19); - assert_eq!(ReservedAmounts::::get((2, para_2)), Some(19)); - }); - } - - #[test] - fn initialize_winners_in_ending_period_works() { - new_test_ext().execute_with(|| { - let ed: u64 = ::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); - - // Make bids - 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::::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::::get(0), Some(winning)); - - 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) - ); - assert_eq!(Winning::::get(0), Some(winning)); - - run_to_block(11); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::EndingPeriod(1, 0) - ); - assert_eq!(Winning::::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::::EndingPeriod(2, 0) - ); - winning[SlotRange::TwoThree as u8 as usize] = Some((3, para_3, 29)); - assert_eq!(Winning::::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::::ParaNotRegistered - ); - assert_ok!(TestRegistrar::::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); - - // Para 1 just won an auction above and won some lease periods. - // No bids can work which overlap these periods. - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_noop!( - Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 1, 4, 1), - Error::::AlreadyLeasedOut, - ); - assert_noop!( - Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 1, 2, 1), - Error::::AlreadyLeasedOut, - ); - assert_noop!( - Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 3, 4, 1), - Error::::AlreadyLeasedOut, - ); - // This is okay, not an overlapping bid. - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 1, 1, 1)); - }); - } - - // Here we will test that taking only 10 samples during the ending period works as expected. - #[test] - fn less_winning_samples_work() { - new_test_ext().execute_with(|| { - let ed: u64 = ::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); - - // Make bids - 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::::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::::get(0), Some(winning)); - - 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) - ); - assert_eq!(Winning::::get(0), Some(winning)); - - // New bids update the current 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::::get(0), Some(winning)); - - run_to_block(20); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::EndingPeriod(1, 0) - ); - assert_eq!(Winning::::get(1), Some(winning)); - run_to_block(25); - // Overbid mid sample - 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::::get(1), Some(winning)); - - run_to_block(30); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::EndingPeriod(2, 0) - ); - assert_eq!(Winning::::get(2), Some(winning)); - - set_last_random(H256::from([254; 32]), 40); - run_to_block(40); - // Auction ended and winner selected - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::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::::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::::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(dummy_hash(), 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(|| { - 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::::get().is_none()); - assert_eq!(Balances::reserved_balance(1), 0); - assert_eq!(ReservedAmounts::::iter().count(), 0); - assert_eq!(Winning::::iter().count(), 0); - }); - } -} +mod tests; #[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(generic_event: ::RuntimeEvent) { - let events = frame_system::Pallet::::events(); - let system_event: ::RuntimeEvent = generic_event.into(); - // compare to the last event record - let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; - assert_eq!(event, &system_event); - } - - fn fill_winners(lease_period_index: LeasePeriodOf) { - let auction_index = AuctionCounter::::get(); - let minimum_balance = CurrencyOf::::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::::make_free_balance_be(&owner, BalanceOf::::max_value()); - - assert!(T::Registrar::register( - owner, - ParaId::from(n), - worst_head_data, - worst_validation_code - ) - .is_ok()); - } - assert_ok!(paras::Pallet::::add_trusted_validation_code( - frame_system::Origin::::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::::make_free_balance_be(&bidder, BalanceOf::::max_value()); - - let slot_range = SlotRange::n((n - 1) as u8).unwrap(); - let (start, end) = slot_range.as_pair(); - - assert!(Auctions::::bid( - RawOrigin::Signed(bidder).into(), - ParaId::from(n), - auction_index, - lease_period_index + start.into(), // First Slot - lease_period_index + end.into(), // Last slot - minimum_balance.saturating_mul(n.into()), // Amount - ) - .is_ok()); - } - } - - benchmarks! { - where_clause { where T: pallet_babe::Config + paras::Config } - - new_auction { - let duration = BlockNumberFor::::max_value(); - let lease_period_index = LeasePeriodOf::::max_value(); - let origin = - T::InitiateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; - }: _(origin, duration, lease_period_index) - verify { - assert_last_event::(Event::::AuctionStarted { - auction_index: AuctionCounter::::get(), - lease_period: LeasePeriodOf::::max_value(), - ending: BlockNumberFor::::max_value(), - }.into()); - } - - // Worst case scenario a new bid comes in which kicks out an existing bid for the same slot. - bid { - // If there is an offset, we need to be on that block to be able to do lease things. - let (_, offset) = T::Leaser::lease_period_length(); - frame_system::Pallet::::set_block_number(offset + One::one()); - - // Create a new auction - let duration = BlockNumberFor::::max_value(); - let lease_period_index = LeasePeriodOf::::zero(); - let origin = T::InitiateOrigin::try_successful_origin() - .expect("InitiateOrigin has no successful origin required for the benchmark"); - Auctions::::new_auction(origin, duration, lease_period_index)?; - - let para = ParaId::from(0); - let new_para = ParaId::from(1_u32); - - // Register the paras - let owner = account("owner", 0, 0); - CurrencyOf::::make_free_balance_be(&owner, BalanceOf::::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::::add_trusted_validation_code( - frame_system::Origin::::Root.into(), - worst_validation_code, - )); - - T::Registrar::execute_pending_transitions(); - - // Make an existing bid - let auction_index = AuctionCounter::::get(); - let first_slot = AuctionInfo::::get().unwrap().0; - let last_slot = first_slot + 3u32.into(); - let first_amount = CurrencyOf::::minimum_balance(); - let first_bidder: T::AccountId = account("first_bidder", 0, 0); - CurrencyOf::::make_free_balance_be(&first_bidder, BalanceOf::::max_value()); - Auctions::::bid( - RawOrigin::Signed(first_bidder.clone()).into(), - para, - auction_index, - first_slot, - last_slot, - first_amount, - )?; - - let caller: T::AccountId = whitelisted_caller(); - CurrencyOf::::make_free_balance_be(&caller, BalanceOf::::max_value()); - let bigger_amount = CurrencyOf::::minimum_balance().saturating_mul(10u32.into()); - assert_eq!(CurrencyOf::::reserved_balance(&first_bidder), first_amount); - }: _(RawOrigin::Signed(caller.clone()), new_para, auction_index, first_slot, last_slot, bigger_amount) - verify { - // Confirms that we unreserved funds from a previous bidder, which is worst case scenario. - assert_eq!(CurrencyOf::::reserved_balance(&caller), bigger_amount); - } - - // Worst case: 10 bidders taking all wining spots, and we need to calculate the winner for auction end. - // Entire winner map should be full and removed at the end of the benchmark. - on_initialize { - // If there is an offset, we need to be on that block to be able to do lease things. - let (lease_length, offset) = T::Leaser::lease_period_length(); - frame_system::Pallet::::set_block_number(offset + One::one()); - - // Create a new auction - let duration: BlockNumberFor = lease_length / 2u32.into(); - let lease_period_index = LeasePeriodOf::::zero(); - let now = frame_system::Pallet::::block_number(); - let origin = T::InitiateOrigin::try_successful_origin() - .expect("InitiateOrigin has no successful origin required for the benchmark"); - Auctions::::new_auction(origin, duration, lease_period_index)?; - - fill_winners::(lease_period_index); - - for winner in Winning::::get(BlockNumberFor::::from(0u32)).unwrap().iter() { - assert!(winner.is_some()); - } - - let winning_data = Winning::::get(BlockNumberFor::::from(0u32)).unwrap(); - // Make winning map full - for i in 0u32 .. (T::EndingPeriod::get() / T::SampleLength::get()).saturated_into() { - Winning::::insert(BlockNumberFor::::from(i), winning_data.clone()); - } - - // Move ahead to the block we want to initialize - frame_system::Pallet::::set_block_number(duration + now + T::EndingPeriod::get()); - - // Trigger epoch change for new random number value: - { - pallet_babe::EpochStart::::set((Zero::zero(), u32::MAX.into())); - pallet_babe::Pallet::::on_initialize(duration + now + T::EndingPeriod::get()); - let authorities = pallet_babe::Pallet::::authorities(); - // Check for non empty authority set since it otherwise emits a No-OP warning. - if !authorities.is_empty() { - pallet_babe::Pallet::::enact_epoch_change(authorities.clone(), authorities, None); - } - } - - }: { - Auctions::::on_initialize(duration + now + T::EndingPeriod::get()); - } verify { - let auction_index = AuctionCounter::::get(); - assert_last_event::(Event::::AuctionClosed { auction_index }.into()); - assert!(Winning::::iter().count().is_zero()); - } - - // Worst case: 10 bidders taking all wining spots, and winning data is full. - cancel_auction { - // If there is an offset, we need to be on that block to be able to do lease things. - let (lease_length, offset) = T::Leaser::lease_period_length(); - frame_system::Pallet::::set_block_number(offset + One::one()); - - // Create a new auction - let duration: BlockNumberFor = lease_length / 2u32.into(); - let lease_period_index = LeasePeriodOf::::zero(); - let now = frame_system::Pallet::::block_number(); - let origin = T::InitiateOrigin::try_successful_origin() - .expect("InitiateOrigin has no successful origin required for the benchmark"); - Auctions::::new_auction(origin, duration, lease_period_index)?; - - fill_winners::(lease_period_index); - - let winning_data = Winning::::get(BlockNumberFor::::from(0u32)).unwrap(); - for winner in winning_data.iter() { - assert!(winner.is_some()); - } - - // Make winning map full - for i in 0u32 .. (T::EndingPeriod::get() / T::SampleLength::get()).saturated_into() { - Winning::::insert(BlockNumberFor::::from(i), winning_data.clone()); - } - assert!(AuctionInfo::::get().is_some()); - }: _(RawOrigin::Root) - verify { - assert!(AuctionInfo::::get().is_none()); - } - - impl_benchmark_test_suite!( - Auctions, - crate::integration_tests::new_test_ext(), - crate::integration_tests::Test, - ); - } -} +mod benchmarking; diff --git a/polkadot/runtime/common/src/auctions/benchmarking.rs b/polkadot/runtime/common/src/auctions/benchmarking.rs new file mode 100644 index 000000000000..10f4934ed94d --- /dev/null +++ b/polkadot/runtime/common/src/auctions/benchmarking.rs @@ -0,0 +1,222 @@ +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(generic_event: ::RuntimeEvent) { + let events = frame_system::Pallet::::events(); + let system_event: ::RuntimeEvent = generic_event.into(); + // compare to the last event record + let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); +} + +fn fill_winners(lease_period_index: LeasePeriodOf) { + let auction_index = AuctionCounter::::get(); + let minimum_balance = CurrencyOf::::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::::make_free_balance_be(&owner, BalanceOf::::max_value()); + + assert!(T::Registrar::register( + owner, + ParaId::from(n), + worst_head_data, + worst_validation_code + ) + .is_ok()); + } + assert_ok!(paras::Pallet::::add_trusted_validation_code( + frame_system::Origin::::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::::make_free_balance_be(&bidder, BalanceOf::::max_value()); + + let slot_range = SlotRange::n((n - 1) as u8).unwrap(); + let (start, end) = slot_range.as_pair(); + + assert!(Auctions::::bid( + RawOrigin::Signed(bidder).into(), + ParaId::from(n), + auction_index, + lease_period_index + start.into(), // First Slot + lease_period_index + end.into(), // Last slot + minimum_balance.saturating_mul(n.into()), // Amount + ) + .is_ok()); + } +} + +benchmarks! { + where_clause { where T: pallet_babe::Config + paras::Config } + + new_auction { + let duration = BlockNumberFor::::max_value(); + let lease_period_index = LeasePeriodOf::::max_value(); + let origin = + T::InitiateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + }: _(origin, duration, lease_period_index) + verify { + assert_last_event::(Event::::AuctionStarted { + auction_index: AuctionCounter::::get(), + lease_period: LeasePeriodOf::::max_value(), + ending: BlockNumberFor::::max_value(), + }.into()); + } + + // Worst case scenario a new bid comes in which kicks out an existing bid for the same slot. + bid { + // If there is an offset, we need to be on that block to be able to do lease things. + let (_, offset) = T::Leaser::lease_period_length(); + frame_system::Pallet::::set_block_number(offset + One::one()); + + // Create a new auction + let duration = BlockNumberFor::::max_value(); + let lease_period_index = LeasePeriodOf::::zero(); + let origin = T::InitiateOrigin::try_successful_origin() + .expect("InitiateOrigin has no successful origin required for the benchmark"); + Auctions::::new_auction(origin, duration, lease_period_index)?; + + let para = ParaId::from(0); + let new_para = ParaId::from(1_u32); + + // Register the paras + let owner = account("owner", 0, 0); + CurrencyOf::::make_free_balance_be(&owner, BalanceOf::::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::::add_trusted_validation_code( + frame_system::Origin::::Root.into(), + worst_validation_code, + )); + + T::Registrar::execute_pending_transitions(); + + // Make an existing bid + let auction_index = AuctionCounter::::get(); + let first_slot = AuctionInfo::::get().unwrap().0; + let last_slot = first_slot + 3u32.into(); + let first_amount = CurrencyOf::::minimum_balance(); + let first_bidder: T::AccountId = account("first_bidder", 0, 0); + CurrencyOf::::make_free_balance_be(&first_bidder, BalanceOf::::max_value()); + Auctions::::bid( + RawOrigin::Signed(first_bidder.clone()).into(), + para, + auction_index, + first_slot, + last_slot, + first_amount, + )?; + + let caller: T::AccountId = whitelisted_caller(); + CurrencyOf::::make_free_balance_be(&caller, BalanceOf::::max_value()); + let bigger_amount = CurrencyOf::::minimum_balance().saturating_mul(10u32.into()); + assert_eq!(CurrencyOf::::reserved_balance(&first_bidder), first_amount); + }: _(RawOrigin::Signed(caller.clone()), new_para, auction_index, first_slot, last_slot, bigger_amount) + verify { + // Confirms that we unreserved funds from a previous bidder, which is worst case scenario. + assert_eq!(CurrencyOf::::reserved_balance(&caller), bigger_amount); + } + + // Worst case: 10 bidders taking all wining spots, and we need to calculate the winner for auction end. + // Entire winner map should be full and removed at the end of the benchmark. + on_initialize { + // If there is an offset, we need to be on that block to be able to do lease things. + let (lease_length, offset) = T::Leaser::lease_period_length(); + frame_system::Pallet::::set_block_number(offset + One::one()); + + // Create a new auction + let duration: BlockNumberFor = lease_length / 2u32.into(); + let lease_period_index = LeasePeriodOf::::zero(); + let now = frame_system::Pallet::::block_number(); + let origin = T::InitiateOrigin::try_successful_origin() + .expect("InitiateOrigin has no successful origin required for the benchmark"); + Auctions::::new_auction(origin, duration, lease_period_index)?; + + fill_winners::(lease_period_index); + + for winner in Winning::::get(BlockNumberFor::::from(0u32)).unwrap().iter() { + assert!(winner.is_some()); + } + + let winning_data = Winning::::get(BlockNumberFor::::from(0u32)).unwrap(); + // Make winning map full + for i in 0u32 .. (T::EndingPeriod::get() / T::SampleLength::get()).saturated_into() { + Winning::::insert(BlockNumberFor::::from(i), winning_data.clone()); + } + + // Move ahead to the block we want to initialize + frame_system::Pallet::::set_block_number(duration + now + T::EndingPeriod::get()); + + // Trigger epoch change for new random number value: + { + pallet_babe::EpochStart::::set((Zero::zero(), u32::MAX.into())); + pallet_babe::Pallet::::on_initialize(duration + now + T::EndingPeriod::get()); + let authorities = pallet_babe::Pallet::::authorities(); + // Check for non empty authority set since it otherwise emits a No-OP warning. + if !authorities.is_empty() { + pallet_babe::Pallet::::enact_epoch_change(authorities.clone(), authorities, None); + } + } + + }: { + Auctions::::on_initialize(duration + now + T::EndingPeriod::get()); + } verify { + let auction_index = AuctionCounter::::get(); + assert_last_event::(Event::::AuctionClosed { auction_index }.into()); + assert!(Winning::::iter().count().is_zero()); + } + + // Worst case: 10 bidders taking all wining spots, and winning data is full. + cancel_auction { + // If there is an offset, we need to be on that block to be able to do lease things. + let (lease_length, offset) = T::Leaser::lease_period_length(); + frame_system::Pallet::::set_block_number(offset + One::one()); + + // Create a new auction + let duration: BlockNumberFor = lease_length / 2u32.into(); + let lease_period_index = LeasePeriodOf::::zero(); + let now = frame_system::Pallet::::block_number(); + let origin = T::InitiateOrigin::try_successful_origin() + .expect("InitiateOrigin has no successful origin required for the benchmark"); + Auctions::::new_auction(origin, duration, lease_period_index)?; + + fill_winners::(lease_period_index); + + let winning_data = Winning::::get(BlockNumberFor::::from(0u32)).unwrap(); + for winner in winning_data.iter() { + assert!(winner.is_some()); + } + + // Make winning map full + for i in 0u32 .. (T::EndingPeriod::get() / T::SampleLength::get()).saturated_into() { + Winning::::insert(BlockNumberFor::::from(i), winning_data.clone()); + } + assert!(AuctionInfo::::get().is_some()); + }: _(RawOrigin::Root) + verify { + assert!(AuctionInfo::::get().is_none()); + } + + impl_benchmark_test_suite!( + Auctions, + crate::integration_tests::new_test_ext(), + crate::integration_tests::Test, + ); +} diff --git a/polkadot/runtime/common/src/auctions/tests.rs b/polkadot/runtime/common/src/auctions/tests.rs new file mode 100644 index 000000000000..a5145d095b07 --- /dev/null +++ b/polkadot/runtime/common/src/auctions/tests.rs @@ -0,0 +1,1034 @@ +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; + +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; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + 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> = RefCell::new(BTreeMap::new()); +} + +fn leases() -> Vec<((ParaId, BlockNumber), LeaseData)> { + LEASES.with(|p| (&*p.borrow()).clone().into_iter().collect::>()) +} + +pub struct TestLeaser; +impl Leaser for TestLeaser { + type AccountId = u64; + type LeasePeriod = BlockNumber; + type Currency = Balances; + + fn lease_out( + para: ParaId, + leaser: &Self::AccountId, + amount: >::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, + ) -> >::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, EnsureSignedBy>; + +thread_local! { + pub static LAST_RANDOM: RefCell> = 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 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::::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; + type EndingPeriod = EndingPeriod; + type SampleLength = SampleLength; + type Randomness = TestPastRandomness; + type InitiateOrigin = RootOrSix; + type WeightInfo = crate::auctions::TestWeightInfo; +} + +// This function basically just builds a genesis storage key/value store according to +// our desired mock up. +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + pallet_balances::GenesisConfig:: { + 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(|| { + // Register para 0, 1, 2, and 3 for tests + assert_ok!(TestRegistrar::::register( + 1, + 0.into(), + dummy_head_data(), + dummy_validation_code() + )); + assert_ok!(TestRegistrar::::register( + 1, + 1.into(), + dummy_head_data(), + dummy_validation_code() + )); + assert_ok!(TestRegistrar::::register( + 1, + 2.into(), + dummy_head_data(), + dummy_validation_code() + )); + assert_ok!(TestRegistrar::::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::::get(), 0); + assert_eq!(TestLeaser::deposit_held(0u32.into(), &1), 0); + 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::auction_status(System::block_number()), + AuctionStatus::::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::::get(), 1); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::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::::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::::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::::get(), 1); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::StartingPeriod + ); + + run_to_block(2); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::StartingPeriod + ); + + run_to_block(3); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::StartingPeriod + ); + + run_to_block(4); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::StartingPeriod + ); + + run_to_block(5); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::StartingPeriod + ); + + run_to_block(6); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::EndingPeriod(0, 0) + ); + + run_to_block(7); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::EndingPeriod(1, 0) + ); + + run_to_block(8); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::EndingPeriod(2, 0) + ); + + run_to_block(9); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::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::::StartingPeriod + ); + run_to_block(8); + // Auction has not yet ended. + assert_eq!(leases(), vec![]); + 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... 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 that the winner can now be chosen. + set_last_random(H256::zero(), 9); + run_to_block(10); + // Auction ended and winner selected + 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 }), + ((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)); + + // User 1 will make a bid for period 1 and 4 for the same Para 0 + 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)); + + // User 2 and 3 will make a bid for para 1 on period 2 and 3 respectively + 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)); + + // Total reserved should be the max of the two + assert_eq!(Balances::reserved_balance(1), 4); + + // Other people are reserved correctly too + assert_eq!(Balances::reserved_balance(2), 2); + assert_eq!(Balances::reserved_balance(3), 3); + + // End the auction. + 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)); + // Only 1 reserved since we have a deposit credit of 5. + 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)); + // 6 reserved since we are bidding on a new para; only works because we don't + 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::::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::>() + ); + }); +} + +#[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); + + // Make a bid and reserve a balance + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), para_1, 1, 1, 4, 9)); + assert_eq!(Balances::reserved_balance(1), 9); + assert_eq!(ReservedAmounts::::get((1, para_1)), Some(9)); + assert_eq!(Balances::reserved_balance(2), 0); + assert_eq!(ReservedAmounts::::get((2, para_2)), None); + + // Bigger bid, reserves new balance and returns funds + assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), para_2, 1, 1, 4, 19)); + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(ReservedAmounts::::get((1, para_1)), None); + assert_eq!(Balances::reserved_balance(2), 19); + assert_eq!(ReservedAmounts::::get((2, para_2)), Some(19)); + }); +} + +#[test] +fn initialize_winners_in_ending_period_works() { + new_test_ext().execute_with(|| { + let ed: u64 = ::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); + + // Make bids + 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::::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::::get(0), Some(winning)); + + 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) + ); + assert_eq!(Winning::::get(0), Some(winning)); + + run_to_block(11); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::EndingPeriod(1, 0) + ); + assert_eq!(Winning::::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::::EndingPeriod(2, 0) + ); + winning[SlotRange::TwoThree as u8 as usize] = Some((3, para_3, 29)); + assert_eq!(Winning::::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::::ParaNotRegistered + ); + assert_ok!(TestRegistrar::::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); + + // Para 1 just won an auction above and won some lease periods. + // No bids can work which overlap these periods. + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_noop!( + Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 1, 4, 1), + Error::::AlreadyLeasedOut, + ); + assert_noop!( + Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 1, 2, 1), + Error::::AlreadyLeasedOut, + ); + assert_noop!( + Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 3, 4, 1), + Error::::AlreadyLeasedOut, + ); + // This is okay, not an overlapping bid. + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 1, 1, 1)); + }); +} + +// Here we will test that taking only 10 samples during the ending period works as expected. +#[test] +fn less_winning_samples_work() { + new_test_ext().execute_with(|| { + let ed: u64 = ::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); + + // Make bids + 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::::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::::get(0), Some(winning)); + + 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) + ); + assert_eq!(Winning::::get(0), Some(winning)); + + // New bids update the current 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::::get(0), Some(winning)); + + run_to_block(20); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::EndingPeriod(1, 0) + ); + assert_eq!(Winning::::get(1), Some(winning)); + run_to_block(25); + // Overbid mid sample + 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::::get(1), Some(winning)); + + run_to_block(30); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::EndingPeriod(2, 0) + ); + assert_eq!(Winning::::get(2), Some(winning)); + + set_last_random(H256::from([254; 32]), 40); + run_to_block(40); + // Auction ended and winner selected + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::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::::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::::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(dummy_hash(), 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(|| { + 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::::get().is_none()); + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(ReservedAmounts::::iter().count(), 0); + assert_eq!(Winning::::iter().count(), 0); + }); +}