pub mod migration;
use crate::traits::{LeaseError, Leaser, Registrar};
use alloc::{vec, vec::Vec};
use frame_support::{
pallet_prelude::*,
traits::{Currency, ReservableCurrency},
weights::Weight,
};
use frame_system::pallet_prelude::*;
pub use pallet::*;
use polkadot_primitives::Id as ParaId;
use sp_runtime::traits::{CheckedConversion, CheckedSub, Saturating, Zero};
type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
type LeasePeriodOf<T> = BlockNumberFor<T>;
pub trait WeightInfo {
fn force_lease() -> Weight;
fn manage_lease_period_start(c: u32, t: u32) -> Weight;
fn clear_all_leases() -> Weight;
fn trigger_onboard() -> Weight;
}
pub struct TestWeightInfo;
impl WeightInfo for TestWeightInfo {
fn force_lease() -> Weight {
Weight::zero()
}
fn manage_lease_period_start(_c: u32, _t: u32) -> Weight {
Weight::zero()
}
fn clear_all_leases() -> Weight {
Weight::zero()
}
fn trigger_onboard() -> Weight {
Weight::zero()
}
}
#[frame_support::pallet]
pub mod pallet {
use super::*;
#[pallet::pallet]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type Currency: ReservableCurrency<Self::AccountId>;
type Registrar: Registrar<AccountId = Self::AccountId>;
#[pallet::constant]
type LeasePeriod: Get<BlockNumberFor<Self>>;
#[pallet::constant]
type LeaseOffset: Get<BlockNumberFor<Self>>;
type ForceOrigin: EnsureOrigin<<Self as frame_system::Config>::RuntimeOrigin>;
type WeightInfo: WeightInfo;
}
#[pallet::storage]
pub type Leases<T: Config> =
StorageMap<_, Twox64Concat, ParaId, Vec<Option<(T::AccountId, BalanceOf<T>)>>, ValueQuery>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
NewLeasePeriod { lease_period: LeasePeriodOf<T> },
Leased {
para_id: ParaId,
leaser: T::AccountId,
period_begin: LeasePeriodOf<T>,
period_count: LeasePeriodOf<T>,
extra_reserved: BalanceOf<T>,
total_amount: BalanceOf<T>,
},
}
#[pallet::error]
pub enum Error<T> {
ParaNotOnboarding,
LeaseError,
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(n: BlockNumberFor<T>) -> 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(T::WeightInfo::force_lease())]
pub fn force_lease(
origin: OriginFor<T>,
para: ParaId,
leaser: T::AccountId,
amount: BalanceOf<T>,
period_begin: LeasePeriodOf<T>,
period_count: LeasePeriodOf<T>,
) -> DispatchResult {
T::ForceOrigin::ensure_origin(origin)?;
Self::lease_out(para, &leaser, amount, period_begin, period_count)
.map_err(|_| Error::<T>::LeaseError)?;
Ok(())
}
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::clear_all_leases())]
pub fn clear_all_leases(origin: OriginFor<T>, para: ParaId) -> DispatchResult {
T::ForceOrigin::ensure_origin(origin)?;
let deposits = Self::all_deposits_held(para);
for (who, deposit) in deposits {
let err_amount = T::Currency::unreserve(&who, deposit);
debug_assert!(err_amount.is_zero());
}
Leases::<T>::remove(para);
Ok(())
}
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::trigger_onboard())]
pub fn trigger_onboard(origin: OriginFor<T>, para: ParaId) -> DispatchResult {
let _ = ensure_signed(origin)?;
let leases = Leases::<T>::get(para);
match leases.first() {
Some(Some(_lease_info)) => T::Registrar::make_parachain(para)?,
Some(None) | None => return Err(Error::<T>::ParaNotOnboarding.into()),
};
Ok(())
}
}
}
impl<T: Config> Pallet<T> {
fn manage_lease_period_start(lease_period_index: LeasePeriodOf<T>) -> Weight {
Self::deposit_event(Event::<T>::NewLeasePeriod { lease_period: lease_period_index });
let old_parachains = T::Registrar::parachains();
let mut parachains = Vec::new();
for (para, mut lease_periods) in Leases::<T>::iter() {
if lease_periods.is_empty() {
continue
}
if lease_periods.len() == 1 {
if let Some((who, value)) = &lease_periods[0] {
T::Currency::unreserve(&who, *value);
}
Leases::<T>::remove(para);
} else {
let maybe_ended_lease = lease_periods.remove(0);
Leases::<T>::insert(para, &lease_periods);
if let Some(ended_lease) = maybe_ended_lease {
let now_held = Self::deposit_held(para, &ended_lease.0);
if let Some(rebate) = ended_lease.1.checked_sub(&now_held) {
T::Currency::unreserve(&ended_lease.0, rebate);
}
}
if lease_periods[0].is_some() {
parachains.push(para);
}
}
}
parachains.sort();
for para in parachains.iter() {
if old_parachains.binary_search(para).is_err() {
let res = T::Registrar::make_parachain(*para);
debug_assert!(res.is_ok());
}
}
for para in old_parachains.iter() {
if parachains.binary_search(para).is_err() {
let res = T::Registrar::make_parathread(*para);
debug_assert!(res.is_ok());
}
}
T::WeightInfo::manage_lease_period_start(
old_parachains.len() as u32,
parachains.len() as u32,
)
}
fn all_deposits_held(para: ParaId) -> Vec<(T::AccountId, BalanceOf<T>)> {
let mut tracker = alloc::collections::btree_map::BTreeMap::new();
Leases::<T>::get(para).into_iter().for_each(|lease| match lease {
Some((who, amount)) => match tracker.get(&who) {
Some(prev_amount) =>
if amount > *prev_amount {
tracker.insert(who, amount);
},
None => {
tracker.insert(who, amount);
},
},
None => {},
});
tracker.into_iter().collect()
}
}
impl<T: Config> crate::traits::OnSwap for Pallet<T> {
fn on_swap(one: ParaId, other: ParaId) {
Leases::<T>::mutate(one, |x| Leases::<T>::mutate(other, |y| core::mem::swap(x, y)))
}
}
impl<T: Config> Leaser<BlockNumberFor<T>> for Pallet<T> {
type AccountId = T::AccountId;
type LeasePeriod = BlockNumberFor<T>;
type Currency = T::Currency;
fn lease_out(
para: ParaId,
leaser: &Self::AccountId,
amount: <Self::Currency as Currency<Self::AccountId>>::Balance,
period_begin: Self::LeasePeriod,
period_count: Self::LeasePeriod,
) -> Result<(), LeaseError> {
let now = frame_system::Pallet::<T>::block_number();
let (current_lease_period, _) =
Self::lease_period_index(now).ok_or(LeaseError::NoLeasePeriod)?;
let offset = period_begin
.checked_sub(¤t_lease_period)
.and_then(|x| x.checked_into::<usize>())
.ok_or(LeaseError::AlreadyEnded)?;
Leases::<T>::try_mutate(para, |d| {
if d.len() < offset {
d.resize_with(offset, || None);
}
let period_count_usize =
period_count.checked_into::<usize>().ok_or(LeaseError::AlreadyEnded)?;
for i in offset..(offset + period_count_usize) {
if d.len() > i {
if d[i] == None {
d[i] = Some((leaser.clone(), amount));
} else {
return Err(LeaseError::AlreadyLeased)
}
} else if d.len() == i {
d.push(Some((leaser.clone(), amount)));
} else {
}
}
let maybe_additional = amount.checked_sub(&Self::deposit_held(para, &leaser));
if let Some(ref additional) = maybe_additional {
T::Currency::reserve(&leaser, *additional)
.map_err(|_| LeaseError::ReserveFailed)?;
}
let reserved = maybe_additional.unwrap_or_default();
if current_lease_period == period_begin {
let _ = T::Registrar::make_parachain(para);
}
Self::deposit_event(Event::<T>::Leased {
para_id: para,
leaser: leaser.clone(),
period_begin,
period_count,
extra_reserved: reserved,
total_amount: amount,
});
Ok(())
})
}
fn deposit_held(
para: ParaId,
leaser: &Self::AccountId,
) -> <Self::Currency as Currency<Self::AccountId>>::Balance {
Leases::<T>::get(para)
.into_iter()
.map(|lease| match lease {
Some((who, amount)) =>
if &who == leaser {
amount
} else {
Zero::zero()
},
None => Zero::zero(),
})
.max()
.unwrap_or_else(Zero::zero)
}
#[cfg(any(feature = "runtime-benchmarks", test))]
fn lease_period_length() -> (BlockNumberFor<T>, BlockNumberFor<T>) {
(T::LeasePeriod::get(), T::LeaseOffset::get())
}
fn lease_period_index(b: BlockNumberFor<T>) -> Option<(Self::LeasePeriod, bool)> {
let offset_block_now = b.checked_sub(&T::LeaseOffset::get())?;
let lease_period = offset_block_now / T::LeasePeriod::get();
let at_begin = (offset_block_now % T::LeasePeriod::get()).is_zero();
Some((lease_period, at_begin))
}
fn already_leased(
para_id: ParaId,
first_period: Self::LeasePeriod,
last_period: Self::LeasePeriod,
) -> bool {
let now = frame_system::Pallet::<T>::block_number();
let (current_lease_period, _) = match Self::lease_period_index(now) {
Some(clp) => clp,
None => return true,
};
let start_period = first_period.max(current_lease_period);
let offset = match (start_period - current_lease_period).checked_into::<usize>() {
Some(offset) => offset,
None => return true,
};
let period_count = match last_period.saturating_sub(start_period).checked_into::<usize>() {
Some(period_count) => period_count,
None => return true,
};
let leases = Leases::<T>::get(para_id);
for slot in offset..=offset + period_count {
if let Some(Some(_)) = leases.get(slot) {
return true
}
}
false
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{mock::TestRegistrar, slots};
use frame_support::{assert_noop, assert_ok, derive_impl, parameter_types};
use frame_system::EnsureRoot;
use pallet_balances;
use polkadot_primitives::BlockNumber;
use polkadot_primitives_test_helpers::{dummy_head_data, dummy_validation_code};
use sp_core::H256;
use sp_runtime::{
traits::{BlakeTwo256, IdentityLookup},
BuildStorage,
};
type Block = frame_system::mocking::MockBlockU32<Test>;
frame_support::construct_runtime!(
pub enum Test
{
System: frame_system,
Balances: pallet_balances,
Slots: slots,
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
type BaseCallFilter = frame_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<Self::AccountId>;
type Block = Block;
type RuntimeEvent = RuntimeEvent;
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>;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for Test {
type AccountStore = System;
}
parameter_types! {
pub const LeasePeriod: BlockNumber = 10;
pub static LeaseOffset: BlockNumber = 0;
pub const ParaDeposit: u64 = 1;
}
impl 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;
}
pub fn new_test_ext() -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().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 {
Slots::on_finalize(System::block_number());
Balances::on_finalize(System::block_number());
System::on_finalize(System::block_number());
System::set_block_number(System::block_number() + 1);
System::on_initialize(System::block_number());
Balances::on_initialize(System::block_number());
Slots::on_initialize(System::block_number());
}
}
#[test]
fn basic_setup_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_eq!(Slots::lease_period_length(), (10, 0));
let now = System::block_number();
assert_eq!(Slots::lease_period_index(now).unwrap().0, 0);
assert_eq!(Slots::deposit_held(1.into(), &1), 0);
run_to_block(10);
let now = System::block_number();
assert_eq!(Slots::lease_period_index(now).unwrap().0, 1);
});
}
#[test]
fn lease_lifecycle_works() {
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(1.into(), &1, 1, 1, 1));
assert_eq!(Slots::deposit_held(1.into(), &1), 1);
assert_eq!(Balances::reserved_balance(1), 1);
run_to_block(19);
assert_eq!(Slots::deposit_held(1.into(), &1), 1);
assert_eq!(Balances::reserved_balance(1), 1);
run_to_block(20);
assert_eq!(Slots::deposit_held(1.into(), &1), 0);
assert_eq!(Balances::reserved_balance(1), 0);
assert_eq!(
TestRegistrar::<Test>::operations(),
vec![(1.into(), 10, true), (1.into(), 20, false),]
);
});
}
#[test]
fn lease_interrupted_lifecycle_works() {
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(1.into(), &1, 6, 1, 1));
assert_ok!(Slots::lease_out(1.into(), &1, 4, 3, 1));
run_to_block(19);
assert_eq!(Slots::deposit_held(1.into(), &1), 6);
assert_eq!(Balances::reserved_balance(1), 6);
run_to_block(20);
assert_eq!(Slots::deposit_held(1.into(), &1), 4);
assert_eq!(Balances::reserved_balance(1), 4);
run_to_block(39);
assert_eq!(Slots::deposit_held(1.into(), &1), 4);
assert_eq!(Balances::reserved_balance(1), 4);
run_to_block(40);
assert_eq!(Slots::deposit_held(1.into(), &1), 0);
assert_eq!(Balances::reserved_balance(1), 0);
assert_eq!(
TestRegistrar::<Test>::operations(),
vec![
(1.into(), 10, true),
(1.into(), 20, false),
(1.into(), 30, true),
(1.into(), 40, false),
]
);
});
}
#[test]
fn lease_relayed_lifecycle_works() {
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!(Slots::lease_out(1.into(), &1, 6, 1, 1).is_ok());
assert!(Slots::lease_out(1.into(), &2, 4, 2, 1).is_ok());
assert_eq!(Slots::deposit_held(1.into(), &1), 6);
assert_eq!(Balances::reserved_balance(1), 6);
assert_eq!(Slots::deposit_held(1.into(), &2), 4);
assert_eq!(Balances::reserved_balance(2), 4);
run_to_block(19);
assert_eq!(Slots::deposit_held(1.into(), &1), 6);
assert_eq!(Balances::reserved_balance(1), 6);
assert_eq!(Slots::deposit_held(1.into(), &2), 4);
assert_eq!(Balances::reserved_balance(2), 4);
run_to_block(20);
assert_eq!(Slots::deposit_held(1.into(), &1), 0);
assert_eq!(Balances::reserved_balance(1), 0);
assert_eq!(Slots::deposit_held(1.into(), &2), 4);
assert_eq!(Balances::reserved_balance(2), 4);
run_to_block(29);
assert_eq!(Slots::deposit_held(1.into(), &1), 0);
assert_eq!(Balances::reserved_balance(1), 0);
assert_eq!(Slots::deposit_held(1.into(), &2), 4);
assert_eq!(Balances::reserved_balance(2), 4);
run_to_block(30);
assert_eq!(Slots::deposit_held(1.into(), &1), 0);
assert_eq!(Balances::reserved_balance(1), 0);
assert_eq!(Slots::deposit_held(1.into(), &2), 0);
assert_eq!(Balances::reserved_balance(2), 0);
assert_eq!(
TestRegistrar::<Test>::operations(),
vec![(1.into(), 10, true), (1.into(), 30, false),]
);
});
}
#[test]
fn lease_deposit_increase_works() {
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!(Slots::lease_out(1.into(), &1, 4, 1, 1).is_ok());
assert_eq!(Slots::deposit_held(1.into(), &1), 4);
assert_eq!(Balances::reserved_balance(1), 4);
assert!(Slots::lease_out(1.into(), &1, 6, 2, 1).is_ok());
assert_eq!(Slots::deposit_held(1.into(), &1), 6);
assert_eq!(Balances::reserved_balance(1), 6);
run_to_block(29);
assert_eq!(Slots::deposit_held(1.into(), &1), 6);
assert_eq!(Balances::reserved_balance(1), 6);
run_to_block(30);
assert_eq!(Slots::deposit_held(1.into(), &1), 0);
assert_eq!(Balances::reserved_balance(1), 0);
assert_eq!(
TestRegistrar::<Test>::operations(),
vec![(1.into(), 10, true), (1.into(), 30, false),]
);
});
}
#[test]
fn lease_deposit_decrease_works() {
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!(Slots::lease_out(1.into(), &1, 6, 1, 1).is_ok());
assert_eq!(Slots::deposit_held(1.into(), &1), 6);
assert_eq!(Balances::reserved_balance(1), 6);
assert!(Slots::lease_out(1.into(), &1, 4, 2, 1).is_ok());
assert_eq!(Slots::deposit_held(1.into(), &1), 6);
assert_eq!(Balances::reserved_balance(1), 6);
run_to_block(19);
assert_eq!(Slots::deposit_held(1.into(), &1), 6);
assert_eq!(Balances::reserved_balance(1), 6);
run_to_block(20);
assert_eq!(Slots::deposit_held(1.into(), &1), 4);
assert_eq!(Balances::reserved_balance(1), 4);
run_to_block(29);
assert_eq!(Slots::deposit_held(1.into(), &1), 4);
assert_eq!(Balances::reserved_balance(1), 4);
run_to_block(30);
assert_eq!(Slots::deposit_held(1.into(), &1), 0);
assert_eq!(Balances::reserved_balance(1), 0);
assert_eq!(
TestRegistrar::<Test>::operations(),
vec![(1.into(), 10, true), (1.into(), 30, false),]
);
});
}
#[test]
fn clear_all_leases_works() {
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()
));
let max_num = 5u32;
for i in 1u32..=max_num {
let j: u64 = i.into();
assert_ok!(Slots::lease_out(1.into(), &j, j * 10 - 1, i * i, i));
assert_eq!(Slots::deposit_held(1.into(), &j), j * 10 - 1);
assert_eq!(Balances::reserved_balance(j), j * 10 - 1);
}
assert_ok!(Slots::clear_all_leases(RuntimeOrigin::root(), 1.into()));
for i in 1u32..=max_num {
let j: u64 = i.into();
assert_eq!(Slots::deposit_held(1.into(), &j), 0);
assert_eq!(Balances::reserved_balance(j), 0);
}
assert!(Leases::<Test>::get(ParaId::from(1_u32)).is_empty());
});
}
#[test]
fn lease_out_current_lease_period() {
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(
1,
ParaId::from(2_u32),
dummy_head_data(),
dummy_validation_code()
));
run_to_block(20);
let now = System::block_number();
assert_eq!(Slots::lease_period_index(now).unwrap().0, 2);
assert!(Slots::lease_out(1.into(), &1, 1, 1, 1).is_err());
assert_ok!(Slots::lease_out(1.into(), &1, 1, 2, 1));
assert_ok!(Slots::lease_out(2.into(), &1, 1, 3, 1));
assert_eq!(TestRegistrar::<Test>::operations(), vec![(1.into(), 20, true),]);
});
}
#[test]
fn trigger_onboard_works() {
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(
1,
ParaId::from(2_u32),
dummy_head_data(),
dummy_validation_code()
));
assert_ok!(TestRegistrar::<Test>::register(
1,
ParaId::from(3_u32),
dummy_head_data(),
dummy_validation_code()
));
Leases::<Test>::insert(ParaId::from(2_u32), vec![Some((0, 0))]);
Leases::<Test>::insert(ParaId::from(3_u32), vec![None, None, Some((0, 0))]);
assert_noop!(
Slots::trigger_onboard(RuntimeOrigin::signed(1), 1.into()),
Error::<Test>::ParaNotOnboarding
);
assert_ok!(Slots::trigger_onboard(RuntimeOrigin::signed(1), 2.into()));
assert_noop!(
Slots::trigger_onboard(RuntimeOrigin::signed(1), 3.into()),
Error::<Test>::ParaNotOnboarding
);
assert!(Slots::trigger_onboard(RuntimeOrigin::signed(1), 2.into()).is_err());
assert_eq!(TestRegistrar::<Test>::operations(), vec![(2.into(), 1, true),]);
});
}
#[test]
fn lease_period_offset_works() {
new_test_ext().execute_with(|| {
let (lpl, offset) = Slots::lease_period_length();
assert_eq!(offset, 0);
assert_eq!(Slots::lease_period_index(0), Some((0, true)));
assert_eq!(Slots::lease_period_index(1), Some((0, false)));
assert_eq!(Slots::lease_period_index(lpl - 1), Some((0, false)));
assert_eq!(Slots::lease_period_index(lpl), Some((1, true)));
assert_eq!(Slots::lease_period_index(lpl + 1), Some((1, false)));
assert_eq!(Slots::lease_period_index(2 * lpl - 1), Some((1, false)));
assert_eq!(Slots::lease_period_index(2 * lpl), Some((2, true)));
assert_eq!(Slots::lease_period_index(2 * lpl + 1), Some((2, false)));
LeaseOffset::set(5);
let (lpl, offset) = Slots::lease_period_length();
assert_eq!(offset, 5);
assert_eq!(Slots::lease_period_index(0), None);
assert_eq!(Slots::lease_period_index(1), None);
assert_eq!(Slots::lease_period_index(offset), Some((0, true)));
assert_eq!(Slots::lease_period_index(lpl), Some((0, false)));
assert_eq!(Slots::lease_period_index(lpl - 1 + offset), Some((0, false)));
assert_eq!(Slots::lease_period_index(lpl + offset), Some((1, true)));
assert_eq!(Slots::lease_period_index(lpl + offset + 1), Some((1, false)));
assert_eq!(Slots::lease_period_index(2 * lpl - 1 + offset), Some((1, false)));
assert_eq!(Slots::lease_period_index(2 * lpl + offset), Some((2, true)));
assert_eq!(Slots::lease_period_index(2 * lpl + offset + 1), Some((2, false)));
});
}
}
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking {
use super::*;
use frame_support::assert_ok;
use frame_system::RawOrigin;
use polkadot_runtime_parachains::paras;
use sp_runtime::traits::{Bounded, One};
use frame_benchmarking::{account, benchmarks, whitelisted_caller, BenchmarkError};
use crate::slots::Pallet as Slots;
fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
let events = frame_system::Pallet::<T>::events();
let system_event: <T as frame_system::Config>::RuntimeEvent = generic_event.into();
let frame_system::EventRecord { event, .. } = &events[events.len() - 1];
assert_eq!(event, &system_event);
}
fn register_a_parathread<T: Config + paras::Config>(i: u32) -> (ParaId, T::AccountId) {
let para = ParaId::from(i);
let leaser: T::AccountId = account("leaser", i, 0);
T::Currency::make_free_balance_be(&leaser, BalanceOf::<T>::max_value());
let worst_head_data = T::Registrar::worst_head_data();
let worst_validation_code = T::Registrar::worst_validation_code();
assert_ok!(T::Registrar::register(
leaser.clone(),
para,
worst_head_data,
worst_validation_code.clone(),
));
assert_ok!(paras::Pallet::<T>::add_trusted_validation_code(
frame_system::Origin::<T>::Root.into(),
worst_validation_code,
));
T::Registrar::execute_pending_transitions();
(para, leaser)
}
benchmarks! {
where_clause { where T: paras::Config }
force_lease {
frame_system::Pallet::<T>::set_block_number(T::LeaseOffset::get() + One::one());
let para = ParaId::from(1337);
let leaser: T::AccountId = account("leaser", 0, 0);
T::Currency::make_free_balance_be(&leaser, BalanceOf::<T>::max_value());
let amount = T::Currency::minimum_balance();
let period_begin = 69u32.into();
let period_count = 3u32.into();
let origin =
T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
}: _<T::RuntimeOrigin>(origin, para, leaser.clone(), amount, period_begin, period_count)
verify {
assert_last_event::<T>(Event::<T>::Leased {
para_id: para,
leaser, period_begin,
period_count,
extra_reserved: amount,
total_amount: amount,
}.into());
}
manage_lease_period_start {
let c in 0 .. 100;
let t in 0 .. 100;
let period_begin = 1u32.into();
let period_count = 4u32.into();
frame_system::Pallet::<T>::set_block_number(T::LeaseOffset::get() + One::one());
let paras_info = (0..t).map(|i| {
register_a_parathread::<T>(i)
}).collect::<Vec<_>>();
T::Registrar::execute_pending_transitions();
for (para, leaser) in paras_info {
let amount = T::Currency::minimum_balance();
let origin = T::ForceOrigin::try_successful_origin()
.expect("ForceOrigin has no successful origin required for the benchmark");
Slots::<T>::force_lease(origin, para, leaser, amount, period_begin, period_count)?;
}
T::Registrar::execute_pending_transitions();
for i in 200 .. 200 + c {
let (para, leaser) = register_a_parathread::<T>(i);
T::Registrar::make_parachain(para)?;
}
T::Registrar::execute_pending_transitions();
for i in 0 .. t {
assert!(T::Registrar::is_parathread(ParaId::from(i)));
}
for i in 200 .. 200 + c {
assert!(T::Registrar::is_parachain(ParaId::from(i)));
}
}: {
Slots::<T>::manage_lease_period_start(period_begin);
} verify {
T::Registrar::execute_pending_transitions();
for i in 0 .. t {
assert!(T::Registrar::is_parachain(ParaId::from(i)));
}
for i in 200 .. 200 + c {
assert!(T::Registrar::is_parathread(ParaId::from(i)));
}
}
clear_all_leases {
let max_people = 8;
let (para, _) = register_a_parathread::<T>(1);
frame_system::Pallet::<T>::set_block_number(T::LeaseOffset::get() + One::one());
for i in 0 .. max_people {
let leaser = account("lease_deposit", i, 0);
let amount = T::Currency::minimum_balance();
T::Currency::make_free_balance_be(&leaser, BalanceOf::<T>::max_value());
let period_count: LeasePeriodOf<T> = 4u32.into();
let period_begin = period_count * i.into();
let origin = T::ForceOrigin::try_successful_origin()
.expect("ForceOrigin has no successful origin required for the benchmark");
Slots::<T>::force_lease(origin, para, leaser, amount, period_begin, period_count)?;
}
for i in 0 .. max_people {
let leaser = account("lease_deposit", i, 0);
assert_eq!(T::Currency::reserved_balance(&leaser), T::Currency::minimum_balance());
}
let origin =
T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
}: _<T::RuntimeOrigin>(origin, para)
verify {
for i in 0 .. max_people {
let leaser = account("lease_deposit", i, 0);
assert_eq!(T::Currency::reserved_balance(&leaser), 0u32.into());
}
}
trigger_onboard {
let (para, _) = register_a_parathread::<T>(1);
Leases::<T>::insert(para, vec![Some((account::<T::AccountId>("lease_insert", 0, 0), BalanceOf::<T>::default()))]);
assert!(T::Registrar::is_parathread(para));
let caller = whitelisted_caller();
}: _(RawOrigin::Signed(caller), para)
verify {
T::Registrar::execute_pending_transitions();
assert!(T::Registrar::is_parachain(para));
}
impl_benchmark_test_suite!(
Slots,
crate::integration_tests::new_test_ext(),
crate::integration_tests::Test,
);
}
}