From ab6a52eed57ce8d5ab0a3f9e4dedbd09a8f56717 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erce=20Can=20Bekt=C3=BCre?= <47954181+ercecan@users.noreply.github.com> Date: Thu, 12 Dec 2024 12:58:45 +0300 Subject: [PATCH] Fork1 EVM tests (#1593) --- crates/evm/src/evm/test_data/BlobBaseFee.abi | 1 + crates/evm/src/evm/test_data/BlobBaseFee.bin | 1 + crates/evm/src/evm/test_data/BlobBaseFee.sol | 9 + crates/evm/src/evm/test_data/Mcopy.abi | 1 + crates/evm/src/evm/test_data/Mcopy.bin | 1 + crates/evm/src/evm/test_data/Mcopy.sol | 15 + .../test_data/SelfdestructingConstructor.abi | 1 + .../test_data/SelfdestructingConstructor.bin | 1 + .../test_data/SelfdestructingConstructor.sol | 10 + .../src/evm/test_data/TransientStorage.abi | 1 + .../src/evm/test_data/TransientStorage.bin | 1 + .../src/evm/test_data/TransientStorage.sol | 29 + .../smart_contracts/blob_base_fee_contract.rs | 44 ++ .../evm/src/smart_contracts/mcopy_contract.rs | 44 ++ crates/evm/src/smart_contracts/mod.rs | 8 + .../selfdestructing_constructor.rs | 52 ++ .../transient_storage_contract.rs | 44 ++ crates/evm/src/tests/call_tests.rs | 150 ++++- crates/evm/src/tests/fork_tests.rs | 537 ++++++++++++++++++ crates/evm/src/tests/mod.rs | 1 + crates/evm/src/tests/utils.rs | 12 + 21 files changed, 959 insertions(+), 4 deletions(-) create mode 100644 crates/evm/src/evm/test_data/BlobBaseFee.abi create mode 100644 crates/evm/src/evm/test_data/BlobBaseFee.bin create mode 100644 crates/evm/src/evm/test_data/BlobBaseFee.sol create mode 100644 crates/evm/src/evm/test_data/Mcopy.abi create mode 100644 crates/evm/src/evm/test_data/Mcopy.bin create mode 100644 crates/evm/src/evm/test_data/Mcopy.sol create mode 100644 crates/evm/src/evm/test_data/SelfdestructingConstructor.abi create mode 100644 crates/evm/src/evm/test_data/SelfdestructingConstructor.bin create mode 100644 crates/evm/src/evm/test_data/SelfdestructingConstructor.sol create mode 100644 crates/evm/src/evm/test_data/TransientStorage.abi create mode 100644 crates/evm/src/evm/test_data/TransientStorage.bin create mode 100644 crates/evm/src/evm/test_data/TransientStorage.sol create mode 100644 crates/evm/src/smart_contracts/blob_base_fee_contract.rs create mode 100644 crates/evm/src/smart_contracts/mcopy_contract.rs create mode 100644 crates/evm/src/smart_contracts/selfdestructing_constructor.rs create mode 100644 crates/evm/src/smart_contracts/transient_storage_contract.rs create mode 100644 crates/evm/src/tests/fork_tests.rs diff --git a/crates/evm/src/evm/test_data/BlobBaseFee.abi b/crates/evm/src/evm/test_data/BlobBaseFee.abi new file mode 100644 index 000000000..5312b34c9 --- /dev/null +++ b/crates/evm/src/evm/test_data/BlobBaseFee.abi @@ -0,0 +1 @@ +[{"inputs":[],"name":"storeBlobBaseFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"x","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/crates/evm/src/evm/test_data/BlobBaseFee.bin b/crates/evm/src/evm/test_data/BlobBaseFee.bin new file mode 100644 index 000000000..680a08ace --- /dev/null +++ b/crates/evm/src/evm/test_data/BlobBaseFee.bin @@ -0,0 +1 @@ +6080604052348015600e575f5ffd5b5060c680601a5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c80630c55699c1460345780634c4b2fc814604e575b5f5ffd5b603a6056565b604051604591906079565b60405180910390f35b6054605b565b005b5f5481565b4a5f81905550565b5f819050919050565b6073816063565b82525050565b5f602082019050608a5f830184606c565b9291505056fea2646970667358221220203976141a6cec06f953d81e19d2a5c9fdf81af642a41beb42f6c2e9cf22a77464736f6c634300081c0033 \ No newline at end of file diff --git a/crates/evm/src/evm/test_data/BlobBaseFee.sol b/crates/evm/src/evm/test_data/BlobBaseFee.sol new file mode 100644 index 000000000..e629b188f --- /dev/null +++ b/crates/evm/src/evm/test_data/BlobBaseFee.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +contract BlobBaseFee{ + uint256 public x; + function storeBlobBaseFee() external { + x = block.blobbasefee; + } +} \ No newline at end of file diff --git a/crates/evm/src/evm/test_data/Mcopy.abi b/crates/evm/src/evm/test_data/Mcopy.abi new file mode 100644 index 000000000..2cafdc728 --- /dev/null +++ b/crates/evm/src/evm/test_data/Mcopy.abi @@ -0,0 +1 @@ +[{"inputs":[],"name":"memoryCopy","outputs":[{"internalType":"bytes32","name":"x","type":"bytes32"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/crates/evm/src/evm/test_data/Mcopy.bin b/crates/evm/src/evm/test_data/Mcopy.bin new file mode 100644 index 000000000..28f86c5e4 --- /dev/null +++ b/crates/evm/src/evm/test_data/Mcopy.bin @@ -0,0 +1 @@ +6080604052348015600e575f5ffd5b5060bc80601a5f395ff3fe6080604052348015600e575f5ffd5b50600436106026575f3560e01c80632dbaeee914602a575b5f5ffd5b60306044565b604051603b9190606f565b60405180910390f35b5f60506020526020805f5e5f519050805f5590565b5f819050919050565b6069816059565b82525050565b5f60208201905060805f8301846062565b9291505056fea264697066735822122036ac0d8164bbe16a7971d0609110f6a3caba5a571d3997e8bcc9e4a9a1ed53c864736f6c634300081c0033 \ No newline at end of file diff --git a/crates/evm/src/evm/test_data/Mcopy.sol b/crates/evm/src/evm/test_data/Mcopy.sol new file mode 100644 index 000000000..07cc6b6e2 --- /dev/null +++ b/crates/evm/src/evm/test_data/Mcopy.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.28; + +contract Mcopy { + + function memoryCopy() external returns (bytes32 x) { + assembly { + mstore(0x20, 0x50) // Store 0x50 at word 1 in memory + mcopy(0, 0x20, 0x20) // Copies 0x50 to word 0 in memory + x := mload(0) // Returns 32 bytes "0x50" + sstore(0, x) // Stores 0x50 at storage slot 0 + } + } +} \ No newline at end of file diff --git a/crates/evm/src/evm/test_data/SelfdestructingConstructor.abi b/crates/evm/src/evm/test_data/SelfdestructingConstructor.abi new file mode 100644 index 000000000..aac7c4c21 --- /dev/null +++ b/crates/evm/src/evm/test_data/SelfdestructingConstructor.abi @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address payable","name":"recipient","type":"address"}],"stateMutability":"payable","type":"constructor"}] \ No newline at end of file diff --git a/crates/evm/src/evm/test_data/SelfdestructingConstructor.bin b/crates/evm/src/evm/test_data/SelfdestructingConstructor.bin new file mode 100644 index 000000000..b3912c821 --- /dev/null +++ b/crates/evm/src/evm/test_data/SelfdestructingConstructor.bin @@ -0,0 +1 @@ +608060405260405160b838038060b88339818101604052810190602191906091565b8073ffffffffffffffffffffffffffffffffffffffff16ff5b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f606582603e565b9050919050565b607381605d565b8114607c575f5ffd5b50565b5f81519050608b81606c565b92915050565b5f6020828403121560a35760a2603a565b5b5f60ae84828501607f565b9150509291505056fe \ No newline at end of file diff --git a/crates/evm/src/evm/test_data/SelfdestructingConstructor.sol b/crates/evm/src/evm/test_data/SelfdestructingConstructor.sol new file mode 100644 index 000000000..d3c46105c --- /dev/null +++ b/crates/evm/src/evm/test_data/SelfdestructingConstructor.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +contract SelfdestructingConstructor { + // Constructor with an address parameter + constructor(address payable recipient) payable { + // Call selfdestruct to send the contract's balance to the recipient + selfdestruct(recipient); + } +} \ No newline at end of file diff --git a/crates/evm/src/evm/test_data/TransientStorage.abi b/crates/evm/src/evm/test_data/TransientStorage.abi new file mode 100644 index 000000000..9f472b35d --- /dev/null +++ b/crates/evm/src/evm/test_data/TransientStorage.abi @@ -0,0 +1 @@ +[{"inputs":[],"name":"claimGift","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/crates/evm/src/evm/test_data/TransientStorage.bin b/crates/evm/src/evm/test_data/TransientStorage.bin new file mode 100644 index 000000000..731759ca5 --- /dev/null +++ b/crates/evm/src/evm/test_data/TransientStorage.bin @@ -0,0 +1 @@ +6080604052348015600e575f5ffd5b506102e68061001c5f395ff3fe608060405260043610610021575f3560e01c80638f7c8d101461002c57610028565b3661002857005b5f5ffd5b348015610037575f5ffd5b50610040610042565b005b5f5f905c906101000a900460ff1615610090576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161008790610251565b60405180910390fd5b60015f5f6101000a815c8160ff021916908315150217905d50670de0b6b3a76400004710156100bd575f5ffd5b5f5f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff161561010f575f5ffd5b5f3373ffffffffffffffffffffffffffffffffffffffff16670de0b6b3a764000060405161013c9061029c565b5f6040518083038185875af1925050503d805f8114610176576040519150601f19603f3d011682016040523d82523d5f602084013e61017b565b606091505b5050905080610188575f5ffd5b60015f5f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff021916908315150217905550505f5f5f6101000a815c8160ff021916908315150217905d50565b5f82825260208201905092915050565b7f5265656e7472616e637920617474656d707400000000000000000000000000005f82015250565b5f61023b6012836101f7565b915061024682610207565b602082019050919050565b5f6020820190508181035f8301526102688161022f565b9050919050565b5f81905092915050565b50565b5f6102875f8361026f565b915061029282610279565b5f82019050919050565b5f6102a68261027c565b915081905091905056fea2646970667358221220a38a24567c1b46caacc375bdb5e850d00f7fd8a4cfeb62c770af9e0d0b3d9c0464736f6c634300081c0033 \ No newline at end of file diff --git a/crates/evm/src/evm/test_data/TransientStorage.sol b/crates/evm/src/evm/test_data/TransientStorage.sol new file mode 100644 index 000000000..c499f8ac7 --- /dev/null +++ b/crates/evm/src/evm/test_data/TransientStorage.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.28; + +contract TransientStorage { + mapping(address => bool) sentGifts; + bool transient locked; + + modifier nonReentrant { + require(!locked, "Reentrancy attempt"); + locked = true; + _; + // Unlocks the guard, making the pattern composable. + // After the function exits, it can be called again, even in the same transaction. + locked = false; + } + + function claimGift() nonReentrant public { + require(address(this).balance >= 1 ether); + require(!sentGifts[msg.sender]); + (bool success, ) = msg.sender.call{value: 1 ether}(""); + require(success); + + // In a reentrant function, doing this last would open up the vulnerability + sentGifts[msg.sender] = true; + } + + // Function to receive Ether. This is required to receive Ether into the contract + receive() external payable {} +} \ No newline at end of file diff --git a/crates/evm/src/smart_contracts/blob_base_fee_contract.rs b/crates/evm/src/smart_contracts/blob_base_fee_contract.rs new file mode 100644 index 000000000..53f6dd7a4 --- /dev/null +++ b/crates/evm/src/smart_contracts/blob_base_fee_contract.rs @@ -0,0 +1,44 @@ +use alloy_sol_types::{sol, SolCall}; + +use super::TestContract; + +// BlobBaseFeeContract wrapper. +sol! { + #[sol(abi)] + BlobBaseFee, + "./src/evm/test_data/BlobBaseFee.abi" +} + +/// BlobBaseFeeContract wrapper. +pub struct BlobBaseFeeContract { + bytecode: Vec, +} + +impl Default for BlobBaseFeeContract { + fn default() -> Self { + let bytecode = { + let bytecode_hex = include_str!("../../../evm/src/evm/test_data/BlobBaseFee.bin"); + hex::decode(bytecode_hex).unwrap() + }; + + Self { bytecode } + } +} + +impl TestContract for BlobBaseFeeContract { + fn byte_code(&self) -> Vec { + self.byte_code() + } +} + +impl BlobBaseFeeContract { + /// BlobBaseFee bytecode. + pub fn byte_code(&self) -> Vec { + self.bytecode.clone() + } + + /// Store blobbasefee + pub fn store_blob_base_fee(&self) -> Vec { + BlobBaseFee::storeBlobBaseFeeCall {}.abi_encode() + } +} diff --git a/crates/evm/src/smart_contracts/mcopy_contract.rs b/crates/evm/src/smart_contracts/mcopy_contract.rs new file mode 100644 index 000000000..beb1e3907 --- /dev/null +++ b/crates/evm/src/smart_contracts/mcopy_contract.rs @@ -0,0 +1,44 @@ +use alloy_sol_types::{sol, SolCall}; + +use super::TestContract; + +// McopyContract wrapper. +sol! { + #[sol(abi)] + Mcopy, + "./src/evm/test_data/Mcopy.abi" +} + +/// McopyContract wrapper. +pub struct McopyContract { + bytecode: Vec, +} + +impl Default for McopyContract { + fn default() -> Self { + let bytecode = { + let bytecode_hex = include_str!("../../../evm/src/evm/test_data/Mcopy.bin"); + hex::decode(bytecode_hex).unwrap() + }; + + Self { bytecode } + } +} + +impl TestContract for McopyContract { + fn byte_code(&self) -> Vec { + self.byte_code() + } +} + +impl McopyContract { + /// Mcopy bytecode. + pub fn byte_code(&self) -> Vec { + self.bytecode.clone() + } + + /// Claims the gift. + pub fn call_mcopy(&self) -> Vec { + Mcopy::memoryCopyCall {}.abi_encode() + } +} diff --git a/crates/evm/src/smart_contracts/mod.rs b/crates/evm/src/smart_contracts/mod.rs index 145c70839..d32c43648 100644 --- a/crates/evm/src/smart_contracts/mod.rs +++ b/crates/evm/src/smart_contracts/mod.rs @@ -1,24 +1,32 @@ //! Includes the smart contracts used by the citrea-evm and the rollup itself, extensively for testing. +mod blob_base_fee_contract; mod blockhash_contract; mod caller_contract; mod coinbase_contract; mod hive_contract; mod infinite_loop_contract; mod logs_contract; +mod mcopy_contract; mod payable_contract; mod self_destructor_contract; +mod selfdestructing_constructor; mod simple_storage_contract; +mod transient_storage_contract; +pub use blob_base_fee_contract::BlobBaseFeeContract; pub use blockhash_contract::BlockHashContract; pub use caller_contract::CallerContract; pub use coinbase_contract::CoinbaseContract; pub use hive_contract::HiveContract; pub use infinite_loop_contract::InfiniteLoopContract; pub use logs_contract::{AnotherLogEvent, LogEvent, LogsContract}; +pub use mcopy_contract::McopyContract; pub use payable_contract::SimplePayableContract; pub use self_destructor_contract::SelfDestructorContract; +pub use selfdestructing_constructor::SelfdestructingConstructorContract; pub use simple_storage_contract::SimpleStorageContract; +pub use transient_storage_contract::TransientStorageContract; /// Trait for testing smart contracts. pub trait TestContract: Default { diff --git a/crates/evm/src/smart_contracts/selfdestructing_constructor.rs b/crates/evm/src/smart_contracts/selfdestructing_constructor.rs new file mode 100644 index 000000000..25866a5c1 --- /dev/null +++ b/crates/evm/src/smart_contracts/selfdestructing_constructor.rs @@ -0,0 +1,52 @@ +use alloy_primitives::Address; +use alloy_sol_types::{sol, SolConstructor}; + +use super::TestContract; + +// SelfdestructingConstructorContract wrapper. +sol! { + #[sol(abi)] + SelfdestructingConstructor, + "./src/evm/test_data/SelfdestructingConstructor.abi" +} + +/// SelfdestructingConstructorContract wrapper. +pub struct SelfdestructingConstructorContract { + bytecode: Vec, +} + +impl Default for SelfdestructingConstructorContract { + fn default() -> Self { + let bytecode = { + let bytecode_hex = + include_str!("../../../evm/src/evm/test_data/SelfdestructingConstructor.bin"); + hex::decode(bytecode_hex).unwrap() + }; + + Self { bytecode } + } +} + +impl TestContract for SelfdestructingConstructorContract { + fn byte_code(&self) -> Vec { + self.byte_code() + } +} + +impl SelfdestructingConstructorContract { + /// SelfdestructingConstructor bytecode. + pub fn byte_code(&self) -> Vec { + self.bytecode.clone() + } + + /// Claims the gift. + pub fn construct(&self, recipient: Address) -> Vec { + let mut v = self.byte_code(); + + v.extend_from_slice( + &SelfdestructingConstructor::constructorCall { recipient }.abi_encode(), + ); + + v + } +} diff --git a/crates/evm/src/smart_contracts/transient_storage_contract.rs b/crates/evm/src/smart_contracts/transient_storage_contract.rs new file mode 100644 index 000000000..b2c931e90 --- /dev/null +++ b/crates/evm/src/smart_contracts/transient_storage_contract.rs @@ -0,0 +1,44 @@ +use alloy_sol_types::{sol, SolCall}; + +use super::TestContract; + +// TransientStorageContract wrapper. +sol! { + #[sol(abi)] + TransientStorage, + "./src/evm/test_data/TransientStorage.abi" +} + +/// TransientStorageContract wrapper. +pub struct TransientStorageContract { + bytecode: Vec, +} + +impl Default for TransientStorageContract { + fn default() -> Self { + let bytecode = { + let bytecode_hex = include_str!("../../../evm/src/evm/test_data/TransientStorage.bin"); + hex::decode(bytecode_hex).unwrap() + }; + + Self { bytecode } + } +} + +impl TestContract for TransientStorageContract { + fn byte_code(&self) -> Vec { + self.byte_code() + } +} + +impl TransientStorageContract { + /// TransientStorage bytecode. + pub fn byte_code(&self) -> Vec { + self.bytecode.clone() + } + + /// Claims the gift. + pub fn claim_gift(&self) -> Vec { + TransientStorage::claimGiftCall {}.abi_encode() + } +} diff --git a/crates/evm/src/tests/call_tests.rs b/crates/evm/src/tests/call_tests.rs index 437cb8708..b947ceba3 100644 --- a/crates/evm/src/tests/call_tests.rs +++ b/crates/evm/src/tests/call_tests.rs @@ -516,6 +516,8 @@ fn failed_transaction_test() { assert_eq!(block.transactions.end, 3); } +// tests first part of https://eips.ethereum.org/EIPS/eip-6780 +// test self destruct behaviour before cancun and after cancun #[test] fn self_destruct_test() { let contract_balance: u64 = 1000000000000000; @@ -576,7 +578,7 @@ fn self_destruct_test() { .get(&contract_addr, &mut working_set) .expect("contract address should exist"); - // Test if we managed to send money to ocntract + // Test if we managed to send money to contract assert_eq!(contract_info.balance, U256::from(contract_balance)); let db_contract = DbAccount::new(contract_addr); @@ -627,6 +629,9 @@ fn self_destruct_test() { .unwrap(); } evm.end_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + evm.finalize_hook(&[99u8; 32].into(), &mut working_set.accessory_state()); + + l2_height += 1; // we now delete destructed accounts from storage assert_eq!(evm.accounts.get(&contract_addr, &mut working_set), None); @@ -657,6 +662,142 @@ fn self_destruct_test() { // the keys should be empty assert_eq!(db_account.keys.len(&mut working_set), 0); + let new_contract_address = address!("e04dd177927f4293a16f9c3f990b45afebc0e12c"); + // Now deploy selfdestruct contract again + evm.begin_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + { + let sender_address = generate_address::("sender"); + let context = C::new(sender_address, l2_height, SovSpecId::Genesis, l1_fee_rate); + + // deploy selfdestruct contract + // send some money to the selfdestruct contract + // set some variable in the contract + let rlp_transactions = vec![ + create_contract_message(&dev_signer, 4, SelfDestructorContract::default()), + send_money_to_contract_message( + new_contract_address, + &dev_signer, + 5, + contract_balance as u128, + ), + set_selfdestruct_arg_message(new_contract_address, &dev_signer, 6, 123), + ]; + + evm.call( + CallMessage { + txs: rlp_transactions, + }, + &context, + &mut working_set, + ) + .unwrap(); + } + evm.end_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + evm.finalize_hook(&[99u8; 32].into(), &mut working_set.accessory_state()); + + l2_height += 1; + + let contract_info = evm + .accounts + .get(&new_contract_address, &mut working_set) + .expect("contract address should exist"); + + let new_contract_code_hash_before_destruct = contract_info.code_hash.unwrap(); + let new_contract_code_before_destruct = evm + .code + .get(&new_contract_code_hash_before_destruct, &mut working_set); + + // Activate fork1 + // After cancun activated here SELFDESTRUCT will recover all funds to the target + // but not delete the account, except when called in the same transaction as creation + // In this case the contract does not have a selfdestruct in the same transaction as creation + // https://eips.ethereum.org/EIPS/eip-6780 + let soft_confirmation_info = HookSoftConfirmationInfo { + l2_height, + da_slot_hash: [5u8; 32], + da_slot_height: 1, + da_slot_txs_commitment: [42u8; 32], + pre_state_root: [10u8; 32].to_vec(), + current_spec: SovSpecId::Fork1, + pub_key: vec![], + deposit_data: vec![], + l1_fee_rate, + timestamp: 0, + }; + + evm.begin_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + { + let sender_address = generate_address::("sender"); + let context = C::new(sender_address, l2_height, SovSpecId::Fork1, l1_fee_rate); + // selfdestruct to die to address with someone other than the creator of the contract + evm.call( + CallMessage { + txs: vec![selfdestruct_message( + new_contract_address, + &dev_signer, + 7, + die_to_address, + )], + }, + &context, + &mut working_set, + ) + .unwrap(); + } + evm.end_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + evm.finalize_hook(&[99u8; 32].into(), &mut working_set.accessory_state()); + + let receipts = evm + .receipts + .iter(&mut working_set.accessory_state()) + .collect::>(); + + // the tx should be a success + assert!(receipts[0].receipt.success); + + // after cancun the funds go but account is not destructed if if selfdestruct is not called in creation + let contract_info = evm + .accounts + .get(&new_contract_address, &mut working_set) + .expect("contract address should exist"); + + // Test if we managed to send money to contract + assert_eq!(contract_info.nonce, 1); + assert_eq!( + contract_info.code_hash.unwrap(), + new_contract_code_hash_before_destruct + ); + + // Both on-chain state and off-chain state code should exist + let code = evm + .code + .get(&new_contract_code_hash_before_destruct, &mut working_set); + assert_eq!(code, new_contract_code_before_destruct); + + let off_chain_code = evm.offchain_code.get( + &new_contract_code_hash_before_destruct, + &mut working_set.offchain_state(), + ); + assert_eq!(off_chain_code, new_contract_code_before_destruct); + + // Test if we managed to send money to contract + assert_eq!(contract_info.balance, U256::from(0)); + + let die_to_contract = evm + .accounts + .get(&die_to_address, &mut working_set) + .expect("die to address should exist"); + + // the to address balance should be equal to double contract balance now that two selfdestructs have been called + assert_eq!(die_to_contract.balance, U256::from(2 * contract_balance)); + + let db_account = DbAccount::new(new_contract_address); + + // the storage should not be empty + assert_eq!( + db_account.storage.get(&U256::from(0), &mut working_set), + Some(U256::from(123)) + ); } #[test] @@ -957,7 +1098,7 @@ pub(crate) fn create_contract_message_with_priority_fee( .unwrap() } -fn set_selfdestruct_arg_message( +pub(crate) fn set_selfdestruct_arg_message( contract_addr: Address, dev_signer: &TestSigner, nonce: u64, @@ -993,7 +1134,7 @@ fn set_arg_transaction( .unwrap() } -fn send_money_to_contract_message( +pub(crate) fn send_money_to_contract_message( contract_addr: Address, signer: &TestSigner, nonce: u64, @@ -1004,7 +1145,7 @@ fn send_money_to_contract_message( .unwrap() } -fn selfdestruct_message( +pub(crate) fn selfdestruct_message( contract_addr: Address, dev_signer: &TestSigner, nonce: u64, @@ -1735,6 +1876,7 @@ fn test_call_with_block_overrides() { } evm.end_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); evm.finalize_hook(&[99u8; 32].into(), &mut working_set.accessory_state()); + l2_height += 1; // Create empty EVM blocks for _i in 0..10 { diff --git a/crates/evm/src/tests/fork_tests.rs b/crates/evm/src/tests/fork_tests.rs new file mode 100644 index 000000000..13e32dfaf --- /dev/null +++ b/crates/evm/src/tests/fork_tests.rs @@ -0,0 +1,537 @@ +use std::str::FromStr; + +use reth_primitives::{address, keccak256, Address, TxKind}; +use revm::primitives::U256; +use sov_modules_api::default_context::DefaultContext; +use sov_modules_api::hooks::HookSoftConfirmationInfo; +use sov_modules_api::utils::generate_address; +use sov_modules_api::{Context, Module, StateMapAccessor, StateVecAccessor}; +use sov_rollup_interface::spec::SpecId as SovSpecId; + +use crate::call::CallMessage; +use crate::evm::DbAccount; +use crate::smart_contracts::{ + BlobBaseFeeContract, McopyContract, SelfdestructingConstructorContract, + TransientStorageContract, +}; +use crate::tests::test_signer::TestSigner; +use crate::tests::utils::{create_contract_message, get_evm, get_evm_config}; +use crate::RlpEvmTransaction; +type C = DefaultContext; + +use super::call_tests::send_money_to_contract_message; +use super::utils::create_contract_message_with_bytecode; + +fn claim_gift_from_transient_storage_contract_transaction( + contract_addr: Address, + dev_signer: &TestSigner, + nonce: u64, +) -> RlpEvmTransaction { + let contract = TransientStorageContract::default(); + dev_signer + .sign_default_transaction(TxKind::Call(contract_addr), contract.claim_gift(), nonce, 0) + .unwrap() +} + +fn call_mcopy(contract_addr: Address, dev_signer: &TestSigner, nonce: u64) -> RlpEvmTransaction { + let contract = McopyContract::default(); + dev_signer + .sign_default_transaction(TxKind::Call(contract_addr), contract.call_mcopy(), nonce, 0) + .unwrap() +} + +fn store_blob_base_fee_transaction( + contract_addr: Address, + dev_signer: &TestSigner, + nonce: u64, +) -> RlpEvmTransaction { + let contract = BlobBaseFeeContract::default(); + dev_signer + .sign_default_transaction( + TxKind::Call(contract_addr), + contract.store_blob_base_fee(), + nonce, + 0, + ) + .unwrap() +} + +#[test] +fn test_cancun_transient_storage_activation() { + let (config, dev_signer, contract_addr) = + get_evm_config(U256::from_str("100000000000000000000").unwrap(), None); + + let (mut evm, mut working_set) = get_evm(&config); + let l1_fee_rate = 0; + let mut l2_height = 2; + + let soft_confirmation_info = HookSoftConfirmationInfo { + l2_height, + da_slot_hash: [5u8; 32], + da_slot_height: 1, + da_slot_txs_commitment: [42u8; 32], + pre_state_root: [10u8; 32].to_vec(), + current_spec: SovSpecId::Genesis, + pub_key: vec![], + deposit_data: vec![], + l1_fee_rate, + timestamp: 0, + }; + + // Deploy transient storage contract + let sender_address = generate_address::("sender"); + evm.begin_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + { + let context = C::new(sender_address, l2_height, SovSpecId::Genesis, l1_fee_rate); + + let deploy_message = + create_contract_message(&dev_signer, 0, TransientStorageContract::default()); + + evm.call( + CallMessage { + txs: vec![deploy_message], + }, + &context, + &mut working_set, + ) + .unwrap(); + } + evm.end_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + evm.finalize_hook(&[99u8; 32].into(), &mut working_set.accessory_state()); + + l2_height += 1; + + // Send money to transient storage contract + evm.begin_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + { + let context = C::new(sender_address, l2_height, SovSpecId::Genesis, l1_fee_rate); + let call_tx = + send_money_to_contract_message(contract_addr, &dev_signer, 1, 10000000000000000000); + + evm.call( + CallMessage { txs: vec![call_tx] }, + &context, + &mut working_set, + ) + .unwrap(); + } + evm.end_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + evm.finalize_hook(&[99u8; 32].into(), &mut working_set.accessory_state()); + + l2_height += 1; + + // Call claim gift from transient storage contract expect to fail on genesis spec + evm.begin_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + { + let context = C::new(sender_address, l2_height, SovSpecId::Genesis, l1_fee_rate); + let call_tx = + claim_gift_from_transient_storage_contract_transaction(contract_addr, &dev_signer, 2); + + evm.call( + CallMessage { txs: vec![call_tx] }, + &context, + &mut working_set, + ) + .unwrap(); + } + evm.end_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + evm.finalize_hook(&[99u8; 32].into(), &mut working_set.accessory_state()); + + l2_height += 1; + + let receipts: Vec<_> = evm + .receipts + .iter(&mut working_set.accessory_state()) + .collect(); + + // Last tx should have failed because cancun is not activated + assert!(!receipts.last().unwrap().receipt.success); + + // Now trying with CANCUN spec on the next block + let soft_confirmation_info = HookSoftConfirmationInfo { + l2_height, + da_slot_hash: [5u8; 32], + da_slot_height: 1, + da_slot_txs_commitment: [42u8; 32], + pre_state_root: [10u8; 32].to_vec(), + current_spec: SovSpecId::Fork1, + pub_key: vec![], + deposit_data: vec![], + l1_fee_rate, + timestamp: 0, + }; + + evm.begin_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + { + let context = C::new(sender_address, l2_height, SovSpecId::Fork1, l1_fee_rate); + let call_tx = + claim_gift_from_transient_storage_contract_transaction(contract_addr, &dev_signer, 3); + + evm.call( + CallMessage { txs: vec![call_tx] }, + &context, + &mut working_set, + ) + .unwrap(); + } + evm.end_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + evm.finalize_hook(&[99u8; 32].into(), &mut working_set.accessory_state()); + + l2_height += 1; + + let receipts: Vec<_> = evm + .receipts + .iter(&mut working_set.accessory_state()) + .collect(); + + // Last tx should have passed + assert!(receipts.last().unwrap().receipt.success); + + evm.begin_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + { + let context = C::new(sender_address, l2_height, SovSpecId::Fork1, l1_fee_rate); + let call_tx = + claim_gift_from_transient_storage_contract_transaction(contract_addr, &dev_signer, 4); + + evm.call( + CallMessage { txs: vec![call_tx] }, + &context, + &mut working_set, + ) + .unwrap(); + } + evm.end_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + evm.finalize_hook(&[99u8; 32].into(), &mut working_set.accessory_state()); + + let receipts: Vec<_> = evm + .receipts + .iter(&mut working_set.accessory_state()) + .collect(); + + // This tx should fail as the contract has already been claimed + assert!(!receipts.last().unwrap().receipt.success); +} + +#[test] +fn test_cancun_mcopy_activation() { + let (config, dev_signer, contract_addr) = + get_evm_config(U256::from_str("100000000000000000000").unwrap(), None); + + let (mut evm, mut working_set) = get_evm(&config); + let l1_fee_rate = 0; + let mut l2_height = 2; + + let soft_confirmation_info = HookSoftConfirmationInfo { + l2_height, + da_slot_hash: [5u8; 32], + da_slot_height: 1, + da_slot_txs_commitment: [42u8; 32], + pre_state_root: [10u8; 32].to_vec(), + current_spec: SovSpecId::Genesis, + pub_key: vec![], + deposit_data: vec![], + l1_fee_rate, + timestamp: 0, + }; + + // Deploy transient storage contract + let sender_address = generate_address::("sender"); + evm.begin_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + { + let context = C::new(sender_address, l2_height, SovSpecId::Genesis, l1_fee_rate); + + let deploy_message = create_contract_message(&dev_signer, 0, McopyContract::default()); + + evm.call( + CallMessage { + txs: vec![deploy_message], + }, + &context, + &mut working_set, + ) + .unwrap(); + } + evm.end_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + evm.finalize_hook(&[99u8; 32].into(), &mut working_set.accessory_state()); + + l2_height += 1; + + // Send money to transient storage contract + evm.begin_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + { + let context = C::new(sender_address, l2_height, SovSpecId::Genesis, l1_fee_rate); + let call_tx = call_mcopy(contract_addr, &dev_signer, 1); + + evm.call( + CallMessage { txs: vec![call_tx] }, + &context, + &mut working_set, + ) + .unwrap(); + } + evm.end_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + evm.finalize_hook(&[99u8; 32].into(), &mut working_set.accessory_state()); + + l2_height += 1; + + let receipts: Vec<_> = evm + .receipts + .iter(&mut working_set.accessory_state()) + .collect(); + + // Last tx should have failed because cancun is not activated + assert!(!receipts.last().unwrap().receipt.success); + + let soft_confirmation_info = HookSoftConfirmationInfo { + l2_height, + da_slot_hash: [5u8; 32], + da_slot_height: 1, + da_slot_txs_commitment: [42u8; 32], + pre_state_root: [10u8; 32].to_vec(), + current_spec: SovSpecId::Fork1, + pub_key: vec![], + deposit_data: vec![], + l1_fee_rate, + timestamp: 0, + }; + + // Send money to transient storage contract + evm.begin_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + { + let context = C::new(sender_address, l2_height, SovSpecId::Fork1, l1_fee_rate); + let call_tx = call_mcopy(contract_addr, &dev_signer, 2); + + evm.call( + CallMessage { txs: vec![call_tx] }, + &context, + &mut working_set, + ) + .unwrap(); + } + evm.end_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + evm.finalize_hook(&[99u8; 32].into(), &mut working_set.accessory_state()); + + let receipts: Vec<_> = evm + .receipts + .iter(&mut working_set.accessory_state()) + .collect(); + + // Last tx should have failed because cancun is not activated + assert!(receipts.last().unwrap().receipt.success); + let db_account = DbAccount::new(contract_addr); + let storage_value = db_account + .storage + .get(&U256::ZERO, &mut working_set) + .unwrap(); + assert_eq!(storage_value, U256::from(80)); +} + +// tests second part (last one) of https://eips.ethereum.org/EIPS/eip-6780 +// First pat is in call_tests.rs `test_self_destruct_restriction` +#[test] +fn test_self_destructing_constructor() { + let contract_balance: u128 = 1000000000000000; + + // address used in selfdestruct + let die_to_address = address!("11115497b157177315e1204f52e588b393111111"); + + let (config, dev_signer, contract_addr) = + get_evm_config(U256::from_str("100000000000000000000").unwrap(), None); + + let (mut evm, mut working_set) = get_evm(&config); + let l1_fee_rate = 0; + let l2_height = 2; + + let soft_confirmation_info = HookSoftConfirmationInfo { + l2_height, + da_slot_hash: [5u8; 32], + da_slot_height: 1, + da_slot_txs_commitment: [42u8; 32], + pre_state_root: [10u8; 32].to_vec(), + current_spec: SovSpecId::Fork1, + pub_key: vec![], + deposit_data: vec![], + l1_fee_rate, + timestamp: 0, + }; + let contract = SelfdestructingConstructorContract::default(); + + let constructed_bytecode = contract.construct(die_to_address); + + evm.begin_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + { + let sender_address = generate_address::("sender"); + let context = C::new(sender_address, l2_height, SovSpecId::Fork1, l1_fee_rate); + + // deploy selfdestruct contract + let rlp_transactions = vec![create_contract_message_with_bytecode( + &dev_signer, + 0, + constructed_bytecode, + Some(contract_balance), + )]; + + evm.call( + CallMessage { + txs: rlp_transactions, + }, + &context, + &mut working_set, + ) + .unwrap(); + } + evm.end_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + evm.finalize_hook(&[99u8; 32].into(), &mut working_set.accessory_state()); + + let contract_info = evm.accounts.get(&contract_addr, &mut working_set); + // Contract should not exist as it is created and selfdestructed in the same transaction + assert!(contract_info.is_none()); + + let die_to_contract_info = evm.accounts.get(&die_to_address, &mut working_set); + + // die_to_address should have the contract balance + assert!(die_to_contract_info.is_some()); + + assert_eq!( + die_to_contract_info.unwrap().balance, + U256::from(contract_balance) + ); + + // after destruction codes should also be removed + // calculated with + // `solc --combined-json bin-runtime SelfdestructingConstructor.sol`` + let contract_runtime_bytecode_str = "60806040525f5ffdfea26469706673582212203744a38e5d136aea11a6095d6338eb5db0faba76bc0f7ee3aea38556128d0e9764736f6c634300081c0033"; + let contract_runtime_bytecode = hex::decode(contract_runtime_bytecode_str).unwrap(); + + let contract_code_hash = keccak256(contract_runtime_bytecode.as_slice()); + + let code = evm.code.get(&contract_code_hash, &mut working_set); + assert!(code.is_none()); + + let off_chain_code = evm + .offchain_code + .get(&contract_code_hash, &mut working_set.offchain_state()); + assert!(off_chain_code.is_none()); +} + +#[test] +fn test_blob_base_fee_should_return_1() { + let (config, dev_signer, contract_addr) = + get_evm_config(U256::from_str("100000000000000000000").unwrap(), None); + + let (mut evm, mut working_set) = get_evm(&config); + let l1_fee_rate = 0; + let mut l2_height = 2; + + let soft_confirmation_info = HookSoftConfirmationInfo { + l2_height, + da_slot_hash: [5u8; 32], + da_slot_height: 1, + da_slot_txs_commitment: [42u8; 32], + pre_state_root: [10u8; 32].to_vec(), + current_spec: SovSpecId::Genesis, + pub_key: vec![], + deposit_data: vec![], + l1_fee_rate, + timestamp: 0, + }; + + // Deploy transient storage contract + let sender_address = generate_address::("sender"); + evm.begin_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + { + let context = C::new(sender_address, l2_height, SovSpecId::Genesis, l1_fee_rate); + + let deploy_message = + create_contract_message(&dev_signer, 0, BlobBaseFeeContract::default()); + + evm.call( + CallMessage { + txs: vec![deploy_message], + }, + &context, + &mut working_set, + ) + .unwrap(); + } + evm.end_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + evm.finalize_hook(&[99u8; 32].into(), &mut working_set.accessory_state()); + + l2_height += 1; + + for _ in 0..10 { + evm.begin_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + evm.end_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + evm.finalize_hook(&[99u8; 32].into(), &mut working_set.accessory_state()); + l2_height += 1; + } + + evm.begin_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + { + let context = C::new(sender_address, l2_height, SovSpecId::Genesis, l1_fee_rate); + let call_tx = store_blob_base_fee_transaction(contract_addr, &dev_signer, 1); + + evm.call( + CallMessage { txs: vec![call_tx] }, + &context, + &mut working_set, + ) + .unwrap(); + } + evm.end_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + evm.finalize_hook(&[99u8; 32].into(), &mut working_set.accessory_state()); + l2_height += 1; + + let receipts: Vec<_> = evm + .receipts + .iter(&mut working_set.accessory_state()) + .collect(); + + // Last tx should have failed because cancun is not activated + assert!(!receipts.last().unwrap().receipt.success); + + // Now trying with CANCUN spec on the next block + let soft_confirmation_info = HookSoftConfirmationInfo { + l2_height, + da_slot_hash: [5u8; 32], + da_slot_height: 1, + da_slot_txs_commitment: [42u8; 32], + pre_state_root: [10u8; 32].to_vec(), + current_spec: SovSpecId::Fork1, + pub_key: vec![], + deposit_data: vec![], + l1_fee_rate, + timestamp: 0, + }; + + evm.begin_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + { + let context = C::new(sender_address, l2_height, SovSpecId::Fork1, l1_fee_rate); + let call_tx = store_blob_base_fee_transaction(contract_addr, &dev_signer, 2); + + evm.call( + CallMessage { txs: vec![call_tx] }, + &context, + &mut working_set, + ) + .unwrap(); + } + evm.end_soft_confirmation_hook(&soft_confirmation_info, &mut working_set); + evm.finalize_hook(&[99u8; 32].into(), &mut working_set.accessory_state()); + + let receipts: Vec<_> = evm + .receipts + .iter(&mut working_set.accessory_state()) + .collect(); + + // Last tx should have passed + assert!(receipts.last().unwrap().receipt.success); + + let db_account = DbAccount::new(contract_addr); + let storage_value = db_account + .storage + .get(&U256::ZERO, &mut working_set) + .unwrap(); + + assert_eq!(storage_value, U256::from(1)); +} diff --git a/crates/evm/src/tests/mod.rs b/crates/evm/src/tests/mod.rs index bcb4b0ecf..2efe80a1f 100644 --- a/crates/evm/src/tests/mod.rs +++ b/crates/evm/src/tests/mod.rs @@ -1,5 +1,6 @@ mod call_tests; mod ef_tests; +mod fork_tests; mod genesis_tests; mod hooks_tests; mod queries; diff --git a/crates/evm/src/tests/utils.rs b/crates/evm/src/tests/utils.rs index bde5b0d37..6475a2c05 100644 --- a/crates/evm/src/tests/utils.rs +++ b/crates/evm/src/tests/utils.rs @@ -132,6 +132,18 @@ pub fn create_contract_message( .sign_default_transaction(TxKind::Create, contract.byte_code(), nonce, 0) .unwrap() } + +pub fn create_contract_message_with_bytecode( + dev_signer: &TestSigner, + nonce: u64, + bytecode: Vec, + value: Option, +) -> RlpEvmTransaction { + dev_signer + .sign_default_transaction(TxKind::Create, bytecode, nonce, value.unwrap_or_default()) + .unwrap() +} + pub(crate) fn create_contract_message_with_fee( dev_signer: &TestSigner, nonce: u64,