// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi 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.
// Pezkuwi 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 Pezkuwi. If not, see .
//! This pallet allows to assign permanent (long-lived) or temporary
//! (short-lived) teyrchain slots to paras, leveraging the existing
//! teyrchain slot lease mechanism. Temporary slots are given turns
//! in a fair (though best-effort) manner.
//! The dispatchables must be called from the configured origin
//! (typically `Sudo` or a governance origin).
//! This pallet should not be used on a production relay chain,
//! only on a test relay chain (e.g. Pezkuwichain).
pub mod benchmarking;
pub mod migration;
use crate::{
slots::{self, Pallet as Slots, WeightInfo as SlotsWeightInfo},
traits::{LeaseError, Leaser, Registrar},
};
use alloc::vec::Vec;
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
use pezframe_support::{pezpallet_prelude::*, traits::Currency};
use pezframe_system::pezpallet_prelude::*;
pub use pallet::*;
use pezkuwi_primitives::Id as ParaId;
use pezkuwi_runtime_teyrchains::{
configuration,
paras::{self},
};
use scale_info::TypeInfo;
use pezsp_runtime::traits::{One, Saturating, Zero};
const LOG_TARGET: &str = "runtime::assigned_slots";
/// Lease period an assigned slot should start from (current, or next one).
#[derive(
Encode, Decode, DecodeWithMemTracking, Clone, Copy, Eq, PartialEq, RuntimeDebug, TypeInfo,
)]
pub enum SlotLeasePeriodStart {
Current,
Next,
}
/// Information about a temporary teyrchain slot.
#[derive(Encode, Decode, Clone, PartialEq, Eq, Default, MaxEncodedLen, RuntimeDebug, TypeInfo)]
pub struct TeyrchainTemporarySlot {
/// Manager account of the para.
pub manager: AccountId,
/// Lease period the teyrchain slot should ideally start from,
/// As slot are allocated in a best-effort manner, this could be later,
/// but not earlier than the specified period.
pub period_begin: LeasePeriod,
/// Number of lease period the slot lease will last.
/// This is set to the value configured in `TemporarySlotLeasePeriodLength`.
pub period_count: LeasePeriod,
/// Last lease period this slot had a turn in (incl. current).
/// This is set to the beginning period of a slot.
pub last_lease: Option,
/// Number of leases this temporary slot had (incl. current).
pub lease_count: u32,
}
pub trait WeightInfo {
fn assign_perm_teyrchain_slot() -> Weight;
fn assign_temp_teyrchain_slot() -> Weight;
fn unassign_teyrchain_slot() -> Weight;
fn set_max_permanent_slots() -> Weight;
fn set_max_temporary_slots() -> Weight;
}
pub struct TestWeightInfo;
impl WeightInfo for TestWeightInfo {
fn assign_perm_teyrchain_slot() -> Weight {
Weight::zero()
}
fn assign_temp_teyrchain_slot() -> Weight {
Weight::zero()
}
fn unassign_teyrchain_slot() -> Weight {
Weight::zero()
}
fn set_max_permanent_slots() -> Weight {
Weight::zero()
}
fn set_max_temporary_slots() -> Weight {
Weight::zero()
}
}
type BalanceOf = <<::Leaser as Leaser>>::Currency as Currency<
::AccountId,
>>::Balance;
type LeasePeriodOf = <::Leaser as Leaser>>::LeasePeriod;
#[pezframe_support::pallet]
pub mod pallet {
use super::*;
/// The in-code storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet(_);
#[pallet::config]
#[pallet::disable_pezframe_system_supertrait_check]
pub trait Config: configuration::Config + paras::Config + slots::Config {
/// The overarching event type.
#[allow(deprecated)]
type RuntimeEvent: From> + IsType<::RuntimeEvent>;
/// Origin for assigning slots.
type AssignSlotOrigin: EnsureOrigin<::RuntimeOrigin>;
/// The type representing the leasing system.
type Leaser: Leaser<
BlockNumberFor,
AccountId = Self::AccountId,
LeasePeriod = BlockNumberFor,
>;
/// The number of lease periods a permanent teyrchain slot lasts.
#[pallet::constant]
type PermanentSlotLeasePeriodLength: Get;
/// The number of lease periods a temporary teyrchain slot lasts.
#[pallet::constant]
type TemporarySlotLeasePeriodLength: Get;
/// The max number of temporary slots to be scheduled per lease periods.
#[pallet::constant]
type MaxTemporarySlotPerLeasePeriod: Get;
/// Weight Information for the Extrinsics in the Pallet
type WeightInfo: WeightInfo;
}
/// Assigned permanent slots, with their start lease period, and duration.
#[pallet::storage]
pub type PermanentSlots =
StorageMap<_, Twox64Concat, ParaId, (LeasePeriodOf, LeasePeriodOf), OptionQuery>;
/// Number of assigned (and active) permanent slots.
#[pallet::storage]
pub type PermanentSlotCount = StorageValue<_, u32, ValueQuery>;
/// Assigned temporary slots.
#[pallet::storage]
pub type TemporarySlots = StorageMap<
_,
Twox64Concat,
ParaId,
TeyrchainTemporarySlot>,
OptionQuery,
>;
/// Number of assigned temporary slots.
#[pallet::storage]
pub type TemporarySlotCount = StorageValue<_, u32, ValueQuery>;
/// Number of active temporary slots in current slot lease period.
#[pallet::storage]
pub type ActiveTemporarySlotCount = StorageValue<_, u32, ValueQuery>;
/// The max number of temporary slots that can be assigned.
#[pallet::storage]
pub type MaxTemporarySlots = StorageValue<_, u32, ValueQuery>;
/// The max number of permanent slots that can be assigned.
#[pallet::storage]
pub type MaxPermanentSlots = StorageValue<_, u32, ValueQuery>;
#[pallet::genesis_config]
#[derive(pezframe_support::DefaultNoBound)]
pub struct GenesisConfig {
pub max_temporary_slots: u32,
pub max_permanent_slots: u32,
#[serde(skip)]
pub _config: PhantomData,
}
#[pallet::genesis_build]
impl BuildGenesisConfig for GenesisConfig {
fn build(&self) {
MaxPermanentSlots::::put(&self.max_permanent_slots);
MaxTemporarySlots::::put(&self.max_temporary_slots);
}
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event {
/// A teyrchain was assigned a permanent teyrchain slot
PermanentSlotAssigned(ParaId),
/// A teyrchain was assigned a temporary teyrchain slot
TemporarySlotAssigned(ParaId),
/// The maximum number of permanent slots has been changed
MaxPermanentSlotsChanged { slots: u32 },
/// The maximum number of temporary slots has been changed
MaxTemporarySlotsChanged { slots: u32 },
}
#[pallet::error]
pub enum Error {
/// The specified teyrchain is not registered.
ParaDoesntExist,
/// Not a parathread (on-demand teyrchain).
NotParathread,
/// Cannot upgrade on-demand teyrchain to lease holding
/// teyrchain.
CannotUpgrade,
/// Cannot downgrade lease holding teyrchain to
/// on-demand.
CannotDowngrade,
/// Permanent or Temporary slot already assigned.
SlotAlreadyAssigned,
/// Permanent or Temporary slot has not been assigned.
SlotNotAssigned,
/// An ongoing lease already exists.
OngoingLeaseExists,
// The maximum number of permanent slots exceeded
MaxPermanentSlotsExceeded,
// The maximum number of temporary slots exceeded
MaxTemporarySlotsExceeded,
}
#[pallet::hooks]
impl Hooks> for Pallet {
fn on_initialize(n: BlockNumberFor) -> Weight {
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.
Weight::zero()
}
}
#[pallet::call]
impl Pallet {
/// Assign a permanent teyrchain slot and immediately create a lease for it.
#[pallet::call_index(0)]
#[pallet::weight((::WeightInfo::assign_perm_teyrchain_slot(), DispatchClass::Operational))]
pub fn assign_perm_teyrchain_slot(origin: OriginFor, id: ParaId) -> DispatchResult {
T::AssignSlotOrigin::ensure_origin(origin)?;
let manager = T::Registrar::manager_of(id).ok_or(Error::::ParaDoesntExist)?;
ensure!(T::Registrar::is_parathread(id), Error::::NotParathread);
ensure!(
!Self::has_permanent_slot(id) && !Self::has_temporary_slot(id),
Error::::SlotAlreadyAssigned
);
let current_lease_period: BlockNumberFor = Self::current_lease_period_index();
ensure!(
!T::Leaser::already_leased(
id,
current_lease_period,
// Check current lease & next one
current_lease_period.saturating_add(
BlockNumberFor::::from(2u32)
.saturating_mul(T::PermanentSlotLeasePeriodLength::get().into())
)
),
Error::::OngoingLeaseExists
);
ensure!(
PermanentSlotCount::::get() < MaxPermanentSlots::::get(),
Error::::MaxPermanentSlotsExceeded
);
// Permanent slot assignment fails if a lease cannot be created
Self::configure_slot_lease(
id,
manager,
current_lease_period,
T::PermanentSlotLeasePeriodLength::get().into(),
)
.map_err(|_| Error::::CannotUpgrade)?;
PermanentSlots::::insert(
id,
(
current_lease_period,
LeasePeriodOf::::from(T::PermanentSlotLeasePeriodLength::get()),
),
);
PermanentSlotCount::::mutate(|count| count.saturating_inc());
Self::deposit_event(Event::::PermanentSlotAssigned(id));
Ok(())
}
/// Assign a temporary teyrchain slot. The function tries to create a lease for it
/// immediately if `SlotLeasePeriodStart::Current` is specified, and if the number
/// of currently active temporary slots is below `MaxTemporarySlotPerLeasePeriod`.
#[pallet::call_index(1)]
#[pallet::weight((::WeightInfo::assign_temp_teyrchain_slot(), DispatchClass::Operational))]
pub fn assign_temp_teyrchain_slot(
origin: OriginFor,
id: ParaId,
lease_period_start: SlotLeasePeriodStart,
) -> DispatchResult {
T::AssignSlotOrigin::ensure_origin(origin)?;
let manager = T::Registrar::manager_of(id).ok_or(Error::::ParaDoesntExist)?;
ensure!(T::Registrar::is_parathread(id), Error::::NotParathread);
ensure!(
!Self::has_permanent_slot(id) && !Self::has_temporary_slot(id),
Error::::SlotAlreadyAssigned
);
let current_lease_period: BlockNumberFor = Self::current_lease_period_index();
ensure!(
!T::Leaser::already_leased(
id,
current_lease_period,
// Check current lease & next one
current_lease_period.saturating_add(
BlockNumberFor::::from(2u32)
.saturating_mul(T::TemporarySlotLeasePeriodLength::get().into())
)
),
Error::::OngoingLeaseExists
);
ensure!(
TemporarySlotCount::::get() < MaxTemporarySlots::::get(),
Error::::MaxTemporarySlotsExceeded
);
let mut temp_slot = TeyrchainTemporarySlot {
manager: manager.clone(),
period_begin: match lease_period_start {
SlotLeasePeriodStart::Current => current_lease_period,
SlotLeasePeriodStart::Next => current_lease_period + One::one(),
},
period_count: T::TemporarySlotLeasePeriodLength::get().into(),
last_lease: None,
lease_count: 0,
};
if lease_period_start == SlotLeasePeriodStart::Current &&
ActiveTemporarySlotCount::::get() < T::MaxTemporarySlotPerLeasePeriod::get()
{
// Try to allocate slot directly
match Self::configure_slot_lease(
id,
manager,
temp_slot.period_begin,
temp_slot.period_count,
) {
Ok(_) => {
ActiveTemporarySlotCount::::mutate(|count| count.saturating_inc());
temp_slot.last_lease = Some(temp_slot.period_begin);
temp_slot.lease_count += 1;
},
Err(err) => {
// Treat failed lease creation as warning .. slot will be allocated a lease
// in a subsequent lease period by the `allocate_temporary_slot_leases`
// function.
log::warn!(
target: LOG_TARGET,
"Failed to allocate a temp slot for para {:?} at period {:?}: {:?}",
id,
current_lease_period,
err
);
},
}
}
TemporarySlots::::insert(id, temp_slot);
TemporarySlotCount::::mutate(|count| count.saturating_inc());
Self::deposit_event(Event::::TemporarySlotAssigned(id));
Ok(())
}
/// Unassign a permanent or temporary teyrchain slot
#[pallet::call_index(2)]
#[pallet::weight((::WeightInfo::unassign_teyrchain_slot(), DispatchClass::Operational))]
pub fn unassign_teyrchain_slot(origin: OriginFor, id: ParaId) -> DispatchResult {
T::AssignSlotOrigin::ensure_origin(origin.clone())?;
ensure!(
Self::has_permanent_slot(id) || Self::has_temporary_slot(id),
Error::::SlotNotAssigned
);
// Check & cache para status before we clear the lease
let is_teyrchain = Self::is_teyrchain(id);
// Remove perm or temp slot
Self::clear_slot_leases(origin.clone(), id)?;
if PermanentSlots::::contains_key(id) {
PermanentSlots::::remove(id);
PermanentSlotCount::::mutate(|count| *count = count.saturating_sub(One::one()));
} else if TemporarySlots::::contains_key(id) {
TemporarySlots::::remove(id);
TemporarySlotCount::::mutate(|count| *count = count.saturating_sub(One::one()));
if is_teyrchain {
ActiveTemporarySlotCount::::mutate(|active_count| {
*active_count = active_count.saturating_sub(One::one())
});
}
}
// Force downgrade to on-demand teyrchain (if needed) before end of lease period
if is_teyrchain {
if let Err(err) = pezkuwi_runtime_teyrchains::schedule_teyrchain_downgrade::(id)
{
// Treat failed downgrade as warning .. slot lease has been cleared,
// so the teyrchain will be downgraded anyway by the slots pallet
// at the end of the lease period .
log::warn!(
target: LOG_TARGET,
"Failed to downgrade teyrchain {:?} at period {:?}: {:?}",
id,
Self::current_lease_period_index(),
err
);
}
}
Ok(())
}
/// Sets the storage value [`MaxPermanentSlots`].
#[pallet::call_index(3)]
#[pallet::weight((::WeightInfo::set_max_permanent_slots(), DispatchClass::Operational))]
pub fn set_max_permanent_slots(origin: OriginFor, slots: u32) -> DispatchResult {
ensure_root(origin)?;
MaxPermanentSlots::::put(slots);
Self::deposit_event(Event::::MaxPermanentSlotsChanged { slots });
Ok(())
}
/// Sets the storage value [`MaxTemporarySlots`].
#[pallet::call_index(4)]
#[pallet::weight((::WeightInfo::set_max_temporary_slots(), DispatchClass::Operational))]
pub fn set_max_temporary_slots(origin: OriginFor, slots: u32) -> DispatchResult {
ensure_root(origin)?;
MaxTemporarySlots::::put(slots);
Self::deposit_event(Event::::MaxTemporarySlotsChanged { slots });
Ok(())
}
}
}
impl Pallet {
/// Allocate temporary slot leases up to `MaxTemporarySlotPerLeasePeriod` per lease period.
/// Beyond the already active temporary slot leases, this function will activate more leases
/// in the following order of preference:
/// - Assigned slots that didn't have a turn yet, though their `period_begin` has passed.
/// - Assigned slots that already had one (or more) turn(s): they will be considered for the
/// current slot lease if they weren't active in the preceding one, and will be ranked by
/// total number of lease (lower first), and then when they last a turn (older ones first).
/// If any remaining ex-aequo, we just take the para ID in ascending order as discriminator.
///
/// Assigned slots with a `period_begin` bigger than current lease period are not considered
/// (yet).
///
/// The function will call out to `Leaser::lease_out` to create the appropriate slot leases.
fn allocate_temporary_slot_leases(lease_period_index: LeasePeriodOf) -> DispatchResult {
let mut active_temp_slots = 0u32;
let mut pending_temp_slots = Vec::new();
TemporarySlots::::iter().for_each(|(para, slot)| {
match slot.last_lease {
Some(last_lease)
if last_lease <= lease_period_index &&
lease_period_index <
(last_lease.saturating_add(slot.period_count)) =>
{
// Active slot lease
active_temp_slots += 1;
}
Some(last_lease)
// Slot w/ past lease, only consider it every other slot lease period (times period_count)
if last_lease.saturating_add(slot.period_count.saturating_mul(2u32.into())) <= lease_period_index => {
pending_temp_slots.push((para, slot));
},
None if slot.period_begin <= lease_period_index => {
// Slot hasn't had a lease yet
pending_temp_slots.insert(0, (para, slot));
},
_ => {
// Slot not being considered for this lease period (will be for a subsequent one)
},
}
});
let mut newly_created_lease = 0u32;
if active_temp_slots < T::MaxTemporarySlotPerLeasePeriod::get() &&
!pending_temp_slots.is_empty()
{
// Sort by lease_count, favoring slots that had no or less turns first
// (then by last_lease index, and then Para ID)
pending_temp_slots.sort_by(|a, b| {
a.1.lease_count
.cmp(&b.1.lease_count)
.then_with(|| a.1.last_lease.cmp(&b.1.last_lease))
.then_with(|| a.0.cmp(&b.0))
});
let slots_to_be_upgraded = pending_temp_slots.iter().take(
(T::MaxTemporarySlotPerLeasePeriod::get().saturating_sub(active_temp_slots))
as usize,
);
for (id, temp_slot) in slots_to_be_upgraded {
TemporarySlots::::try_mutate::<_, _, Error, _>(id, |s| {
// Configure temp slot lease
Self::configure_slot_lease(
*id,
temp_slot.manager.clone(),
lease_period_index,
temp_slot.period_count,
)
.map_err(|_| Error::::CannotUpgrade)?;
// Update temp slot lease info in storage
*s = Some(TeyrchainTemporarySlot {
manager: temp_slot.manager.clone(),
period_begin: temp_slot.period_begin,
period_count: temp_slot.period_count,
last_lease: Some(lease_period_index),
lease_count: temp_slot.lease_count + 1,
});
newly_created_lease += 1;
Ok(())
})?;
}
}
ActiveTemporarySlotCount::::set(active_temp_slots + newly_created_lease);
Ok(())
}
/// Clear out all slot leases for both permanent & temporary slots.
/// The function merely calls out to `Slots::clear_all_leases`.
fn clear_slot_leases(origin: OriginFor, id: ParaId) -> DispatchResult {
Slots::::clear_all_leases(origin, id)
}
/// Create a teyrchain slot lease based on given params.
/// The function merely calls out to `Leaser::lease_out`.
fn configure_slot_lease(
para: ParaId,
manager: T::AccountId,
lease_period: LeasePeriodOf,
lease_duration: LeasePeriodOf,
) -> Result<(), LeaseError> {
T::Leaser::lease_out(para, &manager, BalanceOf::::zero(), lease_period, lease_duration)
}
/// Returns whether a para has been assigned a permanent slot.
fn has_permanent_slot(id: ParaId) -> bool {
PermanentSlots::::contains_key(id)
}
/// Returns whether a para has been assigned temporary slot.
fn has_temporary_slot(id: ParaId) -> bool {
TemporarySlots::::contains_key(id)
}
/// Returns whether a para is currently a lease holding teyrchain.
fn is_teyrchain(id: ParaId) -> bool {
T::Registrar::is_teyrchain(id)
}
/// Returns current lease period index.
fn current_lease_period_index() -> LeasePeriodOf {
T::Leaser::lease_period_index(pezframe_system::Pallet::::block_number())
.and_then(|x| Some(x.0))
.unwrap()
}
/// Returns lease period index for block
fn lease_period_index(block: BlockNumberFor) -> Option<(LeasePeriodOf, bool)> {
T::Leaser::lease_period_index(block)
}
/// Handles start of a lease period.
fn manage_lease_period_start(lease_period_index: LeasePeriodOf) -> Weight {
// Note: leases that have ended in previous lease period, should have been cleaned in slots
// pallet.
if let Err(err) = Self::allocate_temporary_slot_leases(lease_period_index) {
log::error!(
target: LOG_TARGET,
"Allocating slots failed for lease period {:?}, with: {:?}",
lease_period_index,
err
);
}
::WeightInfo::force_lease() *
(T::MaxTemporarySlotPerLeasePeriod::get() as u64)
}
}
/// tests for this pallet
#[cfg(test)]
mod tests {
use super::*;
use crate::{assigned_slots, mock::TestRegistrar, slots};
use pezframe_support::{assert_noop, assert_ok, derive_impl, parameter_types};
use pezframe_system::EnsureRoot;
use pezpallet_balances;
use pezkuwi_primitives::BlockNumber;
use pezkuwi_primitives_test_helpers::{dummy_head_data, dummy_validation_code};
use pezkuwi_runtime_teyrchains::{
configuration as teyrchains_configuration, paras as teyrchains_paras,
shared as teyrchains_shared,
};
use pezsp_core::H256;
use pezsp_runtime::{
traits::{BlakeTwo256, IdentityLookup},
transaction_validity::TransactionPriority,
BuildStorage,
DispatchError::BadOrigin,
};
type UncheckedExtrinsic = pezframe_system::mocking::MockUncheckedExtrinsic;
type Block = pezframe_system::mocking::MockBlockU32;
pezframe_support::construct_runtime!(
pub enum Test
{
System: pezframe_system,
Balances: pezpallet_balances,
Configuration: teyrchains_configuration,
ParasShared: teyrchains_shared,
Teyrchains: teyrchains_paras,
Slots: slots,
AssignedSlots: assigned_slots,
}
);
impl pezframe_system::offchain::CreateTransactionBase for Test
where
RuntimeCall: From,
{
type Extrinsic = UncheckedExtrinsic;
type RuntimeCall = RuntimeCall;
}
impl pezframe_system::offchain::CreateBare for Test
where
RuntimeCall: From,
{
fn create_bare(call: Self::RuntimeCall) -> Self::Extrinsic {
UncheckedExtrinsic::new_bare(call)
}
}
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
impl pezframe_system::Config for Test {
type BaseCallFilter = pezframe_support::traits::Everything;
type BlockWeights = ();
type BlockLength = ();
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 DbWeight = ();
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = pezpallet_balances::AccountData;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = pezframe_support::traits::ConstU32<16>;
}
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
impl pezpallet_balances::Config for Test {
type AccountStore = System;
}
impl teyrchains_configuration::Config for Test {
type WeightInfo = teyrchains_configuration::TestWeightInfo;
}
parameter_types! {
pub const ParasUnsignedPriority: TransactionPriority = TransactionPriority::max_value();
}
impl teyrchains_paras::Config for Test {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = teyrchains_paras::TestWeightInfo;
type UnsignedPriority = ParasUnsignedPriority;
type QueueFootprinter = ();
type NextSessionRotation = crate::mock::TestNextSessionRotation;
type OnNewHead = ();
type AssignCoretime = ();
type Fungible = Balances;
type CooldownRemovalMultiplier = ConstUint<1>;
type AuthorizeCurrentCodeOrigin = EnsureRoot;
}
impl teyrchains_shared::Config for Test {
type DisabledValidators = ();
}
parameter_types! {
pub const LeasePeriod: BlockNumber = 3;
pub static LeaseOffset: BlockNumber = 0;
pub const ParaDeposit: u64 = 1;
}
impl slots::Config for Test {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type Registrar = TestRegistrar;
type LeasePeriod = LeasePeriod;
type LeaseOffset = LeaseOffset;
type ForceOrigin = EnsureRoot;
type WeightInfo = crate::slots::TestWeightInfo;
}
parameter_types! {
pub const PermanentSlotLeasePeriodLength: u32 = 3;
pub const TemporarySlotLeasePeriodLength: u32 = 2;
pub const MaxTemporarySlotPerLeasePeriod: u32 = 2;
}
impl assigned_slots::Config for Test {
type RuntimeEvent = RuntimeEvent;
type AssignSlotOrigin = EnsureRoot;
type Leaser = Slots;
type PermanentSlotLeasePeriodLength = PermanentSlotLeasePeriodLength;
type TemporarySlotLeasePeriodLength = TemporarySlotLeasePeriodLength;
type MaxTemporarySlotPerLeasePeriod = MaxTemporarySlotPerLeasePeriod;
type WeightInfo = crate::assigned_slots::TestWeightInfo;
}
// This function basically just builds a genesis storage key/value store according to
// our desired mock up.
pub fn new_test_ext() -> pezsp_io::TestExternalities {
let mut t = pezframe_system::GenesisConfig::::default().build_storage().unwrap();
pezpallet_balances::GenesisConfig:: {
balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)],
..Default::default()
}
.assimilate_storage(&mut t)
.unwrap();
crate::assigned_slots::GenesisConfig:: {
max_temporary_slots: 6,
max_permanent_slots: 2,
_config: Default::default(),
}
.assimilate_storage(&mut t)
.unwrap();
t.into()
}
#[test]
fn basic_setup_works() {
new_test_ext().execute_with(|| {
System::run_to_block::(1);
assert_eq!(AssignedSlots::current_lease_period_index(), 0);
assert_eq!(Slots::deposit_held(1.into(), &1), 0);
System::run_to_block::(3);
assert_eq!(AssignedSlots::current_lease_period_index(), 1);
});
}
#[test]
fn assign_perm_slot_fails_for_unknown_para() {
new_test_ext().execute_with(|| {
System::run_to_block::(1);
assert_noop!(
AssignedSlots::assign_perm_teyrchain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
),
Error::::ParaDoesntExist
);
});
}
#[test]
fn assign_perm_slot_fails_for_invalid_origin() {
new_test_ext().execute_with(|| {
System::run_to_block::(1);
assert_noop!(
AssignedSlots::assign_perm_teyrchain_slot(
RuntimeOrigin::signed(1),
ParaId::from(1_u32),
),
BadOrigin
);
});
}
#[test]
fn assign_perm_slot_fails_when_not_parathread() {
new_test_ext().execute_with(|| {
System::run_to_block::(1);
assert_ok!(TestRegistrar::::register(
1,
ParaId::from(1_u32),
dummy_head_data(),
dummy_validation_code(),
));
assert_ok!(TestRegistrar::::make_teyrchain(ParaId::from(1_u32)));
assert_noop!(
AssignedSlots::assign_perm_teyrchain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
),
Error::::NotParathread
);
});
}
#[test]
fn assign_perm_slot_fails_when_existing_lease() {
new_test_ext().execute_with(|| {
System::run_to_block::(1);
assert_ok!(TestRegistrar::::register(
1,
ParaId::from(1_u32),
dummy_head_data(),
dummy_validation_code(),
));
// Register lease in current lease period
assert_ok!(Slots::lease_out(ParaId::from(1_u32), &1, 1, 1, 1));
// Try to assign a perm slot in current period fails
assert_noop!(
AssignedSlots::assign_perm_teyrchain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
),
Error::::OngoingLeaseExists
);
// Cleanup
assert_ok!(Slots::clear_all_leases(RuntimeOrigin::root(), 1.into()));
// Register lease for next lease period
assert_ok!(Slots::lease_out(ParaId::from(1_u32), &1, 1, 2, 1));
// Should be detected and also fail
assert_noop!(
AssignedSlots::assign_perm_teyrchain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
),
Error::::OngoingLeaseExists
);
});
}
#[test]
fn assign_perm_slot_fails_when_max_perm_slots_exceeded() {
new_test_ext().execute_with(|| {
System::run_to_block::(1);
assert_ok!(TestRegistrar::::register(
1,
ParaId::from(1_u32),
dummy_head_data(),
dummy_validation_code(),
));
assert_ok!(TestRegistrar::::register(
2,
ParaId::from(2_u32),
dummy_head_data(),
dummy_validation_code(),
));
assert_ok!(TestRegistrar::::register(
3,
ParaId::from(3_u32),
dummy_head_data(),
dummy_validation_code(),
));
assert_ok!(AssignedSlots::assign_perm_teyrchain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
));
assert_ok!(AssignedSlots::assign_perm_teyrchain_slot(
RuntimeOrigin::root(),
ParaId::from(2_u32),
));
assert_eq!(assigned_slots::PermanentSlotCount::::get(), 2);
assert_noop!(
AssignedSlots::assign_perm_teyrchain_slot(
RuntimeOrigin::root(),
ParaId::from(3_u32),
),
Error::::MaxPermanentSlotsExceeded
);
});
}
#[test]
fn assign_perm_slot_succeeds_for_parathread() {
new_test_ext().execute_with(|| {
let mut block = 1;
System::run_to_block::(block);
assert_ok!(TestRegistrar::::register(
1,
ParaId::from(1_u32),
dummy_head_data(),
dummy_validation_code(),
));
assert_eq!(assigned_slots::PermanentSlotCount::::get(), 0);
assert_eq!(assigned_slots::PermanentSlots::::get(ParaId::from(1_u32)), None);
assert_ok!(AssignedSlots::assign_perm_teyrchain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
));
// Para is a lease holding teyrchain for PermanentSlotLeasePeriodLength * LeasePeriod
// blocks
while block < 9 {
println!("block #{}", block);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(1_u32)), true);
assert_eq!(assigned_slots::PermanentSlotCount::::get(), 1);
assert_eq!(AssignedSlots::has_permanent_slot(ParaId::from(1_u32)), true);
assert_eq!(
assigned_slots::PermanentSlots::::get(ParaId::from(1_u32)),
Some((0, 3))
);
assert_eq!(Slots::already_leased(ParaId::from(1_u32), 0, 2), true);
block += 1;
System::run_to_block::(block);
}
// Para lease ended, downgraded back to parathread (on-demand teyrchain)
assert_eq!(TestRegistrar::::is_parathread(ParaId::from(1_u32)), true);
assert_eq!(Slots::already_leased(ParaId::from(1_u32), 0, 5), false);
});
}
#[test]
fn assign_temp_slot_fails_for_unknown_para() {
new_test_ext().execute_with(|| {
System::run_to_block::(1);
assert_noop!(
AssignedSlots::assign_temp_teyrchain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
SlotLeasePeriodStart::Current
),
Error::::ParaDoesntExist
);
});
}
#[test]
fn assign_temp_slot_fails_for_invalid_origin() {
new_test_ext().execute_with(|| {
System::run_to_block::(1);
assert_noop!(
AssignedSlots::assign_temp_teyrchain_slot(
RuntimeOrigin::signed(1),
ParaId::from(1_u32),
SlotLeasePeriodStart::Current
),
BadOrigin
);
});
}
#[test]
fn assign_temp_slot_fails_when_not_parathread() {
new_test_ext().execute_with(|| {
System::run_to_block::(1);
assert_ok!(TestRegistrar::::register(
1,
ParaId::from(1_u32),
dummy_head_data(),
dummy_validation_code(),
));
assert_ok!(TestRegistrar::::make_teyrchain(ParaId::from(1_u32)));
assert_noop!(
AssignedSlots::assign_temp_teyrchain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
SlotLeasePeriodStart::Current
),
Error::::NotParathread
);
});
}
#[test]
fn assign_temp_slot_fails_when_existing_lease() {
new_test_ext().execute_with(|| {
System::run_to_block::(1);
assert_ok!(TestRegistrar::::register(
1,
ParaId::from(1_u32),
dummy_head_data(),
dummy_validation_code(),
));
// Register lease in current lease period
assert_ok!(Slots::lease_out(ParaId::from(1_u32), &1, 1, 1, 1));
// Try to assign a perm slot in current period fails
assert_noop!(
AssignedSlots::assign_temp_teyrchain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
SlotLeasePeriodStart::Current
),
Error::::OngoingLeaseExists
);
// Cleanup
assert_ok!(Slots::clear_all_leases(RuntimeOrigin::root(), 1.into()));
// Register lease for next lease period
assert_ok!(Slots::lease_out(ParaId::from(1_u32), &1, 1, 2, 1));
// Should be detected and also fail
assert_noop!(
AssignedSlots::assign_temp_teyrchain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
SlotLeasePeriodStart::Current
),
Error::::OngoingLeaseExists
);
});
}
#[test]
fn assign_temp_slot_fails_when_max_temp_slots_exceeded() {
new_test_ext().execute_with(|| {
System::run_to_block::(1);
// Register 6 paras & a temp slot for each
for n in 0..=5 {
assert_ok!(TestRegistrar::::register(
n,
ParaId::from(n as u32),
dummy_head_data(),
dummy_validation_code()
));
assert_ok!(AssignedSlots::assign_temp_teyrchain_slot(
RuntimeOrigin::root(),
ParaId::from(n as u32),
SlotLeasePeriodStart::Current
));
}
assert_eq!(assigned_slots::TemporarySlotCount::::get(), 6);
// Attempt to assign one more temp slot
assert_ok!(TestRegistrar::::register(
7,
ParaId::from(7_u32),
dummy_head_data(),
dummy_validation_code(),
));
assert_noop!(
AssignedSlots::assign_temp_teyrchain_slot(
RuntimeOrigin::root(),
ParaId::from(7_u32),
SlotLeasePeriodStart::Current
),
Error::::MaxTemporarySlotsExceeded
);
});
}
#[test]
fn assign_temp_slot_succeeds_for_single_parathread() {
new_test_ext().execute_with(|| {
let mut block = 1;
System::run_to_block::(block);
assert_ok!(TestRegistrar::::register(
1,
ParaId::from(1_u32),
dummy_head_data(),
dummy_validation_code(),
));
assert_eq!(assigned_slots::TemporarySlots::::get(ParaId::from(1_u32)), None);
assert_ok!(AssignedSlots::assign_temp_teyrchain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
SlotLeasePeriodStart::Current
));
assert_eq!(assigned_slots::TemporarySlotCount::::get(), 1);
assert_eq!(assigned_slots::ActiveTemporarySlotCount::::get(), 1);
// Block 1-5
// Para is a lease holding teyrchain for TemporarySlotLeasePeriodLength * LeasePeriod
// blocks
while block < 6 {
println!("block #{}", block);
println!("lease period #{}", AssignedSlots::current_lease_period_index());
println!("lease {:?}", slots::Leases::::get(ParaId::from(1_u32)));
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(1_u32)), true);
assert_eq!(AssignedSlots::has_temporary_slot(ParaId::from(1_u32)), true);
assert_eq!(assigned_slots::ActiveTemporarySlotCount::::get(), 1);
assert_eq!(
assigned_slots::TemporarySlots::::get(ParaId::from(1_u32)),
Some(TeyrchainTemporarySlot {
manager: 1,
period_begin: 0,
period_count: 2, // TemporarySlotLeasePeriodLength
last_lease: Some(0),
lease_count: 1
})
);
assert_eq!(Slots::already_leased(ParaId::from(1_u32), 0, 1), true);
block += 1;
System::run_to_block::(block);
}
// Block 6
println!("block #{}", block);
println!("lease period #{}", AssignedSlots::current_lease_period_index());
println!("lease {:?}", slots::Leases::::get(ParaId::from(1_u32)));
// Para lease ended, downgraded back to on-demand teyrchain
assert_eq!(TestRegistrar::::is_parathread(ParaId::from(1_u32)), true);
assert_eq!(Slots::already_leased(ParaId::from(1_u32), 0, 3), false);
assert_eq!(assigned_slots::ActiveTemporarySlotCount::::get(), 0);
// Block 12
// Para should get a turn after TemporarySlotLeasePeriodLength * LeasePeriod blocks
System::run_to_block::(12);
println!("block #{}", block);
println!("lease period #{}", AssignedSlots::current_lease_period_index());
println!("lease {:?}", slots::Leases::::get(ParaId::from(1_u32)));
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(1_u32)), true);
assert_eq!(Slots::already_leased(ParaId::from(1_u32), 4, 5), true);
assert_eq!(assigned_slots::ActiveTemporarySlotCount::::get(), 1);
});
}
#[test]
fn assign_temp_slot_succeeds_for_multiple_parathreads() {
new_test_ext().execute_with(|| {
// Block 1, Period 0
System::run_to_block::(1);
// Register 6 paras & a temp slot for each
// (3 slots in current lease period, 3 in the next one)
for n in 0..=5 {
assert_ok!(TestRegistrar::::register(
n,
ParaId::from(n as u32),
dummy_head_data(),
dummy_validation_code()
));
assert_ok!(AssignedSlots::assign_temp_teyrchain_slot(
RuntimeOrigin::root(),
ParaId::from(n as u32),
if (n % 2).is_zero() {
SlotLeasePeriodStart::Current
} else {
SlotLeasePeriodStart::Next
}
));
}
// Block 1-5, Period 0-1
for n in 1..=5 {
if n > 1 {
System::run_to_block::(n);
}
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(0)), true);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(1_u32)), false);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(2_u32)), true);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(3_u32)), false);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(4_u32)), false);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(5_u32)), false);
assert_eq!(assigned_slots::ActiveTemporarySlotCount::::get(), 2);
}
// Block 6-11, Period 2-3
for n in 6..=11 {
System::run_to_block::(n);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(0)), false);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(1_u32)), true);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(2_u32)), false);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(3_u32)), true);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(4_u32)), false);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(5_u32)), false);
assert_eq!(assigned_slots::ActiveTemporarySlotCount::::get(), 2);
}
// Block 12-17, Period 4-5
for n in 12..=17 {
System::run_to_block::(n);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(0)), false);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(1_u32)), false);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(2_u32)), false);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(3_u32)), false);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(4_u32)), true);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(5_u32)), true);
assert_eq!(assigned_slots::ActiveTemporarySlotCount::::get(), 2);
}
// Block 18-23, Period 6-7
for n in 18..=23 {
System::run_to_block::(n);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(0)), true);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(1_u32)), false);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(2_u32)), true);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(3_u32)), false);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(4_u32)), false);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(5_u32)), false);
assert_eq!(assigned_slots::ActiveTemporarySlotCount::::get(), 2);
}
// Block 24-29, Period 8-9
for n in 24..=29 {
System::run_to_block::(n);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(0)), false);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(1_u32)), true);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(2_u32)), false);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(3_u32)), true);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(4_u32)), false);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(5_u32)), false);
assert_eq!(assigned_slots::ActiveTemporarySlotCount::::get(), 2);
}
// Block 30-35, Period 10-11
for n in 30..=35 {
System::run_to_block::(n);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(0)), false);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(1_u32)), false);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(2_u32)), false);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(3_u32)), false);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(4_u32)), true);
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(5_u32)), true);
assert_eq!(assigned_slots::ActiveTemporarySlotCount::::get(), 2);
}
});
}
#[test]
fn unassign_slot_fails_for_unknown_para() {
new_test_ext().execute_with(|| {
System::run_to_block::(1);
assert_noop!(
AssignedSlots::unassign_teyrchain_slot(RuntimeOrigin::root(), ParaId::from(1_u32),),
Error::::SlotNotAssigned
);
});
}
#[test]
fn unassign_slot_fails_for_invalid_origin() {
new_test_ext().execute_with(|| {
System::run_to_block::(1);
assert_noop!(
AssignedSlots::assign_perm_teyrchain_slot(
RuntimeOrigin::signed(1),
ParaId::from(1_u32),
),
BadOrigin
);
});
}
#[test]
fn unassign_perm_slot_succeeds() {
new_test_ext().execute_with(|| {
System::run_to_block::(1);
assert_ok!(TestRegistrar::::register(
1,
ParaId::from(1_u32),
dummy_head_data(),
dummy_validation_code(),
));
assert_ok!(AssignedSlots::assign_perm_teyrchain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
));
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(1_u32)), true);
assert_ok!(AssignedSlots::unassign_teyrchain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
));
assert_eq!(assigned_slots::PermanentSlotCount::::get(), 0);
assert_eq!(AssignedSlots::has_permanent_slot(ParaId::from(1_u32)), false);
assert_eq!(assigned_slots::PermanentSlots::::get(ParaId::from(1_u32)), None);
assert_eq!(Slots::already_leased(ParaId::from(1_u32), 0, 2), false);
});
}
#[test]
fn unassign_temp_slot_succeeds() {
new_test_ext().execute_with(|| {
System::run_to_block::(1);
assert_ok!(TestRegistrar::::register(
1,
ParaId::from(1_u32),
dummy_head_data(),
dummy_validation_code(),
));
assert_ok!(AssignedSlots::assign_temp_teyrchain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
SlotLeasePeriodStart::Current
));
assert_eq!(TestRegistrar::::is_teyrchain(ParaId::from(1_u32)), true);
assert_ok!(AssignedSlots::unassign_teyrchain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
));
assert_eq!(assigned_slots::TemporarySlotCount::::get(), 0);
assert_eq!(assigned_slots::ActiveTemporarySlotCount::