From f40fb09f42ffd3c0e4ab2edbe3f35a8d4604e9d5 Mon Sep 17 00:00:00 2001 From: Emma Turner Date: Thu, 18 Jan 2024 09:56:18 +0000 Subject: [PATCH] Kernel/EVM: encapsulate TxCommon gas limit Allows adding custom logic as to whether the gas limit should be that of execution or that with fees incorporated --- .../kernel_evm/ethereum/src/tx_common.rs | 56 +++++++++++-- etherlink/kernel_evm/kernel/src/apply.rs | 78 +++++++++++-------- etherlink/kernel_evm/kernel/src/block.rs | 52 ++++++------- .../kernel/src/block_in_progress.rs | 26 +++---- etherlink/kernel_evm/kernel/src/blueprint.rs | 28 +++---- .../kernel_evm/kernel/src/delayed_inbox.rs | 27 +++---- etherlink/kernel_evm/kernel/src/lib.rs | 20 ++--- .../kernel/src/sequencer_blueprint.rs | 27 +++---- etherlink/kernel_evm/kernel/src/simulation.rs | 54 ++++++------- etherlink/kernel_evm/kernel/src/tick_model.rs | 2 +- 10 files changed, 213 insertions(+), 157 deletions(-) diff --git a/etherlink/kernel_evm/ethereum/src/tx_common.rs b/etherlink/kernel_evm/ethereum/src/tx_common.rs index 419fcf64d688..c26948f187b2 100644 --- a/etherlink/kernel_evm/ethereum/src/tx_common.rs +++ b/etherlink/kernel_evm/ethereum/src/tx_common.rs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022-2023 TriliTech +// SPDX-FileCopyrightText: 2022-2024 TriliTech // SPDX-FileCopyrightText: 2023 Marigold // SPDX-FileCopyrightText: 2023 Nomadic Labs // @@ -87,12 +87,14 @@ pub struct EthereumTransactionCommon { /// More details see here https://eips.ethereum.org/EIPS/eip-1559#abstract pub max_fee_per_gas: U256, - /// A scalar value equal to the maximum - /// amount of gas that should be used in executing - /// this transaction. This is paid up-front, before any - /// computation is done and may not be increased - /// later - pub gas_limit: u64, + /// The maximum amount of gas that the user is willing to pay. + /// + /// *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 pub to: Option, @@ -113,6 +115,35 @@ pub struct EthereumTransactionCommon { } impl EthereumTransactionCommon { + #[allow(clippy::too_many_arguments)] + pub fn new( + type_: TransactionType, + chain_id: Option, + nonce: U256, + max_priority_fee_per_gas: U256, + max_fee_per_gas: U256, + gas_limit: u64, + to: Option, + value: U256, + data: Vec, + access_list: AccessList, + signature: Option, + ) -> Self { + Self { + type_, + chain_id, + nonce, + max_priority_fee_per_gas, + max_fee_per_gas, + gas_limit, + to, + value, + data, + access_list, + signature, + } + } + // This decoding function encapsulates logic of decoding (v, r, s). // There might be 3 possible cases: // - unsigned NON-legacy: (0, 0, 0) @@ -537,6 +568,17 @@ impl EthereumTransactionCommon { .checked_add(block_base_fee_per_gas) .ok_or_else(|| anyhow::anyhow!("Overflow")) } + + /// 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 { + self.gas_limit + } } impl From for EthereumTransactionCommon { diff --git a/etherlink/kernel_evm/kernel/src/apply.rs b/etherlink/kernel_evm/kernel/src/apply.rs index 1e1e34a87974..b91942d1c7a3 100644 --- a/etherlink/kernel_evm/kernel/src/apply.rs +++ b/etherlink/kernel_evm/kernel/src/apply.rs @@ -214,7 +214,7 @@ fn is_valid_ethereum_transaction_common( return Ok(Validity::InvalidChainId); } // Gas limit is bounded. - if transaction.gas_limit > MAX_TRANSACTION_GAS_LIMIT { + if transaction.execution_gas_limit() > MAX_TRANSACTION_GAS_LIMIT { log!(host, Debug, "Transaction status: ERROR_GASLIMIT"); return Ok(Validity::InvalidGasLimit); } @@ -247,10 +247,10 @@ fn is_valid_ethereum_transaction_common( }; // The sender account balance contains at least the cost. - let cost = U256::from(transaction.gas_limit).saturating_mul(effective_gas_price); + let execution_gas_limit = U256::from(transaction.execution_gas_limit()); + let cost = execution_gas_limit.saturating_mul(effective_gas_price); // The sender can afford the max gas fee he set, see EIP-1559 - let max_fee = - U256::from(transaction.gas_limit).saturating_mul(transaction.max_fee_per_gas); + let max_fee = execution_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); @@ -305,7 +305,7 @@ fn apply_ethereum_transaction_common( let to = transaction.to; let call_data = transaction.data.clone(); - let gas_limit = transaction.gas_limit; + let gas_limit = transaction.execution_gas_limit(); let value = transaction.value; let execution_outcome = match run_transaction( host, @@ -605,19 +605,19 @@ mod tests { } fn valid_tx() -> EthereumTransactionCommon { - let transaction = EthereumTransactionCommon { - type_: TransactionType::Eip1559, - chain_id: Some(CHAIN_ID.into()), - nonce: U256::from(0), - max_priority_fee_per_gas: U256::zero(), - max_fee_per_gas: U256::from(21000), - gas_limit: 21000, - to: Some(H160::zero()), - value: U256::zero(), - data: vec![], - access_list: vec![], - signature: None, - }; + let transaction = EthereumTransactionCommon::new( + TransactionType::Eip1559, + Some(CHAIN_ID.into()), + U256::from(0), + U256::zero(), + U256::from(21000), + 21000, + Some(H160::zero()), + U256::zero(), + vec![], + vec![], + None, + ); // sign tx resign(transaction) } @@ -791,8 +791,20 @@ 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(); - transaction.gas_limit = MAX_TRANSACTION_GAS_LIMIT + 1; + let mut transaction = EthereumTransactionCommon::new( + TransactionType::Eip1559, + Some(CHAIN_ID.into()), + U256::from(0), + U256::zero(), + U256::from(21000), + MAX_TRANSACTION_GAS_LIMIT + 1, + Some(H160::zero()), + U256::zero(), + vec![], + vec![], + None, + ); + // sign tx transaction = resign(transaction); // fund account @@ -887,19 +899,19 @@ mod tests { fn test_no_underflow_make_object_tx() { let transaction = Transaction { tx_hash: [0u8; TRANSACTION_HASH_SIZE], - content: TransactionContent::Ethereum(EthereumTransactionCommon { - type_: TransactionType::Eip1559, - chain_id: Some(U256::from(1)), - nonce: U256::from(1), - max_priority_fee_per_gas: U256::zero(), - max_fee_per_gas: U256::from(1), - gas_limit: 21000, - to: Some(H160::zero()), - value: U256::zero(), - data: vec![], - access_list: vec![], - signature: None, - }), + 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 obj = make_object_info( diff --git a/etherlink/kernel_evm/kernel/src/block.rs b/etherlink/kernel_evm/kernel/src/block.rs index eac8cfb58cab..d622e4c44660 100644 --- a/etherlink/kernel_evm/kernel/src/block.rs +++ b/etherlink/kernel_evm/kernel/src/block.rs @@ -384,19 +384,19 @@ mod tests { let to = address_from_str("423163e58aabec5daa3dd1130b759d24bef0f6ea"); let value = U256::from(500000000u64); let data: Vec = vec![]; - EthereumTransactionCommon { - type_: TransactionType::Legacy, + EthereumTransactionCommon::new( + TransactionType::Legacy, chain_id, nonce, - max_priority_fee_per_gas: gas_price, - max_fee_per_gas: gas_price, + gas_price, + gas_price, gas_limit, to, value, data, - access_list: vec![], - signature: Some(TxSignature::new(v, r, s).unwrap()), - } + vec![], + Some(TxSignature::new(v, r, s).unwrap()), + ) } fn dummy_eth_caller() -> H160 { @@ -444,19 +444,19 @@ mod tests { // corresponding contract is kernel_benchmark/scripts/benchmarks/contracts/storage.sol let data: Vec = hex::decode("608060405234801561001057600080fd5b5061017f806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80634e70b1dc1461004657806360fe47b1146100645780636d4ce63c14610080575b600080fd5b61004e61009e565b60405161005b91906100d0565b60405180910390f35b61007e6004803603810190610079919061011c565b6100a4565b005b6100886100ae565b60405161009591906100d0565b60405180910390f35b60005481565b8060008190555050565b60008054905090565b6000819050919050565b6100ca816100b7565b82525050565b60006020820190506100e560008301846100c1565b92915050565b600080fd5b6100f9816100b7565b811461010457600080fd5b50565b600081359050610116816100f0565b92915050565b600060208284031215610132576101316100eb565b5b600061014084828501610107565b9150509291505056fea2646970667358221220ec57e49a647342208a1f5c9b1f2049bf1a27f02e19940819f38929bf67670a5964736f6c63430008120033").unwrap(); - let tx = EthereumTransactionCommon { - type_: tezos_ethereum::transaction::TransactionType::Legacy, - chain_id: Some(DUMMY_CHAIN_ID), + let tx = EthereumTransactionCommon::new( + tezos_ethereum::transaction::TransactionType::Legacy, + Some(DUMMY_CHAIN_ID), nonce, - max_priority_fee_per_gas: gas_price, - max_fee_per_gas: gas_price, + gas_price, + gas_price, gas_limit, - to: None, + None, value, data, - access_list: vec![], - signature: None, - }; + vec![], + None, + ); tx.sign_transaction(private_key.to_string()).unwrap() } @@ -1136,7 +1136,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.gas_limit; + let fees = U256::from(21000) * tx.execution_gas_limit(); set_balance(&mut host, &mut evm_account_storage, &caller, fees); // Prepare a invalid transaction, i.e. with not enough funds. @@ -1238,19 +1238,19 @@ mod tests { let gas_limit = 21000; let value = U256::from(1); let to = address_from_str("423163e58aabec5daa3dd1130b759d24bef0f6ea"); - let tx = EthereumTransactionCommon { - type_: TransactionType::Legacy, - chain_id: Some(U256::one()), + let tx = EthereumTransactionCommon::new( + TransactionType::Legacy, + Some(U256::one()), nonce, - max_fee_per_gas: gas_price, - max_priority_fee_per_gas: gas_price, + gas_price, + gas_price, gas_limit, to, value, - data: vec![], - access_list: vec![], - signature: None, - }; + vec![], + vec![], + None, + ); // corresponding caller's address is 0xaf1276cbb260bb13deddb4209ae99ae6e497f446 tx.sign_transaction( diff --git a/etherlink/kernel_evm/kernel/src/block_in_progress.rs b/etherlink/kernel_evm/kernel/src/block_in_progress.rs index fa6c0db78da5..5ad9d7c0c34b 100644 --- a/etherlink/kernel_evm/kernel/src/block_in_progress.rs +++ b/etherlink/kernel_evm/kernel/src/block_in_progress.rs @@ -436,23 +436,23 @@ mod tests { } fn dummy_etc(i: u8) -> EthereumTransactionCommon { - EthereumTransactionCommon { - type_: TransactionType::Legacy, - chain_id: Some(U256::from(i)), - nonce: U256::from(i), - max_fee_per_gas: U256::from(i), - max_priority_fee_per_gas: U256::from(i), - gas_limit: i.into(), - to: None, - value: U256::from(i), - data: Vec::new(), - access_list: vec![], - signature: Some(new_sig_unsafe( + EthereumTransactionCommon::new( + TransactionType::Legacy, + Some(U256::from(i)), + U256::from(i), + U256::from(i), + U256::from(i), + i.into(), + None, + U256::from(i), + Vec::new(), + vec![], + Some(new_sig_unsafe( (36 + i * 2).into(), // need to be consistent with chain_id H256::from([i; 32]), H256::from([i; 32]), )), - } + ) } fn dummy_tx_eth(i: u8) -> Transaction { diff --git a/etherlink/kernel_evm/kernel/src/blueprint.rs b/etherlink/kernel_evm/kernel/src/blueprint.rs index 85cd1e735224..431dcc70cd8f 100644 --- a/etherlink/kernel_evm/kernel/src/blueprint.rs +++ b/etherlink/kernel_evm/kernel/src/blueprint.rs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022-2023 TriliTech +// SPDX-FileCopyrightText: 2022-2024 TriliTech // SPDX-FileCopyrightText: 2023 Nomadic Labs // SPDX-FileCopyrightText: 2023 Functori // SPDX-FileCopyrightText: 2023 Marigold @@ -63,19 +63,19 @@ mod tests { Some(H160::from_slice(data)) } fn tx_(i: u64) -> EthereumTransactionCommon { - EthereumTransactionCommon { - type_: tezos_ethereum::transaction::TransactionType::Legacy, - chain_id: Some(U256::one()), - nonce: U256::from(i), - max_priority_fee_per_gas: U256::from(40000000u64), - max_fee_per_gas: U256::from(40000000u64), - gas_limit: 21000u64, - to: address_from_str("423163e58aabec5daa3dd1130b759d24bef0f6ea"), - value: U256::from(500000000u64), - data: vec![], - access_list: vec![], - signature: None, - } + EthereumTransactionCommon::new( + tezos_ethereum::transaction::TransactionType::Legacy, + Some(U256::one()), + U256::from(i), + U256::from(40000000u64), + U256::from(40000000u64), + 21000u64, + address_from_str("423163e58aabec5daa3dd1130b759d24bef0f6ea"), + U256::from(500000000u64), + vec![], + vec![], + None, + ) } fn dummy_transaction(i: u8) -> Transaction { diff --git a/etherlink/kernel_evm/kernel/src/delayed_inbox.rs b/etherlink/kernel_evm/kernel/src/delayed_inbox.rs index 1185f282a3cb..24cd49f144e3 100644 --- a/etherlink/kernel_evm/kernel/src/delayed_inbox.rs +++ b/etherlink/kernel_evm/kernel/src/delayed_inbox.rs @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2023 Marigold +// SPDX-FileCopyrightText: 2024 Trilitech use crate::{ inbox::{Deposit, Transaction, TransactionContent}, @@ -173,19 +174,19 @@ mod tests { } fn tx_(i: u64) -> EthereumTransactionCommon { - EthereumTransactionCommon { - type_: tezos_ethereum::transaction::TransactionType::Legacy, - chain_id: Some(U256::one()), - nonce: U256::from(i), - max_priority_fee_per_gas: U256::from(40000000u64), - max_fee_per_gas: U256::from(40000000u64), - gas_limit: 21000u64, - to: address_from_str("423163e58aabec5daa3dd1130b759d24bef0f6ea"), - value: U256::from(500000000u64), - data: vec![], - access_list: vec![], - signature: None, - } + EthereumTransactionCommon::new( + tezos_ethereum::transaction::TransactionType::Legacy, + Some(U256::one()), + U256::from(i), + U256::from(40000000u64), + U256::from(40000000u64), + 21000u64, + address_from_str("423163e58aabec5daa3dd1130b759d24bef0f6ea"), + U256::from(500000000u64), + vec![], + vec![], + None, + ) } fn dummy_transaction(i: u8) -> Transaction { diff --git a/etherlink/kernel_evm/kernel/src/lib.rs b/etherlink/kernel_evm/kernel/src/lib.rs index 7a3df4ece701..f74eff6ebe90 100644 --- a/etherlink/kernel_evm/kernel/src/lib.rs +++ b/etherlink/kernel_evm/kernel/src/lib.rs @@ -1,5 +1,5 @@ // SPDX-FileCopyrightText: 2023 Nomadic Labs -// SPDX-FileCopyrightText: 2023 TriliTech +// SPDX-FileCopyrightText: 2023-2024 TriliTech // SPDX-FileCopyrightText: 2023 Functori // SPDX-FileCopyrightText: 2023 Marigold // @@ -369,19 +369,19 @@ mod tests { let gas_limit = 21000; let value = U256::from(1); let to = address_from_str("423163e58aabec5daa3dd1130b759d24bef0f6ea"); - let tx = EthereumTransactionCommon { - type_: TransactionType::Legacy, - chain_id: Some(U256::one()), + let tx = EthereumTransactionCommon::new( + TransactionType::Legacy, + Some(U256::one()), nonce, - max_fee_per_gas: gas_price, - max_priority_fee_per_gas: gas_price, + gas_price, + gas_price, gas_limit, to, value, - data: vec![], - access_list: vec![], - signature: None, - }; + vec![], + vec![], + None, + ); // corresponding caller's address is 0xaf1276cbb260bb13deddb4209ae99ae6e497f446 tx.sign_transaction( diff --git a/etherlink/kernel_evm/kernel/src/sequencer_blueprint.rs b/etherlink/kernel_evm/kernel/src/sequencer_blueprint.rs index 1f16a758b860..7cc007244ba0 100644 --- a/etherlink/kernel_evm/kernel/src/sequencer_blueprint.rs +++ b/etherlink/kernel_evm/kernel/src/sequencer_blueprint.rs @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2023 Nomadic Labs +// SPDX-FileCopyrightText: 2024 Trilitech // // SPDX-License-Identifier: MIT @@ -161,19 +162,19 @@ mod tests { } fn tx_(i: u64) -> EthereumTransactionCommon { - EthereumTransactionCommon { - type_: tezos_ethereum::transaction::TransactionType::Legacy, - chain_id: Some(U256::one()), - nonce: U256::from(i), - max_priority_fee_per_gas: U256::from(40000000u64), - max_fee_per_gas: U256::from(40000000u64), - gas_limit: 21000u64, - to: address_from_str("423163e58aabec5daa3dd1130b759d24bef0f6ea"), - value: U256::from(500000000u64), - data: vec![], - access_list: vec![], - signature: None, - } + EthereumTransactionCommon::new( + tezos_ethereum::transaction::TransactionType::Legacy, + Some(U256::one()), + U256::from(i), + U256::from(40000000u64), + U256::from(40000000u64), + 21000u64, + address_from_str("423163e58aabec5daa3dd1130b759d24bef0f6ea"), + U256::from(500000000u64), + vec![], + vec![], + None, + ) } fn dummy_transaction(i: u8) -> Transaction { diff --git a/etherlink/kernel_evm/kernel/src/simulation.rs b/etherlink/kernel_evm/kernel/src/simulation.rs index 8879c3a18004..aab55b1502d9 100644 --- a/etherlink/kernel_evm/kernel/src/simulation.rs +++ b/etherlink/kernel_evm/kernel/src/simulation.rs @@ -227,7 +227,7 @@ impl TxValidation { return Ok(TxValidationOutcome::InvalidChainId); } // Check if the gas limit is not too high - if tx.gas_limit > MAX_TRANSACTION_GAS_LIMIT { + if tx.execution_gas_limit() > MAX_TRANSACTION_GAS_LIMIT { return Ok(TxValidationOutcome::GasLimitTooHigh); } // Check if the gas price is high enough @@ -787,19 +787,19 @@ mod tests { let signature = TxSignature::new(v, r, s).unwrap(); - EthereumTransactionCommon { - type_: TransactionType::Legacy, - chain_id: Some(1337.into()), - nonce: 0.into(), - max_priority_fee_per_gas: U256::default(), - max_fee_per_gas: U256::default(), - gas_limit: 2000000, - to: Some(H160::default()), - value: U256::default(), - data: vec![], - signature: Some(signature), - access_list: Vec::default(), - } + EthereumTransactionCommon::new( + TransactionType::Legacy, + Some(1337.into()), + 0.into(), + U256::default(), + U256::default(), + 2000000, + Some(H160::default()), + U256::default(), + vec![], + Vec::default(), + Some(signature), + ) }; let hex = "f8628080831e84809400000000000000000000000000000000000000008080820a96a00c4604516693aafd2e74a993c280455fcad144a414f5aa580d96f3c51d4428e5a0630fb7fc1af4c1c1a82cabb4ef9d12f8fc2e54a047eb3e3bdffc9d23cd07a94e"; @@ -836,19 +836,19 @@ mod tests { fn test_tx_validation_gas_price() { let mut host = MockHost::default(); - let transaction = EthereumTransactionCommon { - type_: TransactionType::Eip1559, - chain_id: Some(U256::from(1)), - nonce: U256::from(0), - max_priority_fee_per_gas: U256::zero(), - max_fee_per_gas: U256::from(1), - gas_limit: 21000, - to: Some(H160::zero()), - value: U256::zero(), - data: vec![], - access_list: vec![], - signature: None, - }; + let transaction = EthereumTransactionCommon::new( + TransactionType::Eip1559, + Some(U256::from(1)), + U256::from(0), + U256::zero(), + U256::from(1), + 21000, + Some(H160::zero()), + U256::zero(), + vec![], + vec![], + None, + ); let signed = transaction .sign_transaction( "e922354a3e5902b5ac474f3ff08a79cff43533826b8f451ae2190b65a9d26158" diff --git a/etherlink/kernel_evm/kernel/src/tick_model.rs b/etherlink/kernel_evm/kernel/src/tick_model.rs index b124cca6cfb8..67793eecbedb 100644 --- a/etherlink/kernel_evm/kernel/src/tick_model.rs +++ b/etherlink/kernel_evm/kernel/src/tick_model.rs @@ -113,7 +113,7 @@ fn estimate_ticks_for_transaction(transaction: &Transaction) -> u64 { match &transaction.content { crate::inbox::TransactionContent::Deposit(_) => constants::TICKS_FOR_DEPOSIT, crate::inbox::TransactionContent::Ethereum(eth) => { - average_ticks_of_gas(eth.gas_limit) + average_ticks_of_gas(eth.execution_gas_limit()) .saturating_add(ticks_of_transaction_overhead(tx_data_size)) } } -- GitLab