diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 006edcb2ef..f42eb76878 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -34,6 +34,15 @@ impl Pallet { // 2. Get subnets to emit to and emissions let subnet_emissions = Self::get_subnet_block_emissions(&subnets, block_emission); let subnets_to_emit_to: Vec = subnet_emissions.keys().copied().collect(); + // --- 2. Get sum of moving alpha 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() { + 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; + log::debug!("total_moving_prices: {total_moving_prices:?}"); // --- 3. Get subnet terms (tao_in, alpha_in, and alpha_out) // Computation is described in detail in the dtao whitepaper. @@ -41,6 +50,8 @@ impl Pallet { let mut alpha_in: BTreeMap = BTreeMap::new(); let mut alpha_out: BTreeMap = BTreeMap::new(); let mut is_subsidized: BTreeMap = BTreeMap::new(); + let mut subsidy_amount: BTreeMap = BTreeMap::new(); + // Only calculate for subnets that we are emitting to. for netuid_i in subnets_to_emit_to.iter() { // Get subnet price. @@ -52,6 +63,11 @@ impl Pallet { .copied() .unwrap_or(asfloat!(0)); log::debug!("default_tao_in_i: {default_tao_in_i:?}"); + + let default_alpha_in_i: U96F32 = + default_tao_in_i.safe_div_or(price_i, U96F32::saturating_from_num(0.0)); + log::debug!("default_alpha_in_i: {default_alpha_in_i:?}"); + // Get alpha_emission total let alpha_emission_i: U96F32 = asfloat!( Self::get_block_emission_for_issuance(Self::get_alpha_issuance(*netuid_i).into()) @@ -59,36 +75,28 @@ impl Pallet { ); log::debug!("alpha_emission_i: {alpha_emission_i:?}"); - // Get initial alpha_in let mut alpha_in_i: U96F32; let mut tao_in_i: U96F32; - let tao_in_ratio: U96F32 = default_tao_in_i.safe_div_or( - U96F32::saturating_from_num(block_emission), - U96F32::saturating_from_num(0.0), - ); - if price_i < tao_in_ratio { - tao_in_i = price_i.saturating_mul(U96F32::saturating_from_num(block_emission)); - alpha_in_i = block_emission; - let difference_tao: U96F32 = default_tao_in_i.saturating_sub(tao_in_i); - // Difference becomes buy. - let buy_swap_result = Self::swap_tao_for_alpha( - *netuid_i, - tou64!(difference_tao).into(), - T::SwapInterface::max_price(), - true, - ); - if let Ok(buy_swap_result_ok) = buy_swap_result { - let bought_alpha = AlphaCurrency::from(buy_swap_result_ok.amount_paid_out); - SubnetAlphaOut::::mutate(*netuid_i, |total| { - *total = total.saturating_sub(bought_alpha); - }); + if default_alpha_in_i > alpha_emission_i + || total_moving_prices < U96F32::saturating_from_num(1.0) + { + let min_alpha_emission = + default_alpha_in_i.min(alpha_emission_i).min(block_emission); + alpha_in_i = min_alpha_emission; + tao_in_i = alpha_in_i.saturating_mul(price_i); + + if total_moving_prices < U96F32::saturating_from_num(1.0) { + let difference_tao: U96F32 = default_tao_in_i.saturating_sub(tao_in_i); + is_subsidized.insert(*netuid_i, true); + subsidy_amount.insert(*netuid_i, difference_tao); } - is_subsidized.insert(*netuid_i, true); } else { + alpha_in_i = default_alpha_in_i; 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); + subsidy_amount.insert(*netuid_i, asfloat!(0.0)); } + log::debug!("tao_in_i: {tao_in_i:?}"); log::debug!("alpha_in_i: {alpha_in_i:?}"); // Get alpha_out. @@ -106,11 +114,38 @@ impl Pallet { 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:?}"); - // --- 4. Injection. + // --- 5. Subsidization + // When sum of moving prices is < 1.0, we subsidize by buying ALPHA with TAO. + if total_moving_prices < U96F32::saturating_from_num(1.0) { + for netuid_i in subnets_to_emit_to.iter() { + let subsidized: bool = *is_subsidized.get(netuid_i).unwrap_or(&false); + if !subsidized { + continue; + } + let subsidy_amount_i: U96F32 = + *subsidy_amount.get(netuid_i).unwrap_or(&asfloat!(0.0)); + + let buy_swap_result = Self::swap_tao_for_alpha( + *netuid_i, + tou64!(subsidy_amount_i).into(), + T::SwapInterface::max_price(), + true, + ); + if let Ok(buy_swap_result_ok) = buy_swap_result { + let bought_alpha = AlphaCurrency::from(buy_swap_result_ok.amount_paid_out); + SubnetAlphaOut::::mutate(*netuid_i, |total| { + *total = total.saturating_sub(bought_alpha); + }); + } + } + } + + // --- 5. Injection. // Actually perform the injection of alpha_in, alpha_out and tao_in into the subnet pool. // This operation changes the pool liquidity each block. for netuid_i in subnets_to_emit_to.iter() { @@ -128,24 +163,31 @@ impl Pallet { SubnetAlphaOut::::mutate(*netuid_i, |total| { *total = total.saturating_add(alpha_out_i); }); + // Inject TAO in. let tao_in_i: TaoCurrency = tou64!(*tao_in.get(netuid_i).unwrap_or(&asfloat!(0))).into(); + let subsidy_tao: TaoCurrency = + tou64!(*subsidy_amount.get(netuid_i).unwrap_or(&asfloat!(0))).into(); SubnetTaoInEmission::::insert(*netuid_i, TaoCurrency::from(tao_in_i)); + + // No need to add subsidy_tao here as it is captured from the swap result above. SubnetTAO::::mutate(*netuid_i, |total| { *total = total.saturating_add(tao_in_i.into()); }); TotalStake::::mutate(|total| { *total = total.saturating_add(tao_in_i.into()); }); + // Here we add subsidy tao as it is technically as issuance TotalIssuance::::mutate(|total| { *total = total.saturating_add(tao_in_i.into()); + *total = total.saturating_add(subsidy_tao.into()); }); // Adjust protocol liquidity based on new reserves T::SwapInterface::adjust_protocol_liquidity(*netuid_i, tao_in_i, alpha_in_i); } - // --- 5. Compute owner cuts and remove them from alpha_out remaining. + // --- 6. Compute owner cuts and remove them from alpha_out remaining. // Remove owner cuts here so that we can properly seperate root dividends in the next step. // Owner cuts are accumulated and then fed to the drain at the end of this func. let cut_percent: U96F32 = Self::get_float_subnet_owner_cut(); @@ -174,7 +216,7 @@ impl Pallet { let tao_weight: U96F32 = root_tao.saturating_mul(Self::get_tao_weight()); log::debug!("tao_weight: {tao_weight:?}"); - // --- 6. Seperate out root dividends in alpha and sell them into tao. + // --- 7. Seperate out root dividends in alpha and sell them into tao. // Then accumulate those dividends for later. for netuid_i in subnets_to_emit_to.iter() { // Get remaining alpha out. @@ -197,7 +239,6 @@ impl Pallet { // 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:?}"); - // Sell root emission through the pool (do not pay fees) let subsidized: bool = *is_subsidized.get(netuid_i).unwrap_or(&false); if !subsidized { let swap_result = Self::swap_alpha_for_tao( @@ -224,14 +265,14 @@ impl Pallet { }); } - // --- 7. Update moving prices after using them in the emission calculation. + // --- 8. Update moving prices after using them in the emission calculation. // Only update price EMA for subnets that we emit to. for netuid_i in subnets_to_emit_to.iter() { // Update moving prices after using them above. Self::update_moving_price(*netuid_i); } - // --- 8. Drain pending emission through the subnet based on tempo. + // --- 9. Drain pending emission through the subnet based on tempo. // Run the epoch for *all* subnets, even if we don't emit anything. for &netuid in subnets.iter() { // Reveal matured weights. diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 39a964a067..ab9381b0ee 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -153,7 +153,7 @@ fn test_coinbase_tao_issuance_different_prices() { new_test_ext(1).execute_with(|| { let netuid1 = NetUid::from(1); let netuid2 = NetUid::from(2); - let emission = 100_000_000; + let emission = 1_000_000_000; add_network(netuid1, 1, 0); add_network(netuid2, 1, 0); @@ -194,16 +194,15 @@ fn test_coinbase_tao_issuance_different_prices() { // Run the coinbase with the emission amount. SubtensorModule::run_coinbase(U96F32::from_num(emission)); - // Assert tao emission is split evenly. assert_abs_diff_eq!( SubnetTAO::::get(netuid1), - TaoCurrency::from(initial_tao + emission / 3), + TaoCurrency::from(initial_tao + emission / 10), epsilon = 1.into(), ); assert_abs_diff_eq!( SubnetTAO::::get(netuid2), - TaoCurrency::from(initial_tao + 2 * emission / 3), + TaoCurrency::from(initial_tao + 2 * emission / 10), epsilon = 1.into(), ); @@ -214,11 +213,7 @@ fn test_coinbase_tao_issuance_different_prices() { tao_issued, epsilon = 10.into() ); - assert_abs_diff_eq!( - TotalStake::::get(), - emission.into(), - epsilon = 10.into() - ); + assert_abs_diff_eq!(TotalStake::::get(), tao_issued, epsilon = 10.into()); }); } @@ -468,8 +463,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( @@ -503,7 +498,7 @@ fn test_coinbase_alpha_issuance_with_cap_trigger_and_block_emission() { let price_2_after = ::SwapInterface::current_alpha_price(netuid2); // AlphaIn gets decreased beacuse of a buy - assert!(u64::from(SubnetAlphaIn::::get(netuid1)) < initial_alpha); + assert!(u64::from(SubnetAlphaIn::::get(netuid1)) < initial_alpha); // HERE assert_eq!( u64::from(SubnetAlphaOut::::get(netuid2)), 21_000_000_000_000_000_u64