Unverified Commit 0f00a780 authored by Gavin Wood's avatar Gavin Wood Committed by GitHub
Browse files

New slots/auctions architecture (#2294)



* TODOs

* Add auctions.rs, comment on changes needed.

* Remove cruft from slots

* Remove more from auctions.rs

* More logic drafting in slots.

* More logic in slots.rs

* patch some errors

* more fixes

* last nit

* Cleanups in slots.rs

* Cleanups in slots.rs

* patches

* make build

* crowdloan to new api

* auction compile

* Use ParaId instead of FundIndex in Crowdloan (#2303)

* use paraid instead of fundindex

* Update crowdloan.rs

* check caller is manager

* Auction tests and fix build warnings.

* Configurable origin for initiating auctions

* Remove on_finalize

* #2303 (manual merge)

* Tests for Slots

* some registrar tests

* Apply suggestions from code review
Co-authored-by: thiolliere's avatarGuillaume Thiolliere <gui.thiolliere@gmail.com>

* Update runtime/common/src/slots.rs
Co-authored-by: thiolliere's avatarGuillaume Thiolliere <gui.thiolliere@gmail.com>

* Slots uses Registrar for CurrentChains

* swap works test

* on swap impl

* traitify parachain cleanup

* explicit lifecycle tracking for paras

* initial implementation of lifecycles and upgrades

* clean up a bit

* Update runtime/common/src/slots.rs
Co-authored-by: thiolliere's avatarGuillaume Thiolliere <gui.thiolliere@gmail.com>

* fix doc comment

* more rigid lifecycle checks

* include paras which are transitioning, and lifecycle query

* format guide

* update api

* update guide

* explicit outgoing state, fix genesis

* handle outgoing with transitioning paras

* Revert "explicit lifecycle tracking for paras"

This reverts commit 4177af7b.

* remove lifecycle tracking from registrar

* do not include transitioning paras in identifier

* Update paras_registrar.rs

* final patches to registrar

* Fix test

* use noop in test

* clean up pending swap on deregistration

* finish registrar tests

* Update roadmap/implementers-guide/src/runtime/paras.md

* Update roadmap/implementers-guide/src/runtime/paras.md

* Update roadmap/implementers-guide/src/runtime/paras.md

* Apply suggestions from code review

* Use matches macro

* Correct terms

* Apply suggestions from code review

* Remove direct need for Slots and Registrar from Crowdloan

* Rejig things slightly

* actions queue

* Revert "actions queue"

This reverts commit b2e9011e.

* Traitify Auction interface.

* Mockups and initial code for Crowdloan testing

* One test...

* collapse onboarding state

* fix some crowdloan tests

* one more

* start benchmarks for auctions

* benchmark bid

* fix more crowdloan tests

* onboard and begin retirement no longer exist

* Revert "onboard and begin retirement no longer exist"

This reverts commit 2e100fd9

.

* Simplify crowdloan and make it work.

* Fixes

* fix some

* finish merge fixes

* fix refund bug in auctions

* Add traits to Registrar for tests and benchmarks

* fix more auction benchmarks

* Fix TestAuctioneer

* finish crowdloan benchmarks

* start setting up full integration tests

* expand integration tests

* finish basic integration test

* add more integration tests

* begin slots benchmarks

* start paras registrar benchmarks

* fix merge

* fix tests

* clean up paras registrar

* remove println

* remove outdated cleanup config

* update benchmarks

* Add WeightInfo

* enable runtime-benchmarks feature flag

* complete swap benchmark

* add parachains and onboarding into westend

* add benchmarks and genesis

* cargo run --release --features=runtime-benchmarks -- benchmark --chain=westend-dev --steps=50 --repeat=20 --pallet=auctions --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --header=./file_header.txt --output=./runtime/westend/src/weights/

* cargo run --release --features=runtime-benchmarks -- benchmark --chain=westend-dev --steps=50 --repeat=20 --pallet=slots --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --header=./file_header.txt --output=./runtime/westend/src/weights/

* fix benchmark execution

* cargo run --release --features=runtime-benchmarks -- benchmark --chain=westend-dev --steps=50 --repeat=20 --pallet=crowdloan --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --header=./file_header.txt --output=./runtime/westend/src/weights/

* cargo run --release --features=runtime-benchmarks -- benchmark --chain=westend-dev --steps=50 --repeat=20 --pallet=paras_registrar --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --header=./file_header.txt --output=./runtime/westend/src/weights/

* Use `new_raise_len` in crowdloan on_initialize

* Update paras_registrar.rs

* fix westend merge

* impl on_swap for crowdloan

* Check fund exists before create

* update for crowdloan sig

* cargo run --release --features=runtime-benchmarks -- benchmark --chain=westend-dev --steps=50 --repeat=20 --pallet=crowdloan --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --header=./file_header.txt --output=./runtime/westend/src/weights/

* slots on_initialize

* use integration tests environment for benchmarks

* fix hrmp event

* auction on_initialize

* cargo run --release --features=runtime-benchmarks -- benchmark --chain=westend-dev --steps=50 --repeat=20 --pallet=auctions --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --header=./file_header.txt --output=./runtime/westend/src/weights/

* fix storage name in auctions

* add auction_index to winning data

* winning data takes into account current auction index

* remove println

* cargo run --release --features=runtime-benchmarks -- benchmark --chain=westend-dev --steps=50 --repeat=20 --pallet=auctions --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --header=./file_header.txt --output=./runtime/westend/src/weights/

* cargo run --release --features=runtime-benchmarks -- benchmark --chain=westend-dev --steps=50 --repeat=20 --pallet=slots --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --header=./file_header.txt --output=./runtime/westend/src/weights/

* Revert "add auction_index to winning data"

* PastRandomness.

* Fixes

* Use new randomness

* fix use of randomness in auctions and runtime config

* expose consts

* fix auction test

* add deposit per byte for para registration

* basic swap integration test

* make swap test more comprehensive

* Add WinningVec for easier retrieval in the front-end.

* clean up `WinningVec` at the end

* Add event for when a new best bid comes in

* Fix propagation of winners in ending period

* fix benchmarks, refund weight in dissolve

* fix unused

* remove some TODOs

* setup opaque keys for paras in westend

* cargo run --release --features=runtime-benchmarks -- benchmark --chain=westend-dev --steps=50 --repeat=20 --pallet=crowdloan --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --header=./file_header.txt --output=./runtime/westend/src/weights/

* remove unused

* cargo run --release --features=runtime-benchmarks -- benchmark --chain=westend-dev --steps=50 --repeat=20 --pallet=auctions --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --header=./file_header.txt --output=./runtime/westend/src/weights/

* back to regular runtime config

* use saturating math where user input can be

* better first slot check

* Update runtime/common/src/claims.rs

* update westend onswap impl
Co-authored-by: Shawn Tabrizi's avatarShawn Tabrizi <shawntabrizi@gmail.com>
Co-authored-by: thiolliere's avatarGuillaume Thiolliere <gui.thiolliere@gmail.com>
Co-authored-by: default avatarParity Benchmarking Bot <admin@parity.io>
parent f64e28a1
Pipeline #128480 passed with stages
in 31 minutes and 52 seconds
......@@ -5960,6 +5960,7 @@ dependencies = [
"frame-support-test",
"frame-system",
"hex-literal",
"impl-trait-for-tuples",
"libsecp256k1",
"log",
"pallet-authorship",
......@@ -10741,6 +10742,7 @@ dependencies = [
"polkadot-parachain",
"polkadot-primitives",
"polkadot-runtime-common",
"polkadot-runtime-parachains",
"rustc-hex",
"serde",
"serde_derive",
......
......@@ -462,6 +462,47 @@ fn westend_staging_testnet_config_genesis(wasm_binary: &[u8]) -> westend::Genesi
pallet_sudo: westend::SudoConfig {
key: endowed_accounts[0].clone(),
},
parachains_configuration: westend_runtime::ParachainsConfigurationConfig {
config: polkadot_runtime_parachains::configuration::HostConfiguration {
validation_upgrade_frequency: 600u32,
validation_upgrade_delay: 300,
acceptance_period: 1200,
max_code_size: 5 * 1024 * 1024,
max_pov_size: 50 * 1024 * 1024,
max_head_data_size: 32 * 1024,
group_rotation_frequency: 20,
chain_availability_period: 4,
thread_availability_period: 4,
max_upward_queue_count: 8,
max_upward_queue_size: 8 * 1024,
max_downward_message_size: 1024,
// this is approximatelly 4ms.
//
// Same as `4 * frame_support::weights::WEIGHT_PER_MILLIS`. We don't bother with
// an import since that's a made up number and should be replaced with a constant
// obtained by benchmarking anyway.
preferred_dispatchable_upward_messages_step_weight: 4 * 1_000_000_000,
max_upward_message_size: 1024,
max_upward_message_num_per_candidate: 5,
hrmp_open_request_ttl: 5,
hrmp_sender_deposit: 0,
hrmp_recipient_deposit: 0,
hrmp_channel_max_capacity: 8,
hrmp_channel_max_total_size: 8 * 1024,
hrmp_max_parachain_inbound_channels: 4,
hrmp_max_parathread_inbound_channels: 4,
hrmp_channel_max_message_size: 1024,
hrmp_max_parachain_outbound_channels: 4,
hrmp_max_parathread_outbound_channels: 4,
hrmp_max_message_num_per_candidate: 5,
no_show_slots: 2,
n_delay_tranches: 25,
needed_approvals: 2,
relay_vrf_modulo_samples: 10,
zeroth_delay_tranche_width: 0,
..Default::default()
},
},
}
}
......@@ -1336,6 +1377,47 @@ pub fn westend_testnet_genesis(
pallet_authority_discovery: westend::AuthorityDiscoveryConfig { keys: vec![] },
pallet_vesting: westend::VestingConfig { vesting: vec![] },
pallet_sudo: westend::SudoConfig { key: root_key },
parachains_configuration: westend_runtime::ParachainsConfigurationConfig {
config: polkadot_runtime_parachains::configuration::HostConfiguration {
validation_upgrade_frequency: 600u32,
validation_upgrade_delay: 300,
acceptance_period: 1200,
max_code_size: 5 * 1024 * 1024,
max_pov_size: 50 * 1024 * 1024,
max_head_data_size: 32 * 1024,
group_rotation_frequency: 20,
chain_availability_period: 4,
thread_availability_period: 4,
max_upward_queue_count: 8,
max_upward_queue_size: 8 * 1024,
max_downward_message_size: 1024,
// this is approximatelly 4ms.
//
// Same as `4 * frame_support::weights::WEIGHT_PER_MILLIS`. We don't bother with
// an import since that's a made up number and should be replaced with a constant
// obtained by benchmarking anyway.
preferred_dispatchable_upward_messages_step_weight: 4 * 1_000_000_000,
max_upward_message_size: 1024,
max_upward_message_num_per_candidate: 5,
hrmp_open_request_ttl: 5,
hrmp_sender_deposit: 0,
hrmp_recipient_deposit: 0,
hrmp_channel_max_capacity: 8,
hrmp_channel_max_total_size: 8 * 1024,
hrmp_max_parachain_inbound_channels: 4,
hrmp_max_parathread_inbound_channels: 4,
hrmp_channel_max_message_size: 1024,
hrmp_max_parachain_outbound_channels: 4,
hrmp_max_parathread_outbound_channels: 4,
hrmp_max_message_num_per_candidate: 5,
no_show_slots: 2,
n_delay_tranches: 25,
needed_approvals: 2,
relay_vrf_modulo_samples: 10,
zeroth_delay_tranche_width: 0,
..Default::default()
},
},
}
}
......
......@@ -5,6 +5,7 @@ authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
[dependencies]
impl-trait-for-tuples = "0.2.0"
bitvec = { version = "0.20.1", default-features = false, features = ["alloc"] }
parity-scale-codec = { version = "2.0.0", default-features = false, features = ["derive"] }
log = { version = "0.4.13", optional = true }
......@@ -33,7 +34,9 @@ pallet-vesting = { git = "https://github.com/paritytech/substrate", branch = "ma
pallet-offences = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-treasury = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true }
pallet-babe = { git = "https://github.com/paritytech/substrate", branch = "master", default-features=false, optional = true }
primitives = { package = "polkadot-primitives", path = "../../primitives", default-features = false }
libsecp256k1 = { version = "0.3.5", default-features = false, optional = true }
......@@ -45,10 +48,10 @@ xcm = { path = "../../xcm", default-features = false }
hex-literal = "0.3.1"
keyring = { package = "sp-keyring", git = "https://github.com/paritytech/substrate", branch = "master" }
sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-babe = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-application-crypto = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-election-providers = { git = "https://github.com/paritytech/substrate", branch = "master" }
frame-support-test = { git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-babe = { git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-randomness-collective-flip = { git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-staking-reward-curve = { git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-treasury = { git = "https://github.com/paritytech/substrate", branch = "master" }
......@@ -93,4 +96,6 @@ runtime-benchmarks = [
"frame-benchmarking",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"runtime-parachains/runtime-benchmarks",
"pallet-babe/runtime-benchmarks",
]
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! Auctioning system to determine the set of Parachains in operation. This includes logic for the
//! auctioning mechanism and for reserving balance as part of the "payment". Unreserving the balance
//! happens elsewhere.
use sp_std::{prelude::*, mem::swap, convert::TryInto};
use sp_runtime::traits::{CheckedSub, Zero, One, Saturating};
use frame_support::{
decl_module, decl_storage, decl_event, decl_error, ensure, dispatch::DispatchResult,
traits::{Randomness, Currency, ReservableCurrency, Get, EnsureOrigin},
weights::{DispatchClass, Weight},
};
use primitives::v1::Id as ParaId;
use frame_system::ensure_signed;
use crate::slot_range::{SlotRange, SLOT_RANGE_COUNT};
use crate::traits::{Leaser, LeaseError, Auctioneer};
use parity_scale_codec::Decode;
type CurrencyOf<T> = <<T as Config>::Leaser as Leaser>::Currency;
type BalanceOf<T> = <<<T as Config>::Leaser as Leaser>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
pub trait WeightInfo {
fn new_auction() -> Weight;
fn bid() -> Weight;
fn on_initialize() -> Weight;
}
pub struct TestWeightInfo;
impl WeightInfo for TestWeightInfo {
fn new_auction() -> Weight { 0 }
fn bid() -> Weight { 0 }
fn on_initialize() -> Weight { 0 }
}
/// The module's configuration trait.
pub trait Config: frame_system::Config {
/// The overarching event type.
type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;
/// The number of blocks over which a single period lasts.
type Leaser: Leaser<AccountId=Self::AccountId, LeasePeriod=Self::BlockNumber>;
/// The number of blocks over which an auction may be retroactively ended.
type EndingPeriod: Get<Self::BlockNumber>;
/// Something that provides randomness in the runtime.
type Randomness: Randomness<Self::Hash, Self::BlockNumber>;
/// The origin which may initiate auctions.
type InitiateOrigin: EnsureOrigin<Self::Origin>;
/// Weight Information for the Extrinsics in the Pallet
type WeightInfo: WeightInfo;
}
/// An auction index. We count auctions in this type.
pub type AuctionIndex = u32;
type LeasePeriodOf<T> = <<T as Config>::Leaser as Leaser>::LeasePeriod;
// Winning data type. This encodes the top bidders of each range together with their bid.
type WinningData<T> =
[Option<(<T as frame_system::Config>::AccountId, ParaId, BalanceOf<T>)>; SLOT_RANGE_COUNT];
// Winners data type. This encodes each of the final winners of a parachain auction, the parachain
// index assigned to them, their winning bid and the range that they won.
type WinnersData<T> = Vec<(<T as frame_system::Config>::AccountId, ParaId, BalanceOf<T>, SlotRange)>;
// This module's storage items.
decl_storage! {
trait Store for Module<T: Config> as Auctions {
/// Number of auctions started so far.
pub AuctionCounter: AuctionIndex;
/// Information relating to the current auction, if there is one.
///
/// The first item in the tuple is the lease period index that the first of the four
/// contiguous lease periods on auction is for. The second is the block number when the
/// auction will "begin to end", i.e. the first block of the Ending Period of the auction.
pub AuctionInfo get(fn auction_info): Option<(LeasePeriodOf<T>, T::BlockNumber)>;
/// Amounts currently reserved in the accounts of the bidders currently winning
/// (sub-)ranges.
pub ReservedAmounts get(fn reserved_amounts):
map hasher(twox_64_concat) (T::AccountId, ParaId) => Option<BalanceOf<T>>;
/// The winning bids for each of the 10 ranges at each block in the final Ending Period of
/// the current auction. The map's key is the 0-based index into the Ending Period. The
/// first block of the ending period is 0; the last is `EndingPeriod - 1`.
pub Winning get(fn winning): map hasher(twox_64_concat) T::BlockNumber => Option<WinningData<T>>;
}
}
decl_event!(
pub enum Event<T> where
AccountId = <T as frame_system::Config>::AccountId,
BlockNumber = <T as frame_system::Config>::BlockNumber,
LeasePeriod = LeasePeriodOf<T>,
ParaId = ParaId,
Balance = BalanceOf<T>,
{
/// An auction started. Provides its index and the block number where it will begin to
/// close and the first lease period of the quadruplet that is auctioned.
/// [auction_index, lease_period, ending]
AuctionStarted(AuctionIndex, LeasePeriod, BlockNumber),
/// An auction ended. All funds become unreserved. [auction_index]
AuctionClosed(AuctionIndex),
/// Someone won the right to deploy a parachain. Balance amount is deducted for deposit.
/// [bidder, range, parachain_id, amount]
WonDeploy(AccountId, SlotRange, ParaId, Balance),
/// An existing parachain won the right to continue.
/// First balance is the extra amount reseved. Second is the total amount reserved.
/// [parachain_id, begin, count, total_amount]
WonRenewal(ParaId, LeasePeriod, LeasePeriod, Balance),
/// Funds were reserved for a winning bid. First balance is the extra amount reserved.
/// Second is the total. [bidder, extra_reserved, total_amount]
Reserved(AccountId, Balance, Balance),
/// Funds were unreserved since bidder is no longer active. [bidder, amount]
Unreserved(AccountId, Balance),
/// Someone attempted to lease the same slot twice for a parachain. The amount is held in reserve
/// but no parachain slot has been leased.
/// \[parachain_id, leaser, amount\]
ReserveConfiscated(ParaId, AccountId, Balance),
/// A new bid has been accepted as the current winner.
/// \[who, para_id, amount, first_slot, last_slot\]
BidAccepted(AccountId, ParaId, Balance, LeasePeriod, LeasePeriod),
}
);
decl_error! {
pub enum Error for Module<T: Config> {
/// This auction is already in progress.
AuctionInProgress,
/// The lease period is in the past.
LeasePeriodInPast,
/// The origin for this call must be a parachain.
NotParaOrigin,
/// The parachain ID is not on-boarding.
ParaNotOnboarding,
/// The origin for this call must be the origin who registered the parachain.
InvalidOrigin,
/// Parachain is already registered.
AlreadyRegistered,
/// The code must correspond to the hash.
InvalidCode,
/// Deployment data has not been set for this parachain.
UnsetDeployData,
/// The bid must overlap all intersecting ranges.
NonIntersectingRange,
/// Not a current auction.
NotCurrentAuction,
/// Not an auction.
NotAuction,
/// Given code size is too large.
CodeTooLarge,
/// Given initial head data is too large.
HeadDataTooLarge,
/// Auction has already ended.
AuctionEnded,
}
}
decl_module! {
pub struct Module<T: Config> for enum Call where origin: T::Origin {
type Error = Error<T>;
const EndingPeriod: T::BlockNumber = T::EndingPeriod::get();
fn deposit_event() = default;
fn on_initialize(n: T::BlockNumber) -> Weight {
let mut weight = T::DbWeight::get().reads(1);
// If the current auction was in its ending period last block, then ensure that the (sub-)range
// winner information is duplicated from the previous block in case no bids happened in the
// last block.
if let Some(offset) = Self::is_ending(n) {
weight = weight.saturating_add(T::DbWeight::get().reads(1));
if !Winning::<T>::contains_key(&offset) {
weight = weight.saturating_add(T::DbWeight::get().writes(1));
let winning_data = offset.checked_sub(&One::one())
.and_then(Winning::<T>::get)
.unwrap_or_default();
Winning::<T>::insert(offset, winning_data);
}
}
// Check to see if an auction just ended.
if let Some((winning_ranges, auction_lease_period_index)) = Self::check_auction_end(n) {
// Auction is ended now. We have the winning ranges and the lease period index which
// acts as the offset. Handle it.
Self::manage_auction_end(
auction_lease_period_index,
winning_ranges,
);
weight = weight.saturating_add(T::WeightInfo::on_initialize());
}
weight
}
/// Create a new auction.
///
/// This can only happen when there isn't already an auction in progress and may only be
/// called by the root origin. Accepts the `duration` of this auction and the
/// `lease_period_index` of the initial lease period of the four that are to be auctioned.
#[weight = (T::WeightInfo::new_auction(), DispatchClass::Operational)]
pub fn new_auction(origin,
#[compact] duration: T::BlockNumber,
#[compact] lease_period_index: LeasePeriodOf<T>,
) {
T::InitiateOrigin::ensure_origin(origin)?;
ensure!(!Self::is_in_progress(), Error::<T>::AuctionInProgress);
ensure!(lease_period_index >= T::Leaser::lease_period_index(), Error::<T>::LeasePeriodInPast);
// Bump the counter.
let n = AuctionCounter::mutate(|n| { *n += 1; *n });
// Set the information.
let ending = frame_system::Module::<T>::block_number().saturating_add(duration);
AuctionInfo::<T>::put((lease_period_index, ending));
Self::deposit_event(RawEvent::AuctionStarted(n, lease_period_index, ending))
}
/// Make a new bid from an account (including a parachain account) for deploying a new
/// parachain.
///
/// Multiple simultaneous bids from the same bidder are allowed only as long as all active
/// bids overlap each other (i.e. are mutually exclusive). Bids cannot be redacted.
///
/// - `sub` is the sub-bidder ID, allowing for multiple competing bids to be made by (and
/// funded by) the same account.
/// - `auction_index` is the index of the auction to bid on. Should just be the present
/// value of `AuctionCounter`.
/// - `first_slot` is the first lease period index of the range to bid on. This is the
/// absolute lease period index value, not an auction-specific offset.
/// - `last_slot` is the last lease period index of the range to bid on. This is the
/// absolute lease period index value, not an auction-specific offset.
/// - `amount` is the amount to bid to be held as deposit for the parachain should the
/// bid win. This amount is held throughout the range.
#[weight = T::WeightInfo::bid()]
pub fn bid(origin,
#[compact] para: ParaId,
#[compact] auction_index: AuctionIndex,
#[compact] first_slot: LeasePeriodOf<T>,
#[compact] last_slot: LeasePeriodOf<T>,
#[compact] amount: BalanceOf<T>
) {
let who = ensure_signed(origin)?;
Self::handle_bid(who, para, auction_index, first_slot, last_slot, amount)?;
}
}
}
impl<T: Config> Auctioneer for Module<T> {
type AccountId = T::AccountId;
type BlockNumber = T::BlockNumber;
type LeasePeriod = T::BlockNumber;
type Currency = CurrencyOf<T>;
fn new_auction(
duration: T::BlockNumber,
lease_period_index: LeasePeriodOf<T>,
) -> DispatchResult {
Self::do_new_auction(duration, lease_period_index)
}
fn is_ending(now: Self::BlockNumber) -> Option<Self::BlockNumber> {
if let Some((_, early_end)) = AuctionInfo::<T>::get() {
if let Some(after_early_end) = now.checked_sub(&early_end) {
if after_early_end < T::EndingPeriod::get() {
return Some(after_early_end)
}
}
}
None
}
fn place_bid(
bidder: T::AccountId,
para: ParaId,
first_slot: LeasePeriodOf<T>,
last_slot: LeasePeriodOf<T>,
amount: BalanceOf<T>,
) -> DispatchResult {
Self::handle_bid(bidder, para, AuctionCounter::get(), first_slot, last_slot, amount)
}
fn lease_period_index() -> Self::LeasePeriod {
T::Leaser::lease_period_index()
}
}
impl<T: Config> Module<T> {
/// True if an auction is in progress.
pub fn is_in_progress() -> bool {
AuctionInfo::<T>::get().map_or(false, |(_, early_end)| {
let late_end = early_end.saturating_add(T::EndingPeriod::get());
// We need to check that the auction isn't in the period where it has definitely ended, but yeah we keep the
// info around because we haven't yet decided *exactly* when in the `EndingPeriod` that it ended.
let now = frame_system::Module::<T>::block_number();
now < late_end
})
}
/// Create a new auction.
///
/// This can only happen when there isn't already an auction in progress. Accepts the `duration`
/// of this auction and the `lease_period_index` of the initial lease period of the four that
/// are to be auctioned.
fn do_new_auction(
duration: T::BlockNumber,
lease_period_index: LeasePeriodOf<T>,
) -> DispatchResult {
ensure!(!Self::is_in_progress(), Error::<T>::AuctionInProgress);
ensure!(lease_period_index >= T::Leaser::lease_period_index(), Error::<T>::LeasePeriodInPast);
// Bump the counter.
let n = AuctionCounter::mutate(|n| { *n += 1; *n });
// Set the information.
let ending = frame_system::Module::<T>::block_number().saturating_add(duration);
AuctionInfo::<T>::put((lease_period_index, ending));
Self::deposit_event(RawEvent::AuctionStarted(n, lease_period_index, ending));
Ok(())
}
/// Actually place a bid in the current auction.
///
/// - `bidder`: The account that will be funding this bid.
/// - `auction_index`: The auction index of the bid. For this to succeed, must equal
/// the current value of `AuctionCounter`.
/// - `first_slot`: The first lease period index of the range to be bid on.
/// - `last_slot`: The last lease period index of the range to be bid on (inclusive).
/// - `amount`: The total amount to be the bid for deposit over the range.
pub fn handle_bid(
bidder: T::AccountId,
para: ParaId,
auction_index: u32,
first_slot: LeasePeriodOf<T>,
last_slot: LeasePeriodOf<T>,
amount: BalanceOf<T>,
) -> DispatchResult {
// Bidding on latest auction.
ensure!(auction_index == AuctionCounter::get(), Error::<T>::NotCurrentAuction);
// Assume it's actually an auction (this should never fail because of above).
let (first_lease_period, early_end) = AuctionInfo::<T>::get().ok_or(Error::<T>::NotAuction)?;
let late_end = early_end.saturating_add(T::EndingPeriod::get());
// We need to check that the auction isn't in the period where it has definitely ended, but yeah we keep the
// info around because we haven't yet decided *exactly* when in the `EndingPeriod` that it ended.
let now = frame_system::Module::<T>::block_number();
ensure!(now < late_end, Error::<T>::AuctionEnded);
// Our range.
let range = SlotRange::new_bounded(first_lease_period, first_slot, last_slot)?;
// Range as an array index.
let range_index = range as u8 as usize;
// The offset into the auction ending set.
let offset = Self::is_ending(frame_system::Module::<T>::block_number()).unwrap_or_default();
// The current winning ranges.
let mut current_winning = Winning::<T>::get(offset)
.or_else(|| offset.checked_sub(&One::one()).and_then(Winning::<T>::get))
.unwrap_or_default();
// If this bid beat the previous winner of our range.
if current_winning[range_index].as_ref().map_or(true, |last| amount > last.2) {
// This must overlap with all existing ranges that we're winning on or it's invalid.
ensure!(current_winning.iter()
.enumerate()
.all(|(i, x)| x.as_ref().map_or(true, |(w, _, _)|
w != &bidder || range.intersects(i.try_into()
.expect("array has SLOT_RANGE_COUNT items; index never reaches that value; qed")
)
)),
Error::<T>::NonIntersectingRange,
);
// Ok; we are the new winner of this range - reserve the additional amount and record.
// Get the amount already held on deposit if this is a renewal bid (i.e. there's
// an existing lease on the same para by the same leaser).
let existing_lease_deposit = T::Leaser::deposit_held(para, &bidder);
let reserve_required = amount.saturating_sub(existing_lease_deposit);
// Get the amount already reserved in any prior and still active bids by us.
let bidder_para = (bidder.clone(), para);
let already_reserved = ReservedAmounts::<T>::get(&bidder_para).unwrap_or_default();
// If these don't already cover the bid...
if let Some(additional) = reserve_required.checked_sub(&already_reserved) {
// ...then reserve some more funds from their account, failing if there's not
// enough funds.
CurrencyOf::<T>::reserve(&bidder, additional)?;
// ...and record the amount reserved.
ReservedAmounts::<T>::insert(&bidder_para, reserve_required);
Self::deposit_event(RawEvent::Reserved(
bidder.clone(),
additional,
reserve_required,
));
}
// Return any funds reserved for the previous winner if they no longer have any active
// bids.
let mut outgoing_winner = Some((bidder.clone(), para, amount));
swap(&mut current_winning[range_index], &mut outgoing_winner);
if let Some((who, para, _amount)) = outgoing_winner {
if current_winning.iter()
.filter_map(Option::as_ref)
.all(|&(ref other, other_para, _)| other != &who || other_para != para)
{
// Previous bidder is no longer winning any ranges: unreserve their funds.
if let Some(amount) = ReservedAmounts::<T>::take(&(who.clone(), para)) {
// It really should be reserved; there's not much we can do here on fail.
let err_amt = CurrencyOf::<T>::unreserve(&who, amount);
debug_assert!(err_amt.is_zero());
Self::deposit_event(RawEvent::Unreserved(who, amount));
}
}
}
// Update the range winner.
Winning::<T>::insert(offset, &current_winning);
Self::deposit_event(RawEvent