diff --git a/etherlink/kernel_evm/ethereum/src/block.rs b/etherlink/kernel_evm/ethereum/src/block.rs index f0634947b8a4238e4b5e7aa24d148c75074935ca..ce273343c87e4057d2c8d090af30f06c2e00d51a 100644 --- a/etherlink/kernel_evm/ethereum/src/block.rs +++ b/etherlink/kernel_evm/ethereum/src/block.rs @@ -21,12 +21,16 @@ use tezos_smart_rollup_encoding::timestamp::Timestamp; #[derive(Debug, Clone, Copy)] pub struct BlockFees { base_fee_per_gas: U256, + flat_fee: U256, } impl BlockFees { /// Setup fee information for the current block - pub const fn new(base_fee_per_gas: U256) -> Self { - Self { base_fee_per_gas } + pub const fn new(base_fee_per_gas: U256, flat_fee: U256) -> Self { + Self { + base_fee_per_gas, + flat_fee, + } } /// The base fee per gas for doing a transaction within the current block. @@ -34,6 +38,23 @@ impl BlockFees { pub const fn base_fee_per_gas(&self) -> U256 { self.base_fee_per_gas } + + /// The flat fee charged per transaction. + #[inline(always)] + pub const fn flat_fee(&self) -> U256 { + self.flat_fee + } + + /// The gas required to cover non-execution fees at the given price. + pub fn gas_for_fees(&self, effective_gas_price: U256) -> U256 { + let (mut gas_for_fees, unpaid_fees) = self.flat_fee.div_mod(effective_gas_price); + + if unpaid_fees != U256::zero() { + gas_for_fees += U256::one(); + } + + gas_for_fees + } } /// All data for an Ethereum block. diff --git a/etherlink/kernel_evm/ethereum/src/tx_common.rs b/etherlink/kernel_evm/ethereum/src/tx_common.rs index 75ac5e06463b678b9889d1f413f84b452b8ee372..bab68c2a7f001c9f6a3436f577a531e291b658e7 100644 --- a/etherlink/kernel_evm/ethereum/src/tx_common.rs +++ b/etherlink/kernel_evm/ethereum/src/tx_common.rs @@ -19,7 +19,6 @@ use thiserror::Error; use crate::{ access_list::AccessList, - block::BlockFees, rlp_helpers::{ append_h256, append_option, append_vec, decode_field, decode_field_h256, decode_list, decode_option, next, @@ -96,8 +95,6 @@ pub struct EthereumTransactionCommon { /// *NB* this is inclusive of any additional fees that are paid, prior to execution: /// - flat fee /// - data availability fee - /// - /// For the execution gas limit, see [execution_gas_limit]. gas_limit: u64, /// The 160-bit address of the message call’s recipient /// or, for a contract creation transaction @@ -555,31 +552,9 @@ impl EthereumTransactionCommon { } } - /// Returns overall_gas_price of the transaction. - /// - /// *NB* this is not the gas price used _for execution_, but rather the gas price that - /// should be reported in the transaction receipt. - pub fn overall_gas_price( - &self, - block_fees: &BlockFees, - ) -> Result { - let block_base_fee_per_gas = block_fees.base_fee_per_gas(); - - if self.max_fee_per_gas >= block_base_fee_per_gas { - Ok(block_base_fee_per_gas) - } else { - Err(anyhow::anyhow!("Underflow when calculating gas price")) - } - } - - /// Returns the gas limit for executing this transaction. - /// - /// This is strictly lower than the gas limit set by the user, as additional gas is required - /// in order to pay for the *flat fee* and *data availability fee*. - /// - /// The user pre-pays this (in addition to the flat fee & data availability fee) prior to execution. - /// If execution does not use all of the execution gas limit, they will be partially refunded. - pub fn execution_gas_limit(&self) -> u64 { + /// Returns the total gas limit for this transaction, including execution and fees. + #[inline(always)] + pub const fn gas_limit_with_fees(&self) -> u64 { self.gas_limit } } diff --git a/etherlink/kernel_evm/evm_evaluation/src/runner.rs b/etherlink/kernel_evm/evm_evaluation/src/runner.rs index 227e6dea8b96c469869d0e624e577ab5ad65652b..17e8e2e89f05f72acf94d25196573ae2cf29db53 100644 --- a/etherlink/kernel_evm/evm_evaluation/src/runner.rs +++ b/etherlink/kernel_evm/evm_evaluation/src/runner.rs @@ -193,7 +193,7 @@ fn execute_transaction( env.tx.value = *unit.transaction.value.get(test.indexes.value).unwrap(); env.tx.transact_to = unit.transaction.to; - let block_fees = BlockFees::new(env.block.basefee); + let block_fees = BlockFees::new(env.block.basefee, U256::zero()); let block_constants = BlockConstants { number: env.block.number, diff --git a/etherlink/kernel_evm/evm_execution/src/handler.rs b/etherlink/kernel_evm/evm_execution/src/handler.rs index aad267d8206ad6cf827d6db767ec5c001be4fb01..965c3dc887d6ba77536bf1e6afab60e45721ac75 100644 --- a/etherlink/kernel_evm/evm_execution/src/handler.rs +++ b/etherlink/kernel_evm/evm_execution/src/handler.rs @@ -13,7 +13,6 @@ use crate::account_storage::{ CODE_HASH_DEFAULT, }; use crate::transaction::TransactionContext; -use crate::ArithmeticErrorKind::FeeOverflow; use crate::EthereumError; use crate::PrecompileSet; use crate::{storage, tick_model_opcodes}; @@ -479,7 +478,7 @@ impl<'a, Host: Runtime> EvmHandler<'a, Host> { let amount = U256::from(gas_limit) .checked_mul(effective_gas_price) - .ok_or(EthereumError::ArithmeticError(FeeOverflow))?; + .ok_or(EthereumError::GasPaymentOverflow)?; log!( self.host, @@ -503,7 +502,7 @@ impl<'a, Host: Runtime> EvmHandler<'a, Host> { let amount = U256::from(unused_gas) .checked_mul(effective_gas_price) - .ok_or(EthereumError::ArithmeticError(FeeOverflow))?; + .ok_or(EthereumError::GasPaymentOverflow)?; log!( self.host, @@ -1908,7 +1907,8 @@ mod test { } fn dummy_first_block() -> BlockConstants { - let block_fees = BlockFees::new(U256::from(12345)); + let block_fees = + BlockFees::new(U256::from(12345), U256::from(123_000_000_000u64)); BlockConstants::first_block(U256::zero(), U256::one(), block_fees) } diff --git a/etherlink/kernel_evm/evm_execution/src/lib.rs b/etherlink/kernel_evm/evm_execution/src/lib.rs index 6d723301b743e13bd9cfb00f565894a23c5ded61..88a73a7b0ddcdb16b13e92ca6c7583e62e77023e 100644 --- a/etherlink/kernel_evm/evm_execution/src/lib.rs +++ b/etherlink/kernel_evm/evm_execution/src/lib.rs @@ -50,11 +50,6 @@ pub enum DurableStorageError { PathError(#[from] host::path::PathError), } -#[derive(Debug, Eq, PartialEq)] -pub enum ArithmeticErrorKind { - FeeOverflow, -} - /// Errors when processing Ethereum transactions /// /// What could possibly go wrong? Some of these are place holders for now. @@ -109,13 +104,19 @@ pub enum EthereumError { /// result of a bug in the EvmHandler. #[error("Inconsistent EvmHandler state: {0}")] InconsistentState(Cow<'static, str>), - /// The execution failed because there was an arithmetic overflow or underflow. - #[error("A computation provoked an arithmetic error: {0:?}")] - ArithmeticError(ArithmeticErrorKind), /// The execution failed because it spent more ticks than the one currently /// available for the current run. #[error("The transaction took more ticks than expected")] OutOfTicks, + /// gas_limit * gas_price > u64::max + #[error("Gas payment overflowed u64::max")] + GasPaymentOverflow, + /// Converting non-execution fees to gas overflowed u64::max + #[error("Gas for fees overflowed u64::max in conversion")] + FeesToGasOverflow, + /// Underflow of gas limit when subtracting gas for fees + #[error("Insufficient gas to cover the non-execution fees")] + GasToFeesUnderflow, } /// Execute an Ethereum Transaction @@ -310,7 +311,7 @@ mod test { } fn dummy_first_block() -> BlockConstants { - let block_fees = BlockFees::new(U256::from(12345)); + let block_fees = BlockFees::new(U256::from(12345), U256::from(500_000)); BlockConstants::first_block(U256::zero(), U256::one(), block_fees) } @@ -1998,7 +1999,7 @@ mod test { let chain_id = U256::from(42); let mut chain_id_bytes = [0u8; 32]; chain_id.to_big_endian(&mut chain_id_bytes); - let block_fees = BlockFees::new(U256::from(54321)); + let block_fees = BlockFees::new(U256::from(54321), U256::from(1000)); let block = BlockConstants::first_block(U256::zero(), chain_id, block_fees); let precompiles = precompiles::precompile_set::(); let mut evm_account_storage = init_evm_account_storage().unwrap(); @@ -2070,7 +2071,7 @@ mod test { let base_fee_per_gas = U256::from(23000); let mut base_fee_per_gas_bytes = [0u8; 32]; base_fee_per_gas.to_big_endian(&mut base_fee_per_gas_bytes); - let block_fees = BlockFees::new(base_fee_per_gas); + let block_fees = BlockFees::new(base_fee_per_gas, U256::zero()); let block = BlockConstants::first_block(U256::zero(), U256::one(), block_fees); let precompiles = precompiles::precompile_set::(); let mut evm_account_storage = init_evm_account_storage().unwrap(); @@ -2314,7 +2315,7 @@ mod test { // Arrange let mut mock_runtime = MockHost::default(); let base_fee_per_gas = U256::from(23000); - let block_fees = BlockFees::new(base_fee_per_gas); + let block_fees = BlockFees::new(base_fee_per_gas, U256::zero()); let block = BlockConstants::first_block(U256::zero(), U256::one(), block_fees); let precompiles = precompiles::precompile_set::(); let mut evm_account_storage = init_evm_account_storage().unwrap(); @@ -2371,7 +2372,7 @@ mod test { // Arrange let mut mock_runtime = MockHost::default(); let base_fee_per_gas = U256::from(23000); - let block_fees = BlockFees::new(base_fee_per_gas); + let block_fees = BlockFees::new(base_fee_per_gas, U256::zero()); let block = BlockConstants::first_block(U256::zero(), U256::one(), block_fees); let precompiles = precompiles::precompile_set::(); let mut evm_account_storage = init_evm_account_storage().unwrap(); @@ -2425,7 +2426,7 @@ mod test { fn first_block() -> BlockConstants { let base_fee_per_gas = U256::from(23000); - let block_fees = BlockFees::new(base_fee_per_gas); + let block_fees = BlockFees::new(base_fee_per_gas, U256::zero()); BlockConstants::first_block(U256::zero(), U256::one(), block_fees) } diff --git a/etherlink/kernel_evm/evm_execution/src/precompiles.rs b/etherlink/kernel_evm/evm_execution/src/precompiles.rs index abc8709b0fa99e64981d3a419a0c467fa579ccde..5cc4a21da84512c3809b330b1b222deb11d7a6bd 100644 --- a/etherlink/kernel_evm/evm_execution/src/precompiles.rs +++ b/etherlink/kernel_evm/evm_execution/src/precompiles.rs @@ -560,7 +560,7 @@ mod tests { ) -> Result { let caller = H160::from_low_u64_be(118u64); let mut mock_runtime = MockHost::default(); - let block_fees = BlockFees::new(U256::from(21000)); + let block_fees = BlockFees::new(U256::from(21000), U256::from(1_000_000)); let block = BlockConstants::first_block(U256::zero(), U256::one(), block_fees); let mut evm_account_storage = init_evm_account_storage().unwrap(); let precompiles = precompile_set::(); diff --git a/etherlink/kernel_evm/kernel/src/apply.rs b/etherlink/kernel_evm/kernel/src/apply.rs index 50eca9813d58211f9556d41c799500dadb5c81bf..c3205e01e794dd764135ec5fef9649f1cef4950b 100644 --- a/etherlink/kernel_evm/kernel/src/apply.rs +++ b/etherlink/kernel_evm/kernel/src/apply.rs @@ -15,7 +15,7 @@ use evm_execution::precompiles::PrecompileBTreeMap; use evm_execution::{run_transaction, EthereumError}; use primitive_types::{H160, U256}; use tezos_data_encoding::enc::BinWriter; -use tezos_ethereum::block::{BlockConstants, BlockFees}; +use tezos_ethereum::block::BlockConstants; use tezos_ethereum::transaction::TransactionHash; use tezos_ethereum::tx_common::EthereumTransactionCommon; use tezos_ethereum::tx_signature::TxSignature; @@ -33,6 +33,7 @@ use tezos_smart_rollup_encoding::outbox::OutboxMessageTransaction; use tezos_smart_rollup_host::runtime::Runtime; use crate::error::Error; +use crate::fees::{tx_execution_gas_limit, FeeUpdates}; use crate::inbox::{Deposit, Transaction, TransactionContent}; use crate::indexable_storage::IndexableStorage; use crate::storage::{index_account, read_ticketer}; @@ -57,18 +58,6 @@ impl Transaction { } } - // This function returns effective_gas_price of the transaction. - // - // This includes both the gas paid for execution, and for the additional flat & data-availability fees. - fn overall_gas_price(&self, block_fees: &BlockFees) -> Result { - match &self.content { - TransactionContent::Deposit(_) => Ok(U256::zero()), - TransactionContent::Ethereum(transaction) => { - transaction.overall_gas_price(block_fees) - } - } - } - fn value(&self) -> U256 { match &self.content { TransactionContent::Deposit(Deposit { amount, .. }) => *amount, @@ -138,13 +127,12 @@ fn make_object_info( transaction: &Transaction, from: H160, index: u32, - gas_used: U256, - block_fees: &BlockFees, + fee_updates: &FeeUpdates, ) -> Result { Ok(TransactionObjectInfo { from, - gas_used, - gas_price: transaction.overall_gas_price(block_fees)?, + gas_used: fee_updates.overall_gas_used, + gas_price: fee_updates.overall_gas_price, hash: transaction.tx_hash, input: transaction.data(), nonce: transaction.nonce(), @@ -213,6 +201,13 @@ fn is_valid_ethereum_transaction_common( log!(host, Debug, "Transaction status: ERROR_CHAINID"); return Ok(Validity::InvalidChainId); } + + // ensure that the user was willing to at least pay the base fee + if transaction.max_fee_per_gas < block_constant.base_fee_per_gas() { + log!(host, Debug, "Transaction status: ERROR_MAX_BASE_FEE"); + return Ok(Validity::InvalidMaxBaseFee); + } + // The transaction signature is valid. let caller = match transaction.caller() { Ok(caller) => caller, @@ -242,10 +237,11 @@ fn is_valid_ethereum_transaction_common( }; // The sender account balance contains at least the cost. - let execution_gas_limit = U256::from(transaction.execution_gas_limit()); - let cost = execution_gas_limit.saturating_mul(effective_gas_price); + let total_gas_limit = U256::from(transaction.gas_limit_with_fees()); + let cost = total_gas_limit.saturating_mul(effective_gas_price); // The sender can afford the max gas fee he set, see EIP-1559 - let max_fee = execution_gas_limit.saturating_mul(transaction.max_fee_per_gas); + let max_fee = total_gas_limit.saturating_mul(transaction.max_fee_per_gas); + if balance < cost || balance < max_fee { log!(host, Debug, "Transaction status: ERROR_PRE_PAY."); return Ok(Validity::InvalidPrePay); @@ -257,12 +253,6 @@ fn is_valid_ethereum_transaction_common( return Ok(Validity::InvalidCode); } - // ensure that the user was willing to at least pay the base fee - if transaction.max_fee_per_gas < block_constant.base_fee_per_gas() { - log!(host, Debug, "Transaction status: ERROR_MAX_BASE_FEE"); - return Ok(Validity::InvalidMaxBaseFee); - } - Ok(Validity::Valid(caller)) } @@ -295,7 +285,7 @@ fn apply_ethereum_transaction_common( let to = transaction.to; let call_data = transaction.data.clone(); - let gas_limit = transaction.execution_gas_limit(); + let gas_limit = tx_execution_gas_limit(transaction, &block_constants.block_fees)?; let value = transaction.value; let execution_outcome = match run_transaction( host, @@ -508,11 +498,19 @@ pub fn apply_transaction( match apply_result { ExecutionResult::Valid(TransactionResult { caller, - execution_outcome, + mut execution_outcome, gas_used, estimated_ticks_used: ticks_used, }) => { - if let Some(outcome) = &execution_outcome { + let fee_updates = match &transaction.content { + TransactionContent::Deposit(_) => FeeUpdates::for_deposit(gas_used), + TransactionContent::Ethereum(tx) => { + FeeUpdates::for_tx(tx, &block_constants.block_fees, gas_used) + } + }; + + if let Some(outcome) = &mut execution_outcome { + fee_updates.modify_outcome(outcome); log!(host, Debug, "Transaction executed, outcome: {:?}", outcome); } @@ -520,13 +518,9 @@ pub fn apply_transaction( post_withdrawals(host, &execution_outcome.withdrawals)? } - let object_info = make_object_info( - transaction, - caller, - index, - gas_used, - &block_constants.block_fees, - )?; + fee_updates.apply(host, evm_account_storage, caller)?; + + let object_info = make_object_info(transaction, caller, index, &fee_updates)?; let receipt_info = make_receipt_info( transaction.tx_hash, @@ -556,20 +550,18 @@ mod tests { use primitive_types::{H160, U256}; use tezos_ethereum::{ block::{BlockConstants, BlockFees}, - transaction::{TransactionType, TRANSACTION_HASH_SIZE}, + transaction::TransactionType, tx_common::EthereumTransactionCommon, }; use tezos_smart_rollup_encoding::timestamp::Timestamp; use tezos_smart_rollup_mock::MockHost; - use crate::inbox::{Transaction, TransactionContent}; - - use super::{is_valid_ethereum_transaction_common, make_object_info}; + use super::is_valid_ethereum_transaction_common; const CHAIN_ID: u32 = 1337; fn mock_block_constants() -> BlockConstants { - let block_fees = BlockFees::new(U256::from(12345)); + let block_fees = BlockFees::new(U256::from(12345), U256::from(1_000_000)); BlockConstants::first_block( U256::from(Timestamp::from(0).as_u64()), CHAIN_ID.into(), @@ -612,14 +604,14 @@ mod tests { .expect("Should have been able to sign") } - fn valid_tx() -> EthereumTransactionCommon { + fn valid_tx(gas_limit: u64) -> EthereumTransactionCommon { let transaction = EthereumTransactionCommon::new( TransactionType::Eip1559, Some(CHAIN_ID.into()), U256::from(0), U256::zero(), U256::from(21000), - 21000, + gas_limit, Some(H160::zero()), U256::zero(), vec![], @@ -640,8 +632,10 @@ mod tests { // setup let address = address_from_str("af1276cbb260bb13deddb4209ae99ae6e497f446"); let gas_price = U256::from(21000); - let balance = U256::from(21000) * gas_price; - let transaction = valid_tx(); + let fee_gas = block_constants.block_fees.gas_for_fees(gas_price); + let balance = (fee_gas + 21000) * gas_price; + let gas_limit = 21000 + fee_gas.as_u64(); + let transaction = valid_tx(gas_limit); // fund account set_balance(&mut host, &mut evm_account_storage, &address, balance); @@ -670,9 +664,11 @@ mod tests { // setup let address = address_from_str("af1276cbb260bb13deddb4209ae99ae6e497f446"); let gas_price = U256::from(21000); - // account doesnt have enough fundes - let balance = U256::from(1); - let transaction = valid_tx(); + let fee_gas = block_constants.block_fees.gas_for_fees(gas_price); + // account doesnt have enough funds for execution + let balance = fee_gas * gas_price; + let gas_limit = 21000 + fee_gas.as_u64(); + let transaction = valid_tx(gas_limit); // fund account set_balance(&mut host, &mut evm_account_storage, &address, balance); @@ -701,8 +697,10 @@ mod tests { // setup let address = address_from_str("af1276cbb260bb13deddb4209ae99ae6e497f446"); let gas_price = U256::from(21000); - let balance = U256::from(21000) * gas_price; - let mut transaction = valid_tx(); + let fee_gas = block_constants.block_fees.gas_for_fees(gas_price); + let balance = (fee_gas + 21000) * gas_price; + let gas_limit = 21000 + fee_gas.as_u64(); + let mut transaction = valid_tx(gas_limit); transaction.signature = None; // fund account set_balance(&mut host, &mut evm_account_storage, &address, balance); @@ -732,8 +730,10 @@ mod tests { // setup let address = address_from_str("af1276cbb260bb13deddb4209ae99ae6e497f446"); let gas_price = U256::from(21000); - let balance = U256::from(21000) * gas_price; - let mut transaction = valid_tx(); + let fee_gas = block_constants.block_fees.gas_for_fees(gas_price); + let balance = (fee_gas + 21000) * gas_price; + let gas_limit = 21000 + fee_gas.as_u64(); + let mut transaction = valid_tx(gas_limit); transaction.nonce = U256::from(42); transaction = resign(transaction); @@ -766,7 +766,7 @@ mod tests { let address = address_from_str("af1276cbb260bb13deddb4209ae99ae6e497f446"); let gas_price = U256::from(21000); let balance = U256::from(21000) * gas_price; - let mut transaction = valid_tx(); + let mut transaction = valid_tx(1); transaction.chain_id = Some(U256::from(42)); transaction = resign(transaction); @@ -796,17 +796,16 @@ mod tests { let block_constants = mock_block_constants(); // setup - let address = address_from_str("af1276cbb260bb13deddb4209ae99ae6e497f446"); let gas_price = U256::from(21000); - let balance = U256::from(21000) * gas_price; - let mut transaction = valid_tx(); + let max_gas_price = U256::one(); + let fee_gas = block_constants.block_fees.gas_for_fees(max_gas_price); + // account doesnt have enough funds for execution + let gas_limit = 21000 + fee_gas.as_u64(); + let mut transaction = valid_tx(gas_limit); // set a max base fee too low - transaction.max_fee_per_gas = U256::from(1); + transaction.max_fee_per_gas = max_gas_price; transaction = resign(transaction); - // fund account - set_balance(&mut host, &mut evm_account_storage, &address, balance); - // act let res = is_valid_ethereum_transaction_common( &mut host, @@ -821,37 +820,4 @@ mod tests { "Transaction should have been rejected" ); } - - #[test] - // when the user specify a max fee per gas lower than base fee, - // the function should fail gracefully - fn test_no_underflow_make_object_tx() { - let transaction = Transaction { - tx_hash: [0u8; TRANSACTION_HASH_SIZE], - content: TransactionContent::Ethereum(EthereumTransactionCommon::new( - TransactionType::Eip1559, - Some(U256::from(1)), - U256::from(1), - U256::zero(), - U256::from(1), - 21000, - Some(H160::zero()), - U256::zero(), - vec![], - vec![], - None, - )), - }; - - let block_fees = BlockFees::new(U256::from(9)); - - let obj = make_object_info( - &transaction, - H160::zero(), - 0u32, - U256::from(21_000), - &block_fees, - ); - assert!(obj.is_err()) - } } diff --git a/etherlink/kernel_evm/kernel/src/block.rs b/etherlink/kernel_evm/kernel/src/block.rs index 37612dee04ea3c763cfc2232f06ac5510de96ec9..cced45881af5ff5a6c53c224652edc1bb14ed84b 100644 --- a/etherlink/kernel_evm/kernel/src/block.rs +++ b/etherlink/kernel_evm/kernel/src/block.rs @@ -390,9 +390,13 @@ mod tests { const DUMMY_CHAIN_ID: U256 = U256::one(); const DUMMY_BASE_FEE_PER_GAS: u64 = 21000u64; + const DUMMY_FLAT_FEE: u64 = 0u64; fn dummy_block_fees() -> BlockFees { - BlockFees::new(DUMMY_BASE_FEE_PER_GAS.into()) + BlockFees::new( + U256::from(DUMMY_BASE_FEE_PER_GAS), + U256::from(DUMMY_FLAT_FEE), + ) } fn dummy_eth_gen_transaction( @@ -1163,7 +1167,7 @@ mod tests { // Ensures the caller has enough balance to pay for the fees, but not // the transaction itself, otherwise the transaction will not even be // taken into account. - let fees = U256::from(21000) * tx.execution_gas_limit(); + let fees = U256::from(21000) * tx.gas_limit_with_fees(); set_balance(&mut host, &mut evm_account_storage, &caller, fees); // Prepare a invalid transaction, i.e. with not enough funds. diff --git a/etherlink/kernel_evm/kernel/src/fees.rs b/etherlink/kernel_evm/kernel/src/fees.rs new file mode 100644 index 0000000000000000000000000000000000000000..7149c32167eefcbad92330868d0f36dbbe4e04aa --- /dev/null +++ b/etherlink/kernel_evm/kernel/src/fees.rs @@ -0,0 +1,356 @@ +// SPDX-FileCopyrightText: 2024 TriliTech +// +// SPDX-License-Identifier: MIT + +//! Adjustments & calculation of fees, over-and-above the execution gas fee. +//! +//! Users submit transactions which contain three values related to fees: +//! - `gas_limit` +//! - `max_fee_per_gas` +//! - `max_priority_fee_per_gas` +//! +//! We ignore `tx.max_priority_fee_per_gas` completely. For every transaction, we act as if the +//! user set `tx.max_priority_fee_per_gas = 0`. We therefore only care about `tx.gas_limit` and +//! `tx.max_fee_per_gas`. + +use evm_execution::account_storage::{account_path, EthereumAccountStorage}; +use evm_execution::handler::ExecutionOutcome; +use evm_execution::EthereumError; +use primitive_types::{H160, U256}; +use tezos_ethereum::block::BlockFees; +use tezos_ethereum::tx_common::EthereumTransactionCommon; +use tezos_smart_rollup_host::runtime::Runtime; + +/// Instructions for 'balancing the books'. +#[derive(Debug)] +pub struct FeeUpdates { + pub overall_gas_price: U256, + pub overall_gas_used: U256, + pub burn_amount: U256, + pub charge_user_amount: U256, + pub compensate_sequencer_amount: U256, +} + +impl FeeUpdates { + /// Returns the fee updates for a deposit. + pub fn for_deposit(gas_used: U256) -> Self { + Self { + overall_gas_used: gas_used, + overall_gas_price: U256::zero(), + burn_amount: U256::zero(), + charge_user_amount: U256::zero(), + compensate_sequencer_amount: U256::zero(), + } + } + + /// Returns fee updates of the transaction. + /// + /// *NB* this is not the gas price used _for execution_, but rather the gas price that + /// should be reported in the transaction receipt. + /// + /// # Prerequisites + /// The user must have already paid for 'execution gas fees'. + pub fn for_tx( + tx: &EthereumTransactionCommon, + block_fees: &BlockFees, + execution_gas_used: U256, + ) -> Self { + let execution_gas_fees = execution_gas_used * block_fees.base_fee_per_gas(); + let initial_added_fees = block_fees.flat_fee(); + let initial_total_fees = initial_added_fees + execution_gas_fees; + + // first, we find the price of gas (with all fees), for the given gas used + let mut gas_price = cdiv(initial_added_fees, execution_gas_used) + .saturating_add(block_fees.base_fee_per_gas()); + + let gas_used = if gas_price > tx.max_fee_per_gas { + // We can't charge more than `max_fee_per_gas`, so bump the gas limit too. + // added_gas = initial_total_fee / mgp - execution_gas + gas_price = tx.max_fee_per_gas; + cdiv(initial_total_fees, gas_price) + } else { + execution_gas_used + }; + + let total_fees = gas_price * gas_used; + + // Due to rounding, we may have a small amount of unaccounted-for gas. + // Assign this to the flat fee (to be burned). + let burn_amount = block_fees.flat_fee() + total_fees - initial_total_fees; + + // For now, just the execution fees. Future MR will include data availability + // also. + let compensate_sequencer_amount = execution_gas_fees; + + Self { + overall_gas_price: gas_price, + overall_gas_used: gas_used, + burn_amount, + charge_user_amount: total_fees - execution_gas_fees, + compensate_sequencer_amount, + } + } + + pub fn modify_outcome(&self, outcome: &mut ExecutionOutcome) { + outcome.gas_used = self.overall_gas_used.as_u64(); + } + + pub fn apply( + &self, + host: &mut impl Runtime, + accounts: &mut EthereumAccountStorage, + caller: H160, + ) -> Result<(), anyhow::Error> { + let caller_account_path = account_path(&caller)?; + let mut caller_account = accounts.get_or_create(host, &caller_account_path)?; + if !caller_account.balance_remove(host, self.charge_user_amount)? { + return Err(anyhow::anyhow!( + "Failed to charge {caller} additional fees of {}", + self.charge_user_amount + )); + } + + Ok(()) + } +} + +/// Adjust a simulation outcome, to take non-execution fees into account. +/// +/// This is done by adjusting `gas_used` upwards. +pub fn simulation_add_gas_for_fees( + mut outcome: ExecutionOutcome, + block_fees: &BlockFees, + tx_gas_price: U256, +) -> Result { + let gas_for_fees = cdiv(block_fees.flat_fee(), tx_gas_price); + let gas_for_fees = gas_as_u64(gas_for_fees)?; + + outcome.gas_used = outcome.gas_used.saturating_add(gas_for_fees); + Ok(outcome) +} + +/// Returns the gas limit for executing this transaction. +/// +/// This is strictly lower than the gas limit set by the user, as additional gas is required +/// in order to pay for the *flat fee* and *data availability fee*. +/// +/// The user pre-pays this (in addition to the flat fee & data availability fee) prior to execution. +/// If execution does not use all of the execution gas limit, they will be partially refunded. +pub fn tx_execution_gas_limit( + tx: &EthereumTransactionCommon, + fees: &BlockFees, +) -> Result { + let gas_for_fees = cdiv(fees.flat_fee(), tx.max_fee_per_gas); + + let gas_for_fees = gas_as_u64(gas_for_fees)?; + + tx.gas_limit_with_fees() + .checked_sub(gas_for_fees) + .ok_or(EthereumError::GasToFeesUnderflow) +} + +fn cdiv(l: U256, r: U256) -> U256 { + match l.div_mod(r) { + (res, rem) if rem.is_zero() => res, + (res, _) => res.saturating_add(U256::one()), + } +} + +fn gas_as_u64(gas_for_fees: U256) -> Result { + gas_for_fees + .try_into() + .map_err(|_e| EthereumError::FeesToGasOverflow) +} + +#[cfg(test)] +mod tests { + use super::*; + use evm::{ExitReason, ExitSucceed}; + use evm_execution::account_storage::{account_path, EthereumAccountStorage}; + use primitive_types::{H160, U256}; + use tezos_smart_rollup_mock::MockHost; + + use proptest::prelude::*; + + proptest! { + #[test] + fn fee_updates_consistent( + flat_fee in any::().prop_map(U256::from), + execution_gas_used in (1_u64..).prop_map(U256::from), + (max_fee_per_gas, base_fee_per_gas) in (1_u64.., 1_u64..) + .prop_map(|(a, b)| if a > b {(a, b)} else {(b, a)}) + .prop_map(|(a, b)| (a.into(), b.into())), + ) { + // Arrange + let tx = mock_tx(max_fee_per_gas); + let block_fees = BlockFees::new(base_fee_per_gas, flat_fee); + + // Act + let updates = FeeUpdates::for_tx(&tx, &block_fees, execution_gas_used); + + // Assert + let assumed_execution_gas_cost = base_fee_per_gas * execution_gas_used; + let total_fee = updates.overall_gas_price * updates.overall_gas_used; + + assert!(updates.burn_amount >= flat_fee, "burn amount should cover flat fee"); + assert_eq!(updates.charge_user_amount, total_fee - assumed_execution_gas_cost, "inconsistent user charge"); + assert_eq!(total_fee, updates.burn_amount + updates.compensate_sequencer_amount, "inconsistent total fees"); + assert_eq!(updates.compensate_sequencer_amount, assumed_execution_gas_cost, "inconsistent sequencer comp"); + } + } + + #[test] + fn simulation_covers_extra_fees() { + fn expect_extra_gas(extra: u64, tx_gas_price: u64, flat_fee: u64) { + // Arrange + let initial_gas_used = 100; + let block_fees = BlockFees::new(U256::one(), U256::from(flat_fee)); + + let simulated_outcome = mock_execution_outcome(initial_gas_used); + + // Act + let res = simulation_add_gas_for_fees( + simulated_outcome, + &block_fees, + tx_gas_price.into(), + ); + + // Assert + let expected = mock_execution_outcome(initial_gas_used + extra); + assert_eq!( + Ok(expected), + res, + "unexpected extra gas at price {tx_gas_price} and fee {flat_fee}" + ); + } + + let flat_fee = 15; + + expect_extra_gas(4, 4, flat_fee); + for tx_gas_price in 5..7 { + expect_extra_gas(3, tx_gas_price, flat_fee); + } + for tx_gas_price in 8..14 { + expect_extra_gas(2, tx_gas_price, flat_fee); + } + for tx_gas_price in 15..40 { + // could be any value of gas price above 15 + expect_extra_gas(1, tx_gas_price, flat_fee); + } + } + + #[test] + fn apply_deducts_balance_from_user() { + // Arrange + let mut host = MockHost::default(); + let mut evm_account_storage = + evm_execution::account_storage::init_account_storage().unwrap(); + + let address = address_from_str("af1276cbb260bb13deddb4209ae99ae6e497f446"); + let balance = U256::from(1000); + set_balance(&mut host, &mut evm_account_storage, address, balance); + + let fee_updates = FeeUpdates { + overall_gas_used: U256::zero(), + overall_gas_price: U256::zero(), + burn_amount: U256::zero(), + charge_user_amount: balance / 2, + compensate_sequencer_amount: U256::zero(), + }; + + // Act + let result = fee_updates.apply(&mut host, &mut evm_account_storage, address); + + // Assert + assert!(result.is_ok()); + let new_balance = get_balance(&mut host, &mut evm_account_storage, address); + assert_eq!(balance / 2, new_balance); + } + + #[test] + fn apply_fails_user_charge_too_large() { + // Arrange + let mut host = MockHost::default(); + let mut evm_account_storage = + evm_execution::account_storage::init_account_storage().unwrap(); + + let address = address_from_str("af1276cbb260bb13deddb4209ae99ae6e497f446"); + let balance = U256::from(1000); + set_balance(&mut host, &mut evm_account_storage, address, balance); + + let fee_updates = FeeUpdates { + overall_gas_used: U256::zero(), + overall_gas_price: U256::zero(), + burn_amount: U256::zero(), + charge_user_amount: balance * 2, + compensate_sequencer_amount: U256::zero(), + }; + + // Act + let result = fee_updates.apply(&mut host, &mut evm_account_storage, address); + + // Assert + assert!(result.is_err()); + let new_balance = get_balance(&mut host, &mut evm_account_storage, address); + assert_eq!(balance, new_balance); + } + + fn address_from_str(s: &str) -> H160 { + let data = &hex::decode(s).unwrap(); + H160::from_slice(data) + } + + fn get_balance( + host: &mut MockHost, + evm_account_storage: &mut EthereumAccountStorage, + address: H160, + ) -> U256 { + let account = evm_account_storage + .get_or_create(host, &account_path(&address).unwrap()) + .unwrap(); + account.balance(host).unwrap() + } + + fn set_balance( + host: &mut MockHost, + evm_account_storage: &mut EthereumAccountStorage, + address: H160, + balance: U256, + ) { + let mut account = evm_account_storage + .get_or_create(host, &account_path(&address).unwrap()) + .unwrap(); + assert!(account.balance(host).unwrap().is_zero()); + + account.balance_add(host, balance).unwrap(); + } + + fn mock_execution_outcome(gas_used: u64) -> ExecutionOutcome { + ExecutionOutcome { + gas_used, + is_success: true, + reason: ExitReason::Succeed(ExitSucceed::Stopped), + new_address: None, + logs: Vec::new(), + result: None, + withdrawals: Vec::new(), + estimated_ticks_used: 1, + } + } + + fn mock_tx(max_fee_per_gas: U256) -> EthereumTransactionCommon { + EthereumTransactionCommon::new( + tezos_ethereum::transaction::TransactionType::Eip1559, + None, + U256::zero(), + U256::zero(), + max_fee_per_gas, + 0, + Some(H160::zero()), + U256::zero(), + vec![], + vec![], + None, + ) + } +} diff --git a/etherlink/kernel_evm/kernel/src/lib.rs b/etherlink/kernel_evm/kernel/src/lib.rs index bfd98c5fafb839d3be9f388ff0bac590f0ec0145..98297bdb89e91168870315e315fe695f64d9e4c3 100644 --- a/etherlink/kernel_evm/kernel/src/lib.rs +++ b/etherlink/kernel_evm/kernel/src/lib.rs @@ -19,10 +19,10 @@ use migration::MigrationStatus; use primitive_types::U256; use storage::{ read_admin, read_base_fee_per_gas, read_chain_id, read_delayed_transaction_bridge, - read_kernel_version, read_last_info_per_level_timestamp, + read_flat_fee, read_kernel_version, read_last_info_per_level_timestamp, read_last_info_per_level_timestamp_stats, read_sequencer_admin, read_ticketer, - sequencer, store_base_fee_per_gas, store_chain_id, store_kernel_version, - store_storage_version, STORAGE_VERSION, STORAGE_VERSION_PATH, + sequencer, store_base_fee_per_gas, store_chain_id, store_flat_fee, + store_kernel_version, store_storage_version, STORAGE_VERSION, STORAGE_VERSION_PATH, }; use tezos_crypto_rs::hash::ContractKt1Hash; use tezos_ethereum::block::BlockFees; @@ -39,6 +39,7 @@ mod blueprint; mod blueprint_storage; mod delayed_inbox; mod error; +mod fees; mod inbox; mod indexable_storage; mod linked_list; @@ -64,6 +65,9 @@ pub const CHAIN_ID: u32 = 1337; /// Distinct from 'intrinsic base fee' of a simple Eth transfer: which costs 21_000 gas. pub const BASE_FEE_PER_GAS: u32 = 21_000; +/// Default base fee (flat), applied to every transaction. Set to 0.005 tez. +pub const FLAT_FEE: u64 = 5 * 10_u64.pow(15); + /// The configuration for the EVM execution. pub const CONFIG: Config = Config::shanghai(); @@ -148,7 +152,16 @@ fn retrieve_block_fees(host: &mut Host) -> Result flat_fee, + Err(_) => { + let flat_fee = U256::from(FLAT_FEE); + store_flat_fee(host, flat_fee)?; + flat_fee + } + }; + + let block_fees = BlockFees::new(base_fee_per_gas, flat_fee); Ok(block_fees) } @@ -342,6 +355,14 @@ mod tests { const DUMMY_CHAIN_ID: U256 = U256::one(); const DUMMY_BASE_FEE_PER_GAS: u64 = 12345u64; + const DUMMY_FLAT_FEE: u64 = 21_100_000u64; + + fn dummy_block_fees() -> BlockFees { + BlockFees::new( + U256::from(DUMMY_BASE_FEE_PER_GAS), + U256::from(DUMMY_FLAT_FEE), + ) + } fn set_balance( host: &mut Host, @@ -500,7 +521,7 @@ mod tests { crate::upgrade::store_kernel_upgrade(&mut host, &broken_kernel_upgrade) .expect("Should be able to store kernel upgrade"); - let block_fees = BlockFees::new(DUMMY_BASE_FEE_PER_GAS.into()); + let block_fees = dummy_block_fees(); // If the upgrade is started, it should raise an error crate::block::produce( diff --git a/etherlink/kernel_evm/kernel/src/simulation.rs b/etherlink/kernel_evm/kernel/src/simulation.rs index 6f3aefad7b33a50b3de0d0a7853b602157cce2dd..6ca8b7131b20c489e24065aef6d72cfec12622f1 100644 --- a/etherlink/kernel_evm/kernel/src/simulation.rs +++ b/etherlink/kernel_evm/kernel/src/simulation.rs @@ -7,6 +7,7 @@ // Module containing most Simulation related code, in one place, to be deleted // when the proxy node simulates directly +use crate::fees::{simulation_add_gas_for_fees, tx_execution_gas_limit}; use crate::{error::Error, error::StorageError, storage}; use crate::{ @@ -141,7 +142,14 @@ impl Evaluation { Ok(EvaluationOutcome::OutOfTicks) } Err(err) => Ok(EvaluationOutcome::EvaluationError(err)), - Ok(outcome) => Ok(EvaluationOutcome::Outcome(outcome)), + Ok(None) => Ok(EvaluationOutcome::Outcome(None)), + Ok(Some(outcome)) => { + let outcome = + simulation_add_gas_for_fees(outcome, &block_fees, gas_price) + .map_err(Error::Simulation)?; + + Ok(EvaluationOutcome::Outcome(Some(outcome))) + } } } } @@ -233,12 +241,7 @@ impl TxValidation { tx_data_size, ); - let gas_price = if let Ok(gas_price) = transaction.overall_gas_price(&block_fees) - { - gas_price - } else { - block_fees.base_fee_per_gas() - }; + let gas_limit = tx_execution_gas_limit(transaction, &block_fees)?; match run_transaction( host, @@ -249,8 +252,8 @@ impl TxValidation { transaction.to, *caller, transaction.data.clone(), - Some(transaction.execution_gas_limit()), // gas could be omitted - gas_price, + Some(gas_limit), // gas could be omitted + block_fees.base_fee_per_gas(), Some(transaction.value), false, allocated_ticks, @@ -924,6 +927,7 @@ mod tests { #[test] fn test_tx_validation_gas_price() { let mut host = MockHost::default(); + let block_fees = crate::retrieve_block_fees(&mut host).unwrap(); let transaction = EthereumTransactionCommon::new( TransactionType::Eip1559, @@ -931,7 +935,7 @@ mod tests { U256::from(0), U256::zero(), U256::from(1), - 21000, + 21000 + block_fees.gas_for_fees(U256::one()).as_u64(), Some(H160::zero()), U256::zero(), vec![], @@ -961,6 +965,7 @@ mod tests { ) .unwrap(); let result = simulation.run(&mut host); + println!("{result:?}"); assert!(result.is_ok()); assert_eq!(TxValidationOutcome::MaxGasFeeTooLow, result.unwrap()); } diff --git a/etherlink/kernel_evm/kernel/src/storage.rs b/etherlink/kernel_evm/kernel/src/storage.rs index 34d8ab3673ea5bc05abf8e67d16f37f1642e17b4..39010d35410d78c4fbe658a79680003325c4820f 100644 --- a/etherlink/kernel_evm/kernel/src/storage.rs +++ b/etherlink/kernel_evm/kernel/src/storage.rs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2023 Nomadic Labs // SPDX-FileCopyrightText: 2023 Functori // SPDX-FileCopyrightText: 2023 Marigold +// SPDX-FileCopyrightText: 2024 Trilitech // // SPDX-License-Identifier: MIT @@ -54,6 +55,7 @@ pub const EVM_TRANSACTIONS_OBJECTS: RefPath = const EVM_CHAIN_ID: RefPath = RefPath::assert_from(b"/chain_id"); const EVM_BASE_FEE_PER_GAS: RefPath = RefPath::assert_from(b"/base_fee_per_gas"); +const EVM_FLAT_FEE: RefPath = RefPath::assert_from(b"/flat_fee"); /// Path to the last info per level timestamp seen. const EVM_INFO_PER_LEVEL_TIMESTAMP: RefPath = @@ -555,6 +557,17 @@ pub fn read_base_fee_per_gas(host: &mut Host) -> Result Result<(), Error> { + write_u256(host, &EVM_FLAT_FEE.into(), base_fee_per_gas) +} + +pub fn read_flat_fee(host: &impl Runtime) -> Result { + read_u256(host, &EVM_FLAT_FEE.into()) +} + pub fn store_timestamp_path( host: &mut Host, path: &OwnedPath, diff --git a/etherlink/tezt/lib/rpc.ml b/etherlink/tezt/lib/rpc.ml index 5f85c7028bace058ec2b80122b43dbf14cdeb7a1..9f581bf098004290ca3e4bb964d9f9365992bd08 100644 --- a/etherlink/tezt/lib/rpc.ml +++ b/etherlink/tezt/lib/rpc.ml @@ -2,6 +2,7 @@ (* *) (* SPDX-License-Identifier: MIT *) (* Copyright (c) 2023 Nomadic Labs *) +(* Copyright (c) 2024 Trilitech *) (* *) (*****************************************************************************) @@ -43,6 +44,14 @@ let get_block_by_number ?(full_tx_objects = false) ~block evm_node = (fun json -> JSON.(json |-> "result" |> Block.of_json)) json) +let get_gas_price evm_node = + let* json = + Evm_node.call_evm_rpc + evm_node + {method_ = "eth_gasPrice"; parameters = `Null} + in + return JSON.(json |-> "result" |> as_string |> Int32.of_string) + module Syntax = struct let ( let*@ ) x f = let* r = x in diff --git a/etherlink/tezt/lib/rpc.mli b/etherlink/tezt/lib/rpc.mli index 68eb3f693fb28c3fa62080b9e7b1f7cf99773fe6..33466b91e8f071dff415e70fa3c77bf87ff0df94 100644 --- a/etherlink/tezt/lib/rpc.mli +++ b/etherlink/tezt/lib/rpc.mli @@ -2,6 +2,7 @@ (* *) (* SPDX-License-Identifier: MIT *) (* Copyright (c) 2023 Nomadic Labs *) +(* Copyright (c) 2024 Trilitech *) (* *) (*****************************************************************************) @@ -20,6 +21,8 @@ val get_block_by_number : Evm_node.t -> (Block.t, error) result Lwt.t +val get_gas_price : Evm_node.t -> Int32.t Lwt.t + module Syntax : sig val ( let*@ ) : ('a, error) result Lwt.t -> ('a -> 'c Lwt.t) -> 'c Lwt.t diff --git a/etherlink/tezt/tests/evm_rollup.ml b/etherlink/tezt/tests/evm_rollup.ml index 284b1b4fc32fa04fe1a6d34095057cd0bc963d14..ae3fb84e60a1f462d497f8a451a4c5be2974a149 100644 --- a/etherlink/tezt/tests/evm_rollup.ml +++ b/etherlink/tezt/tests/evm_rollup.ml @@ -85,21 +85,32 @@ let check_tx_failed ~endpoint ~tx = Check.(is_false status) ~error_msg:"Expected transaction to fail." ; unit -(* Check simple transfer base fee is correct +(* Check simple transfer flat fee is correct We apply a flat base fee to every tx - which is paid through an - increase in estimate gas used at the given price. + increase in in either/both gas_used, gas_price. + + We prefer to keep [gas_used == execution_gas_used] where possible, but + when this results in [gas_price > tx.max_price_per_gas], we set gas_price to + tx.max_price_per_gas, and increase the gas_used in the receipt. *) -let check_tx_gas_for_flat_fee ~flat_fee ~gas_price ~gas_used = - let expected_gas_used = 21000l in - let gas_for_flat_fee = Z.of_int32 (Int32.sub gas_used expected_gas_used) in - let gas_price = gas_price |> Z.of_int32 in - (* integer division truncates - so we make sure to take ceil(a/b) not floor(a/b). - otherwise we'd end up with very slightly less gas than needed to cover the - flat fee. *) - let actual_gas_for_flat_fee = Wei.cdiv flat_fee gas_price in - Check.((actual_gas_for_flat_fee = Wei.to_wei_z gas_for_flat_fee) Wei.typ) - ~error_msg:"Unexpected flat fee" +let check_tx_gas_for_flat_fee ~flat_fee ~expected_execution_gas ~gas_price + ~gas_used ~base_fee_per_gas = + (* execution gas fee *) + let expected_execution_gas = Z.of_int expected_execution_gas in + let expected_base_fee_per_gas = Z.of_int32 base_fee_per_gas in + let execution_gas_fee = + Z.mul expected_execution_gas expected_base_fee_per_gas + in + (* total fee 'in gas' *) + let expected_total_fee = + Z.add (Wei.of_wei_z flat_fee) execution_gas_fee |> Z.to_int64 + in + let total_fee_receipt = + Z.(mul (of_int32 gas_price) (of_int32 gas_used)) |> Z.to_int64 + in + Check.((total_fee_receipt > expected_total_fee) int64) + ~error_msg:"total fee in receipt %L did not cover expected fees of %R" let check_status_n_logs ~endpoint ~status ~logs ~tx = let* receipt = Eth_cli.get_receipt ~endpoint ~tx in @@ -1420,7 +1431,8 @@ let make_transfer ?data ~value ~sender ~receiver full_evm_setup = receiver_balance_after; } -let transfer ?data ~flat_fee ~evm_setup () = +let transfer ?data ~flat_fee ~expected_execution_gas ~evm_setup () = + let* base_fee_per_gas = Rpc.get_gas_price evm_setup.evm_node in let sender, receiver = (Eth_account.bootstrap_accounts.(0), Eth_account.bootstrap_accounts.(1)) in @@ -1470,25 +1482,38 @@ let transfer ?data ~flat_fee ~evm_setup () = ~error_msg:"Unexpected transaction's receiver" ; Check.((tx_object.value = value) Wei.typ) ~error_msg:"Unexpected transaction's value" ; - if Option.is_none data then - check_tx_gas_for_flat_fee ~flat_fee ~gas_used ~gas_price ; + check_tx_gas_for_flat_fee + ~flat_fee + ~expected_execution_gas + ~gas_used + ~gas_price + ~base_fee_per_gas ; unit let test_l2_transfer = - let flat_fee = Wei.zero in - let test_f ~protocol:_ ~evm_setup = transfer ~evm_setup ~flat_fee () in + let flat_fee = Wei.of_eth_string "0.000123" in + let expected_execution_gas = 21000 in + let test_f ~protocol:_ ~evm_setup = + transfer ~evm_setup ~flat_fee ~expected_execution_gas () + in let title = "Check L2 transfers are applied" in let tags = ["evm"; "l2_transfer"] in - register_both ~title ~tags test_f + register_both ~title ~tags ~flat_fee test_f let test_chunked_transaction = - let flat_fee = Wei.zero in + let flat_fee = Wei.of_eth_string "0.005" in + let expected_execution_gas = 117000 in let test_f ~protocol:_ ~evm_setup = - transfer ~data:("0x" ^ String.make 12_000 'a') ~flat_fee ~evm_setup () + transfer + ~data:("0x" ^ String.make 12_000 'a') + ~flat_fee + ~evm_setup + ~expected_execution_gas + () in let title = "Check L2 chunked transfers are applied" in let tags = ["evm"; "l2_transfer"; "chunked"] in - register_both ~title ~tags test_f + register_both ~title ~tags ~flat_fee test_f let test_rpc_txpool_content = register_both