#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
mod xp;
mod fungible;
pub mod types;
pub mod weights;
pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use core::fmt::Debug;
use crate::{
types::{
ForceGenesisConfig, GenesisAcc, IdXp, Stepper, Xp, XpEligibility, XpId, XpProgress,
XpState,
},
weights::WeightInfo,
};
use frame_suite::{
accumulators::DiscreteAccumulator,
base::{Asset, Delimited, RuntimeEnum, Time},
xp::{
XpLockListener, XpMutate, XpMutateListener, XpOwner, XpOwnerListener, XpReap,
XpReapListener, XpReserveListener, XpSystem, XpSystemExtensions,
},
};
use frame_support::{
dispatch::{DispatchResult, GetDispatchInfo},
pallet_prelude::*,
traits::{IsSubType, VariantCount, VariantCountOf},
Blake2_128Concat,
};
use frame_system::{
ensure_root,
pallet_prelude::{BlockNumberFor, *},
};
use scale_info::prelude::boxed::Box;
use sp_runtime::{traits::Dispatchable, DispatchError, Vec};
#[pallet::pallet]
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
#[pallet::config]
pub trait Config<I: 'static = ()>: frame_system::Config {
type RuntimeEvent: From<Event<Self, I>>
+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
type RuntimeCall: Parameter
+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin>
+ GetDispatchInfo
+ From<frame_system::Call<Self>>
+ IsSubType<Call<Self, I>>
+ IsType<<Self as frame_system::Config>::RuntimeCall>;
type ReserveReason: RuntimeEnum + Delimited + Copy + VariantCount;
type LockReason: RuntimeEnum + Delimited + Copy + VariantCount;
type Xp: Asset + From<Self::Pulse>;
type Pulse: Time;
type WeightInfo: WeightInfo;
type Extensions: XpSystemExtensions<Via = Pallet<Self, I>>
+ XpOwnerListener
+ XpMutateListener
+ XpReserveListener
+ XpLockListener
+ XpReapListener;
#[pallet::constant]
type EmitEvents: Get<bool> + Clone + Debug;
}
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
pub min_pulse: T::Pulse,
pub init_xp: T::Xp,
pub pulse_factor: Stepper<T, I>,
pub genesis_acc: Vec<GenesisAcc<T::AccountId, XpId<T>>>,
}
impl<T: Config<I>, I: 'static> Default for GenesisConfig<T, I> {
fn default() -> Self {
Self {
min_pulse: 3u32.into(),
init_xp: 1u32.into(),
pulse_factor: Stepper::<T, I>::new(50u8.into(), 10u8.into()).unwrap(),
genesis_acc: Vec::new(),
}
}
}
#[pallet::genesis_build]
impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
fn build(&self) {
MinPulse::<T, I>::put(self.min_pulse);
InitXp::<T, I>::put(self.init_xp);
MinTimeStamp::<T, I>::put(BlockNumberFor::<T>::zero());
PulseFactor::<T, I>::put(&self.pulse_factor);
for acc_struct in &self.genesis_acc {
Pallet::<T, I>::new_xp(&acc_struct.owner, &acc_struct.id)
}
}
}
#[pallet::storage]
pub type XpOf<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, XpId<T>, Xp<T, I>, OptionQuery>;
#[pallet::storage]
pub type XpOwners<T: Config<I>, I: 'static = ()> = StorageNMap<
_,
(
NMapKey<Blake2_128Concat, T::AccountId>,
NMapKey<Blake2_128Concat, XpId<T>>,
),
(),
OptionQuery,
>;
#[pallet::storage]
pub type ReservedXpOf<T: Config<I>, I: 'static = ()> = StorageMap<
_,
Blake2_128Concat,
XpId<T>,
BoundedVec<IdXp<T::ReserveReason, T::Xp>, VariantCountOf<T::ReserveReason>>,
OptionQuery,
>;
#[pallet::storage]
pub type LockedXpOf<T: Config<I>, I: 'static = ()> = StorageMap<
_,
Blake2_128Concat,
XpId<T>,
BoundedVec<IdXp<T::LockReason, T::Xp>, VariantCountOf<T::LockReason>>,
OptionQuery,
>;
#[pallet::storage]
pub type ReapedXp<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, XpId<T>, (), OptionQuery>;
#[pallet::storage]
pub type MinPulse<T: Config<I>, I: 'static = ()> = StorageValue<_, T::Pulse, ValueQuery>;
#[pallet::storage]
pub type InitXp<T: Config<I>, I: 'static = ()> = StorageValue<_, T::Xp, ValueQuery>;
#[pallet::storage]
pub type PulseFactor<T: Config<I>, I: 'static = ()> =
StorageValue<_, Stepper<T, I>, ValueQuery>;
#[pallet::storage]
pub type MinTimeStamp<T: Config<I>, I: 'static = ()> =
StorageValue<_, BlockNumberFor<T>, ValueQuery>;
#[pallet::error]
pub enum Error<T, I = ()> {
XpNotFound,
XpNotDead,
InvalidXpOwner,
AlreadyXpOwner,
CannotReapLockedXp,
XpLockExists,
CannotGenerateXpKey,
CannotTransferXp,
LowPulseThreshold,
InsufficientLiquidXp,
TooManyLocks,
TooManyReserves,
XpLockNotFound,
XpReserveNotFound,
InvalidMinTimeStamp,
LowTimeStamp,
XpNotReaped,
ReputationDeriveOverflowed,
XpCapOverflowed,
XpCapUnderflowed,
XpComputationError,
CannotLockZero,
CannotReserveZero,
XpAlreadyReaped,
InsufficientReserveXp,
XpReserveCapOverflowed,
XpReserveCapUnderflowed,
XpLockCapOverflowed,
XpLockCapUnderflowed,
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config<I>, I: 'static = ()> {
Xp { id: XpId<T>, xp: T::Xp },
XpOwner { id: XpId<T>, owner: T::AccountId },
XpOfOwner {
owner: T::AccountId,
ids: Vec<XpId<T>>,
},
XpEarn { id: XpId<T>, xp: T::Xp },
XpReap { id: XpId<T> },
XpSlash { id: XpId<T>, xp: T::Xp },
XpLock {
of: XpId<T>,
reason: T::LockReason,
xp: T::Xp,
},
XpLockBurn { of: XpId<T>, reason: T::LockReason },
XpLockSlash {
of: XpId<T>,
reason: T::LockReason,
xp: T::Xp,
},
XpReserve {
of: XpId<T>,
reason: T::ReserveReason,
xp: T::Xp,
},
XpReserveSlash {
of: XpId<T>,
reason: T::ReserveReason,
xp: T::Xp,
},
GenesisConfigUpdated(ForceGenesisConfig<T, I>),
}
#[pallet::call]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::call())]
pub fn call(
origin: OriginFor<T>,
xp_id: XpId<T>,
call: Box<<T as Config<I>>::RuntimeCall>,
) -> DispatchResult {
let caller = ensure_signed(origin)?;
Self::is_owner(&caller, &xp_id)?;
call.dispatch(frame_system::RawOrigin::Signed(xp_id).into())
.map(|_| ())
.map_err(|e| e.error)?;
Ok(())
}
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::handover())]
pub fn handover(
origin: OriginFor<T>,
xp_id: XpId<T>,
new_owner: T::AccountId,
) -> DispatchResult {
let caller = ensure_signed(origin)?;
Self::xp_exists(&xp_id)?;
Self::is_owner(&caller, &xp_id)?;
ensure!(
caller != new_owner,
DispatchError::from(Error::<T, I>::AlreadyXpOwner)
);
Self::transfer_owner(&caller, &xp_id, &new_owner)?;
if !T::EmitEvents::get() {
Self::deposit_event(Event::XpOwner {
id: xp_id,
owner: new_owner,
});
}
Ok(())
}
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::dispose())]
pub fn dispose(
origin: OriginFor<T>,
owner: T::AccountId,
xp_id: XpId<T>,
) -> DispatchResult {
let _caller = ensure_signed(origin)?;
Self::xp_exists(&xp_id)?;
Self::is_owner(&owner, &xp_id)?;
Self::try_reap(&xp_id)?;
if !T::EmitEvents::get() {
Self::deposit_event(Event::XpReap { id: xp_id.clone() });
}
Ok(())
}
#[cfg(any(feature = "dev", feature = "runtime-benchmarks"))]
#[pallet::call_index(3)]
#[pallet::weight(T::WeightInfo::inspect_my_xp())]
pub fn inspect_my_xp(origin: OriginFor<T>, xp_id: XpId<T>) -> DispatchResult {
let caller = ensure_signed(origin)?;
Self::xp_exists(&xp_id)?;
Self::is_owner(&caller, &xp_id)?;
let liquid = Self::xp(&xp_id)?;
Self::deposit_event(Event::Xp {
id: xp_id.clone(),
xp: liquid,
});
Ok(())
}
#[cfg(any(feature = "dev", feature = "runtime-benchmarks"))]
#[pallet::call_index(4)]
#[pallet::weight(T::WeightInfo::inspect_xp_keys_of())]
pub fn inspect_xp_keys_of(origin: OriginFor<T>, owner: T::AccountId) -> DispatchResult {
let _caller = ensure_signed(origin)?;
let xp_ids = Self::xp_keys(&owner)?;
Self::deposit_event(Event::XpOfOwner {
owner: owner,
ids: xp_ids,
});
Ok(())
}
#[pallet::call_index(5)]
#[pallet::weight(T::WeightInfo::force_handover())]
pub fn force_handover(
origin: OriginFor<T>,
owner: T::AccountId,
xp_id: XpId<T>,
new_owner: T::AccountId,
) -> DispatchResult {
ensure_root(origin)?;
Self::xp_exists(&xp_id)?;
Self::is_owner(&owner, &xp_id)?;
ensure!(
owner != new_owner,
DispatchError::from(Error::<T, I>::AlreadyXpOwner)
);
Self::transfer_owner(&owner, &xp_id, &new_owner)?;
if !T::EmitEvents::get() {
Self::deposit_event(Event::XpOwner {
id: xp_id.clone(),
owner: new_owner.clone(),
});
}
Ok(())
}
#[pallet::call_index(6)]
#[pallet::weight(
T::WeightInfo::force_update_init_xp()
.max(T::WeightInfo::force_update_min_pulse())
.max(T::WeightInfo::force_update_pulse_factor())
.max(T::WeightInfo::force_update_min_time_stamp())
)]
pub fn force_genesis_config(
origin: OriginFor<T>,
field: ForceGenesisConfig<T, I>,
) -> DispatchResult {
ensure_root(origin)?;
match field {
ForceGenesisConfig::MinPulse(min_pulse) => MinPulse::<T, I>::set(min_pulse),
ForceGenesisConfig::InitXp(init_xp) => InitXp::<T, I>::set(init_xp),
ForceGenesisConfig::PulseFactor {
threshold,
per_count,
} => {
let Some(stepper) = Stepper::<T, I>::new(threshold, per_count) else {
return Err(Error::<T, I>::LowPulseThreshold.into());
};
PulseFactor::<T, I>::set(stepper);
}
ForceGenesisConfig::MinTimeStamp(min_block) => {
let current_block = frame_system::Pallet::<T>::block_number();
if min_block > current_block {
return Err(Error::<T, I>::InvalidMinTimeStamp.into());
};
MinTimeStamp::<T, I>::set(min_block);
}
}
Self::deposit_event(Event::GenesisConfigUpdated(field));
Ok(())
}
}
impl<T: Config<I>, I: 'static> Pallet<T, I> {
pub fn xp_state(key: &XpId<T>) -> Result<XpState<T, I>, DispatchError> {
let xp = Self::get_xp(key)?;
let eligibility = Self::xp_eligibility(key)?;
let required_pulse = MinPulse::<T, I>::get();
let multiplier = match xp.pulse.value < required_pulse {
true => One::one(),
false => xp.pulse.value,
};
Ok(XpState {
liquid: xp.free,
reserved: xp.reserve,
locked: xp.lock,
multiplier,
eligibility,
})
}
pub fn xp(key: &XpId<T>) -> Result<T::Xp, DispatchError> {
Self::xp_exists(key)?;
let liquid = Self::get_liquid_xp(key)?;
Ok(liquid)
}
pub fn xp_keys(owner: &T::AccountId) -> Result<Vec<XpId<T>>, DispatchError> {
let xp_ids = Self::xp_of_owner(owner)?;
Ok(xp_ids)
}
pub fn is_disposable(key: &XpId<T>) -> DispatchResult {
Self::can_reap(key)?;
Ok(())
}
pub fn xp_eligibility(key: &XpId<T>) -> Result<XpEligibility<T, I>, DispatchError> {
let xp = Self::get_xp(key)?;
let current_pulse = xp.pulse.value;
let current_progress = xp.pulse.step;
let required_pulse = MinPulse::<T, I>::get();
let pulse_factor = PulseFactor::<T, I>::get();
if current_pulse >= required_pulse {
return Ok(XpEligibility::Earning);
}
let threshold = pulse_factor.threshold;
let per_action = pulse_factor.per_count;
ensure!(!per_action.is_zero(), Error::<T, I>::XpComputationError);
let zero = T::Pulse::zero();
let one = T::Pulse::one();
let ceil_div_pulse =
|value: T::Pulse, by: T::Pulse| -> Result<T::Pulse, DispatchError> {
ensure!(!by.is_zero(), Error::<T, I>::XpComputationError);
let adjusted = value.checked_sub(&one).unwrap_or(zero);
adjusted
.checked_div(&by)
.and_then(|v| v.checked_add(&one))
.ok_or(Error::<T, I>::XpComputationError.into())
};
let remaining_pulses = required_pulse
.checked_sub(¤t_pulse)
.ok_or(Error::<T, I>::XpComputationError)?;
let actions_per_pulse = ceil_div_pulse(threshold, per_action)?;
let remaining_progress = threshold.checked_sub(¤t_progress).unwrap_or(zero);
let actions_to_next_pulse = ceil_div_pulse(remaining_progress, per_action)?;
let extra_pulses = remaining_pulses.checked_sub(&one).unwrap_or(zero);
let extra_actions = extra_pulses
.checked_mul(&actions_per_pulse)
.ok_or(Error::<T, I>::XpComputationError)?;
let total_actions = actions_to_next_pulse
.checked_add(&extra_actions)
.ok_or(Error::<T, I>::XpComputationError)?;
Ok(XpEligibility::Progressing(total_actions))
}
pub fn xp_multiplier(key: &XpId<T>) -> Result<Option<T::Pulse>, DispatchError> {
let xp = Self::get_xp(key)?;
let required_pulse = MinPulse::<T, I>::get();
let multiplier = match xp.pulse.value < required_pulse {
true => return Ok(None),
false => xp.pulse.value,
};
let current_block = frame_system::Pallet::<T>::block_number();
if xp.timestamp >= current_block {
return Ok(None);
}
Ok(Some(multiplier))
}
pub fn xp_progress(key: &XpId<T>) -> Result<XpProgress<T, I>, DispatchError> {
let xp = Self::get_xp(key)?;
let config = PulseFactor::<T, I>::get();
Ok(XpProgress {
level: xp.pulse.value,
progress: xp.pulse.step,
threshold: config.threshold,
per_action: config.per_count,
})
}
pub fn earn_preview(key: &XpId<T>, raw: T::Xp) -> Result<XpState<T, I>, DispatchError> {
let xp = Self::get_xp(key)?;
let reward = Self::quote_earn_xp(key, raw)?;
let new_free = xp
.free
.checked_add(&reward)
.ok_or(Error::<T, I>::XpCapOverflowed)?;
let mut next_pulse = xp.pulse.clone();
let config = PulseFactor::<T, I>::get();
<Pallet<T, I> as DiscreteAccumulator>::increment(&mut next_pulse, &config);
let next_key = key; let next_eligibility = match next_pulse.value >= MinPulse::<T, I>::get() {
true => XpEligibility::Earning,
false => Self::xp_eligibility(next_key)?,
};
let next_multiplier = match next_eligibility {
XpEligibility::Earning => next_pulse.value,
_ => T::Pulse::one(),
};
Ok(XpState {
liquid: new_free,
reserved: xp.reserve,
locked: xp.lock,
multiplier: next_multiplier,
eligibility: next_eligibility,
})
}
pub fn xp_last_earn(key: &XpId<T>) -> Result<BlockNumberFor<T>, DispatchError> {
let xp = Self::get_xp(key)?;
Ok(xp.timestamp)
}
}
}
#[cfg(test)]
mod ext_tests {
use crate::{
mock::*,
types::{ForceGenesisConfig, IdXp, XpEligibility},
};
use frame_suite::xp::{XpLock, XpMutate, XpOwner, XpReserve, XpSystem};
use frame_support::{assert_err, assert_ok, traits::VariantCountOf};
use sp_runtime::{BoundedVec, DispatchError};
#[test]
fn pulse_factor_instance_check() {
xp_test_ext().execute_with(|| {
let threshold_1 = 100;
let per_count_1 = 10;
let threshold_2 = 1000;
let per_count_2 = 100;
let old_pulsefactor_instance1 = PulseFactor::get();
let old_pulsefactor_instance2 = PulseFactor2::get();
assert_eq!(
old_pulsefactor_instance1,
Stepper::new(50u8.into(), 10u8.into()).unwrap(),
);
assert_eq!(
old_pulsefactor_instance2,
Stepper2::new(20u8.into(), 6u8.into()).unwrap(),
);
let stepper_1 = Stepper::new(threshold_1, per_count_1).unwrap();
let stepper_2 = Stepper2::new(threshold_2, per_count_2).unwrap();
PulseFactor::set(stepper_1.clone());
PulseFactor2::set(stepper_2.clone());
assert_eq!(PulseFactor::get(), stepper_1);
assert_eq!(PulseFactor2::get(), stepper_2);
});
}
#[test]
fn min_pulse_instance_check() {
xp_test_ext().execute_with(|| {
let min_pulse_1 = 10;
let min_pulse_2 = 15;
let old_minpulse_instance1 = MinPulse::get();
let old_min_pulse_instance2 = MinPulse2::get();
assert_eq!(old_minpulse_instance1, 1);
assert_eq!(old_min_pulse_instance2, 5);
MinPulse::set(min_pulse_1);
MinPulse2::set(min_pulse_2);
assert_eq!(MinPulse::get(), 10);
assert_eq!(MinPulse2::get(), 15);
});
}
#[test]
fn init_xp_instance_check() {
xp_test_ext().execute_with(|| {
let init_xp_1 = 5;
let init_xp_2 = 3;
let old_initxp_instance1 = InitXp::get();
let old_initxp_instance2 = InitXp2::get();
assert_eq!(old_initxp_instance1, 10);
assert_eq!(old_initxp_instance2, 1);
InitXp::set(init_xp_1);
InitXp2::set(init_xp_2);
assert_eq!(InitXp::get(), 5);
assert_eq!(InitXp2::get(), 3);
});
}
#[test]
fn min_time_stamp_instance_check() {
xp_test_ext().execute_with(|| {
let min_time_stamp_1 = 5;
let min_time_stamp_2 = 10;
let old_mintimestamp_instance1 = MinTimeStamp::get();
let old_mintimestamp_instance2 = MinTimeStamp2::get();
assert_eq!(old_mintimestamp_instance1, 0);
assert_eq!(old_mintimestamp_instance2, 0);
MinTimeStamp::set(min_time_stamp_1);
MinTimeStamp2::set(min_time_stamp_2);
assert_eq!(MinTimeStamp::get(), 5);
assert_eq!(MinTimeStamp2::get(), 10);
});
}
#[test]
fn xp_of_instance_check() {
xp_test_ext().execute_with(|| {
let xp_1 = MockXp::default();
XpOf::insert(XP_ALPHA, xp_1);
let xp_2 = MockXp2::default();
XpOf2::insert(XP_BETA, xp_2);
assert!(XpOf::contains_key(XP_ALPHA));
assert!(XpOf2::contains_key(XP_BETA));
assert!(!XpOf::contains_key(XP_BETA));
assert!(!XpOf2::contains_key(XP_ALPHA));
});
}
#[test]
fn xp_owners_instance_check() {
xp_test_ext().execute_with(|| {
XpOwners::insert((ALICE, XP_ALPHA), ());
XpOwners2::insert((BOB, XP_BETA), ());
assert!(XpOwners::contains_key((ALICE, XP_ALPHA)));
assert!(XpOwners2::contains_key((BOB, XP_BETA)));
assert!(!XpOwners::contains_key((BOB, XP_BETA)));
assert!(!XpOwners2::contains_key((ALICE, XP_ALPHA)));
});
}
#[test]
fn reserved_xp_of_instance_check() {
xp_test_ext().execute_with(|| {
let reserve_1 = IdXp::new(STAKING, DEFAULT_POINTS);
ReservedXpOf::try_mutate(XP_ALPHA, |value| {
let vec = value.get_or_insert_with(|| {
BoundedVec::<IdXp<Reason, u64>, VariantCountOf<Reason>>::default()
});
vec.try_push(reserve_1)
})
.unwrap();
let reserve_2 = IdXp::new(GOVERNANCE, DEFAULT_POINTS);
ReservedXpOf2::try_mutate(XP_BETA, |value| {
let vec = value.get_or_insert_with(|| {
BoundedVec::<IdXp<Reason, u64>, VariantCountOf<Reason>>::default()
});
vec.try_push(reserve_2)
})
.unwrap();
assert!(ReservedXpOf::contains_key(XP_ALPHA));
assert!(ReservedXpOf2::contains_key(XP_BETA));
assert!(!ReservedXpOf::contains_key(XP_BETA));
assert!(!ReservedXpOf2::contains_key(XP_ALPHA));
});
}
#[test]
fn locked_xp_of_instance_check() {
xp_test_ext().execute_with(|| {
let lock_1 = IdXp::new(STAKING, DEFAULT_POINTS);
LockedXpOf::try_mutate(XP_ALPHA, |value| {
let vec = value.get_or_insert_with(|| {
BoundedVec::<IdXp<Reason, u64>, VariantCountOf<Reason>>::default()
});
vec.try_push(lock_1)
})
.unwrap();
let lock_2 = IdXp::new(GOVERNANCE, DEFAULT_POINTS);
LockedXpOf2::try_mutate(XP_BETA, |value| {
let vec = value.get_or_insert_with(|| {
BoundedVec::<IdXp<Reason, u64>, VariantCountOf<Reason>>::default()
});
vec.try_push(lock_2)
})
.unwrap();
assert!(LockedXpOf::contains_key(XP_ALPHA));
assert!(LockedXpOf2::contains_key(XP_BETA));
assert!(!LockedXpOf::contains_key(XP_BETA));
assert!(!LockedXpOf2::contains_key(XP_ALPHA));
});
}
#[test]
fn reaped_xp_instance_check() {
xp_test_ext().execute_with(|| {
ReapedXp::insert(XP_ALPHA, ());
ReapedXp2::insert(XP_BETA, ());
assert!(ReapedXp::contains_key(XP_ALPHA));
assert!(ReapedXp2::contains_key(XP_BETA));
assert!(!ReapedXp::contains_key(XP_BETA));
assert!(!ReapedXp2::contains_key(XP_ALPHA));
});
}
#[test]
fn xp_eligibility_success_already_reputed() {
xp_test_ext().execute_with(|| {
Pallet::new_xp(&ALICE, &XP_ALPHA);
let xp = Pallet::get_xp(&XP_ALPHA).unwrap();
let min_pulse = MinPulse::get();
assert!(xp.pulse.value < min_pulse);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
let xp = Pallet::get_xp(&XP_ALPHA).unwrap();
let min_pulse = MinPulse::get();
assert!(xp.pulse.value >= min_pulse);
let status = Pallet::xp_eligibility(&XP_ALPHA).unwrap();
assert_eq!(status, XpEligibility::Earning);
})
}
#[test]
fn xp_eligibility_success_edge_cases() {
xp_test_ext().execute_with(|| {
Pallet::new_xp(&ALICE, &XP_ALPHA);
let stepper = Stepper::new(20, 6).unwrap();
PulseFactor::put(stepper);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
let xp = Pallet::get_xp(&XP_ALPHA).unwrap();
let min_pulse = MinPulse::get();
assert!(xp.pulse.value < min_pulse);
assert_eq!(xp.pulse.step, 12);
let status = Pallet::xp_eligibility(&XP_ALPHA).unwrap();
assert_eq!(status, XpEligibility::Progressing(2));
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
let xp = Pallet::get_xp(&XP_ALPHA).unwrap();
let min_pulse = MinPulse::get();
assert!(xp.pulse.value >= min_pulse);
assert_eq!(xp.pulse.step, 4);
let status = Pallet::xp_eligibility(&XP_ALPHA).unwrap();
assert_eq!(status, XpEligibility::Earning);
})
}
#[test]
fn xp_eligibility_success_calls_to_reach_reputed() {
xp_test_ext().execute_with(|| {
Pallet::new_xp(&ALICE, &XP_ALPHA);
let xp = Pallet::get_xp(&XP_ALPHA).unwrap();
let min_pulse = MinPulse::get();
assert!(xp.pulse.value < min_pulse);
let status = Pallet::xp_eligibility(&XP_ALPHA).unwrap();
assert_eq!(status, XpEligibility::Progressing(5));
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
let status = Pallet::xp_eligibility(&XP_ALPHA).unwrap();
assert_eq!(status, XpEligibility::Progressing(4));
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
let status = Pallet::xp_eligibility(&XP_ALPHA).unwrap();
assert_eq!(status, XpEligibility::Progressing(2));
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
let xp = Pallet::get_xp(&XP_ALPHA).unwrap();
let min_pulse = MinPulse::get();
assert!(xp.pulse.value >= min_pulse);
let status = Pallet::xp_eligibility(&XP_ALPHA).unwrap();
assert_eq!(status, XpEligibility::Earning);
})
}
#[test]
fn xp_multiplier_less_than_min_pulse() {
xp_test_ext().execute_with(|| {
Pallet::new_xp(&ALICE, &XP_ALPHA);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
let xp = Pallet::get_xp(&XP_ALPHA).unwrap();
let min_pulse = MinPulse::get();
assert!(xp.pulse.value < min_pulse);
let current_multiplier = Pallet::xp_multiplier(&XP_ALPHA).unwrap();
assert!(current_multiplier.is_none());
})
}
#[test]
fn xp_multiplier_same_block_protection() {
xp_test_ext().execute_with(|| {
System::set_block_number(1);
Pallet::new_xp(&ALICE, &XP_ALPHA);
System::set_block_number(12);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(13);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(14);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(15);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(16);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(17);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
let xp = Pallet::get_xp(&XP_ALPHA).unwrap();
let min_pulse = MinPulse::get();
assert!(xp.pulse.value == min_pulse);
let current_multiplier = Pallet::xp_multiplier(&XP_ALPHA).unwrap();
assert!(current_multiplier.is_none());
})
}
#[test]
fn xp_multiplier_success() {
xp_test_ext().execute_with(|| {
System::set_block_number(1);
Pallet::new_xp(&ALICE, &XP_ALPHA);
System::set_block_number(12);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(13);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(14);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(15);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(16);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
let xp = Pallet::get_xp(&XP_ALPHA).unwrap();
let min_pulse = MinPulse::get();
assert!(xp.pulse.value == min_pulse);
System::set_block_number(17);
let current_multiplier = Pallet::xp_multiplier(&XP_ALPHA).unwrap();
assert_eq!(current_multiplier, Some(1));
Pallet::set_lock(&XP_ALPHA, &STAKING, DEFAULT_POINTS).unwrap();
System::set_block_number(20);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(21);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(22);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(23);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(24);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
let xp = Pallet::get_xp(&XP_ALPHA).unwrap();
let min_pulse = MinPulse::get();
assert!(xp.pulse.value > min_pulse);
dbg!(xp.pulse.value);
System::set_block_number(25);
let current_multiplier = Pallet::xp_multiplier(&XP_ALPHA).unwrap();
assert_eq!(current_multiplier, Some(2));
})
}
#[test]
fn xp_state_success() {
xp_test_ext().execute_with(|| {
System::set_block_number(5);
Pallet::new_xp(&ALICE, &XP_ALPHA);
System::set_block_number(20);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(21);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(22);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
let xp_state = Pallet::xp_state(&XP_ALPHA).unwrap();
assert_eq!(xp_state.liquid, 10);
assert_eq!(xp_state.reserved, 0);
assert_eq!(xp_state.locked, 0);
assert_eq!(xp_state.multiplier, 1);
assert_eq!(xp_state.eligibility, XpEligibility::Progressing(1));
System::set_block_number(23);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
Pallet::set_lock(&XP_ALPHA, &STAKING, DEFAULT_POINTS).unwrap();
Pallet::set_reserve(&XP_ALPHA, &STAKING, 25).unwrap();
System::set_block_number(24);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
let xp_state = Pallet::xp_state(&XP_ALPHA).unwrap();
assert_eq!(xp_state.liquid, 20);
assert_eq!(xp_state.reserved, 25);
assert_eq!(xp_state.locked, DEFAULT_POINTS);
assert_eq!(xp_state.multiplier, 1);
assert_eq!(xp_state.eligibility, XpEligibility::Earning);
})
}
#[test]
fn fetch_pulse_progress() {
xp_test_ext().execute_with(|| {
System::set_block_number(5);
Pallet::new_xp(&ALICE, &XP_ALPHA);
System::set_block_number(20);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(21);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
let pulse_progress = Pallet::xp_progress(&XP_ALPHA).unwrap();
assert_eq!(pulse_progress.progress, 20);
assert_eq!(pulse_progress.level, 0);
assert_eq!(pulse_progress.threshold, 50);
assert_eq!(pulse_progress.per_action, 10);
System::set_block_number(22);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(23);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(24);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
let pulse_progress = Pallet::xp_progress(&XP_ALPHA).unwrap();
assert_eq!(pulse_progress.progress, 0);
assert_eq!(pulse_progress.level, 1);
assert_eq!(pulse_progress.threshold, 50);
assert_eq!(pulse_progress.per_action, 10);
})
}
#[test]
fn earn_preview_below_min_pulse_returns_zero_reward_and_required_steps() {
xp_test_ext().execute_with(|| {
System::set_block_number(10);
Pallet::new_xp(&ALICE, &XP_ALPHA);
let earn_preview = Pallet::earn_preview(&XP_ALPHA, DEFAULT_POINTS).unwrap();
assert_eq!(earn_preview.liquid, DEFAULT_POINTS);
assert_eq!(earn_preview.reserved, 0);
assert_eq!(earn_preview.locked, 0);
assert_eq!(earn_preview.multiplier, 1);
assert_eq!(earn_preview.eligibility, XpEligibility::Progressing(5));
})
}
#[test]
fn earn_preview_will_repute_progress() {
xp_test_ext().execute_with(|| {
System::set_block_number(10);
Pallet::new_xp(&ALICE, &XP_ALPHA);
System::set_block_number(22);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(23);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(24);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(25);
let earn_preview = Pallet::earn_preview(&XP_ALPHA, DEFAULT_POINTS).unwrap();
assert_eq!(earn_preview.liquid, DEFAULT_POINTS);
assert_eq!(earn_preview.reserved, 0);
assert_eq!(earn_preview.locked, 0);
assert_eq!(earn_preview.multiplier, 1);
assert_eq!(earn_preview.eligibility, XpEligibility::Progressing(2));
System::set_block_number(25);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(26);
let earn_preview = Pallet::earn_preview(&XP_ALPHA, DEFAULT_POINTS).unwrap();
assert_eq!(earn_preview.liquid, DEFAULT_POINTS);
assert_eq!(earn_preview.reserved, 0);
assert_eq!(earn_preview.locked, 0);
assert_eq!(earn_preview.multiplier, 1);
assert_eq!(earn_preview.eligibility, XpEligibility::Earning);
})
}
#[test]
fn earn_preview_above_min_pulse() {
xp_test_ext().execute_with(|| {
System::set_block_number(10);
Pallet::new_xp(&ALICE, &XP_ALPHA);
System::set_block_number(22);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(23);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(24);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(25);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(26);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(27);
let earn_preview = Pallet::earn_preview(&XP_ALPHA, DEFAULT_POINTS).unwrap();
assert_eq!(earn_preview.liquid, 20);
assert_eq!(earn_preview.reserved, 0);
assert_eq!(earn_preview.locked, 0);
assert_eq!(earn_preview.multiplier, 1);
assert_eq!(earn_preview.eligibility, XpEligibility::Earning);
})
}
#[test]
fn earn_preview_multiplier_progress_without_lock() {
xp_test_ext().execute_with(|| {
System::set_block_number(10);
Pallet::new_xp(&ALICE, &XP_ALPHA);
System::set_block_number(22);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(23);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(24);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(25);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(26);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(27);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
let earn_preview = Pallet::earn_preview(&XP_ALPHA, DEFAULT_POINTS).unwrap();
assert_eq!(earn_preview.liquid, 30);
assert_eq!(earn_preview.reserved, 0);
assert_eq!(earn_preview.locked, 0);
assert_eq!(earn_preview.multiplier, 1);
assert_eq!(earn_preview.eligibility, XpEligibility::Earning);
for n in 28..48 {
System::set_block_number(n);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
}
let earn_preview = Pallet::earn_preview(&XP_ALPHA, DEFAULT_POINTS).unwrap();
assert_eq!(earn_preview.liquid, 230);
assert_eq!(earn_preview.reserved, 0);
assert_eq!(earn_preview.locked, 0);
assert_eq!(earn_preview.multiplier, 1);
assert_eq!(earn_preview.eligibility, XpEligibility::Earning);
})
}
#[test]
fn earn_preview_with_lock() {
xp_test_ext().execute_with(|| {
System::set_block_number(10);
Pallet::new_xp(&ALICE, &XP_ALPHA);
System::set_block_number(22);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(23);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(24);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(25);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(26);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
Pallet::set_lock(&XP_ALPHA, &STAKING, DEFAULT_POINTS).unwrap();
System::set_block_number(27);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(28);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(29);
let earn_preview = Pallet::earn_preview(&XP_ALPHA, DEFAULT_POINTS).unwrap();
assert_eq!(earn_preview.liquid, 40);
assert_eq!(earn_preview.reserved, 0);
assert_eq!(earn_preview.locked, 10);
assert_eq!(earn_preview.multiplier, 1);
assert_eq!(earn_preview.eligibility, XpEligibility::Earning);
System::set_block_number(30);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(31);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(32);
let earn_preview = Pallet::earn_preview(&XP_ALPHA, DEFAULT_POINTS).unwrap();
assert_eq!(earn_preview.liquid, 60);
assert_eq!(earn_preview.reserved, 0);
assert_eq!(earn_preview.locked, 10);
assert_eq!(earn_preview.multiplier, 2);
assert_eq!(earn_preview.eligibility, XpEligibility::Earning);
})
}
#[test]
fn earn_preview_with_lock_multiplier_progress() {
xp_test_ext().execute_with(|| {
System::set_block_number(10);
Pallet::new_xp(&ALICE, &XP_ALPHA);
System::set_block_number(22);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(23);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(24);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(25);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(26);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
Pallet::set_lock(&XP_ALPHA, &STAKING, DEFAULT_POINTS).unwrap();
System::set_block_number(27);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(28);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(29);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(30);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(31);
let earn_preview = Pallet::earn_preview(&XP_ALPHA, DEFAULT_POINTS).unwrap();
assert_eq!(earn_preview.liquid, 60);
assert_eq!(earn_preview.reserved, 0);
assert_eq!(earn_preview.locked, 10);
assert_eq!(earn_preview.multiplier, 2);
assert_eq!(earn_preview.eligibility, XpEligibility::Earning);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
for n in 32..42 {
System::set_block_number(n);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
}
let earn_preview = Pallet::earn_preview(&XP_ALPHA, DEFAULT_POINTS).unwrap();
assert_eq!(earn_preview.liquid, 320);
assert_eq!(earn_preview.reserved, 0);
assert_eq!(earn_preview.locked, 10);
assert_eq!(earn_preview.multiplier, 4);
assert_eq!(earn_preview.eligibility, XpEligibility::Earning);
})
}
#[test]
fn earn_preview_with_same_block_protection() {
xp_test_ext().execute_with(|| {
System::set_block_number(10);
Pallet::new_xp(&ALICE, &XP_ALPHA);
System::set_block_number(22);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(23);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(24);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(25);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(26);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
Pallet::set_lock(&XP_ALPHA, &STAKING, DEFAULT_POINTS).unwrap();
System::set_block_number(27);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(28);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(29);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(30);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
System::set_block_number(31);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
let earn_preview = Pallet::earn_preview(&XP_ALPHA, DEFAULT_POINTS).unwrap();
assert_eq!(earn_preview.liquid, 70);
assert_eq!(earn_preview.reserved, 0);
assert_eq!(earn_preview.locked, 10);
assert_eq!(earn_preview.multiplier, 2);
assert_eq!(earn_preview.eligibility, XpEligibility::Earning);
})
}
#[test]
fn earn_preview_matches_earn_xp_actual_reward() {
xp_test_ext().execute_with(|| {
System::set_block_number(10);
Pallet::new_xp(&ALICE, &XP_ALPHA);
Pallet::set_lock(&XP_ALPHA, &STAKING, DEFAULT_POINTS).unwrap();
for n in 20..40 {
System::set_block_number(n);
Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
}
System::set_block_number(41);
let earn_preview = Pallet::earn_preview(&XP_ALPHA, DEFAULT_POINTS).unwrap();
assert_eq!(earn_preview.liquid, 350);
assert_eq!(earn_preview.reserved, 0);
assert_eq!(earn_preview.locked, 10);
assert_eq!(earn_preview.multiplier, 4);
assert_eq!(earn_preview.eligibility, XpEligibility::Earning);
let xp = Pallet::get_xp(&XP_ALPHA).unwrap();
let free_before = xp.free;
let actual_earn = Pallet::earn_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
let xp = Pallet::get_xp(&XP_ALPHA).unwrap();
let free_after = xp.free;
let diff = free_after - free_before;
assert_eq!(free_after, earn_preview.liquid);
assert_eq!(diff, actual_earn);
})
}
#[test]
fn earn_preview_err_xp_not_found() {
xp_test_ext().execute_with(|| {
Pallet::new_xp(&ALICE, &XP_ALPHA);
assert_err!(
Pallet::earn_preview(&XP_BETA, DEFAULT_POINTS),
Error::XpNotFound
);
})
}
#[cfg(feature = "dev")]
#[test]
fn inspect_my_xp_success() {
xp_test_ext().execute_with(|| {
Pallet::new_xp(&ALICE, &XP_ALPHA);
System::set_block_number(1);
assert_ok!(Xp::inspect_my_xp(RuntimeOrigin::signed(ALICE), XP_ALPHA));
System::assert_last_event(
Event::Xp {
id: XP_ALPHA,
xp: InitXp::get(),
}
.into(),
);
});
}
#[cfg(feature = "dev")]
#[test]
fn inspect_my_xp_fail_xp_not_found() {
xp_test_ext().execute_with(|| {
Pallet::new_xp(&ALICE, &XP_ALPHA);
assert_err!(
Xp::inspect_my_xp(RuntimeOrigin::signed(ALICE), XP_BETA),
Error::XpNotFound
);
});
}
#[cfg(feature = "dev")]
#[test]
fn inspect_my_xp_fail_not_signed() {
xp_test_ext().execute_with(|| {
assert_err!(
Xp::inspect_my_xp(RuntimeOrigin::root(), XP_ALPHA),
DispatchError::BadOrigin
);
});
}
#[cfg(feature = "dev")]
#[test]
fn inspect_my_xp_fail_invalid_owner() {
xp_test_ext().execute_with(|| {
Pallet::new_xp(&ALICE, &XP_ALPHA);
assert_err!(
Xp::inspect_my_xp(RuntimeOrigin::signed(BOB), XP_ALPHA),
Error::InvalidXpOwner
);
});
}
#[test]
fn handover_success() {
xp_test_ext().execute_with(|| {
Pallet::new_xp(&ALICE, &XP_ALPHA);
System::set_block_number(1);
assert_ok!(Xp::handover(RuntimeOrigin::signed(ALICE), XP_ALPHA, BOB));
assert_ok!(Pallet::is_owner(&BOB, &XP_ALPHA));
System::assert_last_event(
Event::XpOwner {
id: XP_ALPHA,
owner: BOB,
}
.into(),
);
});
}
#[test]
fn handover_fail_xp_not_found() {
xp_test_ext().execute_with(|| {
assert_err!(
Xp::handover(RuntimeOrigin::signed(ALICE), XP_ALPHA, BOB),
Error::XpNotFound
);
});
}
#[test]
fn handover_fail_not_signed() {
xp_test_ext().execute_with(|| {
assert_err!(
Xp::handover(RuntimeOrigin::root(), XP_ALPHA, BOB),
DispatchError::BadOrigin
);
});
}
#[test]
fn handover_fail_invalid_owner() {
xp_test_ext().execute_with(|| {
Pallet::new_xp(&ALICE, &XP_ALPHA);
assert_err!(
Xp::handover(RuntimeOrigin::signed(CHARLIE), XP_ALPHA, BOB),
Error::InvalidXpOwner
);
});
}
#[test]
fn handover_fail_already_owner() {
xp_test_ext().execute_with(|| {
Pallet::new_xp(&ALICE, &XP_ALPHA);
assert_err!(
Xp::handover(RuntimeOrigin::signed(ALICE), XP_ALPHA, ALICE),
Error::AlreadyXpOwner
);
});
}
#[test]
fn dispose_success() {
xp_test_ext().execute_with(|| {
MinTimeStamp::set(3);
System::set_block_number(1);
Pallet::new_xp(&ALICE, &XP_ALPHA);
Pallet::set_xp(&XP_ALPHA, 0).unwrap();
assert_ok!(Pallet::xp_exists(&XP_ALPHA));
System::set_block_number(2);
assert_ok!(Xp::dispose(RuntimeOrigin::signed(CHARLIE), ALICE, XP_ALPHA));
assert_err!(Pallet::xp_exists(&XP_ALPHA), Error::XpNotFound);
});
}
#[test]
fn dispose_fail_xp_not_found() {
xp_test_ext().execute_with(|| {
Pallet::new_xp(&ALICE, &XP_ALPHA);
assert_err!(
Xp::dispose(RuntimeOrigin::signed(CHARLIE), ALICE, XP_BETA),
Error::XpNotFound
);
});
}
#[test]
fn dispose_fail_not_owner() {
xp_test_ext().execute_with(|| {
Pallet::new_xp(&ALICE, &XP_ALPHA);
assert_err!(
Xp::dispose(RuntimeOrigin::signed(CHARLIE), BOB, XP_ALPHA),
Error::InvalidXpOwner
);
});
}
#[test]
fn dispose_fail_xp_not_dead() {
xp_test_ext().execute_with(|| {
System::set_block_number(1);
System::set_block_number(2);
System::set_block_number(3);
Pallet::new_xp(&ALICE, &XP_ALPHA);
Pallet::set_xp(&XP_ALPHA, DEFAULT_POINTS).unwrap();
assert_err!(
Xp::dispose(RuntimeOrigin::signed(CHARLIE), ALICE, XP_ALPHA),
Error::XpNotDead
);
});
}
#[test]
fn dispose_fail_locked_xp() {
xp_test_ext().execute_with(|| {
MinTimeStamp::set(3);
Pallet::new_xp(&ALICE, &XP_ALPHA);
Pallet::set_lock(&XP_ALPHA, &STAKING, DEFAULT_POINTS).unwrap();
System::set_block_number(2);
assert_err!(
Xp::dispose(RuntimeOrigin::signed(CHARLIE), ALICE, XP_ALPHA),
Error::CannotReapLockedXp
);
});
}
#[test]
fn force_handover_success() {
xp_test_ext().execute_with(|| {
Pallet::new_xp(&ALICE, &XP_ALPHA);
System::set_block_number(1);
assert_ok!(Xp::force_handover(
RuntimeOrigin::root(),
ALICE,
XP_ALPHA,
BOB
));
assert_ok!(Pallet::is_owner(&BOB, &XP_ALPHA));
System::assert_last_event(
Event::XpOwner {
id: XP_ALPHA,
owner: BOB,
}
.into(),
);
});
}
#[test]
fn force_handover_fail_xp_not_found() {
xp_test_ext().execute_with(|| {
Pallet::new_xp(&ALICE, &XP_ALPHA);
assert_err!(
Xp::force_handover(RuntimeOrigin::root(), ALICE, XP_BETA, BOB),
Error::XpNotFound
);
});
}
#[test]
fn force_handover_fail_not_root() {
xp_test_ext().execute_with(|| {
Pallet::new_xp(&ALICE, &XP_ALPHA);
assert_err!(
Xp::force_handover(RuntimeOrigin::signed(CHARLIE), ALICE, XP_ALPHA, BOB),
DispatchError::BadOrigin
);
});
}
#[test]
fn force_handover_fail_invalid_owner() {
xp_test_ext().execute_with(|| {
Pallet::new_xp(&ALICE, &XP_ALPHA);
assert_err!(
Xp::force_handover(RuntimeOrigin::root(), CHARLIE, XP_ALPHA, BOB),
Error::InvalidXpOwner
);
});
}
#[test]
fn force_handover_fail_already_owner() {
xp_test_ext().execute_with(|| {
Pallet::new_xp(&ALICE, &XP_ALPHA);
assert_err!(
Xp::force_handover(RuntimeOrigin::root(), ALICE, XP_ALPHA, ALICE),
Error::AlreadyXpOwner
);
});
}
#[cfg(feature = "dev")]
#[test]
fn inspect_xp_keys_of_success() {
xp_test_ext().execute_with(|| {
Pallet::new_xp(&ALICE, &XP_ALPHA);
Pallet::new_xp(&ALICE, &XP_BETA);
System::set_block_number(1);
assert_ok!(Xp::inspect_xp_keys_of(RuntimeOrigin::signed(ALICE), ALICE));
System::assert_last_event(
Event::XpOfOwner {
owner: ALICE,
ids: vec![XP_ALPHA, XP_BETA],
}
.into(),
);
});
}
#[cfg(feature = "dev")]
#[test]
fn inspect_xp_keys_of_fail_not_signed() {
xp_test_ext().execute_with(|| {
Pallet::new_xp(&ALICE, &XP_ALPHA);
Pallet::new_xp(&ALICE, &XP_BETA);
assert_err!(
Xp::inspect_xp_keys_of(RuntimeOrigin::root(), ALICE),
DispatchError::BadOrigin
);
});
}
#[test]
fn force_genesis_config_min_pulse_success() {
xp_test_ext().execute_with(|| {
System::set_block_number(1);
let new_min_pulse: u32 = 5;
assert_ok!(Xp::force_genesis_config(
RuntimeOrigin::root(),
ForceGenesisConfig::MinPulse(new_min_pulse)
));
assert_eq!(MinPulse::get(), new_min_pulse);
System::assert_last_event(
Event::GenesisConfigUpdated(ForceGenesisConfig::MinPulse(new_min_pulse)).into(),
);
});
}
#[test]
fn force_genesis_config_min_pulse_fail_not_root() {
xp_test_ext().execute_with(|| {
let min_pulse = 5;
assert_err!(
Xp::force_genesis_config(
RuntimeOrigin::signed(CHARLIE),
ForceGenesisConfig::MinPulse(min_pulse)
),
DispatchError::BadOrigin
);
assert_eq!(MinPulse::get(), 1);
});
}
#[test]
fn force_genesis_config_init_xp_success() {
xp_test_ext().execute_with(|| {
System::set_block_number(1);
let new_init_xp = 50;
assert_ok!(Xp::force_genesis_config(
RuntimeOrigin::root(),
ForceGenesisConfig::InitXp(new_init_xp)
));
assert_eq!(InitXp::get(), new_init_xp);
System::assert_last_event(
Event::GenesisConfigUpdated(ForceGenesisConfig::InitXp(new_init_xp)).into(),
);
});
}
#[test]
fn force_genesis_config_init_xp_fail_not_root() {
xp_test_ext().execute_with(|| {
let new_init_xp = 50;
assert_err!(
Xp::force_genesis_config(
RuntimeOrigin::signed(CHARLIE),
ForceGenesisConfig::InitXp(new_init_xp)
),
DispatchError::BadOrigin
);
assert_eq!(InitXp::get(), 10);
});
}
#[test]
fn force_genesis_config_min_time_stamp_success() {
xp_test_ext().execute_with(|| {
System::set_block_number(1);
let new_min_time_stamp = 4;
System::set_block_number(5);
assert_ok!(Xp::force_genesis_config(
RuntimeOrigin::root(),
ForceGenesisConfig::MinTimeStamp(new_min_time_stamp)
));
assert_eq!(MinTimeStamp::get(), new_min_time_stamp);
System::assert_last_event(
Event::GenesisConfigUpdated(ForceGenesisConfig::MinTimeStamp(new_min_time_stamp))
.into(),
);
});
}
#[test]
fn force_genesis_config_min_time_stamp_fail_not_root() {
xp_test_ext().execute_with(|| {
let new_min_time_stamp = 4;
assert_err!(
Xp::force_genesis_config(
RuntimeOrigin::signed(ALICE),
ForceGenesisConfig::MinTimeStamp(new_min_time_stamp)
),
DispatchError::BadOrigin
);
});
}
#[test]
fn force_genesis_config_min_time_stamp_fail_invalid_min_time_stamp() {
xp_test_ext().execute_with(|| {
let new_min_time_stamp = 4;
System::set_block_number(3);
assert_err!(
Xp::force_genesis_config(
RuntimeOrigin::root(),
ForceGenesisConfig::MinTimeStamp(new_min_time_stamp)
),
Error::InvalidMinTimeStamp
);
});
}
#[test]
fn force_genesis_config_pulse_factor_success() {
xp_test_ext().execute_with(|| {
System::set_block_number(1);
let threshold = 100;
let per_count = 10;
assert_ok!(Xp::force_genesis_config(
RuntimeOrigin::root(),
ForceGenesisConfig::PulseFactor {
threshold,
per_count
}
));
let stepper = PulseFactor::get();
assert_eq!(stepper.threshold, threshold);
assert_eq!(stepper.per_count, per_count);
System::assert_last_event(
Event::GenesisConfigUpdated(ForceGenesisConfig::PulseFactor {
threshold,
per_count,
})
.into(),
);
})
}
#[test]
fn force_genesis_config_pulse_factor_fail_low_pulse_threshold() {
xp_test_ext().execute_with(|| {
let threshold = 100;
let per_count = 110;
assert_err!(
Xp::force_genesis_config(
RuntimeOrigin::root(),
ForceGenesisConfig::PulseFactor {
threshold,
per_count
}
),
Error::LowPulseThreshold
);
});
}
#[test]
fn force_genesis_config_pulse_factor_fail_not_root() {
xp_test_ext().execute_with(|| {
let threshold = 100;
let per_count = 10;
assert_err!(
Xp::force_genesis_config(
RuntimeOrigin::signed(ALICE),
ForceGenesisConfig::PulseFactor {
threshold,
per_count
}
),
DispatchError::BadOrigin
);
});
}
#[test]
fn call_success() {
xp_test_ext().execute_with(|| {
Pallet::new_xp(&ALICE, &XP_ALPHA);
Pallet::new_xp(&BOB, &XP_BETA);
let call = Box::new(Call::Xp(crate::Call::handover {
xp_id: XP_ALPHA,
new_owner: BOB,
}));
assert_ok!(Pallet::is_owner(&ALICE, &XP_ALPHA));
System::set_block_number(2);
assert_ok!(Xp::call(RuntimeOrigin::signed(ALICE), XP_ALPHA, call));
assert_err!(Pallet::is_owner(&ALICE, &XP_ALPHA), Error::InvalidXpOwner);
assert_ok!(Pallet::is_owner(&BOB, &XP_ALPHA));
System::assert_last_event(
Event::XpOwner {
id: XP_ALPHA,
owner: BOB,
}
.into(),
);
});
}
#[test]
fn call_fail_invalid_owner() {
xp_test_ext().execute_with(|| {
Pallet::new_xp(&ALICE, &XP_ALPHA);
Pallet::new_xp(&BOB, &XP_BETA);
let call = Box::new(Call::Xp(crate::Call::handover {
xp_id: XP_ALPHA,
new_owner: BOB,
}));
assert_ok!(Pallet::is_owner(&ALICE, &XP_ALPHA));
assert_err!(
Xp::call(RuntimeOrigin::signed(ALICE), XP_BETA, call),
Error::InvalidXpOwner
);
});
}
#[test]
fn call_fail_bad_origin() {
xp_test_ext().execute_with(|| {
Pallet::new_xp(&ALICE, &XP_ALPHA);
Pallet::new_xp(&BOB, &XP_BETA);
let call = Box::new(Call::Xp(crate::Call::handover {
xp_id: XP_ALPHA,
new_owner: BOB,
}));
assert_ok!(Pallet::is_owner(&ALICE, &XP_ALPHA));
assert_err!(
Xp::call(RuntimeOrigin::root(), XP_ALPHA, call),
DispatchError::BadOrigin
);
});
}
}