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 pub const fn min_fee(&self) -> Word {
67 self.min_fee
68 }
69
70 pub const fn max_fee(&self) -> Word {
72 self.max_fee
73 }
74
75 pub const fn min_gas(&self) -> Word {
77 self.min_gas
78 }
79
80 pub const fn max_gas(&self) -> Word {
82 self.max_gas
83 }
84
85 pub const fn into_inner(self) -> (Word, Word) {
87 (self.min_fee, self.max_fee)
88 }
89
90 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 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
131pub 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 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
150pub trait Chargeable: field::Inputs + field::Witnesses + field::Policies {
152 fn min_gas(&self, gas_costs: &GasCosts, fee: &FeeParameters) -> Word {
154 min_gas(self, gas_costs, fee)
155 }
156
157 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 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 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 fn refund_fee(
209 &self,
210 gas_costs: &GasCosts,
211 fee: &FeeParameters,
212 used_gas: Word,
213 gas_price: Word,
214 ) -> Option<Word> {
215 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 let used_fee: u64 = used_fee.try_into().ok()?;
228 self.max_fee_limit().checked_sub(used_fee)
229 }
230
231 fn metered_bytes_size(&self) -> usize;
233
234 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 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 Input::CoinPredicate(_)
251 | Input::MessageCoinPredicate(_)
252 | Input::MessageDataPredicate(_) => true,
253 _ => false,
255 })
256 .map(|input| match input {
257 Input::CoinSigned(_)
259 | Input::MessageCoinSigned(_)
260 | Input::MessageDataSigned(_) => gas_costs.eck1(),
261 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 _ => 0,
288 })
289 .fold(0, |acc, cost| acc.saturating_add(cost))
290 }
291
292 fn gas_used_by_metadata(&self, gas_costs: &GasCosts) -> Word;
294}