diff --git a/pallets/subtensor/src/coinbase/subnet_emissions.rs b/pallets/subtensor/src/coinbase/subnet_emissions.rs index 3677b96f97..80ecf85a42 100644 --- a/pallets/subtensor/src/coinbase/subnet_emissions.rs +++ b/pallets/subtensor/src/coinbase/subnet_emissions.rs @@ -4,6 +4,7 @@ use alloc::collections::BTreeMap; use safe_math::FixedExt; use substrate_fixed::transcendental::{exp, ln}; use substrate_fixed::types::{I32F32, I64F64, U64F64, U96F32}; +use subtensor_swap_interface::SwapHandler; impl Pallet { pub fn get_subnet_block_emissions( @@ -73,8 +74,11 @@ impl Pallet { last_block_ema } } else { - // Initialize EMA flow, set S(current_block) = 0 - let ema_flow = I64F64::saturating_from_num(0); + // Initialize EMA flow, set S(current_block) = min(price, ema_price) * init_factor + let moving_price = I64F64::saturating_from_num(Self::get_moving_alpha_price(netuid)); + let current_price = + I64F64::saturating_from_num(T::SwapInterface::current_alpha_price(netuid)); + let ema_flow = moving_price.min(current_price); SubnetEmaTaoFlow::::insert(netuid, (current_block, ema_flow)); ema_flow } @@ -206,88 +210,8 @@ impl Pallet { offset_flows } - // DEPRECATED: Implementation of shares that uses EMA prices will be gradually deprecated - fn get_shares_price_ema(subnets_to_emit_to: &[NetUid]) -> BTreeMap { - // Get sum of alpha moving prices - let total_moving_prices = subnets_to_emit_to - .iter() - .map(|netuid| U64F64::saturating_from_num(Self::get_moving_alpha_price(*netuid))) - .fold(U64F64::saturating_from_num(0.0), |acc, ema| { - acc.saturating_add(ema) - }); - log::debug!("total_moving_prices: {total_moving_prices:?}"); - - // Calculate shares. - subnets_to_emit_to - .iter() - .map(|netuid| { - let moving_price = - U64F64::saturating_from_num(Self::get_moving_alpha_price(*netuid)); - log::debug!("moving_price_i: {moving_price:?}"); - - let share = moving_price - .checked_div(total_moving_prices) - .unwrap_or(U64F64::saturating_from_num(0)); - - (*netuid, share) - }) - .collect::>() - } - // Combines ema price method and tao flow method linearly over FlowHalfLife blocks pub(crate) fn get_shares(subnets_to_emit_to: &[NetUid]) -> BTreeMap { - let current_block: u64 = Self::get_current_block_as_u64(); - - // Weight of tao flow method - let period = FlowHalfLife::::get(); - let one = U64F64::saturating_from_num(1); - let zero = U64F64::saturating_from_num(0); - let tao_flow_weight = if let Some(start_block) = FlowFirstBlock::::get() { - if (current_block > start_block) && (current_block < start_block.saturating_add(period)) - { - // Combination period in progress - let start_fixed = U64F64::saturating_from_num(start_block); - let current_fixed = U64F64::saturating_from_num(current_block); - let period_fixed = U64F64::saturating_from_num(period); - current_fixed - .saturating_sub(start_fixed) - .safe_div(period_fixed) - } else if current_block >= start_block.saturating_add(period) { - // Over combination period - one - } else { - // Not yet in combination period - zero - } - } else { - zero - }; - - // Get shares for each method as needed - let shares_flow = if tao_flow_weight > zero { - Self::get_shares_flow(subnets_to_emit_to) - } else { - BTreeMap::new() - }; - - let shares_prices = if tao_flow_weight < one { - Self::get_shares_price_ema(subnets_to_emit_to) - } else { - BTreeMap::new() - }; - - // Combine - let mut shares_combined = BTreeMap::new(); - for netuid in subnets_to_emit_to.iter() { - let share_flow = shares_flow.get(netuid).unwrap_or(&zero); - let share_prices = shares_prices.get(netuid).unwrap_or(&zero); - shares_combined.insert( - *netuid, - share_flow.saturating_mul(tao_flow_weight).saturating_add( - share_prices.saturating_mul(one.saturating_sub(tao_flow_weight)), - ), - ); - } - shares_combined + Self::get_shares_flow(subnets_to_emit_to) } } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 93d082f4a6..bfd0e7235b 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -88,7 +88,6 @@ pub mod pallet { use frame_system::pallet_prelude::*; use pallet_drand::types::RoundNumber; use runtime_common::prod_or_fast; - use safe_math::FixedExt; use sp_core::{ConstU32, H160, H256}; use sp_runtime::traits::{Dispatchable, TrailingZeroInput}; use sp_std::collections::btree_map::BTreeMap; @@ -1292,7 +1291,7 @@ pub mod pallet { #[pallet::type_value] /// Default value for flow normalization exponent. pub fn DefaultFlowNormExponent() -> U64F64 { - U64F64::saturating_from_num(15).safe_div(U64F64::saturating_from_num(10)) + U64F64::saturating_from_num(1) } #[pallet::storage] /// --- ITEM --> Flow Normalization Exponent (p) @@ -1302,7 +1301,7 @@ pub mod pallet { /// Default value for flow EMA smoothing. pub fn DefaultFlowEmaSmoothingFactor() -> u64 { // Example values: - // half-life factor value i64 normalized + // 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 @@ -1316,9 +1315,6 @@ pub mod pallet { /// --- ITEM --> Flow EMA smoothing factor (flow alpha), u64 normalized pub type FlowEmaSmoothingFactor = StorageValue<_, u64, ValueQuery, DefaultFlowEmaSmoothingFactor>; - #[pallet::storage] - /// --- ITEM --> Block when TAO flow calculation starts(ed) - pub type FlowFirstBlock = StorageValue<_, u64, OptionQuery>; /// ============================ /// ==== Global Parameters ===== diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index a4cdbfbe94..87a87e911c 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -158,8 +158,6 @@ mod hooks { .saturating_add(migrations::migrate_auto_stake_destination::migrate_auto_stake_destination::()) // Migrate Kappa to default (0.5) .saturating_add(migrations::migrate_kappa_map_to_default::migrate_kappa_map_to_default::()) - // Set the first block of tao flow - .saturating_add(migrations::migrate_set_first_tao_flow_block::migrate_set_first_tao_flow_block::()) // Remove obsolete map entries .saturating_add(migrations::migrate_remove_tao_dividends::migrate_remove_tao_dividends::()); weight diff --git a/pallets/subtensor/src/migrations/migrate_set_first_tao_flow_block.rs b/pallets/subtensor/src/migrations/migrate_set_first_tao_flow_block.rs deleted file mode 100644 index 99c10b99ba..0000000000 --- a/pallets/subtensor/src/migrations/migrate_set_first_tao_flow_block.rs +++ /dev/null @@ -1,38 +0,0 @@ -use super::*; -use crate::HasMigrationRun; -use frame_support::{traits::Get, weights::Weight}; -use scale_info::prelude::string::String; - -pub fn migrate_set_first_tao_flow_block() -> Weight { - let migration_name = b"migrate_set_first_tao_flow_block".to_vec(); - - let mut weight = T::DbWeight::get().reads(1); - if HasMigrationRun::::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) - ); - - // Actual migration - let current_block = Pallet::::get_current_block_as_u64(); - FlowFirstBlock::::set(Some(current_block)); - weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); - - // Mark Migration as Completed - HasMigrationRun::::insert(&migration_name, true); - weight = weight.saturating_add(T::DbWeight::get().reads(2)); - - log::info!( - "Migration '{:?}' completed successfully.", - String::from_utf8_lossy(&migration_name) - ); - - weight -} diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs index 2313870244..d95e4c7bac 100644 --- a/pallets/subtensor/src/migrations/mod.rs +++ b/pallets/subtensor/src/migrations/mod.rs @@ -40,7 +40,6 @@ pub mod migrate_remove_zero_total_hotkey_alpha; pub mod migrate_reset_bonds_moving_average; pub mod migrate_reset_max_burn; pub mod migrate_set_first_emission_block_number; -pub mod migrate_set_first_tao_flow_block; pub mod migrate_set_min_burn; pub mod migrate_set_min_difficulty; pub mod migrate_set_nominator_min_stake; diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 75d4cc3ce9..2d0376c896 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -127,7 +127,6 @@ fn test_coinbase_tao_issuance_base_low_flow() { // 100% tao flow method let block_num = FlowHalfLife::::get(); SubnetEmaTaoFlow::::insert(netuid, (block_num, I64F64::from_num(1_000_000_000))); - FlowFirstBlock::::set(Some(0_u64)); System::set_block_number(block_num); let tao_in_before = SubnetTAO::::get(netuid); @@ -237,12 +236,12 @@ fn test_coinbase_tao_issuance_different_prices() { assert_abs_diff_eq!( SubnetTAO::::get(netuid1), TaoCurrency::from(initial_tao + emission / 3), - epsilon = 1.into(), + epsilon = 10.into(), ); assert_abs_diff_eq!( SubnetTAO::::get(netuid2), TaoCurrency::from(initial_tao + 2 * emission / 3), - epsilon = 1.into(), + epsilon = 10.into(), ); // Prices are low => we limit tao issued (buy alpha with it) @@ -298,11 +297,9 @@ fn test_coinbase_tao_issuance_different_flows() { SubnetMovingPrice::::insert(netuid2, I96F32::from_num(1)); // Set subnet tao flow ema. - // 100% tao flow method let block_num = FlowHalfLife::::get(); SubnetEmaTaoFlow::::insert(netuid1, (block_num, I64F64::from_num(1))); SubnetEmaTaoFlow::::insert(netuid2, (block_num, I64F64::from_num(2))); - FlowFirstBlock::::set(Some(0_u64)); System::set_block_number(block_num); // Set normalization exponent to 1 for simplicity @@ -490,9 +487,9 @@ fn test_coinbase_alpha_issuance_base() { }); } -// Test alpha issuance with different subnet prices. +// Test alpha issuance with different subnet flows. // This test verifies that: -// - Alpha issuance is proportional to subnet prices +// - Alpha issuance is proportional to subnet flows // - Higher priced subnets receive more TAO emission // - Alpha issuance is correctly calculated based on price ratios // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::coinbase::test_coinbase_alpha_issuance_different --exact --show-output --nocapture @@ -507,18 +504,16 @@ fn test_coinbase_alpha_issuance_different() { // Make subnets dynamic. SubnetMechanism::::insert(netuid1, 1); SubnetMechanism::::insert(netuid2, 1); - // Setup prices 1 and 1 + // Setup prices 1 and 2 let initial: u64 = 1_000_000; SubnetTAO::::insert(netuid1, TaoCurrency::from(initial)); SubnetAlphaIn::::insert(netuid1, AlphaCurrency::from(initial)); - SubnetTAO::::insert(netuid2, TaoCurrency::from(initial)); + SubnetTAO::::insert(netuid2, TaoCurrency::from(2 * initial)); SubnetAlphaIn::::insert(netuid2, AlphaCurrency::from(initial)); - // Set subnet prices. + // Set subnet ema prices to 1 and 2 SubnetMovingPrice::::insert(netuid1, I96F32::from_num(1)); SubnetMovingPrice::::insert(netuid2, I96F32::from_num(2)); - // Set tao flow - SubnetEmaTaoFlow::::insert(netuid1, (1u64, I64F64::from_num(1))); - SubnetEmaTaoFlow::::insert(netuid2, (1u64, I64F64::from_num(2))); + // Do NOT Set tao flow, let it initialize // Run coinbase SubtensorModule::run_coinbase(U96F32::from_num(emission)); // tao_in = 333_333 @@ -528,10 +523,10 @@ fn test_coinbase_alpha_issuance_different() { (initial + emission / 3).into() ); // tao_in = 666_666 - // alpha_in = 666_666/price = 666_666 + initial + // alpha_in = 666_666/price = 333_333 + initial assert_eq!( SubnetAlphaIn::::get(netuid2), - (initial + emission / 3 + emission / 3).into() + (initial + (emission * 2 / 3) / 2).into() ); }); } diff --git a/pallets/subtensor/src/tests/subnet_emissions.rs b/pallets/subtensor/src/tests/subnet_emissions.rs index aeece09c2e..9f29b3fee2 100644 --- a/pallets/subtensor/src/tests/subnet_emissions.rs +++ b/pallets/subtensor/src/tests/subnet_emissions.rs @@ -163,9 +163,7 @@ fn get_shares_normal_flows_three_subnets() { let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey); let n3 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - // 100% tao flow method let block_num = FlowHalfLife::::get(); - FlowFirstBlock::::set(Some(0_u64)); System::set_block_number(block_num); // Set (block_number, flow) with reasonable positive flows @@ -212,9 +210,7 @@ fn get_shares_low_flows_sum_one_and_ordering() { let n1 = add_dynamic_network(&owner_hotkey, &owner_coldkey); let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - // 100% tao flow method let block_num = FlowHalfLife::::get(); - FlowFirstBlock::::set(Some(0_u64)); System::set_block_number(block_num); // Tiny flows to exercise precision/scaling path @@ -256,9 +252,7 @@ fn get_shares_high_flows_sum_one_and_ordering() { let n1 = add_dynamic_network(&owner_hotkey, &owner_coldkey); let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - // 100% tao flow method let block_num = FlowHalfLife::::get(); - FlowFirstBlock::::set(Some(0_u64)); System::set_block_number(block_num); // Large but safe flows for I64F64 @@ -298,95 +292,6 @@ fn seed_price_and_flow(n1: NetUid, n2: NetUid, price1: f64, price2: f64, flow1: SubnetEmaTaoFlow::::insert(n2, (now, i64f64(flow2))); } -#[test] -fn get_shares_price_flow_blend_1v3_price_and_3v1_flow() { - new_test_ext(1).execute_with(|| { - // two subnets - let owner_hotkey = U256::from(42); - let owner_coldkey = U256::from(43); - let n1 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - - // define "window" length half_life blocks and set first block to 0 - let half_life: u64 = FlowHalfLife::::get(); - FlowFirstBlock::::set(Some(0_u64)); - FlowNormExponent::::set(u64f64(1.0)); - - // t = 0: expect (0.25, 0.75) - frame_system::Pallet::::set_block_number(0); - seed_price_and_flow(n1, n2, /*price*/ 1.0, 3.0, /*flow*/ 3.0, 1.0); - let shares = SubtensorModule::get_shares(&[n1, n2]); - let s1 = shares.get(&n1).unwrap().to_num::(); - let s2 = shares.get(&n2).unwrap().to_num::(); - assert_abs_diff_eq!(s1 + s2, 1.0_f64, epsilon = 1e-9); - assert_abs_diff_eq!(s1, 0.25_f64, epsilon = 1e-6); - assert_abs_diff_eq!(s2, 0.75_f64, epsilon = 1e-6); - - // t = half_life/2: expect (0.5, 0.5) - frame_system::Pallet::::set_block_number(half_life / 2); - seed_price_and_flow(n1, n2, 1.0, 3.0, 3.0, 1.0); - let shares = SubtensorModule::get_shares(&[n1, n2]); - let s1 = shares.get(&n1).unwrap().to_num::(); - let s2 = shares.get(&n2).unwrap().to_num::(); - assert_abs_diff_eq!(s1 + s2, 1.0_f64, epsilon = 1e-9); - assert_abs_diff_eq!(s1, 0.5_f64, epsilon = 1e-6); - assert_abs_diff_eq!(s2, 0.5_f64, epsilon = 1e-6); - - // t = half_life: expect (0.75, 0.25) - frame_system::Pallet::::set_block_number(half_life); - seed_price_and_flow(n1, n2, 1.0, 3.0, 3.0, 1.0); - let shares = SubtensorModule::get_shares(&[n1, n2]); - let s1 = shares.get(&n1).unwrap().to_num::(); - let s2 = shares.get(&n2).unwrap().to_num::(); - assert_abs_diff_eq!(s1 + s2, 1.0_f64, epsilon = 1e-9); - assert_abs_diff_eq!(s1, 0.75_f64, epsilon = 1e-6); - assert_abs_diff_eq!(s2, 0.25_f64, epsilon = 1e-6); - }); -} - -#[test] -fn get_shares_price_flow_blend_3v1_price_and_1v3_flow() { - new_test_ext(1).execute_with(|| { - let owner_hotkey = U256::from(50); - let owner_coldkey = U256::from(51); - let n1 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - - // window half_life and anchor at 0 - let half_life: u64 = FlowHalfLife::::get(); - FlowFirstBlock::::set(Some(0_u64)); - FlowNormExponent::::set(u64f64(1.0)); - - // t = 0: prices dominate → (0.75, 0.25) - frame_system::Pallet::::set_block_number(0); - seed_price_and_flow(n1, n2, /*price*/ 3.0, 1.0, /*flow*/ 1.0, 3.0); - let shares = SubtensorModule::get_shares(&[n1, n2]); - let s1 = shares.get(&n1).unwrap().to_num::(); - let s2 = shares.get(&n2).unwrap().to_num::(); - assert_abs_diff_eq!(s1 + s2, 1.0_f64, epsilon = 1e-9); - assert_abs_diff_eq!(s1, 0.75_f64, epsilon = 1e-6); - assert_abs_diff_eq!(s2, 0.25_f64, epsilon = 1e-6); - - // t = half_life/2: equal → (0.5, 0.5) - frame_system::Pallet::::set_block_number(half_life / 2); - seed_price_and_flow(n1, n2, 3.0, 1.0, 1.0, 3.0); - let shares = SubtensorModule::get_shares(&[n1, n2]); - let s1 = shares.get(&n1).unwrap().to_num::(); - let s2 = shares.get(&n2).unwrap().to_num::(); - assert_abs_diff_eq!(s1, 0.5_f64, epsilon = 1e-6); - assert_abs_diff_eq!(s2, 0.5_f64, epsilon = 1e-6); - - // t = half_life: flows dominate → (0.25, 0.75) - frame_system::Pallet::::set_block_number(half_life); - seed_price_and_flow(n1, n2, 3.0, 1.0, 1.0, 3.0); - let shares = SubtensorModule::get_shares(&[n1, n2]); - let s1 = shares.get(&n1).unwrap().to_num::(); - let s2 = shares.get(&n2).unwrap().to_num::(); - assert_abs_diff_eq!(s1, 0.25_f64, epsilon = 1e-6); - assert_abs_diff_eq!(s2, 0.75_f64, epsilon = 1e-6); - }); -} - /// If one subnet has a negative EMA flow and the other positive, /// the negative one should contribute no weight (treated as zero), /// so the positive-flow subnet gets the full share. @@ -401,7 +306,6 @@ fn get_shares_negative_vs_positive_flow() { // Configure blending window and current block let half_life: u64 = FlowHalfLife::::get(); - FlowFirstBlock::::set(Some(0_u64)); FlowNormExponent::::set(u64f64(1.0)); frame_system::Pallet::::set_block_number(half_life); TaoFlowCutoff::::set(I64F64::from_num(0)); @@ -442,7 +346,6 @@ fn get_shares_both_negative_flows_zero_emission() { // Configure blending window and current block let half_life: u64 = FlowHalfLife::::get(); - FlowFirstBlock::::set(Some(0_u64)); FlowNormExponent::::set(u64f64(1.0)); frame_system::Pallet::::set_block_number(half_life); TaoFlowCutoff::::set(I64F64::from_num(0)); @@ -479,7 +382,6 @@ fn get_shares_both_below_cutoff_zero_emission() { // Configure blending window and current block let half_life: u64 = FlowHalfLife::::get(); - FlowFirstBlock::::set(Some(0_u64)); FlowNormExponent::::set(u64f64(1.0)); frame_system::Pallet::::set_block_number(half_life); TaoFlowCutoff::::set(I64F64::from_num(2_000)); @@ -519,7 +421,6 @@ fn get_shares_one_below_cutoff_other_full_emission() { // Configure blending window and current block let half_life: u64 = FlowHalfLife::::get(); - FlowFirstBlock::::set(Some(0_u64)); FlowNormExponent::::set(u64f64(1.0)); frame_system::Pallet::::set_block_number(half_life); TaoFlowCutoff::::set(I64F64::from_num(2_000)); @@ -561,7 +462,6 @@ fn get_shares_both_negative_above_cutoff() { // Configure blending window and current block let half_life: u64 = FlowHalfLife::::get(); - FlowFirstBlock::::set(Some(0_u64)); FlowNormExponent::::set(u64f64(1.0)); frame_system::Pallet::::set_block_number(half_life); TaoFlowCutoff::::set(I64F64::from_num(-1000.0));