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