From 44ebd2c3510119e60f70488f01e816d57402d645 Mon Sep 17 00:00:00 2001 From: ju Date: Sun, 5 Apr 2026 15:24:06 +0200 Subject: [PATCH] feat: track EXTCODEHASH, EXTCODESIZE, EXTCODECOPY in UsedStateEVMInspector Closes #58 --- .../rbuilder-primitives/src/evm_inspector.rs | 13 +- .../builders/parallel_builder/groups.rs | 123 ++++++++++++++++++ .../src/building/testing/contracts.json | 4 +- .../testing/evm_inspector_tests/mod.rs | 30 +++++ .../testing/evm_inspector_tests/setup.rs | 9 ++ .../src/building/testing/test_chain_state.rs | 12 ++ mev-test-contract/src/MevTest.sol | 8 ++ 7 files changed, 196 insertions(+), 3 deletions(-) diff --git a/crates/rbuilder-primitives/src/evm_inspector.rs b/crates/rbuilder-primitives/src/evm_inspector.rs index 82dff8287..f179c1d67 100644 --- a/crates/rbuilder-primitives/src/evm_inspector.rs +++ b/crates/rbuilder-primitives/src/evm_inspector.rs @@ -1,4 +1,4 @@ -use ahash::HashMap; +use ahash::{HashMap, HashSet}; use alloy_consensus::Transaction; use alloy_primitives::{Address, B256, U256}; use alloy_rpc_types::AccessList; @@ -34,6 +34,8 @@ pub struct UsedStateTrace { pub sent_amount: HashMap, pub created_contracts: Vec
, pub destructed_contracts: Vec
, + /// addresses whose code was read via EXTCODEHASH, EXTCODESIZE, or EXTCODECOPY + pub read_code_addresses: HashSet
, } impl UsedStateTrace { @@ -74,6 +76,8 @@ impl UsedStateTrace { } self.destructed_contracts.push(*address); } + + self.read_code_addresses.extend(&other.read_code_addresses); } pub fn clear(&mut self) { @@ -84,6 +88,7 @@ impl UsedStateTrace { self.sent_amount.clear(); self.created_contracts.clear(); self.destructed_contracts.clear(); + self.read_code_addresses.clear(); } } @@ -197,6 +202,12 @@ where let addr = interpreter.input.target_address; self.next_step_action = NextStepAction::ReadBalanceResult(addr); } + opcode::EXTCODEHASH | opcode::EXTCODESIZE | opcode::EXTCODECOPY => { + if let Ok(addr) = interpreter.stack.peek(0) { + let addr = Address::from_word(B256::from(addr.to_be_bytes())); + self.used_state_trace.read_code_addresses.insert(addr); + } + } _ => (), } } diff --git a/crates/rbuilder/src/building/builders/parallel_builder/groups.rs b/crates/rbuilder/src/building/builders/parallel_builder/groups.rs index f0ebede3c..a1f9f90c7 100644 --- a/crates/rbuilder/src/building/builders/parallel_builder/groups.rs +++ b/crates/rbuilder/src/building/builders/parallel_builder/groups.rs @@ -29,6 +29,7 @@ struct GroupData { writes: Vec, balance_reads: Vec
, balance_writes: Vec
, + code_reads: Vec
, code_writes: Vec
, conflicting_group_ids: HashSet, } @@ -41,6 +42,7 @@ fn combine_groups(groups: Vec, removed_group_ids: Vec) -> Grou let mut writes = Vec::default(); let mut balance_reads = Vec::default(); let mut balance_writes = Vec::default(); + let mut code_reads = Vec::default(); let mut code_writes = Vec::default(); let mut conflicting_group_ids = removed_group_ids.into_iter().collect::>(); for group in groups { @@ -49,6 +51,7 @@ fn combine_groups(groups: Vec, removed_group_ids: Vec) -> Grou writes.extend(group.writes); balance_reads.extend(group.balance_reads); balance_writes.extend(group.balance_writes); + code_reads.extend(group.code_reads); code_writes.extend(group.code_writes); conflicting_group_ids.extend(group.conflicting_group_ids); } @@ -60,6 +63,8 @@ fn combine_groups(groups: Vec, removed_group_ids: Vec) -> Grou balance_reads.dedup(); balance_writes.sort_unstable(); balance_writes.dedup(); + code_reads.sort_unstable(); + code_reads.dedup(); code_writes.sort_unstable(); code_writes.dedup(); @@ -69,6 +74,7 @@ fn combine_groups(groups: Vec, removed_group_ids: Vec) -> Grou writes, balance_reads, balance_writes, + code_reads, code_writes, conflicting_group_ids, } @@ -82,6 +88,7 @@ pub struct ConflictFinder { group_writes: HashMap>>, // same as above group_balance_reads: HashMap>, group_balance_writes: HashMap>, + group_code_reads: HashMap>, group_code_writes: HashMap>, groups: HashMap, orders: HashSet, @@ -95,6 +102,7 @@ impl ConflictFinder { group_writes: HashMap::default(), group_balance_reads: HashMap::default(), group_balance_writes: HashMap::default(), + group_code_reads: HashMap::default(), group_code_writes: HashMap::default(), groups: HashMap::default(), orders: HashSet::default(), @@ -176,6 +184,16 @@ impl ConflictFinder { let inner_groups = inner_mapping.values().flatten(); all_groups_in_conflict.extend(inner_groups); } + // trying to create / destroy a contract other order is reading code from + if let Some(group) = self.group_code_reads.get(contract_addr) { + all_groups_in_conflict.extend_from_slice(group); + } + } + // reading code of a contract other order is creating / destroying + for code_read_addr in &used_state.read_code_addresses { + if let Some(group) = self.group_code_writes.get(code_read_addr) { + all_groups_in_conflict.extend_from_slice(group); + } } all_groups_in_conflict.sort(); all_groups_in_conflict.dedup(); @@ -198,12 +216,18 @@ impl ConflictFinder { code_writes.sort_unstable(); code_writes.dedup(); + let mut code_reads: Vec
= + used_state.read_code_addresses.into_iter().collect(); + code_reads.sort_unstable(); + code_reads.dedup(); + GroupData { orders: vec![order], reads: used_state.read_slot_values.into_keys().collect(), writes: used_state.written_slot_values.into_keys().collect(), balance_reads: used_state.read_balances.into_keys().collect(), balance_writes, + code_reads, code_writes, conflicting_group_ids: HashSet::default(), } @@ -275,6 +299,12 @@ impl ConflictFinder { &group_data.balance_writes, &mut self.group_balance_writes, ); + add_group_to_map( + group_id, + is_new_id, + &group_data.code_reads, + &mut self.group_code_reads, + ); add_group_to_map( group_id, is_new_id, @@ -302,6 +332,7 @@ impl ConflictFinder { &group_data.balance_writes, &mut self.group_balance_writes, ); + remove_group_from_map(group_id, &group_data.code_reads, &mut self.group_code_reads); remove_group_from_map( group_id, &group_data.code_writes, @@ -440,6 +471,27 @@ mod tests { balance_write: Option<&Address>, contract_creation: Option<&Address>, contract_destruction: Option<&Address>, + ) -> Arc { + self.create_order_with_code_read( + read, + write, + balance_read, + balance_write, + contract_creation, + contract_destruction, + None, + ) + } + + pub fn create_order_with_code_read( + &mut self, + read: Option<&SlotKey>, + write: Option<&SlotKey>, + balance_read: Option<&Address>, + balance_write: Option<&Address>, + contract_creation: Option<&Address>, + contract_destruction: Option<&Address>, + code_read: Option<&Address>, ) -> Arc { let mut trace = UsedStateTrace::default(); if let Some(read) = read { @@ -469,6 +521,9 @@ mod tests { if let Some(contract_address) = contract_destruction { trace.destructed_contracts.push(*contract_address); } + if let Some(code_read_addr) = code_read { + trace.read_code_addresses.insert(*code_read_addr); + } Arc::new(SimulatedOrder::new( Arc::new(Order::Tx(MempoolTx { @@ -595,4 +650,72 @@ mod tests { let groups = cached_groups.get_order_groups(); assert_eq!(groups.len(), 1); } + + #[test] + fn two_code_reads_no_conflict() { + let mut data_gen = DataGenerator::new(); + let addr = Address::random(); + let oa = + data_gen.create_order_with_code_read(None, None, None, None, None, None, Some(&addr)); + let ob = + data_gen.create_order_with_code_read(None, None, None, None, None, None, Some(&addr)); + let mut cached_groups = ConflictFinder::new(); + cached_groups.add_orders(vec![oa, ob]); + let groups = cached_groups.get_order_groups(); + assert_eq!(groups.len(), 2); + } + + #[test] + fn code_read_and_creation_conflict() { + let mut data_gen = DataGenerator::new(); + let addr = Address::random(); + let oa = + data_gen.create_order_with_code_read(None, None, None, None, None, None, Some(&addr)); + let ob = data_gen.create_order(None, None, None, None, Some(&addr), None); + let mut cached_groups = ConflictFinder::new(); + cached_groups.add_orders(vec![oa, ob]); + let groups = cached_groups.get_order_groups(); + assert_eq!(groups.len(), 1); + } + + #[test] + fn code_read_and_destruction_conflict() { + let mut data_gen = DataGenerator::new(); + let addr = Address::random(); + let oa = + data_gen.create_order_with_code_read(None, None, None, None, None, None, Some(&addr)); + let ob = data_gen.create_order(None, None, None, None, None, Some(&addr)); + let mut cached_groups = ConflictFinder::new(); + cached_groups.add_orders(vec![oa, ob]); + let groups = cached_groups.get_order_groups(); + assert_eq!(groups.len(), 1); + } + + #[test] + fn creation_then_code_read_conflict() { + let mut data_gen = DataGenerator::new(); + let addr = Address::random(); + // code_write first, then code_read — tests the reverse direction + let oa = data_gen.create_order(None, None, None, None, Some(&addr), None); + let ob = + data_gen.create_order_with_code_read(None, None, None, None, None, None, Some(&addr)); + let mut cached_groups = ConflictFinder::new(); + cached_groups.add_orders(vec![oa, ob]); + let groups = cached_groups.get_order_groups(); + assert_eq!(groups.len(), 1); + } + + #[test] + fn destruction_then_code_read_conflict() { + let mut data_gen = DataGenerator::new(); + let addr = Address::random(); + // code_write first, then code_read — tests the reverse direction + let oa = data_gen.create_order(None, None, None, None, None, Some(&addr)); + let ob = + data_gen.create_order_with_code_read(None, None, None, None, None, None, Some(&addr)); + let mut cached_groups = ConflictFinder::new(); + cached_groups.add_orders(vec![oa, ob]); + let groups = cached_groups.get_order_groups(); + assert_eq!(groups.len(), 1); + } } diff --git a/crates/rbuilder/src/building/testing/contracts.json b/crates/rbuilder/src/building/testing/contracts.json index 7816e7bd1..7a8c27ced 100644 --- a/crates/rbuilder/src/building/testing/contracts.json +++ b/crates/rbuilder/src/building/testing/contracts.json @@ -1,4 +1,4 @@ { - "MevTest": "0x6080604052600436106100555760003560e01c80634988880a1461005a5780637da3c3ab1461006f578063d6782ec714610077578063e6d252451461008a578063f9711c221461009d578063f9da581d146100b0575b600080fd5b61006d61006836600461027b565b6100b8565b005b61006d610159565b61006d61008536600461029d565b610163565b61006d61009836600461029d565b61017b565b61006d6100ab36600461029d565b6101b0565b61006d610240565b815481811461010d5760405162461bcd60e51b815260206004820152601860248201527f4f6c642076616c756520646f6573206e6f74206d617463680000000000000000604482015260640160405180910390fd5b600061011a8360016102cd565b808555905034156101535760405141903480156108fc02916000818181858888f19350505050158015610151573d6000803e3d6000fd5b505b50505050565b610161610159565b565b6101776001600160a01b03821631476102cd565b5050565b6040516001600160a01b038216903480156108fc02916000818181858888f19350505050158015610177573d6000803e3d6000fd5b60006040516101be9061026f565b604051809103906000f0801580156101da573d6000803e3d6000fd5b50604051631beb261560e01b81526001600160a01b03848116600483015291925090821690631beb26159034906024016000604051808303818588803b15801561022357600080fd5b505af1158015610237573d6000803e3d6000fd5b50505050505050565b60405141903480156108fc02916000818181858888f1935050505015801561026c573d6000803e3d6000fd5b50565b60bf806102f583390190565b6000806040838503121561028e57600080fd5b50508035926020909101359150565b6000602082840312156102af57600080fd5b81356001600160a01b03811681146102c657600080fd5b9392505050565b808201808211156102ee57634e487b7160e01b600052601160045260246000fd5b9291505056fe6080604052348015600f57600080fd5b5060a280601d6000396000f3fe608060405260043610601c5760003560e01c80631beb2615146021575b600080fd5b6030602c366004603e565b6032565b005b806001600160a01b0316ff5b600060208284031215604f57600080fd5b81356001600160a01b0381168114606557600080fd5b939250505056fea26469706673582212207422a0f368426edbe9d06fc472e76995dc7edc7b9e20673c4ab45757ae32f30064736f6c634300081a0033a26469706673582212201b4bf34a5948cd633421a6c6648a0db2ec7df1d12ccf2973d2cf78dd6b5775e464736f6c634300081a0033", - "MevTestInitBytecode": "0x6080604052348015600f57600080fd5b506103e98061001f6000396000f3fe6080604052600436106100555760003560e01c80634988880a1461005a5780637da3c3ab1461006f578063d6782ec714610077578063e6d252451461008a578063f9711c221461009d578063f9da581d146100b0575b600080fd5b61006d61006836600461027b565b6100b8565b005b61006d610159565b61006d61008536600461029d565b610163565b61006d61009836600461029d565b61017b565b61006d6100ab36600461029d565b6101b0565b61006d610240565b815481811461010d5760405162461bcd60e51b815260206004820152601860248201527f4f6c642076616c756520646f6573206e6f74206d617463680000000000000000604482015260640160405180910390fd5b600061011a8360016102cd565b808555905034156101535760405141903480156108fc02916000818181858888f19350505050158015610151573d6000803e3d6000fd5b505b50505050565b610161610159565b565b6101776001600160a01b03821631476102cd565b5050565b6040516001600160a01b038216903480156108fc02916000818181858888f19350505050158015610177573d6000803e3d6000fd5b60006040516101be9061026f565b604051809103906000f0801580156101da573d6000803e3d6000fd5b50604051631beb261560e01b81526001600160a01b03848116600483015291925090821690631beb26159034906024016000604051808303818588803b15801561022357600080fd5b505af1158015610237573d6000803e3d6000fd5b50505050505050565b60405141903480156108fc02916000818181858888f1935050505015801561026c573d6000803e3d6000fd5b50565b60bf806102f583390190565b6000806040838503121561028e57600080fd5b50508035926020909101359150565b6000602082840312156102af57600080fd5b81356001600160a01b03811681146102c657600080fd5b9392505050565b808201808211156102ee57634e487b7160e01b600052601160045260246000fd5b9291505056fe6080604052348015600f57600080fd5b5060a280601d6000396000f3fe608060405260043610601c5760003560e01c80631beb2615146021575b600080fd5b6030602c366004603e565b6032565b005b806001600160a01b0316ff5b600060208284031215604f57600080fd5b81356001600160a01b0381168114606557600080fd5b939250505056fea26469706673582212207422a0f368426edbe9d06fc472e76995dc7edc7b9e20673c4ab45757ae32f30064736f6c634300081a0033a26469706673582212201b4bf34a5948cd633421a6c6648a0db2ec7df1d12ccf2973d2cf78dd6b5775e464736f6c634300081a0033" + "MevTest": "0x60806040526004361061006f575f3560e01c8063d6782ec71161004d578063d6782ec7146100d6578063e6d25245146100f2578063f9711c221461010e578063f9da581d1461012a5761006f565b80634988880a146100735780637da3c3ab1461008f578063c507b59214610099575b5f5ffd5b61008d60048036038101906100889190610387565b610134565b005b6100976101e1565b005b3480156100a4575f5ffd5b506100bf60048036038101906100ba919061041f565b6101eb565b6040516100cd929190610471565b60405180910390f35b6100f060048036038101906100eb91906104d3565b6101fa565b005b61010c600480360381019061010791906104d3565b610221565b005b610128600480360381019061012391906104d3565b610268565b005b6101326102fd565b005b5f8254905081811461017b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161017290610558565b60405180910390fd5b5f60018361018991906105a3565b90508084555f3411156101db574173ffffffffffffffffffffffffffffffffffffffff166108fc3490811502906040515f60405180830381858888f193505050501580156101d9573d5f5f3e3d5ffd5b505b50505050565b6101e96101e1565b565b5f5f823f9150823b9050915091565b8073ffffffffffffffffffffffffffffffffffffffff16314761021d91906105a3565b5050565b8073ffffffffffffffffffffffffffffffffffffffff166108fc3490811502906040515f60405180830381858888f19350505050158015610264573d5f5f3e3d5ffd5b5050565b5f60405161027590610343565b604051809103905ff08015801561028e573d5f5f3e3d5ffd5b5090508073ffffffffffffffffffffffffffffffffffffffff16631beb261534846040518363ffffffff1660e01b81526004016102cb91906105e5565b5f604051808303818588803b1580156102e2575f5ffd5b505af11580156102f4573d5f5f3e3d5ffd5b50505050505050565b4173ffffffffffffffffffffffffffffffffffffffff166108fc3490811502906040515f60405180830381858888f19350505050158015610340573d5f5f3e3d5ffd5b50565b61011f806105ff83390190565b5f5ffd5b5f819050919050565b61036681610354565b8114610370575f5ffd5b50565b5f813590506103818161035d565b92915050565b5f5f6040838503121561039d5761039c610350565b5b5f6103aa85828601610373565b92505060206103bb85828601610373565b9150509250929050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6103ee826103c5565b9050919050565b6103fe816103e4565b8114610408575f5ffd5b50565b5f81359050610419816103f5565b92915050565b5f6020828403121561043457610433610350565b5b5f6104418482850161040b565b91505092915050565b5f819050919050565b61045c8161044a565b82525050565b61046b81610354565b82525050565b5f6040820190506104845f830185610453565b6104916020830184610462565b9392505050565b5f6104a2826103c5565b9050919050565b6104b281610498565b81146104bc575f5ffd5b50565b5f813590506104cd816104a9565b92915050565b5f602082840312156104e8576104e7610350565b5b5f6104f5848285016104bf565b91505092915050565b5f82825260208201905092915050565b7f4f6c642076616c756520646f6573206e6f74206d6174636800000000000000005f82015250565b5f6105426018836104fe565b915061054d8261050e565b602082019050919050565b5f6020820190508181035f83015261056f81610536565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6105ad82610354565b91506105b883610354565b92508282019050808211156105d0576105cf610576565b5b92915050565b6105df81610498565b82525050565b5f6020820190506105f85f8301846105d6565b9291505056fe6080604052348015600e575f5ffd5b506101038061001c5f395ff3fe608060405260043610601b575f3560e01c80631beb261514601f575b5f5ffd5b603560048036038101906031919060a7565b6037565b005b8073ffffffffffffffffffffffffffffffffffffffff16ff5b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f607b826054565b9050919050565b6089816073565b81146092575f5ffd5b50565b5f8135905060a1816082565b92915050565b5f6020828403121560b95760b86050565b5b5f60c4848285016095565b9150509291505056fea264697066735822122046046525505278e63569492562d418a9cee99cfa26817db4080edd9b928aed4f64736f6c63430008210033a2646970667358221220d492e479b75edcd03de33e0e0d5a0e2fcd05a4092f6b2cea7c4e7b75497566e864736f6c63430008210033", + "MevTestInitBytecode": "0x6080604052348015600e575f5ffd5b506107538061001c5f395ff3fe60806040526004361061006f575f3560e01c8063d6782ec71161004d578063d6782ec7146100d6578063e6d25245146100f2578063f9711c221461010e578063f9da581d1461012a5761006f565b80634988880a146100735780637da3c3ab1461008f578063c507b59214610099575b5f5ffd5b61008d60048036038101906100889190610387565b610134565b005b6100976101e1565b005b3480156100a4575f5ffd5b506100bf60048036038101906100ba919061041f565b6101eb565b6040516100cd929190610471565b60405180910390f35b6100f060048036038101906100eb91906104d3565b6101fa565b005b61010c600480360381019061010791906104d3565b610221565b005b610128600480360381019061012391906104d3565b610268565b005b6101326102fd565b005b5f8254905081811461017b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161017290610558565b60405180910390fd5b5f60018361018991906105a3565b90508084555f3411156101db574173ffffffffffffffffffffffffffffffffffffffff166108fc3490811502906040515f60405180830381858888f193505050501580156101d9573d5f5f3e3d5ffd5b505b50505050565b6101e96101e1565b565b5f5f823f9150823b9050915091565b8073ffffffffffffffffffffffffffffffffffffffff16314761021d91906105a3565b5050565b8073ffffffffffffffffffffffffffffffffffffffff166108fc3490811502906040515f60405180830381858888f19350505050158015610264573d5f5f3e3d5ffd5b5050565b5f60405161027590610343565b604051809103905ff08015801561028e573d5f5f3e3d5ffd5b5090508073ffffffffffffffffffffffffffffffffffffffff16631beb261534846040518363ffffffff1660e01b81526004016102cb91906105e5565b5f604051808303818588803b1580156102e2575f5ffd5b505af11580156102f4573d5f5f3e3d5ffd5b50505050505050565b4173ffffffffffffffffffffffffffffffffffffffff166108fc3490811502906040515f60405180830381858888f19350505050158015610340573d5f5f3e3d5ffd5b50565b61011f806105ff83390190565b5f5ffd5b5f819050919050565b61036681610354565b8114610370575f5ffd5b50565b5f813590506103818161035d565b92915050565b5f5f6040838503121561039d5761039c610350565b5b5f6103aa85828601610373565b92505060206103bb85828601610373565b9150509250929050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6103ee826103c5565b9050919050565b6103fe816103e4565b8114610408575f5ffd5b50565b5f81359050610419816103f5565b92915050565b5f6020828403121561043457610433610350565b5b5f6104418482850161040b565b91505092915050565b5f819050919050565b61045c8161044a565b82525050565b61046b81610354565b82525050565b5f6040820190506104845f830185610453565b6104916020830184610462565b9392505050565b5f6104a2826103c5565b9050919050565b6104b281610498565b81146104bc575f5ffd5b50565b5f813590506104cd816104a9565b92915050565b5f602082840312156104e8576104e7610350565b5b5f6104f5848285016104bf565b91505092915050565b5f82825260208201905092915050565b7f4f6c642076616c756520646f6573206e6f74206d6174636800000000000000005f82015250565b5f6105426018836104fe565b915061054d8261050e565b602082019050919050565b5f6020820190508181035f83015261056f81610536565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6105ad82610354565b91506105b883610354565b92508282019050808211156105d0576105cf610576565b5b92915050565b6105df81610498565b82525050565b5f6020820190506105f85f8301846105d6565b9291505056fe6080604052348015600e575f5ffd5b506101038061001c5f395ff3fe608060405260043610601b575f3560e01c80631beb261514601f575b5f5ffd5b603560048036038101906031919060a7565b6037565b005b8073ffffffffffffffffffffffffffffffffffffffff16ff5b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f607b826054565b9050919050565b6089816073565b81146092575f5ffd5b50565b5f8135905060a1816082565b92915050565b5f6020828403121560b95760b86050565b5b5f60c4848285016095565b9150509291505056fea264697066735822122046046525505278e63569492562d418a9cee99cfa26817db4080edd9b928aed4f64736f6c63430008210033a2646970667358221220d492e479b75edcd03de33e0e0d5a0e2fcd05a4092f6b2cea7c4e7b75497566e864736f6c63430008210033" } diff --git a/crates/rbuilder/src/building/testing/evm_inspector_tests/mod.rs b/crates/rbuilder/src/building/testing/evm_inspector_tests/mod.rs index fe3f384c8..ed1933533 100644 --- a/crates/rbuilder/src/building/testing/evm_inspector_tests/mod.rs +++ b/crates/rbuilder/src/building/testing/evm_inspector_tests/mod.rs @@ -154,3 +154,33 @@ fn test_ephemeral_contract_destruct() -> eyre::Result<()> { Ok(()) } + +#[test] +fn test_read_code() -> eyre::Result<()> { + let test_setup = TestSetup::new()?; + + let mev_test_contract_addr = test_setup.named_address(NamedAddr::MevTest)?; + let dummy_addr = test_setup.named_address(NamedAddr::Dummy)?; + + // read code of MevTest contract (has code) + let tx = test_setup.make_test_read_code_tx(mev_test_contract_addr)?; + let used_state_trace = test_setup.inspect_tx_without_commit(tx)?; + + assert!( + used_state_trace + .read_code_addresses + .contains(&mev_test_contract_addr), + "should track EXTCODEHASH/EXTCODESIZE on contract with code" + ); + + // read code of dummy address (no code) + let tx = test_setup.make_test_read_code_tx(dummy_addr)?; + let used_state_trace = test_setup.inspect_tx_without_commit(tx)?; + + assert!( + used_state_trace.read_code_addresses.contains(&dummy_addr), + "should track EXTCODEHASH/EXTCODESIZE on address without code" + ); + + Ok(()) +} diff --git a/crates/rbuilder/src/building/testing/evm_inspector_tests/setup.rs b/crates/rbuilder/src/building/testing/evm_inspector_tests/setup.rs index 07f536879..001b5ec7f 100644 --- a/crates/rbuilder/src/building/testing/evm_inspector_tests/setup.rs +++ b/crates/rbuilder/src/building/testing/evm_inspector_tests/setup.rs @@ -68,6 +68,15 @@ impl TestSetup { Ok(tx) } + pub fn make_test_read_code_tx( + &self, + target: Address, + ) -> eyre::Result> { + let tx_args = TxArgs::new_test_read_code(NamedAddr::User(0), 0, target); + let tx = self.test_chain.sign_tx(tx_args)?; + Ok(tx) + } + pub fn make_test_ephemeral_contract_destruct_tx( &self, refund_addr: Address, diff --git a/crates/rbuilder/src/building/testing/test_chain_state.rs b/crates/rbuilder/src/building/testing/test_chain_state.rs index 11ac87655..323dd7298 100644 --- a/crates/rbuilder/src/building/testing/test_chain_state.rs +++ b/crates/rbuilder/src/building/testing/test_chain_state.rs @@ -508,6 +508,17 @@ impl TxArgs { .value(value) } + /// This transaction for test purpose only, it reads the code hash and size of the given address. + pub fn new_test_read_code(from: NamedAddr, nonce: u64, target: Address) -> Self { + Self::new(from, nonce).to(NamedAddr::MevTest).input( + [ + (*TEST_READ_CODE).into(), + B256::left_padding_from(target.as_slice()).to_vec(), + ] + .concat(), + ) + } + /// This transaction for test purpose only, it deploys a contract and let it selfdestruct within the tx. pub fn new_test_ephemeral_contract_destruct( from: NamedAddr, @@ -587,6 +598,7 @@ lazy_static! { static ref TEST_READ_BALANCE: [u8; 4] = selector("testReadBalance(address)"); static ref TEST_EPHEMERAL_CONTRACT_DESTRUCT: [u8; 4] = selector("testEphemeralContractDestruct(address)"); + static ref TEST_READ_CODE: [u8; 4] = selector("testReadCode(address)"); } impl TestContracts { diff --git a/mev-test-contract/src/MevTest.sol b/mev-test-contract/src/MevTest.sol index 9b05603c6..e898df559 100644 --- a/mev-test-contract/src/MevTest.sol +++ b/mev-test-contract/src/MevTest.sol @@ -53,4 +53,12 @@ contract MevTest { EphemeralContractTest ephemeral_contract = new EphemeralContractTest(); ephemeral_contract.destruct{value: msg.value}(refund); } + + /// Reads the code hash and code size of the given address, for testing evm inspector with extcodehash/extcodesize opcodes. + function testReadCode(address target) public view returns (bytes32 h, uint256 s) { + assembly { + h := extcodehash(target) + s := extcodesize(target) + } + } }