Skip to content

Commit

Permalink
Pay l1 fee for a halted tx (#571)
Browse files Browse the repository at this point in the history
* Pass args to make test

* Add InfiniteLoop test contract

* Fix Conbase test contract comments

* Pay l1 fee for halted tx
  • Loading branch information
kpp authored May 15, 2024
1 parent 64f21cc commit bc74540
Show file tree
Hide file tree
Showing 9 changed files with 209 additions and 16 deletions.
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ test-legacy: ## Runs test suite with output from tests printed
@cargo test -- --nocapture -Zunstable-options --report-time

test: $(EF_TESTS_DIR) ## Runs test suite using next test
@cargo nextest run --workspace --all-features --no-fail-fast
@cargo nextest run --workspace --all-features --no-fail-fast $(filter-out $@,$(MAKECMDGOALS))

install-dev-tools: ## Installs all necessary cargo helpers
cargo install cargo-llvm-cov
Expand Down Expand Up @@ -94,3 +94,6 @@ $(EF_TESTS_DIR):
.PHONY: ef-tests
ef-tests: $(EF_TESTS_DIR) ## Runs Ethereum Foundation tests.
cargo nextest run -p citrea-evm general_state_tests

%:
@:
21 changes: 9 additions & 12 deletions crates/evm/src/evm/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,19 +356,16 @@ impl<SPEC: Spec, EXT: CitreaExternalExt, DB: Database> CitreaHandler<SPEC, EXT,
let l1_fee_rate = context.external.l1_fee_rate();
let l1_fee = U256::from(diff_size) * U256::from(l1_fee_rate);
context.external.set_tx_info(TxInfo { diff_size, l1_fee });
if result.interpreter_result().is_ok() {
// Deduct L1 fee only if tx is successful.
if context.is_system_caller() {
// System caller doesn't pay L1 fee.
} else {
if let Some(_out_of_funds) = decrease_caller_balance(context, l1_fee)? {
return Err(EVMError::Custom(format!(
"Not enough funds for L1 fee: {}",
l1_fee
)));
}
increase_coinbase_balance(context, l1_fee)?;
if context.is_system_caller() {
// System caller doesn't pay L1 fee.
} else {
if let Some(_out_of_funds) = decrease_caller_balance(context, l1_fee)? {
return Err(EVMError::Custom(format!(
"Not enough funds for L1 fee: {}",
l1_fee
)));
}
increase_coinbase_balance(context, l1_fee)?;
}

revm::handler::mainnet::output(context, result)
Expand Down
2 changes: 1 addition & 1 deletion crates/evm/src/evm/test_data/Coinbase.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
pragma solidity ^0.8.0;

// solc --abi --bin Store.sol -o . --overwrite
// solc --abi --bin Coinbase.sol -o . --overwrite
contract Coinbase {
// Function to reward the miner
function rewardMiner() external payable {
Expand Down
1 change: 1 addition & 0 deletions crates/evm/src/evm/test_data/InfiniteLoop.abi
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"inputs":[],"name":"infiniteLoop","outputs":[],"stateMutability":"pure","type":"function"}]
1 change: 1 addition & 0 deletions crates/evm/src/evm/test_data/InfiniteLoop.bin
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
6080604052348015600e575f80fd5b5060f98061001b5f395ff3fe6080604052348015600e575f80fd5b50600436106026575f3560e01c80631dbf353d14602a575b5f80fd5b60306032565b005b5f5b600115604857806042906081565b90506034565b50565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f819050919050565b5f6089826078565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820360b85760b7604b565b5b60018201905091905056fea264697066735822122063d45c8f67ee9489817454d921213bbbd708c138a0e8b730c7f0f0bfc61ceaac64736f6c63430008190033
14 changes: 14 additions & 0 deletions crates/evm/src/evm/test_data/InfiniteLoop.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: GPL-3

pragma solidity ^0.8.0;

// solc --abi --bin InfiniteLoop.sol -o . --overwrite
contract InfiniteLoop {
// Function to infinitely loop and do nothing.
function infiniteLoop() pure public {
uint256 a = 0;
while (true) {
++a;
}
}
}
65 changes: 65 additions & 0 deletions crates/evm/src/smart_contracts/infinite_loop_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use std::any::Any;

use ethers_contract::BaseContract;
use ethers_core::types::Bytes;

use super::{make_contract_from_abi, test_data_path, TestContract};

/// InfiniteLoopContract wrapper.
pub struct InfiniteLoopContract {
bytecode: Bytes,
base_contract: BaseContract,
}

impl Default for InfiniteLoopContract {
fn default() -> Self {
let contract_data = {
let mut path = test_data_path();
path.push("InfiniteLoop.bin");

let contract_data = std::fs::read_to_string(path).unwrap();
hex::decode(contract_data).unwrap()
};

let contract = {
let mut path = test_data_path();
path.push("InfiniteLoop.abi");

make_contract_from_abi(path)
};

Self {
bytecode: Bytes::from(contract_data),
base_contract: contract,
}
}
}

impl TestContract for InfiniteLoopContract {
/// Caller bytecode.
fn byte_code(&self) -> Bytes {
self.byte_code()
}
/// Dynamically dispatch from trait. Downcast to InfiniteLoopContract.
fn as_any(&self) -> &dyn Any {
self
}
/// Create the default instance of the smart contract.
fn default_(&self) -> Self
where
Self: Sized,
{
Self::default()
}
}

impl InfiniteLoopContract {
/// InfiniteLoop bytecode.
pub fn byte_code(&self) -> Bytes {
self.bytecode.clone()
}
/// Calls InfiniteLoop::infiniteLoop.
pub fn call_infinite_loop(&self) -> Bytes {
self.base_contract.encode("infiniteLoop", ()).unwrap()
}
}
2 changes: 2 additions & 0 deletions crates/evm/src/smart_contracts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod blockhash_contract;
mod caller_contract;
mod coinbase_contract;
mod hive_contract;
mod infinite_loop_contract;
mod logs_contract;
mod payable_contract;
mod self_destructor_contract;
Expand All @@ -18,6 +19,7 @@ use ethers_contract::BaseContract;
use ethers_core::abi::Abi;
use ethers_core::types::Bytes;
pub use hive_contract::HiveContract;
pub use infinite_loop_contract::InfiniteLoopContract;
pub use logs_contract::LogsContract;
pub use payable_contract::SimplePayableContract;
pub use self_destructor_contract::SelfDestructorContract;
Expand Down
114 changes: 112 additions & 2 deletions crates/evm/src/tests/call_tests.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::str::FromStr;

use reth_primitives::constants::ETHEREUM_BLOCK_GAS_LIMIT;
use reth_primitives::{Address, BlockNumberOrTag, Bytes, TransactionKind};
use reth_primitives::{address, Address, BlockNumberOrTag, Bytes, TransactionKind};
use reth_rpc_types::request::{TransactionInput, TransactionRequest};
use revm::primitives::{SpecId, KECCAK_EMPTY, U256};
use sov_modules_api::default_context::DefaultContext;
Expand All @@ -12,7 +12,8 @@ use sov_modules_api::{Context, Module, StateMapAccessor, StateVecAccessor};
use crate::call::CallMessage;
use crate::evm::primitive_types::Receipt;
use crate::smart_contracts::{
BlockHashContract, LogsContract, SelfDestructorContract, SimpleStorageContract, TestContract,
BlockHashContract, InfiniteLoopContract, LogsContract, SelfDestructorContract,
SimpleStorageContract, TestContract,
};
use crate::tests::test_signer::TestSigner;
use crate::tests::utils::get_evm;
Expand Down Expand Up @@ -961,3 +962,112 @@ fn test_l1_fee_not_enough_funds() {
let db_coinbase = evm.accounts.get(&config.coinbase, &mut working_set);
assert!(db_coinbase.is_none());
}

#[test]
fn test_l1_fee_halt() {
let (config, dev_signer, _) =
get_evm_config_starting_base_fee(U256::from_str("2000000").unwrap(), None, 1);

let (evm, mut working_set) = get_evm(&config);
let l1_fee_rate = 1;

evm.begin_soft_confirmation_hook(
&HookSoftConfirmationInfo {
da_slot_hash: [5u8; 32],
da_slot_height: 1,
da_slot_txs_commitment: [42u8; 32],
pre_state_root: [10u8; 32].to_vec(),
pub_key: vec![],
deposit_data: vec![],
l1_fee_rate,
timestamp: 0,
},
&mut working_set,
);
{
let sender_address = generate_address::<C>("sender");
let sequencer_address = generate_address::<C>("sequencer");
let context = C::new(sender_address, sequencer_address, 1);

let deploy_message =
create_contract_message_with_fee(&dev_signer, 0, InfiniteLoopContract::default(), 1);

let call_message = dev_signer
.sign_default_transaction_with_fee(
TransactionKind::Call(address!("819c5497b157177315e1204f52e588b393771719")),
InfiniteLoopContract::default()
.call_infinite_loop()
.into_iter()
.collect(),
1,
0,
1,
)
.unwrap();

evm.call(
CallMessage {
txs: vec![deploy_message, call_message],
},
&context,
&mut working_set,
)
.unwrap();
}
evm.end_soft_confirmation_hook(&mut working_set);
evm.finalize_hook(&[99u8; 32].into(), &mut working_set.accessory_state());

assert_eq!(
evm.receipts
.iter(&mut working_set.accessory_state())
.collect::<Vec<_>>(),
[
Receipt {
receipt: reth_primitives::Receipt {
tx_type: reth_primitives::TxType::Eip1559,
success: true,
cumulative_gas_used: 106947,
logs: vec![],
},
gas_used: 106947,
log_index_start: 0,
diff_size: 445,
},
Receipt {
receipt: reth_primitives::Receipt {
tx_type: reth_primitives::TxType::Eip1559,
success: false,
cumulative_gas_used: 1106947,
logs: vec![],
},
gas_used: 1000000,
log_index_start: 0,
diff_size: 52,
},
]
);

let db_account = evm
.accounts
.get(&dev_signer.address(), &mut working_set)
.unwrap();

let expenses = 1106947 + // evm gas
445 + // l1 contract deploy fee
52; // l1 contract call fee

assert_eq!(
db_account.info.balance,
U256::from(
2000000 - // initial balance
expenses
)
);

let coinbase_account = evm
.accounts
.get(&config.coinbase, &mut working_set)
.unwrap();

assert_eq!(coinbase_account.info.balance, U256::from(expenses));
}

0 comments on commit bc74540

Please sign in to comment.