Skip to content

Commit bd56a94

Browse files
committed
Fees in alpha for multiple subnets. Empty tests.
1 parent d878a86 commit bd56a94

File tree

6 files changed

+806
-93
lines changed

6 files changed

+806
-93
lines changed

Cargo.lock

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

pallets/transaction-fee/Cargo.toml

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,34 @@ version = "0.1.0"
44
edition.workspace = true
55

66
[dependencies]
7-
codec = { workspace = true }
8-
frame-support = { workspace = true }
9-
frame-system = { workspace = true }
10-
log = { workspace = true }
11-
pallet-subtensor = { workspace = true }
12-
pallet-transaction-payment = { workspace = true }
13-
scale-info = { workspace = true }
14-
smallvec = { workspace = true }
15-
sp-runtime = { workspace = true }
16-
substrate-fixed = { workspace = true }
17-
subtensor-runtime-common = { workspace = true }
7+
codec.workspace = true
8+
frame-support.workspace = true
9+
frame-system.workspace = true
10+
log.workspace = true
11+
pallet-subtensor.workspace = true
12+
pallet-transaction-payment.workspace = true
13+
scale-info.workspace = true
14+
smallvec.workspace = true
15+
sp-runtime.workspace = true
16+
sp-std.workspace = true
17+
substrate-fixed.workspace = true
18+
subtensor-runtime-common.workspace = true
19+
20+
[dev-dependencies]
21+
pallet-balances = { workspace = true, features = ["std"] }
22+
pallet-crowdloan = { workspace = true, default-features = false }
23+
pallet-drand = { workspace = true, default-features = false }
24+
pallet-evm-chain-id.workspace = true
25+
pallet-grandpa.workspace = true
26+
pallet-preimage = { workspace = true, default-features = false }
27+
pallet-scheduler.workspace = true
28+
pallet-subtensor-swap.workspace = true
29+
sp-consensus-aura.workspace = true
30+
sp-consensus-grandpa.workspace = true
31+
sp-core.workspace = true
32+
sp-io.workspace = true
33+
sp-tracing.workspace = true
34+
sp-weights.workspace = true
1835

1936
[lints]
2037
workspace = true
@@ -26,10 +43,25 @@ std = [
2643
"frame-support/std",
2744
"frame-system/std",
2845
"log/std",
46+
"pallet-balances/std",
47+
"pallet-crowdloan/std",
48+
"pallet-drand/std",
49+
"pallet-evm-chain-id/std",
50+
"pallet-grandpa/std",
51+
"pallet-preimage/std",
52+
"pallet-scheduler/std",
2953
"pallet-subtensor/std",
54+
"pallet-subtensor-swap/std",
3055
"pallet-transaction-payment/std",
3156
"scale-info/std",
3257
"sp-runtime/std",
58+
"sp-std/std",
59+
"sp-consensus-aura/std",
60+
"sp-consensus-grandpa/std",
61+
"sp-core/std",
62+
"sp-io/std",
63+
"sp-tracing/std",
64+
"sp-weights/std",
3365
"substrate-fixed/std",
3466
"subtensor-runtime-common/std",
3567
]

pallets/transaction-fee/src/lib.rs

Lines changed: 137 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,41 @@
11
#![cfg_attr(not(feature = "std"), no_std)]
22

3-
use frame_support::pallet_prelude::*;
3+
// FRAME
44
use frame_support::{
5-
dispatch::{GetDispatchInfo, PostDispatchInfo},
5+
pallet_prelude::*,
66
traits::{
77
Imbalance, IsSubType, OnUnbalanced,
88
fungible::{Balanced, Credit, Debt, Inspect},
99
tokens::{Precision, WithdrawConsequence},
10-
UnfilteredDispatchable
1110
},
1211
weights::{WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial},
1312
};
1413

14+
// Runtime
1515
use sp_runtime::{
1616
Perbill, Saturating,
17-
traits::{Dispatchable, DispatchInfoOf, PostDispatchInfoOf},
17+
traits::{DispatchInfoOf, PostDispatchInfoOf},
1818
};
19-
// use substrate_fixed::types::U96F32;
20-
// use subtensor_runtime_common::{AlphaCurrency, NetUid};
2119

20+
// Pallets
21+
use pallet_subtensor::Call as SubtensorCall;
22+
use pallet_transaction_payment::Config as PTPConfig;
23+
use pallet_transaction_payment::OnChargeTransaction;
24+
25+
// Misc
26+
use core::marker::PhantomData;
2227
use smallvec::smallvec;
23-
use subtensor_runtime_common::Balance;
24-
25-
pub use pallet_transaction_payment::OnChargeTransaction;
26-
27-
#[frame_support::pallet]
28-
pub mod pallet {
29-
use super::*;
30-
31-
#[pallet::config]
32-
pub trait Config: frame_system::Config {
33-
type RuntimeCall: Parameter
34-
+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
35-
+ GetDispatchInfo
36-
+ From<frame_system::Call<Self>>
37-
+ UnfilteredDispatchable<RuntimeOrigin = Self::RuntimeOrigin>
38-
+ IsSubType<Call<Self>>
39-
+ IsType<<Self as frame_system::Config>::RuntimeCall>;
40-
}
28+
use sp_std::vec::Vec;
29+
use subtensor_runtime_common::{Balance, NetUid};
4130

42-
#[pallet::pallet]
43-
pub struct Pallet<T>(_);
31+
// Tests
32+
#[cfg(test)]
33+
mod tests;
4434

45-
// #[pallet::call]
46-
// impl<T: Config> Pallet<T> {
47-
// // You now have access to T::OnChargeTransaction, T::WeightToFee, etc.
48-
// }
49-
}
35+
type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
36+
type CallOf<T> = <T as frame_system::Config>::RuntimeCall;
5037

5138
pub struct LinearWeightToFee;
52-
5339
impl WeightToFeePolynomial for LinearWeightToFee {
5440
type Balance = Balance;
5541

@@ -65,102 +51,174 @@ impl WeightToFeePolynomial for LinearWeightToFee {
6551
}
6652
}
6753

68-
/// Custom FungibleAdapter based on standard FungibleAdapter from transsaction_payment
54+
/// Trait that allows working with Alpha
55+
pub trait AlphaFeeHandler<T: frame_system::Config> {
56+
fn can_withdraw_in_alpha(
57+
coldkey: &AccountIdOf<T>,
58+
alpha_vec: &Vec<(AccountIdOf<T>, NetUid)>,
59+
tao_amount: u64,
60+
) -> bool;
61+
fn withdraw_in_alpha(
62+
coldkey: &AccountIdOf<T>,
63+
alpha_vec: &Vec<(AccountIdOf<T>, NetUid)>,
64+
tao_amount: u64,
65+
);
66+
}
67+
68+
/// Enum that describes either a withdrawn amount of transaction fee in TAO or the
69+
/// fact that fee was charged in Alpha (without an amount because it is not needed)
70+
pub enum WithdrawnFee<T: frame_system::Config, F: Balanced<AccountIdOf<T>>> {
71+
Tao(Credit<AccountIdOf<T>, F>),
72+
Alpha,
73+
}
74+
75+
/// Custom OnChargeTransaction implementation based on standard FungibleAdapter from transaction_payment
6976
/// FRAME pallet
70-
///
71-
pub struct FungibleAdapter<F, OU>(PhantomData<(F, OU)>);
77+
///
78+
pub struct SubtensorTxFeeHandler<F, OU>(PhantomData<(F, OU)>);
79+
80+
/// This implementation contains the list of calls that require paying transaction
81+
/// fees in Alpha
82+
impl<F, OU> SubtensorTxFeeHandler<F, OU> {
83+
/// Returns Vec<(hotkey, netuid)> if the given call should pay fees in Alpha instead of TAO.
84+
/// The vector represents all subnets where this hotkey has any alpha stake. Fees will be
85+
/// distributed evenly between subnets in case of multiple subnets.
86+
pub fn fees_in_alpha<T>(call: &CallOf<T>) -> Vec<(AccountIdOf<T>, NetUid)>
87+
where
88+
T: frame_system::Config + pallet_subtensor::Config,
89+
CallOf<T>: IsSubType<pallet_subtensor::Call<T>>,
90+
{
91+
let mut alpha_vec: Vec<(AccountIdOf<T>, NetUid)> = Vec::new();
92+
93+
// Otherwise, switch to Alpha for the extrinsics that assume converting Alpha
94+
// to TAO
95+
// TODO: Populate the list
96+
match call.is_sub_type() {
97+
Some(SubtensorCall::remove_stake { hotkey, netuid, .. }) => {
98+
log::info!("fees_in_alpha: matched remove_stake => use Alpha");
99+
alpha_vec.push((hotkey.clone(), *netuid))
100+
}
101+
_ => {}
102+
}
103+
104+
alpha_vec
105+
}
106+
}
72107

73-
impl<T, F, OU> OnChargeTransaction<T> for FungibleAdapter<F, OU>
108+
impl<T, F, OU> OnChargeTransaction<T> for SubtensorTxFeeHandler<F, OU>
74109
where
75-
T: crate::pallet::Config + frame_system::Config + pallet_transaction_payment::Config,
110+
T: PTPConfig + pallet_subtensor::Config,
111+
CallOf<T>: IsSubType<pallet_subtensor::Call<T>>,
76112
F: Balanced<T::AccountId>,
77-
OU: OnUnbalanced<Credit<T::AccountId, F>>,
113+
OU: OnUnbalanced<Credit<T::AccountId, F>> + AlphaFeeHandler<T>,
114+
<F as Inspect<AccountIdOf<T>>>::Balance: Into<u64>,
78115
{
79-
type LiquidityInfo = Option<Credit<T::AccountId, F>>;
116+
type LiquidityInfo = Option<WithdrawnFee<T, F>>;
80117
type Balance = <F as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
81118

82119
fn withdraw_fee(
83-
who: &<T>::AccountId,
84-
call: &<T as frame_system::Config>::RuntimeCall,
85-
_dispatch_info: &DispatchInfoOf<<T as frame_system::Config>::RuntimeCall>,
120+
who: &AccountIdOf<T>,
121+
call: &CallOf<T>,
122+
_dispatch_info: &DispatchInfoOf<CallOf<T>>,
86123
fee: Self::Balance,
87124
_tip: Self::Balance,
88125
) -> Result<Self::LiquidityInfo, TransactionValidityError> {
89-
90-
match call {
91-
<T as pallet::Config>::RuntimeCall::SubtensorModule(pallet_subtensor::Call::remove_stake { hotkey: _, netuid: _, amount_unstaked: _ }) => {
92-
log::error!("=========== calling remove_stake");
93-
},
94-
_ => {}
95-
}
96-
97126
if fee.is_zero() {
98127
return Ok(None);
99128
}
100129

130+
// Traditional fees in TAO
101131
match F::withdraw(
102132
who,
103133
fee,
104134
Precision::Exact,
105135
frame_support::traits::tokens::Preservation::Preserve,
106136
frame_support::traits::tokens::Fortitude::Polite,
107137
) {
108-
Ok(imbalance) => Ok(Some(imbalance)),
109-
Err(_) => Err(InvalidTransaction::Payment.into()),
138+
Ok(imbalance) => Ok(Some(WithdrawnFee::Tao(imbalance))),
139+
Err(_) => {
140+
let alpha_vec = Self::fees_in_alpha::<T>(call);
141+
if !alpha_vec.is_empty() {
142+
let fee_u64: u64 = fee.into();
143+
OU::withdraw_in_alpha(who, &alpha_vec, fee_u64);
144+
return Ok(Some(WithdrawnFee::Alpha));
145+
}
146+
Err(InvalidTransaction::Payment.into())
147+
}
110148
}
111149
}
112150

113151
fn can_withdraw_fee(
114-
who: &T::AccountId,
115-
_call: &<T as frame_system::Config>::RuntimeCall,
116-
_dispatch_info: &DispatchInfoOf<<T as frame_system::Config>::RuntimeCall>,
152+
who: &AccountIdOf<T>,
153+
call: &CallOf<T>,
154+
_dispatch_info: &DispatchInfoOf<CallOf<T>>,
117155
fee: Self::Balance,
118156
_tip: Self::Balance,
119157
) -> Result<(), TransactionValidityError> {
120158
if fee.is_zero() {
121159
return Ok(());
122160
}
123161

162+
// Prefer traditional fees in TAO
124163
match F::can_withdraw(who, fee) {
125164
WithdrawConsequence::Success => Ok(()),
126-
_ => Err(InvalidTransaction::Payment.into()),
165+
_ => {
166+
// Fallback to fees in Alpha if possible
167+
let alpha_vec = Self::fees_in_alpha::<T>(call);
168+
if !alpha_vec.is_empty() {
169+
let fee_u64: u64 = fee.into();
170+
if OU::can_withdraw_in_alpha(who, &alpha_vec, fee_u64) {
171+
return Ok(());
172+
}
173+
}
174+
Err(InvalidTransaction::Payment.into())
175+
}
127176
}
128177
}
129178

130179
fn correct_and_deposit_fee(
131-
who: &<T>::AccountId,
132-
_dispatch_info: &DispatchInfoOf<<T as frame_system::Config>::RuntimeCall>,
133-
_post_info: &PostDispatchInfoOf<<T as frame_system::Config>::RuntimeCall>,
180+
who: &AccountIdOf<T>,
181+
_dispatch_info: &DispatchInfoOf<CallOf<T>>,
182+
_post_info: &PostDispatchInfoOf<CallOf<T>>,
134183
corrected_fee: Self::Balance,
135184
tip: Self::Balance,
136185
already_withdrawn: Self::LiquidityInfo,
137186
) -> Result<(), TransactionValidityError> {
138-
if let Some(paid) = already_withdrawn {
139-
// Calculate how much refund we should return
140-
let refund_amount = paid.peek().saturating_sub(corrected_fee);
141-
// refund to the the account that paid the fees if it exists. otherwise, don't refind
142-
// anything.
143-
let refund_imbalance = if F::total_balance(who) > F::Balance::zero() {
144-
F::deposit(who, refund_amount, Precision::BestEffort)
145-
.unwrap_or_else(|_| Debt::<T::AccountId, F>::zero())
146-
} else {
147-
Debt::<T::AccountId, F>::zero()
148-
};
149-
// merge the imbalance caused by paying the fees and refunding parts of it again.
150-
let adjusted_paid: Credit<T::AccountId, F> = paid
151-
.offset(refund_imbalance)
152-
.same()
153-
.map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?;
154-
// Call someone else to handle the imbalance (fee and tip separately)
155-
let (tip, fee) = adjusted_paid.split(tip);
156-
OU::on_unbalanceds(Some(fee).into_iter().chain(Some(tip)));
187+
if let Some(withdrawn) = already_withdrawn {
188+
// Fee may be paid in TAO or in Alpha. Only refund and update total issuance for
189+
// TAO fees because Alpha fees are charged precisely and do not need any adjustments
190+
match withdrawn {
191+
WithdrawnFee::Tao(paid) => {
192+
// Calculate how much refund we should return
193+
let refund_amount = paid.peek().saturating_sub(corrected_fee);
194+
// refund to the account that paid the fees if it exists. otherwise, don't refund
195+
// anything.
196+
let refund_imbalance = if F::total_balance(who) > F::Balance::zero() {
197+
F::deposit(who, refund_amount, Precision::BestEffort)
198+
.unwrap_or_else(|_| Debt::<T::AccountId, F>::zero())
199+
} else {
200+
Debt::<T::AccountId, F>::zero()
201+
};
202+
// merge the imbalance caused by paying the fees and refunding parts of it again.
203+
let adjusted_paid: Credit<T::AccountId, F> =
204+
paid.offset(refund_imbalance).same().map_err(|_| {
205+
TransactionValidityError::Invalid(InvalidTransaction::Payment)
206+
})?;
207+
// Call someone else to handle the imbalance (fee and tip separately)
208+
let (tip, fee) = adjusted_paid.split(tip);
209+
OU::on_unbalanceds(Some(fee).into_iter().chain(Some(tip)));
210+
}
211+
WithdrawnFee::Alpha => {
212+
// We do not refund Alpha, charges are final
213+
}
214+
}
157215
}
158216

159217
Ok(())
160218
}
161219

162220
#[cfg(feature = "runtime-benchmarks")]
163-
fn endow_account(who: &T::AccountId, amount: Self::Balance) {
221+
fn endow_account(who: &AccountIdOf<T>, amount: Self::Balance) {
164222
let _ = F::deposit(who, amount, Precision::BestEffort);
165223
}
166224

0 commit comments

Comments
 (0)