Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion pallets/subtensor/src/coinbase/block_step.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ impl<T: Config + pallet_drand::Config> Pallet<T> {
Self::try_set_pending_children(block_number);
// --- 8. Run auto-claim root divs.
Self::run_auto_claim_root_divs(last_block_hash);
// --- 9. Populate root coldkey maps.
// --- 9. Update root alpha flow EMAs for all subnets
Self::update_root_alpha_flow_emas();
// --- 10. Populate root coldkey maps.
Self::populate_root_coldkey_staking_maps();

// Return ok.
Expand Down Expand Up @@ -311,4 +313,14 @@ impl<T: Config + pallet_drand::Config> Pallet<T> {
};
}
}

pub fn update_root_alpha_flow_emas() {
let subnets: Vec<NetUid> = Self::get_all_subnet_netuids();
for netuid in subnets.into_iter().filter(|netuid| *netuid != NetUid::ROOT) {
// Update root alpha inflow EMA (updates if needed based on block)
Self::get_ema_root_alpha_inflow(netuid);
// Update root alpha outflow EMA (updates if needed based on block)
Self::get_ema_root_alpha_outflow(netuid);
}
}
}
4 changes: 4 additions & 0 deletions pallets/subtensor/src/coinbase/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,10 @@ impl<T: Config> Pallet<T> {
SubnetMovingPrice::<T>::remove(netuid);
SubnetTaoFlow::<T>::remove(netuid);
SubnetEmaTaoFlow::<T>::remove(netuid);
SubnetRootAlphaInflow::<T>::remove(netuid);
SubnetRootAlphaOutflow::<T>::remove(netuid);
SubnetEmaRootAlphaInflow::<T>::remove(netuid);
SubnetEmaRootAlphaOutflow::<T>::remove(netuid);
SubnetTaoProvided::<T>::remove(netuid);

// --- 13. Token / mechanism / registration toggles.
Expand Down
18 changes: 15 additions & 3 deletions pallets/subtensor/src/coinbase/run_coinbase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ impl<T: Config> Pallet<T> {
tao_in: &BTreeMap<NetUid, U96F32>,
alpha_in: &BTreeMap<NetUid, U96F32>,
excess_tao: &BTreeMap<NetUid, U96F32>,
) {
) -> BTreeMap<NetUid, AlphaCurrency> {
let mut chain_bought_alpha: BTreeMap<NetUid, AlphaCurrency> = BTreeMap::new();

for netuid_i in subnets_to_emit_to.iter() {
let tao_in_i: TaoCurrency =
tou64!(*tao_in.get(netuid_i).unwrap_or(&asfloat!(0))).into();
Expand All @@ -78,7 +80,8 @@ impl<T: Config> Pallet<T> {
);
if let Ok(buy_swap_result_ok) = buy_swap_result {
let bought_alpha: AlphaCurrency = buy_swap_result_ok.amount_paid_out.into();
Self::recycle_subnet_alpha(*netuid_i, bought_alpha);
// Instead of recycling, add to chain_bought_alpha for distribution to root dividends
chain_bought_alpha.insert(*netuid_i, bought_alpha);
}
}

Expand Down Expand Up @@ -109,6 +112,8 @@ impl<T: Config> Pallet<T> {
.saturating_add(difference_tao.into());
});
}

chain_bought_alpha
}

pub fn get_subnet_terms(
Expand Down Expand Up @@ -179,7 +184,7 @@ impl<T: Config> Pallet<T> {
log::debug!("excess_amount: {excess_amount:?}");

// --- 2. Inject TAO and ALPHA to pool and swap with excess TAO.
Self::inject_and_maybe_swap(subnets_to_emit_to, &tao_in, &alpha_in, &excess_amount);
let chain_bought_alpha = Self::inject_and_maybe_swap(&subnets_to_emit_to, &tao_in, &alpha_in, &excess_amount);

// --- 3. Inject ALPHA for participants.
let cut_percent: U96F32 = Self::get_float_subnet_owner_cut();
Expand Down Expand Up @@ -239,6 +244,13 @@ impl<T: Config> Pallet<T> {
PendingRootAlphaDivs::<T>::mutate(*netuid_i, |total| {
*total = total.saturating_add(tou64!(root_alpha).into());
});

// Add chain-bought alpha to pending root dividends
if let Some(chain_bought) = chain_bought_alpha.get(netuid_i) {
PendingRootAlphaDivs::<T>::mutate(*netuid_i, |total| {
*total = total.saturating_add(*chain_bought);
});
}
} else {
// If we are not selling the root alpha, we should recycle it.
Self::recycle_subnet_alpha(*netuid_i, AlphaCurrency::from(tou64!(root_alpha)));
Expand Down
64 changes: 64 additions & 0 deletions pallets/subtensor/src/coinbase/subnet_emissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,70 @@ impl<T: Config> Pallet<T> {
SubnetTaoFlow::<T>::remove(netuid);
}

pub fn record_root_alpha_inflow(netuid: NetUid, amount: AlphaCurrency) {
SubnetRootAlphaInflow::<T>::mutate(netuid, |flow| {
*flow = flow.saturating_add(u64::from(amount) as i64);
});
}

pub fn record_root_alpha_outflow(netuid: NetUid, amount: AlphaCurrency) {
SubnetRootAlphaOutflow::<T>::mutate(netuid, |flow| {
*flow = flow.saturating_sub(u64::from(amount) as i64);
});
}

pub fn reset_root_alpha_inflow(netuid: NetUid) {
SubnetRootAlphaInflow::<T>::remove(netuid);
}

pub fn reset_root_alpha_outflow(netuid: NetUid) {
SubnetRootAlphaOutflow::<T>::remove(netuid);
}

// Update SubnetEmaRootAlphaInflow if needed and return its value for the current block
pub fn get_ema_root_alpha_inflow(netuid: NetUid) -> I64F64 {
let current_block: u64 = Self::get_current_block_as_u64();
let block_flow = I64F64::saturating_from_num(SubnetRootAlphaInflow::<T>::get(netuid));
let (last_block, last_block_ema) =
SubnetEmaRootAlphaInflow::<T>::get(netuid).unwrap_or((0, I64F64::saturating_from_num(0)));

if last_block != current_block {
let flow_alpha = I64F64::saturating_from_num(RootAlphaFlowEmaSmoothingFactor::<T>::get())
.safe_div(I64F64::saturating_from_num(i64::MAX));
let one = I64F64::saturating_from_num(1);
let ema_flow = (one.saturating_sub(flow_alpha))
.saturating_mul(last_block_ema)
.saturating_add(flow_alpha.saturating_mul(block_flow));
SubnetEmaRootAlphaInflow::<T>::insert(netuid, (current_block, ema_flow));
Self::reset_root_alpha_inflow(netuid);
ema_flow
} else {
last_block_ema
}
}

// Update SubnetEmaRootAlphaOutflow if needed and return its value for the current block
pub fn get_ema_root_alpha_outflow(netuid: NetUid) -> I64F64 {
let current_block: u64 = Self::get_current_block_as_u64();
let block_flow = I64F64::saturating_from_num(SubnetRootAlphaOutflow::<T>::get(netuid));
let (last_block, last_block_ema) =
SubnetEmaRootAlphaOutflow::<T>::get(netuid).unwrap_or((0, I64F64::saturating_from_num(0)));

if last_block != current_block {
let flow_alpha = I64F64::saturating_from_num(RootAlphaFlowEmaSmoothingFactor::<T>::get())
.safe_div(I64F64::saturating_from_num(i64::MAX));
let one = I64F64::saturating_from_num(1);
let ema_flow = (one.saturating_sub(flow_alpha))
.saturating_mul(last_block_ema)
.saturating_add(flow_alpha.saturating_mul(block_flow));
SubnetEmaRootAlphaOutflow::<T>::insert(netuid, (current_block, ema_flow));
Self::reset_root_alpha_outflow(netuid);
ema_flow
} else {
last_block_ema
}
}

// Update SubnetEmaTaoFlow if needed and return its value for
// the current block
#[allow(dead_code)]
Expand Down
63 changes: 55 additions & 8 deletions pallets/subtensor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,16 +329,11 @@ pub mod pallet {
)]
/// Enum for the per-coldkey root claim setting.
pub enum RootClaimTypeEnum {
/// Swap any alpha emission for TAO.
/// Keep all alpha emission (manual claim only).
#[default]
Swap,
/// Keep all alpha emission.
Keep,
/// Keep all alpha emission for specified subnets.
KeepSubnets {
/// Subnets to keep alpha emissions (swap everything else).
subnets: BTreeSet<NetUid>,
},
/// Keep all alpha emission (auto-claim enabled).
AutoKeep,
}

/// Default minimum root claim amount.
Expand Down Expand Up @@ -1457,6 +1452,45 @@ pub mod pallet {
pub type SubnetEmaTaoFlow<T: Config> =
StorageMap<_, Identity, NetUid, (u64, I64F64), OptionQuery>;

/// Temporary storage for root alpha inflow per subnet (reset each block after EMA update).
#[pallet::storage]
pub type SubnetRootAlphaInflow<T: Config> =
StorageMap<_, Identity, NetUid, i64, ValueQuery, DefaultZeroI64<T>>;

/// Temporary storage for root alpha outflow per subnet (reset each block after EMA update).
#[pallet::storage]
pub type SubnetRootAlphaOutflow<T: Config> =
StorageMap<_, Identity, NetUid, i64, ValueQuery, DefaultZeroI64<T>>;

/// EMA of root alpha inflow (claims) per subnet.
/// Tracks EMA of alpha claimed from root dividends (in alpha terms, no price multiplication).
#[pallet::storage]
pub type SubnetEmaRootAlphaInflow<T: Config> =
StorageMap<_, Identity, NetUid, (u64, I64F64), OptionQuery>;

/// EMA of root alpha outflow (sells) per subnet.
/// Tracks EMA of claimed root alpha that is sold/transferred (in alpha terms, no price multiplication).
#[pallet::storage]
pub type SubnetEmaRootAlphaOutflow<T: Config> =
StorageMap<_, Identity, NetUid, (u64, I64F64), OptionQuery>;

/// Tracks claimed root alpha per hotkey, coldkey, and netuid for EMA tracking.
/// Similar to RootClaimed, but unlike RootClaimed it is updated when actual alpha is given
/// to the coldkey (on claim_root) and when it leaves the wallet (unstake, transfer, etc.).
/// Unlike RootClaimed, we don't update it on staking in/out of hotkeys.
#[pallet::storage]
pub type RootClaimedForEma<T: Config> = StorageNMap<
_,
(
NMapKey<Identity, NetUid>, // subnet
NMapKey<Blake2_128Concat, T::AccountId>, // hotkey
NMapKey<Blake2_128Concat, T::AccountId>, // coldkey
),
AlphaCurrency,
ValueQuery,
DefaultZeroAlpha<T>,
>;

/// Default value for flow cutoff.
#[pallet::type_value]
pub fn DefaultFlowCutoff<T: Config>() -> I64F64 {
Expand Down Expand Up @@ -1484,6 +1518,15 @@ pub mod pallet {
29_597_889_189_277
}
#[pallet::type_value]
/// Default value for root alpha flow EMA smoothing.
pub fn DefaultRootAlphaFlowEmaSmoothingFactor<T: Config>() -> u64 {
// Example values:
// half-life factor value i64 normalized (x 2^63)
// 216000 (1 month) --> 0.000003209009576 ( 29_597_889_189_277)
// 50400 (1 week) --> 0.000013752825678 (126_847_427_788_335)
29_597_889_189_277
}
#[pallet::type_value]
/// Flow EMA smoothing half-life.
pub fn FlowHalfLife<T: Config>() -> u64 {
216_000
Expand All @@ -1492,6 +1535,10 @@ pub mod pallet {
/// --- ITEM --> Flow EMA smoothing factor (flow alpha), u64 normalized
pub type FlowEmaSmoothingFactor<T: Config> =
StorageValue<_, u64, ValueQuery, DefaultFlowEmaSmoothingFactor<T>>;
#[pallet::storage]
/// --- ITEM --> Root alpha flow EMA smoothing factor (flow alpha), u64 normalized
pub type RootAlphaFlowEmaSmoothingFactor<T: Config> =
StorageValue<_, u64, ValueQuery, DefaultRootAlphaFlowEmaSmoothingFactor<T>>;

/// ============================
/// ==== Global Parameters =====
Expand Down
4 changes: 3 additions & 1 deletion pallets/subtensor/src/macros/hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ mod hooks {
//Migrate CRV3 to TimelockedCommits
.saturating_add(migrations::migrate_crv3_v2_to_timelocked::migrate_crv3_v2_to_timelocked::<T>())
// Migrate to fix root counters
.saturating_add(migrations::migrate_fix_root_tao_and_alpha_in::migrate_fix_root_tao_and_alpha_in::<T>())
.saturating_add(migrations::migrate_fix_root_tao_and_alpha_in::migrate_fix_root_tao_and_alpha_in::<T>())
// Migrate root claim types to Keep
.saturating_add(migrations::migrate_root_claim_type_to_keep::migrate_root_claim_type_to_keep::<T>())
// Migrate last block rate limiting storage items
.saturating_add(migrations::migrate_rate_limiting_last_blocks::migrate_obsolete_rate_limiting_last_blocks_storage::<T>())
// Re-encode rate limit keys after introducing OwnerHyperparamUpdate variant
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use alloc::string::String;
use frame_support::IterableStorageMap;
use frame_support::weights::Weight;

use super::*;

pub fn migrate_root_claim_type_to_keep<T: Config>() -> Weight {
let migration_name = b"migrate_root_claim_type_to_keep".to_vec();

// Initialize the weight with one read operation.
let mut weight = T::DbWeight::get().reads(1);

// Check if the migration has already run
if HasMigrationRun::<T>::get(&migration_name) {
log::info!(
"Migration '{:?}' has already run. Skipping.",
String::from_utf8_lossy(&migration_name)
);
return weight;
}
log::info!(
"Running migration '{}'",
String::from_utf8_lossy(&migration_name)
);

// Iterate through all RootClaimType entries and convert to Keep
let mut reads = 0u64;
let mut writes = 0u64;

for (coldkey, old_type) in RootClaimType::<T>::iter() {
reads += 1;
// Convert any old type to Keep
// Old variants: Swap, Keep, KeepSubnets { subnets }
// New variants: Keep, AutoKeep
// All old types become Keep (default, manual claim only)
RootClaimType::<T>::insert(coldkey, RootClaimTypeEnum::Keep);
writes += 1;
}

weight = weight.saturating_add(T::DbWeight::get().reads_writes(reads, writes));

// Mark the migration as completed
HasMigrationRun::<T>::insert(&migration_name, true);
weight = weight.saturating_add(T::DbWeight::get().writes(1));

log::info!(
"Migration '{:?}' completed. Converted {} root claim types to Keep.",
String::from_utf8_lossy(&migration_name),
writes
);

// Return the migration weight.
weight
}

1 change: 1 addition & 0 deletions pallets/subtensor/src/migrations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub mod migrate_rao;
pub mod migrate_rate_limit_keys;
pub mod migrate_rate_limiting_last_blocks;
pub mod migrate_remove_commitments_rate_limit;
pub mod migrate_root_claim_type_to_keep;
pub mod migrate_remove_network_modality;
pub mod migrate_remove_old_identity_maps;
pub mod migrate_remove_stake_map;
Expand Down
Loading