Skip to content

Commit 91b29de

Browse files
committed
Implement smart fees for remove_stake and tests
1 parent bd56a94 commit 91b29de

File tree

8 files changed

+782
-149
lines changed

8 files changed

+782
-149
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pallets/subtensor/src/staking/remove_stake.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ impl<T: Config> Pallet<T> {
5454

5555
Self::ensure_subtoken_enabled(netuid)?;
5656

57+
// 1.1. Cap the alpha_unstaked at available Alpha because user might be paying transaxtion fees
58+
// in Alpha and their total is already reduced by now.
59+
let alpha_available =
60+
Self::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid);
61+
let alpha_unstaked = alpha_unstaked.min(alpha_available);
62+
5763
// 2. Validate the user input
5864
Self::validate_remove_stake(
5965
&coldkey,

pallets/subtensor/src/tests/staking.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,7 @@ fn test_remove_stake_no_enough_stake() {
656656
netuid,
657657
amount.into(),
658658
),
659-
Error::<Test>::NotEnoughStakeToWithdraw
659+
Error::<Test>::AmountTooLow
660660
);
661661
});
662662
}

pallets/transaction-fee/Cargo.toml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,26 @@ codec.workspace = true
88
frame-support.workspace = true
99
frame-system.workspace = true
1010
log.workspace = true
11+
pallet-balances = { workspace = true, default-features = false }
1112
pallet-subtensor.workspace = true
13+
pallet-subtensor-swap.workspace = true
1214
pallet-transaction-payment.workspace = true
13-
scale-info.workspace = true
1415
smallvec.workspace = true
1516
sp-runtime.workspace = true
1617
sp-std.workspace = true
1718
substrate-fixed.workspace = true
1819
subtensor-runtime-common.workspace = true
20+
subtensor-swap-interface.workspace = true
1921

2022
[dev-dependencies]
21-
pallet-balances = { workspace = true, features = ["std"] }
23+
frame-executive.workspace = true
2224
pallet-crowdloan = { workspace = true, default-features = false }
2325
pallet-drand = { workspace = true, default-features = false }
2426
pallet-evm-chain-id.workspace = true
2527
pallet-grandpa.workspace = true
2628
pallet-preimage = { workspace = true, default-features = false }
2729
pallet-scheduler.workspace = true
28-
pallet-subtensor-swap.workspace = true
30+
scale-info.workspace = true
2931
sp-consensus-aura.workspace = true
3032
sp-consensus-grandpa.workspace = true
3133
sp-core.workspace = true
@@ -40,6 +42,7 @@ workspace = true
4042
default = ["std"]
4143
std = [
4244
"codec/std",
45+
"frame-executive/std",
4346
"frame-support/std",
4447
"frame-system/std",
4548
"log/std",
@@ -64,9 +67,14 @@ std = [
6467
"sp-weights/std",
6568
"substrate-fixed/std",
6669
"subtensor-runtime-common/std",
70+
"subtensor-swap-interface/std",
6771
]
6872
runtime-benchmarks = [
6973
"frame-support/runtime-benchmarks",
7074
"frame-system/runtime-benchmarks",
7175
"sp-runtime/runtime-benchmarks",
76+
"pallet-transaction-payment/runtime-benchmarks",
77+
"pallet-balances/runtime-benchmarks",
78+
"pallet-subtensor/runtime-benchmarks",
79+
"pallet-subtensor-swap/runtime-benchmarks",
7280
]

pallets/transaction-fee/src/lib.rs

Lines changed: 124 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ use frame_support::{
55
pallet_prelude::*,
66
traits::{
77
Imbalance, IsSubType, OnUnbalanced,
8-
fungible::{Balanced, Credit, Debt, Inspect},
8+
fungible::{
9+
Balanced, Credit, Debt, DecreaseIssuance, Imbalance as FungibleImbalance,
10+
IncreaseIssuance, Inspect,
11+
},
912
tokens::{Precision, WithdrawConsequence},
1013
},
1114
weights::{WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial},
@@ -21,11 +24,13 @@ use sp_runtime::{
2124
use pallet_subtensor::Call as SubtensorCall;
2225
use pallet_transaction_payment::Config as PTPConfig;
2326
use pallet_transaction_payment::OnChargeTransaction;
27+
use subtensor_swap_interface::SwapHandler;
2428

2529
// Misc
2630
use core::marker::PhantomData;
2731
use smallvec::smallvec;
2832
use sp_std::vec::Vec;
33+
use substrate_fixed::types::U96F32;
2934
use subtensor_runtime_common::{Balance, NetUid};
3035

3136
// Tests
@@ -55,16 +60,130 @@ impl WeightToFeePolynomial for LinearWeightToFee {
5560
pub trait AlphaFeeHandler<T: frame_system::Config> {
5661
fn can_withdraw_in_alpha(
5762
coldkey: &AccountIdOf<T>,
58-
alpha_vec: &Vec<(AccountIdOf<T>, NetUid)>,
63+
alpha_vec: &[(AccountIdOf<T>, NetUid)],
5964
tao_amount: u64,
6065
) -> bool;
6166
fn withdraw_in_alpha(
6267
coldkey: &AccountIdOf<T>,
63-
alpha_vec: &Vec<(AccountIdOf<T>, NetUid)>,
68+
alpha_vec: &[(AccountIdOf<T>, NetUid)],
6469
tao_amount: u64,
6570
);
6671
}
6772

73+
/// Deduct the transaction fee from the Subtensor Pallet TotalIssuance when charging the transaction
74+
/// fee.
75+
pub struct TransactionFeeHandler<T>(core::marker::PhantomData<T>);
76+
impl<T> Default for TransactionFeeHandler<T> {
77+
fn default() -> Self {
78+
Self(core::marker::PhantomData)
79+
}
80+
}
81+
82+
impl<T>
83+
OnUnbalanced<
84+
FungibleImbalance<
85+
u64,
86+
DecreaseIssuance<AccountIdOf<T>, pallet_balances::Pallet<T>>,
87+
IncreaseIssuance<AccountIdOf<T>, pallet_balances::Pallet<T>>,
88+
>,
89+
> for TransactionFeeHandler<T>
90+
where
91+
T: frame_system::Config,
92+
T: pallet_subtensor::Config,
93+
T: pallet_balances::Config<Balance = u64>,
94+
{
95+
fn on_nonzero_unbalanced(
96+
imbalance: FungibleImbalance<
97+
u64,
98+
DecreaseIssuance<AccountIdOf<T>, pallet_balances::Pallet<T>>,
99+
IncreaseIssuance<AccountIdOf<T>, pallet_balances::Pallet<T>>,
100+
>,
101+
) {
102+
let ti_before = pallet_subtensor::TotalIssuance::<T>::get();
103+
pallet_subtensor::TotalIssuance::<T>::put(ti_before.saturating_sub(imbalance.peek()));
104+
drop(imbalance);
105+
}
106+
}
107+
108+
/// Handle Alpha fees
109+
impl<T> AlphaFeeHandler<T> for TransactionFeeHandler<T>
110+
where
111+
T: frame_system::Config,
112+
T: pallet_subtensor::Config,
113+
T: pallet_subtensor_swap::Config,
114+
{
115+
/// This function checks if tao_amount fee can be withdraw in Alpha currency
116+
/// by converting Alpha to TAO at the current price and ignoring slippage.
117+
///
118+
/// If this function returns true, the transaction will be included in the block
119+
/// and Alpha will be withdraw from the account, no matter whether transaction
120+
/// is successful or not.
121+
///
122+
/// If this function returns true, but at the time of execution the Alpha price
123+
/// changes and it becomes impossible to pay tx fee with the Alpha balance,
124+
/// the transaction still executes and all Alpha is withdrawn from the account.
125+
fn can_withdraw_in_alpha(
126+
coldkey: &AccountIdOf<T>,
127+
alpha_vec: &[(AccountIdOf<T>, NetUid)],
128+
tao_amount: u64,
129+
) -> bool {
130+
if alpha_vec.is_empty() {
131+
// Alpha vector is empty, nothing to withdraw
132+
return false;
133+
}
134+
135+
// Divide tao_amount among all alpha entries
136+
let tao_per_entry = tao_amount.checked_div(alpha_vec.len() as u64).unwrap_or(0);
137+
138+
// The rule here is that we should be able to withdraw at least from one entry.
139+
// This is not ideal because it may not pay all fees, but UX is the priority
140+
// and this approach still provides spam protection.
141+
alpha_vec.iter().any(|(hotkey, netuid)| {
142+
let alpha_balance = U96F32::saturating_from_num(
143+
pallet_subtensor::Pallet::<T>::get_stake_for_hotkey_and_coldkey_on_subnet(
144+
hotkey, coldkey, *netuid,
145+
),
146+
);
147+
let alpha_price = pallet_subtensor_swap::Pallet::<T>::current_alpha_price(*netuid);
148+
alpha_price.saturating_mul(alpha_balance) >= tao_per_entry
149+
})
150+
}
151+
152+
fn withdraw_in_alpha(
153+
coldkey: &AccountIdOf<T>,
154+
alpha_vec: &[(AccountIdOf<T>, NetUid)],
155+
tao_amount: u64,
156+
) {
157+
if alpha_vec.is_empty() {
158+
return;
159+
}
160+
161+
let tao_per_entry = tao_amount.checked_div(alpha_vec.len() as u64).unwrap_or(0);
162+
163+
alpha_vec.iter().for_each(|(hotkey, netuid)| {
164+
// Divide tao_amount evenly among all alpha entries
165+
let alpha_balance = U96F32::saturating_from_num(
166+
pallet_subtensor::Pallet::<T>::get_stake_for_hotkey_and_coldkey_on_subnet(
167+
hotkey, coldkey, *netuid,
168+
),
169+
);
170+
let alpha_price = pallet_subtensor_swap::Pallet::<T>::current_alpha_price(*netuid);
171+
let alpha_fee = U96F32::saturating_from_num(tao_per_entry)
172+
.checked_div(alpha_price)
173+
.unwrap_or(alpha_balance)
174+
.min(alpha_balance)
175+
.saturating_to_num::<u64>();
176+
177+
pallet_subtensor::Pallet::<T>::decrease_stake_for_hotkey_and_coldkey_on_subnet(
178+
hotkey,
179+
coldkey,
180+
*netuid,
181+
alpha_fee.into(),
182+
);
183+
});
184+
}
185+
}
186+
68187
/// Enum that describes either a withdrawn amount of transaction fee in TAO or the
69188
/// fact that fee was charged in Alpha (without an amount because it is not needed)
70189
pub enum WithdrawnFee<T: frame_system::Config, F: Balanced<AccountIdOf<T>>> {
@@ -217,12 +336,12 @@ where
217336
Ok(())
218337
}
219338

220-
#[cfg(feature = "runtime-benchmarks")]
339+
// #[cfg(feature = "runtime-benchmarks")]
221340
fn endow_account(who: &AccountIdOf<T>, amount: Self::Balance) {
222341
let _ = F::deposit(who, amount, Precision::BestEffort);
223342
}
224343

225-
#[cfg(feature = "runtime-benchmarks")]
344+
// #[cfg(feature = "runtime-benchmarks")]
226345
fn minimum_balance() -> Self::Balance {
227346
F::minimum_balance()
228347
}

0 commit comments

Comments
 (0)