use crate::{
base::{Asset, Delimited, Keyed, RuntimeEnum, RuntimeError, Time},
misc::Ignore,
};
use core::cmp::Ordering;
use frame_support::{
pallet_prelude::*,
traits::{tokens::Precision, VariantCount, VariantCountOf},
};
use sp_runtime::traits::Saturating;
use sp_std::vec::Vec;
pub enum XpError {
XpNotFound,
XpReserveNotFound,
XpLockNotFound,
InsufficientLiquidXp,
TooManyReserves,
TooManyLocks,
CannotLockZero,
CannotReserveZero,
XpAlreadyReaped,
XpNotDead,
CannotReapLockedXp,
InsufficientReserveXp,
XpCapOverflowed,
XpCapUnderflowed,
XpReserveCapOverflowed,
XpReserveCapUnderflowed,
XpLockCapOverflowed,
XpLockCapUnderflowed,
}
pub trait XpErrorHandler {
type Error: RuntimeError;
fn from_xp_error(e: XpError) -> Self::Error;
}
pub type Key<T> = <T as XpSystem>::XpKey;
pub type Points<T> = <T as XpSystem>::Points;
pub type Owner<T> = <T as XpOwner>::Owner;
pub type LockReason<T> = <T as XpLock>::LockReason;
pub type ReserveReason<T> = <T as XpReserve>::ReserveReason;
pub trait XpSystem {
type Xp: Delimited;
type Points: Asset;
type XpKey: Keyed;
type TimeStamp: Time;
type Extension: XpSystemExtensions<Via = Self>;
fn xp_exists(key: &Self::XpKey) -> DispatchResult;
fn has_minimum_xp(key: &Self::XpKey) -> DispatchResult;
fn get_xp(key: &Self::XpKey) -> Result<Self::Xp, DispatchError>;
fn get_liquid_xp(key: &Self::XpKey) -> Result<Self::Points, DispatchError>;
fn get_usable_xp(key: &Self::XpKey) -> Result<Self::Points, DispatchError>;
}
pub trait XpSystemExtensions
where
Self: Sized,
{
type Via: XpSystem;
}
impl<T> XpSystemExtensions for Ignore<T>
where
Self: Sized,
T: XpSystem,
{
type Via = T;
}
pub trait XpOwner
where
Self: XpSystem<Extension: XpOwnerListener + XpSystemExtensions<Via = Self>>,
{
type Owner: Keyed;
fn is_owner(owner: &Self::Owner, key: &Self::XpKey) -> DispatchResult;
fn xp_of_owner(owner: &Self::Owner) -> Result<Vec<Self::XpKey>, DispatchError>;
fn xp_key_gen(owner: &Self::Owner, xp: &Self::Xp) -> Result<Self::XpKey, DispatchError>;
fn transfer_owner(
owner: &Self::Owner,
key: &Self::XpKey,
new_owner: &Self::Owner,
) -> DispatchResult {
Self::is_owner(owner, key)?;
if owner == new_owner {
return Ok(());
}
Self::set_owner(owner, key, new_owner)?;
Self::on_xp_transfer(key, new_owner);
Ok(())
}
fn set_owner(
current_owner: &Self::Owner,
key: &Self::XpKey,
new_owner: &Self::Owner,
) -> DispatchResult;
fn on_xp_transfer(key: &Self::XpKey, new_owner: &Self::Owner) {
Self::Extension::xp_transferred(key, new_owner);
}
}
pub trait XpOwnerListener
where
Self: XpSystemExtensions,
Self::Via: XpOwner,
{
fn xp_transferred(_key: &Key<Self::Via>, _new_owner: &Owner<Self::Via>) {}
}
impl<T> XpOwnerListener for Ignore<T>
where
Self: XpSystemExtensions<Via = T>,
T: XpOwner,
{
}
pub trait XpMutate
where
Self: XpOwner
+ XpErrorHandler
+ XpSystem<Extension: XpMutateListener + XpSystemExtensions<Via = Self>>,
{
fn init_xp() -> Self::Points;
fn create_xp(owner: &Self::Owner, key: &Self::XpKey) -> DispatchResult {
Self::new_xp(owner, key);
let init = Self::init_xp();
Self::set_xp(key, init)?;
Self::on_xp_create(key, owner);
Ok(())
}
fn new_xp(owner: &Self::Owner, key: &Self::XpKey);
fn set_xp(key: &Self::XpKey, points: Self::Points) -> DispatchResult;
fn earn_xp(key: &Self::XpKey, points: Self::Points) -> Result<Self::Points, DispatchError> {
let quote = Self::quote_earn_xp(key, points)?;
Self::set_xp(key, quote)?;
Self::on_xp_earn(key, quote);
Ok(quote)
}
fn quote_earn_xp(
key: &Self::XpKey,
points: Self::Points,
) -> Result<Self::Points, DispatchError>;
fn slash_xp(key: &Self::XpKey, points: Self::Points) -> Result<Self::Points, DispatchError> {
<Self as XpSystem>::xp_exists(key)?;
let liquid = Self::get_liquid_xp(key)?;
if liquid >= points {
let remaining = liquid.saturating_sub(points);
Self::set_xp(key, remaining)?;
Self::on_xp_slash(key, points);
return Ok(points);
}
let burn = Self::reset_xp(key)?;
Self::on_xp_slash(key, liquid);
Ok(burn)
}
fn reset_xp(key: &Self::XpKey) -> Result<Self::Points, DispatchError> {
<Self as XpSystem>::xp_exists(key)?;
let liquid = Self::get_liquid_xp(key)?;
let reset_points = Self::Points::zero();
Self::set_xp(key, reset_points)?;
Self::on_xp_update(key, reset_points);
Ok(liquid)
}
fn on_xp_create(key: &Self::XpKey, owner: &Self::Owner) {
Self::Extension::xp_created(key, owner);
}
fn on_xp_earn(key: &Self::XpKey, earned_points: Self::Points) {
Self::Extension::xp_earned(key, earned_points);
}
fn on_xp_slash(key: &Self::XpKey, slashed_points: Self::Points) {
Self::Extension::xp_slashed(key, slashed_points);
}
fn on_xp_update(key: &Self::XpKey, current_points: Self::Points) {
Self::Extension::xp_updated(key, current_points);
}
}
pub trait XpMutateListener
where
Self: XpOwnerListener,
Self::Via: XpMutate,
{
fn xp_created(_key: &Key<Self::Via>, _owner: &Owner<Self::Via>) {}
fn xp_earned(_key: &Key<Self::Via>, _earned_points: Points<Self::Via>) {}
fn xp_slashed(_key: &Key<Self::Via>, _slashed_points: Points<Self::Via>) {}
fn xp_resetted(_key: &Key<Self::Via>) {}
fn xp_updated(_key: &Key<Self::Via>, _current_points: Points<Self::Via>) {}
}
impl<T> XpMutateListener for Ignore<T>
where
Self: XpOwnerListener + XpSystemExtensions<Via = T>,
T: XpMutate,
{
}
pub trait XpReserve
where
Self: XpMutate + XpSystem<Extension: XpReserveListener + XpSystemExtensions<Via = Self>>,
{
type Reserve: Delimited;
type ReserveReason: RuntimeEnum + VariantCount;
fn reserve_exists(key: &Self::XpKey, reason: &Self::ReserveReason) -> DispatchResult;
fn has_reserve(key: &Self::XpKey) -> DispatchResult;
fn can_reserve_xp(key: &Self::XpKey, points: Self::Points) -> DispatchResult {
ensure!(
!points.is_zero(),
Self::from_xp_error(XpError::CannotReserveZero).into()
);
let reservable = <Self as XpSystem>::get_liquid_xp(key)?;
let total_reserved = Self::total_reserved(key)?;
if points > reservable {
return Err(Self::from_xp_error(XpError::InsufficientLiquidXp).into());
}
total_reserved
.checked_add(&points)
.ok_or(Self::from_xp_error(XpError::XpReserveCapOverflowed).into())?;
Ok(())
}
fn can_reserve_mutate(
key: &Self::XpKey,
reason: &Self::ReserveReason,
points: Self::Points,
) -> DispatchResult {
let reserved = Self::get_reserve_xp(key, reason)?;
let total_reserved = Self::total_reserved(key)?;
match reserved.cmp(&points) {
Ordering::Less => {
let increase = points.saturating_sub(reserved);
total_reserved
.checked_add(&increase)
.ok_or(Self::from_xp_error(XpError::XpReserveCapOverflowed).into())?;
Ok(())
}
Ordering::Greater => {
let decrease = reserved.saturating_sub(points);
total_reserved
.checked_sub(&decrease)
.ok_or(Self::from_xp_error(XpError::XpReserveCapUnderflowed).into())?;
Ok(())
}
Ordering::Equal => Ok(()),
}
}
fn can_reserve_new(key: &Self::XpKey, points: Self::Points) -> DispatchResult {
<Self as XpSystem>::xp_exists(key)?;
let reserves = Self::get_all_reserves(key)?;
if reserves.len() >= Self::maximum_reserves() {
return Err(Self::from_xp_error(XpError::TooManyReserves).into());
}
let total_reserved = Self::total_reserved(key)?;
total_reserved
.checked_add(&points)
.ok_or(Self::from_xp_error(XpError::XpReserveCapOverflowed).into())?;
Ok(())
}
fn get_reserve_xp(
key: &Self::XpKey,
reason: &Self::ReserveReason,
) -> Result<Self::Points, DispatchError>;
fn total_reserved(key: &Self::XpKey) -> Result<Self::Points, DispatchError>;
fn get_all_reserves(key: &Self::XpKey) -> Result<Vec<Self::ReserveReason>, DispatchError>;
fn maximum_reserves() -> usize {
VariantCountOf::<Self::ReserveReason>::get() as usize
}
fn set_reserve(
key: &Self::XpKey,
reason: &Self::ReserveReason,
points: Self::Points,
) -> DispatchResult;
fn reserve_xp(
key: &Self::XpKey,
reason: &Self::ReserveReason,
points: Self::Points,
) -> DispatchResult {
<Self as XpSystem>::xp_exists(key)?;
let liquid = <Self as XpSystem>::get_liquid_xp(key)?;
if liquid < points {
return Err(Self::from_xp_error(XpError::InsufficientLiquidXp).into());
};
if Self::reserve_exists(key, reason).is_err() {
let remaining = liquid.saturating_sub(points);
<Self as XpMutate>::set_xp(key, remaining)?;
Self::set_reserve(key, reason, points)?;
Self::on_reserve_update(key, reason, points);
return Ok(());
}
let remaining = liquid.saturating_sub(points);
<Self as XpMutate>::set_xp(key, remaining)?;
let old_reserve_points = Self::get_reserve_xp(key, reason)?;
let new_reserve_points = old_reserve_points
.checked_add(&points)
.ok_or(Self::from_xp_error(XpError::XpReserveCapOverflowed).into())?;
Self::set_reserve(key, reason, new_reserve_points)?;
Self::on_reserve_update(key, reason, new_reserve_points);
Ok(())
}
fn withdraw_reserve(key: &Self::XpKey, reason: &Self::ReserveReason) -> DispatchResult {
<Self as XpSystem>::xp_exists(key)?;
let reserve_points = Self::get_reserve_xp(key, reason)?;
Self::withdraw_reserve_partial(key, reason, reserve_points, Precision::BestEffort)?;
Ok(())
}
fn reset_reserve(
key: &Self::XpKey,
reason: &Self::ReserveReason,
) -> Result<Self::Points, DispatchError> {
<Self as XpSystem>::xp_exists(key)?;
let reserve_xp = Self::get_reserve_xp(key, reason)?;
let reset_points = Zero::zero();
Self::set_reserve(key, reason, reset_points)?;
Ok(reserve_xp)
}
fn slash_reserve(
key: &Self::XpKey,
reason: &Self::ReserveReason,
points: Self::Points,
) -> Result<Self::Points, DispatchError> {
<Self as XpSystem>::xp_exists(key)?;
let reserve_xp = Self::get_reserve_xp(key, reason)?;
if reserve_xp < points {
let burn_reserve_xp = Self::reset_reserve(key, reason)?;
Self::on_reserve_slash(key, reason, burn_reserve_xp);
return Ok(burn_reserve_xp);
}
let remaining = reserve_xp.saturating_sub(points);
Self::set_reserve(key, reason, remaining)?;
Self::on_reserve_slash(key, reason, points);
Ok(points)
}
fn withdraw_reserve_partial(
key: &Self::XpKey,
reason: &Self::ReserveReason,
points: Self::Points,
precision: Precision,
) -> DispatchResult {
<Self as XpSystem>::xp_exists(key)?;
if points.is_zero() {
return Ok(());
}
Self::reserve_exists(key, reason)?;
let reserve = Self::get_reserve_xp(key, reason)?;
let liquid = <Self>::get_liquid_xp(key)?;
let (new_reserve, new_free) = match precision {
Precision::Exact => {
let new_reserve = reserve
.checked_sub(&points)
.ok_or(Self::from_xp_error(XpError::InsufficientReserveXp).into())?;
let new_free = liquid
.checked_add(&points)
.ok_or(Self::from_xp_error(XpError::XpCapOverflowed).into())?;
(new_reserve, new_free)
}
Precision::BestEffort => {
let new_reserve = reserve.saturating_sub(points);
let new_free = liquid.saturating_add(reserve.min(points));
(new_reserve, new_free)
}
};
match new_reserve.is_zero() {
true => {
let zero = Self::Points::zero();
Self::set_reserve(key, reason, zero)?;
Self::on_reserve_update(key, reason, zero);
}
false => {
Self::set_reserve(key, reason, new_reserve)?;
Self::on_reserve_update(key, reason, new_reserve);
}
}
Self::set_xp(key, new_free)?;
Ok(())
}
fn on_reserve_update(
key: &Self::XpKey,
reason: &Self::ReserveReason,
reserve_points: Self::Points,
) {
Self::Extension::reserve_updated(key, reason, reserve_points);
}
fn on_reserve_slash(
key: &Self::XpKey,
reason: &Self::ReserveReason,
slashed_points: Self::Points,
) {
Self::Extension::reserve_slashed(key, reason, slashed_points);
}
}
pub trait XpReserveListener
where
Self: XpMutateListener,
Self::Via: XpReserve,
{
fn reserve_updated(
_key: &Key<Self::Via>,
_reason: &ReserveReason<Self::Via>,
_total_points: Points<Self::Via>,
) {
}
fn reserve_slashed(
_key: &Key<Self::Via>,
_reason: &ReserveReason<Self::Via>,
_slashed_points: Points<Self::Via>,
) {
}
}
impl<T> XpReserveListener for Ignore<T>
where
Self: XpMutateListener + XpSystemExtensions<Via = T>,
T: XpReserve,
{
}
pub trait XpLock
where
Self: XpMutate + XpSystem<Extension: XpLockListener + XpSystemExtensions<Via = Self>>,
{
type Lock: Delimited;
type LockReason: RuntimeEnum + VariantCount;
fn lock_exists(key: &Self::XpKey, reason: &Self::LockReason) -> DispatchResult;
fn has_lock(key: &Self::XpKey) -> DispatchResult;
fn can_lock_xp(key: &Self::XpKey, points: Self::Points) -> DispatchResult {
Self::can_lock_new(key, points)?;
let lockable = <Self as XpSystem>::get_liquid_xp(key)?;
let total_locked = Self::total_locked(key)?;
if points > lockable {
return Err(Self::from_xp_error(XpError::InsufficientLiquidXp).into());
}
match total_locked.checked_add(&points) {
Some(_pass) => Ok(()),
None => Err(Self::from_xp_error(XpError::XpLockCapOverflowed).into()),
}
}
fn can_lock_mutate(
key: &Self::XpKey,
reason: &Self::LockReason,
points: Self::Points,
) -> DispatchResult {
ensure!(
!points.is_zero(),
Self::from_xp_error(XpError::CannotLockZero).into()
);
let locked = Self::get_lock_xp(key, reason)?;
let total_locked = Self::total_locked(key)?;
match locked.cmp(&points) {
Ordering::Less => {
let increase = points.saturating_sub(locked);
total_locked
.checked_add(&increase)
.ok_or(Self::from_xp_error(XpError::XpLockCapOverflowed).into())?;
Ok(())
}
Ordering::Greater => {
let decrease = locked.saturating_sub(points);
total_locked
.checked_sub(&decrease)
.ok_or(Self::from_xp_error(XpError::XpLockCapUnderflowed).into())?;
Ok(())
}
Ordering::Equal => Ok(()),
}
}
fn can_lock_new(key: &Self::XpKey, points: Self::Points) -> DispatchResult {
<Self as XpSystem>::xp_exists(key)?;
ensure!(
!points.is_zero(),
Self::from_xp_error(XpError::CannotLockZero).into()
);
let locks = Self::get_all_locks(key)?;
if locks.len() >= Self::maximum_locks() {
return Err(Self::from_xp_error(XpError::TooManyLocks).into());
};
let total_locked = Self::total_locked(key)?;
total_locked
.checked_add(&points)
.ok_or(Self::from_xp_error(XpError::XpLockCapOverflowed).into())?;
Ok(())
}
fn get_lock_xp(
key: &Self::XpKey,
reason: &Self::LockReason,
) -> Result<Self::Points, DispatchError>;
fn total_locked(key: &Self::XpKey) -> Result<Self::Points, DispatchError>;
fn get_all_locks(key: &Self::XpKey) -> Result<Vec<Self::LockReason>, DispatchError>;
fn maximum_locks() -> usize {
VariantCountOf::<Self::LockReason>::get() as usize
}
fn burn_lock(key: &Self::XpKey, reason: &Self::LockReason) -> DispatchResult;
fn set_lock(
key: &Self::XpKey,
reason: &Self::LockReason,
points: Self::Points,
) -> DispatchResult;
fn lock_xp(
key: &Self::XpKey,
reason: &Self::LockReason,
points: Self::Points,
) -> DispatchResult {
<Self as XpSystem>::xp_exists(key)?;
ensure!(
!points.is_zero(),
Self::from_xp_error(XpError::CannotLockZero).into()
);
let liquid = <Self as XpSystem>::get_liquid_xp(key)?;
if liquid < points {
return Err(Self::from_xp_error(XpError::InsufficientLiquidXp).into());
};
if Self::lock_exists(key, reason).is_err() {
let remaining = liquid.saturating_sub(points);
<Self as XpMutate>::set_xp(key, remaining)?;
Self::set_lock(key, reason, points)?;
Self::on_lock_update(key, reason, points);
return Ok(());
}
let remaining = liquid.saturating_sub(points);
<Self as XpMutate>::set_xp(key, remaining)?;
let old_lock_points = Self::get_lock_xp(key, reason)?;
let new_lock_points = old_lock_points
.checked_add(&points)
.ok_or(Self::from_xp_error(XpError::XpLockCapOverflowed).into())?;
Self::set_lock(key, reason, new_lock_points)?;
Self::on_lock_update(key, reason, new_lock_points);
Ok(())
}
fn withdraw_lock(key: &Self::XpKey, reason: &Self::LockReason) -> DispatchResult {
<Self as XpSystem>::xp_exists(key)?;
<Self as XpLock>::lock_exists(key, reason)?;
let lock_points = Self::get_lock_xp(key, reason)?;
let liquid = <Self as XpSystem>::get_liquid_xp(key)?;
let new_liquid = liquid.saturating_add(lock_points);
<Self as XpMutate>::set_xp(key, new_liquid)?;
<Self as XpLock>::burn_lock(key, reason)?;
Self::on_lock_burn(key, reason);
Ok(())
}
fn slash_lock(
key: &Self::XpKey,
reason: &Self::LockReason,
points: Self::Points,
) -> Result<Self::Points, DispatchError> {
<Self as XpSystem>::xp_exists(key)?;
let lock_xp = Self::get_lock_xp(key, reason)?;
if lock_xp < points {
Self::burn_lock(key, reason)?;
Self::on_lock_slash(key, reason, lock_xp);
Self::on_lock_burn(key, reason);
return Ok(lock_xp);
}
let remaining = lock_xp.saturating_sub(points);
Self::set_lock(key, reason, remaining)?;
Self::on_lock_slash(key, reason, points);
Ok(points)
}
fn on_lock_update(key: &Self::XpKey, reason: &Self::LockReason, lock_points: Self::Points) {
Self::Extension::lock_updated(key, reason, lock_points);
}
fn on_lock_burn(key: &Self::XpKey, reason: &Self::LockReason) {
Self::Extension::lock_burned(key, reason);
}
fn on_lock_slash(key: &Self::XpKey, reason: &Self::LockReason, slashed_points: Self::Points) {
Self::Extension::lock_slashed(key, reason, slashed_points);
}
}
pub trait XpLockListener
where
Self: XpMutateListener,
Self::Via: XpLock,
{
fn lock_updated(
_key: &Key<Self::Via>,
_reason: &LockReason<Self::Via>,
_total_points: Points<Self::Via>,
) {
}
fn lock_burned(_key: &Key<Self::Via>, _reason: &LockReason<Self::Via>) {}
fn lock_slashed(
_key: &Key<Self::Via>,
_reason: &LockReason<Self::Via>,
_slashed_points: Points<Self::Via>,
) {
}
}
impl<T> XpLockListener for Ignore<T>
where
Self: XpMutateListener + XpSystemExtensions<Via = T>,
T: XpLock,
{
}
pub trait XpReap
where
Self: XpLock + XpSystem<Extension: XpReapListener + XpSystemExtensions<Via = Self>>,
{
fn is_reaped(key: &Self::XpKey) -> DispatchResult;
fn can_reap(key: &Self::XpKey) -> DispatchResult {
if Self::is_reaped(key).is_ok() {
return Err(Self::from_xp_error(XpError::XpAlreadyReaped).into());
}
Self::xp_exists(key)?;
if Self::has_minimum_xp(key).is_ok() {
return Err(Self::from_xp_error(XpError::XpNotDead).into());
}
if <Self as XpLock>::has_lock(key).is_ok() {
return Err(Self::from_xp_error(XpError::CannotReapLockedXp).into());
}
Ok(())
}
fn reap_xp(key: &Self::XpKey) -> Result<Self::Points, DispatchError>;
fn try_reap(key: &Self::XpKey) -> Result<Self::Points, DispatchError> {
Self::can_reap(key)?;
let p = Self::reap_xp(key)?;
Self::on_xp_reap(key);
Ok(p)
}
fn on_xp_reap(key: &Self::XpKey) {
Self::Extension::xp_reaped(key);
}
}
pub trait XpReapListener
where
Self: XpLockListener,
Self::Via: XpLock,
{
fn xp_reaped(_key: &Key<Self::Via>) {}
}
impl<T> XpReapListener for Ignore<T>
where
Self: XpLockListener + XpSystemExtensions<Via = T>,
T: XpLock,
{
}
pub trait BeginXp
where
Self: XpReap + XpSystem<Extension: XpReapListener + XpSystemExtensions<Via = Self>>,
{
fn begin_xp(owner: &Self::Owner, key: &Self::XpKey, points: Self::Points) -> DispatchResult {
let exists = Self::xp_exists(key).is_ok();
let reaped = Self::is_reaped(key).is_ok();
if reaped {
return Err(Self::from_xp_error(XpError::XpAlreadyReaped).into());
}
if !exists {
Self::create_xp(owner, key)?;
return Ok(());
}
Self::earn_xp(key, points)?;
Ok(())
}
}
impl<T> BeginXp for T where
T: XpReap + XpSystem<Extension: XpReapListener + XpSystemExtensions<Via = Self>>
{
}