pallet_block_fees/
lib.rs

1// Copyright (C) 2023 Subspace Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// 	http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Pallet Domain Transaction Fees
17
18#![cfg_attr(not(feature = "std"), no_std)]
19
20#[cfg(not(feature = "std"))]
21extern crate alloc;
22
23pub mod fees;
24
25pub use pallet::*;
26
27#[frame_support::pallet]
28mod pallet {
29    #[cfg(not(feature = "std"))]
30    use alloc::vec::Vec;
31    use frame_support::pallet_prelude::*;
32    use frame_support::storage::generator::StorageValue as _;
33    use frame_system::pallet_prelude::*;
34    use parity_scale_codec::{Codec, MaxEncodedLen};
35    use scale_info::TypeInfo;
36    use sp_block_fees::{INHERENT_IDENTIFIER, InherentError, InherentType};
37    use sp_domains::ChainId;
38    use sp_domains::execution_receipt::BlockFees;
39    use sp_runtime::traits::{AtLeast32BitUnsigned, MaybeSerializeDeserialize, Saturating};
40    use sp_runtime::{FixedPointOperand, SaturatedConversion};
41    use sp_std::fmt::Debug;
42    use sp_std::result;
43
44    #[pallet::config]
45    pub trait Config: frame_system::Config {
46        /// The balance of an account.
47        type Balance: Parameter
48            + Member
49            + AtLeast32BitUnsigned
50            + Codec
51            + Default
52            + Copy
53            + MaybeSerializeDeserialize
54            + Debug
55            + MaxEncodedLen
56            + TypeInfo
57            + FixedPointOperand;
58
59        /// The domain chain byte fee
60        type DomainChainByteFee: Get<Self::Balance>;
61    }
62
63    /// The accumulated rewards of the current block
64    ///
65    /// Currently, the only source of rewards is the transaction fees, in the future it
66    /// will include the XDM reward.
67    #[pallet::storage]
68    #[pallet::getter(fn collected_block_fees)]
69    pub(super) type CollectedBlockFees<T: Config> =
70        StorageValue<_, BlockFees<T::Balance>, ValueQuery>;
71
72    /// The consensus chain byte fee
73    ///
74    /// NOTE: we are using `ValueQuery` for convenience, which means the transactions in the domain block #1
75    // are not charged for the consensus chain storage fees.
76    #[pallet::storage]
77    #[pallet::getter(fn consensus_chain_byte_fee)]
78    pub(super) type ConsensusChainByteFee<T: Config> = StorageValue<_, T::Balance, ValueQuery>;
79
80    /// The next consensus chain byte fee, it will take affect after the execution of the current
81    /// block to ensure the operator are using the same fee for both validating and executing the domain
82    /// transaction in the next block.
83    #[pallet::storage]
84    pub(super) type NextConsensusChainByteFee<T: Config> = StorageValue<_, T::Balance, ValueQuery>;
85
86    /// Pallet block-fees to store the accumulated rewards of the current block
87    #[pallet::pallet]
88    #[pallet::without_storage_info]
89    pub struct Pallet<T>(_);
90
91    #[pallet::hooks]
92    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
93        fn on_initialize(_now: BlockNumberFor<T>) -> Weight {
94            // NOTE: set the `CollectedBlockFees` to an empty value instead of removing the value
95            // completely so we can generate a storage proof to prove the empty value, which is used
96            // in the fraud proof.
97            CollectedBlockFees::<T>::set(BlockFees::<T::Balance>::default());
98            T::DbWeight::get().writes(1)
99        }
100
101        fn on_finalize(_now: BlockNumberFor<T>) {
102            let transaction_byte_fee = NextConsensusChainByteFee::<T>::take();
103            ConsensusChainByteFee::<T>::put(transaction_byte_fee);
104        }
105    }
106
107    #[pallet::call]
108    impl<T: Config> Pallet<T> {
109        #[pallet::call_index(0)]
110        #[pallet::weight((
111        // For now, it is safe to treat this call as lightweight, because ensure_none() only
112        // performs a few enum variant checks (which may optimise to a single instruction).
113        // Each instruction typically weighs under 300 picoseconds. Similarly, a Balance is at most
114        // 16 bytes.
115        // TODO: eventually we should benchmark this weight
116        Weight::from_all(10_000).saturating_add(
117            T::DbWeight::get().writes(1)
118        ),
119        DispatchClass::Mandatory
120        ))]
121        pub fn set_next_consensus_chain_byte_fee(
122            origin: OriginFor<T>,
123            #[pallet::compact] transaction_byte_fee: T::Balance,
124        ) -> DispatchResult {
125            ensure_none(origin)?;
126            NextConsensusChainByteFee::<T>::put(transaction_byte_fee);
127            Ok(())
128        }
129    }
130
131    #[pallet::inherent]
132    impl<T: Config> ProvideInherent for Pallet<T> {
133        type Call = Call<T>;
134        type Error = InherentError;
135        const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
136
137        fn create_inherent(data: &InherentData) -> Option<Self::Call> {
138            let inherent_data = data
139                .get_data::<InherentType>(&INHERENT_IDENTIFIER)
140                .expect("Domain block fees inherent data not correctly encoded")
141                .expect("Domain block fees inherent data must be provided");
142
143            let transaction_byte_fee = inherent_data.saturated_into::<T::Balance>();
144
145            Some(Call::set_next_consensus_chain_byte_fee {
146                transaction_byte_fee,
147            })
148        }
149
150        fn check_inherent(
151            call: &Self::Call,
152            data: &InherentData,
153        ) -> result::Result<(), Self::Error> {
154            let inherent_data = data
155                .get_data::<InherentType>(&INHERENT_IDENTIFIER)
156                .expect("Domain block fees inherent data not correctly encoded")
157                .expect("Domain block fees inherent data must be provided");
158
159            let provided_transaction_byte_fee = inherent_data.saturated_into::<T::Balance>();
160
161            if let Call::set_next_consensus_chain_byte_fee {
162                transaction_byte_fee,
163            } = call
164                && transaction_byte_fee != &provided_transaction_byte_fee
165            {
166                return Err(InherentError::IncorrectConsensusChainByteFee);
167            }
168
169            Ok(())
170        }
171
172        fn is_inherent(call: &Self::Call) -> bool {
173            matches!(call, Call::set_next_consensus_chain_byte_fee { .. })
174        }
175    }
176
177    impl<T: Config> Pallet<T> {
178        /// Note the domain execution fee including the storage and compute fee on domain chain,
179        /// tip, and the XDM reward.
180        pub fn note_domain_execution_fee(rewards: T::Balance) {
181            CollectedBlockFees::<T>::mutate(|block_fees| {
182                block_fees.domain_execution_fee =
183                    block_fees.domain_execution_fee.saturating_add(rewards);
184            });
185        }
186
187        /// Note consensus chain storage fee
188        pub fn note_consensus_storage_fee(storage_fee: T::Balance) {
189            CollectedBlockFees::<T>::mutate(|block_fees| {
190                block_fees.consensus_storage_fee =
191                    block_fees.consensus_storage_fee.saturating_add(storage_fee);
192            });
193        }
194
195        /// Note burned balance on domains
196        pub fn note_burned_balance(burned_balance: T::Balance) {
197            CollectedBlockFees::<T>::mutate(|block_fees| {
198                block_fees.burned_balance =
199                    block_fees.burned_balance.saturating_add(burned_balance);
200            });
201        }
202
203        /// Note chain reward fees.
204        pub fn note_chain_rewards(chain_id: ChainId, balance: T::Balance) {
205            CollectedBlockFees::<T>::mutate(|block_fees| {
206                let total_balance = match block_fees.chain_rewards.get(&chain_id) {
207                    None => balance,
208                    Some(prev_balance) => prev_balance.saturating_add(balance),
209                };
210                block_fees.chain_rewards.insert(chain_id, total_balance)
211            });
212        }
213
214        /// Return the final domain transaction byte fee, which consist of:
215        /// - The `ConsensusChainByteFee` for the consensus chain storage cost since the domain
216        ///   transaction need to be bundled and submitted to the consensus chain first.
217        ///
218        /// - The `DomainChainByteFee` for the domain chain storage cost
219        pub fn final_domain_transaction_byte_fee() -> T::Balance {
220            ConsensusChainByteFee::<T>::get().saturating_add(T::DomainChainByteFee::get())
221        }
222
223        pub fn block_fees_storage_key() -> Vec<u8> {
224            CollectedBlockFees::<T>::storage_value_final_key().to_vec()
225        }
226    }
227}