Files
pezkuwi-sdk/pezcumulus/teyrchains/pallets/pez-treasury/src/lib.rs
T
pezkuwichain 1c0e57d984 feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies'
Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk.

Key changes include:
- Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks.
- Modified internal documentation and code comments to reflect PezkuwiChain naming and structure.
- Replaced direct references to  with  or specific paths within the  for XCM, Pezkuwi, and other modules.
- Cleaned up deprecated  issue and PR references in various  and  files, particularly in  and  modules.
- Adjusted image and logo URLs in documentation to point to PezkuwiChain assets.
- Removed or rephrased comments related to external Polkadot/Substrate PRs and issues.

This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
2025-12-14 00:04:10 +03:00

470 lines
14 KiB
Rust

#![cfg_attr(not(feature = "std"), no_std)]
//! # PEZ Treasury Pallet
//!
//! A pallet for managing the PEZ token distribution and treasury with automated halving mechanics.
//!
//! ## Overview
//!
//! This pallet manages the complete lifecycle of PEZ token distribution including:
//!
//! - **Genesis Distribution**: One-time initial distribution to treasury, presale, and founder
//! accounts
//! - **Halving Mechanism**: Automatic reduction of monthly releases every 48 months (4 years)
//! - **Monthly Releases**: Scheduled distribution to incentive and government pots
//! - **Multi-Pot System**: Separate accounts for treasury, incentive rewards, and governance
//!
//! ## Token Economics
//!
//! - **Total Supply**: 5,000,000,000 PEZ (5 billion tokens)
//! - **Treasury Allocation**: 96.25% (4,812,500,000 PEZ)
//! - **Presale Allocation**: 1.875% (93,750,000 PEZ)
//! - **Founder Allocation**: 1.875% (93,750,000 PEZ)
//!
//! ## Halving Schedule
//!
//! - **Halving Period**: Every 48 months (4 years)
//! - **Period Duration**: 20,736,000 blocks (~4 years at 10 blocks/minute)
//! - **Distribution**: 70% to Incentive Pot, 30% to Government Pot
//! - **Automatic Halving**: Monthly release amount halves at the start of each new period
//!
//! ## Security Features
//!
//! - **One-Time Genesis**: Genesis distribution can only occur once (protected by storage flag)
//! - **Privileged Operations**: All extrinsics require privileged origin (root or governance)
//! - **Block-Based Scheduling**: Monthly releases based on block numbers for determinism
//!
//! ## Interface
//!
//! ### Extrinsics
//!
//! - `force_genesis_distribution()` - Perform initial token distribution (one-time only,
//! privileged)
//! - `initialize_treasury()` - Initialize the halving mechanism and start monthly releases
//! (privileged)
//! - `release_monthly_funds()` - Release monthly funds to incentive and government pots
//! (privileged)
//!
//! ### Storage
//!
//! - `HalvingInfo` - Current halving period data and monthly release amount
//! - `MonthlyReleases` - Historical record of all monthly distributions
//! - `GenesisDistributionDone` - Flag to prevent duplicate genesis distribution
//!
//! ### Runtime Integration Example
//!
//! ```ignore
//! impl pezpallet_pez_treasury::Config for Runtime {
//! type RuntimeEvent = RuntimeEvent;
//! type Assets = Assets;
//! type WeightInfo = pezpallet_pez_treasury::weights::BizinikiwiWeight<Runtime>;
//! type PezAssetId = ConstU32<1>; // PEZ asset ID
//! type TreasuryPalletId = TreasuryPalletId;
//! type IncentivePotId = IncentivePotId;
//! type GovernmentPotId = GovernmentPotId;
//! type PresaleAccount = PresaleAccount;
//! type FounderAccount = FounderAccount;
//! type ForceOrigin = EnsureRoot<AccountId>;
//! }
//! ```
pub use pallet::*;
pub use weights::WeightInfo;
pub mod migrations;
pub mod weights;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
use pezframe_support::{
traits::{
fungibles::{Inspect, Mutate},
tokens::Preservation,
Get,
},
PalletId,
};
use pezframe_system::pezpallet_prelude::BlockNumberFor;
use scale_info::TypeInfo;
use pezsp_runtime::traits::{AccountIdConversion, Saturating, Zero};
#[pezframe_support::pallet]
pub mod pallet {
use super::*;
use pezframe_support::pezpallet_prelude::*;
use pezframe_system::pezpallet_prelude::*;
// use pezsp_runtime::traits::CheckedDiv;
pub const HALVING_PERIOD_MONTHS: u32 = 48; // 4 years = 48 months
pub const BLOCKS_PER_MONTH: u32 = 432_000; // ~30 days * 24 hours * 60 minutes * 10 blocks/minute
pub const HALVING_PERIOD_BLOCKS: u32 = HALVING_PERIOD_MONTHS * BLOCKS_PER_MONTH;
pub const TOTAL_SUPPLY: u128 = 5_000_000_000 * 1_000_000_000_000; // 5 billion PEZ (12 decimal)
pub const TREASURY_ALLOCATION: u128 = 4_812_500_000 * 1_000_000_000_000; // %96.25
pub const PRESALE_ALLOCATION: u128 = 93_750_000 * 1_000_000_000_000; // %1.875
pub const FOUNDER_ALLOCATION: u128 = 93_750_000 * 1_000_000_000_000; // %1.875
#[pallet::pallet]
#[pallet::storage_version(migrations::STORAGE_VERSION)]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: pezframe_system::Config + TypeInfo {
type Assets: Mutate<Self::AccountId>;
type WeightInfo: weights::WeightInfo;
#[pallet::constant]
type PezAssetId: Get<<Self::Assets as Inspect<Self::AccountId>>::AssetId>;
#[pallet::constant]
type TreasuryPalletId: Get<PalletId>;
#[pallet::constant]
type IncentivePotId: Get<PalletId>;
#[pallet::constant]
type GovernmentPotId: Get<PalletId>;
#[pallet::constant]
type PresaleAccount: Get<Self::AccountId>;
#[pallet::constant]
type FounderAccount: Get<Self::AccountId>;
type ForceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
}
pub type BalanceOf<T> =
<<T as Config>::Assets as Inspect<<T as pezframe_system::Config>::AccountId>>::Balance;
#[pallet::storage]
#[pallet::getter(fn halving_info)]
pub type HalvingInfo<T: Config> = StorageValue<_, HalvingData<T>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn monthly_releases)]
pub type MonthlyReleases<T: Config> =
StorageMap<_, Blake2_128Concat, u32, MonthlyRelease<T>, OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn next_release_month)]
pub type NextReleaseMonth<T: Config> = StorageValue<_, u32, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn treasury_start_block)]
pub type TreasuryStartBlock<T: Config> = StorageValue<_, BlockNumberFor<T>, OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn genesis_distribution_done)]
pub type GenesisDistributionDone<T: Config> = StorageValue<_, bool, ValueQuery>;
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
pub struct HalvingData<T: Config> {
pub current_period: u32,
pub period_start_block: BlockNumberFor<T>,
pub monthly_amount: BalanceOf<T>,
pub total_released: BalanceOf<T>,
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
pub struct MonthlyRelease<T: Config> {
pub month_index: u32,
pub release_block: BlockNumberFor<T>,
pub amount_released: BalanceOf<T>,
pub incentive_amount: BalanceOf<T>,
pub government_amount: BalanceOf<T>,
}
impl<T: Config> Default for HalvingData<T> {
fn default() -> Self {
Self {
current_period: 0,
period_start_block: Zero::zero(),
monthly_amount: Zero::zero(),
total_released: Zero::zero(),
}
}
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
TreasuryInitialized {
start_block: BlockNumberFor<T>,
initial_monthly_amount: BalanceOf<T>,
},
MonthlyFundsReleased {
month_index: u32,
total_amount: BalanceOf<T>,
incentive_amount: BalanceOf<T>,
government_amount: BalanceOf<T>,
},
NewHalvingPeriod {
period: u32,
new_monthly_amount: BalanceOf<T>,
},
GenesisDistributionCompleted {
treasury_amount: BalanceOf<T>,
presale_amount: BalanceOf<T>,
founder_amount: BalanceOf<T>,
},
}
#[pallet::error]
pub enum Error<T> {
TreasuryAlreadyInitialized,
TreasuryNotInitialized,
MonthlyReleaseAlreadyDone,
InsufficientTreasuryBalance,
InvalidHalvingPeriod,
ReleaseTooEarly,
GenesisDistributionAlreadyDone,
}
#[pallet::genesis_config]
#[derive(pezframe_support::DefaultNoBound)]
pub struct GenesisConfig<T: Config> {
pub initialize_treasury: bool,
#[serde(skip)]
pub _phantom: core::marker::PhantomData<T>,
}
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
if self.initialize_treasury {
let _ = Pallet::<T>::do_initialize_treasury();
}
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::initialize_treasury())]
pub fn initialize_treasury(origin: OriginFor<T>) -> DispatchResult {
T::ForceOrigin::ensure_origin(origin)?;
Self::do_initialize_treasury()
}
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::release_monthly_funds())]
pub fn release_monthly_funds(origin: OriginFor<T>) -> DispatchResult {
T::ForceOrigin::ensure_origin(origin)?;
Self::do_monthly_release()
}
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::force_genesis_distribution())]
pub fn force_genesis_distribution(origin: OriginFor<T>) -> DispatchResult {
T::ForceOrigin::ensure_origin(origin)?;
Self::do_genesis_distribution()
}
}
impl<T: Config> Pallet<T> {
pub fn treasury_account_id() -> T::AccountId {
T::TreasuryPalletId::get().into_account_truncating()
}
pub fn incentive_pot_account_id() -> T::AccountId {
T::IncentivePotId::get().into_account_truncating()
}
pub fn government_pot_account_id() -> T::AccountId {
T::GovernmentPotId::get().into_account_truncating()
}
pub fn do_genesis_distribution() -> DispatchResult {
// SECURITY: Ensure genesis distribution can only happen once
ensure!(
!GenesisDistributionDone::<T>::get(),
Error::<T>::GenesisDistributionAlreadyDone
);
let treasury_account = Self::treasury_account_id();
let presale_account = T::PresaleAccount::get();
let founder_account = T::FounderAccount::get();
let treasury_amount: BalanceOf<T> = TREASURY_ALLOCATION
.try_into()
.map_err(|_| Error::<T>::InsufficientTreasuryBalance)?;
let presale_amount: BalanceOf<T> = PRESALE_ALLOCATION
.try_into()
.map_err(|_| Error::<T>::InsufficientTreasuryBalance)?;
let founder_amount: BalanceOf<T> = FOUNDER_ALLOCATION
.try_into()
.map_err(|_| Error::<T>::InsufficientTreasuryBalance)?;
T::Assets::mint_into(T::PezAssetId::get(), &treasury_account, treasury_amount)?;
T::Assets::mint_into(T::PezAssetId::get(), &presale_account, presale_amount)?;
T::Assets::mint_into(T::PezAssetId::get(), &founder_account, founder_amount)?;
// Mark genesis distribution as completed
GenesisDistributionDone::<T>::put(true);
Self::deposit_event(Event::GenesisDistributionCompleted {
treasury_amount,
presale_amount,
founder_amount,
});
Ok(())
}
pub fn do_initialize_treasury() -> DispatchResult {
ensure!(
TreasuryStartBlock::<T>::get().is_none(),
Error::<T>::TreasuryAlreadyInitialized
);
let current_block = pezframe_system::Pallet::<T>::block_number();
let treasury_balance = TREASURY_ALLOCATION;
let first_period_total =
treasury_balance.checked_div(2).ok_or(Error::<T>::InvalidHalvingPeriod)?;
let monthly_amount = first_period_total
.checked_div(HALVING_PERIOD_MONTHS.into())
.ok_or(Error::<T>::InvalidHalvingPeriod)?;
let monthly_amount_balance: BalanceOf<T> =
monthly_amount.try_into().map_err(|_| Error::<T>::InsufficientTreasuryBalance)?;
let halving_data = HalvingData {
current_period: 0,
period_start_block: current_block,
monthly_amount: monthly_amount_balance,
total_released: Zero::zero(),
};
TreasuryStartBlock::<T>::put(current_block);
HalvingInfo::<T>::put(halving_data);
NextReleaseMonth::<T>::put(0);
Self::deposit_event(Event::TreasuryInitialized {
start_block: current_block,
initial_monthly_amount: monthly_amount_balance,
});
Ok(())
}
pub fn do_monthly_release() -> DispatchResult {
ensure!(TreasuryStartBlock::<T>::get().is_some(), Error::<T>::TreasuryNotInitialized);
let current_block = pezframe_system::Pallet::<T>::block_number();
let start_block = TreasuryStartBlock::<T>::get().unwrap();
let next_month = NextReleaseMonth::<T>::get();
ensure!(
!MonthlyReleases::<T>::contains_key(next_month),
Error::<T>::MonthlyReleaseAlreadyDone
);
let blocks_passed = current_block.saturating_sub(start_block);
let months_passed: u32 = (blocks_passed / BLOCKS_PER_MONTH.into())
.try_into()
.map_err(|_| Error::<T>::InvalidHalvingPeriod)?;
// To release month 0, months_passed must be >= 1 (next_month + 1)
// To release month 1, months_passed must be >= 2
ensure!(months_passed > next_month, Error::<T>::ReleaseTooEarly);
let mut halving_data = HalvingInfo::<T>::get();
let current_period_passed_months =
months_passed.saturating_sub(halving_data.current_period * HALVING_PERIOD_MONTHS);
if current_period_passed_months >= HALVING_PERIOD_MONTHS {
halving_data.current_period = halving_data.current_period.saturating_add(1);
halving_data.monthly_amount = halving_data
.monthly_amount
.checked_div(&2u32.into())
.ok_or(Error::<T>::InvalidHalvingPeriod)?;
halving_data.period_start_block = current_block;
Self::deposit_event(Event::NewHalvingPeriod {
period: halving_data.current_period,
new_monthly_amount: halving_data.monthly_amount,
});
}
let monthly_amount = halving_data.monthly_amount;
let incentive_amount = monthly_amount
.checked_mul(&75u32.into())
.and_then(|v| v.checked_div(&100u32.into()))
.ok_or(Error::<T>::InvalidHalvingPeriod)?;
let government_amount = monthly_amount.saturating_sub(incentive_amount);
let treasury_account = Self::treasury_account_id();
let incentive_pot = Self::incentive_pot_account_id();
let government_pot = Self::government_pot_account_id();
T::Assets::transfer(
T::PezAssetId::get(),
&treasury_account,
&incentive_pot,
incentive_amount,
Preservation::Preserve,
)
.map_err(|_| Error::<T>::InsufficientTreasuryBalance)?;
T::Assets::transfer(
T::PezAssetId::get(),
&treasury_account,
&government_pot,
government_amount,
Preservation::Preserve,
)
.map_err(|_| Error::<T>::InsufficientTreasuryBalance)?;
halving_data.total_released =
halving_data.total_released.saturating_add(monthly_amount);
HalvingInfo::<T>::put(halving_data);
let release_info = MonthlyRelease {
month_index: next_month,
release_block: current_block,
amount_released: monthly_amount,
incentive_amount,
government_amount,
};
MonthlyReleases::<T>::insert(next_month, release_info);
NextReleaseMonth::<T>::put(next_month + 1);
Self::deposit_event(Event::MonthlyFundsReleased {
month_index: next_month,
total_amount: monthly_amount,
incentive_amount,
government_amount,
});
Ok(())
}
pub fn get_current_halving_info() -> HalvingData<T> {
HalvingInfo::<T>::get()
}
pub fn get_incentive_pot_balance() -> BalanceOf<T> {
let pot_account = Self::incentive_pot_account_id();
T::Assets::balance(T::PezAssetId::get(), &pot_account)
}
pub fn get_government_pot_balance() -> BalanceOf<T> {
let pot_account = Self::government_pot_account_id();
T::Assets::balance(T::PezAssetId::get(), &pot_account)
}
}
}