11#![ cfg_attr( not( feature = "std" ) , no_std) ]
22
3- use frame_support :: pallet_prelude :: * ;
3+ // FRAME
44use 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
1515use 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 ;
2227use 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
5138pub struct LinearWeightToFee ;
52-
5339impl 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 >
74109where
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