Skip to main content

pallet_transaction_fees/
lib.rs

1//! Pallet for charging and re-distributing transaction fees.
2
3#![cfg_attr(not(feature = "std"), no_std)]
4#![forbid(unsafe_code)]
5#![warn(rust_2018_idioms, missing_debug_implementations)]
6
7pub mod weights;
8
9use frame_support::sp_runtime::SaturatedConversion;
10use frame_support::sp_runtime::traits::Zero;
11use frame_support::traits::{Currency, Get};
12use frame_support::weights::Weight;
13use frame_system::pallet_prelude::*;
14pub use pallet::*;
15use parity_scale_codec::{Codec, Decode, Encode};
16use scale_info::TypeInfo;
17use subspace_runtime_primitives::FindBlockRewardAddress;
18
19type BalanceOf<T> =
20    <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
21
22pub trait WeightInfo {
23    fn on_initialize() -> Weight;
24}
25
26#[derive(Encode, Decode, TypeInfo)]
27struct CollectedFees<Balance: Codec> {
28    storage: Balance,
29    compute: Balance,
30    tips: Balance,
31}
32
33#[frame_support::pallet]
34mod pallet {
35    use super::{BalanceOf, CollectedFees, WeightInfo};
36    use frame_support::pallet_prelude::*;
37    use frame_support::traits::Currency;
38    use frame_system::pallet_prelude::*;
39    use subspace_runtime_primitives::{BlockTransactionByteFee, FindBlockRewardAddress};
40
41    #[pallet::config]
42    pub trait Config: frame_system::Config<RuntimeEvent: From<Event<Self>>> {
43        /// Minimum desired number of replicas of the blockchain to be stored by the network,
44        /// impacts storage fees.
45        #[pallet::constant]
46        type MinReplicationFactor: Get<u16>;
47
48        /// How many credits there is in circulation.
49        #[pallet::constant]
50        type CreditSupply: Get<BalanceOf<Self>>;
51
52        /// How much space there is on the network.
53        #[pallet::constant]
54        type TotalSpacePledged: Get<u128>;
55
56        /// How big is the history of the blockchain in archived state (thus includes erasure
57        /// coding, but not replication).
58        #[pallet::constant]
59        type BlockchainHistorySize: Get<u128>;
60
61        type Currency: Currency<Self::AccountId>;
62
63        type FindBlockRewardAddress: FindBlockRewardAddress<Self::AccountId>;
64
65        /// Whether dynamic cost of storage should be used
66        type DynamicCostOfStorage: Get<bool>;
67
68        type WeightInfo: WeightInfo;
69    }
70
71    /// The value of `transaction_byte_fee` for both the current and the next block.
72    ///
73    /// The `next` value of `transaction_byte_fee` is updated at block finalization and used to
74    /// validate extrinsic to be included in the next block, the value is move to `current` at
75    /// block initialization and used to execute extrinsic in the current block. Together it
76    /// ensure we use the same value for both validating and executing the extrinsic.
77    ///
78    /// NOTE: both the `current` and `next` value is set to the default `Balance::max_value` in
79    /// the genesis block which means there will be no signed extrinsic included in block #1.
80    #[pallet::storage]
81    pub(super) type TransactionByteFee<T> =
82        StorageValue<_, BlockTransactionByteFee<BalanceOf<T>>, ValueQuery>;
83
84    /// Temporary value (cleared at block finalization) used to determine if the `transaction_byte_fee`
85    /// is used to validate extrinsic or execute extrinsic.
86    #[pallet::storage]
87    pub(super) type IsDuringBlockExecution<T: Config> = StorageValue<_, bool, ValueQuery>;
88
89    /// Temporary value (cleared at block finalization) which contains current block author, so we
90    /// can issue fees during block finalization.
91    #[pallet::storage]
92    pub(super) type BlockAuthor<T: Config> = StorageValue<_, T::AccountId>;
93
94    /// Temporary value (cleared at block finalization) which contains current block fees, so we can
95    /// issue fees during block finalization.
96    #[pallet::storage]
97    pub(super) type CollectedBlockFees<T: Config> = StorageValue<_, CollectedFees<BalanceOf<T>>>;
98
99    /// Pallet transaction fees for issuing fees to block authors.
100    #[pallet::pallet]
101    #[pallet::without_storage_info]
102    pub struct Pallet<T>(_);
103
104    /// `pallet-transaction-fees` events
105    #[pallet::event]
106    #[pallet::generate_deposit(pub(super) fn deposit_event)]
107    pub enum Event<T: Config> {
108        /// Storage fees.
109        #[codec(index = 0)]
110        BlockFees {
111            /// Block author that received the fees.
112            who: T::AccountId,
113            /// Amount of collected storage fees.
114            storage: BalanceOf<T>,
115            /// Amount of collected compute fees.
116            compute: BalanceOf<T>,
117            /// Amount of collected tips.
118            tips: BalanceOf<T>,
119        },
120        /// Fees burned due to equivocated block author or rewards not enabled.
121        #[codec(index = 1)]
122        BurnedBlockFees {
123            /// Amount of burned storage fees.
124            storage: BalanceOf<T>,
125            /// Amount of burned compute fees.
126            compute: BalanceOf<T>,
127            /// Amount of burned tips.
128            tips: BalanceOf<T>,
129        },
130    }
131
132    #[pallet::hooks]
133    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T>
134    where
135        BalanceOf<T>: From<u8> + From<u64>,
136    {
137        fn on_initialize(now: BlockNumberFor<T>) -> Weight {
138            Self::do_initialize(now);
139            T::WeightInfo::on_initialize()
140        }
141
142        fn on_finalize(now: BlockNumberFor<T>) {
143            Self::do_finalize(now);
144        }
145    }
146}
147
148impl<T: Config> Pallet<T>
149where
150    BalanceOf<T>: From<u64>,
151{
152    fn do_initialize(_n: BlockNumberFor<T>) {
153        // Block author may equivocate, in which case they'll not be present here
154        if let Some(block_author) = T::FindBlockRewardAddress::find_block_reward_address() {
155            BlockAuthor::<T>::put(block_author);
156        }
157
158        CollectedBlockFees::<T>::put(CollectedFees {
159            storage: BalanceOf::<T>::zero(),
160            compute: BalanceOf::<T>::zero(),
161            tips: BalanceOf::<T>::zero(),
162        });
163
164        // Update the `current` value to the `next`
165        TransactionByteFee::<T>::mutate(|transaction_byte_fee| {
166            transaction_byte_fee.current = transaction_byte_fee.next
167        });
168        IsDuringBlockExecution::<T>::set(true);
169    }
170
171    fn do_finalize(_n: BlockNumberFor<T>) {
172        // Update the value for the next `transaction_byte_fee`
173        TransactionByteFee::<T>::mutate(|transaction_byte_fee| {
174            transaction_byte_fee.next = Self::calculate_transaction_byte_fee()
175        });
176        IsDuringBlockExecution::<T>::take();
177
178        let collected_fees = CollectedBlockFees::<T>::take()
179            .expect("`CollectedBlockFees` was set in `on_initialize`; qed");
180
181        let total = collected_fees.storage + collected_fees.compute + collected_fees.tips;
182
183        if !total.is_zero() {
184            // Block author may equivocate, in which case they'll not be present here
185            if let Some(block_author) = BlockAuthor::<T>::take() {
186                let _imbalance = T::Currency::deposit_creating(&block_author, total);
187                Self::deposit_event(Event::<T>::BlockFees {
188                    who: block_author.clone(),
189                    storage: collected_fees.storage,
190                    compute: collected_fees.compute,
191                    tips: collected_fees.tips,
192                });
193            } else {
194                // If farmer equivocated, fees are burned
195                let amount = collected_fees.storage + collected_fees.compute + collected_fees.tips;
196                if !amount.is_zero() {
197                    Self::deposit_event(Event::<T>::BurnedBlockFees {
198                        storage: collected_fees.storage,
199                        compute: collected_fees.compute,
200                        tips: collected_fees.tips,
201                    });
202                }
203            }
204        }
205    }
206
207    /// Return the current `transaction_byte_fee` value for executing extrinsic and
208    /// return the next `transaction_byte_fee` value for validating extrinsic to be
209    /// included in the next block
210    pub fn transaction_byte_fee() -> BalanceOf<T> {
211        if !T::DynamicCostOfStorage::get() {
212            return BalanceOf::<T>::from(1);
213        }
214
215        if IsDuringBlockExecution::<T>::get() {
216            TransactionByteFee::<T>::get().current
217        } else {
218            TransactionByteFee::<T>::get().next
219        }
220    }
221
222    pub fn calculate_transaction_byte_fee() -> BalanceOf<T> {
223        let credit_supply = T::CreditSupply::get();
224
225        match (T::TotalSpacePledged::get() / u128::from(T::MinReplicationFactor::get()))
226            .checked_sub(T::BlockchainHistorySize::get())
227        {
228            Some(free_space) if free_space > 0 => {
229                credit_supply / BalanceOf::<T>::saturated_from(free_space)
230            }
231            _ => credit_supply,
232        }
233    }
234
235    pub fn note_transaction_fees(
236        storage_fee: BalanceOf<T>,
237        compute_fee: BalanceOf<T>,
238        tip: BalanceOf<T>,
239    ) {
240        CollectedBlockFees::<T>::mutate(|collected_block_fees| {
241            // `CollectedBlockFees` was set in `on_initialize` if it is `None` means this
242            // function is called offchain (i.e. transaction validation) thus safe to skip
243            if let Some(collected_block_fees) = collected_block_fees.as_mut() {
244                collected_block_fees.storage += storage_fee;
245                collected_block_fees.compute += compute_fee;
246                collected_block_fees.tips += tip;
247            }
248        });
249    }
250}
251
252impl<T: Config> subspace_runtime_primitives::StorageFee<BalanceOf<T>> for Pallet<T>
253where
254    BalanceOf<T>: From<u64>,
255{
256    fn transaction_byte_fee() -> BalanceOf<T> {
257        Self::transaction_byte_fee()
258    }
259
260    fn note_storage_fees(storage_fee: BalanceOf<T>) {
261        Self::note_transaction_fees(storage_fee, Zero::zero(), Zero::zero())
262    }
263}