diff --git a/CHANGELOG.md b/CHANGELOG.md index 24aa50754..0ddbf24a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed #### Breaking +- [#682](https://github.com/FuelLabs/fuel-vm/pull/682): Include `Tip` policy in fee calculation - [#683](https://github.com/FuelLabs/fuel-vm/pull/683): Simplify `InterpreterStorage` by removing dependency on `MerkleRootStorage` and removing `merkle_` prefix from method names. - [#672](https://github.com/FuelLabs/fuel-vm/pull/672): Remove `GasPrice` policy - [#672](https://github.com/FuelLabs/fuel-vm/pull/672): Add `gas_price` field to transaction execution diff --git a/fuel-tx/src/transaction/fee.rs b/fuel-tx/src/transaction/fee.rs index 437c64f61..02f90b399 100644 --- a/fuel-tx/src/transaction/fee.rs +++ b/fuel-tx/src/transaction/fee.rs @@ -1,6 +1,9 @@ use crate::{ field, - field::WitnessLimit, + field::{ + Tip, + WitnessLimit, + }, input::{ coin::{ CoinPredicate, @@ -13,6 +16,7 @@ use crate::{ MessageDataSigned, }, }, + policies::PolicyType, FeeParameters, GasCosts, Input, @@ -151,11 +155,13 @@ pub trait Chargeable: field::Inputs + field::Witnesses + field::Policies { fee: &FeeParameters, gas_price: Word, ) -> u128 { - gas_to_fee( + 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. @@ -167,11 +173,13 @@ pub trait Chargeable: field::Inputs + field::Witnesses + field::Policies { fee: &FeeParameters, gas_price: Word, ) -> u128 { - gas_to_fee( + 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 @@ -190,7 +198,9 @@ pub trait Chargeable: field::Inputs + field::Witnesses + field::Policies { let min_gas = self.min_gas(gas_costs, fee); let total_used_gas = min_gas.saturating_add(used_gas); - let used_fee = gas_to_fee(total_used_gas, gas_price, fee.gas_price_factor); + 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); let refund = self .max_fee(gas_costs, fee, gas_price) diff --git a/fuel-vm/src/checked_transaction.rs b/fuel-vm/src/checked_transaction.rs index f4cf24c58..8a6e3e005 100644 --- a/fuel-vm/src/checked_transaction.rs +++ b/fuel-vm/src/checked_transaction.rs @@ -646,9 +646,11 @@ impl From for CheckError { } #[cfg(feature = "random")] +#[allow(non_snake_case)] #[cfg(test)] mod tests { #![allow(clippy::cast_possible_truncation)] + use super::*; use alloc::vec; use fuel_asm::op; @@ -656,6 +658,7 @@ mod tests { use fuel_tx::{ field::{ ScriptGasLimit, + Tip, WitnessLimit, Witnesses, }, @@ -919,6 +922,7 @@ mod tests { gas_limit: u64, input_amount: u64, gas_price_factor: u64, + tip: u64, seed: u64, ) -> TestResult { // dont divide by zero @@ -930,7 +934,7 @@ mod tests { let gas_costs = GasCosts::default(); let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(gas_price_factor); let base_asset_id = rng.gen(); - let tx = predicate_message_coin_tx(rng, gas_limit, input_amount); + let tx = predicate_message_coin_tx(rng, gas_limit, input_amount, tip); if let Ok(valid) = is_valid_max_fee(&tx, gas_price, &gas_costs, &fee_params, &base_asset_id) @@ -949,6 +953,7 @@ mod tests { input_amount: u64, gas_price_factor: u64, seed: u64, + tip: u64, ) -> TestResult { // dont divide by zero if gas_price_factor == 0 { @@ -958,7 +963,7 @@ mod tests { let rng = &mut StdRng::seed_from_u64(seed); let gas_costs = GasCosts::default(); let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(gas_price_factor); - let tx = predicate_message_coin_tx(rng, gas_limit, input_amount); + let tx = predicate_message_coin_tx(rng, gas_limit, input_amount, tip); // Given let used_gas = 0; @@ -985,6 +990,7 @@ mod tests { input_amount: u64, gas_price: u64, gas_price_factor: u64, + tip: u64, seed: u64, ) -> TestResult { // verify min fee a transaction can consume based on bytes is correct @@ -997,7 +1003,7 @@ mod tests { let gas_costs = GasCosts::default(); let fee_params = FeeParameters::DEFAULT.with_gas_price_factor(gas_price_factor); let base_asset_id = rng.gen(); - let tx = predicate_message_coin_tx(rng, gas_limit, input_amount); + let tx = predicate_message_coin_tx(rng, gas_limit, input_amount, tip); if let Ok(valid) = is_valid_min_fee(&tx, &gas_costs, &fee_params, &base_asset_id, gas_price) @@ -1466,6 +1472,76 @@ mod tests { assert_eq!(err, CheckError::Validity(ValidityError::BalanceOverflow)); } + fn arb_tx(rng: &mut StdRng) -> Script { + let input_amount = 1000; + let gas_limit = 1000; + base_asset_tx(rng, input_amount, gas_limit) + } + + #[test] + fn into_checked_basic__min_fee_calc_includes_tip() { + let rng = &mut StdRng::seed_from_u64(2322u64); + let gas_price = 1; + let mut tx = arb_tx(rng); + + // given + let tipless_tx = tx.clone(); + + let min_fee_without_tip = tipless_tx + .into_checked_basic(1.into(), &ConsensusParameters::standard(), gas_price) + .unwrap() + .metadata() + .fee + .min_fee(); + + let tip = 100; + + // when + tx.set_tip(tip); + + let min_fee_with_tip = tx + .into_checked_basic(1.into(), &ConsensusParameters::standard(), gas_price) + .unwrap() + .metadata() + .fee + .min_fee(); + + // then + assert_eq!(min_fee_without_tip + tip, min_fee_with_tip); + } + + #[test] + fn into_checked_basic__max_fee_calc_includes_tip() { + let rng = &mut StdRng::seed_from_u64(2322u64); + let gas_price = 1; + let mut tx = arb_tx(rng); + + // given + let tipless_tx = tx.clone(); + + let max_fee_without_tip = tipless_tx + .into_checked_basic(1.into(), &ConsensusParameters::standard(), gas_price) + .unwrap() + .metadata() + .fee + .max_fee(); + + let tip = 100; + + // when + tx.set_tip(tip); + + let max_fee_with_tip = tx + .into_checked_basic(1.into(), &ConsensusParameters::standard(), gas_price) + .unwrap() + .metadata() + .fee + .max_fee(); + + // then + assert_eq!(max_fee_without_tip + tip, max_fee_with_tip); + } + #[test] fn gas_fee_cant_overflow() { let rng = &mut StdRng::seed_from_u64(2322u64); @@ -1526,7 +1602,7 @@ mod tests { CheckError::Validity(ValidityError::InsufficientInputAmount { asset: any_asset, expected: input_amount + 1, - provided: input_amount + provided: input_amount, }), checked ); @@ -1563,7 +1639,7 @@ mod tests { .into_checked( block_height, &ConsensusParameters::standard_with_id(chain_id), - arb_gas_price + arb_gas_price, ) .unwrap() // Sets Checks::Signatures @@ -1596,11 +1672,11 @@ mod tests { .into_checked( block_height, &consensus_params, - arb_gas_price + arb_gas_price, ) .unwrap() // Sets Checks::Predicates - .check_predicates( &check_predicate_params) + .check_predicates(&check_predicate_params) .unwrap(); assert!(checked .checks() @@ -1655,7 +1731,9 @@ mod tests { .try_into() .map_err(|_| ValidityError::BalanceOverflow)?; - let result = max_fee == available_balances.fee.max_fee(); + let max_fee_with_tip = max_fee + tx.tip(); + + let result = max_fee_with_tip == available_balances.fee.max_fee(); Ok(result) } @@ -1701,7 +1779,9 @@ mod tests { .try_into() .map_err(|_| ValidityError::BalanceOverflow)?; - Ok(min_fee == available_balances.fee.min_fee()) + let min_fee_with_tip = min_fee + tx.tip(); + + Ok(min_fee_with_tip == available_balances.fee.min_fee()) } fn valid_coin_tx( @@ -1783,8 +1863,10 @@ mod tests { rng: &mut StdRng, gas_limit: u64, input_amount: u64, + tip: u64, ) -> Script { TransactionBuilder::script(vec![], vec![]) + .tip(tip) .script_gas_limit(gas_limit) .add_input(Input::message_coin_predicate( rng.gen(),