diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 9d6d44de8..d296aa98d 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -39,22 +39,24 @@ impl Pallet { log::debug!("Subnets to emit to: {subnets_to_emit_to:?}"); // --- 2. Get sum of tao reserves ( in a later version we will switch to prices. ) - let mut acc_total_moving_prices = U96F32::saturating_from_num(0.0); // Only get price EMA for subnets that we emit to. - for netuid_i in subnets_to_emit_to.iter() { - // Get and update the moving price of each subnet adding the total together. - acc_total_moving_prices = - acc_total_moving_prices.saturating_add(Self::get_moving_alpha_price(*netuid_i)); - } - let total_moving_prices = acc_total_moving_prices; + let total_moving_prices = subnets_to_emit_to + .iter() + .map(|netuid| Self::get_moving_alpha_price(*netuid)) + .fold(U96F32::saturating_from_num(0.0), |acc, ema| { + acc.saturating_add(ema) + }); log::debug!("total_moving_prices: {total_moving_prices:?}"); + let subsidy_mode = total_moving_prices < U96F32::saturating_from_num(1.0); + log::debug!("subsidy_mode: {subsidy_mode:?}"); + // --- 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 tao_issued: BTreeMap = BTreeMap::new(); let mut alpha_in: BTreeMap = BTreeMap::new(); let mut alpha_out: BTreeMap = BTreeMap::new(); - let mut is_subsidized: BTreeMap = BTreeMap::new(); // Only calculate for subnets that we are emitting to. for netuid_i in subnets_to_emit_to.iter() { // Get subnet price. @@ -79,11 +81,7 @@ impl Pallet { // Get initial alpha_in let mut alpha_in_i: U96F32; let mut tao_in_i: U96F32; - let tao_in_ratio: U96F32 = default_tao_in_i.safe_div_or( - U96F32::saturating_from_num(block_emission), - U96F32::saturating_from_num(0.0), - ); - if price_i < tao_in_ratio { + if subsidy_mode { tao_in_i = price_i.saturating_mul(U96F32::saturating_from_num(block_emission)); alpha_in_i = block_emission; let difference_tao: U96F32 = default_tao_in_i.saturating_sub(tao_in_i); @@ -100,11 +98,9 @@ impl Pallet { *total = total.saturating_sub(bought_alpha); }); } - is_subsidized.insert(*netuid_i, true); } else { tao_in_i = default_tao_in_i; alpha_in_i = tao_in_i.safe_div_or(price_i, alpha_emission_i); - is_subsidized.insert(*netuid_i, false); } log::debug!("alpha_in_i: {alpha_in_i:?}"); @@ -120,6 +116,7 @@ impl Pallet { } // Insert values into maps tao_in.insert(*netuid_i, tao_in_i); + tao_issued.insert(*netuid_i, default_tao_in_i); alpha_in.insert(*netuid_i, alpha_in_i); alpha_out.insert(*netuid_i, alpha_out_i); } @@ -155,10 +152,13 @@ impl Pallet { TotalStake::::mutate(|total| { *total = total.saturating_add(tao_in_i.into()); }); + // TAO issued + let tao_issued_i: TaoCurrency = + tou64!(*tao_issued.get(netuid_i).unwrap_or(&asfloat!(0))).into(); TotalIssuance::::mutate(|total| { - *total = total.saturating_add(tao_in_i.into()); + *total = total.saturating_add(tao_issued_i.into()); }); - // Adjust protocol liquidity based on new reserves + // Adjust protocol liquidity based on added reserves T::SwapInterface::adjust_protocol_liquidity(*netuid_i, tao_in_i, alpha_in_i); } @@ -215,8 +215,7 @@ impl Pallet { let pending_alpha: U96F32 = alpha_out_i.saturating_sub(root_alpha); log::debug!("pending_alpha: {pending_alpha:?}"); // Sell root emission through the pool (do not pay fees) - let subsidized: bool = *is_subsidized.get(netuid_i).unwrap_or(&false); - if !subsidized { + if !subsidy_mode { let swap_result = Self::swap_alpha_for_tao( *netuid_i, tou64!(root_alpha).into(), diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 39a964a06..41d150463 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -207,8 +207,83 @@ fn test_coinbase_tao_issuance_different_prices() { epsilon = 1.into(), ); - // Prices are low => we limit tao issued (buy alpha with it) - let tao_issued = TaoCurrency::from(((0.1 + 0.2) * emission as f64) as u64); + // EMA Prices are high => full emission + let tao_issued = TaoCurrency::from(emission); + assert_abs_diff_eq!( + TotalIssuance::::get(), + tao_issued, + epsilon = 10.into() + ); + assert_abs_diff_eq!( + TotalStake::::get(), + emission.into(), + epsilon = 10.into() + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::coinbase::test_coinbase_subsidies --exact --show-output --nocapture +#[test] +fn test_coinbase_subsidies() { + new_test_ext(1).execute_with(|| { + let netuid1 = NetUid::from(1); + let netuid2 = NetUid::from(2); + let emission = 100_000_000; + add_network(netuid1, 1, 0); + add_network(netuid2, 1, 0); + + // Setup prices 0.1 and 0.2 + let initial_tao: u64 = 100_000_u64; + let initial_alpha1: u64 = initial_tao * 10; + let initial_alpha2: u64 = initial_tao * 5; + mock::setup_reserves(netuid1, initial_tao.into(), initial_alpha1.into()); + mock::setup_reserves(netuid2, initial_tao.into(), initial_alpha2.into()); + + // Force the swap to initialize + SubtensorModule::swap_tao_for_alpha( + netuid1, + TaoCurrency::ZERO, + 1_000_000_000_000.into(), + false, + ) + .unwrap(); + SubtensorModule::swap_tao_for_alpha( + netuid2, + TaoCurrency::ZERO, + 1_000_000_000_000.into(), + false, + ) + .unwrap(); + + // Make subnets dynamic. + SubnetMechanism::::insert(netuid1, 1); + SubnetMechanism::::insert(netuid2, 1); + + // Set subnet EMA prices (sum < 1) + SubnetMovingPrice::::insert(netuid1, I96F32::from_num(0.1)); + SubnetMovingPrice::::insert(netuid2, I96F32::from_num(0.2)); + + // Assert initial TAO reserves. + assert_eq!(SubnetTAO::::get(netuid1), initial_tao.into()); + assert_eq!(SubnetTAO::::get(netuid2), initial_tao.into()); + + // Run the coinbase with the emission amount. + SubtensorModule::run_coinbase(U96F32::from_num(emission)); + + // Assert tao emission is split evenly and SubnetTAO additions sum to full emission + assert_abs_diff_eq!( + SubnetTAO::::get(netuid1), + TaoCurrency::from(initial_tao + emission / 3), + epsilon = 1.into(), + ); + assert_abs_diff_eq!( + SubnetTAO::::get(netuid2), + TaoCurrency::from(initial_tao + 2 * emission / 3), + epsilon = 1.into(), + ); + + // Prices are low => we buy alpha with TAO issued, but full emission is issued + let tao_issued = TaoCurrency::from(emission); assert_abs_diff_eq!( TotalIssuance::::get(), tao_issued, @@ -430,8 +505,8 @@ fn test_coinbase_alpha_issuance_with_cap_trigger() { SubnetTAO::::insert(netuid2, TaoCurrency::from(initial)); SubnetAlphaIn::::insert(netuid2, AlphaCurrency::from(initial_alpha)); // Make price extremely low. // Set subnet prices. - SubnetMovingPrice::::insert(netuid1, I96F32::from_num(1)); - SubnetMovingPrice::::insert(netuid2, I96F32::from_num(2)); + SubnetMovingPrice::::insert(netuid1, I96F32::from_num(0.1)); + SubnetMovingPrice::::insert(netuid2, I96F32::from_num(0.2)); // Run coinbase SubtensorModule::run_coinbase(U96F32::from_num(emission)); // tao_in = 333_333 @@ -468,8 +543,8 @@ fn test_coinbase_alpha_issuance_with_cap_trigger_and_block_emission() { // Enable emission FirstEmissionBlockNumber::::insert(netuid1, 0); FirstEmissionBlockNumber::::insert(netuid2, 0); - SubnetMovingPrice::::insert(netuid1, I96F32::from_num(1)); - SubnetMovingPrice::::insert(netuid2, I96F32::from_num(2)); + SubnetMovingPrice::::insert(netuid1, I96F32::from_num(0.1)); + SubnetMovingPrice::::insert(netuid2, I96F32::from_num(0.2)); // Force the swap to initialize SubtensorModule::swap_tao_for_alpha(