Skip to content

Commit 3d6b0a6

Browse files
authored
Merge pull request #1892 from opentensor/fix/v3_emission_devnet_ready
Fix v3 emissions on low prices
2 parents 86b66e6 + c329448 commit 3d6b0a6

File tree

7 files changed

+153
-78
lines changed

7 files changed

+153
-78
lines changed

pallets/subtensor/src/coinbase/run_coinbase.rs

Lines changed: 62 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ impl<T: Config> Pallet<T> {
5353
let mut tao_in: BTreeMap<NetUid, U96F32> = BTreeMap::new();
5454
let mut alpha_in: BTreeMap<NetUid, U96F32> = BTreeMap::new();
5555
let mut alpha_out: BTreeMap<NetUid, U96F32> = BTreeMap::new();
56+
let mut is_subsidized: BTreeMap<NetUid, bool> = BTreeMap::new();
5657
// Only calculate for subnets that we are emitting to.
5758
for netuid_i in subnets_to_emit_to.iter() {
5859
// Get subnet price.
@@ -62,23 +63,50 @@ impl<T: Config> Pallet<T> {
6263
let moving_price_i: U96F32 = Self::get_moving_alpha_price(*netuid_i);
6364
log::debug!("moving_price_i: {:?}", moving_price_i);
6465
// Emission is price over total.
65-
let mut tao_in_i: U96F32 = block_emission
66+
let default_tao_in_i: U96F32 = block_emission
6667
.saturating_mul(moving_price_i)
6768
.checked_div(total_moving_prices)
6869
.unwrap_or(asfloat!(0.0));
69-
log::debug!("tao_in_i: {:?}", tao_in_i);
70+
log::debug!("default_tao_in_i: {:?}", default_tao_in_i);
7071
// Get alpha_emission total
7172
let alpha_emission_i: U96F32 = asfloat!(
7273
Self::get_block_emission_for_issuance(Self::get_alpha_issuance(*netuid_i).into())
7374
.unwrap_or(0)
7475
);
7576
log::debug!("alpha_emission_i: {:?}", alpha_emission_i);
77+
7678
// Get initial alpha_in
77-
let alpha_in_i: U96F32 = tao_in_i
78-
.checked_div(price_i)
79-
.unwrap_or(alpha_emission_i)
80-
.min(alpha_emission_i);
79+
let alpha_in_i: U96F32;
80+
let mut tao_in_i: U96F32;
81+
let tao_in_ratio: U96F32 = default_tao_in_i.safe_div_or(
82+
U96F32::saturating_from_num(block_emission),
83+
U96F32::saturating_from_num(0.0),
84+
);
85+
if price_i < tao_in_ratio {
86+
tao_in_i = price_i.saturating_mul(U96F32::saturating_from_num(block_emission));
87+
alpha_in_i = alpha_emission_i;
88+
let difference_tao: U96F32 = default_tao_in_i.saturating_sub(tao_in_i);
89+
// Difference becomes buy.
90+
let buy_swap_result = Self::swap_tao_for_alpha(
91+
*netuid_i,
92+
tou64!(difference_tao),
93+
T::SwapInterface::max_price(),
94+
true,
95+
);
96+
if let Ok(buy_swap_result_ok) = buy_swap_result {
97+
let bought_alpha = AlphaCurrency::from(buy_swap_result_ok.amount_paid_out);
98+
SubnetAlphaOut::<T>::mutate(*netuid_i, |total| {
99+
*total = total.saturating_sub(bought_alpha);
100+
});
101+
}
102+
is_subsidized.insert(*netuid_i, true);
103+
} else {
104+
tao_in_i = default_tao_in_i;
105+
alpha_in_i = tao_in_i.safe_div_or(price_i, alpha_emission_i);
106+
is_subsidized.insert(*netuid_i, false);
107+
}
81108
log::debug!("alpha_in_i: {:?}", alpha_in_i);
109+
82110
// Get alpha_out.
83111
let alpha_out_i = alpha_emission_i;
84112
// Only emit TAO if the subnetwork allows registration.
@@ -101,15 +129,15 @@ impl<T: Config> Pallet<T> {
101129
// This operation changes the pool liquidity each block.
102130
for netuid_i in subnets_to_emit_to.iter() {
103131
// Inject Alpha in.
104-
let alpha_in_i: AlphaCurrency =
105-
tou64!(*alpha_in.get(netuid_i).unwrap_or(&asfloat!(0))).into();
132+
let alpha_in_i =
133+
AlphaCurrency::from(tou64!(*alpha_in.get(netuid_i).unwrap_or(&asfloat!(0))));
106134
SubnetAlphaInEmission::<T>::insert(*netuid_i, alpha_in_i);
107135
SubnetAlphaIn::<T>::mutate(*netuid_i, |total| {
108136
*total = total.saturating_add(alpha_in_i);
109137
});
110138
// Injection Alpha out.
111-
let alpha_out_i: AlphaCurrency =
112-
tou64!(*alpha_out.get(netuid_i).unwrap_or(&asfloat!(0))).into();
139+
let alpha_out_i =
140+
AlphaCurrency::from(tou64!(*alpha_out.get(netuid_i).unwrap_or(&asfloat!(0))));
113141
SubnetAlphaOutEmission::<T>::insert(*netuid_i, alpha_out_i);
114142
SubnetAlphaOut::<T>::mutate(*netuid_i, |total| {
115143
*total = total.saturating_add(alpha_out_i);
@@ -182,29 +210,30 @@ impl<T: Config> Pallet<T> {
182210
let pending_alpha: U96F32 = alpha_out_i.saturating_sub(root_alpha);
183211
log::debug!("pending_alpha: {:?}", pending_alpha);
184212
// Sell root emission through the pool (do not pay fees)
185-
let swap_result = Self::swap_alpha_for_tao(
186-
*netuid_i,
187-
tou64!(root_alpha).into(),
188-
T::SwapInterface::min_price(),
189-
true,
190-
);
191-
if let Ok(ok_result) = swap_result {
192-
let root_tao: u64 = ok_result.amount_paid_out;
193-
194-
log::debug!("root_tao: {:?}", root_tao);
195-
// Accumulate alpha emission in pending.
196-
PendingAlphaSwapped::<T>::mutate(*netuid_i, |total| {
197-
*total = total.saturating_add(tou64!(root_alpha).into());
198-
});
199-
// Accumulate alpha emission in pending.
200-
PendingEmission::<T>::mutate(*netuid_i, |total| {
201-
*total = total.saturating_add(tou64!(pending_alpha).into());
202-
});
203-
// Accumulate root divs for subnet.
204-
PendingRootDivs::<T>::mutate(*netuid_i, |total| {
205-
*total = total.saturating_add(root_tao);
206-
});
213+
let subsidized: bool = *is_subsidized.get(netuid_i).unwrap_or(&false);
214+
if !subsidized {
215+
let swap_result = Self::swap_alpha_for_tao(
216+
*netuid_i,
217+
tou64!(root_alpha).into(),
218+
T::SwapInterface::min_price(),
219+
true,
220+
);
221+
if let Ok(ok_result) = swap_result {
222+
let root_tao: u64 = ok_result.amount_paid_out;
223+
// Accumulate root divs for subnet.
224+
PendingRootDivs::<T>::mutate(*netuid_i, |total| {
225+
*total = total.saturating_add(root_tao);
226+
});
227+
}
207228
}
229+
// Accumulate alpha emission in pending.
230+
PendingAlphaSwapped::<T>::mutate(*netuid_i, |total| {
231+
*total = total.saturating_add(tou64!(root_alpha).into());
232+
});
233+
// Accumulate alpha emission in pending.
234+
PendingEmission::<T>::mutate(*netuid_i, |total| {
235+
*total = total.saturating_add(tou64!(pending_alpha).into());
236+
});
208237
}
209238

210239
// --- 7 Update moving prices after using them in the emission calculation.
@@ -236,7 +265,7 @@ impl<T: Config> Pallet<T> {
236265
PendingEmission::<T>::insert(netuid, AlphaCurrency::ZERO);
237266

238267
// Get and drain the subnet pending root divs.
239-
let pending_tao: u64 = PendingRootDivs::<T>::get(netuid);
268+
let pending_tao = PendingRootDivs::<T>::get(netuid);
240269
PendingRootDivs::<T>::insert(netuid, 0);
241270

242271
// Get this amount as alpha that was swapped for pending root divs.

pallets/subtensor/src/staking/stake_utils.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,7 @@ impl<T: Config> Pallet<T> {
629629
netuid: NetUid,
630630
tao: u64,
631631
price_limit: u64,
632+
drop_fees: bool,
632633
) -> Result<SwapResult, DispatchError> {
633634
// Step 1: Get the mechanism type for the subnet (0 for Stable, 1 for Dynamic)
634635
let mechanism_id: u16 = SubnetMechanism::<T>::get(netuid);
@@ -638,7 +639,7 @@ impl<T: Config> Pallet<T> {
638639
OrderType::Buy,
639640
tao,
640641
price_limit,
641-
false,
642+
drop_fees,
642643
false,
643644
)?;
644645
let alpha_decrease =
@@ -821,7 +822,7 @@ impl<T: Config> Pallet<T> {
821822
set_limit: bool,
822823
) -> Result<AlphaCurrency, DispatchError> {
823824
// Swap the tao to alpha.
824-
let swap_result = Self::swap_tao_for_alpha(netuid, tao, price_limit)?;
825+
let swap_result = Self::swap_tao_for_alpha(netuid, tao, price_limit, false)?;
825826

826827
ensure!(swap_result.amount_paid_out > 0, Error::<T>::AmountTooLow);
827828

pallets/subtensor/src/subnets/registration.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,13 @@ impl<T: Config> Pallet<T> {
141141
Self::remove_balance_from_coldkey_account(&coldkey, registration_cost)?;
142142

143143
// Tokens are swapped and then burned.
144-
let burned_alpha =
145-
Self::swap_tao_for_alpha(netuid, actual_burn_amount, T::SwapInterface::max_price())?
146-
.amount_paid_out;
144+
let burned_alpha = Self::swap_tao_for_alpha(
145+
netuid,
146+
actual_burn_amount,
147+
T::SwapInterface::max_price(),
148+
false,
149+
)?
150+
.amount_paid_out;
147151
SubnetAlphaOut::<T>::mutate(netuid, |total| {
148152
*total = total.saturating_sub(burned_alpha.into())
149153
});

pallets/subtensor/src/tests/children.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3031,6 +3031,7 @@ fn test_parent_child_chain_emission() {
30313031
netuid,
30323032
total_tao.to_num::<u64>(),
30333033
<Test as Config>::SwapInterface::max_price(),
3034+
false,
30343035
)
30353036
.unwrap()
30363037
.amount_paid_out,

pallets/subtensor/src/tests/coinbase.rs

Lines changed: 76 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use pallet_subtensor_swap::position::PositionId;
1010
use sp_core::U256;
1111
use substrate_fixed::types::{I64F64, I96F32, U96F32};
1212
use subtensor_runtime_common::AlphaCurrency;
13+
use subtensor_swap_interface::SwapHandler;
1314

1415
#[allow(clippy::arithmetic_side_effects)]
1516
fn close(value: u64, target: u64, eps: u64) {
@@ -158,22 +159,49 @@ fn test_coinbase_tao_issuance_different_prices() {
158159
let emission: u64 = 100_000_000;
159160
add_network(netuid1, 1, 0);
160161
add_network(netuid2, 1, 0);
162+
163+
// Setup prices 0.1 and 0.2
164+
let initial_tao: u64 = 100_000_u64;
165+
let initial_alpha1: u64 = initial_tao * 10;
166+
let initial_alpha2: u64 = initial_tao * 5;
167+
mock::setup_reserves(netuid1, initial_tao, initial_alpha1.into());
168+
mock::setup_reserves(netuid2, initial_tao, initial_alpha2.into());
169+
170+
// Force the swap to initialize
171+
SubtensorModule::swap_tao_for_alpha(netuid1, 0, 1_000_000_000_000, false).unwrap();
172+
SubtensorModule::swap_tao_for_alpha(netuid2, 0, 1_000_000_000_000, false).unwrap();
173+
161174
// Make subnets dynamic.
162175
SubnetMechanism::<Test>::insert(netuid1, 1);
163176
SubnetMechanism::<Test>::insert(netuid2, 1);
177+
164178
// Set subnet prices.
165179
SubnetMovingPrice::<Test>::insert(netuid1, I96F32::from_num(1));
166180
SubnetMovingPrice::<Test>::insert(netuid2, I96F32::from_num(2));
181+
167182
// Assert initial TAO reserves.
168-
assert_eq!(SubnetTAO::<Test>::get(netuid1), 0);
169-
assert_eq!(SubnetTAO::<Test>::get(netuid2), 0);
183+
assert_eq!(SubnetTAO::<Test>::get(netuid1), initial_tao);
184+
assert_eq!(SubnetTAO::<Test>::get(netuid2), initial_tao);
185+
170186
// Run the coinbase with the emission amount.
171187
SubtensorModule::run_coinbase(U96F32::from_num(emission));
188+
172189
// Assert tao emission is split evenly.
173-
assert_eq!(SubnetTAO::<Test>::get(netuid1), emission / 3);
174-
assert_eq!(SubnetTAO::<Test>::get(netuid2), emission / 3 + emission / 3);
175-
close(TotalIssuance::<Test>::get(), emission, 2);
176-
close(TotalStake::<Test>::get(), emission, 2);
190+
assert_abs_diff_eq!(
191+
SubnetTAO::<Test>::get(netuid1),
192+
initial_tao + emission / 3,
193+
epsilon = 1,
194+
);
195+
assert_abs_diff_eq!(
196+
SubnetTAO::<Test>::get(netuid2),
197+
initial_tao + 2 * emission / 3,
198+
epsilon = 1,
199+
);
200+
201+
// Prices are low => we limit tao issued (buy alpha with it)
202+
let tao_issued = ((0.1 + 0.2) * emission as f64) as u64;
203+
assert_abs_diff_eq!(TotalIssuance::<Test>::get(), tao_issued, epsilon = 10);
204+
assert_abs_diff_eq!(TotalStake::<Test>::get(), emission, epsilon = 10);
177205
});
178206
}
179207

@@ -391,17 +419,11 @@ fn test_coinbase_alpha_issuance_with_cap_trigger() {
391419
SubtensorModule::run_coinbase(U96F32::from_num(emission));
392420
// tao_in = 333_333
393421
// alpha_in = 333_333/price > 1_000_000_000 --> 1_000_000_000 + initial_alpha
394-
assert_eq!(
395-
SubnetAlphaIn::<Test>::get(netuid1),
396-
(initial_alpha + 1_000_000_000).into()
397-
);
422+
assert!(SubnetAlphaIn::<Test>::get(netuid1) < (initial_alpha + 1_000_000_000).into());
398423
assert_eq!(SubnetAlphaOut::<Test>::get(netuid2), 1_000_000_000.into());
399424
// tao_in = 666_666
400425
// alpha_in = 666_666/price > 1_000_000_000 --> 1_000_000_000 + initial_alpha
401-
assert_eq!(
402-
SubnetAlphaIn::<Test>::get(netuid2),
403-
(initial_alpha + 1_000_000_000).into()
404-
);
426+
assert!(SubnetAlphaIn::<Test>::get(netuid2) < (initial_alpha + 1_000_000_000).into());
405427
assert_eq!(SubnetAlphaOut::<Test>::get(netuid2), 1_000_000_000.into()); // Gets full block emission.
406428
});
407429
}
@@ -415,39 +437,56 @@ fn test_coinbase_alpha_issuance_with_cap_trigger_and_block_emission() {
415437
let emission: u64 = 1_000_000;
416438
add_network(netuid1, 1, 0);
417439
add_network(netuid2, 1, 0);
440+
418441
// Make subnets dynamic.
419442
SubnetMechanism::<Test>::insert(netuid1, 1);
420443
SubnetMechanism::<Test>::insert(netuid2, 1);
421-
// Setup prices 1000000
422-
let initial: u64 = 1_000;
423-
let initial_alpha = AlphaCurrency::from(initial * 1000000);
424-
SubnetTAO::<Test>::insert(netuid1, initial);
425-
SubnetAlphaIn::<Test>::insert(netuid1, initial_alpha); // Make price extremely low.
426-
SubnetTAO::<Test>::insert(netuid2, initial);
427-
SubnetAlphaIn::<Test>::insert(netuid2, initial_alpha); // Make price extremely low.
428-
// Set issuance to greater than 21M
429-
SubnetAlphaOut::<Test>::insert(netuid1, AlphaCurrency::from(22_000_000_000_000_000)); // Set issuance above 21M
430-
SubnetAlphaOut::<Test>::insert(netuid2, AlphaCurrency::from(22_000_000_000_000_000)); // Set issuance above 21M
431-
// Set subnet prices.
444+
445+
// Setup prices 0.000001
446+
let initial_tao: u64 = 10_000_u64;
447+
let initial_alpha: u64 = initial_tao * 100_000_u64;
448+
mock::setup_reserves(netuid1, initial_tao, initial_alpha.into());
449+
mock::setup_reserves(netuid2, initial_tao, initial_alpha.into());
450+
451+
// Enable emission
452+
FirstEmissionBlockNumber::<Test>::insert(netuid1, 0);
453+
FirstEmissionBlockNumber::<Test>::insert(netuid2, 0);
432454
SubnetMovingPrice::<Test>::insert(netuid1, I96F32::from_num(1));
433455
SubnetMovingPrice::<Test>::insert(netuid2, I96F32::from_num(2));
456+
457+
// Force the swap to initialize
458+
SubtensorModule::swap_tao_for_alpha(netuid1, 0, 1_000_000_000_000, false).unwrap();
459+
SubtensorModule::swap_tao_for_alpha(netuid2, 0, 1_000_000_000_000, false).unwrap();
460+
461+
// Get the prices before the run_coinbase
462+
let price_1_before = <Test as pallet::Config>::SwapInterface::current_alpha_price(netuid1);
463+
let price_2_before = <Test as pallet::Config>::SwapInterface::current_alpha_price(netuid2);
464+
465+
// Set issuance at 21M
466+
SubnetAlphaOut::<Test>::insert(netuid1, AlphaCurrency::from(21_000_000_000_000_000)); // Set issuance above 21M
467+
SubnetAlphaOut::<Test>::insert(netuid2, AlphaCurrency::from(21_000_000_000_000_000)); // Set issuance above 21M
468+
434469
// Run coinbase
435470
SubtensorModule::run_coinbase(U96F32::from_num(emission));
436-
// tao_in = 333_333
437-
// alpha_in = 333_333/price > 1_000_000_000 --> 0 + initial_alpha
438-
assert_eq!(SubnetAlphaIn::<Test>::get(netuid1), initial_alpha);
471+
472+
// Get the prices after the run_coinbase
473+
let price_1_after = <Test as pallet::Config>::SwapInterface::current_alpha_price(netuid1);
474+
let price_2_after = <Test as pallet::Config>::SwapInterface::current_alpha_price(netuid2);
475+
476+
// AlphaIn gets decreased beacuse of a buy
477+
assert!(u64::from(SubnetAlphaIn::<Test>::get(netuid1)) < initial_alpha);
439478
assert_eq!(
440-
SubnetAlphaOut::<Test>::get(netuid2),
441-
22_000_000_000_000_000.into()
479+
u64::from(SubnetAlphaOut::<Test>::get(netuid2)),
480+
21_000_000_000_000_000_u64
442481
);
443-
// tao_in = 666_666
444-
// alpha_in = 666_666/price > 1_000_000_000 --> 0 + initial_alpha
445-
assert_eq!(SubnetAlphaIn::<Test>::get(netuid2), initial_alpha);
482+
assert!(u64::from(SubnetAlphaIn::<Test>::get(netuid2)) < initial_alpha);
446483
assert_eq!(
447-
SubnetAlphaOut::<Test>::get(netuid2),
448-
22_000_000_000_000_000.into()
484+
u64::from(SubnetAlphaOut::<Test>::get(netuid2)),
485+
21_000_000_000_000_000_u64
449486
);
450-
// No emission.
487+
488+
assert!(price_1_after > price_1_before);
489+
assert!(price_2_after > price_2_before);
451490
});
452491
}
453492

@@ -2388,7 +2427,7 @@ fn test_coinbase_v3_liquidity_update() {
23882427
let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey);
23892428

23902429
// Force the swap to initialize
2391-
SubtensorModule::swap_tao_for_alpha(netuid, 0, 1_000_000_000_000).unwrap();
2430+
SubtensorModule::swap_tao_for_alpha(netuid, 0, 1_000_000_000_000, false).unwrap();
23922431

23932432
let protocol_account_id = pallet_subtensor_swap::Pallet::<Test>::protocol_account_id();
23942433
let position = pallet_subtensor_swap::Positions::<Test>::get((

pallets/subtensor/src/tests/staking.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,7 @@ fn test_add_stake_partial_below_min_stake_fails() {
565565
mock::setup_reserves(netuid, amount * 10, (amount * 10).into());
566566

567567
// Force the swap to initialize
568-
SubtensorModule::swap_tao_for_alpha(netuid, 0, 1_000_000_000_000).unwrap();
568+
SubtensorModule::swap_tao_for_alpha(netuid, 0, 1_000_000_000_000, false).unwrap();
569569

570570
// Get the current price (should be 1.0)
571571
let current_price =
@@ -3306,7 +3306,7 @@ fn test_max_amount_add_dynamic() {
33063306
SubnetAlphaIn::<Test>::insert(netuid, alpha_in);
33073307

33083308
// Force the swap to initialize
3309-
SubtensorModule::swap_tao_for_alpha(netuid, 0, 1_000_000_000_000).unwrap();
3309+
SubtensorModule::swap_tao_for_alpha(netuid, 0, 1_000_000_000_000, false).unwrap();
33103310

33113311
if !alpha_in.is_zero() {
33123312
let expected_price = U96F32::from_num(tao_in) / U96F32::from_num(alpha_in);
@@ -5705,7 +5705,7 @@ fn test_large_swap() {
57055705
pallet_subtensor_swap::EnabledUserLiquidity::<Test>::insert(NetUid::from(netuid), true);
57065706

57075707
// Force the swap to initialize
5708-
SubtensorModule::swap_tao_for_alpha(netuid, 0, 1_000_000_000_000).unwrap();
5708+
SubtensorModule::swap_tao_for_alpha(netuid, 0, 1_000_000_000_000, false).unwrap();
57095709

57105710
setup_positions(netuid.into());
57115711

0 commit comments

Comments
 (0)