#![cfg_attr(not(feature = "std"), no_std)] #[cfg(test)] mod mock; #[cfg(test)] mod tests; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::pallet_prelude::*; use frame_support::traits::{Imbalance, SameOrOther, TryDrop, tokens::imbalance::TryMerge}; use scale_info::TypeInfo; use sp_runtime::traits::Zero; use subtensor_macros::freeze_struct; use subtensor_runtime_common::{AlphaBalance, NetUid, Token}; pub use pallet::*; /// Lightweight mint record that can later be resolved to a subnet or user alpha balance. #[freeze_struct("2da64a64e80a7880")] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] pub struct PositiveAlphaImbalance { netuid: NetUid, amount: AlphaBalance, } #[freeze_struct("1f16c8937e05cf36")] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] pub struct NegativeAlphaImbalance { netuid: NetUid, amount: AlphaBalance, } impl PositiveAlphaImbalance { pub fn new(netuid: NetUid, amount: AlphaBalance) -> Self { Self { netuid, amount } } pub fn netuid(&self) -> NetUid { self.netuid } pub fn amount(&self) -> AlphaBalance { self.amount } } impl NegativeAlphaImbalance { pub fn new(netuid: NetUid, amount: AlphaBalance) -> Self { Self { netuid, amount } } } fn log_netuid_mismatch(context: &'static str, left: NetUid, right: NetUid) { log::error!( target: "runtime::alpha-assets", "{context}: attempted to combine alpha imbalances from different netuids: left={left}, right={right}" ); } impl TryDrop for PositiveAlphaImbalance { fn try_drop(self) -> Result<(), Self> { if self.amount.is_zero() { Ok(()) } else { Err(self) } } } impl TryDrop for NegativeAlphaImbalance { fn try_drop(self) -> Result<(), Self> { if self.amount.is_zero() { Ok(()) } else { Err(self) } } } impl TryMerge for PositiveAlphaImbalance { fn try_merge(self, other: Self) -> Result { if self.netuid == other.netuid { Ok(Self::new( self.netuid, self.amount.saturating_add(other.amount), )) } else { Err((self, other)) } } } impl TryMerge for NegativeAlphaImbalance { fn try_merge(self, other: Self) -> Result { if self.netuid == other.netuid { Ok(Self::new( self.netuid, self.amount.saturating_add(other.amount), )) } else { Err((self, other)) } } } impl Imbalance for PositiveAlphaImbalance { type Opposite = NegativeAlphaImbalance; fn zero() -> Self { Self::default() } fn drop_zero(self) -> Result<(), Self> { self.try_drop() } fn split(self, amount: AlphaBalance) -> (Self, Self) { let first = self.amount.min(amount); let second = self.amount.saturating_sub(first); ( Self::new(self.netuid, first), Self::new(self.netuid, second), ) } fn extract(&mut self, amount: AlphaBalance) -> Self { let extracted = self.amount.min(amount); self.amount = self.amount.saturating_sub(extracted); Self::new(self.netuid, extracted) } fn merge(self, other: Self) -> Self { match self.try_merge(other) { Ok(merged) => merged, Err((left, right)) => { log_netuid_mismatch("merge(positive)", left.netuid, right.netuid); left } } } fn subsume(&mut self, other: Self) { if self.netuid != other.netuid { log_netuid_mismatch("subsume(positive)", self.netuid, other.netuid); return; } self.amount = self.amount.saturating_add(other.amount); } fn offset(self, other: Self::Opposite) -> SameOrOther { if self.netuid != other.netuid { log_netuid_mismatch("offset(positive)", self.netuid, other.netuid); return SameOrOther::Same(self); } if self.amount > other.amount { SameOrOther::Same(Self::new( self.netuid, self.amount.saturating_sub(other.amount), )) } else if other.amount > self.amount { SameOrOther::Other(NegativeAlphaImbalance::new( self.netuid, other.amount.saturating_sub(self.amount), )) } else { SameOrOther::None } } fn peek(&self) -> AlphaBalance { self.amount } } impl Imbalance for NegativeAlphaImbalance { type Opposite = PositiveAlphaImbalance; fn zero() -> Self { Self::default() } fn drop_zero(self) -> Result<(), Self> { self.try_drop() } fn split(self, amount: AlphaBalance) -> (Self, Self) { let first = self.amount.min(amount); let second = self.amount.saturating_sub(first); ( Self::new(self.netuid, first), Self::new(self.netuid, second), ) } fn extract(&mut self, amount: AlphaBalance) -> Self { let extracted = self.amount.min(amount); self.amount = self.amount.saturating_sub(extracted); Self::new(self.netuid, extracted) } fn merge(self, other: Self) -> Self { match self.try_merge(other) { Ok(merged) => merged, Err((left, right)) => { log_netuid_mismatch("merge(negative)", left.netuid, right.netuid); left } } } fn subsume(&mut self, other: Self) { if self.netuid != other.netuid { log_netuid_mismatch("subsume(negative)", self.netuid, other.netuid); return; } self.amount = self.amount.saturating_add(other.amount); } fn offset(self, other: Self::Opposite) -> SameOrOther { if self.netuid != other.netuid { log_netuid_mismatch("offset(negative)", self.netuid, other.netuid); return SameOrOther::Same(self); } if self.amount > other.amount { SameOrOther::Same(Self::new( self.netuid, self.amount.saturating_sub(other.amount), )) } else if other.amount > self.amount { SameOrOther::Other(PositiveAlphaImbalance::new( self.netuid, other.amount.saturating_sub(self.amount), )) } else { SameOrOther::None } } fn peek(&self) -> AlphaBalance { self.amount } } /// Loose-coupling interface for alpha issuance operations. pub trait AlphaAssetsInterface { fn total_alpha_issuance(netuid: NetUid) -> AlphaBalance; fn mint_alpha(netuid: NetUid, amount: AlphaBalance) -> PositiveAlphaImbalance; fn burn_alpha(netuid: NetUid, amount: AlphaBalance) -> AlphaBalance; fn recycle_alpha(netuid: NetUid, amount: AlphaBalance) -> AlphaBalance; } impl AlphaAssetsInterface for () { fn total_alpha_issuance(_netuid: NetUid) -> AlphaBalance { AlphaBalance::ZERO } fn mint_alpha(netuid: NetUid, amount: AlphaBalance) -> PositiveAlphaImbalance { PositiveAlphaImbalance::new(netuid, amount) } fn burn_alpha(_netuid: NetUid, amount: AlphaBalance) -> AlphaBalance { amount } fn recycle_alpha(_netuid: NetUid, amount: AlphaBalance) -> AlphaBalance { amount } } #[deny(missing_docs)] #[frame_support::pallet] #[allow(clippy::expect_used)] pub mod pallet { use super::*; #[pallet::pallet] #[pallet::without_storage_info] pub struct Pallet(_); #[pallet::config] pub trait Config: frame_system::Config {} /// Total alpha issuance tracked by the pallet. #[pallet::storage] #[pallet::getter(fn total_alpha_issuance)] pub type TotalAlphaIssuance = StorageMap<_, Twox64Concat, NetUid, AlphaBalance, ValueQuery>; /// Total alpha burned per subnet through this pallet. #[pallet::storage] #[pallet::getter(fn alpha_burned)] pub type AlphaBurned = StorageMap<_, Twox64Concat, NetUid, AlphaBalance, ValueQuery>; /// Total alpha recycled per subnet through this pallet. #[pallet::storage] #[pallet::getter(fn alpha_recycled)] pub type AlphaRecycled = StorageMap<_, Twox64Concat, NetUid, AlphaBalance, ValueQuery>; } impl Pallet { pub fn mint_alpha(netuid: NetUid, amount: AlphaBalance) -> PositiveAlphaImbalance { if !amount.is_zero() { TotalAlphaIssuance::::mutate(netuid, |issuance| { *issuance = (*issuance).saturating_add(amount); }); } PositiveAlphaImbalance::new(netuid, amount) } pub fn burn_alpha(netuid: NetUid, amount: AlphaBalance) -> AlphaBalance { if !amount.is_zero() { AlphaBurned::::mutate(netuid, |burned| { *burned = (*burned).saturating_add(amount); }); } amount } pub fn recycle_alpha(netuid: NetUid, amount: AlphaBalance) -> AlphaBalance { if !amount.is_zero() { AlphaRecycled::::mutate(netuid, |recycled| { *recycled = (*recycled).saturating_add(amount); }); TotalAlphaIssuance::::mutate(netuid, |issuance| { *issuance = (*issuance).saturating_sub(amount); }); } amount } } impl AlphaAssetsInterface for Pallet { fn total_alpha_issuance(netuid: NetUid) -> AlphaBalance { TotalAlphaIssuance::::get(netuid) } fn mint_alpha(netuid: NetUid, amount: AlphaBalance) -> PositiveAlphaImbalance { Self::mint_alpha(netuid, amount) } fn burn_alpha(netuid: NetUid, amount: AlphaBalance) -> AlphaBalance { Self::burn_alpha(netuid, amount) } fn recycle_alpha(netuid: NetUid, amount: AlphaBalance) -> AlphaBalance { Self::recycle_alpha(netuid, amount) } }