From 263eaad065d607d7af2d2c163c5090b8d73216c1 Mon Sep 17 00:00:00 2001 From: Leila Wang Date: Wed, 18 Dec 2024 09:38:16 +0000 Subject: [PATCH] feat: add priority fees to gas settings (#10763) Please read [contributing guidelines](CONTRIBUTING.md) and remove this line. --- .../vm/avm/trace/public_inputs.hpp | 2 + .../src/barretenberg/vm/avm/trace/trace.cpp | 12 ++- .../src/barretenberg/vm/aztec_constants.hpp | 2 +- .../src/core/libraries/ConstantsGen.sol | 20 ++-- .../tail_output_validator_builder/mod.nr | 1 + .../rollup-lib/src/base/components/mod.nr | 1 + .../compute_transaction_fee.nr | 97 +++++++++++++++++++ .../mod.nr | 5 + .../src/base/private_base_rollup.nr | 17 ++-- .../crates/types/src/abis/gas_settings.nr | 20 +++- .../crates/types/src/constants.nr | 5 +- .../types/src/transaction/tx_request.nr | 9 +- .../crates/types/src/utils/field.nr | 8 ++ .../src/contract/base_contract_interaction.ts | 3 +- yarn-project/circuits.js/src/constants.gen.ts | 22 ++--- yarn-project/circuits.js/src/fees/index.ts | 1 + .../src/fees/transaction_fee.test.ts | 67 +++++++++++++ .../circuits.js/src/fees/transaction_fee.ts | 17 ++++ yarn-project/circuits.js/src/index.ts | 1 + .../__snapshots__/tx_request.test.ts.snap | 2 +- .../circuits.js/src/structs/gas_settings.ts | 61 ++++++++---- .../src/structs/tx_request.test.ts | 2 +- yarn-project/cli-wallet/src/cmds/cancel_tx.ts | 19 +++- yarn-project/cli-wallet/src/cmds/index.ts | 21 +++- yarn-project/cli-wallet/src/cmds/send.ts | 2 +- .../cli-wallet/src/utils/options/fees.ts | 22 +++-- .../end-to-end/src/e2e_fees/failures.test.ts | 3 +- .../src/type_conversion.ts | 2 + .../simulator/src/public/fixtures/index.ts | 2 +- .../simulator/src/public/public_tx_context.ts | 8 +- .../src/public/public_tx_simulator.test.ts | 51 ++++++++-- 31 files changed, 416 insertions(+), 89 deletions(-) create mode 100644 noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/private_base_rollup_output_composer/compute_transaction_fee.nr create mode 100644 noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/private_base_rollup_output_composer/mod.nr create mode 100644 yarn-project/circuits.js/src/fees/index.ts create mode 100644 yarn-project/circuits.js/src/fees/transaction_fee.test.ts create mode 100644 yarn-project/circuits.js/src/fees/transaction_fee.ts diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/public_inputs.hpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/public_inputs.hpp index e7b0f4d1ffe..0f41c44fc99 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/public_inputs.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/public_inputs.hpp @@ -38,6 +38,7 @@ struct GasSettings { Gas gas_limits; Gas teardown_gas_limits; GasFees max_fees_per_gas; + GasFees max_priority_fees_per_gas; }; inline void read(uint8_t const*& it, GasSettings& gas_settings) @@ -46,6 +47,7 @@ inline void read(uint8_t const*& it, GasSettings& gas_settings) read(it, gas_settings.gas_limits); read(it, gas_settings.teardown_gas_limits); read(it, gas_settings.max_fees_per_gas); + read(it, gas_settings.max_priority_fees_per_gas); } struct GlobalVariables { diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp index 95a26c9cd0f..991f14b1b1f 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp @@ -259,8 +259,16 @@ void AvmTraceBuilder::pay_fee() { auto clk = static_cast(main_trace.size()) + 1; - auto tx_fee = (public_inputs.global_variables.gas_fees.fee_per_da_gas * public_inputs.end_gas_used.da_gas) + - (public_inputs.global_variables.gas_fees.fee_per_l2_gas * public_inputs.end_gas_used.l2_gas); + auto gas_settings = public_inputs.gas_settings; + auto gas_fees = public_inputs.global_variables.gas_fees; + auto priority_fee_per_da_gas = std::min(gas_settings.max_priority_fees_per_gas.fee_per_da_gas, + gas_settings.max_fees_per_gas.fee_per_da_gas - gas_fees.fee_per_da_gas); + auto priority_fee_per_l2_gas = std::min(gas_settings.max_priority_fees_per_gas.fee_per_l2_gas, + gas_settings.max_fees_per_gas.fee_per_l2_gas - gas_fees.fee_per_l2_gas); + auto total_fee_per_da_gas = gas_fees.fee_per_da_gas + priority_fee_per_da_gas; + auto total_fee_per_l2_gas = gas_fees.fee_per_l2_gas + priority_fee_per_l2_gas; + auto tx_fee = (total_fee_per_da_gas * public_inputs.end_gas_used.da_gas) + + (total_fee_per_l2_gas * public_inputs.end_gas_used.l2_gas); if (public_inputs.fee_payer == 0) { vinfo("No one is paying the fee of ", tx_fee); diff --git a/barretenberg/cpp/src/barretenberg/vm/aztec_constants.hpp b/barretenberg/cpp/src/barretenberg/vm/aztec_constants.hpp index 24d69635058..d180cb2508b 100644 --- a/barretenberg/cpp/src/barretenberg/vm/aztec_constants.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/aztec_constants.hpp @@ -46,7 +46,7 @@ #define TOTAL_FEES_LENGTH 1 #define PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH 867 #define AVM_ACCUMULATED_DATA_LENGTH 320 -#define AVM_CIRCUIT_PUBLIC_INPUTS_LENGTH 1009 +#define AVM_CIRCUIT_PUBLIC_INPUTS_LENGTH 1011 #define AVM_VERIFICATION_KEY_LENGTH_IN_FIELDS 86 #define AVM_PROOF_LENGTH_IN_FIELDS 4155 #define AVM_PUBLIC_COLUMN_MAX_SIZE 1024 diff --git a/l1-contracts/src/core/libraries/ConstantsGen.sol b/l1-contracts/src/core/libraries/ConstantsGen.sol index d68316c16df..6e274c79760 100644 --- a/l1-contracts/src/core/libraries/ConstantsGen.sol +++ b/l1-contracts/src/core/libraries/ConstantsGen.sol @@ -138,7 +138,7 @@ library Constants { uint256 internal constant AZTEC_ADDRESS_LENGTH = 1; uint256 internal constant GAS_FEES_LENGTH = 2; uint256 internal constant GAS_LENGTH = 2; - uint256 internal constant GAS_SETTINGS_LENGTH = 6; + uint256 internal constant GAS_SETTINGS_LENGTH = 8; uint256 internal constant CALL_CONTEXT_LENGTH = 4; uint256 internal constant CONTENT_COMMITMENT_LENGTH = 4; uint256 internal constant CONTRACT_INSTANCE_LENGTH = 16; @@ -180,30 +180,30 @@ library Constants { uint256 internal constant ROLLUP_VALIDATION_REQUESTS_LENGTH = 2; uint256 internal constant STATE_REFERENCE_LENGTH = 8; uint256 internal constant TREE_SNAPSHOTS_LENGTH = 8; - uint256 internal constant TX_CONTEXT_LENGTH = 8; - uint256 internal constant TX_REQUEST_LENGTH = 12; + uint256 internal constant TX_CONTEXT_LENGTH = 10; + uint256 internal constant TX_REQUEST_LENGTH = 14; uint256 internal constant TOTAL_FEES_LENGTH = 1; uint256 internal constant TOTAL_MANA_USED_LENGTH = 1; uint256 internal constant BLOCK_HEADER_LENGTH = 25; uint256 internal constant BLOCK_HEADER_LENGTH_BYTES = 648; - uint256 internal constant PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH = 739; + uint256 internal constant PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH = 741; uint256 internal constant PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH = 867; - uint256 internal constant PRIVATE_CONTEXT_INPUTS_LENGTH = 38; + uint256 internal constant PRIVATE_CONTEXT_INPUTS_LENGTH = 40; uint256 internal constant FEE_RECIPIENT_LENGTH = 2; uint256 internal constant AGGREGATION_OBJECT_LENGTH = 16; uint256 internal constant SCOPED_READ_REQUEST_LEN = 3; uint256 internal constant PUBLIC_DATA_READ_LENGTH = 3; uint256 internal constant PRIVATE_VALIDATION_REQUESTS_LENGTH = 772; uint256 internal constant COMBINED_ACCUMULATED_DATA_LENGTH = 902; - uint256 internal constant TX_CONSTANT_DATA_LENGTH = 35; - uint256 internal constant COMBINED_CONSTANT_DATA_LENGTH = 44; + uint256 internal constant TX_CONSTANT_DATA_LENGTH = 37; + uint256 internal constant COMBINED_CONSTANT_DATA_LENGTH = 46; uint256 internal constant PRIVATE_ACCUMULATED_DATA_LENGTH = 1412; - uint256 internal constant PRIVATE_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 2226; + uint256 internal constant PRIVATE_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 2229; uint256 internal constant PRIVATE_TO_PUBLIC_ACCUMULATED_DATA_LENGTH = 900; uint256 internal constant PRIVATE_TO_AVM_ACCUMULATED_DATA_LENGTH = 160; uint256 internal constant NUM_PRIVATE_TO_AVM_ACCUMULATED_DATA_ARRAYS = 3; - uint256 internal constant PRIVATE_TO_PUBLIC_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 1845; - uint256 internal constant KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 958; + uint256 internal constant PRIVATE_TO_PUBLIC_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 1847; + uint256 internal constant KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 960; uint256 internal constant CONSTANT_ROLLUP_DATA_LENGTH = 13; uint256 internal constant BASE_OR_MERGE_PUBLIC_INPUTS_LENGTH = 52; uint256 internal constant BLOCK_ROOT_OR_BLOCK_MERGE_PUBLIC_INPUTS_LENGTH = 986; diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/tests/tail_output_validator_builder/mod.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/tests/tail_output_validator_builder/mod.nr index c84cbeaab2b..e147656197e 100644 --- a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/tests/tail_output_validator_builder/mod.nr +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/tests/tail_output_validator_builder/mod.nr @@ -30,6 +30,7 @@ impl TailOutputValidatorBuilder { Gas::new(DEFAULT_GAS_LIMIT, DEFAULT_GAS_LIMIT), Gas::new(DEFAULT_TEARDOWN_GAS_LIMIT, DEFAULT_TEARDOWN_GAS_LIMIT), GasFees::new(10, 10), + GasFees::new(3, 3), ); let mut output = FixtureBuilder::new(); diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/mod.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/mod.nr index 128c7bed5bd..1831a1cc730 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/mod.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/mod.nr @@ -6,6 +6,7 @@ pub(crate) mod public_data_tree; mod private_tube_data_validator; mod public_tube_data_validator; +pub(crate) mod private_base_rollup_output_composer; pub(crate) mod validate_tube_data; pub(crate) use private_tube_data_validator::PrivateTubeDataValidator; diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/private_base_rollup_output_composer/compute_transaction_fee.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/private_base_rollup_output_composer/compute_transaction_fee.nr new file mode 100644 index 00000000000..67c1b9a5eb4 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/private_base_rollup_output_composer/compute_transaction_fee.nr @@ -0,0 +1,97 @@ +use types::abis::{gas::Gas, gas_fees::GasFees, gas_settings::GasSettings}; + +pub fn compute_transaction_fee( + gas_fees: GasFees, + gas_settings: GasSettings, + gas_used: Gas, +) -> Field { + let max_priority_fees = gas_settings.max_priority_fees_per_gas; + let max_fees = gas_settings.max_fees_per_gas; + // max_fees are guaranteed to be greater than or equal to gas_fees, which is checked in tube_data_validator. + let priority_fees = GasFees::new( + dep::types::utils::field::min( + max_priority_fees.fee_per_da_gas, + max_fees.fee_per_da_gas - gas_fees.fee_per_da_gas, + ), + dep::types::utils::field::min( + max_priority_fees.fee_per_l2_gas, + max_fees.fee_per_l2_gas - gas_fees.fee_per_l2_gas, + ), + ); + + let total_fees = GasFees::new( + gas_fees.fee_per_da_gas + priority_fees.fee_per_da_gas, + gas_fees.fee_per_l2_gas + priority_fees.fee_per_l2_gas, + ); + + gas_used.compute_fee(total_fees) +} + +mod tests { + use super::compute_transaction_fee; + use types::abis::{gas::Gas, gas_fees::GasFees, gas_settings::GasSettings}; + + struct TestBuilder { + gas_fees: GasFees, + gas_settings: GasSettings, + gas_used: Gas, + } + + impl TestBuilder { + pub fn new() -> Self { + let gas_fees = GasFees::new(12, 34); + + let mut gas_settings = GasSettings::empty(); + gas_settings.max_fees_per_gas = GasFees::new(56, 78); + gas_settings.max_priority_fees_per_gas = GasFees::new(5, 7); + + let gas_used = Gas::new(2, 3); + + TestBuilder { gas_fees, gas_settings, gas_used } + } + + pub fn compute(self) -> Field { + compute_transaction_fee(self.gas_fees, self.gas_settings, self.gas_used) + } + } + + #[test] + fn compute_transaction_fee_default() { + let builder = TestBuilder::new(); + let fee = builder.compute(); + + // Make sure the following value matches the one in `transaction_fee.test.ts` in `circuits.js`. + // Paying gas_fees + max_priority_fees. + let expected_fee = 2 * (12 + 5) + 3 * (34 + 7); + assert_eq(fee, expected_fee); + } + + #[test] + fn compute_transaction_fee_zero_priority_fees() { + let mut builder = TestBuilder::new(); + + builder.gas_settings.max_priority_fees_per_gas = GasFees::empty(); + + let fee = builder.compute(); + + // Make sure the following value matches the one in `transaction_fee.test.ts` in `circuits.js`. + // Paying gas_fees only. + let expected_fee_empty_priority = 2 * 12 + 3 * 34; + assert_eq(fee, expected_fee_empty_priority); + } + + #[test] + fn compute_transaction_fee_capped_max_fees() { + let mut builder = TestBuilder::new(); + + // Increase gas_fees so that gas_fees + max_priority_fees > max_fees. + builder.gas_fees = GasFees::new(53, 74); + + let fee = builder.compute(); + + // Make sure the following value matches the one in `transaction_fee.test.ts` in `circuits.js`. + // max_fees were applied. + let expected_max_fee = 2 * 56 + 3 * 78; + assert_eq(fee, expected_max_fee); + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/private_base_rollup_output_composer/mod.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/private_base_rollup_output_composer/mod.nr new file mode 100644 index 00000000000..83e15e4b4ee --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/private_base_rollup_output_composer/mod.nr @@ -0,0 +1,5 @@ +mod compute_transaction_fee; + +pub use compute_transaction_fee::compute_transaction_fee; + +// TODO: Move everything that composes the output of private base rollup to this component. diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/private_base_rollup.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/private_base_rollup.nr index 06bdc554757..d27dcd2b467 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/private_base_rollup.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/private_base_rollup.nr @@ -4,7 +4,8 @@ use crate::{ components::{ archive::perform_archive_membership_check, constants::validate_combined_constant_data, fees::compute_fee_payer_fee_juice_balance_leaf_slot, - nullifier_tree::nullifier_tree_batch_insert, PrivateTubeDataValidator, + nullifier_tree::nullifier_tree_batch_insert, + private_base_rollup_output_composer::compute_transaction_fee, PrivateTubeDataValidator, public_data_tree::public_data_tree_insert, }, state_diff_hints::PrivateBaseStateDiffHints, @@ -47,12 +48,6 @@ pub struct PrivateBaseRollupInputs { } impl PrivateBaseRollupInputs { - fn compute_transaction_fee(self) -> Field { - let gas_fees = self.constants.global_variables.gas_fees; - let gas_used = self.tube_data.public_inputs.gas_used; - gas_used.compute_fee(gas_fees) - } - pub fn execute(self) -> BaseOrMergeRollupPublicInputs { // TODO(#10311): There should be an empty base rollup. // There's at least 1 nullifier for a tx. So gas_used won't be empty if the previous circuit is private_tail. @@ -64,8 +59,6 @@ impl PrivateBaseRollupInputs { tube_data_validator.validate_with_rollup_data(self.constants); } - let transaction_fee = self.compute_transaction_fee(); - validate_combined_constant_data(self.tube_data.public_inputs.constants, self.constants); self.validate_kernel_start_state(); @@ -98,6 +91,12 @@ impl PrivateBaseRollupInputs { let end_nullifier_tree_snapshot = self.check_nullifier_tree_non_membership_and_insert_to_tree(); + let transaction_fee = compute_transaction_fee( + self.constants.global_variables.gas_fees, + self.tube_data.public_inputs.constants.tx_context.gas_settings, + self.tube_data.public_inputs.gas_used, + ); + // Write fee to public data tree let fee_public_data_write = self.build_fee_public_data_write(transaction_fee); let end_public_data_tree_snapshot = diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/gas_settings.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/gas_settings.nr index 42301eb096d..7957367b52c 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/gas_settings.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/gas_settings.nr @@ -9,11 +9,17 @@ pub struct GasSettings { pub gas_limits: Gas, pub teardown_gas_limits: Gas, pub max_fees_per_gas: GasFees, + pub max_priority_fees_per_gas: GasFees, } impl GasSettings { - pub fn new(gas_limits: Gas, teardown_gas_limits: Gas, max_fees_per_gas: GasFees) -> Self { - Self { gas_limits, teardown_gas_limits, max_fees_per_gas } + pub fn new( + gas_limits: Gas, + teardown_gas_limits: Gas, + max_fees_per_gas: GasFees, + max_priority_fees_per_gas: GasFees, + ) -> Self { + Self { gas_limits, teardown_gas_limits, max_fees_per_gas, max_priority_fees_per_gas } } } @@ -22,12 +28,18 @@ impl Eq for GasSettings { (self.gas_limits == other.gas_limits) & (self.teardown_gas_limits == other.teardown_gas_limits) & (self.max_fees_per_gas == other.max_fees_per_gas) + & (self.max_priority_fees_per_gas == other.max_priority_fees_per_gas) } } impl Empty for GasSettings { fn empty() -> Self { - GasSettings::new(Gas::empty(), Gas::empty(), GasFees::empty()) + GasSettings::new( + Gas::empty(), + Gas::empty(), + GasFees::empty(), + GasFees::empty(), + ) } } @@ -38,6 +50,7 @@ impl Serialize for GasSettings { serialized.extend_from_array(self.gas_limits.serialize()); serialized.extend_from_array(self.teardown_gas_limits.serialize()); serialized.extend_from_array(self.max_fees_per_gas.serialize()); + serialized.extend_from_array(self.max_priority_fees_per_gas.serialize()); serialized.storage() } @@ -50,6 +63,7 @@ impl Deserialize for GasSettings { reader.read_struct(Gas::deserialize), reader.read_struct(Gas::deserialize), reader.read_struct(GasFees::deserialize), + reader.read_struct(GasFees::deserialize), ) } } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr index 7586a274d62..12a824936fc 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr @@ -209,7 +209,10 @@ pub global DEFAULT_TPK_M_Y: Field = pub global AZTEC_ADDRESS_LENGTH: u32 = 1; pub global GAS_FEES_LENGTH: u32 = 2; pub global GAS_LENGTH: u32 = 2; -pub global GAS_SETTINGS_LENGTH: u32 = GAS_LENGTH * 2 + GAS_FEES_LENGTH; +pub global GAS_SETTINGS_LENGTH: u32 = GAS_LENGTH /* gas_limits */ + + GAS_LENGTH /* teardown_gas_limits */ + + GAS_FEES_LENGTH /* max_fees_per_gas */ + + GAS_FEES_LENGTH /* max_priority_fees_per_gas */; pub global CALL_CONTEXT_LENGTH: u32 = 4; pub global CONTENT_COMMITMENT_LENGTH: u32 = 4; pub global CONTRACT_INSTANCE_LENGTH: u32 = 16; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/transaction/tx_request.nr b/noir-projects/noir-protocol-circuits/crates/types/src/transaction/tx_request.nr index 2d86849101f..92344807691 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/transaction/tx_request.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/transaction/tx_request.nr @@ -93,7 +93,12 @@ mod tests { #[test] fn compute_hash() { - let gas_settings = GasSettings::new(Gas::new(2, 2), Gas::new(1, 1), GasFees::new(3, 3)); + let gas_settings = GasSettings::new( + Gas::new(2, 2), + Gas::new(1, 1), + GasFees::new(4, 4), + GasFees::new(3, 3), + ); let tx_request = TxRequest { origin: AztecAddress::from_field(1), args_hash: 3, @@ -105,7 +110,7 @@ mod tests { }; // Value from tx_request.test.ts "compute hash" test let test_data_tx_request_hash = - 0x1b15ac8432c3c35718075a277d86f11263f057e2325afa208d6b19e37ff59a8d; + 0x2d265ee0e3b9d206873a66527485afa3c6ebbfbaf451811666c9558a9f6e3d46; assert(tx_request.hash() == test_data_tx_request_hash); } } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/field.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/field.nr index 796d0906e77..91ddbe290b7 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/field.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/field.nr @@ -43,6 +43,14 @@ pub fn full_field_greater_than(lhs: Field, rhs: Field) -> bool { rhs.lt(lhs) } +pub fn min(f1: Field, f2: Field) -> Field { + if f1.lt(f2) { + f1 + } else { + f2 + } +} + #[test] unconstrained fn bytes_field_test() { // Tests correctness of field_from_bytes_32_trunc against existing methods diff --git a/yarn-project/aztec.js/src/contract/base_contract_interaction.ts b/yarn-project/aztec.js/src/contract/base_contract_interaction.ts index 8ab2f2c91bd..ad8353d0d6c 100644 --- a/yarn-project/aztec.js/src/contract/base_contract_interaction.ts +++ b/yarn-project/aztec.js/src/contract/base_contract_interaction.ts @@ -122,6 +122,7 @@ export abstract class BaseContractInteraction { const defaultFeeOptions = await this.getDefaultFeeOptions(request.fee); const paymentMethod = defaultFeeOptions.paymentMethod; const maxFeesPerGas = defaultFeeOptions.gasSettings.maxFeesPerGas; + const maxPriorityFeesPerGas = defaultFeeOptions.gasSettings.maxPriorityFeesPerGas; let gasSettings = defaultFeeOptions.gasSettings; if (request.fee?.estimateGas) { @@ -132,7 +133,7 @@ export abstract class BaseContractInteraction { simulationResult, request.fee?.estimatedGasPadding, ); - gasSettings = GasSettings.from({ maxFeesPerGas, gasLimits, teardownGasLimits }); + gasSettings = GasSettings.from({ maxFeesPerGas, maxPriorityFeesPerGas, gasLimits, teardownGasLimits }); this.log.verbose( `Estimated gas limits for tx: DA=${gasLimits.daGas} L2=${gasLimits.l2Gas} teardownDA=${teardownGasLimits.daGas} teardownL2=${teardownGasLimits.l2Gas}`, ); diff --git a/yarn-project/circuits.js/src/constants.gen.ts b/yarn-project/circuits.js/src/constants.gen.ts index d3404675396..ac7e24b713c 100644 --- a/yarn-project/circuits.js/src/constants.gen.ts +++ b/yarn-project/circuits.js/src/constants.gen.ts @@ -116,7 +116,7 @@ export const DEFAULT_TPK_M_Y = 1457571873670220605010242502922942621563166447116 export const AZTEC_ADDRESS_LENGTH = 1; export const GAS_FEES_LENGTH = 2; export const GAS_LENGTH = 2; -export const GAS_SETTINGS_LENGTH = 6; +export const GAS_SETTINGS_LENGTH = 8; export const CALL_CONTEXT_LENGTH = 4; export const CONTENT_COMMITMENT_LENGTH = 4; export const CONTRACT_INSTANCE_LENGTH = 16; @@ -158,32 +158,32 @@ export const PUBLIC_INNER_CALL_REQUEST_LENGTH = 13; export const ROLLUP_VALIDATION_REQUESTS_LENGTH = 2; export const STATE_REFERENCE_LENGTH = 8; export const TREE_SNAPSHOTS_LENGTH = 8; -export const TX_CONTEXT_LENGTH = 8; -export const TX_REQUEST_LENGTH = 12; +export const TX_CONTEXT_LENGTH = 10; +export const TX_REQUEST_LENGTH = 14; export const TOTAL_FEES_LENGTH = 1; export const TOTAL_MANA_USED_LENGTH = 1; export const BLOCK_HEADER_LENGTH = 25; export const BLOCK_HEADER_LENGTH_BYTES = 648; -export const PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH = 739; +export const PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH = 741; export const PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH = 867; -export const PRIVATE_CONTEXT_INPUTS_LENGTH = 38; +export const PRIVATE_CONTEXT_INPUTS_LENGTH = 40; export const FEE_RECIPIENT_LENGTH = 2; export const AGGREGATION_OBJECT_LENGTH = 16; export const SCOPED_READ_REQUEST_LEN = 3; export const PUBLIC_DATA_READ_LENGTH = 3; export const PRIVATE_VALIDATION_REQUESTS_LENGTH = 772; export const COMBINED_ACCUMULATED_DATA_LENGTH = 902; -export const TX_CONSTANT_DATA_LENGTH = 35; -export const COMBINED_CONSTANT_DATA_LENGTH = 44; +export const TX_CONSTANT_DATA_LENGTH = 37; +export const COMBINED_CONSTANT_DATA_LENGTH = 46; export const PRIVATE_ACCUMULATED_DATA_LENGTH = 1412; -export const PRIVATE_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 2226; +export const PRIVATE_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 2229; export const PRIVATE_TO_PUBLIC_ACCUMULATED_DATA_LENGTH = 900; export const PRIVATE_TO_AVM_ACCUMULATED_DATA_LENGTH = 160; export const NUM_PRIVATE_TO_AVM_ACCUMULATED_DATA_ARRAYS = 3; export const AVM_ACCUMULATED_DATA_LENGTH = 320; -export const PRIVATE_TO_PUBLIC_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 1845; -export const KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 958; -export const AVM_CIRCUIT_PUBLIC_INPUTS_LENGTH = 1009; +export const PRIVATE_TO_PUBLIC_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 1847; +export const KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 960; +export const AVM_CIRCUIT_PUBLIC_INPUTS_LENGTH = 1011; export const CONSTANT_ROLLUP_DATA_LENGTH = 13; export const BASE_OR_MERGE_PUBLIC_INPUTS_LENGTH = 52; export const BLOCK_ROOT_OR_BLOCK_MERGE_PUBLIC_INPUTS_LENGTH = 986; diff --git a/yarn-project/circuits.js/src/fees/index.ts b/yarn-project/circuits.js/src/fees/index.ts new file mode 100644 index 00000000000..99ed046cf47 --- /dev/null +++ b/yarn-project/circuits.js/src/fees/index.ts @@ -0,0 +1 @@ +export * from './transaction_fee.js'; diff --git a/yarn-project/circuits.js/src/fees/transaction_fee.test.ts b/yarn-project/circuits.js/src/fees/transaction_fee.test.ts new file mode 100644 index 00000000000..b3c344601e1 --- /dev/null +++ b/yarn-project/circuits.js/src/fees/transaction_fee.test.ts @@ -0,0 +1,67 @@ +import { updateInlineTestData } from '@aztec/foundation/testing/files'; +import { type Writeable } from '@aztec/foundation/types'; + +import { Fr, Gas, GasFees, GasSettings } from '../structs/index.js'; +import { computeTransactionFee } from './transaction_fee.js'; + +describe('computeTransactionFee', () => { + let gasFees: GasFees; + let gasSettings: Writeable; + let gasUsed: Gas; + + const expectFee = (feeStr: string) => { + const fee = computeTransactionFee(gasFees, gasSettings, gasUsed); + expect(fee).toEqual(new Fr(BigInt(eval(feeStr)))); + }; + + beforeEach(() => { + gasFees = new GasFees(12, 34); + + const maxFeesPerGas = new GasFees(56, 78); + const maxPriorityFeesPerGas = new GasFees(5, 7); + gasSettings = GasSettings.from({ ...GasSettings.empty(), maxFeesPerGas, maxPriorityFeesPerGas }); + + gasUsed = new Gas(2, 3); + }); + + it('computes the transaction fee with priority fee', () => { + const feeStr = '2 * (12 + 5) + 3 * (34 + 7)'; + expectFee(feeStr); + + // Run with AZTEC_GENERATE_TEST_DATA=1 to update noir test data + updateInlineTestData( + 'noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/private_base_rollup_output_composer/compute_transaction_fee.nr', + 'expected_fee', + feeStr, + ); + }); + + it('computes the transaction fee without priority fee', () => { + gasSettings.maxPriorityFeesPerGas = GasFees.empty(); + + const feeStr = '2 * 12 + 3 * 34'; + expectFee(feeStr); + + // Run with AZTEC_GENERATE_TEST_DATA=1 to update noir test data + updateInlineTestData( + 'noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/private_base_rollup_output_composer/compute_transaction_fee.nr', + 'expected_fee_empty_priority', + feeStr, + ); + }); + + it('computes the transaction fee paying max fee', () => { + // Increase gas_fees so that gasFees + maxPriorityFeesPerGas > maxFeesPerGas. + gasFees = new GasFees(53, 74); + + const feeStr = '2 * 56 + 3 * 78'; + expectFee(feeStr); + + // Run with AZTEC_GENERATE_TEST_DATA=1 to update noir test data + updateInlineTestData( + 'noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/private_base_rollup_output_composer/compute_transaction_fee.nr', + 'expected_max_fee', + feeStr, + ); + }); +}); diff --git a/yarn-project/circuits.js/src/fees/transaction_fee.ts b/yarn-project/circuits.js/src/fees/transaction_fee.ts new file mode 100644 index 00000000000..017275091fe --- /dev/null +++ b/yarn-project/circuits.js/src/fees/transaction_fee.ts @@ -0,0 +1,17 @@ +import { type Fr, type Gas, GasFees, type GasSettings } from '../structs/index.js'; + +export function computeTransactionFee(gasFees: GasFees, gasSettings: GasSettings, gasUsed: Gas): Fr { + const { maxFeesPerGas, maxPriorityFeesPerGas } = gasSettings; + const minField = (f1: Fr, f2: Fr) => (f1.lt(f2) ? f1 : f2); + const priorityFees = new GasFees( + minField(maxPriorityFeesPerGas.feePerDaGas, maxFeesPerGas.feePerDaGas.sub(gasFees.feePerDaGas)), + minField(maxPriorityFeesPerGas.feePerL2Gas, maxFeesPerGas.feePerL2Gas.sub(gasFees.feePerL2Gas)), + ); + + const totalFees = new GasFees( + gasFees.feePerDaGas.add(priorityFees.feePerDaGas), + gasFees.feePerL2Gas.add(priorityFees.feePerL2Gas), + ); + + return gasUsed.computeFee(totalFees); +} diff --git a/yarn-project/circuits.js/src/index.ts b/yarn-project/circuits.js/src/index.ts index 9ed5971c96f..e4c04ed31fa 100644 --- a/yarn-project/circuits.js/src/index.ts +++ b/yarn-project/circuits.js/src/index.ts @@ -1,5 +1,6 @@ export * from './constants.js'; export * from './contract/index.js'; +export * from './fees/index.js'; export * from './hints/index.js'; export * from './interfaces/index.js'; export * from './keys/index.js'; diff --git a/yarn-project/circuits.js/src/structs/__snapshots__/tx_request.test.ts.snap b/yarn-project/circuits.js/src/structs/__snapshots__/tx_request.test.ts.snap index 89e87fba81d..73f68740498 100644 --- a/yarn-project/circuits.js/src/structs/__snapshots__/tx_request.test.ts.snap +++ b/yarn-project/circuits.js/src/structs/__snapshots__/tx_request.test.ts.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`TxRequest compute hash 1`] = `"0x1b15ac8432c3c35718075a277d86f11263f057e2325afa208d6b19e37ff59a8d"`; +exports[`TxRequest compute hash 1`] = `"0x2d265ee0e3b9d206873a66527485afa3c6ebbfbaf451811666c9558a9f6e3d46"`; diff --git a/yarn-project/circuits.js/src/structs/gas_settings.ts b/yarn-project/circuits.js/src/structs/gas_settings.ts index c5449a7ac9d..e432c8a8f8a 100644 --- a/yarn-project/circuits.js/src/structs/gas_settings.ts +++ b/yarn-project/circuits.js/src/structs/gas_settings.ts @@ -14,6 +14,7 @@ export class GasSettings { public readonly gasLimits: Gas, public readonly teardownGasLimits: Gas, public readonly maxFeesPerGas: GasFees, + public readonly maxPriorityFeesPerGas: GasFees, ) {} static get schema() { @@ -22,6 +23,7 @@ export class GasSettings { gasLimits: Gas.schema, teardownGasLimits: Gas.schema, maxFeesPerGas: GasFees.schema, + maxPriorityFeesPerGas: GasFees.schema, }) .transform(GasSettings.from); } @@ -30,16 +32,27 @@ export class GasSettings { return this.toBuffer().length; } - static from(args: { gasLimits: FieldsOf; teardownGasLimits: FieldsOf; maxFeesPerGas: FieldsOf }) { + static from(args: { + gasLimits: FieldsOf; + teardownGasLimits: FieldsOf; + maxFeesPerGas: FieldsOf; + maxPriorityFeesPerGas: FieldsOf; + }) { return new GasSettings( Gas.from(args.gasLimits), Gas.from(args.teardownGasLimits), GasFees.from(args.maxFeesPerGas), + GasFees.from(args.maxPriorityFeesPerGas), ); } clone() { - return new GasSettings(this.gasLimits.clone(), this.teardownGasLimits.clone(), this.maxFeesPerGas.clone()); + return new GasSettings( + this.gasLimits.clone(), + this.teardownGasLimits.clone(), + this.maxFeesPerGas.clone(), + this.maxPriorityFeesPerGas.clone(), + ); } /** Returns the maximum fee to be paid according to gas limits and max fees set. */ @@ -56,11 +69,16 @@ export class GasSettings { /** Zero-value gas settings. */ static empty() { - return new GasSettings(Gas.empty(), Gas.empty(), GasFees.empty()); + return new GasSettings(Gas.empty(), Gas.empty(), GasFees.empty(), GasFees.empty()); } /** Default gas settings to use when user has not provided them. Requires explicit max fees per gas. */ - static default(overrides: { gasLimits?: Gas; teardownGasLimits?: Gas; maxFeesPerGas: GasFees }) { + static default(overrides: { + gasLimits?: Gas; + teardownGasLimits?: Gas; + maxFeesPerGas: GasFees; + maxPriorityFeesPerGas?: GasFees; + }) { return GasSettings.from({ gasLimits: overrides.gasLimits ?? { l2Gas: DEFAULT_GAS_LIMIT, daGas: DEFAULT_GAS_LIMIT }, teardownGasLimits: overrides.teardownGasLimits ?? { @@ -68,32 +86,36 @@ export class GasSettings { daGas: DEFAULT_TEARDOWN_GAS_LIMIT, }, maxFeesPerGas: overrides.maxFeesPerGas, - }); - } - - /** Default gas settings with no teardown */ - static teardownless(opts: { maxFeesPerGas: GasFees }) { - return GasSettings.default({ - teardownGasLimits: Gas.from({ l2Gas: 0, daGas: 0 }), - maxFeesPerGas: opts.maxFeesPerGas, + maxPriorityFeesPerGas: overrides.maxPriorityFeesPerGas ?? GasFees.empty(), }); } isEmpty() { - return this.gasLimits.isEmpty() && this.teardownGasLimits.isEmpty() && this.maxFeesPerGas.isEmpty(); + return ( + this.gasLimits.isEmpty() && + this.teardownGasLimits.isEmpty() && + this.maxFeesPerGas.isEmpty() && + this.maxPriorityFeesPerGas.isEmpty() + ); } equals(other: GasSettings) { return ( this.gasLimits.equals(other.gasLimits) && this.teardownGasLimits.equals(other.teardownGasLimits) && - this.maxFeesPerGas.equals(other.maxFeesPerGas) + this.maxFeesPerGas.equals(other.maxFeesPerGas) && + this.maxPriorityFeesPerGas.equals(other.maxPriorityFeesPerGas) ); } static fromBuffer(buffer: Buffer | BufferReader): GasSettings { const reader = BufferReader.asReader(buffer); - return new GasSettings(reader.readObject(Gas), reader.readObject(Gas), reader.readObject(GasFees)); + return new GasSettings( + reader.readObject(Gas), + reader.readObject(Gas), + reader.readObject(GasFees), + reader.readObject(GasFees), + ); } toBuffer() { @@ -102,7 +124,12 @@ export class GasSettings { static fromFields(fields: Fr[] | FieldReader): GasSettings { const reader = FieldReader.asReader(fields); - return new GasSettings(reader.readObject(Gas), reader.readObject(Gas), reader.readObject(GasFees)); + return new GasSettings( + reader.readObject(Gas), + reader.readObject(Gas), + reader.readObject(GasFees), + reader.readObject(GasFees), + ); } toFields(): Fr[] { @@ -116,6 +143,6 @@ export class GasSettings { } static getFields(fields: FieldsOf) { - return [fields.gasLimits, fields.teardownGasLimits, fields.maxFeesPerGas] as const; + return [fields.gasLimits, fields.teardownGasLimits, fields.maxFeesPerGas, fields.maxPriorityFeesPerGas] as const; } } diff --git a/yarn-project/circuits.js/src/structs/tx_request.test.ts b/yarn-project/circuits.js/src/structs/tx_request.test.ts index 723e322817e..fec6fab23b7 100644 --- a/yarn-project/circuits.js/src/structs/tx_request.test.ts +++ b/yarn-project/circuits.js/src/structs/tx_request.test.ts @@ -35,7 +35,7 @@ describe('TxRequest', () => { }); it('compute hash', () => { - const gasSettings = new GasSettings(new Gas(2, 2), new Gas(1, 1), new GasFees(3, 3)); + const gasSettings = new GasSettings(new Gas(2, 2), new Gas(1, 1), new GasFees(4, 4), new GasFees(3, 3)); const txRequest = TxRequest.from({ origin: AztecAddress.fromBigInt(1n), functionData: new FunctionData(FunctionSelector.fromField(new Fr(2n)), /*isPrivate=*/ true), diff --git a/yarn-project/cli-wallet/src/cmds/cancel_tx.ts b/yarn-project/cli-wallet/src/cmds/cancel_tx.ts index 2d6ae924075..222fb7501cb 100644 --- a/yarn-project/cli-wallet/src/cmds/cancel_tx.ts +++ b/yarn-project/cli-wallet/src/cmds/cancel_tx.ts @@ -1,17 +1,19 @@ import { type AccountWalletWithSecretKey, type FeePaymentMethod, SentTx, type TxHash, TxStatus } from '@aztec/aztec.js'; import { type FeeOptions } from '@aztec/aztec.js/entrypoint'; -import { type Fr, type GasSettings } from '@aztec/circuits.js'; +import { type Fr, GasFees, GasSettings } from '@aztec/circuits.js'; import { type LogFn } from '@aztec/foundation/log'; export async function cancelTx( wallet: AccountWalletWithSecretKey, { txHash, - gasSettings, + gasSettings: prevTxGasSettings, nonce, cancellable, }: { txHash: TxHash; gasSettings: GasSettings; nonce: Fr; cancellable: boolean }, paymentMethod: FeePaymentMethod, + increasedFees: GasFees, + maxFeesPerGas: GasFees | undefined, log: LogFn, ) { const receipt = await wallet.getTxReceipt(txHash); @@ -20,13 +22,20 @@ export async function cancelTx( return; } + const maxPriorityFeesPerGas = new GasFees( + prevTxGasSettings.maxPriorityFeesPerGas.feePerDaGas.add(increasedFees.feePerDaGas), + prevTxGasSettings.maxPriorityFeesPerGas.feePerL2Gas.add(increasedFees.feePerL2Gas), + ); + const fee: FeeOptions = { paymentMethod, - gasSettings, + gasSettings: GasSettings.from({ + ...prevTxGasSettings, + maxPriorityFeesPerGas, + maxFeesPerGas: maxFeesPerGas ?? prevTxGasSettings.maxFeesPerGas, + }), }; - // TODO(#9805): Increase max_priority_fee_per_gas. - const txRequest = await wallet.createTxExecutionRequest({ calls: [], fee, diff --git a/yarn-project/cli-wallet/src/cmds/index.ts b/yarn-project/cli-wallet/src/cmds/index.ts index 41f280451cd..1915e080af7 100644 --- a/yarn-project/cli-wallet/src/cmds/index.ts +++ b/yarn-project/cli-wallet/src/cmds/index.ts @@ -1,6 +1,7 @@ import { getIdentities } from '@aztec/accounts/utils'; import { createCompatibleClient } from '@aztec/aztec.js/rpc'; import { TxHash } from '@aztec/aztec.js/tx_hash'; +import { GasFees } from '@aztec/circuits.js'; import { PublicKeys } from '@aztec/circuits.js/types'; import { ETHEREUM_HOST, @@ -38,6 +39,7 @@ import { createProfileOption, createTypeOption, integerArgParser, + parseGasFees, parsePaymentMethod, } from '../utils/options/index.js'; import { type PXEWrapper } from '../utils/pxe_wrapper.js'; @@ -580,7 +582,7 @@ export function injectCommands( program .command('cancel-tx') - .description('Cancels a peding tx by reusing its nonce with a higher fee and an empty payload') + .description('Cancels a pending tx by reusing its nonce with a higher fee and an empty payload') .argument('', 'A transaction hash to cancel.', txHash => aliasedTxHashParser(txHash, db)) .addOption(pxeOption) .addOption( @@ -588,21 +590,30 @@ export function injectCommands( ) .addOption(createAccountOption('Alias or address of the account to simulate from', !db, db)) .addOption(FeeOpts.paymentMethodOption().default('method=none')) + .option( + '-i --increased-fees ', + 'The amounts by which the fees are increased', + value => parseGasFees(value), + new GasFees(1, 1), + ) + .option('--max-fees-per-gas ', 'Maximum fees per gas unit for DA and L2 computation.', value => + parseGasFees(value), + ) .action(async (txHash, options) => { const { cancelTx } = await import('./cancel_tx.js'); - const { from: parsedFromAddress, rpcUrl, secretKey, payment } = options; + const { from: parsedFromAddress, rpcUrl, secretKey, payment, increasedFees, maxFeesPerGas } = options; const client = pxeWrapper?.getPXE() ?? (await createCompatibleClient(rpcUrl, debugLogger)); const account = await createOrRetrieveAccount(client, parsedFromAddress, db, secretKey); const wallet = await getWalletWithScopes(account, db); const txData = db?.retrieveTxData(txHash); - if (!txData) { - throw new Error('Transaction data not found in the database, cannnot reuse nonce'); + throw new Error('Transaction data not found in the database, cannot reuse nonce'); } + const paymentMethod = await parsePaymentMethod(payment, log, db)(wallet); - await cancelTx(wallet, txData, paymentMethod, log); + await cancelTx(wallet, txData, paymentMethod, increasedFees, maxFeesPerGas, log); }); program diff --git a/yarn-project/cli-wallet/src/cmds/send.ts b/yarn-project/cli-wallet/src/cmds/send.ts index b87097ec125..5fcdfc3a3ba 100644 --- a/yarn-project/cli-wallet/src/cmds/send.ts +++ b/yarn-project/cli-wallet/src/cmds/send.ts @@ -50,8 +50,8 @@ export async function send( log('Transaction pending. Check status with check-tx'); } const gasSettings = GasSettings.from({ + ...feeOpts.gasSettings, ...gasLimits, - maxFeesPerGas: feeOpts.gasSettings.maxFeesPerGas, }); return { txHash, diff --git a/yarn-project/cli-wallet/src/utils/options/fees.ts b/yarn-project/cli-wallet/src/utils/options/fees.ts index f0852956a22..829a2c8c67b 100644 --- a/yarn-project/cli-wallet/src/utils/options/fees.ts +++ b/yarn-project/cli-wallet/src/utils/options/fees.ts @@ -12,6 +12,7 @@ export type CliFeeArgs = { gasLimits?: string; payment?: string; maxFeesPerGas?: string; + maxPriorityFeesPerGas?: string; estimateGas?: boolean; }; @@ -69,7 +70,11 @@ export class FeeOpts implements IFeeOpts { return [ new Option('--gas-limits ', 'Gas limits for the tx.'), FeeOpts.paymentMethodOption(), - new Option('--max-fee-per-gas ', 'Maximum fee per gas unit for DA and L2 computation.'), + new Option('--max-fees-per-gas ', 'Maximum fees per gas unit for DA and L2 computation.'), + new Option( + '--max-priority-fees-per-gas ', + 'Maximum priority fees per gas unit for DA and L2 computation.', + ), new Option('--no-estimate-gas', 'Whether to automatically estimate gas limits for the tx.'), new Option('--estimate-gas-only', 'Only report gas estimation for the tx, do not send it.'), ]; @@ -77,12 +82,13 @@ export class FeeOpts implements IFeeOpts { static async fromCli(args: CliFeeArgs, pxe: PXE, log: LogFn, db?: WalletDB) { const estimateOnly = args.estimateGasOnly; - const gasFees = args.maxFeesPerGas - ? parseGasFees(args.maxFeesPerGas) - : { maxFeesPerGas: await pxe.getCurrentBaseFees() }; + const gasLimits = args.gasLimits ? parseGasLimits(args.gasLimits) : {}; + const maxFeesPerGas = args.maxFeesPerGas ? parseGasFees(args.maxFeesPerGas) : await pxe.getCurrentBaseFees(); + const maxPriorityFeesPerGas = args.maxPriorityFeesPerGas ? parseGasFees(args.maxPriorityFeesPerGas) : undefined; const gasSettings = GasSettings.default({ - ...gasFees, - ...(args.gasLimits ? parseGasLimits(args.gasLimits) : {}), + ...gasLimits, + maxFeesPerGas, + maxPriorityFeesPerGas, }); if (!args.gasLimits && !args.payment) { @@ -212,7 +218,7 @@ function parseGasLimits(gasLimits: string): { gasLimits: Gas; teardownGasLimits: }; } -function parseGasFees(gasFees: string): { maxFeesPerGas: GasFees } { +export function parseGasFees(gasFees: string): GasFees { const parsed = gasFees.split(',').reduce((acc, fee) => { const [dimension, value] = fee.split('='); acc[dimension] = parseInt(value, 10); @@ -226,5 +232,5 @@ function parseGasFees(gasFees: string): { maxFeesPerGas: GasFees } { } } - return { maxFeesPerGas: new GasFees(parsed.da, parsed.l2) }; + return new GasFees(parsed.da, parsed.l2); } diff --git a/yarn-project/end-to-end/src/e2e_fees/failures.test.ts b/yarn-project/end-to-end/src/e2e_fees/failures.test.ts index 26ce82243ed..7efc18bc661 100644 --- a/yarn-project/end-to-end/src/e2e_fees/failures.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/failures.test.ts @@ -262,8 +262,7 @@ describe('e2e_fees failures', () => { await bananaCoin.methods.mint_to_public(aliceAddress, publicMintedAlicePublicBananas).send().wait(); const badGas = GasSettings.from({ - gasLimits: gasSettings.gasLimits, - maxFeesPerGas: gasSettings.maxFeesPerGas, + ...gasSettings, teardownGasLimits: Gas.empty(), }); diff --git a/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts b/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts index feab75d5767..e6dcafdeee5 100644 --- a/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts +++ b/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts @@ -524,6 +524,7 @@ export function mapGasSettingsFromNoir(gasSettings: GasSettingsNoir): GasSetting mapGasFromNoir(gasSettings.gas_limits), mapGasFromNoir(gasSettings.teardown_gas_limits), mapGasFeesFromNoir(gasSettings.max_fees_per_gas), + mapGasFeesFromNoir(gasSettings.max_priority_fees_per_gas), ); } @@ -532,6 +533,7 @@ export function mapGasSettingsToNoir(gasSettings: GasSettings): GasSettingsNoir gas_limits: mapGasToNoir(gasSettings.gasLimits), teardown_gas_limits: mapGasToNoir(gasSettings.teardownGasLimits), max_fees_per_gas: mapGasFeesToNoir(gasSettings.maxFeesPerGas), + max_priority_fees_per_gas: mapGasFeesToNoir(gasSettings.maxPriorityFeesPerGas), }; } diff --git a/yarn-project/simulator/src/public/fixtures/index.ts b/yarn-project/simulator/src/public/fixtures/index.ts index 691c0f4ae85..6789e8622d3 100644 --- a/yarn-project/simulator/src/public/fixtures/index.ts +++ b/yarn-project/simulator/src/public/fixtures/index.ts @@ -125,7 +125,7 @@ export function createTxForPublicCall( } const teardownGasLimits = isTeardown ? gasLimits : Gas.empty(); - const gasSettings = new GasSettings(gasLimits, teardownGasLimits, GasFees.empty()); + const gasSettings = new GasSettings(gasLimits, teardownGasLimits, GasFees.empty(), GasFees.empty()); const txContext = new TxContext(Fr.zero(), Fr.zero(), gasSettings); const constantData = new TxConstantData(BlockHeader.empty(), txContext, Fr.zero(), Fr.zero()); diff --git a/yarn-project/simulator/src/public/public_tx_context.ts b/yarn-project/simulator/src/public/public_tx_context.ts index 9c3eaf60d03..ec7aaa93316 100644 --- a/yarn-project/simulator/src/public/public_tx_context.ts +++ b/yarn-project/simulator/src/public/public_tx_context.ts @@ -26,6 +26,7 @@ import { RevertCode, type StateReference, TreeSnapshots, + computeTransactionFee, countAccumulatedItems, } from '@aztec/circuits.js'; import { type Logger, createLogger } from '@aztec/foundation/log'; @@ -298,12 +299,15 @@ export class PublicTxContext { * Should only be called during or after teardown. */ private getTransactionFeeUnsafe(): Fr { - const txFee = this.getTotalGasUsed().computeFee(this.globalVariables.gasFees); + const gasUsed = this.getTotalGasUsed(); + const txFee = computeTransactionFee(this.globalVariables.gasFees, this.gasSettings, gasUsed); + this.log.debug(`Computed tx fee`, { txFee, - gasUsed: inspect(this.getTotalGasUsed()), + gasUsed: inspect(gasUsed), gasFees: inspect(this.globalVariables.gasFees), }); + return txFee; } diff --git a/yarn-project/simulator/src/public/public_tx_simulator.test.ts b/yarn-project/simulator/src/public/public_tx_simulator.test.ts index 4eca8f04d7e..2a9d1d62936 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator.test.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator.test.ts @@ -42,13 +42,14 @@ describe('public_tx_simulator', () => { // Nullifier must be >=128 since tree starts with 128 entries pre-filled const MIN_NULLIFIER = 128; // Gas settings. - const gasFees = GasFees.from({ feePerDaGas: new Fr(2), feePerL2Gas: new Fr(3) }); - const gasLimits = Gas.from({ daGas: 100, l2Gas: 150 }); - const teardownGasLimits = Gas.from({ daGas: 20, l2Gas: 30 }); - const maxFeesPerGas = gasFees; + const gasLimits = new Gas(100, 150); + const teardownGasLimits = new Gas(20, 30); + const gasFees = new GasFees(2, 3); + let maxFeesPerGas = gasFees; + let maxPriorityFeesPerGas = GasFees.empty(); // gasUsed for the tx after private execution, minus the teardownGasLimits. - const privateGasUsed = Gas.from({ daGas: 13, l2Gas: 17 }); + const privateGasUsed = new Gas(13, 17); // gasUsed for each enqueued call. const enqueuedCallGasUsed = new Gas(12, 34); @@ -86,7 +87,12 @@ describe('public_tx_simulator', () => { numberOfRevertiblePublicCallRequests: numberOfAppLogicCalls, hasPublicTeardownCallRequest: hasPublicTeardownCall, }); - tx.data.constants.txContext.gasSettings = GasSettings.from({ gasLimits, teardownGasLimits, maxFeesPerGas }); + tx.data.constants.txContext.gasSettings = GasSettings.from({ + gasLimits, + teardownGasLimits, + maxFeesPerGas, + maxPriorityFeesPerGas, + }); tx.data.forPublic!.nonRevertibleAccumulatedData.nullifiers[0] = new Fr(7777); tx.data.forPublic!.nonRevertibleAccumulatedData.nullifiers[1] = new Fr(8888); @@ -746,4 +752,37 @@ describe('public_tx_simulator', () => { await checkNullifierRoot(txResult); }); + + it('runs a tx with non-empty priority fees', async () => { + // gasFees = new GasFees(2, 3); + maxPriorityFeesPerGas = new GasFees(5, 7); + // The max fee is gasFee + priorityFee + 1. + maxFeesPerGas = new GasFees(2 + 5 + 1, 3 + 7 + 1); + + const tx = mockTxWithPublicCalls({ + numberOfSetupCalls: 1, + numberOfAppLogicCalls: 1, + hasPublicTeardownCall: true, + }); + + const txResult = await simulator.simulate(tx); + expect(txResult.revertCode).toEqual(RevertCode.OK); + + const expectedPublicGasUsed = enqueuedCallGasUsed.mul(2); // 1 for setup and 1 for app logic. + const expectedTeardownGasUsed = enqueuedCallGasUsed; + const expectedTotalGas = privateGasUsed.add(expectedPublicGasUsed).add(expectedTeardownGasUsed); + expect(txResult.gasUsed).toEqual({ + totalGas: expectedTotalGas, + teardownGas: expectedTeardownGasUsed, + }); + + const output = txResult.avmProvingRequest!.inputs.output; + + const expectedGasUsedForFee = expectedTotalGas.sub(expectedTeardownGasUsed).add(teardownGasLimits); + expect(output.endGasUsed).toEqual(expectedGasUsedForFee); + + const totalFees = new GasFees(2 + 5, 3 + 7); + const expectedTxFee = expectedGasUsedForFee.computeFee(totalFees); + expect(output.transactionFee).toEqual(expectedTxFee); + }); });