fuel_tx/transaction/
fee.rs

1use crate::{
2    field,
3    field::{
4        MaxFeeLimit,
5        Tip,
6        WitnessLimit,
7    },
8    input::{
9        coin::{
10            CoinPredicate,
11            CoinSigned,
12        },
13        message::{
14            MessageCoinPredicate,
15            MessageCoinSigned,
16            MessageDataPredicate,
17            MessageDataSigned,
18        },
19    },
20    policies::PolicyType,
21    FeeParameters,
22    GasCosts,
23    Input,
24};
25use fuel_asm::Word;
26use fuel_types::canonical::Serialize;
27use hashbrown::HashSet;
28
29#[derive(
30    Debug,
31    Default,
32    Clone,
33    Copy,
34    PartialEq,
35    Eq,
36    PartialOrd,
37    Ord,
38    Hash,
39    serde::Serialize,
40    serde::Deserialize,
41)]
42pub struct TransactionFee {
43    pub(crate) min_fee: Word,
44    pub(crate) max_fee: Word,
45    pub(crate) min_gas: Word,
46    pub(crate) max_gas: Word,
47}
48
49impl From<TransactionFee> for Word {
50    fn from(fee: TransactionFee) -> Word {
51        fee.max_fee
52    }
53}
54
55impl TransactionFee {
56    pub const fn new(min_fee: Word, max_fee: Word, min_gas: Word, max_gas: Word) -> Self {
57        Self {
58            min_fee,
59            max_fee,
60            min_gas,
61            max_gas,
62        }
63    }
64
65    /// Minimum fee value to pay for the base transaction without script execution.
66    pub const fn min_fee(&self) -> Word {
67        self.min_fee
68    }
69
70    /// Maximum fee value to pay for the transaction with script execution.
71    pub const fn max_fee(&self) -> Word {
72        self.max_fee
73    }
74
75    /// The minimum amount of gas (not fee!) used by this tx
76    pub const fn min_gas(&self) -> Word {
77        self.min_gas
78    }
79
80    /// The max amount of gas (not fee!) usable by this tx
81    pub const fn max_gas(&self) -> Word {
82        self.max_gas
83    }
84
85    /// Convert into a tuple containing the inner min & total fee values
86    pub const fn into_inner(self) -> (Word, Word) {
87        (self.min_fee, self.max_fee)
88    }
89
90    /// Attempt to subtract the maximum fee value from a given balance
91    ///
92    /// Will return `None` if arithmetic overflow occurs.
93    pub fn checked_deduct_total(&self, balance: Word) -> Option<Word> {
94        let fee = self.max_fee();
95
96        balance.checked_sub(fee)
97    }
98
99    /// Attempt to create a transaction fee from parameters and transaction internals
100    ///
101    /// Will return `None` if arithmetic overflow occurs.
102    pub fn checked_from_tx<T>(
103        gas_costs: &GasCosts,
104        params: &FeeParameters,
105        tx: &T,
106        gas_price: Word,
107    ) -> Option<Self>
108    where
109        T: Chargeable,
110    {
111        let min_gas = tx.min_gas(gas_costs, params);
112        let max_gas = tx.max_gas(gas_costs, params);
113        let min_fee = tx.min_fee(gas_costs, params, gas_price).try_into().ok()?;
114        let max_fee = tx.max_fee(gas_costs, params, gas_price).try_into().ok()?;
115
116        if min_fee > max_fee {
117            return None;
118        }
119
120        Some(Self::new(min_fee, max_fee, min_gas, max_gas))
121    }
122}
123
124fn gas_to_fee(gas: Word, gas_price: Word, factor: Word) -> u128 {
125    let total_price = (gas as u128)
126        .checked_mul(gas_price as u128)
127        .expect("Impossible to overflow because multiplication of two `u64` <= `u128`");
128    total_price.div_ceil(factor as u128)
129}
130
131/// Returns the minimum gas required to start execution of any transaction.
132pub fn min_gas<Tx>(tx: &Tx, gas_costs: &GasCosts, fee: &FeeParameters) -> Word
133where
134    Tx: Chargeable + ?Sized,
135{
136    let bytes_size = tx.metered_bytes_size();
137
138    let vm_initialization_gas = gas_costs.vm_initialization().resolve(bytes_size as Word);
139
140    // It's okay to saturate because we have the `max_gas_per_tx` rule for transaction
141    // validity. In the production, the value always will be lower than
142    // `u64::MAX`.
143    let bytes_gas = fee.gas_per_byte().saturating_mul(bytes_size as u64);
144    tx.gas_used_by_inputs(gas_costs)
145        .saturating_add(tx.gas_used_by_metadata(gas_costs))
146        .saturating_add(bytes_gas)
147        .saturating_add(vm_initialization_gas)
148}
149
150/// Means that the blockchain charges fee for the transaction.
151pub trait Chargeable: field::Inputs + field::Witnesses + field::Policies {
152    /// Returns the minimum gas required to start transaction execution.
153    fn min_gas(&self, gas_costs: &GasCosts, fee: &FeeParameters) -> Word {
154        min_gas(self, gas_costs, fee)
155    }
156
157    /// Returns the maximum possible gas after the end of transaction execution.
158    ///
159    /// The function guarantees that the value is not less than [Self::min_gas].
160    fn max_gas(&self, gas_costs: &GasCosts, fee: &FeeParameters) -> Word {
161        let remaining_allowed_witness_gas = self
162            .witness_limit()
163            .saturating_sub(self.witnesses().size_dynamic() as u64)
164            .saturating_mul(fee.gas_per_byte());
165
166        self.min_gas(gas_costs, fee)
167            .saturating_add(remaining_allowed_witness_gas)
168    }
169
170    /// Returns the minimum fee required to start transaction execution.
171    fn min_fee(
172        &self,
173        gas_costs: &GasCosts,
174        fee: &FeeParameters,
175        gas_price: Word,
176    ) -> u128 {
177        let tip = self.tip();
178        let gas_fee = gas_to_fee(
179            self.min_gas(gas_costs, fee),
180            gas_price,
181            fee.gas_price_factor(),
182        );
183        gas_fee.saturating_add(tip as u128)
184    }
185
186    /// Returns the maximum possible fee after the end of transaction execution.
187    ///
188    /// The function guarantees that the value is not less than [Self::min_fee].
189    fn max_fee(
190        &self,
191        gas_costs: &GasCosts,
192        fee: &FeeParameters,
193        gas_price: Word,
194    ) -> u128 {
195        let tip = self.tip();
196        let gas_fee = gas_to_fee(
197            self.max_gas(gas_costs, fee),
198            gas_price,
199            fee.gas_price_factor(),
200        );
201        gas_fee.saturating_add(tip as u128)
202    }
203
204    /// Returns the fee amount that can be refunded back based on the `used_gas` and
205    /// current state of the transaction.
206    ///
207    /// Return `None` if overflow occurs.
208    fn refund_fee(
209        &self,
210        gas_costs: &GasCosts,
211        fee: &FeeParameters,
212        used_gas: Word,
213        gas_price: Word,
214    ) -> Option<Word> {
215        // We've already charged the user for witnesses as part of the minimal gas and all
216        // execution required to validate transaction validity rules.
217        let min_gas = self.min_gas(gas_costs, fee);
218
219        let total_used_gas = min_gas.saturating_add(used_gas);
220        let tip = self.policies().get(PolicyType::Tip).unwrap_or(0);
221        let used_fee = gas_to_fee(total_used_gas, gas_price, fee.gas_price_factor())
222            .saturating_add(tip as u128);
223
224        // It is okay to saturate everywhere above because it only can decrease the value
225        // of `refund`. But here, because we need to return the amount we
226        // want to refund, we need to handle the overflow caused by the price.
227        let used_fee: u64 = used_fee.try_into().ok()?;
228        self.max_fee_limit().checked_sub(used_fee)
229    }
230
231    /// Used for accounting purposes when charging byte based fees.
232    fn metered_bytes_size(&self) -> usize;
233
234    /// Returns the gas used by the inputs.
235    fn gas_used_by_inputs(&self, gas_costs: &GasCosts) -> Word {
236        let mut witness_cache: HashSet<u16> = HashSet::new();
237        self.inputs()
238            .iter()
239            .filter(|input| match input {
240                // Include signed inputs of unique witness indices
241                Input::CoinSigned(CoinSigned { witness_index, .. })
242                | Input::MessageCoinSigned(MessageCoinSigned { witness_index, .. })
243                | Input::MessageDataSigned(MessageDataSigned { witness_index, .. })
244                    if !witness_cache.contains(witness_index) =>
245                {
246                    witness_cache.insert(*witness_index);
247                    true
248                }
249                // Include all predicates
250                Input::CoinPredicate(_)
251                | Input::MessageCoinPredicate(_)
252                | Input::MessageDataPredicate(_) => true,
253                // Ignore all other inputs
254                _ => false,
255            })
256            .map(|input| match input {
257                // Charge EC recovery cost for signed inputs
258                Input::CoinSigned(_)
259                | Input::MessageCoinSigned(_)
260                | Input::MessageDataSigned(_) => gas_costs.eck1(),
261                // Charge the cost of the contract root for predicate inputs
262                Input::CoinPredicate(CoinPredicate {
263                    predicate,
264                    predicate_gas_used,
265                    ..
266                })
267                | Input::MessageCoinPredicate(MessageCoinPredicate {
268                    predicate,
269                    predicate_gas_used,
270                    ..
271                })
272                | Input::MessageDataPredicate(MessageDataPredicate {
273                    predicate,
274                    predicate_gas_used,
275                    ..
276                }) => {
277                    let bytes_size = self.metered_bytes_size();
278                    let vm_initialization_gas =
279                        gas_costs.vm_initialization().resolve(bytes_size as Word);
280                    gas_costs
281                        .contract_root()
282                        .resolve(predicate.len() as u64)
283                        .saturating_add(*predicate_gas_used)
284                        .saturating_add(vm_initialization_gas)
285                }
286                // Charge nothing for all other inputs
287                _ => 0,
288            })
289            .fold(0, |acc, cost| acc.saturating_add(cost))
290    }
291
292    /// Used for accounting purposes when charging for metadata creation.
293    fn gas_used_by_metadata(&self, gas_costs: &GasCosts) -> Word;
294}