Skip to content

Commit 6a76ecc

Browse files
camfairchild0xcactishamil-gadelshin
authored
hotfix: epoch w/subsidy fix (#2187)
* impl tests benchmarks * bump spec * remove duplicate remove * use alpha out here * Hotfix/vune/epoch sub fix (#2186) * fix: injection logic * fix: fmt' * emit full alpha if subsidized * fix coinbase test * fix tests * Revert "Hotfix/vune/epoch sub fix (#2186)" This reverts commit ccc6bba. * add total issuance bump * no alpha out thing * add tests for emissions w/ subs * bump spec * Fix clippy warnings * Hotfix/vune/epoch sub fix (#2186) * fix: injection logic * fix: fmt' * rename drain_pending to distr em * remove arg and total internally * test pending em * make sure we check subsidize during those tests * fix root claim tests * helper and clippy * bump spec * rename subsidy to root flag * only pull these values if root selling * rename subsidy checks to the root_sell flag * rename subsidy to excess_tao * use recycle alpha helper * Hotfix/vune/subnet-dereg-burn-use-issuance (#2190) * use alpha issuance for owner estimate * fix tests * use price for estimate only * fix tests for using just price * docs: comment fix * remove log out --------- Co-authored-by: 0xcacti <97139981+0xcacti@users.noreply.github.com> Co-authored-by: Shamil Gadelshin <shamilgadelshin@gmail.com>
1 parent 1f520ed commit 6a76ecc

File tree

10 files changed

+650
-208
lines changed

10 files changed

+650
-208
lines changed

pallets/subtensor/src/benchmarks.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1615,7 +1615,7 @@ mod pallet_benchmarks {
16151615
);
16161616

16171617
let pending_root_alpha = 10_000_000u64;
1618-
Subtensor::<T>::drain_pending_emission(
1618+
Subtensor::<T>::distribute_emission(
16191619
netuid,
16201620
AlphaCurrency::ZERO,
16211621
pending_root_alpha.into(),

pallets/subtensor/src/coinbase/root.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,6 @@ impl<T: Config> Pallet<T> {
348348
RAORecycledForRegistration::<T>::remove(netuid);
349349
MaxRegistrationsPerBlock::<T>::remove(netuid);
350350
WeightsVersionKey::<T>::remove(netuid);
351-
PendingRootAlphaDivs::<T>::remove(netuid);
352351

353352
// --- 17. Subtoken / feature flags.
354353
LiquidAlphaOn::<T>::remove(netuid);

pallets/subtensor/src/coinbase/run_coinbase.rs

Lines changed: 88 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,15 @@ impl<T: Config> Pallet<T> {
3434
// 2. Get subnets to emit to and emissions
3535
let subnet_emissions = Self::get_subnet_block_emissions(&subnets, block_emission);
3636
let subnets_to_emit_to: Vec<NetUid> = subnet_emissions.keys().copied().collect();
37+
let root_sell_flag = Self::get_network_root_sell_flag(&subnets_to_emit_to);
3738

3839
// --- 3. Get subnet terms (tao_in, alpha_in, and alpha_out)
3940
// Computation is described in detail in the dtao whitepaper.
4041
let mut tao_in: BTreeMap<NetUid, U96F32> = BTreeMap::new();
4142
let mut alpha_in: BTreeMap<NetUid, U96F32> = BTreeMap::new();
4243
let mut alpha_out: BTreeMap<NetUid, U96F32> = BTreeMap::new();
43-
let mut is_subsidized: BTreeMap<NetUid, bool> = BTreeMap::new();
44+
let mut excess_tao: BTreeMap<NetUid, U96F32> = BTreeMap::new();
45+
4446
// Only calculate for subnets that we are emitting to.
4547
for netuid_i in subnets_to_emit_to.iter() {
4648
// Get subnet price.
@@ -52,6 +54,9 @@ impl<T: Config> Pallet<T> {
5254
.copied()
5355
.unwrap_or(asfloat!(0));
5456
log::debug!("default_tao_in_i: {default_tao_in_i:?}");
57+
let default_alpha_in_i: U96F32 =
58+
default_tao_in_i.safe_div_or(price_i, U96F32::saturating_from_num(0.0));
59+
log::debug!("default_alpha_in_i: {default_alpha_in_i:?}");
5560
// Get alpha_emission total
5661
let alpha_emission_i: U96F32 = asfloat!(
5762
Self::get_block_emission_for_issuance(Self::get_alpha_issuance(*netuid_i).into())
@@ -62,32 +67,16 @@ impl<T: Config> Pallet<T> {
6267
// Get initial alpha_in
6368
let mut alpha_in_i: U96F32;
6469
let mut tao_in_i: U96F32;
65-
let tao_in_ratio: U96F32 = default_tao_in_i.safe_div_or(
66-
U96F32::saturating_from_num(block_emission),
67-
U96F32::saturating_from_num(0.0),
68-
);
69-
if price_i < tao_in_ratio {
70-
tao_in_i = price_i.saturating_mul(U96F32::saturating_from_num(block_emission));
71-
alpha_in_i = block_emission;
70+
71+
if default_alpha_in_i > alpha_emission_i {
72+
alpha_in_i = alpha_emission_i;
73+
tao_in_i = alpha_in_i.saturating_mul(price_i);
7274
let difference_tao: U96F32 = default_tao_in_i.saturating_sub(tao_in_i);
73-
// Difference becomes buy.
74-
let buy_swap_result = Self::swap_tao_for_alpha(
75-
*netuid_i,
76-
tou64!(difference_tao).into(),
77-
T::SwapInterface::max_price(),
78-
true,
79-
);
80-
if let Ok(buy_swap_result_ok) = buy_swap_result {
81-
let bought_alpha = AlphaCurrency::from(buy_swap_result_ok.amount_paid_out);
82-
SubnetAlphaOut::<T>::mutate(*netuid_i, |total| {
83-
*total = total.saturating_sub(bought_alpha);
84-
});
85-
}
86-
is_subsidized.insert(*netuid_i, true);
75+
excess_tao.insert(*netuid_i, difference_tao);
8776
} else {
8877
tao_in_i = default_tao_in_i;
89-
alpha_in_i = tao_in_i.safe_div_or(price_i, alpha_emission_i);
90-
is_subsidized.insert(*netuid_i, false);
78+
alpha_in_i = default_alpha_in_i;
79+
excess_tao.insert(*netuid_i, U96F32::from_num(0.0));
9180
}
9281
log::debug!("alpha_in_i: {alpha_in_i:?}");
9382

@@ -109,10 +98,34 @@ impl<T: Config> Pallet<T> {
10998
log::debug!("tao_in: {tao_in:?}");
11099
log::debug!("alpha_in: {alpha_in:?}");
111100
log::debug!("alpha_out: {alpha_out:?}");
101+
log::debug!("excess_tao: {excess_tao:?}");
102+
log::debug!("root_sell_flag: {root_sell_flag:?}");
103+
104+
// --- 4. Inject and buy Alpha with any excess TAO.
105+
for netuid_i in subnets_to_emit_to.iter() {
106+
let tao_in_i: TaoCurrency =
107+
tou64!(*tao_in.get(netuid_i).unwrap_or(&asfloat!(0))).into();
108+
let alpha_in_i: AlphaCurrency =
109+
AlphaCurrency::from(tou64!(*alpha_in.get(netuid_i).unwrap_or(&asfloat!(0))));
110+
let difference_tao: U96F32 = *excess_tao.get(netuid_i).unwrap_or(&asfloat!(0));
111+
112+
T::SwapInterface::adjust_protocol_liquidity(*netuid_i, tao_in_i, alpha_in_i);
113+
114+
if difference_tao > asfloat!(0) {
115+
let buy_swap_result = Self::swap_tao_for_alpha(
116+
*netuid_i,
117+
tou64!(difference_tao).into(),
118+
T::SwapInterface::max_price(),
119+
true,
120+
);
121+
if let Ok(buy_swap_result_ok) = buy_swap_result {
122+
let bought_alpha = AlphaCurrency::from(buy_swap_result_ok.amount_paid_out);
123+
Self::recycle_subnet_alpha(*netuid_i, bought_alpha);
124+
}
125+
}
126+
}
112127

113-
// --- 4. Injection.
114-
// Actually perform the injection of alpha_in, alpha_out and tao_in into the subnet pool.
115-
// This operation changes the pool liquidity each block.
128+
// --- 5. Update counters
116129
for netuid_i in subnets_to_emit_to.iter() {
117130
// Inject Alpha in.
118131
let alpha_in_i =
@@ -138,14 +151,15 @@ impl<T: Config> Pallet<T> {
138151
TotalStake::<T>::mutate(|total| {
139152
*total = total.saturating_add(tao_in_i.into());
140153
});
154+
155+
let difference_tao: U96F32 = *excess_tao.get(netuid_i).unwrap_or(&asfloat!(0));
141156
TotalIssuance::<T>::mutate(|total| {
142157
*total = total.saturating_add(tao_in_i.into());
158+
*total = total.saturating_add(tou64!(difference_tao).into());
143159
});
144-
// Adjust protocol liquidity based on new reserves
145-
T::SwapInterface::adjust_protocol_liquidity(*netuid_i, tao_in_i, alpha_in_i);
146160
}
147161

148-
// --- 5. Compute owner cuts and remove them from alpha_out remaining.
162+
// --- 6. Compute owner cuts and remove them from alpha_out remaining.
149163
// Remove owner cuts here so that we can properly seperate root dividends in the next step.
150164
// Owner cuts are accumulated and then fed to the drain at the end of this func.
151165
let cut_percent: U96F32 = Self::get_float_subnet_owner_cut();
@@ -174,51 +188,55 @@ impl<T: Config> Pallet<T> {
174188
let tao_weight: U96F32 = root_tao.saturating_mul(Self::get_tao_weight());
175189
log::debug!("tao_weight: {tao_weight:?}");
176190

177-
// --- 6. Seperate out root dividends in alpha and keep them.
191+
// --- 7. Seperate out root dividends in alpha and keep them.
178192
// Then accumulate those dividends for later.
179193
for netuid_i in subnets_to_emit_to.iter() {
180194
// Get remaining alpha out.
181195
let alpha_out_i: U96F32 = *alpha_out.get(netuid_i).unwrap_or(&asfloat!(0.0));
182196
log::debug!("alpha_out_i: {alpha_out_i:?}");
183-
// Get total ALPHA on subnet.
184-
let alpha_issuance: U96F32 = asfloat!(Self::get_alpha_issuance(*netuid_i));
185-
log::debug!("alpha_issuance: {alpha_issuance:?}");
186-
// Get root proportional dividends.
187-
let root_proportion: U96F32 = tao_weight
188-
.checked_div(tao_weight.saturating_add(alpha_issuance))
189-
.unwrap_or(asfloat!(0.0));
190-
log::debug!("root_proportion: {root_proportion:?}");
197+
191198
// Get root proportion of alpha_out dividends.
192-
let root_alpha: U96F32 = root_proportion
193-
.saturating_mul(alpha_out_i) // Total alpha emission per block remaining.
194-
.saturating_mul(asfloat!(0.5)); // 50% to validators.
199+
let mut root_alpha: U96F32 = asfloat!(0.0);
200+
if root_sell_flag {
201+
// Get ALPHA issuance.
202+
let alpha_issuance: U96F32 = asfloat!(Self::get_alpha_issuance(*netuid_i));
203+
log::debug!("alpha_issuance: {alpha_issuance:?}");
204+
205+
// Get root proportional dividends.
206+
let root_proportion: U96F32 = tao_weight
207+
.checked_div(tao_weight.saturating_add(alpha_issuance))
208+
.unwrap_or(asfloat!(0.0));
209+
log::debug!("root_proportion: {root_proportion:?}");
210+
211+
// Get root alpha from root prop.
212+
root_alpha = root_proportion
213+
.saturating_mul(alpha_out_i) // Total alpha emission per block remaining.
214+
.saturating_mul(asfloat!(0.5)); // 50% to validators.
215+
PendingRootAlphaDivs::<T>::mutate(*netuid_i, |total| {
216+
*total = total.saturating_add(tou64!(root_alpha).into());
217+
});
218+
}
195219
// Remove root alpha from alpha_out.
196220
log::debug!("root_alpha: {root_alpha:?}");
221+
197222
// Get pending alpha as original alpha_out - root_alpha.
198223
let pending_alpha: U96F32 = alpha_out_i.saturating_sub(root_alpha);
199224
log::debug!("pending_alpha: {pending_alpha:?}");
200225

201-
let subsidized: bool = *is_subsidized.get(netuid_i).unwrap_or(&false);
202-
if !subsidized {
203-
PendingRootAlphaDivs::<T>::mutate(*netuid_i, |total| {
204-
*total = total.saturating_add(tou64!(root_alpha).into());
205-
});
206-
}
207-
208226
// Accumulate alpha emission in pending.
209227
PendingEmission::<T>::mutate(*netuid_i, |total| {
210228
*total = total.saturating_add(tou64!(pending_alpha).into());
211229
});
212230
}
213231

214-
// --- 7. Update moving prices after using them in the emission calculation.
232+
// --- 8. Update moving prices after using them in the emission calculation.
215233
// Only update price EMA for subnets that we emit to.
216234
for netuid_i in subnets_to_emit_to.iter() {
217235
// Update moving prices after using them above.
218236
Self::update_moving_price(*netuid_i);
219237
}
220238

221-
// --- 8. Drain pending emission through the subnet based on tempo.
239+
// --- 9. Drain pending emission through the subnet based on tempo.
222240
// Run the epoch for *all* subnets, even if we don't emit anything.
223241
for &netuid in subnets.iter() {
224242
// Reveal matured weights.
@@ -245,15 +263,26 @@ impl<T: Config> Pallet<T> {
245263
let owner_cut = PendingOwnerCut::<T>::get(netuid);
246264
PendingOwnerCut::<T>::insert(netuid, AlphaCurrency::ZERO);
247265

248-
// Drain pending root alpha divs, alpha emission, and owner cut.
249-
Self::drain_pending_emission(netuid, pending_alpha, pending_root_alpha, owner_cut);
266+
// Distribute the emission.
267+
Self::distribute_emission(netuid, pending_alpha, pending_root_alpha, owner_cut);
250268
} else {
251269
// Increment
252270
BlocksSinceLastStep::<T>::mutate(netuid, |total| *total = total.saturating_add(1));
253271
}
254272
}
255273
}
256274

275+
pub fn get_network_root_sell_flag(subnets_to_emit_to: &[NetUid]) -> bool {
276+
let total_ema_price: U96F32 = subnets_to_emit_to
277+
.iter()
278+
.map(|netuid| Self::get_moving_alpha_price(*netuid))
279+
.sum();
280+
281+
// If the total EMA price is less than or equal to 1
282+
// then we WILL NOT root sell.
283+
total_ema_price > U96F32::saturating_from_num(1)
284+
}
285+
257286
pub fn calculate_dividends_and_incentives(
258287
netuid: NetUid,
259288
hotkey_emission: Vec<(T::AccountId, AlphaCurrency, AlphaCurrency)>,
@@ -482,6 +511,7 @@ impl<T: Config> Pallet<T> {
482511
let destination = maybe_dest.clone().unwrap_or(hotkey.clone());
483512

484513
if let Some(dest) = maybe_dest {
514+
log::debug!("incentives: auto staking {incentive:?} to {dest:?}");
485515
Self::deposit_event(Event::<T>::AutoStakeAdded {
486516
netuid,
487517
destination: dest,
@@ -490,6 +520,7 @@ impl<T: Config> Pallet<T> {
490520
incentive,
491521
});
492522
}
523+
493524
Self::increase_stake_for_hotkey_and_coldkey_on_subnet(
494525
&destination,
495526
&owner,
@@ -600,7 +631,7 @@ impl<T: Config> Pallet<T> {
600631
(incentives, (alpha_dividends, root_alpha_dividends))
601632
}
602633

603-
pub fn drain_pending_emission(
634+
pub fn distribute_emission(
604635
netuid: NetUid,
605636
pending_alpha: AlphaCurrency,
606637
pending_root_alpha: AlphaCurrency,
@@ -611,10 +642,11 @@ impl<T: Config> Pallet<T> {
611642
);
612643

613644
let tao_weight = Self::get_tao_weight();
645+
let total_alpha = pending_alpha.saturating_add(pending_root_alpha);
614646

615647
// Run the epoch.
616648
let hotkey_emission: Vec<(T::AccountId, AlphaCurrency, AlphaCurrency)> =
617-
Self::epoch_with_mechanisms(netuid, pending_alpha.saturating_add(pending_root_alpha));
649+
Self::epoch_with_mechanisms(netuid, total_alpha);
618650
log::debug!("hotkey_emission: {hotkey_emission:?}");
619651

620652
// Compute the pending validator alpha.
@@ -630,8 +662,7 @@ impl<T: Config> Pallet<T> {
630662
log::debug!("incentive_sum: {incentive_sum:?}");
631663

632664
let pending_validator_alpha = if !incentive_sum.is_zero() {
633-
pending_alpha
634-
.saturating_add(pending_root_alpha)
665+
total_alpha
635666
.saturating_div(2.into())
636667
.saturating_sub(pending_root_alpha)
637668
} else {

pallets/subtensor/src/coinbase/subnet_emissions.rs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,26 @@
11
use super::*;
2-
use crate::alloc::borrow::ToOwned;
32
use alloc::collections::BTreeMap;
43
use safe_math::FixedExt;
54
use substrate_fixed::transcendental::{exp, ln};
65
use substrate_fixed::types::{I32F32, I64F64, U64F64, U96F32};
76
use subtensor_swap_interface::SwapHandler;
87

98
impl<T: Config> Pallet<T> {
10-
pub fn get_subnet_block_emissions(
11-
subnets: &[NetUid],
12-
block_emission: U96F32,
13-
) -> BTreeMap<NetUid, U96F32> {
9+
pub fn get_subnets_to_emit_to(subnets: &[NetUid]) -> Vec<NetUid> {
1410
// Filter out subnets with no first emission block number.
15-
let subnets_to_emit_to: Vec<NetUid> = subnets
16-
.to_owned()
17-
.clone()
18-
.into_iter()
11+
subnets
12+
.iter()
1913
.filter(|netuid| FirstEmissionBlockNumber::<T>::get(*netuid).is_some())
20-
.collect();
21-
log::debug!("Subnets to emit to: {subnets_to_emit_to:?}");
14+
.copied()
15+
.collect()
16+
}
2217

18+
pub fn get_subnet_block_emissions(
19+
subnets_to_emit_to: &[NetUid],
20+
block_emission: U96F32,
21+
) -> BTreeMap<NetUid, U96F32> {
2322
// Get subnet TAO emissions.
24-
let shares = Self::get_shares(&subnets_to_emit_to);
23+
let shares = Self::get_shares(subnets_to_emit_to);
2524
log::debug!("Subnet emission shares = {shares:?}");
2625

2726
shares

pallets/subtensor/src/staking/remove_stake.rs

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -447,19 +447,13 @@ impl<T: Config> Pallet<T> {
447447
let should_refund_owner: bool = reg_at < start_block;
448448

449449
// 3) Compute owner's received emission in TAO at current price (ONLY if we may refund).
450-
// Emission::<T> is Vec<AlphaCurrency>. We:
451-
// - sum emitted α,
450+
// We:
451+
// - get the current alpha issuance,
452452
// - apply owner fraction to get owner α,
453453
// - price that α using a *simulated* AMM swap.
454454
let mut owner_emission_tao = TaoCurrency::ZERO;
455455
if should_refund_owner && !lock_cost.is_zero() {
456-
let total_emitted_alpha_u128: u128 =
457-
Emission::<T>::get(netuid)
458-
.into_iter()
459-
.fold(0u128, |acc, e_alpha| {
460-
let e_u64: u64 = Into::<u64>::into(e_alpha);
461-
acc.saturating_add(e_u64 as u128)
462-
});
456+
let total_emitted_alpha_u128: u128 = Self::get_alpha_issuance(netuid).to_u64() as u128;
463457

464458
if total_emitted_alpha_u128 > 0 {
465459
let owner_fraction: U96F32 = Self::get_float_subnet_owner_cut();
@@ -469,22 +463,12 @@ impl<T: Config> Pallet<T> {
469463
.saturating_to_num::<u64>();
470464

471465
owner_emission_tao = if owner_alpha_u64 > 0 {
472-
let order = GetTaoForAlpha::with_amount(owner_alpha_u64);
473-
match T::SwapInterface::sim_swap(netuid.into(), order) {
474-
Ok(sim) => TaoCurrency::from(sim.amount_paid_out),
475-
Err(e) => {
476-
log::debug!(
477-
"destroy_alpha_in_out_stakes: sim_swap owner α→τ failed (netuid={netuid:?}, alpha={owner_alpha_u64}, err={e:?}); falling back to price multiply.",
478-
);
479-
let cur_price: U96F32 =
480-
T::SwapInterface::current_alpha_price(netuid.into());
481-
let val_u64 = U96F32::from_num(owner_alpha_u64)
482-
.saturating_mul(cur_price)
483-
.floor()
484-
.saturating_to_num::<u64>();
485-
TaoCurrency::from(val_u64)
486-
}
487-
}
466+
let cur_price: U96F32 = T::SwapInterface::current_alpha_price(netuid.into());
467+
let val_u64 = U96F32::from_num(owner_alpha_u64)
468+
.saturating_mul(cur_price)
469+
.floor()
470+
.saturating_to_num::<u64>();
471+
TaoCurrency::from(val_u64)
488472
} else {
489473
TaoCurrency::ZERO
490474
};

pallets/subtensor/src/tests/children.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2827,7 +2827,7 @@ fn test_set_weights_no_parent() {
28272827
});
28282828
}
28292829

2830-
/// Test that drain_pending_emission sends childkey take fully to the nominators if childkey
2830+
/// Test that distribute_emission sends childkey take fully to the nominators if childkey
28312831
/// doesn't have its own stake, independently of parent hotkey take.
28322832
/// cargo test --package pallet-subtensor --lib -- tests::children::test_childkey_take_drain --exact --show-output
28332833
#[allow(clippy::assertions_on_constants)]

0 commit comments

Comments
 (0)