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