Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 9 additions & 25 deletions pallets/subtensor/src/staking/remove_stake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,19 +447,13 @@ impl<T: Config> Pallet<T> {
let should_refund_owner: bool = reg_at < start_block;

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

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

owner_emission_tao = if owner_alpha_u64 > 0 {
let order = GetTaoForAlpha::with_amount(owner_alpha_u64);
match T::SwapInterface::sim_swap(netuid.into(), order) {
Ok(sim) => TaoCurrency::from(sim.amount_paid_out),
Err(e) => {
log::debug!(
"destroy_alpha_in_out_stakes: sim_swap owner α→τ failed (netuid={netuid:?}, alpha={owner_alpha_u64}, err={e:?}); falling back to price multiply.",
);
let cur_price: U96F32 =
T::SwapInterface::current_alpha_price(netuid.into());
let val_u64 = U96F32::from_num(owner_alpha_u64)
.saturating_mul(cur_price)
.floor()
.saturating_to_num::<u64>();
TaoCurrency::from(val_u64)
}
}
let cur_price: U96F32 = T::SwapInterface::current_alpha_price(netuid.into());
let val_u64 = U96F32::from_num(owner_alpha_u64)
.saturating_mul(cur_price)
.floor()
.saturating_to_num::<u64>();
TaoCurrency::from(val_u64)
} else {
TaoCurrency::ZERO
};
Expand Down
132 changes: 72 additions & 60 deletions pallets/subtensor/src/tests/networks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,43 +216,49 @@ fn dissolve_owner_cut_refund_logic() {
// One staker and a TAO pot (not relevant to refund amount).
let sh = U256::from(77);
let sc = U256::from(88);
Alpha::<Test>::insert((sh, sc, net), U64F64::from_num(100u128));
SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet(
&sh,
&sc,
net,
AlphaCurrency::from(800u64),
);
SubnetTAO::<Test>::insert(net, TaoCurrency::from(1_000));

// Lock & emissions: total emitted α = 800.
let lock: TaoCurrency = TaoCurrency::from(2_000);
SubtensorModule::set_subnet_locked_balance(net, lock);
Emission::<Test>::insert(
net,
vec![AlphaCurrency::from(200), AlphaCurrency::from(600)],
);
// ensure there was some Alpha issued
assert!(SubtensorModule::get_alpha_issuance(net).to_u64() > 0);

// Owner cut = 11796 / 65535 (about 18%).
SubnetOwnerCut::<Test>::put(11_796u16);

// Compute expected refund with the SAME math as the pallet.
let frac: U96F32 = SubtensorModule::get_float_subnet_owner_cut();
let total_emitted_alpha: u64 = 800;
let total_emitted_alpha: u64 = SubtensorModule::get_alpha_issuance(net).to_u64();
let owner_alpha_u64: u64 = U96F32::from_num(total_emitted_alpha)
.saturating_mul(frac)
.floor()
.saturating_to_num::<u64>();

// Current α→τ price for this subnet.
let price: U96F32 =
<Test as pallet::Config>::SwapInterface::current_alpha_price(net.into());
let owner_emission_tao_u64: u64 = U96F32::from_num(owner_alpha_u64)
.saturating_mul(price)
.floor()
.saturating_to_num::<u64>();
// Use the current alpha price to estimate the TAO equivalent.
let owner_emission_tao = {
let price: U96F32 =
<Test as pallet::Config>::SwapInterface::current_alpha_price(net.into());
U96F32::from_num(owner_alpha_u64)
.saturating_mul(price)
.floor()
.saturating_to_num::<u64>()
.into()
};

let expected_refund: TaoCurrency =
lock.saturating_sub(TaoCurrency::from(owner_emission_tao_u64));
let expected_refund: TaoCurrency = lock.saturating_sub(owner_emission_tao);

let before = SubtensorModule::get_coldkey_balance(&oc);
assert_ok!(SubtensorModule::do_dissolve_network(net));
let after = SubtensorModule::get_coldkey_balance(&oc);

assert!(after > before); // some refund is expected
assert_eq!(
TaoCurrency::from(after),
TaoCurrency::from(before) + expected_refund
Expand Down Expand Up @@ -841,15 +847,10 @@ fn destroy_alpha_out_many_stakers_complex_distribution() {
SubnetTAO::<Test>::insert(netuid, TaoCurrency::from(tao_pot));
SubtensorModule::set_subnet_locked_balance(netuid, TaoCurrency::from(lock));

// ensure there was some Alpha issued
assert!(SubtensorModule::get_alpha_issuance(netuid).to_u64() > 0);

// Owner already earned some emission; owner-cut = 50 %
Emission::<Test>::insert(
netuid,
vec![
AlphaCurrency::from(1_000),
AlphaCurrency::from(2_000),
AlphaCurrency::from(1_500),
],
);
SubnetOwnerCut::<Test>::put(32_768u16); // ~ 0.5 in fixed-point

// ── 4) balances before ──────────────────────────────────────────────
Expand Down Expand Up @@ -879,28 +880,23 @@ fn destroy_alpha_out_many_stakers_complex_distribution() {

// ── 5b) expected owner refund with price-aware emission deduction ───
let frac: U96F32 = SubtensorModule::get_float_subnet_owner_cut();
let total_emitted_alpha: u64 = 1_000 + 2_000 + 1_500; // 4500 α
let total_emitted_alpha: u64 = SubtensorModule::get_alpha_issuance(netuid).to_u64();
let owner_alpha_u64: u64 = U96F32::from_num(total_emitted_alpha)
.saturating_mul(frac)
.floor()
.saturating_to_num::<u64>();

let order = GetTaoForAlpha::<Test>::with_amount(owner_alpha_u64);
let owner_emission_tao =
<Test as pallet::Config>::SwapInterface::sim_swap(netuid.into(), order)
.map(|res| res.amount_paid_out)
.unwrap_or_else(|_| {
// Fallback matches the pallet's fallback
let price: U96F32 =
<Test as pallet::Config>::SwapInterface::current_alpha_price(netuid.into());
U96F32::from_num(owner_alpha_u64)
.saturating_mul(price)
.floor()
.saturating_to_num::<u64>()
.into()
});

let expected_refund = lock.saturating_sub(owner_emission_tao.to_u64());
let owner_emission_tao: u64 = {
// Fallback matches the pallet's fallback
let price: U96F32 =
<Test as pallet::Config>::SwapInterface::current_alpha_price(netuid.into());
U96F32::from_num(owner_alpha_u64)
.saturating_mul(price)
.floor()
.saturating_to_num::<u64>()
};

let expected_refund = lock.saturating_sub(owner_emission_tao);

// ── 6) run distribution (credits τ to coldkeys, wipes α state) ─────
assert_ok!(SubtensorModule::destroy_alpha_in_out_stakes(netuid));
Expand Down Expand Up @@ -947,34 +943,38 @@ fn destroy_alpha_out_refund_gating_by_registration_block() {
// Lock and (nonzero) emissions
let lock_u64: u64 = 50_000;
SubtensorModule::set_subnet_locked_balance(netuid, TaoCurrency::from(lock_u64));
Emission::<Test>::insert(
netuid,
vec![AlphaCurrency::from(1_500u64), AlphaCurrency::from(3_000u64)], // total 4_500 α
);
// Owner cut ≈ 50%
SubnetOwnerCut::<Test>::put(32_768u16);

// give some stake to other key
let other_cold = U256::from(1_234);
let other_hot = U256::from(2_345);
SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet(
&other_hot,
&other_cold,
netuid,
AlphaCurrency::from(30u64), // not nearly enough to cover the lock
);

// ensure there was some Alpha issued
assert!(SubtensorModule::get_alpha_issuance(netuid).to_u64() > 0);

// Compute expected refund using the same math as the pallet
let frac: U96F32 = SubtensorModule::get_float_subnet_owner_cut();
let total_emitted_alpha: u64 = 1_500 + 3_000; // 4_500 α
let total_emitted_alpha: u64 = SubtensorModule::get_alpha_issuance(netuid).to_u64();
let owner_alpha_u64: u64 = U96F32::from_num(total_emitted_alpha)
.saturating_mul(frac)
.floor()
.saturating_to_num::<u64>();

// Prefer sim_swap; fall back to current price if unavailable.
let order = GetTaoForAlpha::<Test>::with_amount(owner_alpha_u64);
let owner_emission_tao_u64 =
<Test as pallet::Config>::SwapInterface::sim_swap(netuid.into(), order)
.map(|res| res.amount_paid_out.to_u64())
.unwrap_or_else(|_| {
let price: U96F32 =
<Test as pallet::Config>::SwapInterface::current_alpha_price(netuid.into());
U96F32::from_num(owner_alpha_u64)
.saturating_mul(price)
.floor()
.saturating_to_num::<u64>()
});
let owner_emission_tao_u64 = {
let price: U96F32 =
<Test as pallet::Config>::SwapInterface::current_alpha_price(netuid.into());
U96F32::from_num(owner_alpha_u64)
.saturating_mul(price)
.floor()
.saturating_to_num::<u64>()
};

let expected_refund: u64 = lock_u64.saturating_sub(owner_emission_tao_u64);

Expand Down Expand Up @@ -1011,7 +1011,17 @@ fn destroy_alpha_out_refund_gating_by_registration_block() {
// Lock and emissions present (should be ignored for refund)
let lock_u64: u64 = 42_000;
SubtensorModule::set_subnet_locked_balance(netuid, TaoCurrency::from(lock_u64));
Emission::<Test>::insert(netuid, vec![AlphaCurrency::from(5_000u64)]);
// give some stake to other key
let other_cold = U256::from(1_234);
let other_hot = U256::from(2_345);
SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet(
&other_hot,
&other_cold,
netuid,
AlphaCurrency::from(300u64), // not nearly enough to cover the lock
);
// ensure there was some Alpha issued
assert!(SubtensorModule::get_alpha_issuance(netuid).to_u64() > 0);
SubnetOwnerCut::<Test>::put(32_768u16); // ~50%

// Balances before
Expand Down Expand Up @@ -1046,7 +1056,9 @@ fn destroy_alpha_out_refund_gating_by_registration_block() {

// lock = 0; emissions present (must not matter)
SubtensorModule::set_subnet_locked_balance(netuid, TaoCurrency::from(0u64));
Emission::<Test>::insert(netuid, vec![AlphaCurrency::from(10_000u64)]);
SubnetAlphaOut::<Test>::insert(netuid, AlphaCurrency::from(10_000));
// ensure there was some Alpha issued
assert!(SubtensorModule::get_alpha_issuance(netuid).to_u64() > 0);
SubnetOwnerCut::<Test>::put(32_768u16); // ~50%

let owner_before = SubtensorModule::get_coldkey_balance(&owner_cold);
Expand Down
Loading