diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 2a13a18aa..1e7d6991c 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -1615,7 +1615,7 @@ mod pallet_benchmarks { ); let pending_root_alpha = 10_000_000u64; - Subtensor::::drain_pending_emission( + Subtensor::::distribute_emission( netuid, AlphaCurrency::ZERO, pending_root_alpha.into(), diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 642a7f18a..80965f0bb 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -348,7 +348,6 @@ impl Pallet { RAORecycledForRegistration::::remove(netuid); MaxRegistrationsPerBlock::::remove(netuid); WeightsVersionKey::::remove(netuid); - PendingRootAlphaDivs::::remove(netuid); // --- 17. Subtoken / feature flags. LiquidAlphaOn::::remove(netuid); diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 53ac3c6ac..d998ad1f7 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -34,13 +34,15 @@ impl Pallet { // 2. Get subnets to emit to and emissions let subnet_emissions = Self::get_subnet_block_emissions(&subnets, block_emission); let subnets_to_emit_to: Vec = subnet_emissions.keys().copied().collect(); + let root_sell_flag = Self::get_network_root_sell_flag(&subnets_to_emit_to); // --- 3. Get subnet terms (tao_in, alpha_in, and alpha_out) // Computation is described in detail in the dtao whitepaper. let mut tao_in: BTreeMap = BTreeMap::new(); let mut alpha_in: BTreeMap = BTreeMap::new(); let mut alpha_out: BTreeMap = BTreeMap::new(); - let mut is_subsidized: BTreeMap = BTreeMap::new(); + let mut excess_tao: BTreeMap = BTreeMap::new(); + // Only calculate for subnets that we are emitting to. for netuid_i in subnets_to_emit_to.iter() { // Get subnet price. @@ -52,6 +54,9 @@ impl Pallet { .copied() .unwrap_or(asfloat!(0)); log::debug!("default_tao_in_i: {default_tao_in_i:?}"); + let default_alpha_in_i: U96F32 = + default_tao_in_i.safe_div_or(price_i, U96F32::saturating_from_num(0.0)); + log::debug!("default_alpha_in_i: {default_alpha_in_i:?}"); // Get alpha_emission total let alpha_emission_i: U96F32 = asfloat!( Self::get_block_emission_for_issuance(Self::get_alpha_issuance(*netuid_i).into()) @@ -62,32 +67,16 @@ impl Pallet { // Get initial alpha_in let mut alpha_in_i: U96F32; let mut tao_in_i: U96F32; - let tao_in_ratio: U96F32 = default_tao_in_i.safe_div_or( - U96F32::saturating_from_num(block_emission), - U96F32::saturating_from_num(0.0), - ); - if price_i < tao_in_ratio { - tao_in_i = price_i.saturating_mul(U96F32::saturating_from_num(block_emission)); - alpha_in_i = block_emission; + + if default_alpha_in_i > alpha_emission_i { + alpha_in_i = alpha_emission_i; + tao_in_i = alpha_in_i.saturating_mul(price_i); let difference_tao: U96F32 = default_tao_in_i.saturating_sub(tao_in_i); - // Difference becomes buy. - let buy_swap_result = Self::swap_tao_for_alpha( - *netuid_i, - tou64!(difference_tao).into(), - T::SwapInterface::max_price(), - true, - ); - if let Ok(buy_swap_result_ok) = buy_swap_result { - let bought_alpha = AlphaCurrency::from(buy_swap_result_ok.amount_paid_out); - SubnetAlphaOut::::mutate(*netuid_i, |total| { - *total = total.saturating_sub(bought_alpha); - }); - } - is_subsidized.insert(*netuid_i, true); + excess_tao.insert(*netuid_i, difference_tao); } else { tao_in_i = default_tao_in_i; - alpha_in_i = tao_in_i.safe_div_or(price_i, alpha_emission_i); - is_subsidized.insert(*netuid_i, false); + alpha_in_i = default_alpha_in_i; + excess_tao.insert(*netuid_i, U96F32::from_num(0.0)); } log::debug!("alpha_in_i: {alpha_in_i:?}"); @@ -109,10 +98,34 @@ impl Pallet { log::debug!("tao_in: {tao_in:?}"); log::debug!("alpha_in: {alpha_in:?}"); log::debug!("alpha_out: {alpha_out:?}"); + log::debug!("excess_tao: {excess_tao:?}"); + log::debug!("root_sell_flag: {root_sell_flag:?}"); + + // --- 4. Inject and buy Alpha with any excess TAO. + 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(); + let alpha_in_i: AlphaCurrency = + AlphaCurrency::from(tou64!(*alpha_in.get(netuid_i).unwrap_or(&asfloat!(0)))); + let difference_tao: U96F32 = *excess_tao.get(netuid_i).unwrap_or(&asfloat!(0)); + + T::SwapInterface::adjust_protocol_liquidity(*netuid_i, tao_in_i, alpha_in_i); + + if difference_tao > asfloat!(0) { + let buy_swap_result = Self::swap_tao_for_alpha( + *netuid_i, + tou64!(difference_tao).into(), + T::SwapInterface::max_price(), + true, + ); + if let Ok(buy_swap_result_ok) = buy_swap_result { + let bought_alpha = AlphaCurrency::from(buy_swap_result_ok.amount_paid_out); + Self::recycle_subnet_alpha(*netuid_i, bought_alpha); + } + } + } - // --- 4. Injection. - // Actually perform the injection of alpha_in, alpha_out and tao_in into the subnet pool. - // This operation changes the pool liquidity each block. + // --- 5. Update counters for netuid_i in subnets_to_emit_to.iter() { // Inject Alpha in. let alpha_in_i = @@ -138,14 +151,15 @@ impl Pallet { TotalStake::::mutate(|total| { *total = total.saturating_add(tao_in_i.into()); }); + + let difference_tao: U96F32 = *excess_tao.get(netuid_i).unwrap_or(&asfloat!(0)); TotalIssuance::::mutate(|total| { *total = total.saturating_add(tao_in_i.into()); + *total = total.saturating_add(tou64!(difference_tao).into()); }); - // Adjust protocol liquidity based on new reserves - T::SwapInterface::adjust_protocol_liquidity(*netuid_i, tao_in_i, alpha_in_i); } - // --- 5. Compute owner cuts and remove them from alpha_out remaining. + // --- 6. Compute owner cuts and remove them from alpha_out remaining. // Remove owner cuts here so that we can properly seperate root dividends in the next step. // Owner cuts are accumulated and then fed to the drain at the end of this func. let cut_percent: U96F32 = Self::get_float_subnet_owner_cut(); @@ -174,51 +188,55 @@ impl Pallet { let tao_weight: U96F32 = root_tao.saturating_mul(Self::get_tao_weight()); log::debug!("tao_weight: {tao_weight:?}"); - // --- 6. Seperate out root dividends in alpha and keep them. + // --- 7. Seperate out root dividends in alpha and keep them. // Then accumulate those dividends for later. for netuid_i in subnets_to_emit_to.iter() { // Get remaining alpha out. let alpha_out_i: U96F32 = *alpha_out.get(netuid_i).unwrap_or(&asfloat!(0.0)); log::debug!("alpha_out_i: {alpha_out_i:?}"); - // Get total ALPHA on subnet. - let alpha_issuance: U96F32 = asfloat!(Self::get_alpha_issuance(*netuid_i)); - log::debug!("alpha_issuance: {alpha_issuance:?}"); - // Get root proportional dividends. - let root_proportion: U96F32 = tao_weight - .checked_div(tao_weight.saturating_add(alpha_issuance)) - .unwrap_or(asfloat!(0.0)); - log::debug!("root_proportion: {root_proportion:?}"); + // Get root proportion of alpha_out dividends. - let root_alpha: U96F32 = root_proportion - .saturating_mul(alpha_out_i) // Total alpha emission per block remaining. - .saturating_mul(asfloat!(0.5)); // 50% to validators. + let mut root_alpha: U96F32 = asfloat!(0.0); + if root_sell_flag { + // Get ALPHA issuance. + let alpha_issuance: U96F32 = asfloat!(Self::get_alpha_issuance(*netuid_i)); + log::debug!("alpha_issuance: {alpha_issuance:?}"); + + // Get root proportional dividends. + let root_proportion: U96F32 = tao_weight + .checked_div(tao_weight.saturating_add(alpha_issuance)) + .unwrap_or(asfloat!(0.0)); + log::debug!("root_proportion: {root_proportion:?}"); + + // Get root alpha from root prop. + root_alpha = root_proportion + .saturating_mul(alpha_out_i) // Total alpha emission per block remaining. + .saturating_mul(asfloat!(0.5)); // 50% to validators. + PendingRootAlphaDivs::::mutate(*netuid_i, |total| { + *total = total.saturating_add(tou64!(root_alpha).into()); + }); + } // Remove root alpha from alpha_out. log::debug!("root_alpha: {root_alpha:?}"); + // Get pending alpha as original alpha_out - root_alpha. let pending_alpha: U96F32 = alpha_out_i.saturating_sub(root_alpha); log::debug!("pending_alpha: {pending_alpha:?}"); - let subsidized: bool = *is_subsidized.get(netuid_i).unwrap_or(&false); - if !subsidized { - PendingRootAlphaDivs::::mutate(*netuid_i, |total| { - *total = total.saturating_add(tou64!(root_alpha).into()); - }); - } - // Accumulate alpha emission in pending. PendingEmission::::mutate(*netuid_i, |total| { *total = total.saturating_add(tou64!(pending_alpha).into()); }); } - // --- 7. Update moving prices after using them in the emission calculation. + // --- 8. Update moving prices after using them in the emission calculation. // Only update price EMA for subnets that we emit to. for netuid_i in subnets_to_emit_to.iter() { // Update moving prices after using them above. Self::update_moving_price(*netuid_i); } - // --- 8. Drain pending emission through the subnet based on tempo. + // --- 9. Drain pending emission through the subnet based on tempo. // Run the epoch for *all* subnets, even if we don't emit anything. for &netuid in subnets.iter() { // Reveal matured weights. @@ -245,8 +263,8 @@ impl Pallet { let owner_cut = PendingOwnerCut::::get(netuid); PendingOwnerCut::::insert(netuid, AlphaCurrency::ZERO); - // Drain pending root alpha divs, alpha emission, and owner cut. - Self::drain_pending_emission(netuid, pending_alpha, pending_root_alpha, owner_cut); + // Distribute the emission. + Self::distribute_emission(netuid, pending_alpha, pending_root_alpha, owner_cut); } else { // Increment BlocksSinceLastStep::::mutate(netuid, |total| *total = total.saturating_add(1)); @@ -254,6 +272,17 @@ impl Pallet { } } + pub fn get_network_root_sell_flag(subnets_to_emit_to: &[NetUid]) -> bool { + let total_ema_price: U96F32 = subnets_to_emit_to + .iter() + .map(|netuid| Self::get_moving_alpha_price(*netuid)) + .sum(); + + // If the total EMA price is less than or equal to 1 + // then we WILL NOT root sell. + total_ema_price > U96F32::saturating_from_num(1) + } + pub fn calculate_dividends_and_incentives( netuid: NetUid, hotkey_emission: Vec<(T::AccountId, AlphaCurrency, AlphaCurrency)>, @@ -482,6 +511,7 @@ impl Pallet { let destination = maybe_dest.clone().unwrap_or(hotkey.clone()); if let Some(dest) = maybe_dest { + log::debug!("incentives: auto staking {incentive:?} to {dest:?}"); Self::deposit_event(Event::::AutoStakeAdded { netuid, destination: dest, @@ -490,6 +520,7 @@ impl Pallet { incentive, }); } + Self::increase_stake_for_hotkey_and_coldkey_on_subnet( &destination, &owner, @@ -600,7 +631,7 @@ impl Pallet { (incentives, (alpha_dividends, root_alpha_dividends)) } - pub fn drain_pending_emission( + pub fn distribute_emission( netuid: NetUid, pending_alpha: AlphaCurrency, pending_root_alpha: AlphaCurrency, @@ -611,10 +642,11 @@ impl Pallet { ); let tao_weight = Self::get_tao_weight(); + let total_alpha = pending_alpha.saturating_add(pending_root_alpha); // Run the epoch. let hotkey_emission: Vec<(T::AccountId, AlphaCurrency, AlphaCurrency)> = - Self::epoch_with_mechanisms(netuid, pending_alpha.saturating_add(pending_root_alpha)); + Self::epoch_with_mechanisms(netuid, total_alpha); log::debug!("hotkey_emission: {hotkey_emission:?}"); // Compute the pending validator alpha. @@ -630,8 +662,7 @@ impl Pallet { log::debug!("incentive_sum: {incentive_sum:?}"); let pending_validator_alpha = if !incentive_sum.is_zero() { - pending_alpha - .saturating_add(pending_root_alpha) + total_alpha .saturating_div(2.into()) .saturating_sub(pending_root_alpha) } else { diff --git a/pallets/subtensor/src/coinbase/subnet_emissions.rs b/pallets/subtensor/src/coinbase/subnet_emissions.rs index 184f27a4b..895a908ba 100644 --- a/pallets/subtensor/src/coinbase/subnet_emissions.rs +++ b/pallets/subtensor/src/coinbase/subnet_emissions.rs @@ -1,5 +1,4 @@ use super::*; -use crate::alloc::borrow::ToOwned; use alloc::collections::BTreeMap; use safe_math::FixedExt; use substrate_fixed::transcendental::{exp, ln}; @@ -7,21 +6,21 @@ use substrate_fixed::types::{I32F32, I64F64, U64F64, U96F32}; use subtensor_swap_interface::SwapHandler; impl Pallet { - pub fn get_subnet_block_emissions( - subnets: &[NetUid], - block_emission: U96F32, - ) -> BTreeMap { + pub fn get_subnets_to_emit_to(subnets: &[NetUid]) -> Vec { // Filter out subnets with no first emission block number. - let subnets_to_emit_to: Vec = subnets - .to_owned() - .clone() - .into_iter() + subnets + .iter() .filter(|netuid| FirstEmissionBlockNumber::::get(*netuid).is_some()) - .collect(); - log::debug!("Subnets to emit to: {subnets_to_emit_to:?}"); + .copied() + .collect() + } + pub fn get_subnet_block_emissions( + subnets_to_emit_to: &[NetUid], + block_emission: U96F32, + ) -> BTreeMap { // Get subnet TAO emissions. - let shares = Self::get_shares(&subnets_to_emit_to); + let shares = Self::get_shares(subnets_to_emit_to); log::debug!("Subnet emission shares = {shares:?}"); shares diff --git a/pallets/subtensor/src/staking/remove_stake.rs b/pallets/subtensor/src/staking/remove_stake.rs index 5771029f7..7fdd33555 100644 --- a/pallets/subtensor/src/staking/remove_stake.rs +++ b/pallets/subtensor/src/staking/remove_stake.rs @@ -447,19 +447,13 @@ impl Pallet { let should_refund_owner: bool = reg_at < start_block; // 3) Compute owner's received emission in TAO at current price (ONLY if we may refund). - // Emission:: is Vec. We: - // - sum emitted α, + // We: + // - get the current alpha issuance, // - apply owner fraction to get owner α, // - price that α using a *simulated* AMM swap. let mut owner_emission_tao = TaoCurrency::ZERO; if should_refund_owner && !lock_cost.is_zero() { - let total_emitted_alpha_u128: u128 = - Emission::::get(netuid) - .into_iter() - .fold(0u128, |acc, e_alpha| { - let e_u64: u64 = Into::::into(e_alpha); - acc.saturating_add(e_u64 as u128) - }); + let total_emitted_alpha_u128: u128 = Self::get_alpha_issuance(netuid).to_u64() as u128; if total_emitted_alpha_u128 > 0 { let owner_fraction: U96F32 = Self::get_float_subnet_owner_cut(); @@ -469,22 +463,12 @@ impl Pallet { .saturating_to_num::(); owner_emission_tao = if owner_alpha_u64 > 0 { - let order = GetTaoForAlpha::with_amount(owner_alpha_u64); - match T::SwapInterface::sim_swap(netuid.into(), order) { - Ok(sim) => TaoCurrency::from(sim.amount_paid_out), - Err(e) => { - log::debug!( - "destroy_alpha_in_out_stakes: sim_swap owner α→τ failed (netuid={netuid:?}, alpha={owner_alpha_u64}, err={e:?}); falling back to price multiply.", - ); - let cur_price: U96F32 = - T::SwapInterface::current_alpha_price(netuid.into()); - let val_u64 = U96F32::from_num(owner_alpha_u64) - .saturating_mul(cur_price) - .floor() - .saturating_to_num::(); - TaoCurrency::from(val_u64) - } - } + let cur_price: U96F32 = T::SwapInterface::current_alpha_price(netuid.into()); + let val_u64 = U96F32::from_num(owner_alpha_u64) + .saturating_mul(cur_price) + .floor() + .saturating_to_num::(); + TaoCurrency::from(val_u64) } else { TaoCurrency::ZERO }; diff --git a/pallets/subtensor/src/tests/children.rs b/pallets/subtensor/src/tests/children.rs index e9b8c2aa6..14b7a0b29 100644 --- a/pallets/subtensor/src/tests/children.rs +++ b/pallets/subtensor/src/tests/children.rs @@ -2827,7 +2827,7 @@ fn test_set_weights_no_parent() { }); } -/// Test that drain_pending_emission sends childkey take fully to the nominators if childkey +/// Test that distribute_emission sends childkey take fully to the nominators if childkey /// doesn't have its own stake, independently of parent hotkey take. /// cargo test --package pallet-subtensor --lib -- tests::children::test_childkey_take_drain --exact --show-output #[allow(clippy::assertions_on_constants)] diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index e73417a32..951dfa0ae 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -6,8 +6,8 @@ use crate::tests::mock::{ use crate::{ DefaultMinRootClaimAmount, Error, MAX_NUM_ROOT_CLAIMS, MAX_ROOT_CLAIM_THRESHOLD, NetworksAdded, NumRootClaim, NumStakingColdkeys, PendingRootAlphaDivs, RootClaimable, RootClaimableThreshold, - StakingColdkeys, StakingColdkeysByIndex, SubnetAlphaIn, SubnetMechanism, SubnetTAO, - SubtokenEnabled, Tempo, pallet, + StakingColdkeys, StakingColdkeysByIndex, SubnetAlphaIn, SubnetMechanism, SubnetMovingPrice, + SubnetTAO, SubtokenEnabled, Tempo, pallet, }; use crate::{RootClaimType, RootClaimTypeEnum, RootClaimed}; use approx::assert_abs_diff_eq; @@ -18,7 +18,7 @@ use frame_support::{assert_err, assert_noop, assert_ok}; use sp_core::{H256, U256}; use sp_runtime::DispatchError; use std::collections::BTreeSet; -use substrate_fixed::types::{I96F32, U96F32}; +use substrate_fixed::types::{I96F32, U64F64, U96F32}; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; use subtensor_swap_interface::SwapHandler; @@ -72,7 +72,7 @@ fn test_claim_root_with_drain_emissions() { // Distribute pending root alpha let pending_root_alpha = 1_000_000u64; - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, pending_root_alpha.into(), @@ -140,7 +140,7 @@ fn test_claim_root_with_drain_emissions() { // Distribute pending root alpha (round 2) - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, pending_root_alpha.into(), @@ -241,7 +241,7 @@ fn test_claim_root_adding_stake_proportionally_for_two_stakers() { // Distribute pending root alpha let pending_root_alpha = 10_000_000u64; - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, pending_root_alpha.into(), @@ -342,7 +342,7 @@ fn test_claim_root_adding_stake_disproportionally_for_two_stakers() { // Distribute pending root alpha let pending_root_alpha = 10_000_000u64; - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, pending_root_alpha.into(), @@ -433,7 +433,7 @@ fn test_claim_root_with_changed_stake() { // Distribute pending root alpha let pending_root_alpha = 10_000_000u64; - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, pending_root_alpha.into(), @@ -485,7 +485,7 @@ fn test_claim_root_with_changed_stake() { // Distribute pending root alpha let pending_root_alpha = 10_000_000u64; - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, pending_root_alpha.into(), @@ -538,7 +538,7 @@ fn test_claim_root_with_changed_stake() { // Distribute pending root alpha let pending_root_alpha = 10_000_000u64; - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, pending_root_alpha.into(), @@ -630,7 +630,7 @@ fn test_claim_root_with_drain_emissions_and_swap_claim_type() { // Distribute pending root alpha let pending_root_alpha = 10_000_000u64; - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, pending_root_alpha.into(), @@ -674,7 +674,7 @@ fn test_claim_root_with_drain_emissions_and_swap_claim_type() { // Distribute and claim pending root alpha (round 2) - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, pending_root_alpha.into(), @@ -710,7 +710,7 @@ fn test_claim_root_with_drain_emissions_and_swap_claim_type() { ); // Distribute and claim pending root alpha (round 3) - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, pending_root_alpha.into(), @@ -779,6 +779,18 @@ fn test_claim_root_with_run_coinbase() { initial_total_hotkey_alpha.into(), ); + // Set moving price > 1.0 and price > 1.0 + // So we turn ON root sell + SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); + pallet_subtensor_swap::AlphaSqrtPrice::::insert( + netuid, + U64F64::saturating_from_num(10.0), + ); + + // Make sure we are root selling, so we have root alpha divs. + let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); + assert!(root_sell_flag, "Root sell flag should be true"); + // Distribute pending root alpha let initial_stake: u64 = @@ -878,6 +890,18 @@ fn test_claim_root_with_block_emissions() { ); SubtensorModule::maybe_add_coldkey_index(&coldkey); + // Set moving price > 1.0 and price > 1.0 + // So we turn ON root sell + SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); + pallet_subtensor_swap::AlphaSqrtPrice::::insert( + netuid, + U64F64::saturating_from_num(10.0), + ); + + // Make sure we are root selling, so we have root alpha divs. + let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); + assert!(root_sell_flag, "Root sell flag should be true"); + let initial_total_hotkey_alpha = 10_000_000u64; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, @@ -991,12 +1015,27 @@ fn test_claim_root_coinbase_distribution() { let initial_alpha_issuance = SubtensorModule::get_alpha_issuance(netuid); let alpha_emissions: AlphaCurrency = 1_000_000_000u64.into(); - // Check total issuance (saved to pending alpha divs) + // Set moving price > 1.0 and price > 1.0 + // So we turn ON root sell + SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); + pallet_subtensor_swap::AlphaSqrtPrice::::insert( + netuid, + U64F64::saturating_from_num(10.0), + ); + + // Make sure we are root selling, so we have root alpha divs. + let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); + assert!(root_sell_flag, "Root sell flag should be true"); + // Check total issuance (saved to pending alpha divs) run_to_block(2); let alpha_issuance = SubtensorModule::get_alpha_issuance(netuid); - assert_eq!(initial_alpha_issuance + alpha_emissions, alpha_issuance); + // We went two blocks so we should have 2x the alpha emissions + assert_eq!( + initial_alpha_issuance + alpha_emissions.saturating_mul(2.into()), + alpha_issuance + ); let root_prop = initial_tao as f64 / (u64::from(alpha_issuance) + initial_tao) as f64; let root_validators_share = 0.5f64; @@ -1096,7 +1135,7 @@ fn test_claim_root_with_swap_coldkey() { // Distribute pending root alpha let pending_root_alpha = 1_000_000u64; - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, pending_root_alpha.into(), @@ -1186,7 +1225,7 @@ fn test_claim_root_with_swap_hotkey() { // Distribute pending root alpha let pending_root_alpha = 1_000_000u64; - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, pending_root_alpha.into(), @@ -1302,7 +1341,7 @@ fn test_claim_root_on_network_deregistration() { // Distribute pending root alpha let pending_root_alpha = 10_000_000u64; - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, pending_root_alpha.into(), @@ -1442,7 +1481,7 @@ fn test_claim_root_with_unrelated_subnets() { // Distribute pending root alpha let pending_root_alpha = 1_000_000u64; - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, pending_root_alpha.into(), diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 60644b2a2..5d03afa97 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -245,7 +245,7 @@ fn test_coinbase_tao_issuance_different_prices() { ); // Prices are low => we limit tao issued (buy alpha with it) - let tao_issued = TaoCurrency::from(((0.1 + 0.2) * emission as f64) as u64); + let tao_issued = TaoCurrency::from(((1.0) * emission as f64) as u64); assert_abs_diff_eq!( TotalIssuance::::get(), tao_issued, @@ -661,15 +661,27 @@ fn test_owner_cut_base() { #[test] fn test_pending_emission() { new_test_ext(1).execute_with(|| { - let netuid = NetUid::from(1); let emission: u64 = 1_000_000; - add_network(netuid, 1, 0); + let hotkey = U256::from(1); + let coldkey = U256::from(2); + let netuid = add_dynamic_network(&hotkey, &coldkey); + Tempo::::insert(netuid, 1); + FirstEmissionBlockNumber::::insert(netuid, 0); + mock::setup_reserves(netuid, 1_000_000.into(), 1.into()); SubtensorModule::run_coinbase(U96F32::from_num(0)); SubnetTAO::::insert(NetUid::ROOT, TaoCurrency::from(1_000_000_000)); // Add root weight. SubtensorModule::run_coinbase(U96F32::from_num(0)); SubtensorModule::set_tempo(netuid, 10000); // Large number (dont drain) SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 + + // Set moving price > 1.0 + SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); + + // Make sure we are root selling, so we have root alpha divs. + let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); + assert!(root_sell_flag, "Root sell flag should be true"); + SubtensorModule::run_coinbase(U96F32::from_num(0)); // 1 TAO / ( 1 + 3 ) = 0.25 * 1 / 2 = 125000000 @@ -678,6 +690,12 @@ fn test_pending_emission() { 1_000_000_000 - 125000000, epsilon = 1 ); // 1 - swapped. + + assert_abs_diff_eq!( + u64::from(PendingRootAlphaDivs::::get(netuid)), + 125000000, + epsilon = 1 + ); // 1 / 2 = 125000000 }); } @@ -685,7 +703,7 @@ fn test_pending_emission() { #[test] fn test_drain_base() { new_test_ext(1).execute_with(|| { - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( 0.into(), AlphaCurrency::ZERO, AlphaCurrency::ZERO, @@ -700,7 +718,7 @@ fn test_drain_base_with_subnet() { new_test_ext(1).execute_with(|| { let netuid = NetUid::from(1); add_network(netuid, 1, 0); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, AlphaCurrency::ZERO, @@ -725,7 +743,7 @@ fn test_drain_base_with_subnet_with_single_staker_not_registered() { stake_before, ); let pending_alpha = AlphaCurrency::from(1_000_000_000); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, pending_alpha.into(), AlphaCurrency::ZERO, @@ -754,7 +772,7 @@ fn test_drain_base_with_subnet_with_single_staker_registered() { stake_before, ); let pending_alpha = AlphaCurrency::from(1_000_000_000); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, pending_alpha, AlphaCurrency::ZERO, @@ -798,7 +816,7 @@ fn test_drain_base_with_subnet_with_single_staker_registered_root_weight() { let pending_alpha = AlphaCurrency::from(1_000_000_000); let pending_root_alpha = AlphaCurrency::from(1_000_000_000); assert_eq!(SubnetTAO::::get(NetUid::ROOT), TaoCurrency::ZERO); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, pending_alpha, pending_root_alpha, @@ -845,7 +863,7 @@ fn test_drain_base_with_subnet_with_two_stakers_registered() { stake_before, ); let pending_alpha = AlphaCurrency::from(1_000_000_000); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, pending_alpha, AlphaCurrency::ZERO, @@ -910,7 +928,7 @@ fn test_drain_base_with_subnet_with_two_stakers_registered_and_root() { let pending_tao = TaoCurrency::from(1_000_000_000); let pending_alpha = AlphaCurrency::from(1_000_000_000); assert_eq!(SubnetTAO::::get(NetUid::ROOT), TaoCurrency::ZERO); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, pending_alpha, AlphaCurrency::ZERO, @@ -985,12 +1003,7 @@ fn test_drain_base_with_subnet_with_two_stakers_registered_and_root_different_am let pending_tao = TaoCurrency::from(1_000_000_000); let pending_alpha = AlphaCurrency::from(1_000_000_000); assert_eq!(SubnetTAO::::get(NetUid::ROOT), TaoCurrency::ZERO); - SubtensorModule::drain_pending_emission( - netuid, - pending_alpha, - AlphaCurrency::ZERO, - 0.into(), - ); + SubtensorModule::distribute_emission(netuid, pending_alpha, AlphaCurrency::ZERO, 0.into()); let stake_after1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey1, &coldkey, netuid); let root_after1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( @@ -1065,7 +1078,7 @@ fn test_drain_base_with_subnet_with_two_stakers_registered_and_root_different_am let pending_tao = TaoCurrency::from(1_000_000_000); let pending_alpha = AlphaCurrency::from(1_000_000_000); assert_eq!(SubnetTAO::::get(NetUid::ROOT), TaoCurrency::ZERO); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, pending_alpha, AlphaCurrency::ZERO, @@ -1126,7 +1139,7 @@ fn test_drain_alpha_childkey_parentkey() { ChildkeyTake::::insert(child, netuid, u16::MAX / 10); let pending_alpha = AlphaCurrency::from(1_000_000_000); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, pending_alpha, AlphaCurrency::ZERO, @@ -1351,7 +1364,7 @@ fn test_get_root_children_drain() { // Lets drain let pending_alpha = AlphaCurrency::from(1_000_000_000); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( alpha, pending_alpha, AlphaCurrency::ZERO, @@ -1374,7 +1387,7 @@ fn test_get_root_children_drain() { // Lets drain let pending_alpha = AlphaCurrency::from(1_000_000_000); let pending_root1 = TaoCurrency::from(1_000_000_000); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( alpha, pending_alpha, // pending_root1, @@ -1398,7 +1411,7 @@ fn test_get_root_children_drain() { // Lets drain let pending_alpha = AlphaCurrency::from(1_000_000_000); let pending_root2 = TaoCurrency::from(1_000_000_000); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( alpha, pending_alpha, AlphaCurrency::ZERO, @@ -1486,7 +1499,7 @@ fn test_get_root_children_drain_half_proportion() { // Lets drain! let pending_alpha = AlphaCurrency::from(1_000_000_000); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( alpha, pending_alpha, AlphaCurrency::ZERO, @@ -1572,7 +1585,7 @@ fn test_get_root_children_drain_with_take() { // Lets drain! let pending_alpha = AlphaCurrency::from(1_000_000_000); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( alpha, pending_alpha, AlphaCurrency::ZERO, @@ -1659,7 +1672,7 @@ fn test_get_root_children_drain_with_half_take() { // Lets drain! let pending_alpha = AlphaCurrency::from(1_000_000_000); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( alpha, pending_alpha, AlphaCurrency::ZERO, @@ -1761,7 +1774,7 @@ fn test_get_root_children_drain_with_half_take() { // // Lets drain! // let pending_alpha = AlphaCurrency::from(1_000_000_000); -// SubtensorModule::drain_pending_emission(alpha, pending_alpha, 0, 0.into(), 0.into()); +// SubtensorModule::distribute_emission(alpha, pending_alpha, 0, 0.into(), 0.into()); // // Alice and Bob make the same amount. // close( @@ -2349,7 +2362,7 @@ fn test_calculate_dividends_and_incentives_only_miners() { } #[test] -fn test_drain_pending_emission_no_miners_all_drained() { +fn test_distribute_emission_no_miners_all_drained() { new_test_ext(1).execute_with(|| { let netuid = add_dynamic_network(&U256::from(1), &U256::from(2)); let hotkey = U256::from(3); @@ -2374,7 +2387,7 @@ fn test_drain_pending_emission_no_miners_all_drained() { // Set the emission to be 1 million. let emission = AlphaCurrency::from(1_000_000); // Run drain pending without any miners. - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, emission, AlphaCurrency::ZERO, @@ -2393,9 +2406,9 @@ fn test_drain_pending_emission_no_miners_all_drained() { }); } -// cargo test --package pallet-subtensor --lib -- tests::coinbase::test_drain_pending_emission_zero_emission --exact --show-output +// cargo test --package pallet-subtensor --lib -- tests::coinbase::test_distribute_emission_zero_emission --exact --show-output #[test] -fn test_drain_pending_emission_zero_emission() { +fn test_distribute_emission_zero_emission() { new_test_ext(1).execute_with(|| { let netuid = add_dynamic_network_disable_commit_reveal(&U256::from(1), &U256::from(2)); let hotkey = U256::from(3); @@ -2446,7 +2459,7 @@ fn test_drain_pending_emission_zero_emission() { Dividends::::remove(netuid); // Set the emission to be ZERO. - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, 0.into(), AlphaCurrency::ZERO, @@ -2739,7 +2752,7 @@ fn test_drain_alpha_childkey_parentkey_with_burn() { let child_stake_before = SubtensorModule::get_stake_for_hotkey_on_subnet(&child, netuid); let pending_alpha = AlphaCurrency::from(1_000_000_000); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, pending_alpha, AlphaCurrency::ZERO, @@ -2909,3 +2922,368 @@ fn test_zero_shares_zero_emission() { assert_eq!(SubnetAlphaIn::::get(netuid2), initial.into()); }); } + +#[test] +fn test_mining_emission_distribution_with_no_root_sell() { + new_test_ext(1).execute_with(|| { + let validator_coldkey = U256::from(1); + let validator_hotkey = U256::from(2); + let validator_miner_coldkey = U256::from(3); + let validator_miner_hotkey = U256::from(4); + let miner_coldkey = U256::from(5); + let miner_hotkey = U256::from(6); + let netuid = NetUid::from(1); + let subnet_tempo = 10; + let stake: u64 = 100_000_000_000; + let root_stake: u64 = 200_000_000_000; // 200 TAO + + // Create root network + SubtensorModule::set_tao_weight(0); // Start tao weight at 0 + SubtokenEnabled::::insert(NetUid::ROOT, true); + NetworksAdded::::insert(NetUid::ROOT, true); + + // Add network, register hotkeys, and setup network parameters + add_network(netuid, subnet_tempo, 0); + SubnetMechanism::::insert(netuid, 1); // Set mechanism to 1 + + // Setup large LPs to prevent slippage + SubnetTAO::::insert(netuid, TaoCurrency::from(1_000_000_000_000_000)); + SubnetAlphaIn::::insert(netuid, AlphaCurrency::from(1_000_000_000_000_000)); + + register_ok_neuron(netuid, validator_hotkey, validator_coldkey, 0); + register_ok_neuron(netuid, validator_miner_hotkey, validator_miner_coldkey, 1); + register_ok_neuron(netuid, miner_hotkey, miner_coldkey, 2); + SubtensorModule::add_balance_to_coldkey_account( + &validator_coldkey, + stake + ExistentialDeposit::get(), + ); + SubtensorModule::add_balance_to_coldkey_account( + &validator_miner_coldkey, + stake + ExistentialDeposit::get(), + ); + SubtensorModule::add_balance_to_coldkey_account( + &miner_coldkey, + stake + ExistentialDeposit::get(), + ); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + step_block(subnet_tempo); + SubnetOwnerCut::::set(u16::MAX / 10); + // There are two validators and three neurons + MaxAllowedUids::::set(netuid, 3); + SubtensorModule::set_max_allowed_validators(netuid, 2); + + // Setup stakes: + // Stake from validator + // Stake from valiminer + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(validator_coldkey), + validator_hotkey, + netuid, + stake.into() + )); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(validator_miner_coldkey), + validator_miner_hotkey, + netuid, + stake.into() + )); + + // Setup YUMA so that it creates emissions + Weights::::insert(NetUidStorageIndex::from(netuid), 0, vec![(1, 0xFFFF)]); + Weights::::insert(NetUidStorageIndex::from(netuid), 1, vec![(2, 0xFFFF)]); + BlockAtRegistration::::set(netuid, 0, 1); + BlockAtRegistration::::set(netuid, 1, 1); + BlockAtRegistration::::set(netuid, 2, 1); + LastUpdate::::set(NetUidStorageIndex::from(netuid), vec![2, 2, 2]); + Kappa::::set(netuid, u16::MAX / 5); + ActivityCutoff::::set(netuid, u16::MAX); // makes all stake active + ValidatorPermit::::insert(netuid, vec![true, true, false]); + + // Run run_coinbase until emissions are drained + step_block(subnet_tempo); + + // Add stake to validator so it has root stake + SubtensorModule::add_balance_to_coldkey_account(&validator_coldkey, root_stake.into()); + // init root + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(validator_coldkey), + validator_hotkey, + NetUid::ROOT, + root_stake.into() + )); + // Set tao weight non zero + SubtensorModule::set_tao_weight(u64::MAX / 10); + + // Make root sell NOT happen + // set price very low, e.g. a lot of alpha in + //SubnetAlphaIn::::insert(netuid, AlphaCurrency::from(1_000_000_000_000_000)); + pallet_subtensor_swap::AlphaSqrtPrice::::insert( + netuid, + U64F64::saturating_from_num(0.01), + ); + + // Make sure we ARE NOT root selling, so we do not have root alpha divs. + let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); + assert!(!root_sell_flag, "Root sell flag should be false"); + + // Run run_coinbase until emissions are drained + step_block(subnet_tempo); + + let old_root_alpha_divs = PendingRootAlphaDivs::::get(netuid); + let per_block_emission = SubtensorModule::get_block_emission_for_issuance( + SubtensorModule::get_alpha_issuance(netuid).into(), + ) + .unwrap_or(0); + + // step by one block + step_block(1); + // Verify that root alpha divs + let new_root_alpha_divs = PendingRootAlphaDivs::::get(netuid); + // Check that we are indeed NOT root selling, i.e. that root alpha divs are NOT increasing + assert_eq!( + new_root_alpha_divs, old_root_alpha_divs, + "Root alpha divs should not increase" + ); + // Check root divs are zero + assert_eq!( + new_root_alpha_divs, + AlphaCurrency::ZERO, + "Root alpha divs should be zero" + ); + let miner_stake_before_epoch = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &miner_hotkey, + &miner_coldkey, + netuid, + ); + // Run again but with some root stake + step_block(subnet_tempo - 2); + assert_abs_diff_eq!( + PendingEmission::::get(netuid).to_u64(), + U96F32::saturating_from_num(per_block_emission) + .saturating_mul(U96F32::saturating_from_num(subnet_tempo as u64)) + .saturating_mul(U96F32::saturating_from_num(0.90)) + .saturating_to_num::(), + epsilon = 100_000_u64.into() + ); + step_block(1); + assert!( + BlocksSinceLastStep::::get(netuid) == 0, + "Blocks since last step should be 0" + ); + + let miner_uid = Uids::::get(netuid, miner_hotkey).unwrap_or(0); + log::info!("Miner uid: {miner_uid:?}"); + let miner_incentive: AlphaCurrency = { + let miner_incentive = Incentive::::get(NetUidStorageIndex::from(netuid)) + .get(miner_uid as usize) + .copied(); + + assert!(miner_incentive.is_some()); + + (miner_incentive.unwrap_or_default() as u64).into() + }; + log::info!("Miner incentive: {miner_incentive:?}"); + + // Miner emissions + let miner_emission_1: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &miner_hotkey, + &miner_coldkey, + netuid, + ) + .to_u64() + - miner_stake_before_epoch.to_u64(); + + assert_abs_diff_eq!( + Incentive::::get(NetUidStorageIndex::from(netuid)) + .iter() + .sum::(), + u16::MAX, + epsilon = 10 + ); + + assert_abs_diff_eq!( + miner_emission_1, + U96F32::saturating_from_num(miner_incentive) + .saturating_div(u16::MAX.into()) + .saturating_mul(U96F32::saturating_from_num(per_block_emission)) + .saturating_mul(U96F32::saturating_from_num(subnet_tempo + 1)) + .saturating_mul(U96F32::saturating_from_num(0.45)) // miner cut + .saturating_to_num::(), + epsilon = 1_000_000_u64 + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::coinbase::test_mining_emission_distribution_with_root_sell --exact --show-output --nocapture +#[test] +fn test_mining_emission_distribution_with_root_sell() { + new_test_ext(1).execute_with(|| { + let validator_coldkey = U256::from(1); + let validator_hotkey = U256::from(2); + let validator_miner_coldkey = U256::from(3); + let validator_miner_hotkey = U256::from(4); + let miner_coldkey = U256::from(5); + let miner_hotkey = U256::from(6); + let subnet_tempo = 10; + let stake: u64 = 100_000_000_000; + let root_stake: u64 = 200_000_000_000; // 200 TAO + + // Create root network + SubtensorModule::set_tao_weight(0); // Start tao weight at 0 + SubtokenEnabled::::insert(NetUid::ROOT, true); + NetworksAdded::::insert(NetUid::ROOT, true); + + // Add network, register hotkeys, and setup network parameters + let owner_hotkey = U256::from(10); + let owner_coldkey = U256::from(11); + let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); + Tempo::::insert(netuid, 1); + FirstEmissionBlockNumber::::insert(netuid, 0); + + // Setup large LPs to prevent slippage + SubnetTAO::::insert(netuid, TaoCurrency::from(1_000_000_000_000_000)); + SubnetAlphaIn::::insert(netuid, AlphaCurrency::from(1_000_000_000_000_000)); + + register_ok_neuron(netuid, validator_hotkey, validator_coldkey, 0); + register_ok_neuron(netuid, validator_miner_hotkey, validator_miner_coldkey, 1); + register_ok_neuron(netuid, miner_hotkey, miner_coldkey, 2); + SubtensorModule::add_balance_to_coldkey_account( + &validator_coldkey, + stake + ExistentialDeposit::get(), + ); + SubtensorModule::add_balance_to_coldkey_account( + &validator_miner_coldkey, + stake + ExistentialDeposit::get(), + ); + SubtensorModule::add_balance_to_coldkey_account( + &miner_coldkey, + stake + ExistentialDeposit::get(), + ); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + step_block(subnet_tempo); + SubnetOwnerCut::::set(u16::MAX / 10); + // There are two validators and three neurons + MaxAllowedUids::::set(netuid, 3); + SubtensorModule::set_max_allowed_validators(netuid, 2); + + // Setup stakes: + // Stake from validator + // Stake from valiminer + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(validator_coldkey), + validator_hotkey, + netuid, + stake.into() + )); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(validator_miner_coldkey), + validator_miner_hotkey, + netuid, + stake.into() + )); + + // Setup YUMA so that it creates emissions + Weights::::insert(NetUidStorageIndex::from(netuid), 0, vec![(1, 0xFFFF)]); + Weights::::insert(NetUidStorageIndex::from(netuid), 1, vec![(2, 0xFFFF)]); + BlockAtRegistration::::set(netuid, 0, 1); + BlockAtRegistration::::set(netuid, 1, 1); + BlockAtRegistration::::set(netuid, 2, 1); + LastUpdate::::set(NetUidStorageIndex::from(netuid), vec![2, 2, 2]); + Kappa::::set(netuid, u16::MAX / 5); + ActivityCutoff::::set(netuid, u16::MAX); // makes all stake active + ValidatorPermit::::insert(netuid, vec![true, true, false]); + + // Run run_coinbase until emissions are drained + step_block(subnet_tempo); + + // Add stake to validator so it has root stake + SubtensorModule::add_balance_to_coldkey_account(&validator_coldkey, root_stake.into()); + // init root + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(validator_coldkey), + validator_hotkey, + NetUid::ROOT, + root_stake.into() + )); + // Set tao weight non zero + SubtensorModule::set_tao_weight(u64::MAX / 10); + + // Make root sell happen + // Set moving price > 1.0 + // Set price > 1.0 + pallet_subtensor_swap::AlphaSqrtPrice::::insert( + netuid, + U64F64::saturating_from_num(10.0), + ); + + SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); + + // Make sure we are root selling, so we have root alpha divs. + let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); + assert!(root_sell_flag, "Root sell flag should be true"); + + // Run run_coinbase until emissions are drained + step_block(subnet_tempo); + + let old_root_alpha_divs = PendingRootAlphaDivs::::get(netuid); + let miner_stake_before_epoch = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &miner_hotkey, + &miner_coldkey, + netuid, + ); + + // step by one block + step_block(1); + // Verify root alpha divs + let new_root_alpha_divs = PendingRootAlphaDivs::::get(netuid); + // Check that we ARE root selling, i.e. that root alpha divs are changing + assert_ne!( + new_root_alpha_divs, old_root_alpha_divs, + "Root alpha divs should be changing" + ); + assert!( + new_root_alpha_divs > AlphaCurrency::ZERO, + "Root alpha divs should be greater than 0" + ); + + // Run again but with some root stake + step_block(subnet_tempo - 1); + + let miner_uid = Uids::::get(netuid, miner_hotkey).unwrap_or(0); + let miner_incentive: AlphaCurrency = { + let miner_incentive = Incentive::::get(NetUidStorageIndex::from(netuid)) + .get(miner_uid as usize) + .copied(); + + assert!(miner_incentive.is_some()); + + (miner_incentive.unwrap_or_default() as u64).into() + }; + log::info!("Miner incentive: {miner_incentive:?}"); + + let per_block_emission = SubtensorModule::get_block_emission_for_issuance( + SubtensorModule::get_alpha_issuance(netuid).into(), + ) + .unwrap_or(0); + + // Miner emissions + let miner_emission_1: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &miner_hotkey, + &miner_coldkey, + netuid, + ) + .to_u64() + - miner_stake_before_epoch.to_u64(); + + assert_abs_diff_eq!( + miner_emission_1, + U96F32::saturating_from_num(miner_incentive) + .saturating_div(u16::MAX.into()) + .saturating_mul(U96F32::saturating_from_num(per_block_emission)) + .saturating_mul(U96F32::saturating_from_num(subnet_tempo + 1)) + .saturating_mul(U96F32::saturating_from_num(0.45)) // miner cut + .saturating_to_num::(), + epsilon = 1_000_000_u64 + ); + }); +} diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 0449c67f8..3706878bb 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -216,43 +216,49 @@ fn dissolve_owner_cut_refund_logic() { // One staker and a TAO pot (not relevant to refund amount). let sh = U256::from(77); let sc = U256::from(88); - Alpha::::insert((sh, sc, net), U64F64::from_num(100u128)); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &sh, + &sc, + net, + AlphaCurrency::from(800u64), + ); SubnetTAO::::insert(net, TaoCurrency::from(1_000)); // Lock & emissions: total emitted α = 800. let lock: TaoCurrency = TaoCurrency::from(2_000); SubtensorModule::set_subnet_locked_balance(net, lock); - Emission::::insert( - net, - vec![AlphaCurrency::from(200), AlphaCurrency::from(600)], - ); + // ensure there was some Alpha issued + assert!(SubtensorModule::get_alpha_issuance(net).to_u64() > 0); // Owner cut = 11796 / 65535 (about 18%). SubnetOwnerCut::::put(11_796u16); // Compute expected refund with the SAME math as the pallet. let frac: U96F32 = SubtensorModule::get_float_subnet_owner_cut(); - let total_emitted_alpha: u64 = 800; + let total_emitted_alpha: u64 = SubtensorModule::get_alpha_issuance(net).to_u64(); let owner_alpha_u64: u64 = U96F32::from_num(total_emitted_alpha) .saturating_mul(frac) .floor() .saturating_to_num::(); - // Current α→τ price for this subnet. - let price: U96F32 = - ::SwapInterface::current_alpha_price(net.into()); - let owner_emission_tao_u64: u64 = U96F32::from_num(owner_alpha_u64) - .saturating_mul(price) - .floor() - .saturating_to_num::(); + // Use the current alpha price to estimate the TAO equivalent. + let owner_emission_tao = { + let price: U96F32 = + ::SwapInterface::current_alpha_price(net.into()); + U96F32::from_num(owner_alpha_u64) + .saturating_mul(price) + .floor() + .saturating_to_num::() + .into() + }; - let expected_refund: TaoCurrency = - lock.saturating_sub(TaoCurrency::from(owner_emission_tao_u64)); + let expected_refund: TaoCurrency = lock.saturating_sub(owner_emission_tao); let before = SubtensorModule::get_coldkey_balance(&oc); assert_ok!(SubtensorModule::do_dissolve_network(net)); let after = SubtensorModule::get_coldkey_balance(&oc); + assert!(after > before); // some refund is expected assert_eq!( TaoCurrency::from(after), TaoCurrency::from(before) + expected_refund @@ -841,15 +847,10 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { SubnetTAO::::insert(netuid, TaoCurrency::from(tao_pot)); SubtensorModule::set_subnet_locked_balance(netuid, TaoCurrency::from(lock)); + // ensure there was some Alpha issued + assert!(SubtensorModule::get_alpha_issuance(netuid).to_u64() > 0); + // Owner already earned some emission; owner-cut = 50 % - Emission::::insert( - netuid, - vec![ - AlphaCurrency::from(1_000), - AlphaCurrency::from(2_000), - AlphaCurrency::from(1_500), - ], - ); SubnetOwnerCut::::put(32_768u16); // ~ 0.5 in fixed-point // ── 4) balances before ────────────────────────────────────────────── @@ -879,28 +880,23 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { // ── 5b) expected owner refund with price-aware emission deduction ─── let frac: U96F32 = SubtensorModule::get_float_subnet_owner_cut(); - let total_emitted_alpha: u64 = 1_000 + 2_000 + 1_500; // 4500 α + let total_emitted_alpha: u64 = SubtensorModule::get_alpha_issuance(netuid).to_u64(); let owner_alpha_u64: u64 = U96F32::from_num(total_emitted_alpha) .saturating_mul(frac) .floor() .saturating_to_num::(); - let order = GetTaoForAlpha::::with_amount(owner_alpha_u64); - let owner_emission_tao = - ::SwapInterface::sim_swap(netuid.into(), order) - .map(|res| res.amount_paid_out) - .unwrap_or_else(|_| { - // Fallback matches the pallet's fallback - let price: U96F32 = - ::SwapInterface::current_alpha_price(netuid.into()); - U96F32::from_num(owner_alpha_u64) - .saturating_mul(price) - .floor() - .saturating_to_num::() - .into() - }); - - let expected_refund = lock.saturating_sub(owner_emission_tao.to_u64()); + let owner_emission_tao: u64 = { + // Fallback matches the pallet's fallback + let price: U96F32 = + ::SwapInterface::current_alpha_price(netuid.into()); + U96F32::from_num(owner_alpha_u64) + .saturating_mul(price) + .floor() + .saturating_to_num::() + }; + + let expected_refund = lock.saturating_sub(owner_emission_tao); // ── 6) run distribution (credits τ to coldkeys, wipes α state) ───── assert_ok!(SubtensorModule::destroy_alpha_in_out_stakes(netuid)); @@ -947,34 +943,38 @@ fn destroy_alpha_out_refund_gating_by_registration_block() { // Lock and (nonzero) emissions let lock_u64: u64 = 50_000; SubtensorModule::set_subnet_locked_balance(netuid, TaoCurrency::from(lock_u64)); - Emission::::insert( - netuid, - vec![AlphaCurrency::from(1_500u64), AlphaCurrency::from(3_000u64)], // total 4_500 α - ); // Owner cut ≈ 50% SubnetOwnerCut::::put(32_768u16); + // give some stake to other key + let other_cold = U256::from(1_234); + let other_hot = U256::from(2_345); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &other_hot, + &other_cold, + netuid, + AlphaCurrency::from(30u64), // not nearly enough to cover the lock + ); + + // ensure there was some Alpha issued + assert!(SubtensorModule::get_alpha_issuance(netuid).to_u64() > 0); + // Compute expected refund using the same math as the pallet let frac: U96F32 = SubtensorModule::get_float_subnet_owner_cut(); - let total_emitted_alpha: u64 = 1_500 + 3_000; // 4_500 α + let total_emitted_alpha: u64 = SubtensorModule::get_alpha_issuance(netuid).to_u64(); let owner_alpha_u64: u64 = U96F32::from_num(total_emitted_alpha) .saturating_mul(frac) .floor() .saturating_to_num::(); - // Prefer sim_swap; fall back to current price if unavailable. - let order = GetTaoForAlpha::::with_amount(owner_alpha_u64); - let owner_emission_tao_u64 = - ::SwapInterface::sim_swap(netuid.into(), order) - .map(|res| res.amount_paid_out.to_u64()) - .unwrap_or_else(|_| { - let price: U96F32 = - ::SwapInterface::current_alpha_price(netuid.into()); - U96F32::from_num(owner_alpha_u64) - .saturating_mul(price) - .floor() - .saturating_to_num::() - }); + let owner_emission_tao_u64 = { + let price: U96F32 = + ::SwapInterface::current_alpha_price(netuid.into()); + U96F32::from_num(owner_alpha_u64) + .saturating_mul(price) + .floor() + .saturating_to_num::() + }; let expected_refund: u64 = lock_u64.saturating_sub(owner_emission_tao_u64); @@ -1011,7 +1011,17 @@ fn destroy_alpha_out_refund_gating_by_registration_block() { // Lock and emissions present (should be ignored for refund) let lock_u64: u64 = 42_000; SubtensorModule::set_subnet_locked_balance(netuid, TaoCurrency::from(lock_u64)); - Emission::::insert(netuid, vec![AlphaCurrency::from(5_000u64)]); + // give some stake to other key + let other_cold = U256::from(1_234); + let other_hot = U256::from(2_345); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &other_hot, + &other_cold, + netuid, + AlphaCurrency::from(300u64), // not nearly enough to cover the lock + ); + // ensure there was some Alpha issued + assert!(SubtensorModule::get_alpha_issuance(netuid).to_u64() > 0); SubnetOwnerCut::::put(32_768u16); // ~50% // Balances before @@ -1046,7 +1056,9 @@ fn destroy_alpha_out_refund_gating_by_registration_block() { // lock = 0; emissions present (must not matter) SubtensorModule::set_subnet_locked_balance(netuid, TaoCurrency::from(0u64)); - Emission::::insert(netuid, vec![AlphaCurrency::from(10_000u64)]); + SubnetAlphaOut::::insert(netuid, AlphaCurrency::from(10_000)); + // ensure there was some Alpha issued + assert!(SubtensorModule::get_alpha_issuance(netuid).to_u64() > 0); SubnetOwnerCut::::put(32_768u16); // ~50% let owner_before = SubtensorModule::get_coldkey_balance(&owner_cold); diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 266a75570..fe9bda8b6 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -220,7 +220,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 338, + spec_version: 342, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1,