use crate::{
slots::{self, Pallet as Slots, WeightInfo},
traits::{LeaseError, Leaser, Registrar},
MAXIMUM_BLOCK_WEIGHT,
};
use frame_support::{pallet_prelude::*, traits::Currency};
use frame_system::pallet_prelude::*;
pub use pallet::*;
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use primitives::v2::Id as ParaId;
use runtime_parachains::{
configuration,
paras::{self},
};
use scale_info::TypeInfo;
use sp_runtime::traits::{One, Saturating, Zero};
use sp_std::prelude::*;
#[derive(Encode, Decode, Clone, Copy, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub enum SlotLeasePeriodStart {
Current,
Next,
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, Default, MaxEncodedLen, RuntimeDebug, TypeInfo)]
pub struct ParachainTemporarySlot<AccountId, LeasePeriod> {
pub manager: AccountId,
pub period_begin: LeasePeriod,
pub period_count: LeasePeriod,
pub last_lease: Option<LeasePeriod>,
pub lease_count: u32,
}
type BalanceOf<T> = <<<T as Config>::Leaser as Leaser<<T as frame_system::Config>::BlockNumber>>::Currency as Currency<
<T as frame_system::Config>::AccountId,
>>::Balance;
type LeasePeriodOf<T> =
<<T as Config>::Leaser as Leaser<<T as frame_system::Config>::BlockNumber>>::LeasePeriod;
#[frame_support::pallet]
pub mod pallet {
use super::*;
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
#[pallet::config]
#[pallet::disable_frame_system_supertrait_check]
pub trait Config: configuration::Config + paras::Config + slots::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type AssignSlotOrigin: EnsureOrigin<<Self as frame_system::Config>::RuntimeOrigin>;
type Leaser: Leaser<
Self::BlockNumber,
AccountId = Self::AccountId,
LeasePeriod = Self::BlockNumber,
>;
#[pallet::constant]
type PermanentSlotLeasePeriodLength: Get<u32>;
#[pallet::constant]
type TemporarySlotLeasePeriodLength: Get<u32>;
#[pallet::constant]
type MaxPermanentSlots: Get<u32>;
#[pallet::constant]
type MaxTemporarySlots: Get<u32>;
#[pallet::constant]
type MaxTemporarySlotPerLeasePeriod: Get<u32>;
}
#[pallet::storage]
#[pallet::getter(fn permanent_slots)]
pub type PermanentSlots<T: Config> =
StorageMap<_, Twox64Concat, ParaId, (LeasePeriodOf<T>, LeasePeriodOf<T>), OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn permanent_slot_count)]
pub type PermanentSlotCount<T: Config> = StorageValue<_, u32, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn temporary_slots)]
pub type TemporarySlots<T: Config> = StorageMap<
_,
Twox64Concat,
ParaId,
ParachainTemporarySlot<T::AccountId, LeasePeriodOf<T>>,
OptionQuery,
>;
#[pallet::storage]
#[pallet::getter(fn temporary_slot_count)]
pub type TemporarySlotCount<T: Config> = StorageValue<_, u32, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn active_temporary_slot_count)]
pub type ActiveTemporarySlotCount<T: Config> = StorageValue<_, u32, ValueQuery>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
PermanentSlotAssigned(ParaId),
TemporarySlotAssigned(ParaId),
}
#[pallet::error]
pub enum Error<T> {
ParaDoesntExist,
NotParathread,
CannotUpgrade,
CannotDowngrade,
SlotAlreadyAssigned,
SlotNotAssigned,
OngoingLeaseExists,
MaxPermanentSlotsExceeded,
MaxTemporarySlotsExceeded,
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(n: T::BlockNumber) -> Weight {
if let Some((lease_period, first_block)) = Self::lease_period_index(n) {
if first_block {
return Self::manage_lease_period_start(lease_period)
}
}
Weight::zero()
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(((MAXIMUM_BLOCK_WEIGHT / 10) as Weight, DispatchClass::Operational))]
pub fn assign_perm_parachain_slot(origin: OriginFor<T>, id: ParaId) -> DispatchResult {
T::AssignSlotOrigin::ensure_origin(origin)?;
let manager = T::Registrar::manager_of(id).ok_or(Error::<T>::ParaDoesntExist)?;
ensure!(T::Registrar::is_parathread(id), Error::<T>::NotParathread,);
ensure!(
!Self::has_permanent_slot(id) && !Self::has_temporary_slot(id),
Error::<T>::SlotAlreadyAssigned
);
let current_lease_period: T::BlockNumber = Self::current_lease_period_index();
ensure!(
!T::Leaser::already_leased(
id,
current_lease_period,
current_lease_period.saturating_add(
T::BlockNumber::from(2u32)
.saturating_mul(T::PermanentSlotLeasePeriodLength::get().into())
)
),
Error::<T>::OngoingLeaseExists
);
ensure!(
PermanentSlotCount::<T>::get() < T::MaxPermanentSlots::get(),
Error::<T>::MaxPermanentSlotsExceeded
);
Self::configure_slot_lease(
id,
manager,
current_lease_period,
T::PermanentSlotLeasePeriodLength::get().into(),
)
.map_err(|_| Error::<T>::CannotUpgrade)?;
PermanentSlots::<T>::insert(
id,
(
current_lease_period,
LeasePeriodOf::<T>::from(T::PermanentSlotLeasePeriodLength::get()),
),
);
<PermanentSlotCount<T>>::mutate(|count| count.saturating_inc());
Self::deposit_event(Event::<T>::PermanentSlotAssigned(id));
Ok(())
}
#[pallet::call_index(1)]
#[pallet::weight(((MAXIMUM_BLOCK_WEIGHT / 10) as Weight, DispatchClass::Operational))]
pub fn assign_temp_parachain_slot(
origin: OriginFor<T>,
id: ParaId,
lease_period_start: SlotLeasePeriodStart,
) -> DispatchResult {
T::AssignSlotOrigin::ensure_origin(origin)?;
let manager = T::Registrar::manager_of(id).ok_or(Error::<T>::ParaDoesntExist)?;
ensure!(T::Registrar::is_parathread(id), Error::<T>::NotParathread);
ensure!(
!Self::has_permanent_slot(id) && !Self::has_temporary_slot(id),
Error::<T>::SlotAlreadyAssigned
);
let current_lease_period: T::BlockNumber = Self::current_lease_period_index();
ensure!(
!T::Leaser::already_leased(
id,
current_lease_period,
current_lease_period.saturating_add(
T::BlockNumber::from(2u32)
.saturating_mul(T::TemporarySlotLeasePeriodLength::get().into())
)
),
Error::<T>::OngoingLeaseExists
);
ensure!(
TemporarySlotCount::<T>::get() < T::MaxTemporarySlots::get(),
Error::<T>::MaxTemporarySlotsExceeded
);
let mut temp_slot = ParachainTemporarySlot {
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 &&
Self::active_temporary_slot_count() < T::MaxTemporarySlotPerLeasePeriod::get()
{
match Self::configure_slot_lease(
id,
manager,
temp_slot.period_begin,
temp_slot.period_count,
) {
Ok(_) => {
ActiveTemporarySlotCount::<T>::mutate(|count| count.saturating_inc());
temp_slot.last_lease = Some(temp_slot.period_begin);
temp_slot.lease_count += 1;
},
Err(err) => {
log::warn!(target: "assigned_slots",
"Failed to allocate a temp slot for para {:?} at period {:?}: {:?}",
id, current_lease_period, err
);
},
}
}
TemporarySlots::<T>::insert(id, temp_slot);
<TemporarySlotCount<T>>::mutate(|count| count.saturating_inc());
Self::deposit_event(Event::<T>::TemporarySlotAssigned(id));
Ok(())
}
#[pallet::call_index(2)]
#[pallet::weight(((MAXIMUM_BLOCK_WEIGHT / 10) as Weight, DispatchClass::Operational))]
pub fn unassign_parachain_slot(origin: OriginFor<T>, id: ParaId) -> DispatchResult {
T::AssignSlotOrigin::ensure_origin(origin.clone())?;
ensure!(
Self::has_permanent_slot(id) || Self::has_temporary_slot(id),
Error::<T>::SlotNotAssigned
);
let is_parachain = Self::is_parachain(id);
Self::clear_slot_leases(origin.clone(), id)?;
if PermanentSlots::<T>::contains_key(id) {
PermanentSlots::<T>::remove(id);
<PermanentSlotCount<T>>::mutate(|count| *count = count.saturating_sub(One::one()));
} else if TemporarySlots::<T>::contains_key(id) {
TemporarySlots::<T>::remove(id);
<TemporarySlotCount<T>>::mutate(|count| *count = count.saturating_sub(One::one()));
if is_parachain {
<ActiveTemporarySlotCount<T>>::mutate(|active_count| {
*active_count = active_count.saturating_sub(One::one())
});
}
}
if is_parachain {
if let Err(err) = runtime_parachains::schedule_parachain_downgrade::<T>(id) {
log::warn!(target: "assigned_slots",
"Failed to downgrade parachain {:?} at period {:?}: {:?}",
id, Self::current_lease_period_index(), err
);
}
}
Ok(())
}
}
}
impl<T: Config> Pallet<T> {
fn allocate_temporary_slot_leases(lease_period_index: LeasePeriodOf<T>) -> DispatchResult {
let mut active_temp_slots = 0u32;
let mut pending_temp_slots = Vec::new();
TemporarySlots::<T>::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_temp_slots += 1;
}
Some(last_lease)
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 => {
pending_temp_slots.insert(0, (para, slot));
},
_ => {
},
}
});
let mut newly_created_lease = 0u32;
if active_temp_slots < T::MaxTemporarySlotPerLeasePeriod::get() &&
!pending_temp_slots.is_empty()
{
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::<T>::try_mutate::<_, _, Error<T>, _>(id, |s| {
Self::configure_slot_lease(
*id,
temp_slot.manager.clone(),
lease_period_index,
temp_slot.period_count,
)
.map_err(|_| Error::<T>::CannotUpgrade)?;
*s = Some(ParachainTemporarySlot {
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::<T>::set(active_temp_slots + newly_created_lease);
Ok(())
}
fn clear_slot_leases(origin: OriginFor<T>, id: ParaId) -> DispatchResult {
Slots::<T>::clear_all_leases(origin, id)
}
fn configure_slot_lease(
para: ParaId,
manager: T::AccountId,
lease_period: LeasePeriodOf<T>,
lease_duration: LeasePeriodOf<T>,
) -> Result<(), LeaseError> {
T::Leaser::lease_out(para, &manager, BalanceOf::<T>::zero(), lease_period, lease_duration)
}
fn has_permanent_slot(id: ParaId) -> bool {
PermanentSlots::<T>::contains_key(id)
}
fn has_temporary_slot(id: ParaId) -> bool {
TemporarySlots::<T>::contains_key(id)
}
fn is_parachain(id: ParaId) -> bool {
T::Registrar::is_parachain(id)
}
fn current_lease_period_index() -> LeasePeriodOf<T> {
T::Leaser::lease_period_index(frame_system::Pallet::<T>::block_number())
.and_then(|x| Some(x.0))
.unwrap()
}
fn lease_period_index(block: BlockNumberFor<T>) -> Option<(LeasePeriodOf<T>, bool)> {
T::Leaser::lease_period_index(block)
}
fn manage_lease_period_start(lease_period_index: LeasePeriodOf<T>) -> Weight {
if let Err(err) = Self::allocate_temporary_slot_leases(lease_period_index) {
log::error!(target: "assigned_slots",
"Allocating slots failed for lease period {:?}, with: {:?}",
lease_period_index, err
);
}
<T as slots::Config>::WeightInfo::force_lease() *
(T::MaxTemporarySlotPerLeasePeriod::get() as u64)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{assigned_slots, mock::TestRegistrar, slots};
use ::test_helpers::{dummy_head_data, dummy_validation_code};
use frame_support::{assert_noop, assert_ok, parameter_types};
use frame_system::EnsureRoot;
use pallet_balances;
use primitives::v2::{BlockNumber, Header};
use runtime_parachains::{
configuration as parachains_configuration, paras as parachains_paras,
shared as parachains_shared,
};
use sp_core::H256;
use sp_runtime::{
traits::{BlakeTwo256, IdentityLookup},
transaction_validity::TransactionPriority,
DispatchError::BadOrigin,
};
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;
frame_support::construct_runtime!(
pub enum Test where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
Configuration: parachains_configuration::{Pallet, Call, Storage, Config<T>},
ParasShared: parachains_shared::{Pallet, Call, Storage},
Parachains: parachains_paras::{Pallet, Call, Storage, Config, Event},
Slots: slots::{Pallet, Call, Storage, Event<T>},
AssignedSlots: assigned_slots::{Pallet, Call, Storage, Event<T>},
}
);
impl<C> frame_system::offchain::SendTransactionTypes<C> for Test
where
RuntimeCall: From<C>,
{
type Extrinsic = UncheckedExtrinsic;
type OverarchingCall = RuntimeCall;
}
parameter_types! {
pub const BlockHashCount: u32 = 250;
}
impl frame_system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
type BlockWeights = ();
type BlockLength = ();
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
type Index = u64;
type BlockNumber = BlockNumber;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type RuntimeEvent = RuntimeEvent;
type BlockHashCount = BlockHashCount;
type DbWeight = ();
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
parameter_types! {
pub const ExistentialDeposit: u64 = 1;
}
impl pallet_balances::Config for Test {
type Balance = u64;
type RuntimeEvent = RuntimeEvent;
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type WeightInfo = ();
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
}
impl parachains_configuration::Config for Test {
type WeightInfo = parachains_configuration::TestWeightInfo;
}
parameter_types! {
pub const ParasUnsignedPriority: TransactionPriority = TransactionPriority::max_value();
}
impl parachains_paras::Config for Test {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = parachains_paras::TestWeightInfo;
type UnsignedPriority = ParasUnsignedPriority;
type NextSessionRotation = crate::mock::TestNextSessionRotation;
}
impl parachains_shared::Config for Test {}
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<Test>;
type LeasePeriod = LeasePeriod;
type LeaseOffset = LeaseOffset;
type ForceOrigin = EnsureRoot<Self::AccountId>;
type WeightInfo = crate::slots::TestWeightInfo;
}
parameter_types! {
pub const PermanentSlotLeasePeriodLength: u32 = 3;
pub const TemporarySlotLeasePeriodLength: u32 = 2;
pub const MaxPermanentSlots: u32 = 2;
pub const MaxTemporarySlots: u32 = 6;
pub const MaxTemporarySlotPerLeasePeriod: u32 = 2;
}
impl assigned_slots::Config for Test {
type RuntimeEvent = RuntimeEvent;
type AssignSlotOrigin = EnsureRoot<Self::AccountId>;
type Leaser = Slots;
type PermanentSlotLeasePeriodLength = PermanentSlotLeasePeriodLength;
type TemporarySlotLeasePeriodLength = TemporarySlotLeasePeriodLength;
type MaxPermanentSlots = MaxPermanentSlots;
type MaxTemporarySlots = MaxTemporarySlots;
type MaxTemporarySlotPerLeasePeriod = MaxTemporarySlotPerLeasePeriod;
}
pub fn new_test_ext() -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
pallet_balances::GenesisConfig::<Test> {
balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)],
}
.assimilate_storage(&mut t)
.unwrap();
t.into()
}
fn run_to_block(n: BlockNumber) {
while System::block_number() < n {
let mut block = System::block_number();
AssignedSlots::on_finalize(block);
Slots::on_finalize(block);
Parachains::on_finalize(block);
ParasShared::on_finalize(block);
Configuration::on_finalize(block);
Balances::on_finalize(block);
System::on_finalize(block);
System::set_block_number(block + 1);
block = System::block_number();
System::on_initialize(block);
Balances::on_initialize(block);
Configuration::on_initialize(block);
ParasShared::on_initialize(block);
Parachains::on_initialize(block);
Slots::on_initialize(block);
AssignedSlots::on_initialize(block);
}
}
#[test]
fn basic_setup_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_eq!(AssignedSlots::current_lease_period_index(), 0);
assert_eq!(Slots::deposit_held(1.into(), &1), 0);
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(|| {
run_to_block(1);
assert_noop!(
AssignedSlots::assign_perm_parachain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
),
Error::<Test>::ParaDoesntExist
);
});
}
#[test]
fn assign_perm_slot_fails_for_invalid_origin() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_noop!(
AssignedSlots::assign_perm_parachain_slot(
RuntimeOrigin::signed(1),
ParaId::from(1_u32),
),
BadOrigin
);
});
}
#[test]
fn assign_perm_slot_fails_when_not_parathread() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(TestRegistrar::<Test>::register(
1,
ParaId::from(1_u32),
dummy_head_data(),
dummy_validation_code(),
));
assert_ok!(TestRegistrar::<Test>::make_parachain(ParaId::from(1_u32)));
assert_noop!(
AssignedSlots::assign_perm_parachain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
),
Error::<Test>::NotParathread
);
});
}
#[test]
fn assign_perm_slot_fails_when_existing_lease() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(TestRegistrar::<Test>::register(
1,
ParaId::from(1_u32),
dummy_head_data(),
dummy_validation_code(),
));
assert_ok!(Slots::lease_out(ParaId::from(1_u32), &1, 1, 1, 1));
assert_noop!(
AssignedSlots::assign_perm_parachain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
),
Error::<Test>::OngoingLeaseExists
);
assert_ok!(Slots::clear_all_leases(RuntimeOrigin::root(), 1.into()));
assert_ok!(Slots::lease_out(ParaId::from(1_u32), &1, 1, 2, 1));
assert_noop!(
AssignedSlots::assign_perm_parachain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
),
Error::<Test>::OngoingLeaseExists
);
});
}
#[test]
fn assign_perm_slot_fails_when_max_perm_slots_exceeded() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(TestRegistrar::<Test>::register(
1,
ParaId::from(1_u32),
dummy_head_data(),
dummy_validation_code(),
));
assert_ok!(TestRegistrar::<Test>::register(
2,
ParaId::from(2_u32),
dummy_head_data(),
dummy_validation_code(),
));
assert_ok!(TestRegistrar::<Test>::register(
3,
ParaId::from(3_u32),
dummy_head_data(),
dummy_validation_code(),
));
assert_ok!(AssignedSlots::assign_perm_parachain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
));
assert_ok!(AssignedSlots::assign_perm_parachain_slot(
RuntimeOrigin::root(),
ParaId::from(2_u32),
));
assert_eq!(AssignedSlots::permanent_slot_count(), 2);
assert_noop!(
AssignedSlots::assign_perm_parachain_slot(
RuntimeOrigin::root(),
ParaId::from(3_u32),
),
Error::<Test>::MaxPermanentSlotsExceeded
);
});
}
#[test]
fn assign_perm_slot_succeeds_for_parathread() {
new_test_ext().execute_with(|| {
let mut block = 1;
run_to_block(block);
assert_ok!(TestRegistrar::<Test>::register(
1,
ParaId::from(1_u32),
dummy_head_data(),
dummy_validation_code(),
));
assert_eq!(AssignedSlots::permanent_slot_count(), 0);
assert_eq!(AssignedSlots::permanent_slots(ParaId::from(1_u32)), None);
assert_ok!(AssignedSlots::assign_perm_parachain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
));
while block < 9 {
println!("block #{}", block);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(1_u32)), true);
assert_eq!(AssignedSlots::permanent_slot_count(), 1);
assert_eq!(AssignedSlots::has_permanent_slot(ParaId::from(1_u32)), true);
assert_eq!(AssignedSlots::permanent_slots(ParaId::from(1_u32)), Some((0, 3)));
assert_eq!(Slots::already_leased(ParaId::from(1_u32), 0, 2), true);
block += 1;
run_to_block(block);
}
assert_eq!(TestRegistrar::<Test>::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(|| {
run_to_block(1);
assert_noop!(
AssignedSlots::assign_temp_parachain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
SlotLeasePeriodStart::Current
),
Error::<Test>::ParaDoesntExist
);
});
}
#[test]
fn assign_temp_slot_fails_for_invalid_origin() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_noop!(
AssignedSlots::assign_temp_parachain_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(|| {
run_to_block(1);
assert_ok!(TestRegistrar::<Test>::register(
1,
ParaId::from(1_u32),
dummy_head_data(),
dummy_validation_code(),
));
assert_ok!(TestRegistrar::<Test>::make_parachain(ParaId::from(1_u32)));
assert_noop!(
AssignedSlots::assign_temp_parachain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
SlotLeasePeriodStart::Current
),
Error::<Test>::NotParathread
);
});
}
#[test]
fn assign_temp_slot_fails_when_existing_lease() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(TestRegistrar::<Test>::register(
1,
ParaId::from(1_u32),
dummy_head_data(),
dummy_validation_code(),
));
assert_ok!(Slots::lease_out(ParaId::from(1_u32), &1, 1, 1, 1));
assert_noop!(
AssignedSlots::assign_temp_parachain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
SlotLeasePeriodStart::Current
),
Error::<Test>::OngoingLeaseExists
);
assert_ok!(Slots::clear_all_leases(RuntimeOrigin::root(), 1.into()));
assert_ok!(Slots::lease_out(ParaId::from(1_u32), &1, 1, 2, 1));
assert_noop!(
AssignedSlots::assign_temp_parachain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
SlotLeasePeriodStart::Current
),
Error::<Test>::OngoingLeaseExists
);
});
}
#[test]
fn assign_temp_slot_fails_when_max_temp_slots_exceeded() {
new_test_ext().execute_with(|| {
run_to_block(1);
for n in 0..=5 {
assert_ok!(TestRegistrar::<Test>::register(
n,
ParaId::from(n as u32),
dummy_head_data(),
dummy_validation_code()
));
assert_ok!(AssignedSlots::assign_temp_parachain_slot(
RuntimeOrigin::root(),
ParaId::from(n as u32),
SlotLeasePeriodStart::Current
));
}
assert_eq!(AssignedSlots::temporary_slot_count(), 6);
assert_ok!(TestRegistrar::<Test>::register(
7,
ParaId::from(7_u32),
dummy_head_data(),
dummy_validation_code(),
));
assert_noop!(
AssignedSlots::assign_temp_parachain_slot(
RuntimeOrigin::root(),
ParaId::from(7_u32),
SlotLeasePeriodStart::Current
),
Error::<Test>::MaxTemporarySlotsExceeded
);
});
}
#[test]
fn assign_temp_slot_succeeds_for_single_parathread() {
new_test_ext().execute_with(|| {
let mut block = 1;
run_to_block(block);
assert_ok!(TestRegistrar::<Test>::register(
1,
ParaId::from(1_u32),
dummy_head_data(),
dummy_validation_code(),
));
assert_eq!(AssignedSlots::temporary_slots(ParaId::from(1_u32)), None);
assert_ok!(AssignedSlots::assign_temp_parachain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
SlotLeasePeriodStart::Current
));
assert_eq!(AssignedSlots::temporary_slot_count(), 1);
assert_eq!(AssignedSlots::active_temporary_slot_count(), 1);
while block < 6 {
println!("block #{}", block);
println!("lease period #{}", AssignedSlots::current_lease_period_index());
println!("lease {:?}", Slots::lease(ParaId::from(1_u32)));
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(1_u32)), true);
assert_eq!(AssignedSlots::has_temporary_slot(ParaId::from(1_u32)), true);
assert_eq!(AssignedSlots::active_temporary_slot_count(), 1);
assert_eq!(
AssignedSlots::temporary_slots(ParaId::from(1_u32)),
Some(ParachainTemporarySlot {
manager: 1,
period_begin: 0,
period_count: 2, last_lease: Some(0),
lease_count: 1
})
);
assert_eq!(Slots::already_leased(ParaId::from(1_u32), 0, 1), true);
block += 1;
run_to_block(block);
}
println!("block #{}", block);
println!("lease period #{}", AssignedSlots::current_lease_period_index());
println!("lease {:?}", Slots::lease(ParaId::from(1_u32)));
assert_eq!(TestRegistrar::<Test>::is_parathread(ParaId::from(1_u32)), true);
assert_eq!(Slots::already_leased(ParaId::from(1_u32), 0, 3), false);
assert_eq!(AssignedSlots::active_temporary_slot_count(), 0);
run_to_block(12);
println!("block #{}", block);
println!("lease period #{}", AssignedSlots::current_lease_period_index());
println!("lease {:?}", Slots::lease(ParaId::from(1_u32)));
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(1_u32)), true);
assert_eq!(Slots::already_leased(ParaId::from(1_u32), 4, 5), true);
assert_eq!(AssignedSlots::active_temporary_slot_count(), 1);
});
}
#[test]
fn assign_temp_slot_succeeds_for_multiple_parathreads() {
new_test_ext().execute_with(|| {
run_to_block(1);
for n in 0..=5 {
assert_ok!(TestRegistrar::<Test>::register(
n,
ParaId::from(n as u32),
dummy_head_data(),
dummy_validation_code()
));
assert_ok!(AssignedSlots::assign_temp_parachain_slot(
RuntimeOrigin::root(),
ParaId::from(n as u32),
if (n % 2).is_zero() {
SlotLeasePeriodStart::Current
} else {
SlotLeasePeriodStart::Next
}
));
}
for n in 1..=5 {
if n > 1 {
run_to_block(n);
}
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(0)), true);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(1_u32)), false);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(2_u32)), true);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(3_u32)), false);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(4_u32)), false);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(5_u32)), false);
assert_eq!(AssignedSlots::active_temporary_slot_count(), 2);
}
for n in 6..=11 {
run_to_block(n);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(0)), false);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(1_u32)), true);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(2_u32)), false);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(3_u32)), true);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(4_u32)), false);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(5_u32)), false);
assert_eq!(AssignedSlots::active_temporary_slot_count(), 2);
}
for n in 12..=17 {
run_to_block(n);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(0)), false);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(1_u32)), false);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(2_u32)), false);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(3_u32)), false);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(4_u32)), true);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(5_u32)), true);
assert_eq!(AssignedSlots::active_temporary_slot_count(), 2);
}
for n in 18..=23 {
run_to_block(n);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(0)), true);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(1_u32)), false);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(2_u32)), true);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(3_u32)), false);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(4_u32)), false);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(5_u32)), false);
assert_eq!(AssignedSlots::active_temporary_slot_count(), 2);
}
for n in 24..=29 {
run_to_block(n);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(0)), false);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(1_u32)), true);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(2_u32)), false);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(3_u32)), true);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(4_u32)), false);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(5_u32)), false);
assert_eq!(AssignedSlots::active_temporary_slot_count(), 2);
}
for n in 30..=35 {
run_to_block(n);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(0)), false);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(1_u32)), false);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(2_u32)), false);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(3_u32)), false);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(4_u32)), true);
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(5_u32)), true);
assert_eq!(AssignedSlots::active_temporary_slot_count(), 2);
}
});
}
#[test]
fn unassign_slot_fails_for_unknown_para() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_noop!(
AssignedSlots::unassign_parachain_slot(RuntimeOrigin::root(), ParaId::from(1_u32),),
Error::<Test>::SlotNotAssigned
);
});
}
#[test]
fn unassign_slot_fails_for_invalid_origin() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_noop!(
AssignedSlots::assign_perm_parachain_slot(
RuntimeOrigin::signed(1),
ParaId::from(1_u32),
),
BadOrigin
);
});
}
#[test]
fn unassign_perm_slot_succeeds() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(TestRegistrar::<Test>::register(
1,
ParaId::from(1_u32),
dummy_head_data(),
dummy_validation_code(),
));
assert_ok!(AssignedSlots::assign_perm_parachain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
));
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(1_u32)), true);
assert_ok!(AssignedSlots::unassign_parachain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
));
assert_eq!(AssignedSlots::permanent_slot_count(), 0);
assert_eq!(AssignedSlots::has_permanent_slot(ParaId::from(1_u32)), false);
assert_eq!(AssignedSlots::permanent_slots(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(|| {
run_to_block(1);
assert_ok!(TestRegistrar::<Test>::register(
1,
ParaId::from(1_u32),
dummy_head_data(),
dummy_validation_code(),
));
assert_ok!(AssignedSlots::assign_temp_parachain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
SlotLeasePeriodStart::Current
));
assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(1_u32)), true);
assert_ok!(AssignedSlots::unassign_parachain_slot(
RuntimeOrigin::root(),
ParaId::from(1_u32),
));
assert_eq!(AssignedSlots::temporary_slot_count(), 0);
assert_eq!(AssignedSlots::active_temporary_slot_count(), 0);
assert_eq!(AssignedSlots::has_temporary_slot(ParaId::from(1_u32)), false);
assert_eq!(AssignedSlots::temporary_slots(ParaId::from(1_u32)), None);
assert_eq!(Slots::already_leased(ParaId::from(1_u32), 0, 1), false);
});
}
}