#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::manual_inspect)]
#![doc = simple_mermaid::mermaid!("../docs/timegraph_flows.mmd")]
pub use pallet::*;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[polkadot_sdk::frame_support::pallet]
pub mod pallet {
use polkadot_sdk::{frame_support, frame_system, sp_runtime};
use frame_support::pallet_prelude::*;
use frame_support::traits::{Currency, ExistenceRequirement, ReservableCurrency};
use frame_system::pallet_prelude::*;
use sp_runtime::traits::Saturating;
pub type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
pub trait WeightInfo {
fn deposit() -> Weight;
fn withdraw() -> Weight;
fn transfer_to_pool() -> Weight;
fn transfer_award_to_user() -> Weight;
fn set_timegraph_account() -> Weight;
fn set_reward_pool_account() -> Weight;
fn set_threshold() -> Weight;
}
impl WeightInfo for () {
fn deposit() -> Weight {
Weight::default()
}
fn withdraw() -> Weight {
Weight::default()
}
fn transfer_to_pool() -> Weight {
Weight::default()
}
fn transfer_award_to_user() -> Weight {
Weight::default()
}
fn set_timegraph_account() -> Weight {
Weight::default()
}
fn set_reward_pool_account() -> Weight {
Weight::default()
}
fn set_threshold() -> Weight {
Weight::default()
}
}
#[pallet::pallet]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: polkadot_sdk::frame_system::Config {
type RuntimeEvent: From<Event<Self>>
+ IsType<<Self as polkadot_sdk::frame_system::Config>::RuntimeEvent>;
type WeightInfo: WeightInfo;
type Currency: Currency<Self::AccountId> + ReservableCurrency<Self::AccountId>;
#[pallet::constant]
type InitialThreshold: Get<BalanceOf<Self>>;
#[pallet::constant]
type InitialTimegraphAccount: Get<Self::AccountId>;
#[pallet::constant]
type InitialRewardPoolAccount: Get<Self::AccountId>;
type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
}
#[pallet::storage]
#[pallet::getter(fn next_deposit_sequence)]
pub type NextDepositSequence<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, u64, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn next_withdrawal_sequence)]
pub type NextWithdrawalSequence<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, u64, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn timegraph_account)]
pub type TimegraphAccount<T: Config> =
StorageValue<_, T::AccountId, ValueQuery, T::InitialTimegraphAccount>;
#[pallet::storage]
#[pallet::getter(fn reward_pool_account)]
pub type RewardPoolAccount<T: Config> =
StorageValue<_, T::AccountId, ValueQuery, T::InitialRewardPoolAccount>;
#[pallet::storage]
#[pallet::getter(fn threshold)]
pub type Threshold<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery, T::InitialThreshold>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
Deposit { who: T::AccountId, amount: BalanceOf<T>, sequence: u64 },
Withdrawal { who: T::AccountId, amount: BalanceOf<T>, sequence: u64 },
TransferToPool { from: T::AccountId, to: T::AccountId, amount: BalanceOf<T> },
TransferAwardToUser { from: T::AccountId, to: T::AccountId, amount: BalanceOf<T> },
TimegraphAccountReset { old: T::AccountId, new: T::AccountId },
RewardPoolAccountReset { old: T::AccountId, new: T::AccountId },
ThresholdReset { old: BalanceOf<T>, new: BalanceOf<T> },
}
#[pallet::error]
pub enum Error<T> {
SequenceNumberOverflow,
ZeroAmount,
WithdrawalAmountOverReserve,
NotWithdrawalRequired,
RewardPoolOutOfBalance,
RewardToSameAccount,
SameTimegraphAccount,
SameRewardPoolAccount,
SameThreshold,
SenderIsNotTimegraph,
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::deposit())]
pub fn deposit(origin: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {
let who = ensure_signed(origin)?;
ensure!(amount > 0_u32.into(), Error::<T>::ZeroAmount);
T::Currency::reserve(&who, amount)?;
NextDepositSequence::<T>::try_mutate(&who, |x| -> DispatchResult {
*x = x.checked_add(1).ok_or(Error::<T>::SequenceNumberOverflow)?;
Ok(())
})?;
Self::deposit_event(Event::Deposit {
who: who.clone(),
amount,
sequence: NextDepositSequence::<T>::get(who),
});
Ok(())
}
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::withdraw())]
pub fn withdraw(origin: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {
let who = ensure_signed(origin)?;
ensure!(amount > 0_u32.into(), Error::<T>::ZeroAmount);
let current_reserve = T::Currency::reserved_balance(&who);
let threshold = Threshold::<T>::get();
ensure!(
amount.saturating_add(threshold) <= current_reserve,
Error::<T>::WithdrawalAmountOverReserve
);
ensure!(
T::Currency::unreserve(&who, amount) == 0_u32.into(),
Error::<T>::NotWithdrawalRequired
);
NextWithdrawalSequence::<T>::try_mutate(&who, |x| -> DispatchResult {
*x = x.checked_add(1).ok_or(Error::<T>::SequenceNumberOverflow)?;
Ok(())
})?;
Self::deposit_event(Event::Withdrawal {
who: who.clone(),
amount,
sequence: NextWithdrawalSequence::<T>::get(&who),
});
Ok(())
}
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::transfer_to_pool())]
pub fn transfer_to_pool(
origin: OriginFor<T>,
account: T::AccountId,
amount: BalanceOf<T>,
) -> DispatchResult {
Self::ensure_timegraph(origin)?;
let unserved = T::Currency::unreserve(&account, amount);
ensure!(unserved == 0_u32.into(), Error::<T>::NotWithdrawalRequired);
T::Currency::transfer(
&account,
&RewardPoolAccount::<T>::get(),
amount,
ExistenceRequirement::KeepAlive,
)?;
Self::deposit_event(Event::TransferToPool {
from: account.clone(),
to: RewardPoolAccount::<T>::get(),
amount,
});
Ok(())
}
#[pallet::call_index(3)]
#[pallet::weight(T::WeightInfo::transfer_award_to_user())]
pub fn transfer_award_to_user(
origin: OriginFor<T>,
account: T::AccountId,
amount: BalanceOf<T>,
) -> DispatchResult {
Self::ensure_timegraph(origin)?;
ensure!(account != RewardPoolAccount::<T>::get(), Error::<T>::RewardToSameAccount);
let pool_account = RewardPoolAccount::<T>::get();
let pool_balance = T::Currency::free_balance(&pool_account);
ensure!(pool_balance > amount, Error::<T>::RewardPoolOutOfBalance);
T::Currency::transfer(
&pool_account,
&account,
amount,
ExistenceRequirement::KeepAlive,
)?;
T::Currency::reserve(&account, amount)?;
Self::deposit_event(Event::TransferAwardToUser {
from: RewardPoolAccount::<T>::get(),
to: account,
amount,
});
Ok(())
}
#[pallet::call_index(4)]
#[pallet::weight(T::WeightInfo::withdraw())]
pub fn set_timegraph_account(
origin: OriginFor<T>,
account: T::AccountId,
) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
ensure!(
account.clone() != TimegraphAccount::<T>::get(),
Error::<T>::SameTimegraphAccount
);
Self::deposit_event(Event::TimegraphAccountReset {
old: TimegraphAccount::<T>::get(),
new: account.clone(),
});
TimegraphAccount::<T>::set(account);
Ok(())
}
#[pallet::call_index(5)]
#[pallet::weight(T::WeightInfo::withdraw())]
pub fn set_reward_pool_account(
origin: OriginFor<T>,
account: T::AccountId,
) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
ensure!(
account.clone() != RewardPoolAccount::<T>::get(),
Error::<T>::SameRewardPoolAccount
);
Self::deposit_event(Event::RewardPoolAccountReset {
old: RewardPoolAccount::<T>::get(),
new: account.clone(),
});
RewardPoolAccount::<T>::set(account);
Ok(())
}
#[pallet::call_index(6)]
#[pallet::weight(T::WeightInfo::withdraw())]
pub fn set_threshold(origin: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
ensure!(amount != Threshold::<T>::get(), Error::<T>::SameThreshold);
Self::deposit_event(Event::ThresholdReset {
old: Threshold::<T>::get(),
new: amount,
});
Threshold::<T>::set(amount);
Ok(())
}
}
impl<T: Config> Pallet<T> {
pub fn ensure_timegraph(origin: OriginFor<T>) -> DispatchResult {
let who = ensure_signed(origin)?;
let current_account = TimegraphAccount::<T>::get();
ensure!(who == current_account, Error::<T>::SenderIsNotTimegraph);
Ok(())
}
}
}