Skip to content

Commit 1b40d1d

Browse files
authored
Merge pull request #1386 from opentensor/fix/staking-fees
Fix/staking fees
2 parents 0ceb6c9 + a062fb9 commit 1b40d1d

File tree

6 files changed

+152
-33
lines changed

6 files changed

+152
-33
lines changed

pallets/subtensor/src/staking/add_stake.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::*;
2-
use sp_core::Get;
2+
use substrate_fixed::types::I96F32;
33

44
impl<T: Config> Pallet<T> {
55
/// ---- The implementation for the extrinsic add_stake: Adds stake to a hotkey account.
@@ -58,13 +58,19 @@ impl<T: Config> Pallet<T> {
5858
)?;
5959

6060
// 3. Ensure the remove operation from the coldkey is a success.
61-
let tao_staked: u64 =
62-
Self::remove_balance_from_coldkey_account(&coldkey, stake_to_be_added)?;
61+
let tao_staked: I96F32 =
62+
Self::remove_balance_from_coldkey_account(&coldkey, stake_to_be_added)?.into();
6363

6464
// 4. Swap the stake into alpha on the subnet and increase counters.
6565
// Emit the staking event.
6666
let fee = DefaultStakingFee::<T>::get();
67-
Self::stake_into_subnet(&hotkey, &coldkey, netuid, tao_staked, fee);
67+
Self::stake_into_subnet(
68+
&hotkey,
69+
&coldkey,
70+
netuid,
71+
tao_staked.saturating_to_num::<u64>(),
72+
fee,
73+
);
6874

6975
// Ok and return.
7076
Ok(())
@@ -148,12 +154,19 @@ impl<T: Config> Pallet<T> {
148154
}
149155

150156
// 5. Ensure the remove operation from the coldkey is a success.
151-
let tao_staked: u64 = Self::remove_balance_from_coldkey_account(&coldkey, possible_stake)?;
157+
let tao_staked: I96F32 =
158+
Self::remove_balance_from_coldkey_account(&coldkey, possible_stake)?.into();
152159

153160
// 6. Swap the stake into alpha on the subnet and increase counters.
154161
// Emit the staking event.
155162
let fee = DefaultStakingFee::<T>::get();
156-
Self::stake_into_subnet(&hotkey, &coldkey, netuid, tao_staked, fee);
163+
Self::stake_into_subnet(
164+
&hotkey,
165+
&coldkey,
166+
netuid,
167+
tao_staked.saturating_to_num::<u64>(),
168+
fee,
169+
);
157170

158171
// Ok and return.
159172
Ok(())

pallets/subtensor/src/staking/move_stake.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use super::*;
22
use safe_math::*;
33
use sp_core::Get;
4-
use substrate_fixed::types::U64F64;
4+
use substrate_fixed::types::{I96F32, U64F64};
55

66
impl<T: Config> Pallet<T> {
77
/// Moves stake from one hotkey to another across subnets.
@@ -338,7 +338,13 @@ impl<T: Config> Pallet<T> {
338338
};
339339

340340
// Unstake from the origin subnet, returning TAO (or a 1:1 equivalent).
341-
let fee = DefaultStakingFee::<T>::get().safe_div(2);
341+
let fee = Self::calculate_staking_fee(
342+
origin_netuid,
343+
origin_hotkey,
344+
I96F32::saturating_from_num(alpha_amount),
345+
)
346+
.safe_div(2);
347+
342348
let tao_unstaked = Self::unstake_from_subnet(
343349
origin_hotkey,
344350
origin_coldkey,

pallets/subtensor/src/staking/remove_stake.rs

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::*;
2-
use sp_core::Get;
2+
use substrate_fixed::types::I96F32;
33

44
impl<T: Config> Pallet<T> {
55
/// ---- The implementation for the extrinsic remove_stake: Removes stake from a hotkey account and adds it onto a coldkey.
@@ -58,7 +58,11 @@ impl<T: Config> Pallet<T> {
5858
)?;
5959

6060
// 3. Swap the alpba to tao and update counters for this subnet.
61-
let fee = DefaultStakingFee::<T>::get();
61+
let fee = Self::calculate_staking_fee(
62+
netuid,
63+
&hotkey,
64+
I96F32::saturating_from_num(alpha_unstaked),
65+
);
6266
let tao_unstaked: u64 =
6367
Self::unstake_from_subnet(&hotkey, &coldkey, netuid, alpha_unstaked, fee);
6468

@@ -109,8 +113,6 @@ impl<T: Config> Pallet<T> {
109113
origin: T::RuntimeOrigin,
110114
hotkey: T::AccountId,
111115
) -> dispatch::DispatchResult {
112-
let fee = DefaultStakingFee::<T>::get();
113-
114116
// 1. We check the transaction is signed by the caller and retrieve the T::AccountId coldkey information.
115117
let coldkey = ensure_signed(origin)?;
116118
log::info!("do_unstake_all( origin:{:?} hotkey:{:?} )", coldkey, hotkey);
@@ -126,20 +128,26 @@ impl<T: Config> Pallet<T> {
126128
log::debug!("All subnet netuids: {:?}", netuids);
127129

128130
// 4. Iterate through all subnets and remove stake.
129-
for netuid in netuids.iter() {
131+
for netuid in netuids.into_iter() {
130132
// Ensure that the hotkey has enough stake to withdraw.
131133
let alpha_unstaked =
132-
Self::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, *netuid);
134+
Self::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid);
135+
let fee = Self::calculate_staking_fee(
136+
netuid,
137+
&hotkey,
138+
I96F32::saturating_from_num(alpha_unstaked),
139+
);
140+
133141
if alpha_unstaked > 0 {
134142
// Swap the alpha to tao and update counters for this subnet.
135143
let tao_unstaked: u64 =
136-
Self::unstake_from_subnet(&hotkey, &coldkey, *netuid, alpha_unstaked, fee);
144+
Self::unstake_from_subnet(&hotkey, &coldkey, netuid, alpha_unstaked, fee);
137145

138146
// Add the balance to the coldkey. If the above fails we will not credit this coldkey.
139147
Self::add_balance_to_coldkey_account(&coldkey, tao_unstaked);
140148

141149
// If the stake is below the minimum, we clear the nomination from storage.
142-
Self::clear_small_nomination_if_required(&hotkey, &coldkey, *netuid);
150+
Self::clear_small_nomination_if_required(&hotkey, &coldkey, netuid);
143151
}
144152
}
145153

@@ -177,8 +185,6 @@ impl<T: Config> Pallet<T> {
177185
origin: T::RuntimeOrigin,
178186
hotkey: T::AccountId,
179187
) -> dispatch::DispatchResult {
180-
let fee = DefaultStakingFee::<T>::get();
181-
182188
// 1. We check the transaction is signed by the caller and retrieve the T::AccountId coldkey information.
183189
let coldkey = ensure_signed(origin)?;
184190
log::info!("do_unstake_all( origin:{:?} hotkey:{:?} )", coldkey, hotkey);
@@ -195,22 +201,28 @@ impl<T: Config> Pallet<T> {
195201

196202
// 4. Iterate through all subnets and remove stake.
197203
let mut total_tao_unstaked: u64 = 0;
198-
for netuid in netuids.iter() {
204+
for netuid in netuids.into_iter() {
199205
// If not Root network.
200-
if *netuid != Self::get_root_netuid() {
206+
if netuid != Self::get_root_netuid() {
201207
// Ensure that the hotkey has enough stake to withdraw.
202208
let alpha_unstaked =
203-
Self::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, *netuid);
209+
Self::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid);
210+
let fee = Self::calculate_staking_fee(
211+
netuid,
212+
&hotkey,
213+
I96F32::saturating_from_num(alpha_unstaked),
214+
);
215+
204216
if alpha_unstaked > 0 {
205217
// Swap the alpha to tao and update counters for this subnet.
206-
let tao_unstaked: u64 =
207-
Self::unstake_from_subnet(&hotkey, &coldkey, *netuid, alpha_unstaked, fee);
218+
let tao_unstaked =
219+
Self::unstake_from_subnet(&hotkey, &coldkey, netuid, alpha_unstaked, fee);
208220

209221
// Increment total
210222
total_tao_unstaked = total_tao_unstaked.saturating_add(tao_unstaked);
211223

212224
// If the stake is below the minimum, we clear the nomination from storage.
213-
Self::clear_small_nomination_if_required(&hotkey, &coldkey, *netuid);
225+
Self::clear_small_nomination_if_required(&hotkey, &coldkey, netuid);
214226
}
215227
}
216228
}
@@ -302,8 +314,12 @@ impl<T: Config> Pallet<T> {
302314
)?;
303315

304316
// 4. Swap the alpha to tao and update counters for this subnet.
305-
let fee = DefaultStakingFee::<T>::get();
306-
let tao_unstaked: u64 =
317+
let fee = Self::calculate_staking_fee(
318+
netuid,
319+
&hotkey,
320+
I96F32::saturating_from_num(alpha_unstaked),
321+
);
322+
let tao_unstaked =
307323
Self::unstake_from_subnet(&hotkey, &coldkey, netuid, possible_alpha, fee);
308324

309325
// 5. We add the balance to the coldkey. If the above fails we will not credit this coldkey.

pallets/subtensor/src/staking/stake_utils.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -806,7 +806,7 @@ impl<T: Config> Pallet<T> {
806806
/// Stakes TAO into a subnet for a given hotkey and coldkey pair.
807807
///
808808
/// We update the pools associated with a subnet as well as update hotkey alpha shares.
809-
pub fn stake_into_subnet(
809+
pub(crate) fn stake_into_subnet(
810810
hotkey: &T::AccountId,
811811
coldkey: &T::AccountId,
812812
netuid: u16,
@@ -1066,6 +1066,28 @@ impl<T: Config> Pallet<T> {
10661066

10671067
Ok(())
10681068
}
1069+
1070+
pub(crate) fn calculate_staking_fee(
1071+
netuid: u16,
1072+
hotkey: &T::AccountId,
1073+
alpha_estimate: I96F32,
1074+
) -> u64 {
1075+
if (netuid == Self::get_root_netuid()) || (SubnetMechanism::<T>::get(netuid)) == 0 {
1076+
DefaultStakingFee::<T>::get()
1077+
} else {
1078+
let fee = alpha_estimate
1079+
.saturating_mul(
1080+
I96F32::saturating_from_num(AlphaDividendsPerSubnet::<T>::get(netuid, &hotkey))
1081+
.safe_div(I96F32::saturating_from_num(TotalHotkeyAlpha::<T>::get(
1082+
&hotkey, netuid,
1083+
))),
1084+
)
1085+
.saturating_mul(Self::get_alpha_price(netuid)) // fee needs to be in TAO
1086+
.saturating_to_num::<u64>();
1087+
1088+
fee.max(DefaultStakingFee::<T>::get())
1089+
}
1090+
}
10691091
}
10701092

10711093
///////////////////////////////////////////

pallets/subtensor/src/tests/staking.rs

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2219,11 +2219,71 @@ fn test_remove_stake_fee_goes_to_subnet_tao() {
22192219
});
22202220
}
22212221

2222+
// cargo test --package pallet-subtensor --lib -- tests::staking::test_remove_stake_fee_realistic_values --exact --show-output --nocapture
22222223
#[test]
2223-
fn test_stake_below_min_validate() {
2224-
// Testing the signed extension validate function
2225-
// correctly filters the `add_stake` transaction.
2224+
fn test_remove_stake_fee_realistic_values() {
2225+
new_test_ext(1).execute_with(|| {
2226+
let subnet_owner_coldkey = U256::from(1001);
2227+
let subnet_owner_hotkey = U256::from(1002);
2228+
let hotkey = U256::from(2);
2229+
let coldkey = U256::from(3);
2230+
let alpha_to_unstake = 111_180_000_000;
2231+
let alpha_divs = 2_816_190;
2232+
2233+
let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey);
2234+
SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey);
2235+
2236+
// Mock a realistic scenario:
2237+
// Subnet 1 has 3896 TAO and 128_011 Alpha in reserves, which
2238+
// makes its price ~0.03.
2239+
// A hotkey has 111 Alpha stake and is unstaking all Alpha.
2240+
// Alpha dividends of this hotkey are ~0.0028
2241+
// This makes fee be equal ~0.0028 Alpha ~= 84000 rao
2242+
let tao_reserve: U96F32 = U96F32::from_num(3_896_056_559_708_u64);
2243+
let alpha_in: U96F32 = U96F32::from_num(128_011_331_299_964_u64);
2244+
SubnetTAO::<Test>::insert(netuid, tao_reserve.to_num::<u64>());
2245+
SubnetAlphaIn::<Test>::insert(netuid, alpha_in.to_num::<u64>());
2246+
AlphaDividendsPerSubnet::<Test>::insert(netuid, hotkey, alpha_divs);
2247+
let current_price = SubtensorModule::get_alpha_price(netuid).to_num::<f64>();
2248+
2249+
// Add stake first time to init TotalHotkeyAlpha
2250+
SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet(
2251+
&hotkey,
2252+
&coldkey,
2253+
netuid,
2254+
alpha_to_unstake,
2255+
);
2256+
2257+
// Estimate fees
2258+
let expected_fee: f64 = current_price * alpha_divs as f64;
2259+
2260+
// Remove stake to measure fee
2261+
let balance_before = SubtensorModule::get_coldkey_balance(&coldkey);
2262+
let expected_tao_no_fee =
2263+
SubtensorModule::sim_swap_alpha_for_tao(netuid, alpha_to_unstake).unwrap();
2264+
2265+
assert_ok!(SubtensorModule::remove_stake(
2266+
RuntimeOrigin::signed(coldkey),
2267+
hotkey,
2268+
netuid,
2269+
alpha_to_unstake
2270+
));
22262271

2272+
// Calculate expected fee
2273+
let balance_after = SubtensorModule::get_coldkey_balance(&coldkey);
2274+
let actual_fee = expected_tao_no_fee as f64 - (balance_after - balance_before) as f64;
2275+
log::info!("Actual fee: {:?}", actual_fee);
2276+
2277+
assert_abs_diff_eq!(
2278+
actual_fee as u64,
2279+
expected_fee as u64,
2280+
epsilon = expected_fee as u64 / 1000
2281+
);
2282+
});
2283+
}
2284+
2285+
#[test]
2286+
fn test_stake_below_min_validate() {
22272287
new_test_ext(0).execute_with(|| {
22282288
let subnet_owner_coldkey = U256::from(1001);
22292289
let subnet_owner_hotkey = U256::from(1002);

pallets/subtensor/src/tests/swap_coldkey.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use frame_support::traits::schedule::DispatchTime;
1414
use frame_support::traits::schedule::v3::Named as ScheduleNamed;
1515
use sp_core::{Get, H256, U256};
1616
use sp_runtime::DispatchError;
17+
use substrate_fixed::types::I96F32;
1718

1819
// // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_total_hotkey_coldkey_stakes_this_interval --exact --nocapture
1920
// #[test]
@@ -537,7 +538,9 @@ fn test_swap_concurrent_modifications() {
537538
let netuid: u16 = 1;
538539
let initial_stake = 1_000_000_000_000;
539540
let additional_stake = 500_000_000_000;
540-
let fee = DefaultStakingFee::<Test>::get();
541+
let initial_stake_alpha =
542+
I96F32::from(initial_stake).saturating_mul(SubtensorModule::get_alpha_price(netuid));
543+
let fee = SubtensorModule::calculate_staking_fee(netuid, &hotkey, initial_stake_alpha);
541544

542545
// Setup initial state
543546
add_network(netuid, 1, 1);
@@ -588,15 +591,14 @@ fn test_swap_concurrent_modifications() {
588591
&mut weight
589592
));
590593

591-
let eps = 500; // RAO
592594
assert_abs_diff_eq!(
593595
SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(
594596
&hotkey,
595597
&new_coldkey,
596598
netuid
597599
),
598600
stake_before_swap + additional_stake - fee,
599-
epsilon = eps
601+
epsilon = (stake_before_swap + additional_stake - fee) / 1000
600602
);
601603
assert!(!Alpha::<Test>::contains_key((hotkey, old_coldkey, netuid)));
602604
});

0 commit comments

Comments
 (0)