diff --git a/runtime/common/src/auctions.rs b/runtime/common/src/auctions.rs index a3866fed2ba2..e605328885eb 100644 --- a/runtime/common/src/auctions.rs +++ b/runtime/common/src/auctions.rs @@ -34,8 +34,9 @@ use primitives::v1::Id as ParaId; use sp_runtime::traits::{CheckedSub, One, Saturating, Zero}; use sp_std::{mem::swap, prelude::*}; -type CurrencyOf = <::Leaser as Leaser>::Currency; -type BalanceOf = <<::Leaser as Leaser>::Currency as Currency< +type CurrencyOf = + <::Leaser as Leaser<::BlockNumber>>::Currency; +type BalanceOf = <<::Leaser as Leaser<::BlockNumber>>::Currency as Currency< ::AccountId, >>::Balance; @@ -65,7 +66,9 @@ impl WeightInfo for TestWeightInfo { /// An auction index. We count auctions in this type. pub type AuctionIndex = u32; -type LeasePeriodOf = <::Leaser as Leaser>::LeasePeriod; +type LeasePeriodOf = + <::Leaser as Leaser<::BlockNumber>>::LeasePeriod; + // Winning data type. This encodes the top bidders of each range together with their bid. type WinningData = [Option<(::AccountId, ParaId, BalanceOf)>; SlotRange::SLOT_RANGE_COUNT]; @@ -91,7 +94,11 @@ pub mod pallet { type Event: From> + IsType<::Event>; /// The type representing the leasing system. - type Leaser: Leaser; + type Leaser: Leaser< + Self::BlockNumber, + AccountId = Self::AccountId, + LeasePeriod = Self::BlockNumber, + >; /// The parachain registrar type. type Registrar: Registrar; @@ -299,9 +306,8 @@ pub mod pallet { } } -impl Auctioneer for Pallet { +impl Auctioneer for Pallet { type AccountId = T::AccountId; - type BlockNumber = T::BlockNumber; type LeasePeriod = T::BlockNumber; type Currency = CurrencyOf; @@ -313,7 +319,7 @@ impl Auctioneer for Pallet { } // Returns the status of the auction given the current block number. - fn auction_status(now: Self::BlockNumber) -> AuctionStatus { + fn auction_status(now: T::BlockNumber) -> AuctionStatus { let early_end = match AuctionInfo::::get() { Some((_, early_end)) => early_end, None => return AuctionStatus::NotStarted, @@ -346,12 +352,13 @@ impl Auctioneer for Pallet { Self::handle_bid(bidder, para, AuctionCounter::::get(), first_slot, last_slot, amount) } - fn lease_period_index() -> Self::LeasePeriod { - T::Leaser::lease_period_index() + fn lease_period_index(b: T::BlockNumber) -> Option<(Self::LeasePeriod, bool)> { + T::Leaser::lease_period_index(b) } - fn lease_period() -> Self::LeasePeriod { - T::Leaser::lease_period() + #[cfg(any(feature = "runtime-benchmarks", test))] + fn lease_period_length() -> (T::BlockNumber, T::BlockNumber) { + T::Leaser::lease_period_length() } fn has_won_an_auction(para: ParaId, bidder: &T::AccountId) -> bool { @@ -374,10 +381,11 @@ impl Pallet { ) -> DispatchResult { let maybe_auction = AuctionInfo::::get(); ensure!(maybe_auction.is_none(), Error::::AuctionInProgress); - ensure!( - lease_period_index >= T::Leaser::lease_period_index(), - Error::::LeasePeriodInPast - ); + let now = frame_system::Pallet::::block_number(); + if let Some((current_lease_period, _)) = T::Leaser::lease_period_index(now) { + // If there is no active lease period, then we don't need to make this check. + ensure!(lease_period_index >= current_lease_period, Error::::LeasePeriodInPast); + } // Bump the counter. let n = AuctionCounter::::mutate(|n| { @@ -567,7 +575,9 @@ impl Pallet { let period_count = LeasePeriodOf::::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::ReserveFailed) | + Err(LeaseError::AlreadyEnded) | + Err(LeaseError::NoLeasePeriod) => { // Should never happen since we just unreserved this amount (and our offset is from the // present period). But if it does, there's not much we can do. }, @@ -735,7 +745,7 @@ mod tests { } pub struct TestLeaser; - impl Leaser for TestLeaser { + impl Leaser for TestLeaser { type AccountId = u64; type LeasePeriod = BlockNumber; type Currency = Balances; @@ -749,7 +759,10 @@ mod tests { ) -> Result<(), LeaseError> { LEASES.with(|l| { let mut leases = l.borrow_mut(); - if period_begin < Self::lease_period_index() { + 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) { @@ -779,12 +792,18 @@ mod tests { .unwrap_or_default() } - fn lease_period() -> Self::LeasePeriod { - 10 + fn lease_period_length() -> (BlockNumber, BlockNumber) { + (10, 0) } - fn lease_period_index() -> Self::LeasePeriod { - (System::block_number() / Self::lease_period()).into() + 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( diff --git a/runtime/common/src/crowdloan.rs b/runtime/common/src/crowdloan.rs index c45f9750f631..115a89fac542 100644 --- a/runtime/common/src/crowdloan.rs +++ b/runtime/common/src/crowdloan.rs @@ -72,8 +72,11 @@ use sp_runtime::{ }; use sp_std::vec::Vec; -type CurrencyOf = <::Auctioneer as Auctioneer>::Currency; -type LeasePeriodOf = <::Auctioneer as Auctioneer>::LeasePeriod; +type CurrencyOf = + <::Auctioneer as Auctioneer<::BlockNumber>>::Currency; +type LeasePeriodOf = <::Auctioneer as Auctioneer< + ::BlockNumber, +>>::LeasePeriod; type BalanceOf = as Currency<::AccountId>>::Balance; #[allow(dead_code)] @@ -203,8 +206,8 @@ pub mod pallet { /// The type representing the auctioning system. type Auctioneer: Auctioneer< + Self::BlockNumber, AccountId = Self::AccountId, - BlockNumber = Self::BlockNumber, LeasePeriod = Self::BlockNumber, >; @@ -313,6 +316,8 @@ pub mod pallet { AlreadyInNewRaise, /// No contributions allowed during the VRF delay VrfDelayInProgress, + /// A lease period has not started yet, due to an offset in the starting block. + NoLeasePeriod, } #[pallet::hooks] @@ -365,20 +370,31 @@ pub mod pallet { verifier: Option, ) -> DispatchResult { let depositor = ensure_signed(origin)?; + let now = frame_system::Pallet::::block_number(); ensure!(first_period <= last_period, Error::::LastPeriodBeforeFirstPeriod); let last_period_limit = first_period .checked_add(&((SlotRange::LEASE_PERIODS_PER_SLOT as u32) - 1).into()) .ok_or(Error::::FirstPeriodTooFarInFuture)?; ensure!(last_period <= last_period_limit, Error::::LastPeriodTooFarInFuture); - ensure!(end > >::block_number(), Error::::CannotEndInPast); - let last_possible_win_date = (first_period.saturating_add(One::one())) - .saturating_mul(T::Auctioneer::lease_period()); - ensure!(end <= last_possible_win_date, Error::::EndTooFarInFuture); - ensure!( - first_period >= T::Auctioneer::lease_period_index(), - Error::::FirstPeriodInPast - ); + ensure!(end > now, Error::::CannotEndInPast); + + // Here we check the lease period on the ending block is at most the first block of the + // period after `first_period`. If it would be larger, there is no way we could win an + // active auction, thus it would make no sense to have a crowdloan this long. + let (lease_period_at_end, is_first_block) = + T::Auctioneer::lease_period_index(end).ok_or(Error::::NoLeasePeriod)?; + let adjusted_lease_period_at_end = if is_first_block { + lease_period_at_end.saturating_sub(One::one()) + } else { + lease_period_at_end + }; + ensure!(adjusted_lease_period_at_end <= first_period, Error::::EndTooFarInFuture); + + // Can't start a crowdloan for a lease period that already passed. + if let Some((current_lease_period, _)) = T::Auctioneer::lease_period_index(now) { + ensure!(first_period >= current_lease_period, Error::::FirstPeriodInPast); + } // There should not be an existing fund. ensure!(!Funds::::contains_key(index), Error::::FundNotEnded); @@ -439,7 +455,9 @@ pub mod pallet { ensure!(now < fund.end, Error::::ContributionPeriodOver); // Make sure crowdloan is in a valid lease period - let current_lease_period = T::Auctioneer::lease_period_index(); + let now = frame_system::Pallet::::block_number(); + let (current_lease_period, _) = + T::Auctioneer::lease_period_index(now).ok_or(Error::::NoLeasePeriod)?; ensure!(current_lease_period <= fund.first_period, Error::::ContributionPeriodOver); // Make sure crowdloan has not already won. @@ -751,7 +769,8 @@ impl Pallet { // `fund.end` can represent the end of a failed crowdloan or the beginning of retirement // If the current lease period is past the first period they are trying to bid for, then // it is already too late to win the bid. - let current_lease_period = T::Auctioneer::lease_period_index(); + let (current_lease_period, _) = + T::Auctioneer::lease_period_index(now).ok_or(Error::::NoLeasePeriod)?; ensure!( now >= fund.end || current_lease_period > fund.first_period, Error::::FundNotEnded @@ -931,14 +950,16 @@ mod tests { } pub struct TestAuctioneer; - impl Auctioneer for TestAuctioneer { + impl Auctioneer for TestAuctioneer { type AccountId = u64; - type BlockNumber = BlockNumber; type LeasePeriod = u64; type Currency = Balances; fn new_auction(duration: u64, lease_period_index: u64) -> DispatchResult { - assert!(lease_period_index >= Self::lease_period_index()); + let now = System::block_number(); + let (current_lease_period, _) = + Self::lease_period_index(now).ok_or("no lease period yet")?; + assert!(lease_period_index >= current_lease_period); let ending = System::block_number().saturating_add(duration); AUCTION.with(|p| *p.borrow_mut() = Some((lease_period_index, ending))); @@ -991,12 +1012,17 @@ mod tests { Ok(()) } - fn lease_period_index() -> u64 { - System::block_number() / Self::lease_period() + fn lease_period_index(b: BlockNumber) -> Option<(u64, 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 lease_period() -> u64 { - 20 + fn lease_period_length() -> (u64, u64) { + (20, 0) } fn has_won_an_auction(para: ParaId, bidder: &u64) -> bool { @@ -1367,7 +1393,8 @@ mod tests { let para_3 = new_para(); assert_ok!(Crowdloan::create(Origin::signed(1), para_3, 1000, 1, 4, 40, None)); run_to_block(40); - assert_eq!(TestAuctioneer::lease_period_index(), 2); + let now = System::block_number(); + assert_eq!(TestAuctioneer::lease_period_index(now).unwrap().0, 2); assert_noop!( Crowdloan::contribute(Origin::signed(1), para_3, 49, None), Error::::ContributionPeriodOver @@ -1842,7 +1869,11 @@ mod benchmarking { fn create_fund(id: u32, end: T::BlockNumber) -> ParaId { let cap = BalanceOf::::max_value(); - let lease_period_index = T::Auctioneer::lease_period_index(); + let (_, offset) = T::Auctioneer::lease_period_length(); + // Set to the very beginning of lease period index 0. + frame_system::Pallet::::set_block_number(offset); + let now = frame_system::Pallet::::block_number(); + let (lease_period_index, _) = T::Auctioneer::lease_period_index(now).unwrap_or_default(); let first_period = lease_period_index; let last_period = lease_period_index + ((SlotRange::LEASE_PERIODS_PER_SLOT as u32) - 1).into(); @@ -1894,7 +1925,8 @@ mod benchmarking { let cap = BalanceOf::::max_value(); let first_period = 0u32.into(); let last_period = 3u32.into(); - let end = T::Auctioneer::lease_period(); + let (lpl, offset) = T::Auctioneer::lease_period_length(); + let end = lpl + offset; let caller: T::AccountId = whitelisted_caller(); let head_data = T::Registrar::worst_head_data(); @@ -1913,7 +1945,9 @@ mod benchmarking { // Contribute has two arms: PreEnding and Ending, but both are equal complexity. contribute { - let fund_index = create_fund::(1, 100u32.into()); + let (lpl, offset) = T::Auctioneer::lease_period_length(); + let end = lpl + offset; + let fund_index = create_fund::(1, end); let caller: T::AccountId = whitelisted_caller(); let contribution = T::MinContribution::get(); CurrencyOf::::make_free_balance_be(&caller, BalanceOf::::max_value()); @@ -1931,7 +1965,9 @@ mod benchmarking { } withdraw { - let fund_index = create_fund::(1337, 100u32.into()); + let (lpl, offset) = T::Auctioneer::lease_period_length(); + let end = lpl + offset; + let fund_index = create_fund::(1337, end); let caller: T::AccountId = whitelisted_caller(); let contributor = account("contributor", 0, 0); contribute_fund::(&contributor, fund_index); @@ -1945,7 +1981,9 @@ mod benchmarking { #[skip_meta] refund { let k in 0 .. T::RemoveKeysLimit::get(); - let fund_index = create_fund::(1337, 100u32.into()); + let (lpl, offset) = T::Auctioneer::lease_period_length(); + let end = lpl + offset; + let fund_index = create_fund::(1337, end); // Dissolve will remove at most `RemoveKeysLimit` at once. for i in 0 .. k { @@ -1960,7 +1998,9 @@ mod benchmarking { } dissolve { - let fund_index = create_fund::(1337, 100u32.into()); + let (lpl, offset) = T::Auctioneer::lease_period_length(); + let end = lpl + offset; + let fund_index = create_fund::(1337, end); let caller: T::AccountId = whitelisted_caller(); frame_system::Pallet::::set_block_number(T::BlockNumber::max_value()); }: _(RawOrigin::Signed(caller.clone()), fund_index) @@ -1973,7 +2013,8 @@ mod benchmarking { let cap = BalanceOf::::max_value(); let first_period = 0u32.into(); let last_period = 3u32.into(); - let end = T::Auctioneer::lease_period(); + let (lpl, offset) = T::Auctioneer::lease_period_length(); + let end = lpl + offset; let caller: T::AccountId = whitelisted_caller(); let head_data = T::Registrar::worst_head_data(); @@ -1997,7 +2038,9 @@ mod benchmarking { } add_memo { - let fund_index = create_fund::(1, 100u32.into()); + let (lpl, offset) = T::Auctioneer::lease_period_length(); + let end = lpl + offset; + let fund_index = create_fund::(1, end); let caller: T::AccountId = whitelisted_caller(); contribute_fund::(&caller, fund_index); let worst_memo = vec![42; T::MaxMemoLength::get().into()]; @@ -2011,7 +2054,9 @@ mod benchmarking { } poke { - let fund_index = create_fund::(1, 100u32.into()); + let (lpl, offset) = T::Auctioneer::lease_period_length(); + let end = lpl + offset; + let fund_index = create_fund::(1, end); let caller: T::AccountId = whitelisted_caller(); contribute_fund::(&caller, fund_index); NewRaise::::kill(); @@ -2028,7 +2073,8 @@ mod benchmarking { on_initialize { // We test the complexity over different number of new raise let n in 2 .. 100; - let end_block: T::BlockNumber = 100u32.into(); + let (lpl, offset) = T::Auctioneer::lease_period_length(); + let end_block = lpl + offset - 1u32.into(); let pubkey = crypto::create_ed25519_pubkey(b"//verifier".to_vec()); @@ -2043,7 +2089,8 @@ mod benchmarking { Crowdloan::::contribute(RawOrigin::Signed(contributor).into(), fund_index, contribution, Some(sig))?; } - let lease_period_index = T::Auctioneer::lease_period_index(); + let now = frame_system::Pallet::::block_number(); + let (lease_period_index, _) = T::Auctioneer::lease_period_index(now).unwrap_or_default(); let duration = end_block .checked_sub(&frame_system::Pallet::::block_number()) .ok_or("duration of auction less than zero")?; @@ -2062,7 +2109,7 @@ mod benchmarking { impl_benchmark_test_suite!( Crowdloan, - crate::integration_tests::new_test_ext(), + crate::integration_tests::new_test_ext_with_offset(10), crate::integration_tests::Test, ); } diff --git a/runtime/common/src/integration_tests.rs b/runtime/common/src/integration_tests.rs index 5653100815aa..f8f84b5f1fbc 100644 --- a/runtime/common/src/integration_tests.rs +++ b/runtime/common/src/integration_tests.rs @@ -203,6 +203,7 @@ impl auctions::Config for Test { parameter_types! { pub const LeasePeriod: BlockNumber = 100; + pub static LeaseOffset: BlockNumber = 0; } impl slots::Config for Test { @@ -210,6 +211,7 @@ impl slots::Config for Test { type Currency = Balances; type Registrar = Registrar; type LeasePeriod = LeasePeriod; + type LeaseOffset = LeaseOffset; type WeightInfo = crate::slots::TestWeightInfo; } @@ -254,6 +256,11 @@ pub fn new_test_ext() -> TestExternalities { ext } +pub fn new_test_ext_with_offset(n: BlockNumber) -> TestExternalities { + LeaseOffset::set(n); + new_test_ext() +} + const BLOCKS_PER_SESSION: u32 = 10; fn maybe_new_session(n: u32) { @@ -298,171 +305,176 @@ fn last_event() -> Event { System::events().pop().expect("Event expected").event } +// Runs an end to end test of the auction, crowdloan, slots, and onboarding process over varying +// lease period offsets. #[test] fn basic_end_to_end_works() { - new_test_ext().execute_with(|| { - let para_1 = LOWEST_PUBLIC_ID; - let para_2 = LOWEST_PUBLIC_ID + 1; - assert!(System::block_number().is_one()); - // User 1 and 2 will own parachains - Balances::make_free_balance_be(&1, 1_000_000_000); - Balances::make_free_balance_be(&2, 1_000_000_000); - // First register 2 parathreads - let genesis_head = Registrar::worst_head_data(); - let validation_code = Registrar::worst_validation_code(); - assert_ok!(Registrar::reserve(Origin::signed(1))); - assert_ok!(Registrar::register( - Origin::signed(1), - ParaId::from(para_1), - genesis_head.clone(), - validation_code.clone(), - )); - assert_ok!(Registrar::reserve(Origin::signed(2))); - assert_ok!(Registrar::register( - Origin::signed(2), - ParaId::from(2001), - genesis_head, - validation_code, - )); - - // Paras should be onboarding - assert_eq!(Paras::lifecycle(ParaId::from(para_1)), Some(ParaLifecycle::Onboarding)); - assert_eq!(Paras::lifecycle(ParaId::from(para_2)), Some(ParaLifecycle::Onboarding)); - - // Start a new auction in the future - let duration = 99u32; - let lease_period_index_start = 4u32; - assert_ok!(Auctions::new_auction(Origin::root(), duration, lease_period_index_start)); - - // 2 sessions later they are parathreads - run_to_session(2); - assert_eq!(Paras::lifecycle(ParaId::from(para_1)), Some(ParaLifecycle::Parathread)); - assert_eq!(Paras::lifecycle(ParaId::from(para_2)), Some(ParaLifecycle::Parathread)); - - // Para 1 will bid directly for slot 1, 2 - // Open a crowdloan for Para 2 for slot 3, 4 - assert_ok!(Crowdloan::create( - Origin::signed(2), - ParaId::from(para_2), - 1_000, // Cap - lease_period_index_start + 2, // First Slot - lease_period_index_start + 3, // Last Slot - 200, // Block End - None, - )); - let crowdloan_account = Crowdloan::fund_account_id(ParaId::from(para_2)); - - // Auction ending begins on block 100, so we make a bid before then. - run_to_block(90); - - Balances::make_free_balance_be(&10, 1_000_000_000); - Balances::make_free_balance_be(&20, 1_000_000_000); + for offset in [0u32, 50, 100, 200].iter() { + LeaseOffset::set(*offset); + new_test_ext().execute_with(|| { + let para_1 = LOWEST_PUBLIC_ID; + let para_2 = LOWEST_PUBLIC_ID + 1; + assert!(System::block_number().is_one()); + // User 1 and 2 will own parachains + Balances::make_free_balance_be(&1, 1_000_000_000); + Balances::make_free_balance_be(&2, 1_000_000_000); + // First register 2 parathreads + let genesis_head = Registrar::worst_head_data(); + let validation_code = Registrar::worst_validation_code(); + assert_ok!(Registrar::reserve(Origin::signed(1))); + assert_ok!(Registrar::register( + Origin::signed(1), + ParaId::from(para_1), + genesis_head.clone(), + validation_code.clone(), + )); + assert_ok!(Registrar::reserve(Origin::signed(2))); + assert_ok!(Registrar::register( + Origin::signed(2), + ParaId::from(2001), + genesis_head, + validation_code, + )); - // User 10 will bid directly for parachain 1 - assert_ok!(Auctions::bid( - Origin::signed(10), - ParaId::from(para_1), - 1, // Auction Index - lease_period_index_start + 0, // First Slot - lease_period_index_start + 1, // Last slot - 910, // Amount - )); + // Paras should be onboarding + assert_eq!(Paras::lifecycle(ParaId::from(para_1)), Some(ParaLifecycle::Onboarding)); + assert_eq!(Paras::lifecycle(ParaId::from(para_2)), Some(ParaLifecycle::Onboarding)); - // User 2 will be a contribute to crowdloan for parachain 2 - Balances::make_free_balance_be(&2, 1_000_000_000); - assert_ok!(Crowdloan::contribute(Origin::signed(2), ParaId::from(para_2), 920, None)); + // Start a new auction in the future + let duration = 99u32 + offset; + let lease_period_index_start = 4u32; + assert_ok!(Auctions::new_auction(Origin::root(), duration, lease_period_index_start)); - // Auction ends at block 110 - run_to_block(109); - assert_eq!( - last_event(), - crowdloan::Event::::HandleBidResult(ParaId::from(para_2), Ok(())).into(), - ); - run_to_block(110); - assert_eq!(last_event(), auctions::Event::::AuctionClosed(1).into()); + // 2 sessions later they are parathreads + run_to_session(2); + assert_eq!(Paras::lifecycle(ParaId::from(para_1)), Some(ParaLifecycle::Parathread)); + assert_eq!(Paras::lifecycle(ParaId::from(para_2)), Some(ParaLifecycle::Parathread)); - // Paras should have won slots - assert_eq!( - slots::Leases::::get(ParaId::from(para_1)), - // -- 1 --- 2 --- 3 --------- 4 ------------ 5 -------- - vec![None, None, None, Some((10, 910)), Some((10, 910))], - ); - assert_eq!( - slots::Leases::::get(ParaId::from(para_2)), - // -- 1 --- 2 --- 3 --- 4 --- 5 ---------------- 6 --------------------------- 7 ---------------- - vec![ - None, - None, - None, - None, + // Para 1 will bid directly for slot 1, 2 + // Open a crowdloan for Para 2 for slot 3, 4 + assert_ok!(Crowdloan::create( + Origin::signed(2), + ParaId::from(para_2), + 1_000, // Cap + lease_period_index_start + 2, // First Slot + lease_period_index_start + 3, // Last Slot + 200 + offset, // Block End None, - Some((crowdloan_account, 920)), - Some((crowdloan_account, 920)) - ], - ); - - // Should not be able to contribute to a winning crowdloan - Balances::make_free_balance_be(&3, 1_000_000_000); - assert_noop!( - Crowdloan::contribute(Origin::signed(3), ParaId::from(2001), 10, None), - CrowdloanError::::BidOrLeaseActive - ); - - // New leases will start on block 400 - let lease_start_block = 400; - run_to_block(lease_start_block); - - // First slot, Para 1 should be transitioning to Parachain - assert_eq!( - Paras::lifecycle(ParaId::from(para_1)), - Some(ParaLifecycle::UpgradingParathread) - ); - assert_eq!(Paras::lifecycle(ParaId::from(para_2)), Some(ParaLifecycle::Parathread)); - - // Two sessions later, it has upgraded - run_to_block(lease_start_block + 20); - assert_eq!(Paras::lifecycle(ParaId::from(para_1)), Some(ParaLifecycle::Parachain)); - assert_eq!(Paras::lifecycle(ParaId::from(para_2)), Some(ParaLifecycle::Parathread)); - - // Second slot nothing happens :) - run_to_block(lease_start_block + 100); - assert_eq!(Paras::lifecycle(ParaId::from(para_1)), Some(ParaLifecycle::Parachain)); - assert_eq!(Paras::lifecycle(ParaId::from(para_2)), Some(ParaLifecycle::Parathread)); - - // Third slot, Para 2 should be upgrading, and Para 1 is downgrading - run_to_block(lease_start_block + 200); - assert_eq!( - Paras::lifecycle(ParaId::from(para_1)), - Some(ParaLifecycle::DowngradingParachain) - ); - assert_eq!( - Paras::lifecycle(ParaId::from(para_2)), - Some(ParaLifecycle::UpgradingParathread) - ); + )); + let crowdloan_account = Crowdloan::fund_account_id(ParaId::from(para_2)); - // Two sessions later, they have transitioned - run_to_block(lease_start_block + 220); - assert_eq!(Paras::lifecycle(ParaId::from(para_1)), Some(ParaLifecycle::Parathread)); - assert_eq!(Paras::lifecycle(ParaId::from(para_2)), Some(ParaLifecycle::Parachain)); + // Auction ending begins on block 100 + offset, so we make a bid before then. + run_to_block(90 + offset); - // Fourth slot nothing happens :) - run_to_block(lease_start_block + 300); - assert_eq!(Paras::lifecycle(ParaId::from(para_1)), Some(ParaLifecycle::Parathread)); - assert_eq!(Paras::lifecycle(ParaId::from(para_2)), Some(ParaLifecycle::Parachain)); + Balances::make_free_balance_be(&10, 1_000_000_000); + Balances::make_free_balance_be(&20, 1_000_000_000); - // Fifth slot, Para 2 is downgrading - run_to_block(lease_start_block + 400); - assert_eq!(Paras::lifecycle(ParaId::from(para_1)), Some(ParaLifecycle::Parathread)); - assert_eq!( - Paras::lifecycle(ParaId::from(para_2)), - Some(ParaLifecycle::DowngradingParachain) - ); + // User 10 will bid directly for parachain 1 + assert_ok!(Auctions::bid( + Origin::signed(10), + ParaId::from(para_1), + 1, // Auction Index + lease_period_index_start + 0, // First Slot + lease_period_index_start + 1, // Last slot + 910, // Amount + )); - // Two sessions later, Para 2 is downgraded - run_to_block(lease_start_block + 420); - assert_eq!(Paras::lifecycle(ParaId::from(para_1)), Some(ParaLifecycle::Parathread)); - assert_eq!(Paras::lifecycle(ParaId::from(para_2)), Some(ParaLifecycle::Parathread)); - }); + // User 2 will be a contribute to crowdloan for parachain 2 + Balances::make_free_balance_be(&2, 1_000_000_000); + assert_ok!(Crowdloan::contribute(Origin::signed(2), ParaId::from(para_2), 920, None)); + + // Auction ends at block 110 + offset + run_to_block(109 + offset); + assert_eq!( + last_event(), + crowdloan::Event::::HandleBidResult(ParaId::from(para_2), Ok(())).into(), + ); + run_to_block(110 + offset); + assert_eq!(last_event(), auctions::Event::::AuctionClosed(1).into()); + + // Paras should have won slots + assert_eq!( + slots::Leases::::get(ParaId::from(para_1)), + // -- 1 --- 2 --- 3 --------- 4 ------------ 5 -------- + vec![None, None, None, Some((10, 910)), Some((10, 910))], + ); + assert_eq!( + slots::Leases::::get(ParaId::from(para_2)), + // -- 1 --- 2 --- 3 --- 4 --- 5 ---------------- 6 --------------------------- 7 ---------------- + vec![ + None, + None, + None, + None, + None, + Some((crowdloan_account, 920)), + Some((crowdloan_account, 920)) + ], + ); + + // Should not be able to contribute to a winning crowdloan + Balances::make_free_balance_be(&3, 1_000_000_000); + assert_noop!( + Crowdloan::contribute(Origin::signed(3), ParaId::from(2001), 10, None), + CrowdloanError::::BidOrLeaseActive + ); + + // New leases will start on block 400 + let lease_start_block = 400 + offset; + run_to_block(lease_start_block); + + // First slot, Para 1 should be transitioning to Parachain + assert_eq!( + Paras::lifecycle(ParaId::from(para_1)), + Some(ParaLifecycle::UpgradingParathread) + ); + assert_eq!(Paras::lifecycle(ParaId::from(para_2)), Some(ParaLifecycle::Parathread)); + + // Two sessions later, it has upgraded + run_to_block(lease_start_block + 20); + assert_eq!(Paras::lifecycle(ParaId::from(para_1)), Some(ParaLifecycle::Parachain)); + assert_eq!(Paras::lifecycle(ParaId::from(para_2)), Some(ParaLifecycle::Parathread)); + + // Second slot nothing happens :) + run_to_block(lease_start_block + 100); + assert_eq!(Paras::lifecycle(ParaId::from(para_1)), Some(ParaLifecycle::Parachain)); + assert_eq!(Paras::lifecycle(ParaId::from(para_2)), Some(ParaLifecycle::Parathread)); + + // Third slot, Para 2 should be upgrading, and Para 1 is downgrading + run_to_block(lease_start_block + 200); + assert_eq!( + Paras::lifecycle(ParaId::from(para_1)), + Some(ParaLifecycle::DowngradingParachain) + ); + assert_eq!( + Paras::lifecycle(ParaId::from(para_2)), + Some(ParaLifecycle::UpgradingParathread) + ); + + // Two sessions later, they have transitioned + run_to_block(lease_start_block + 220); + assert_eq!(Paras::lifecycle(ParaId::from(para_1)), Some(ParaLifecycle::Parathread)); + assert_eq!(Paras::lifecycle(ParaId::from(para_2)), Some(ParaLifecycle::Parachain)); + + // Fourth slot nothing happens :) + run_to_block(lease_start_block + 300); + assert_eq!(Paras::lifecycle(ParaId::from(para_1)), Some(ParaLifecycle::Parathread)); + assert_eq!(Paras::lifecycle(ParaId::from(para_2)), Some(ParaLifecycle::Parachain)); + + // Fifth slot, Para 2 is downgrading + run_to_block(lease_start_block + 400); + assert_eq!(Paras::lifecycle(ParaId::from(para_1)), Some(ParaLifecycle::Parathread)); + assert_eq!( + Paras::lifecycle(ParaId::from(para_2)), + Some(ParaLifecycle::DowngradingParachain) + ); + + // Two sessions later, Para 2 is downgraded + run_to_block(lease_start_block + 420); + assert_eq!(Paras::lifecycle(ParaId::from(para_1)), Some(ParaLifecycle::Parathread)); + assert_eq!(Paras::lifecycle(ParaId::from(para_2)), Some(ParaLifecycle::Parathread)); + }); + } } #[test] diff --git a/runtime/common/src/slots.rs b/runtime/common/src/slots.rs index 8fe4a77f187e..35c520afeaf1 100644 --- a/runtime/common/src/slots.rs +++ b/runtime/common/src/slots.rs @@ -83,6 +83,10 @@ pub mod pallet { #[pallet::constant] type LeasePeriod: Get; + /// The number of blocks to offset each lease period by. + #[pallet::constant] + type LeaseOffset: Get; + /// Weight Information for the Extrinsics in the Pallet type WeightInfo: WeightInfo; } @@ -138,14 +142,15 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn on_initialize(n: T::BlockNumber) -> Weight { - // If we're beginning a new lease period then handle that. - let lease_period = T::LeasePeriod::get(); - if (n % lease_period).is_zero() { - let lease_period_index = n / lease_period; - Self::manage_lease_period_start(lease_period_index) - } else { - 0 + if let Some((lease_period, first_block)) = Self::lease_period_index(n) { + // If we're beginning a new lease period then handle that. + if first_block { + return Self::manage_lease_period_start(lease_period) + } } + + // We didn't return early above, so we didn't do anything. + 0 } } @@ -321,7 +326,7 @@ impl crate::traits::OnSwap for Pallet { } } -impl Leaser for Pallet { +impl Leaser for Pallet { type AccountId = T::AccountId; type LeasePeriod = T::BlockNumber; type Currency = T::Currency; @@ -333,7 +338,9 @@ impl Leaser for Pallet { period_begin: Self::LeasePeriod, period_count: Self::LeasePeriod, ) -> Result<(), LeaseError> { - let current_lease_period = Self::lease_period_index(); + let now = frame_system::Pallet::::block_number(); + let (current_lease_period, _) = + Self::lease_period_index(now).ok_or(LeaseError::NoLeasePeriod)?; // Finally, we update the deposit held so it is `amount` for the new lease period // indices that were won in the auction. let offset = period_begin @@ -427,12 +434,18 @@ impl Leaser for Pallet { .unwrap_or_else(Zero::zero) } - fn lease_period() -> Self::LeasePeriod { - T::LeasePeriod::get() + #[cfg(any(feature = "runtime-benchmarks", test))] + fn lease_period_length() -> (T::BlockNumber, T::BlockNumber) { + (T::LeasePeriod::get(), T::LeaseOffset::get()) } - fn lease_period_index() -> Self::LeasePeriod { - >::block_number() / T::LeasePeriod::get() + fn lease_period_index(b: T::BlockNumber) -> Option<(Self::LeasePeriod, bool)> { + // Note that blocks before `LeaseOffset` do not count as any lease period. + let offset_block_now = b.checked_sub(&T::LeaseOffset::get())?; + let lease_period = offset_block_now / T::LeasePeriod::get(); + let first_block = (offset_block_now % T::LeasePeriod::get()).is_zero(); + + Some((lease_period, first_block)) } fn already_leased( @@ -440,7 +453,11 @@ impl Leaser for Pallet { first_period: Self::LeasePeriod, last_period: Self::LeasePeriod, ) -> bool { - let current_lease_period = Self::lease_period_index(); + let now = frame_system::Pallet::::block_number(); + let (current_lease_period, _) = match Self::lease_period_index(now) { + Some(clp) => clp, + None => return true, + }; // Can't look in the past, so we pick whichever is the biggest. let start_period = first_period.max(current_lease_period); @@ -545,6 +562,7 @@ mod tests { parameter_types! { pub const LeasePeriod: BlockNumber = 10; + pub static LeaseOffset: BlockNumber = 0; pub const ParaDeposit: u64 = 1; } @@ -553,6 +571,7 @@ mod tests { type Currency = Balances; type Registrar = TestRegistrar; type LeasePeriod = LeasePeriod; + type LeaseOffset = LeaseOffset; type WeightInfo = crate::slots::TestWeightInfo; } @@ -584,12 +603,14 @@ mod tests { fn basic_setup_works() { new_test_ext().execute_with(|| { run_to_block(1); - assert_eq!(Slots::lease_period(), 10); - assert_eq!(Slots::lease_period_index(), 0); + assert_eq!(Slots::lease_period_length(), (10, 0)); + let now = System::block_number(); + assert_eq!(Slots::lease_period_index(now).unwrap().0, 0); assert_eq!(Slots::deposit_held(1.into(), &1), 0); run_to_block(10); - assert_eq!(Slots::lease_period_index(), 1); + let now = System::block_number(); + assert_eq!(Slots::lease_period_index(now).unwrap().0, 1); }); } @@ -850,7 +871,8 @@ mod tests { )); run_to_block(20); - assert_eq!(Slots::lease_period_index(), 2); + let now = System::block_number(); + assert_eq!(Slots::lease_period_index(now).unwrap().0, 2); // Can't lease from the past assert!(Slots::lease_out(1.into(), &1, 1, 1, 1).is_err()); // Lease in the current period triggers onboarding @@ -913,6 +935,37 @@ mod tests { assert_eq!(TestRegistrar::::operations(), vec![(2.into(), 1, true),]); }); } + + #[test] + fn lease_period_offset_works() { + new_test_ext().execute_with(|| { + let (lpl, offset) = Slots::lease_period_length(); + assert_eq!(offset, 0); + assert_eq!(Slots::lease_period_index(0), Some((0, true))); + assert_eq!(Slots::lease_period_index(1), Some((0, false))); + assert_eq!(Slots::lease_period_index(lpl - 1), Some((0, false))); + assert_eq!(Slots::lease_period_index(lpl), Some((1, true))); + assert_eq!(Slots::lease_period_index(lpl + 1), Some((1, false))); + assert_eq!(Slots::lease_period_index(2 * lpl - 1), Some((1, false))); + assert_eq!(Slots::lease_period_index(2 * lpl), Some((2, true))); + assert_eq!(Slots::lease_period_index(2 * lpl + 1), Some((2, false))); + + // Lease period is 10, and we add an offset of 5. + LeaseOffset::set(5); + let (lpl, offset) = Slots::lease_period_length(); + assert_eq!(offset, 5); + assert_eq!(Slots::lease_period_index(0), None); + assert_eq!(Slots::lease_period_index(1), None); + assert_eq!(Slots::lease_period_index(offset), Some((0, true))); + assert_eq!(Slots::lease_period_index(lpl), Some((0, false))); + assert_eq!(Slots::lease_period_index(lpl - 1 + offset), Some((0, false))); + assert_eq!(Slots::lease_period_index(lpl + offset), Some((1, true))); + assert_eq!(Slots::lease_period_index(lpl + offset + 1), Some((1, false))); + assert_eq!(Slots::lease_period_index(2 * lpl - 1 + offset), Some((1, false))); + assert_eq!(Slots::lease_period_index(2 * lpl + offset), Some((2, true))); + assert_eq!(Slots::lease_period_index(2 * lpl + offset + 1), Some((2, false))); + }); + } } #[cfg(feature = "runtime-benchmarks")] diff --git a/runtime/common/src/traits.rs b/runtime/common/src/traits.rs index 938fefd963c0..98abe62c2a11 100644 --- a/runtime/common/src/traits.rs +++ b/runtime/common/src/traits.rs @@ -94,10 +94,12 @@ pub enum LeaseError { AlreadyLeased, /// The period to be leased has already ended. AlreadyEnded, + /// A lease period has not started yet, due to an offset in the starting block. + NoLeasePeriod, } /// Lease manager. Used by the auction module to handle parachain slot leases. -pub trait Leaser { +pub trait Leaser { /// An account identifier for a leaser. type AccountId; @@ -133,11 +135,16 @@ pub trait Leaser { leaser: &Self::AccountId, ) -> >::Balance; - /// The lease period. This is constant, but can't be a `const` due to it being a runtime configurable quantity. - fn lease_period() -> Self::LeasePeriod; + /// The length of a lease period, and any offset which may be introduced. + /// This is only used in benchmarking to automate certain calls. + #[cfg(any(feature = "runtime-benchmarks", test))] + fn lease_period_length() -> (BlockNumber, BlockNumber); - /// Returns the current lease period. - fn lease_period_index() -> Self::LeasePeriod; + /// Returns the lease period at `block`, and if this is the first block of a new lease period. + /// + /// Will return `None` if the first lease period has not started yet, for example when an offset + /// is placed. + fn lease_period_index(block: BlockNumber) -> Option<(Self::LeasePeriod, bool)>; /// Returns true if the parachain already has a lease in any of lease periods in the inclusive /// range `[first_period, last_period]`, intersected with the unbounded range [`current_lease_period`..] . @@ -189,13 +196,10 @@ impl AuctionStatus { } } -pub trait Auctioneer { +pub trait Auctioneer { /// An account identifier for a leaser. type AccountId; - /// The measurement type for counting blocks. - type BlockNumber; - /// The measurement type for counting lease periods (generally the same as `BlockNumber`). type LeasePeriod; @@ -207,13 +211,10 @@ pub trait Auctioneer { /// This can only happen when there isn't already an auction in progress. Accepts the `duration` /// of this auction and the `lease_period_index` of the initial lease period of the four that /// are to be auctioned. - fn new_auction( - duration: Self::BlockNumber, - lease_period_index: Self::LeasePeriod, - ) -> DispatchResult; + fn new_auction(duration: BlockNumber, lease_period_index: Self::LeasePeriod) -> DispatchResult; /// Given the current block number, return the current auction status. - fn auction_status(now: Self::BlockNumber) -> AuctionStatus; + fn auction_status(now: BlockNumber) -> AuctionStatus; /// Place a bid in the current auction. /// @@ -234,11 +235,16 @@ pub trait Auctioneer { amount: >::Balance, ) -> DispatchResult; - /// Returns the current lease period. - fn lease_period_index() -> Self::LeasePeriod; + /// The length of a lease period, and any offset which may be introduced. + /// This is only used in benchmarking to automate certain calls. + #[cfg(any(feature = "runtime-benchmarks", test))] + fn lease_period_length() -> (BlockNumber, BlockNumber); - /// Returns the length of a lease period. - fn lease_period() -> Self::LeasePeriod; + /// Returns the lease period at `block`, and if this is the first block of a new lease period. + /// + /// Will return `None` if the first lease period has not started yet, for example when an offset + /// is placed. + fn lease_period_index(block: BlockNumber) -> Option<(Self::LeasePeriod, bool)>; /// Check if the para and user combination has won an auction in the past. fn has_won_an_auction(para: ParaId, bidder: &Self::AccountId) -> bool; diff --git a/runtime/kusama/src/lib.rs b/runtime/kusama/src/lib.rs index 5f461a88ea2e..bbf0aae53123 100644 --- a/runtime/kusama/src/lib.rs +++ b/runtime/kusama/src/lib.rs @@ -1212,6 +1212,7 @@ impl slots::Config for Runtime { type Currency = Balances; type Registrar = Registrar; type LeasePeriod = LeasePeriod; + type LeaseOffset = (); type WeightInfo = weights::runtime_common_slots::WeightInfo; } diff --git a/runtime/polkadot/src/lib.rs b/runtime/polkadot/src/lib.rs index 7a772733809e..e57f4564e7a2 100644 --- a/runtime/polkadot/src/lib.rs +++ b/runtime/polkadot/src/lib.rs @@ -1173,6 +1173,12 @@ impl paras_registrar::Config for Runtime { parameter_types! { // 12 weeks = 3 months per lease period -> 8 lease periods ~ 2 years pub const LeasePeriod: BlockNumber = 12 * WEEKS; + // Polkadot Genesis was on May 26, 2020. + // Target Parachain Onboarding Date: Dec 15, 2021. + // Difference is 568 days. + // We want a lease period to start on the target onboarding date. + // 568 % (12 * 7) = 64 day offset + pub const LeaseOffset: BlockNumber = 64 * DAYS; } impl slots::Config for Runtime { @@ -1180,6 +1186,7 @@ impl slots::Config for Runtime { type Currency = Balances; type Registrar = Registrar; type LeasePeriod = LeasePeriod; + type LeaseOffset = LeaseOffset; type WeightInfo = weights::runtime_common_slots::WeightInfo; } diff --git a/runtime/rococo/src/lib.rs b/runtime/rococo/src/lib.rs index fc8f8b2c138b..db00630ede9f 100644 --- a/runtime/rococo/src/lib.rs +++ b/runtime/rococo/src/lib.rs @@ -1003,6 +1003,7 @@ impl slots::Config for Runtime { type Currency = Balances; type Registrar = Registrar; type LeasePeriod = LeasePeriod; + type LeaseOffset = (); type WeightInfo = slots::TestWeightInfo; } diff --git a/runtime/westend/src/lib.rs b/runtime/westend/src/lib.rs index 338fa57c52b6..17138875a7c2 100644 --- a/runtime/westend/src/lib.rs +++ b/runtime/westend/src/lib.rs @@ -883,6 +883,7 @@ impl slots::Config for Runtime { type Currency = Balances; type Registrar = Registrar; type LeasePeriod = LeasePeriod; + type LeaseOffset = (); type WeightInfo = weights::runtime_common_slots::WeightInfo; }