Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
6540036
feat(l1): add amsterdam fork to chain config
lakshya-sky Dec 23, 2025
733f4bd
update revm runner with todo and fix the check error
lakshya-sky Dec 23, 2025
c838262
fix tests
lakshya-sky Dec 23, 2025
b860d7b
fix(l1): use current header instead of parent to get excess blob gas …
lakshya-sky Dec 23, 2025
282a56d
TODO: need to add bal in block header and body
lakshya-sky Dec 23, 2025
d7bbe93
added bal types
lakshya-sky Dec 26, 2025
fe588a0
derive debug
lakshya-sky Dec 26, 2025
c0aa92f
Merge branch 'feature/bal-types' into feat/add-new-payload-v5
lakshya-sky Dec 26, 2025
fcf0063
added rlp conversions
lakshya-sky Dec 26, 2025
c007745
Merge branch 'feature/bal-types' into feat/add-new-payload-v5
lakshya-sky Dec 26, 2025
4ce3e76
rlp decode
lakshya-sky Dec 26, 2025
5412b37
Merge branch 'feature/bal-types' into feat/add-new-payload-v5
lakshya-sky Dec 26, 2025
5dcb02c
payload v5 method working
lakshya-sky Dec 26, 2025
8637533
Merge remote-tracking branch 'origin/main' into feat/eip-7928
edg-l Jan 26, 2026
353fd0c
fix 0x serialization
edg-l Jan 26, 2026
f6101ea
improve usize -> u32
edg-l Jan 26, 2026
eb56483
fix migrations
edg-l Jan 26, 2026
dbffb82
fix sim
edg-l Jan 26, 2026
f4aa729
fix slotchange sort
edg-l Jan 26, 2026
8a9cb48
fix migrations
edg-l Jan 26, 2026
30efe27
fix state_v2
edg-l Jan 26, 2026
628ae45
Merge branch 'main' into eip_7928
edg-l Jan 26, 2026
013f0e0
implement BAL execution tracking and storage
edg-l Jan 26, 2026
c0c0521
validate hash
edg-l Jan 26, 2026
ed2ac1a
optimize
edg-l Jan 26, 2026
e151502
rpc payload integration
edg-l Jan 26, 2026
a9f0387
move cheap check first
edg-l Jan 27, 2026
1a0d3c3
Merge branch 'main' into eip_7928
edg-l Jan 27, 2026
1f1edf7
Merge remote-tracking branch 'origin/eip_7928' into eip_7928_tracking
edg-l Jan 27, 2026
a01beb0
fix
edg-l Jan 27, 2026
89f2607
bug fixes
edg-l Jan 27, 2026
beb008d
fixes
edg-l Jan 27, 2026
73fe2ef
improve perf
edg-l Jan 27, 2026
0937bd5
more tests
edg-l Jan 27, 2026
0cfba04
Merge branch 'main' into eip_7928
edg-l Jan 28, 2026
d6bef31
Merge branch 'main' into eip_7928
edg-l Jan 28, 2026
4152621
fix
edg-l Jan 28, 2026
2f4d6f4
Merge remote-tracking branch 'origin/eip_7928' into eip_7928_tracking
edg-l Jan 28, 2026
5f2cb9b
fix system address
edg-l Jan 29, 2026
7e8c1d7
fix
edg-l Jan 29, 2026
0c36cb9
Merge branch 'main' into eip_7928
edg-l Jan 29, 2026
e1b7e69
Merge remote-tracking branch 'origin/eip_7928' into eip_7928_tracking
edg-l Jan 29, 2026
2a8ea2b
add missing rpc methods
edg-l Jan 29, 2026
86eaa2b
add debug rpc
edg-l Jan 29, 2026
49d62d8
add cloneless sorting
edg-l Jan 29, 2026
9ee9cef
Merge branch 'main' into eip_7928
edg-l Jan 30, 2026
c192992
Merge remote-tracking branch 'origin/eip_7928' into eip_7928_tracking
edg-l Jan 30, 2026
88b5904
Merge remote-tracking branch 'origin/main' into eip_7928_tracking
edg-l Feb 2, 2026
9b8130d
Merge branch 'main' into eip_7928_tracking
edg-l Feb 2, 2026
632be7b
Only pay coinbase if there's actually a fee to pay.
edg-l Feb 2, 2026
9e0e094
fix Per EIP-7928, storage reads now persist on revert, and reverted …
edg-l Feb 2, 2026
5521fdd
fixes
edg-l Feb 2, 2026
54ef2ed
fix
edg-l Feb 2, 2026
12db8d0
Fix #5: Record coinbase as touched for user transactions
edg-l Feb 2, 2026
0f5ea51
Take a BAL checkpoint AFTER clearing the backup.
edg-l Feb 2, 2026
b5b0063
Merge branch 'main' into eip_7928_tracking
edg-l Feb 3, 2026
b55af2c
instead of adding ALL recorded nonce changes, it now groups them by t…
edg-l Feb 3, 2026
788cd03
clippy
edg-l Feb 3, 2026
46f6c33
fixes
edg-l Feb 3, 2026
cdea9ce
clippy
edg-l Feb 3, 2026
8ccbe76
fixes
edg-l Feb 3, 2026
c544b1b
fixes
edg-l Feb 3, 2026
4b891bc
Merge branch 'main' into eip_7928_tracking
edg-l Feb 3, 2026
3830068
Merge remote-tracking branch 'origin/main' into eip_7928_tracking
edg-l Feb 4, 2026
5894320
move tests
edg-l Feb 4, 2026
309f232
enable some tests
edg-l Feb 4, 2026
e54a55e
fix payload
edg-l Feb 4, 2026
945cb62
test(ef_tests): enable passing EIP-7928 tests, skip dependent ones
edg-l Feb 4, 2026
cd7d3d2
lint
edg-l Feb 4, 2026
e487ac1
cow optimize clone
edg-l Feb 4, 2026
bba6f1e
update eips.md
edg-l Feb 4, 2026
776779a
Merge branch 'main' into eip_7928_tracking
edg-l Feb 5, 2026
aa6ed59
review comments
edg-l Feb 5, 2026
ce8754c
fix(eip7928): remove dead code tx_initial_balances and fix misleading…
edg-l Feb 5, 2026
9bed87d
fix(eip7928): don't pre-record coinbase in BAL for withdrawal-only bl…
edg-l Feb 5, 2026
eb298bc
test(eip7928): update skip list - 7 more BAL tests now pass
edg-l Feb 5, 2026
e510b64
Merge branch 'main' into eip_7928_tracking
edg-l Feb 6, 2026
6c2a48b
fix
edg-l Feb 6, 2026
3104367
Merge branch 'main' into eip_7928_tracking
edg-l Feb 6, 2026
a0588e6
Merge branch 'main' into eip_7928_tracking
edg-l Feb 6, 2026
f3a89f7
Merge branch 'main' into eip_7928_tracking
edg-l Feb 9, 2026
3cfe858
apply comments2
edg-l Feb 9, 2026
b3354ad
fix more ef tests
edg-l Feb 6, 2026
d04a5ec
lint
edg-l Feb 9, 2026
791ec90
ef test script
edg-l Feb 9, 2026
a5f246e
fix(bal): clean up BAL recorder on selfdestruct (EIP-7928/EIP-6780)
edg-l Feb 9, 2026
f1d931a
fix(bal): track SYSTEM_ADDRESS changes from regular transactions
edg-l Feb 9, 2026
1f82d9d
test: enable all Amsterdam selfdestruct EF tests
edg-l Feb 9, 2026
0374b2b
move script
edg-l Feb 9, 2026
cf5fb03
state tests fixes
edg-l Feb 9, 2026
04ff0a7
fix(eip-7708): correct ETH transfer log emission for selfdestruct-to-…
edg-l Feb 9, 2026
8dfea3f
fix: collapse nested if in track_selfdestruct to satisfy clippy
edg-l Feb 9, 2026
66d82b3
lint
edg-l Feb 9, 2026
11df0f7
refactor: extract record_bal_call_touch helper for CALL-family opcodes
edg-l Feb 9, 2026
b1e5119
docs: add safety comment for unwrap_or(u64::MAX) in BAL touch helper
edg-l Feb 9, 2026
21c9681
format
edg-l Feb 9, 2026
138c5e7
fixes
edg-l Feb 9, 2026
d06b953
lint
edg-l Feb 9, 2026
c5a958f
Merge branch 'main' into bal_fixes
edg-l Feb 10, 2026
5645616
fix
edg-l Feb 10, 2026
1f7be69
review fix
edg-l Feb 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,5 @@ core.*

# Log files
*.log

__pycache__/
116 changes: 106 additions & 10 deletions crates/common/types/block_access_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,10 @@ pub struct BlockAccessListRecorder {
/// Used for efficient checkpoint/restore without cloning storage_reads.
/// On restore, we truncate this Vec and the slots go back to being reads.
reads_promoted_to_writes: BTreeMap<Address, Vec<U256>>,
/// When true, SYSTEM_ADDRESS balance/nonce/touch changes are filtered out.
/// Set during system contract calls (EIP-2935, EIP-4788, etc.) where the
/// system address account is backed up and restored, so changes are transient.
in_system_call: bool,
}

impl BlockAccessListRecorder {
Expand Down Expand Up @@ -588,6 +592,15 @@ impl BlockAccessListRecorder {
}
}

// If this slot was promoted from read to write, undo the promotion
// so build() doesn't skip it from storage_reads.
if let Some(promoted) = self.reads_promoted_to_writes.get_mut(&addr) {
promoted.retain(|s| *s != slot);
if promoted.is_empty() {
self.reads_promoted_to_writes.remove(&addr);
}
}

// Add as a read instead
self.storage_reads.entry(addr).or_default().insert(slot);
}
Expand Down Expand Up @@ -638,24 +651,40 @@ impl BlockAccessListRecorder {
self.current_index
}

/// Marks the recorder as being inside a system contract call.
/// While in this mode, SYSTEM_ADDRESS balance/nonce/touch changes are filtered out
/// because system calls backup and restore the system address account state.
pub fn enter_system_call(&mut self) {
self.in_system_call = true;
}

/// Marks the recorder as no longer inside a system contract call.
pub fn exit_system_call(&mut self) {
self.in_system_call = false;
}

/// Records an address as touched during execution.
/// The address will appear in the BAL even if it has no state changes.
///
/// Note: SYSTEM_ADDRESS is excluded unless it has actual state changes.
/// Note: SYSTEM_ADDRESS is excluded during system contract calls.
pub fn record_touched_address(&mut self, address: Address) {
// SYSTEM_ADDRESS is only included if it has actual state changes
if address != SYSTEM_ADDRESS {
self.touched_addresses.insert(address);
if address == SYSTEM_ADDRESS && self.in_system_call {
return;
}
self.touched_addresses.insert(address);
}

/// Records multiple addresses as touched during execution.
/// More efficient than calling `record_touched_address` in a loop.
///
/// Note: SYSTEM_ADDRESS is filtered out.
/// Note: SYSTEM_ADDRESS is filtered out during system contract calls.
pub fn extend_touched_addresses(&mut self, addresses: impl Iterator<Item = Address>) {
self.touched_addresses
.extend(addresses.filter(|addr| *addr != SYSTEM_ADDRESS));
if self.in_system_call {
self.touched_addresses
.extend(addresses.filter(|addr| *addr != SYSTEM_ADDRESS));
} else {
self.touched_addresses.extend(addresses);
}
}

/// Records a storage slot read.
Expand Down Expand Up @@ -736,7 +765,8 @@ impl BlockAccessListRecorder {
/// Should be called after every balance modification.
/// Per EIP-7928, only the final balance per (address, block_access_index) is recorded.
/// If multiple balance changes occur within the same transaction, only the last one matters.
/// Note: SYSTEM_ADDRESS balance changes are excluded (system calls backup/restore it).
/// Note: SYSTEM_ADDRESS balance changes are excluded during system contract calls
/// (system calls backup/restore the system address account state).
///
/// IMPORTANT: We always push new entries (never update in-place) to support checkpoint/restore.
/// The checkpoint mechanism captures lengths, not values. If we updated in-place, the restored
Expand All @@ -745,7 +775,7 @@ impl BlockAccessListRecorder {
pub fn record_balance_change(&mut self, address: Address, post_balance: U256) {
// SYSTEM_ADDRESS balance changes from system contract calls should not be recorded
// (system calls backup and restore SYSTEM_ADDRESS state)
if address == SYSTEM_ADDRESS {
if address == SYSTEM_ADDRESS && self.in_system_call {
return;
}

Expand Down Expand Up @@ -779,7 +809,7 @@ impl BlockAccessListRecorder {
/// Note: SYSTEM_ADDRESS nonce changes from system calls are excluded.
pub fn record_nonce_change(&mut self, address: Address, post_nonce: u64) {
// SYSTEM_ADDRESS nonce changes from system contract calls should not be recorded
if address == SYSTEM_ADDRESS {
if address == SYSTEM_ADDRESS && self.in_system_call {
return;
}
self.nonce_changes
Expand Down Expand Up @@ -1093,4 +1123,70 @@ impl BlockAccessListRecorder {
// Note: touched_addresses is intentionally NOT restored - per EIP-7928,
// accessed addresses must be included even from reverted calls
}

/// Handles BAL cleanup for a self-destructed account per EIP-7928/EIP-6780.
/// Called after destroy_account for contracts created and destroyed in the same tx.
/// Removes nonce/code changes, converts storage writes to reads.
/// Matches EELS `track_selfdestruct` in state_tracker.py:315.
pub fn track_selfdestruct(&mut self, address: Address) {
let idx = self.current_index;

// 1. Remove nonce changes for this address at current tx index
if let Some(changes) = self.nonce_changes.get_mut(&address) {
changes.retain(|(i, _)| *i != idx);
if changes.is_empty() {
self.nonce_changes.remove(&address);
}
}

// 2. Remove balance changes if pre-balance was 0 (round-trip: 0→X→0)
// If initial_balance was never set, treat it as 0 (contract created with no value)
let pre_balance = self
.initial_balances
.get(&address)
.copied()
.unwrap_or_default();
if pre_balance.is_zero()
&& let Some(changes) = self.balance_changes.get_mut(&address)
{
changes.retain(|(i, _)| *i != idx);
if changes.is_empty() {
self.balance_changes.remove(&address);
}
}

// 3. Remove code changes for this address at current tx index
if let Some(changes) = self.code_changes.get_mut(&address) {
changes.retain(|(i, _)| *i != idx);
if changes.is_empty() {
self.code_changes.remove(&address);
}
}

// 4. Convert storage writes from current tx to reads
if let Some(slots) = self.storage_writes.get_mut(&address) {
let mut slots_to_read: Vec<U256> = Vec::new();
for (slot, changes) in slots.iter_mut() {
if changes.iter().any(|(i, _)| *i == idx) {
slots_to_read.push(*slot);
}
changes.retain(|(i, _)| *i != idx);
}
slots.retain(|_, changes| !changes.is_empty());
if slots.is_empty() {
self.storage_writes.remove(&address);
}

for slot in slots_to_read {
self.storage_reads.entry(address).or_default().insert(slot);
// Undo read-to-write promotion for these slots
if let Some(promoted) = self.reads_promoted_to_writes.get_mut(&address) {
promoted.retain(|s| *s != slot);
if promoted.is_empty() {
self.reads_promoted_to_writes.remove(&address);
}
}
}
}
}
}
17 changes: 14 additions & 3 deletions crates/vm/backends/levm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -751,10 +751,21 @@ pub fn generic_system_contract_levm(
data: calldata,
..Default::default()
});
let mut vm =
VM::new(env, db, tx, LevmCallTracer::disabled(), vm_type).map_err(EvmError::from)?;
// EIP-7928: Mark BAL recorder as in system call mode to filter SYSTEM_ADDRESS changes
if let Some(recorder) = db.bal_recorder.as_mut() {
recorder.enter_system_call();
}

let result = VM::new(env, db, tx, LevmCallTracer::disabled(), vm_type)
.and_then(|mut vm| vm.execute())
.map_err(EvmError::from);

// EIP-7928: Exit system call mode before restoring accounts (must run even on error)
if let Some(recorder) = db.bal_recorder.as_mut() {
recorder.exit_system_call();
}

let report = vm.execute().map_err(EvmError::from)?;
let report = result?;

if let Some(system_account) = system_account_backup {
db.current_accounts_state
Expand Down
13 changes: 10 additions & 3 deletions crates/vm/levm/src/hooks/default_hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,11 @@ pub fn refund_sender(

// EIP-7778: Separate block vs user gas accounting for Amsterdam+
if vm.env.config.fork >= Fork::Amsterdam {
// Block accounting uses pre-refund gas
ctx_result.gas_used = gas_used_pre_refund;
// User pays post-refund gas
// Block accounting uses max(pre-refund gas, calldata floor)
// This prevents gas smuggling via refunds (EIP-7778)
let floor = vm.get_min_gas_used()?;
ctx_result.gas_used = gas_used_pre_refund.max(floor);
// User pays post-refund gas (with floor)
ctx_result.gas_spent = gas_spent;
} else {
// Pre-Amsterdam: both use post-refund value
Expand Down Expand Up @@ -300,6 +302,11 @@ pub fn delete_self_destruct_accounts(vm: &mut VM<'_>) -> Result<(), VMError> {

*account_to_remove = LevmAccount::default();
account_to_remove.mark_destroyed();

// EIP-7928: Clean up BAL for selfdestructed account
if let Some(recorder) = vm.db.bal_recorder.as_mut() {
recorder.track_selfdestruct(*address);
}
}

Ok(())
Expand Down
Loading