From 54e7033c712ae668a2054f7d7c6364d5f446c448 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 00:26:41 -0500 Subject: [PATCH 01/32] change name of reveal fn and move to block step --- pallets/subtensor/src/coinbase/block_step.rs | 14 +++++++++++++- pallets/subtensor/src/coinbase/reveal_commits.rs | 2 +- pallets/subtensor/src/coinbase/run_coinbase.rs | 7 ++----- pallets/subtensor/src/tests/weights.rs | 6 +++--- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/pallets/subtensor/src/coinbase/block_step.rs b/pallets/subtensor/src/coinbase/block_step.rs index 818235a955..3547230a54 100644 --- a/pallets/subtensor/src/coinbase/block_step.rs +++ b/pallets/subtensor/src/coinbase/block_step.rs @@ -18,7 +18,9 @@ impl Pallet { .to_u64(), ); log::debug!("Block emission: {block_emission:?}"); - // --- 3. Run emission through network. + + // --- 3. Reveal matured weights. + Self::reveal_crv3_commits(); Self::run_coinbase(block_emission); // --- 4. Set pending children on the epoch; but only after the coinbase has been run. Self::try_set_pending_children(block_number); @@ -261,4 +263,14 @@ impl Pallet { return next_value.saturating_to_num::().into(); } } + + pub fn reveal_crv3_commits() { + let netuids: Vec = Self::get_all_subnet_netuids(); + for netuid in netuids.into_iter().filter(|netuid| *netuid != NetUid::ROOT) { + // Reveal matured weights. + if let Err(e) = Self::reveal_crv3_commits_for_subnet(netuid) { + log::warn!("Failed to reveal commits for subnet {netuid} due to error: {e:?}"); + }; + } + } } diff --git a/pallets/subtensor/src/coinbase/reveal_commits.rs b/pallets/subtensor/src/coinbase/reveal_commits.rs index 889c41d96a..6fdafa76da 100644 --- a/pallets/subtensor/src/coinbase/reveal_commits.rs +++ b/pallets/subtensor/src/coinbase/reveal_commits.rs @@ -36,7 +36,7 @@ pub struct LegacyWeightsTlockPayload { impl Pallet { /// The `reveal_crv3_commits` function is run at the very beginning of epoch `n`, - pub fn reveal_crv3_commits(netuid: NetUid) -> dispatch::DispatchResult { + pub fn reveal_crv3_commits_for_subnet(netuid: NetUid) -> dispatch::DispatchResult { let reveal_period = Self::get_reveal_period(netuid); let cur_block = Self::get_current_block_as_u64(); let cur_epoch = Self::get_epoch_index(netuid, cur_block); diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 5b1c4b5704..3824ceb8bd 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -239,11 +239,8 @@ impl Pallet { // --- 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. - if let Err(e) = Self::reveal_crv3_commits(netuid) { - log::warn!("Failed to reveal commits for subnet {netuid} due to error: {e:?}"); - }; - // Pass on subnets that have not reached their tempo. + + // Run the epoch if applicable. if Self::should_run_epoch(netuid, current_block) && Self::is_epoch_input_state_consistent(netuid) { diff --git a/pallets/subtensor/src/tests/weights.rs b/pallets/subtensor/src/tests/weights.rs index a71a225dc7..21d37984e4 100644 --- a/pallets/subtensor/src/tests/weights.rs +++ b/pallets/subtensor/src/tests/weights.rs @@ -5016,7 +5016,7 @@ fn test_reveal_crv3_commits_cannot_reveal_after_reveal_epoch() { step_epochs(1, netuid); // Attempt to reveal commits after the reveal epoch has passed - assert_ok!(SubtensorModule::reveal_crv3_commits(netuid)); + assert_ok!(SubtensorModule::reveal_crv3_commits_for_subnet(netuid)); // Verify that the weights for the neuron have not been set let weights_sparse = SubtensorModule::get_weights_sparse(netuid.into()); @@ -5353,7 +5353,7 @@ fn test_reveal_crv3_commits_decryption_failure() { }, ); - assert_ok!(SubtensorModule::reveal_crv3_commits(netuid)); + assert_ok!(SubtensorModule::reveal_crv3_commits_for_subnet(netuid)); let neuron_uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey) .expect("Failed to get neuron UID for hotkey") as usize; @@ -5973,7 +5973,7 @@ fn test_reveal_crv3_commits_removes_past_epoch_commits() { // --------------------------------------------------------------------- // Run the reveal pass WITHOUT a pulse – only expiry housekeeping runs. // --------------------------------------------------------------------- - assert_ok!(SubtensorModule::reveal_crv3_commits(netuid)); + assert_ok!(SubtensorModule::reveal_crv3_commits_for_subnet(netuid)); // past_epoch (< reveal_epoch) must be gone assert!( From 8ad48b37cb8f05bb6999fa8708e3c3d266a2fb19 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 00:28:22 -0500 Subject: [PATCH 02/32] update moving prices outside of coinbase --- pallets/subtensor/src/coinbase/block_step.rs | 19 ++++++++++++++++--- .../subtensor/src/coinbase/run_coinbase.rs | 11 ++--------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/pallets/subtensor/src/coinbase/block_step.rs b/pallets/subtensor/src/coinbase/block_step.rs index 3547230a54..21f7866459 100644 --- a/pallets/subtensor/src/coinbase/block_step.rs +++ b/pallets/subtensor/src/coinbase/block_step.rs @@ -21,12 +21,15 @@ impl Pallet { // --- 3. Reveal matured weights. Self::reveal_crv3_commits(); + // --- 4. Run emission through network. Self::run_coinbase(block_emission); - // --- 4. Set pending children on the epoch; but only after the coinbase has been run. + // --- 5. Update moving prices AFTER using them for emissions. + Self::update_moving_prices(); + // --- 6. Set pending children on the epoch; but only after the coinbase has been run. Self::try_set_pending_children(block_number); - // --- 5. Run auto-claim root divs. + // --- 7. Run auto-claim root divs. Self::run_auto_claim_root_divs(last_block_hash); - // --- 6. Populate root coldkey maps. + // --- 8. Populate root coldkey maps. Self::populate_root_coldkey_staking_maps(); // Return ok. @@ -264,6 +267,16 @@ impl Pallet { } } + pub fn update_moving_prices() { + let subnets_to_emit_to: Vec = + Self::get_subnets_to_emit_to(&Self::get_all_subnet_netuids()); + // 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); + } + } + pub fn reveal_crv3_commits() { let netuids: Vec = Self::get_all_subnet_netuids(); for netuid in netuids.into_iter().filter(|netuid| *netuid != NetUid::ROOT) { diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 3824ceb8bd..07f33e67a6 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -228,15 +228,8 @@ impl Pallet { *total = total.saturating_add(tou64!(pending_alpha).into()); }); } - - // --- 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); - } - - // --- 9. Drain pending emission through the subnet based on tempo. + + // --- Drain pending emissions for all subnets hat are at their tempo. // Run the epoch for *all* subnets, even if we don't emit anything. for &netuid in subnets.iter() { From f00a7ac9a0d12de60b990ca548762f31a4e00367 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 00:28:37 -0500 Subject: [PATCH 03/32] filter root out just in case --- pallets/subtensor/src/coinbase/subnet_emissions.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pallets/subtensor/src/coinbase/subnet_emissions.rs b/pallets/subtensor/src/coinbase/subnet_emissions.rs index 895a908ba8..c2a12b3c61 100644 --- a/pallets/subtensor/src/coinbase/subnet_emissions.rs +++ b/pallets/subtensor/src/coinbase/subnet_emissions.rs @@ -7,9 +7,11 @@ use subtensor_swap_interface::SwapHandler; impl Pallet { pub fn get_subnets_to_emit_to(subnets: &[NetUid]) -> Vec { + // Filter out root subnet. // Filter out subnets with no first emission block number. subnets .iter() + .filter(|netuid| *netuid != NetUid::ROOT) .filter(|netuid| FirstEmissionBlockNumber::::get(*netuid).is_some()) .copied() .collect() From feeee61979ffcddfeac5567b728750e8a73124f0 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 00:37:13 -0500 Subject: [PATCH 04/32] refactor coinbase --- .../subtensor/src/coinbase/run_coinbase.rs | 332 +++++++++++------- .../src/coinbase/subnet_emissions.rs | 2 +- 2 files changed, 199 insertions(+), 135 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 07f33e67a6..12c11a0118 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -22,111 +22,67 @@ impl Pallet { pub fn run_coinbase(block_emission: U96F32) { // --- 0. Get current block. let current_block: u64 = Self::get_current_block_as_u64(); - log::debug!("Current block: {current_block:?}"); - - // --- 1. Get all netuids (filter out root) + log::debug!( + "Running coinbase for block {current_block:?} with block emission: {block_emission:?}" + ); + // --- 1. Get all subnets (excluding root). let subnets: Vec = Self::get_all_subnet_netuids() .into_iter() .filter(|netuid| *netuid != NetUid::ROOT) .collect(); - log::debug!("All subnet netuids: {subnets:?}"); + log::debug!("All subnets: {subnets:?}"); - // 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); + // --- 2. Get subnets to emit to + let subnets_to_emit_to: Vec = Self::get_subnets_to_emit_to(&subnets); + log::debug!("Subnets to emit to: {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 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. - let price_i = T::SwapInterface::current_alpha_price((*netuid_i).into()); - log::debug!("price_i: {price_i:?}"); - // Emission is price over total. - let default_tao_in_i: U96F32 = subnet_emissions - .get(netuid_i) - .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()) - .unwrap_or(0) - ); - log::debug!("alpha_emission_i: {alpha_emission_i:?}"); + // --- 3. Get emissions for subnets to emit to + let subnet_emissions = + Self::get_subnet_block_emissions(&subnets_to_emit_to, block_emission); + log::debug!("Subnet emissions: {subnet_emissions:?}"); + let root_sell_flag = Self::get_network_root_sell_flag(&subnets_to_emit_to); + log::debug!("Root sell flag: {root_sell_flag:?}"); - // Get initial alpha_in - let mut alpha_in_i: U96F32; - let mut tao_in_i: U96F32; + // --- 4. Emit to subnets for this block. + Self::emit_to_subnets(&subnets_to_emit_to, &subnet_emissions, root_sell_flag); - 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); - excess_tao.insert(*netuid_i, difference_tao); - } else { - tao_in_i = default_tao_in_i; - 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:?}"); + // --- 5. Drain pending emissions. + let emissions_to_distribute = Self::drain_pending(&subnets, current_block); - // Get alpha_out. - let mut alpha_out_i = alpha_emission_i; - // Only emit TAO if the subnetwork allows registration. - if !Self::get_network_registration_allowed(*netuid_i) - && !Self::get_network_pow_registration_allowed(*netuid_i) - { - tao_in_i = asfloat!(0.0); - alpha_in_i = asfloat!(0.0); - alpha_out_i = asfloat!(0.0); - } - // Insert values into maps - tao_in.insert(*netuid_i, tao_in_i); - alpha_in.insert(*netuid_i, alpha_in_i); - alpha_out.insert(*netuid_i, alpha_out_i); - } - 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:?}"); + // --- 6. Distribute the emissions to the subnets. + Self::distribute_emissions_to_subnets(&emissions_to_distribute); + } - // --- 4. Inject and buy Alpha with any excess TAO. + pub fn inject_and_subsidize( + subnets_to_emit_to: &[NetUid], + tao_in: &BTreeMap, + alpha_in: &BTreeMap, + excess_tao: &BTreeMap, + ) { + // --- 2. Inject and subsidize 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)); + tou64!(*alpha_in.get(netuid_i).unwrap_or(&asfloat!(0))).into(); + let tao_to_swap_with: TaoCurrency = + tou64!(excess_tao.get(netuid_i).unwrap_or(&asfloat!(0))).into(); T::SwapInterface::adjust_protocol_liquidity(*netuid_i, tao_in_i, alpha_in_i); - if difference_tao > asfloat!(0) { + if tao_to_swap_with > TaoCurrency::ZERO { let buy_swap_result = Self::swap_tao_for_alpha( *netuid_i, - tou64!(difference_tao).into(), + tao_to_swap_with, 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); + let bought_alpha: AlphaCurrency = buy_swap_result_ok.amount_paid_out.into(); Self::recycle_subnet_alpha(*netuid_i, bought_alpha); } } - } - // --- 5. Update counters - for netuid_i in subnets_to_emit_to.iter() { // Inject Alpha in. let alpha_in_i = AlphaCurrency::from(tou64!(*alpha_in.get(netuid_i).unwrap_or(&asfloat!(0)))); @@ -134,52 +90,116 @@ impl Pallet { SubnetAlphaIn::::mutate(*netuid_i, |total| { *total = total.saturating_add(alpha_in_i); }); - // Injection Alpha out. - let alpha_out_i = - AlphaCurrency::from(tou64!(*alpha_out.get(netuid_i).unwrap_or(&asfloat!(0)))); - SubnetAlphaOutEmission::::insert(*netuid_i, alpha_out_i); - SubnetAlphaOut::::mutate(*netuid_i, |total| { - *total = total.saturating_add(alpha_out_i); - }); + // Inject TAO in. - let tao_in_i: TaoCurrency = + let injected_tao: TaoCurrency = tou64!(*tao_in.get(netuid_i).unwrap_or(&asfloat!(0))).into(); - SubnetTaoInEmission::::insert(*netuid_i, TaoCurrency::from(tao_in_i)); + SubnetTaoInEmission::::insert(*netuid_i, injected_tao); SubnetTAO::::mutate(*netuid_i, |total| { - *total = total.saturating_add(tao_in_i.into()); + *total = total.saturating_add(injected_tao); }); TotalStake::::mutate(|total| { - *total = total.saturating_add(tao_in_i.into()); + *total = total.saturating_add(injected_tao); }); - let difference_tao: U96F32 = *excess_tao.get(netuid_i).unwrap_or(&asfloat!(0)); + // Update total TAO issuance. + let difference_tao = tou64!(*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()); + *total = total + .saturating_add(injected_tao.into()) + .saturating_add(difference_tao.into()); }); } + } - // --- 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(); - let mut owner_cuts: BTreeMap = BTreeMap::new(); - for netuid_i in subnets_to_emit_to.iter() { - // Get alpha out. - let alpha_out_i: U96F32 = *alpha_out.get(netuid_i).unwrap_or(&asfloat!(0)); - log::debug!("alpha_out_i: {alpha_out_i:?}"); - // Calculate the owner cut. - let owner_cut_i: U96F32 = alpha_out_i.saturating_mul(cut_percent); - log::debug!("owner_cut_i: {owner_cut_i:?}"); - // Save owner cut. - *owner_cuts.entry(*netuid_i).or_insert(asfloat!(0)) = owner_cut_i; - // Save new alpha_out. - alpha_out.insert(*netuid_i, alpha_out_i.saturating_sub(owner_cut_i)); - // Accumulate the owner cut in pending. - PendingOwnerCut::::mutate(*netuid_i, |total| { - *total = total.saturating_add(tou64!(owner_cut_i).into()); - }); + pub fn get_subnet_terms( + subnet_emissions: &BTreeMap, + ) -> ( + BTreeMap, + BTreeMap, + BTreeMap, + BTreeMap, + ) { + // 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 subsidy_amount: BTreeMap = BTreeMap::new(); + + // Only calculate for subnets that we are emitting to. + for (&netuid_i, &tao_emission_i) in subnet_emissions.iter() { + let tao_in_i: U96F32; + let alpha_in_i: U96F32; + let alpha_out_i: U96F32; + // Only emit if the subnetwork allows registration. + if !Self::get_network_registration_allowed(netuid_i) + && !Self::get_network_pow_registration_allowed(netuid_i) + { + tao_in_i = asfloat!(0.0); + alpha_in_i = asfloat!(0.0); + alpha_out_i = asfloat!(0.0); + } else { + // Get alpha_emission total + let alpha_emission_i: U96F32 = asfloat!( + Self::get_block_emission_for_issuance( + Self::get_alpha_issuance(netuid_i).into() + ) + .unwrap_or(0) + ); + log::debug!("alpha_emission_i: {alpha_emission_i:?}"); + + // Get alpha_out. + alpha_out_i = alpha_emission_i; + + // Get subnet price. + let price_i = T::SwapInterface::current_alpha_price(netuid_i.into()); + log::debug!("price_i: {price_i:?}"); + + log::debug!("default_tao_in_i: {tao_emission_i:?}"); + let default_alpha_in_i: U96F32 = + tao_emission_i.safe_div_or(price_i, U96F32::saturating_from_num(alpha_emission_i)); + + // Get initial alpha_in + if default_alpha_in_i > alpha_emission_i { + alpha_in_i = alpha_emission_i; + tao_in_i = alpha_in_i.saturating_mul(price_i); + } else { + tao_in_i = tao_emission_i; + alpha_in_i = tao_in_i.safe_div_or(price_i, alpha_emission_i); + } + + let subsidy_tao: U96F32 = tao_emission_i.saturating_sub(tao_in_i); + subsidy_amount.insert(netuid_i, subsidy_tao); + } + + // Insert values into maps + tao_in.insert(netuid_i, tao_in_i); + alpha_in.insert(netuid_i, alpha_in_i); + alpha_out.insert(netuid_i, alpha_out_i); } + (tao_in, alpha_in, alpha_out, subsidy_amount) + } + + pub fn emit_to_subnets( + subnets_to_emit_to: &[NetUid], + subnet_emissions: &BTreeMap, + root_sell_flag: bool, + ) { + // --- 1. Get subnet terms (tao_in, alpha_in, and alpha_out) + // and subsidy amount. + let (tao_in, alpha_in, alpha_out, subsidy_amount) = + Self::get_subnet_terms(subnet_emissions); + + log::debug!("tao_in: {tao_in:?}"); + log::debug!("alpha_in: {alpha_in:?}"); + log::debug!("alpha_out: {alpha_out:?}"); + log::debug!("subsidy_amount: {subsidy_amount:?}"); + + // --- 2. Inject TAO and ALPHA to pool and subsidize. + Self::inject_and_subsidize(subnets_to_emit_to, &tao_in, &alpha_in, &subsidy_amount); + + // --- 3. Inject ALPHA for participants. + let cut_percent: U96F32 = Self::get_float_subnet_owner_cut(); // Get total TAO on root. let root_tao: U96F32 = asfloat!(SubnetTAO::::get(NetUid::ROOT)); @@ -188,20 +208,32 @@ impl Pallet { let tao_weight: U96F32 = root_tao.saturating_mul(Self::get_tao_weight()); log::debug!("tao_weight: {tao_weight:?}"); - // --- 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 alpha_out for this block. + let mut alpha_out_i: U96F32 = *alpha_out.get(netuid_i).unwrap_or(&asfloat!(0)); + + let alpha_created: AlphaCurrency = AlphaCurrency::from(tou64!(alpha_out_i)); + SubnetAlphaOutEmission::::insert(*netuid_i, alpha_created); + SubnetAlphaOut::::mutate(*netuid_i, |total| { + *total = total.saturating_add(alpha_created); + }); + + // Calculate the owner cut. + let owner_cut_i: U96F32 = alpha_out_i.saturating_mul(cut_percent); + log::debug!("owner_cut_i: {owner_cut_i:?}"); + // Deduct owner cut from alpha_out. + alpha_out_i = alpha_out_i.saturating_sub(owner_cut_i); + // Accumulate the owner cut in pending. + PendingOwnerCut::::mutate(*netuid_i, |total| { + *total = total.saturating_add(tou64!(owner_cut_i).into()); + }); // Get root proportion of alpha_out dividends. let mut root_alpha: U96F32 = asfloat!(0.0); if root_sell_flag { - // Get ALPHA issuance. + // 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)) @@ -216,23 +248,45 @@ impl Pallet { *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:?}"); + // Deduct root alpha from alpha_out. + alpha_out_i = alpha_out_i.saturating_sub(root_alpha); // Accumulate alpha emission in pending. + let pending_alpha: AlphaCurrency = tou64!(alpha_out_i).into(); + log::debug!("pending_alpha: {pending_alpha:?}"); PendingEmission::::mutate(*netuid_i, |total| { - *total = total.saturating_add(tou64!(pending_alpha).into()); + *total = total.saturating_add(pending_alpha); }); } - + } + + pub fn get_network_subsidy_mode(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 subsidize the network. + total_ema_price <= U96F32::saturating_from_num(1) + } + + pub fn drain_pending( + subnets: &[NetUid], + current_block: u64, + ) -> BTreeMap { + // Map of netuid to (pending_alpha, pending_root_alpha, pending_owner_cut). + let mut emissions_to_distribute: BTreeMap< + NetUid, + (AlphaCurrency, AlphaCurrency, AlphaCurrency), + > = BTreeMap::new(); // --- Drain pending emissions for all subnets hat are at their tempo. // Run the epoch for *all* subnets, even if we don't emit anything. for &netuid in subnets.iter() { - + // Increment blocks since last step. + BlocksSinceLastStep::::mutate(netuid, |total| *total = total.saturating_add(1)); + // Run the epoch if applicable. if Self::should_run_epoch(netuid, current_block) && Self::is_epoch_input_state_consistent(netuid) @@ -241,25 +295,35 @@ impl Pallet { BlocksSinceLastStep::::insert(netuid, 0); LastMechansimStepBlock::::insert(netuid, current_block); - // Get and drain the subnet pending emission. + // Get and drain the pending subnet emission. let pending_alpha = PendingEmission::::get(netuid); PendingEmission::::insert(netuid, AlphaCurrency::ZERO); - // Get and drain the subnet pending root alpha divs. + // Get and drain the pending Alpha for root divs. let pending_root_alpha = PendingRootAlphaDivs::::get(netuid); PendingRootAlphaDivs::::insert(netuid, AlphaCurrency::ZERO); - // Get owner cut and drain. + // Get and drain the pending owner cut. let owner_cut = PendingOwnerCut::::get(netuid); PendingOwnerCut::::insert(netuid, AlphaCurrency::ZERO); - // 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)); + // Save the emissions to distribute. + emissions_to_distribute + .insert(netuid, (pending_alpha, pending_root_alpha, owner_cut)); } } + emissions_to_distribute + } + + pub fn distribute_emissions_to_subnets( + emissions_to_distribute: &BTreeMap, + ) { + for (&netuid, &(pending_alpha, pending_root_alpha, pending_owner_cut)) in + emissions_to_distribute.iter() + { + // Distribute the emission to the subnet. + Self::distribute_emission(netuid, pending_alpha, pending_root_alpha, pending_owner_cut); + } } pub fn get_network_root_sell_flag(subnets_to_emit_to: &[NetUid]) -> bool { diff --git a/pallets/subtensor/src/coinbase/subnet_emissions.rs b/pallets/subtensor/src/coinbase/subnet_emissions.rs index c2a12b3c61..374619b22f 100644 --- a/pallets/subtensor/src/coinbase/subnet_emissions.rs +++ b/pallets/subtensor/src/coinbase/subnet_emissions.rs @@ -11,7 +11,7 @@ impl Pallet { // Filter out subnets with no first emission block number. subnets .iter() - .filter(|netuid| *netuid != NetUid::ROOT) + .filter(|&netuid| *netuid != NetUid::ROOT) .filter(|netuid| FirstEmissionBlockNumber::::get(*netuid).is_some()) .copied() .collect() From 4abd92df8d626a35cd41cb8a41cb20d86a6be691 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 00:37:26 -0500 Subject: [PATCH 05/32] chore: fmt --- pallets/subtensor/src/coinbase/run_coinbase.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 12c11a0118..d678b8b1a8 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -156,8 +156,8 @@ impl Pallet { log::debug!("price_i: {price_i:?}"); log::debug!("default_tao_in_i: {tao_emission_i:?}"); - let default_alpha_in_i: U96F32 = - tao_emission_i.safe_div_or(price_i, U96F32::saturating_from_num(alpha_emission_i)); + let default_alpha_in_i: U96F32 = tao_emission_i + .safe_div_or(price_i, U96F32::saturating_from_num(alpha_emission_i)); // Get initial alpha_in if default_alpha_in_i > alpha_emission_i { From 1f965d47ddfcf03694b49dc473b3e52bf3cb24f6 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 00:49:35 -0500 Subject: [PATCH 06/32] chore: clippy --- pallets/subtensor/src/coinbase/subnet_emissions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/coinbase/subnet_emissions.rs b/pallets/subtensor/src/coinbase/subnet_emissions.rs index 374619b22f..3f06092084 100644 --- a/pallets/subtensor/src/coinbase/subnet_emissions.rs +++ b/pallets/subtensor/src/coinbase/subnet_emissions.rs @@ -12,7 +12,7 @@ impl Pallet { subnets .iter() .filter(|&netuid| *netuid != NetUid::ROOT) - .filter(|netuid| FirstEmissionBlockNumber::::get(*netuid).is_some()) + .filter(|&netuid| FirstEmissionBlockNumber::::get(*netuid).is_some()) .copied() .collect() } From 1ea4ddf1cc25d0469bb4c71832d4907e28e8a482 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 02:04:57 -0500 Subject: [PATCH 07/32] add test for reg disabled --- pallets/subtensor/src/tests/coinbase.rs | 46 +++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 5d03afa973..6eeca6a436 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -3287,3 +3287,49 @@ fn test_mining_emission_distribution_with_root_sell() { ); }); } + +#[test] +fn test_coinbase_subnets_with_no_reg_get_no_emission() { + new_test_ext(1).execute_with(|| { + let zero = U96F32::saturating_from_num(0); + let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2)); + let netuid1 = add_dynamic_network(&U256::from(3), &U256::from(4)); + + let subnet_emissions = BTreeMap::from([ + (netuid0, U96F32::saturating_from_num(1)), + (netuid1, U96F32::saturating_from_num(1)), + ]); + + let (tao_in, alpha_in, alpha_out, subsidy_amount) = + SubtensorModule::get_subnet_terms(&subnet_emissions); + assert_eq!(tao_in.len(), 2); + assert_eq!(alpha_in.len(), 2); + assert_eq!(alpha_out.len(), 2); + + assert!(tao_in[&netuid0] > zero); + assert!(alpha_in[&netuid0] > zero); + assert!(alpha_out[&netuid0] > zero); + + assert!(tao_in[&netuid1] > zero); + assert!(alpha_in[&netuid1] > zero); + assert!(alpha_out[&netuid1] > zero); + + // Disabled registration of both methods + NetworkRegistrationAllowed::::insert(netuid0, false); + NetworkPowRegistrationAllowed::::insert(netuid0, false); + + let (tao_in_2, alpha_in_2, alpha_out_2, subsidy_amount_2) = + SubtensorModule::get_subnet_terms(&subnet_emissions); + assert_eq!(tao_in_2.len(), 2); + assert_eq!(alpha_in_2.len(), 2); + assert_eq!(alpha_out_2.len(), 2); + + assert!(tao_in_2[&netuid0] == zero); + assert!(alpha_in_2[&netuid0] == zero); + assert!(alpha_out_2[&netuid0] == zero); + + assert!(tao_in_2[&netuid1] > zero); + assert!(alpha_in_2[&netuid1] > zero); + assert!(alpha_out_2[&netuid1] > zero); + }); +} From 7a9b662e6f25e4d8cfb32db46c13174c5665e394 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 02:14:24 -0500 Subject: [PATCH 08/32] refactor get sn terms --- pallets/subtensor/src/coinbase/run_coinbase.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index d678b8b1a8..91e67a5f68 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -128,8 +128,8 @@ impl Pallet { // Only calculate for subnets that we are emitting to. for (&netuid_i, &tao_emission_i) in subnet_emissions.iter() { - let tao_in_i: U96F32; - let alpha_in_i: U96F32; + let mut tao_in_i: U96F32; + let mut alpha_in_i: U96F32; let alpha_out_i: U96F32; // Only emit if the subnetwork allows registration. if !Self::get_network_registration_allowed(netuid_i) @@ -155,17 +155,12 @@ impl Pallet { let price_i = T::SwapInterface::current_alpha_price(netuid_i.into()); log::debug!("price_i: {price_i:?}"); - log::debug!("default_tao_in_i: {tao_emission_i:?}"); - let default_alpha_in_i: U96F32 = tao_emission_i - .safe_div_or(price_i, U96F32::saturating_from_num(alpha_emission_i)); + tao_in_i = tao_emission_i; + alpha_in_i = tao_emission_i.safe_div_or(price_i, U96F32::saturating_from_num(0.0)); - // Get initial alpha_in - if default_alpha_in_i > alpha_emission_i { + if alpha_in_i > alpha_emission_i { alpha_in_i = alpha_emission_i; tao_in_i = alpha_in_i.saturating_mul(price_i); - } else { - tao_in_i = tao_emission_i; - alpha_in_i = tao_in_i.safe_div_or(price_i, alpha_emission_i); } let subsidy_tao: U96F32 = tao_emission_i.saturating_sub(tao_in_i); From 0b5df3c8ed890706ef9c468ebd9aeaa7cbf195d9 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 10:36:17 -0500 Subject: [PATCH 09/32] test wip -- set price --- pallets/subtensor/src/tests/coinbase.rs | 79 ++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 6eeca6a436..1696441a74 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -8,7 +8,10 @@ use approx::assert_abs_diff_eq; use frame_support::assert_ok; use pallet_subtensor_swap::position::PositionId; use sp_core::U256; -use substrate_fixed::types::{I64F64, I96F32, U64F64, U96F32}; +use substrate_fixed::{ + transcendental::sqrt, + types::{I64F64, I96F32, U64F64, U96F32}, +}; use subtensor_runtime_common::{AlphaCurrency, NetUidStorageIndex}; use subtensor_swap_interface::{SwapEngine, SwapHandler}; @@ -3333,3 +3336,77 @@ fn test_coinbase_subnets_with_no_reg_get_no_emission() { assert!(alpha_out_2[&netuid1] > zero); }); } + +#[test] +fn test_coinbase_alpha_in_more_than_alpha_emission() { + new_test_ext(1).execute_with(|| { + let zero = U96F32::saturating_from_num(0); + let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2)); + mock::setup_reserves( + netuid0, + TaoCurrency::from(1_000_000_000_000_000), + AlphaCurrency::from(1_000_000_000_000_000), + ); + + // Set netuid0 to have price tao_emission / price > alpha_emission + let alpha_emission = U96F32::saturating_from_num( + SubtensorModule::get_block_emission_for_issuance( + SubtensorModule::get_alpha_issuance(netuid0).into(), + ) + .unwrap_or(0), + ); + let price_to_set: U64F64 = U64F64::saturating_from_num(0.2); + let price_to_set_fixed: U96F32 = U96F32::saturating_from_num(price_to_set); + let sqrt_price_to_set: U64F64 = sqrt(price_to_set).unwrap(); + + let tao_emission: U96F32 = U96F32::saturating_from_num(alpha_emission) + .saturating_mul(price_to_set_fixed) + .saturating_add(U96F32::saturating_from_num(0.01)); + + // Set the price + pallet_subtensor_swap::AlphaSqrtPrice::::insert(netuid0, sqrt_price_to_set); + // Check the price is set + assert_abs_diff_eq!( + pallet_subtensor_swap::Pallet::::current_alpha_price(netuid0).to_num::(), + price_to_set.to_num::(), + epsilon = 1.0 + ); + + let subnet_emissions = BTreeMap::from([(netuid0, tao_emission)]); + + let (tao_in, alpha_in, alpha_out, subsidy_amount) = + SubtensorModule::get_subnet_terms(&subnet_emissions); + + // Check our condition is met + assert!(tao_emission / price_to_set_fixed > alpha_emission); + + // alpha_out should be the alpha_emission, always + assert_abs_diff_eq!( + alpha_out[&netuid0].to_num::(), + alpha_emission.to_num::(), + epsilon = 1.0 + ); + + // alpha_in should equal the alpha_emission + assert_abs_diff_eq!( + alpha_in[&netuid0].to_num::(), + alpha_emission.to_num::(), + epsilon = 1.0 + ); + // tao_in should be the alpha_in at the ratio of the price + assert_abs_diff_eq!( + tao_in[&netuid0].to_num::(), + alpha_in[&netuid0] + .saturating_mul(price_to_set_fixed) + .to_num::(), + epsilon = 1.0 + ); + + // subsidy_amount should be the difference between the tao_emission and the tao_in + assert_abs_diff_eq!( + subsidy_amount[&netuid0].to_num::(), + tao_emission.to_num::() - tao_in[&netuid0].to_num::(), + epsilon = 1.0 + ); + }); +} From fb0cd73328e6b0e044674cb2de6f34694e6b0a75 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 18:28:48 -0500 Subject: [PATCH 10/32] make maybe init v3 pub --- pallets/swap/src/pallet/impls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index 34b5e624e6..de08022060 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -68,7 +68,7 @@ impl Pallet { } // initializes V3 swap for a subnet if needed - pub(super) fn maybe_initialize_v3(netuid: NetUid) -> Result<(), Error> { + pub fn maybe_initialize_v3(netuid: NetUid) -> Result<(), Error> { if SwapV3Initialized::::get(netuid) { return Ok(()); } From 6e2003594ac01ad672849495ab512a67db447b8e Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 18:29:05 -0500 Subject: [PATCH 11/32] add type --- pallets/subtensor/src/coinbase/run_coinbase.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 91e67a5f68..ef15d8a425 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -152,7 +152,7 @@ impl Pallet { alpha_out_i = alpha_emission_i; // Get subnet price. - let price_i = T::SwapInterface::current_alpha_price(netuid_i.into()); + let price_i: U96F32 = T::SwapInterface::current_alpha_price(netuid_i.into()); log::debug!("price_i: {price_i:?}"); tao_in_i = tao_emission_i; From 660b1c647b3da7025931ea795c517d9f3d7a9ecf Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 18:29:18 -0500 Subject: [PATCH 12/32] fix coinbase test --- pallets/subtensor/src/tests/coinbase.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 1696441a74..80d1e9b4f7 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -3347,6 +3347,8 @@ fn test_coinbase_alpha_in_more_than_alpha_emission() { TaoCurrency::from(1_000_000_000_000_000), AlphaCurrency::from(1_000_000_000_000_000), ); + // Initialize swap v3 + Swap::maybe_initialize_v3(netuid0); // Set netuid0 to have price tao_emission / price > alpha_emission let alpha_emission = U96F32::saturating_from_num( @@ -3355,7 +3357,7 @@ fn test_coinbase_alpha_in_more_than_alpha_emission() { ) .unwrap_or(0), ); - let price_to_set: U64F64 = U64F64::saturating_from_num(0.2); + let price_to_set: U64F64 = U64F64::saturating_from_num(0.01); let price_to_set_fixed: U96F32 = U96F32::saturating_from_num(price_to_set); let sqrt_price_to_set: U64F64 = sqrt(price_to_set).unwrap(); @@ -3369,7 +3371,7 @@ fn test_coinbase_alpha_in_more_than_alpha_emission() { assert_abs_diff_eq!( pallet_subtensor_swap::Pallet::::current_alpha_price(netuid0).to_num::(), price_to_set.to_num::(), - epsilon = 1.0 + epsilon = 0.001 ); let subnet_emissions = BTreeMap::from([(netuid0, tao_emission)]); From a0c2369aea6ec72b7fc7e23550e83c38f3514d50 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 18:31:08 -0500 Subject: [PATCH 13/32] rename test vars and add comment --- pallets/subtensor/src/tests/coinbase.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 80d1e9b4f7..36e8a2b783 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -3337,6 +3337,7 @@ fn test_coinbase_subnets_with_no_reg_get_no_emission() { }); } +// Tests for the excess TAO condition #[test] fn test_coinbase_alpha_in_more_than_alpha_emission() { new_test_ext(1).execute_with(|| { @@ -3376,7 +3377,7 @@ fn test_coinbase_alpha_in_more_than_alpha_emission() { let subnet_emissions = BTreeMap::from([(netuid0, tao_emission)]); - let (tao_in, alpha_in, alpha_out, subsidy_amount) = + let (tao_in, alpha_in, alpha_out, excess_tao) = SubtensorModule::get_subnet_terms(&subnet_emissions); // Check our condition is met @@ -3404,9 +3405,9 @@ fn test_coinbase_alpha_in_more_than_alpha_emission() { epsilon = 1.0 ); - // subsidy_amount should be the difference between the tao_emission and the tao_in + // excess_tao should be the difference between the tao_emission and the tao_in assert_abs_diff_eq!( - subsidy_amount[&netuid0].to_num::(), + excess_tao[&netuid0].to_num::(), tao_emission.to_num::() - tao_in[&netuid0].to_num::(), epsilon = 1.0 ); From 8652a5fd2b2ac9dfa4a578c2c9d3c46a6d125a0b Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 18:34:49 -0500 Subject: [PATCH 14/32] rename fn --- pallets/subtensor/src/coinbase/run_coinbase.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index ef15d8a425..d0f6a1ac09 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -53,13 +53,12 @@ impl Pallet { Self::distribute_emissions_to_subnets(&emissions_to_distribute); } - pub fn inject_and_subsidize( + pub fn inject_and_maybe_swap( subnets_to_emit_to: &[NetUid], tao_in: &BTreeMap, alpha_in: &BTreeMap, excess_tao: &BTreeMap, ) { - // --- 2. Inject and subsidize 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(); @@ -190,8 +189,8 @@ impl Pallet { log::debug!("alpha_out: {alpha_out:?}"); log::debug!("subsidy_amount: {subsidy_amount:?}"); - // --- 2. Inject TAO and ALPHA to pool and subsidize. - Self::inject_and_subsidize(subnets_to_emit_to, &tao_in, &alpha_in, &subsidy_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); // --- 3. Inject ALPHA for participants. let cut_percent: U96F32 = Self::get_float_subnet_owner_cut(); From ba0fae0332fd63a92c02a8e4a9f675a7df70ce44 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 18:35:09 -0500 Subject: [PATCH 15/32] rename sub -> excess --- pallets/subtensor/src/coinbase/run_coinbase.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index d0f6a1ac09..fda0210549 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -123,7 +123,7 @@ impl Pallet { let mut tao_in: BTreeMap = BTreeMap::new(); let mut alpha_in: BTreeMap = BTreeMap::new(); let mut alpha_out: BTreeMap = BTreeMap::new(); - let mut subsidy_amount: BTreeMap = BTreeMap::new(); + let mut excess_amount: BTreeMap = BTreeMap::new(); // Only calculate for subnets that we are emitting to. for (&netuid_i, &tao_emission_i) in subnet_emissions.iter() { @@ -162,8 +162,8 @@ impl Pallet { tao_in_i = alpha_in_i.saturating_mul(price_i); } - let subsidy_tao: U96F32 = tao_emission_i.saturating_sub(tao_in_i); - subsidy_amount.insert(netuid_i, subsidy_tao); + let excess_tao: U96F32 = tao_emission_i.saturating_sub(tao_in_i); + excess_amount.insert(netuid_i, excess_tao); } // Insert values into maps @@ -171,7 +171,7 @@ impl Pallet { alpha_in.insert(netuid_i, alpha_in_i); alpha_out.insert(netuid_i, alpha_out_i); } - (tao_in, alpha_in, alpha_out, subsidy_amount) + (tao_in, alpha_in, alpha_out, excess_amount) } pub fn emit_to_subnets( @@ -180,14 +180,13 @@ impl Pallet { root_sell_flag: bool, ) { // --- 1. Get subnet terms (tao_in, alpha_in, and alpha_out) - // and subsidy amount. - let (tao_in, alpha_in, alpha_out, subsidy_amount) = - Self::get_subnet_terms(subnet_emissions); + // and excess_tao amounts. + let (tao_in, alpha_in, alpha_out, excess_amount) = Self::get_subnet_terms(subnet_emissions); log::debug!("tao_in: {tao_in:?}"); log::debug!("alpha_in: {alpha_in:?}"); log::debug!("alpha_out: {alpha_out:?}"); - log::debug!("subsidy_amount: {subsidy_amount:?}"); + 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); From 8e6286c20dc49c7419ed4c5b8d641abe709f764f Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 18:49:11 -0500 Subject: [PATCH 16/32] remove ref to subs --- pallets/subtensor/src/tests/coinbase.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 36e8a2b783..b1546d219d 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -3303,8 +3303,7 @@ fn test_coinbase_subnets_with_no_reg_get_no_emission() { (netuid1, U96F32::saturating_from_num(1)), ]); - let (tao_in, alpha_in, alpha_out, subsidy_amount) = - SubtensorModule::get_subnet_terms(&subnet_emissions); + let (tao_in, alpha_in, alpha_out, _) = SubtensorModule::get_subnet_terms(&subnet_emissions); assert_eq!(tao_in.len(), 2); assert_eq!(alpha_in.len(), 2); assert_eq!(alpha_out.len(), 2); @@ -3321,7 +3320,7 @@ fn test_coinbase_subnets_with_no_reg_get_no_emission() { NetworkRegistrationAllowed::::insert(netuid0, false); NetworkPowRegistrationAllowed::::insert(netuid0, false); - let (tao_in_2, alpha_in_2, alpha_out_2, subsidy_amount_2) = + let (tao_in_2, alpha_in_2, alpha_out_2, _) = SubtensorModule::get_subnet_terms(&subnet_emissions); assert_eq!(tao_in_2.len(), 2); assert_eq!(alpha_in_2.len(), 2); From c38aa09dcf67330afd116d752f1c30092bf3ead9 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 18:49:41 -0500 Subject: [PATCH 17/32] remove dup fn --- pallets/subtensor/src/coinbase/run_coinbase.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index a03992347f..b82a0b0bf0 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -255,16 +255,6 @@ impl Pallet { } } - pub fn get_network_subsidy_mode(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 subsidize the network. - total_ema_price <= U96F32::saturating_from_num(1) - } - pub fn drain_pending( subnets: &[NetUid], current_block: u64, From 61227c8a21e9e17f08f9a4cc050533d4b17e257a Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 18:49:51 -0500 Subject: [PATCH 18/32] rename vars --- pallets/subtensor/src/coinbase/run_coinbase.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index b82a0b0bf0..4c0ba98974 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -123,7 +123,7 @@ impl Pallet { let mut tao_in: BTreeMap = BTreeMap::new(); let mut alpha_in: BTreeMap = BTreeMap::new(); let mut alpha_out: BTreeMap = BTreeMap::new(); - let mut excess_amount: BTreeMap = BTreeMap::new(); + let mut excess_tao: BTreeMap = BTreeMap::new(); // Only calculate for subnets that we are emitting to. for (&netuid_i, &tao_emission_i) in subnet_emissions.iter() { @@ -162,8 +162,8 @@ impl Pallet { tao_in_i = alpha_in_i.saturating_mul(price_i); } - let excess_tao: U96F32 = tao_emission_i.saturating_sub(tao_in_i); - excess_amount.insert(netuid_i, excess_tao); + let excess_amount: U96F32 = tao_emission_i.saturating_sub(tao_in_i); + excess_tao.insert(netuid_i, excess_amount); } // Insert values into maps @@ -171,7 +171,7 @@ impl Pallet { alpha_in.insert(netuid_i, alpha_in_i); alpha_out.insert(netuid_i, alpha_out_i); } - (tao_in, alpha_in, alpha_out, excess_amount) + (tao_in, alpha_in, alpha_out, excess_tao) } pub fn emit_to_subnets( From c2598a9296b921e11b3a705168656ed60fd0228c Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 20:10:26 -0500 Subject: [PATCH 19/32] add test for injection order --- pallets/subtensor/src/tests/coinbase.rs | 39 +++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index b1546d219d..6b4f83746c 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -3412,3 +3412,42 @@ fn test_coinbase_alpha_in_more_than_alpha_emission() { ); }); } + +// Tests for the excess TAO condition +#[test] +fn test_coinbase_inject_and_maybe_swap_does_not_skew_reserves() { + new_test_ext(1).execute_with(|| { + let zero = U96F32::saturating_from_num(0); + let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2)); + mock::setup_reserves( + netuid0, + TaoCurrency::from(1_000_000_000_000_000), + AlphaCurrency::from(1_000_000_000_000_000), + ); + // Initialize swap v3 + Swap::maybe_initialize_v3(netuid0); + + let tao_in = BTreeMap::from([(netuid0, U96F32::saturating_from_num(123))]); + let alpha_in = BTreeMap::from([(netuid0, U96F32::saturating_from_num(456))]); + let excess_tao = BTreeMap::from([(netuid0, U96F32::saturating_from_num(789100))]); + + // Run the inject and maybe swap + SubtensorModule::inject_and_maybe_swap(&[netuid0], &tao_in, &alpha_in, &excess_tao); + + let tao_in_after = SubnetTAO::::get(netuid0); + let alpha_in_after = SubnetAlphaIn::::get(netuid0); + + // Make sure that when we inject and swap, we do it in the right order. + // Thereby not skewing the ratio away from the price. + let ratio_after: U96F32 = U96F32::saturating_from_num(alpha_in_after.to_u64()) + .saturating_div(U96F32::saturating_from_num(tao_in_after.to_u64())); + let price_after: U96F32 = U96F32::saturating_from_num( + pallet_subtensor_swap::Pallet::::current_alpha_price(netuid0).to_num::(), + ); + assert_abs_diff_eq!( + ratio_after.to_num::(), + price_after.to_num::(), + epsilon = 1.0 + ); + }); +} From 09a2cac9d15b55dfccd8d25f2e73b7f7c2ce80ff Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 20:12:28 -0500 Subject: [PATCH 20/32] add comment to test --- pallets/subtensor/src/tests/coinbase.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 6b4f83746c..c50ecb1276 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -3429,6 +3429,7 @@ fn test_coinbase_inject_and_maybe_swap_does_not_skew_reserves() { let tao_in = BTreeMap::from([(netuid0, U96F32::saturating_from_num(123))]); let alpha_in = BTreeMap::from([(netuid0, U96F32::saturating_from_num(456))]); + // We have excess TAO, so we will be swapping with it. let excess_tao = BTreeMap::from([(netuid0, U96F32::saturating_from_num(789100))]); // Run the inject and maybe swap From 3b9c11e20b846fd197c1cec3af619d3cecb9f148 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 20:46:37 -0500 Subject: [PATCH 21/32] add tests for drain pending --- pallets/subtensor/src/tests/coinbase.rs | 89 ++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index c50ecb1276..52a2b5f482 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -3413,7 +3413,7 @@ fn test_coinbase_alpha_in_more_than_alpha_emission() { }); } -// Tests for the excess TAO condition +// Tests for the inject and swap are in the right order. #[test] fn test_coinbase_inject_and_maybe_swap_does_not_skew_reserves() { new_test_ext(1).execute_with(|| { @@ -3452,3 +3452,90 @@ fn test_coinbase_inject_and_maybe_swap_does_not_skew_reserves() { ); }); } + +#[test] +fn test_coinbase_drain_pending_increments_blockssincelaststep() { + new_test_ext(1).execute_with(|| { + let zero = U96F32::saturating_from_num(0); + let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2)); + + let blocks_since_last_step_before = BlocksSinceLastStep::::get(netuid0); + + // Check that blockssincelaststep is incremented + SubtensorModule::drain_pending(&[netuid0], 1); + + let blocks_since_last_step_after = BlocksSinceLastStep::::get(netuid0); + assert!(blocks_since_last_step_after > blocks_since_last_step_before); + assert_eq!( + blocks_since_last_step_after, + blocks_since_last_step_before + 1 + ); + }); +} + +#[test] +fn test_coinbase_drain_pending_resets_blockssincelaststep() { + new_test_ext(1).execute_with(|| { + let zero = U96F32::saturating_from_num(0); + let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2)); + Tempo::::insert(netuid0, 100); + // Ensure the block number we use is the tempo block + let block_number = 98; + assert!(SubtensorModule::should_run_epoch(netuid0, block_number)); + + let blocks_since_last_step_before = 12345678; + BlocksSinceLastStep::::insert(netuid0, blocks_since_last_step_before); + LastMechansimStepBlock::::insert(netuid0, 12345); // garbage value + + // Check that blockssincelaststep is reset to 0 on tempo + SubtensorModule::drain_pending(&[netuid0], block_number); + + let blocks_since_last_step_after = BlocksSinceLastStep::::get(netuid0); + assert_eq!(blocks_since_last_step_after, 0); + // Also check LastMechansimStepBlock is set to the block number we ran on + assert_eq!(LastMechansimStepBlock::::get(netuid0), block_number); + }); +} + +#[test] +fn test_coinbase_drain_pending_gets_counters_and_resets_them() { + new_test_ext(1).execute_with(|| { + let zero = U96F32::saturating_from_num(0); + let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2)); + Tempo::::insert(netuid0, 100); + // Ensure the block number we use is the tempo block + let block_number = 98; + assert!(SubtensorModule::should_run_epoch(netuid0, block_number)); + + mock::setup_reserves( + netuid0, + TaoCurrency::from(1_000_000_000_000_000), + AlphaCurrency::from(1_000_000_000_000_000), + ); + // Initialize swap v3 + Swap::maybe_initialize_v3(netuid0); + + let pending_em = AlphaCurrency::from(123434534); + let pending_root = AlphaCurrency::from(12222222); + let pending_owner_cut = AlphaCurrency::from(12345678); + + PendingEmission::::insert(netuid0, pending_em); + PendingRootAlphaDivs::::insert(netuid0, pending_root); + PendingOwnerCut::::insert(netuid0, pending_owner_cut); + + let emissions_to_distribute = SubtensorModule::drain_pending(&[netuid0], block_number); + assert_eq!(emissions_to_distribute.len(), 1); + assert_eq!( + emissions_to_distribute[&netuid0], + (pending_em, pending_root, pending_owner_cut) + ); + + // Check that the pending emissions are reset + assert_eq!(PendingEmission::::get(netuid0), AlphaCurrency::ZERO); + assert_eq!( + PendingRootAlphaDivs::::get(netuid0), + AlphaCurrency::ZERO + ); + assert_eq!(PendingOwnerCut::::get(netuid0), AlphaCurrency::ZERO); + }); +} From 1b540d9727808737b25a4b0e50796da09c201ab0 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 20:54:31 -0500 Subject: [PATCH 22/32] rename test --- pallets/subtensor/src/tests/coinbase.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 52a2b5f482..07ffdf09cf 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -3338,7 +3338,7 @@ fn test_coinbase_subnets_with_no_reg_get_no_emission() { // Tests for the excess TAO condition #[test] -fn test_coinbase_alpha_in_more_than_alpha_emission() { +fn test_coinbase_subnet_terms_with_alpha_in_more_than_alpha_emission() { new_test_ext(1).execute_with(|| { let zero = U96F32::saturating_from_num(0); let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2)); From d3d5819a4e413f3b2dace869ac9522f914545224 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 20:59:50 -0500 Subject: [PATCH 23/32] rename tests --- pallets/subtensor/src/tests/coinbase.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 07ffdf09cf..d90b7fee5f 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -3292,7 +3292,7 @@ fn test_mining_emission_distribution_with_root_sell() { } #[test] -fn test_coinbase_subnets_with_no_reg_get_no_emission() { +fn test_coinbase_subnet_terms_with_no_reg_get_no_emission() { new_test_ext(1).execute_with(|| { let zero = U96F32::saturating_from_num(0); let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2)); @@ -3338,7 +3338,7 @@ fn test_coinbase_subnets_with_no_reg_get_no_emission() { // Tests for the excess TAO condition #[test] -fn test_coinbase_subnet_terms_with_alpha_in_more_than_alpha_emission() { +fn test_coinbase_subnet_terms_with_alpha_in_gt_alpha_emission() { new_test_ext(1).execute_with(|| { let zero = U96F32::saturating_from_num(0); let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2)); From f1c04761291dbd8f3c0e0d7ebbe93caf97a61292 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 21:01:46 -0500 Subject: [PATCH 24/32] lower eps --- pallets/subtensor/src/tests/coinbase.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index d90b7fee5f..39173c8aee 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -3386,14 +3386,14 @@ fn test_coinbase_subnet_terms_with_alpha_in_gt_alpha_emission() { assert_abs_diff_eq!( alpha_out[&netuid0].to_num::(), alpha_emission.to_num::(), - epsilon = 1.0 + epsilon = 0.01 ); // alpha_in should equal the alpha_emission assert_abs_diff_eq!( alpha_in[&netuid0].to_num::(), alpha_emission.to_num::(), - epsilon = 1.0 + epsilon = 0.01 ); // tao_in should be the alpha_in at the ratio of the price assert_abs_diff_eq!( @@ -3401,14 +3401,14 @@ fn test_coinbase_subnet_terms_with_alpha_in_gt_alpha_emission() { alpha_in[&netuid0] .saturating_mul(price_to_set_fixed) .to_num::(), - epsilon = 1.0 + epsilon = 0.01 ); // excess_tao should be the difference between the tao_emission and the tao_in assert_abs_diff_eq!( excess_tao[&netuid0].to_num::(), tao_emission.to_num::() - tao_in[&netuid0].to_num::(), - epsilon = 1.0 + epsilon = 0.01 ); }); } From 9de47edd62018e4621beedf237ce1800956ecc4a Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 21:02:00 -0500 Subject: [PATCH 25/32] add another subnetterms test --- pallets/subtensor/src/tests/coinbase.rs | 62 +++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 39173c8aee..50769262da 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -3413,6 +3413,68 @@ fn test_coinbase_subnet_terms_with_alpha_in_gt_alpha_emission() { }); } +#[test] +fn test_coinbase_subnet_terms_with_alpha_in_lte_alpha_emission() { + new_test_ext(1).execute_with(|| { + let zero = U96F32::saturating_from_num(0); + let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2)); + mock::setup_reserves( + netuid0, + TaoCurrency::from(1_000_000_000_000_000), + AlphaCurrency::from(1_000_000_000_000_000), + ); + // Initialize swap v3 + Swap::maybe_initialize_v3(netuid0); + + let alpha_emission = U96F32::saturating_from_num( + SubtensorModule::get_block_emission_for_issuance( + SubtensorModule::get_alpha_issuance(netuid0).into(), + ) + .unwrap_or(0), + ); + let tao_emission = U96F32::saturating_from_num(34566756_u64); + + let price: U96F32 = Swap::current_alpha_price(netuid0); + + let subnet_emissions = BTreeMap::from([(netuid0, tao_emission)]); + + let (tao_in, alpha_in, alpha_out, excess_tao) = + SubtensorModule::get_subnet_terms(&subnet_emissions); + + // Check our condition is met + assert!(tao_emission / price <= alpha_emission); + + // alpha_out should be the alpha_emission, always + assert_abs_diff_eq!( + alpha_out[&netuid0].to_num::(), + alpha_emission.to_num::(), + epsilon = 0.1 + ); + + // assuming alpha_in < alpha_emission + // Then alpha_in should be tao_emission / price + assert_abs_diff_eq!( + alpha_in[&netuid0].to_num::(), + tao_emission.to_num::() / price.to_num::(), + epsilon = 0.01 + ); + + // tao_in should be the tao_emission + assert_abs_diff_eq!( + tao_in[&netuid0].to_num::(), + tao_emission.to_num::(), + epsilon = 0.01 + ); + + // excess_tao should be 0 + assert_abs_diff_eq!( + excess_tao[&netuid0].to_num::(), + tao_emission.to_num::() - tao_in[&netuid0].to_num::(), + epsilon = 0.01 + ); + }); +} + // Tests for the inject and swap are in the right order. #[test] fn test_coinbase_inject_and_maybe_swap_does_not_skew_reserves() { From 01f8f6135e273839f240572b56bf3cc211aa8d3b Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 21:04:30 -0500 Subject: [PATCH 26/32] remove unneeded test setup --- pallets/subtensor/src/tests/coinbase.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 50769262da..726c7c608e 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -3569,14 +3569,6 @@ fn test_coinbase_drain_pending_gets_counters_and_resets_them() { let block_number = 98; assert!(SubtensorModule::should_run_epoch(netuid0, block_number)); - mock::setup_reserves( - netuid0, - TaoCurrency::from(1_000_000_000_000_000), - AlphaCurrency::from(1_000_000_000_000_000), - ); - // Initialize swap v3 - Swap::maybe_initialize_v3(netuid0); - let pending_em = AlphaCurrency::from(123434534); let pending_root = AlphaCurrency::from(12222222); let pending_owner_cut = AlphaCurrency::from(12345678); From 16822b44b2c6eb23ecb246a16add8ead6187f653 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 6 Nov 2025 22:42:04 -0500 Subject: [PATCH 27/32] wip --- pallets/subtensor/src/tests/coinbase.rs | 139 ++++++++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 726c7c608e..4c0f9877f8 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -3593,3 +3593,142 @@ fn test_coinbase_drain_pending_gets_counters_and_resets_them() { assert_eq!(PendingOwnerCut::::get(netuid0), AlphaCurrency::ZERO); }); } + +#[test] +fn test_coinbase_emit_to_subnets_with_no_root_sell() { + new_test_ext(1).execute_with(|| { + let zero = U96F32::saturating_from_num(0); + let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2)); + // Set owner cut to ~10% + SubnetOwnerCut::::set(u16::MAX / 10); + mock::setup_reserves( + netuid0, + TaoCurrency::from(1_000_000_000_000_000), + AlphaCurrency::from(1_000_000_000_000_000), + ); + // Initialize swap v3 + Swap::maybe_initialize_v3(netuid0); + + let tao_emission = U96F32::saturating_from_num(12345678); + let subnet_emissions = BTreeMap::from([(netuid0, tao_emission)]); + + // NO root sell + let root_sell_flag = false; + + let alpha_emission = U96F32::saturating_from_num( + SubtensorModule::get_block_emission_for_issuance( + SubtensorModule::get_alpha_issuance(netuid0).into(), + ) + .unwrap_or(0), + ); + let price: U96F32 = Swap::current_alpha_price(netuid0); + let (tao_in, alpha_in, alpha_out, excess_tao) = + SubtensorModule::get_subnet_terms(&subnet_emissions); + // Based on the price, we should have NO excess TAO + assert!(tao_emission / price <= alpha_emission); + + // ==== Run the emit to subnets ===== + SubtensorModule::emit_to_subnets(&[netuid0], &subnet_emissions, root_sell_flag); + + // Find the owner cut expected + let owner_cut: U96F32 = SubtensorModule::get_float_subnet_owner_cut(); + let owner_cut_expected: U96F32 = owner_cut.saturating_mul(alpha_emission); + log::info!("owner_cut_expected: {owner_cut_expected:?}"); + log::info!("alpha_emission: {alpha_emission:?}"); + log::info!("owner_cut: {owner_cut:?}"); + + // ===== Check that the pending emissions are set correctly ===== + // Owner cut is as expected + assert_abs_diff_eq!( + PendingOwnerCut::::get(netuid0).to_u64(), + owner_cut_expected.saturating_to_num::(), + epsilon = 200_u64 + ); + // NO root sell, so no root alpha divs + assert_eq!( + PendingRootAlphaDivs::::get(netuid0), + AlphaCurrency::ZERO + ); + // Should be alpha_emission minus the owner cut, + // we don't deduct any root alpha b/c NO root sell + assert_abs_diff_eq!( + PendingEmission::::get(netuid0).to_u64(), + alpha_emission + .saturating_sub(owner_cut_expected) + .saturating_to_num::(), + epsilon = 200_u64 + ); + }); +} + +#[test] +fn test_coinbase_emit_to_subnets_with_root_sell() { + new_test_ext(1).execute_with(|| { + let zero = U96F32::saturating_from_num(0); + let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2)); + // Set owner cut to ~10% + SubnetOwnerCut::::set(u16::MAX / 10); + mock::setup_reserves( + netuid0, + TaoCurrency::from(1_000_000_000_000_000), + AlphaCurrency::from(1_000_000_000_000_000), + ); + // Initialize swap v3 + Swap::maybe_initialize_v3(netuid0); + + let tao_emission = U96F32::saturating_from_num(12345678); + let subnet_emissions = BTreeMap::from([(netuid0, tao_emission)]); + + // NO root sell + let root_sell_flag = true; + + let alpha_emission: U96F32 = U96F32::saturating_from_num( + SubtensorModule::get_block_emission_for_issuance( + SubtensorModule::get_alpha_issuance(netuid0).into(), + ) + .unwrap_or(0), + ); + let price: U96F32 = Swap::current_alpha_price(netuid0); + let (tao_in, alpha_in, alpha_out, excess_tao) = + SubtensorModule::get_subnet_terms(&subnet_emissions); + // Based on the price, we should have NO excess TAO + assert!(tao_emission / price <= alpha_emission); + + // ==== Run the emit to subnets ===== + SubtensorModule::emit_to_subnets(&[netuid0], &subnet_emissions, root_sell_flag); + + // Find the owner cut expected + let owner_cut: U96F32 = SubtensorModule::get_float_subnet_owner_cut(); + let owner_cut_expected: U96F32 = owner_cut.saturating_mul(alpha_emission); + log::info!("owner_cut_expected: {owner_cut_expected:?}"); + log::info!("alpha_emission: {alpha_emission:?}"); + log::info!("owner_cut: {owner_cut:?}"); + + let expected_root_alpha_divs: AlphaCurrency = AlphaCurrency::from(12345678); + + let expected_emission: U96F32 = alpha_emission + .saturating_sub(owner_cut_expected) + .saturating_sub(U96F32::saturating_from_num(expected_root_alpha_divs.to_u64()).into()); + + // ===== Check that the pending emissions are set correctly ===== + // Owner cut is as expected + assert_abs_diff_eq!( + PendingOwnerCut::::get(netuid0).to_u64(), + owner_cut_expected.saturating_to_num::(), + epsilon = 200_u64 + ); + // YES root sell, so we have root alpha divs + assert_abs_diff_eq!( + PendingRootAlphaDivs::::get(netuid0).to_u64(), + expected_root_alpha_divs.to_u64(), + epsilon = 200_u64 + ); + // Should be alpha_emission minus the owner cut, + // minus the root alpha divs + assert_abs_diff_eq!( + PendingEmission::::get(netuid0).to_u64(), + expected_emission.saturating_to_num::(), + epsilon = 200_u64 + ); + }); +} From 8b8f763a700021d24e2b5223110832cbc7997c5c Mon Sep 17 00:00:00 2001 From: camfairchild Date: Sat, 8 Nov 2025 17:18:04 -0500 Subject: [PATCH 28/32] move reg disabled filter to sn to emit to --- .../subtensor/src/coinbase/run_coinbase.rs | 50 +++++++------------ .../src/coinbase/subnet_emissions.rs | 10 +++- 2 files changed, 25 insertions(+), 35 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 6b0d59ac0a..2b8ba6d14e 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -127,45 +127,29 @@ impl Pallet { // Only calculate for subnets that we are emitting to. for (&netuid_i, &tao_emission_i) in subnet_emissions.iter() { - let mut tao_in_i: U96F32; - let mut alpha_in_i: U96F32; - let alpha_out_i: U96F32; - // Only emit if the subnetwork allows registration. - if !Self::get_network_registration_allowed(netuid_i) - && !Self::get_network_pow_registration_allowed(netuid_i) - { - tao_in_i = asfloat!(0.0); - alpha_in_i = asfloat!(0.0); - alpha_out_i = asfloat!(0.0); - } else { - // Get alpha_emission total - let alpha_emission_i: U96F32 = asfloat!( - Self::get_block_emission_for_issuance( - Self::get_alpha_issuance(netuid_i).into() - ) + // Get alpha_emission this block. + let alpha_emission_i: U96F32 = asfloat!( + Self::get_block_emission_for_issuance(Self::get_alpha_issuance(netuid_i).into()) .unwrap_or(0) - ); - log::debug!("alpha_emission_i: {alpha_emission_i:?}"); - - // Get alpha_out. - alpha_out_i = alpha_emission_i; - - // Get subnet price. - let price_i: U96F32 = T::SwapInterface::current_alpha_price(netuid_i.into()); - log::debug!("price_i: {price_i:?}"); + ); + log::debug!("alpha_emission_i: {alpha_emission_i:?}"); - tao_in_i = tao_emission_i; - alpha_in_i = tao_emission_i.safe_div_or(price_i, U96F32::saturating_from_num(0.0)); + // Get subnet price. + let price_i: U96F32 = T::SwapInterface::current_alpha_price(netuid_i.into()); + log::debug!("price_i: {price_i:?}"); - if alpha_in_i > alpha_emission_i { - alpha_in_i = alpha_emission_i; - tao_in_i = alpha_in_i.saturating_mul(price_i); - } + let mut tao_in_i: U96F32 = tao_emission_i; + let alpha_out_i: U96F32 = alpha_emission_i; + let mut alpha_in_i: U96F32 = tao_emission_i.safe_div_or(price_i, U96F32::from_num(0.0)); - let excess_amount: U96F32 = tao_emission_i.saturating_sub(tao_in_i); - excess_tao.insert(netuid_i, excess_amount); + if alpha_in_i > alpha_emission_i { + alpha_in_i = alpha_emission_i; + tao_in_i = alpha_in_i.saturating_mul(price_i); } + let excess_amount: U96F32 = tao_emission_i.saturating_sub(tao_in_i); + excess_tao.insert(netuid_i, excess_amount); + // Insert values into maps tao_in.insert(netuid_i, tao_in_i); alpha_in.insert(netuid_i, alpha_in_i); diff --git a/pallets/subtensor/src/coinbase/subnet_emissions.rs b/pallets/subtensor/src/coinbase/subnet_emissions.rs index 3f06092084..1a5a8ec402 100644 --- a/pallets/subtensor/src/coinbase/subnet_emissions.rs +++ b/pallets/subtensor/src/coinbase/subnet_emissions.rs @@ -11,8 +11,14 @@ impl Pallet { // Filter out subnets with no first emission block number. subnets .iter() - .filter(|&netuid| *netuid != NetUid::ROOT) - .filter(|&netuid| FirstEmissionBlockNumber::::get(*netuid).is_some()) + .filter(|netuid| !netuid.is_root()) + .filter(|netuid| FirstEmissionBlockNumber::::get(*netuid).is_some()) + .filter(|netuid| SubtokenEnabled::::get(*netuid)) + .filter(|&netuid| { + // Only emit TAO if the subnetwork allows registration. + Self::get_network_registration_allowed(*netuid) + || Self::get_network_pow_registration_allowed(*netuid) + }) .copied() .collect() } From 5dfdf475361b320dd6a8f82ab88ad3992fe96153 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Tue, 11 Nov 2025 21:37:16 -0500 Subject: [PATCH 29/32] fix test for change from main --- pallets/subtensor/src/tests/coinbase.rs | 61 ++++++++++++------------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 2bf260be5c..f600cc4f2b 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -3326,47 +3326,42 @@ fn test_mining_emission_distribution_with_root_sell() { } #[test] -fn test_coinbase_subnet_terms_with_no_reg_get_no_emission() { +fn test_coinbase_subnets_with_no_reg_get_no_emission() { new_test_ext(1).execute_with(|| { let zero = U96F32::saturating_from_num(0); let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2)); let netuid1 = add_dynamic_network(&U256::from(3), &U256::from(4)); - let subnet_emissions = BTreeMap::from([ - (netuid0, U96F32::saturating_from_num(1)), - (netuid1, U96F32::saturating_from_num(1)), - ]); - - let (tao_in, alpha_in, alpha_out, _) = SubtensorModule::get_subnet_terms(&subnet_emissions); - assert_eq!(tao_in.len(), 2); - assert_eq!(alpha_in.len(), 2); - assert_eq!(alpha_out.len(), 2); - - assert!(tao_in[&netuid0] > zero); - assert!(alpha_in[&netuid0] > zero); - assert!(alpha_out[&netuid0] > zero); - - assert!(tao_in[&netuid1] > zero); - assert!(alpha_in[&netuid1] > zero); - assert!(alpha_out[&netuid1] > zero); - - // Disabled registration of both methods + // Setup initial state + SubtokenEnabled::::insert(netuid0, true); + SubtokenEnabled::::insert(netuid1, true); + FirstEmissionBlockNumber::::insert(netuid0, 0); + FirstEmissionBlockNumber::::insert(netuid1, 0); + // Explicitly allow registration for both subnets + NetworkRegistrationAllowed::::insert(netuid0, true); + NetworkRegistrationAllowed::::insert(netuid1, true); + NetworkPowRegistrationAllowed::::insert(netuid0, false); + NetworkPowRegistrationAllowed::::insert(netuid1, true); + + // Note that netuid0 has only one method allowed + // And, netuid1 has *both* methods allowed + // Both should be in the list. + let subnets_to_emit_to_0 = SubtensorModule::get_subnets_to_emit_to(&[netuid0, netuid1]); + // Check that both subnets are in the list + assert_eq!(subnets_to_emit_to_0.len(), 2); + assert!(subnets_to_emit_to_0.contains(&netuid0)); + assert!(subnets_to_emit_to_0.contains(&netuid1)); + + // Disabled registration of both methods on ONLY netuid0 NetworkRegistrationAllowed::::insert(netuid0, false); NetworkPowRegistrationAllowed::::insert(netuid0, false); - let (tao_in_2, alpha_in_2, alpha_out_2, _) = - SubtensorModule::get_subnet_terms(&subnet_emissions); - assert_eq!(tao_in_2.len(), 2); - assert_eq!(alpha_in_2.len(), 2); - assert_eq!(alpha_out_2.len(), 2); - - assert!(tao_in_2[&netuid0] == zero); - assert!(alpha_in_2[&netuid0] == zero); - assert!(alpha_out_2[&netuid0] == zero); - - assert!(tao_in_2[&netuid1] > zero); - assert!(alpha_in_2[&netuid1] > zero); - assert!(alpha_out_2[&netuid1] > zero); + // Check that netuid0 is not in the list + let subnets_to_emit_to_1 = SubtensorModule::get_subnets_to_emit_to(&[netuid0, netuid1]); + assert_eq!(subnets_to_emit_to_1.len(), 1); + assert!(!subnets_to_emit_to_1.contains(&netuid0)); + // Netuid1 still in the list + assert!(subnets_to_emit_to_1.contains(&netuid1)); }); } From 5cfed02e98be5f156c18d9b10e3b006523afbfd3 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Tue, 11 Nov 2025 21:37:33 -0500 Subject: [PATCH 30/32] remove line from merge resolution --- pallets/subtensor/src/coinbase/run_coinbase.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 0fd4974712..a16bda0b69 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -232,9 +232,6 @@ impl Pallet { } log::debug!("root_alpha: {root_alpha:?}"); - // Deduct root alpha from alpha_out. - alpha_out_i = alpha_out_i.saturating_sub(root_alpha); - // Get pending server alpha, which is the miner cut of the alpha out. // Currently miner cut is 50% of the alpha out. let pending_server_alpha = alpha_out_i.saturating_mul(asfloat!(0.5)); From 55e52c1868929207bf4d6eb84d290cf36384557a Mon Sep 17 00:00:00 2001 From: camfairchild Date: Tue, 11 Nov 2025 21:39:05 -0500 Subject: [PATCH 31/32] refactor emit_to_sns --- .../subtensor/src/coinbase/run_coinbase.rs | 25 ++++++++++--------- pallets/subtensor/src/tests/coinbase.rs | 2 +- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index a16bda0b69..5cb2ea45f8 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -221,25 +221,16 @@ impl Pallet { .saturating_mul(asfloat!(0.5)); // 50% to validators. log::debug!("root_alpha: {root_alpha:?}"); - if root_sell_flag { - // Only accumulate root alpha divs if root sell is allowed. - PendingRootAlphaDivs::::mutate(*netuid_i, |total| { - *total = total.saturating_add(tou64!(root_alpha).into()); - }); - } else { - // If we are not selling the root alpha, we should recycle it. - Self::recycle_subnet_alpha(*netuid_i, AlphaCurrency::from(tou64!(root_alpha))); - } - log::debug!("root_alpha: {root_alpha:?}"); - // Get pending server alpha, which is the miner cut of the alpha out. // Currently miner cut is 50% of the alpha out. let pending_server_alpha = alpha_out_i.saturating_mul(asfloat!(0.5)); + log::debug!("pending_server_alpha: {pending_server_alpha:?}"); // The total validator alpha is the remaining alpha out minus the server alpha. let total_validator_alpha = alpha_out_i.saturating_sub(pending_server_alpha); - + log::debug!("total_validator_alpha: {total_validator_alpha:?}"); // The alpha validators don't get the root alpha. let pending_validator_alpha = total_validator_alpha.saturating_sub(root_alpha); + log::debug!("pending_validator_alpha: {pending_validator_alpha:?}"); // Accumulate the server alpha emission. PendingServerEmission::::mutate(*netuid_i, |total| { @@ -249,6 +240,16 @@ impl Pallet { PendingValidatorEmission::::mutate(*netuid_i, |total| { *total = total.saturating_add(tou64!(pending_validator_alpha).into()); }); + + if root_sell_flag { + // Only accumulate root alpha divs if root sell is allowed. + PendingRootAlphaDivs::::mutate(*netuid_i, |total| { + *total = total.saturating_add(tou64!(root_alpha).into()); + }); + } else { + // If we are not selling the root alpha, we should recycle it. + Self::recycle_subnet_alpha(*netuid_i, AlphaCurrency::from(tou64!(root_alpha))); + } } } diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index f600cc4f2b..9388ab6070 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -3361,7 +3361,7 @@ fn test_coinbase_subnets_with_no_reg_get_no_emission() { assert_eq!(subnets_to_emit_to_1.len(), 1); assert!(!subnets_to_emit_to_1.contains(&netuid0)); // Netuid1 still in the list - assert!(subnets_to_emit_to_1.contains(&netuid1)); + assert!(subnets_to_emit_to_1.contains(&netuid1)); }); } From e0e8c9b58b3b0b8ebe7a502427cf8d44fe6bcebc Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 12 Nov 2025 12:15:30 -0500 Subject: [PATCH 32/32] bump spec --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 5e17dc6f7d..f6843c50ee 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: 345, + spec_version: 346, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1,