Skip to content

EIP-7708 (ETH transfer logs) - #2469

Open
brett-monad wants to merge 1 commit into
eip-8246-remove-selfdestruct-burnfrom
eip-7708-eth-transfer-logs
Open

brett-monad wants to merge 1 commit into
eip-8246-remove-selfdestruct-burnfrom
eip-7708-eth-transfer-logs

Conversation

@brett-monad

@brett-monad brett-monad commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Emit a standardised log for every native ETH movement, so indexers can track native value the way they already track ERC-20 transfers.

Transfer(address,address,uint256) (topic0 0xddf252ad…) on every non-zero value transfer to a different account: the top-level tx transfer, value-carrying CALL, SELFDESTRUCT, and CREATE/CREATE2. This reuses the existing eth_simulate native-transfer machinery, re-gated to fire on the consensus path. Two emitter addresses are in play and 7708 does not replace one with the other: the consensus log from ETH_SYSTEM_ADDRESS fires whenever 7708 is active, and eth_simulate's synthetic from the ERC-7528 native-token address 0xeeee… is emitted alongside it when traceTransfers is set — geth returns both. Note that logIndex counts both, so a consumer dropping the 0xeeee… entries sees only odd indices.

The address was already a function-local constant in process_requests.cpp, the sender the EIP-7002/7251 system calls execute as. 7708 needs the same address, so it is promoted to a shared ETH_SYSTEM_ADDRESS rather than leaving two spellings of one consensus constant that can silently diverge.

There is deliberately no Burn log

An earlier draft of EIP-7708 also specified Burn(address,uint256) for ETH destroyed by SELFDESTRUCT. That draft then took EIP-8246 as a prerequisite and dropped the log, because after 8246 there is no burn left to report. geth and erigon are in the same configuration.

Active on MONAD_NEXT

eip_7708_active() is >= MONAD_ETH_AMSTERDAM in both trait families, and MonadTraits<MONAD_NEXT>::evm_rev() is Amsterdam, so the rule is live on the configuration that ships. The tests run over an explicit two-element type list — MonadRevisionConstant<MONAD_NEXT> and EvmRevisionConstant<MONAD_ETH_AMSTERDAM> — naming both revisions where 7708 is active. They are named rather than derived from LATEST_SUPPORTED_EVM_FORK so the suite does not depend on that constant having been advanced.

Spec tests

No change to the Amsterdam exclusion list. The Amsterdam suite is disabled at
this commit and cannot open here: the fixtures are generated with the whole
fork active, so any Amsterdam EIP not yet in the tree fails its own fixtures —
at this commit the eip8024_dupn_swapn_exchange and
frontier/opcodes/all_opcodes cases, 47 of 710. It opens once every Amsterdam
EIP is in the tree.

Unit tests are unaffected and are the real gate here: test_state 379 and
test_call_trace 234 passing, both builds clean.

Prerequisite

EIP-7708 lists requires: 8246, which must be live before this is. Without it
two SELFDESTRUCT paths still destroy ETH with no recipient — a Transfer log
cannot express that — so 7708 would claim complete native-value logging while
those burns went unlogged.

🤖 Generated with Claude Code

https://claude.ai/code/session_01T2YcdwU1mj3Lqunp9Y7pqa

@brett-monad
brett-monad force-pushed the eip-7708-eth-transfer-logs branch 4 times, most recently from 5b7256d to db72acf Compare August 3, 2026 13:56
@brett-monad
brett-monad force-pushed the eip-7708-eth-transfer-logs branch from db72acf to ac3bce4 Compare August 3, 2026 16:34
@brett-monad
brett-monad force-pushed the eip-7708-eth-transfer-logs branch from ac3bce4 to 23cef36 Compare August 3, 2026 20:14
@Baltoli
Baltoli force-pushed the eip-7708-eth-transfer-logs branch from 23cef36 to 7a9f21c Compare August 4, 2026 11:01
Comment thread category/execution/ethereum/evmc_host.hpp Outdated
Comment thread category/execution/ethereum/evmc_host.hpp Outdated
Comment thread category/execution/ethereum/test/test_call_trace.cpp Outdated
Comment thread category/execution/ethereum/test/test_call_trace.cpp Outdated
Comment thread category/execution/ethereum/test/test_call_trace.cpp Outdated
Comment thread category/vm/evm/traits.hpp Outdated
Comment thread category/vm/evm/traits.hpp Outdated
Comment thread test/ethereum_test/exclude/MONAD_NEXT_amsterdam.cmake Outdated
@Baltoli

Baltoli commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Burn(address,uint256) (topic0 0xcc16f5db..), a monad-specific extension, for ETH destroyed when an EIP-6780 same-tx-created-and-destroyed account is deleted

why is there a Monad-specific extension needed?

@brett-monad
brett-monad force-pushed the eip-7708-eth-transfer-logs branch from 7a9f21c to 3ae693f Compare August 10, 2026 17:07
@brett-monad
brett-monad force-pushed the eip-7708-eth-transfer-logs branch from 3ae693f to 0d6fe6c Compare August 11, 2026 02:52
@brett-monad

Copy link
Copy Markdown
Contributor Author

@claude review this PR

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements EIP-7708-style observability for native ETH movement by emitting synthetic ERC-20-like Transfer logs (and a Monad-specific Burn log for same-tx created+destroyed accounts) from SYSTEM_ADDRESS, wiring emission through the consensus execution path and updating tests/fixtures accordingly.

Changes:

  • Add EIP-7708 feature-gate (eip_7708_active) to the traits interface and trait implementations.
  • Emit consensus Transfer(from,to,value) logs for non-zero native value movement (plus optional eth_simulate synthetic logs), and emit Burn(address,uint256) logs for destroyed residual balances (inline + finalization).
  • Change State::destruct_suicides to return sorted (address, balance) residuals for deterministic burn-log emission ordering; expand/adjust tests and Amsterdam fixture allowlist.

Verdict: NEEDS CHANGES
🤖 Generated with Claude Code

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
test/ethereum_test/exclude/MONAD_NEXT_amsterdam.cmake Opens MONAD_NEXT+Amsterdam fixture allowlist to the full suite with updated rationale.
category/vm/evm/traits.hpp Adds eip_7708_active() to the Traits contract and implementations.
category/rpc/monad_executor_test.cpp Updates eth_simulate expectations to include consensus Transfer logs when 7708 is active.
category/execution/ethereum/test/test_call_trace.cpp Updates call-trace tests and adds pinned Amsterdam fixtures for 7708 Transfer/Burn behavior.
category/execution/ethereum/state3/state.hpp Updates destruct_suicides API to return burned residual balances for 7708.
category/execution/ethereum/state3/state.cpp Captures and sorts burned residual balances during suicide destruction for deterministic Burn emission.
category/execution/ethereum/state2/test/test_state.cpp Adds pinned Amsterdam test asserting burned residuals are returned sorted by address.
category/execution/ethereum/process_requests.cpp Removes local SYSTEM_ADDRESS spelling in favor of shared constant.
category/execution/ethereum/execute_transaction.cpp Emits finalization Burn logs based on destruct_suicides return value when 7708 is active.
category/execution/ethereum/execute_transaction_test.cpp Adds pinned Amsterdam test validating finalization-site Burn log behavior.
category/execution/ethereum/evmc_host.hpp Promotes SYSTEM_ADDRESS and SIMULATE_NATIVE_TOKEN_LOG_ADDRESS, adds Burn-log builder, and emits 7708 Transfer/Burn logs.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread category/vm/evm/traits.hpp

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds EIP-7708 Transfer log emission from SYSTEM_ADDRESS on every value-carrying transfer (top-level tx, value-carrying CALL, SELFDESTRUCT, CREATE/CREATE2), plus a monad-specific Burn log at the two sites where an EIP-6780 same-tx-created-and-destroyed account loses ETH: inline at selfdestruct-to-self, and at finalization for residual balance. The value > 0 && from != to predicate is shared with the existing eth_simulate traceTransfers machinery; both consensus and ERC-7528 synthetic emissions coexist rather than one replacing the other, matching geth. SYSTEM_ADDRESS is promoted from a function-local constant in process_requests.cpp to a shared inline constexpr in evmc_host.hpp. Active on MonadTraits<MONAD_NEXT> (which maps to MONAD_ETH_AMSTERDAM) and on EvmTraits<MONAD_ETH_AMSTERDAM> and later.

I traced the following and found nothing wrong:

  • Refactored emit_native_transfer_event: for every combination of eip_7708_active and log_native_transfers_ the pre-activation truth table matches the old single-branch check; post-activation the consensus log is emitted unconditionally on the shared predicate and the synthetic still fires only under the flag.
  • EvmcHost::selfdestruct inline burn: is_current_incarnation(address) is captured before state_.selfdestruct (correct — the selfdestruct doesn't touch incarnation, but this reads consistently even if it did). The burn is gated on created_this_tx && beneficiary == address && transferred_balance > 0, and the paired emit_native_transfer_event self-skips on from == to, so no double emission. The comment "store_log without a matching on_log" documents the intentional trace/receipt asymmetry.
  • Finalization path: destruct_suicides runs after the beneficiary award, captures (address, balance) inside the CANCUN+ branch (guarded by static_assert(!eip_7708_active() || evm_rev() >= CANCUN) — nice), and sorts by address. execute_final then emits Burn logs from the returned pairs. Ordering matters and is called out in the comment.
  • Revert safety: state_.store_log is versioned, so the inline burn rolls back with logs_.pop_reject if the emitting frame reverts. Finalization burns are at version 0 and don't need revert semantics.
  • [[maybe_unused]] auto const burned = state.destruct_suicides<traits>();burned is only touched inside if constexpr (eip_7708_active()), so the attribute is necessary to silence the unused-variable warning on the discarded-branch path; NRVO keeps the returned empty vector free of overhead pre-activation.
  • Trait wiring: the eip_7708_active() requirement is added to the Traits concept, and both EvmTraits and MonadTraits add matching consteval predicates keyed on MONAD_ETH_AMSTERDAM.

Test coverage is thorough — top-level Transfer log, consensus + synthetic pairing, inline burn, finalization burn (via beneficiary award landing on a selfdestructed account), address ordering of the burned set, and the burn-out-of-call-trace invariant. All are pinned to EvmTraits<MONAD_ETH_AMSTERDAM> since no monad revision reaches AMSTERDAM in the shared matrix yet, with the !eip_7708_active() static_asserts on the two touched pre-activation tests making the intent explicit.

One meta note that doesn't block: the PR title still says "held inert on MONAD_NEXT", but the code and the commit message activate on MONAD_NEXT (via MonadTraits<MONAD_NEXT>::evm_rev() == MONAD_ETH_AMSTERDAM), and the MONAD_NEXT_amsterdam.cmake allowlist opens to "*" — consistent with activation, not inertness. Worth updating the title before merge.

All CI green (clang-format, clang-tidy, trait instantiation, Debug+ASAN, RelWithDebInfo, RISC-V, VM tests, CodeQL).

Verdict: CORRECT

🤖 Generated with Claude Code

@brett-monad
brett-monad force-pushed the eip-7708-eth-transfer-logs branch 3 times, most recently from 8f8fdc6 to c708971 Compare August 26, 2026 08:56
@brett-monad
brett-monad force-pushed the eip-7708-eth-transfer-logs branch 2 times, most recently from 699bca9 to e8a609b Compare August 26, 2026 15:35
@brett-monad
brett-monad changed the base branch from eip-8246-remove-selfdestruct-burn to ryan-slotnum August 26, 2026 15:39
@brett-monad
brett-monad force-pushed the eip-7708-eth-transfer-logs branch from e8a609b to 34bafb7 Compare August 27, 2026 12:42
@brett-monad
brett-monad changed the base branch from ryan-slotnum to main August 27, 2026 15:19
@brett-monad
brett-monad force-pushed the eip-7708-eth-transfer-logs branch from 34bafb7 to 68a419c Compare August 27, 2026 15:19
Comment thread category/execution/ethereum/evmc_host.hpp Outdated
Comment thread category/execution/ethereum/evmc_host.hpp Outdated
Comment thread category/rpc/monad_executor_test.cpp Outdated
dhil
dhil previously approved these changes Sep 11, 2026
Comment thread category/execution/ethereum/test/test_call_trace.cpp Outdated
Comment thread category/execution/ethereum/evmc_host.hpp Outdated
Comment thread category/execution/ethereum/evmc_host.hpp Outdated
Comment on lines +268 to +272
// Not every ETH movement emits. The EIP excludes withdrawals (not attached
// to a transaction, so no natural emission point), priority fees and the
// base-fee burn (derivable from the header). Beyond the EIP, monad's
// staking contract moves, mints and burns ETH by direct balance mutation
// rather than a value transfer, so none of that emits either.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we explicitly settle the staking contract choice when building eth_simulateV1 @dhil? It seems analogous to withdrawals etc. from Ethereum, but I wanted to check that explicitly.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No we did not. What semantics do we want here?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well - the argument here for Ethereum is that there is no transaction associated with withdrawals etc., so there's nowhere to hang logs from (and the transfers are derivable from the headers there anyway). I think this is different in Monad, because staking operations happen via system calls? If there is a transaction "in hand" when we do staking-related balance transfers, then it would make sense to emit the logs for them, even if it's a system transaction.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. I am inclined to believe that right now balance transfers done via system calls are not observable in the eth_simulateV1 transfer log. I will test it out, and draft a patch to fix the semantics if needed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that property is sort of inert at present though, because we don't allow simulation of system calls? It becomes relevant here because they're consensus artifacts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Protocol-wise, I am inclined to believe that we should also emit a transfer log. Then I agree with your other comment about hoisting out the emission logic, because it should be used to emit transfer events inside staking too.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, agreed - thanks for the sense check

Comment thread category/execution/ethereum/evmc_host.hpp Outdated
Comment thread category/execution/ethereum/evmc_host.hpp
Comment thread category/execution/ethereum/evmc_host.hpp Outdated
Comment thread category/execution/ethereum/evmc_host.hpp Outdated
Comment thread category/rpc/monad_executor_test.cpp Outdated
Comment thread category/rpc/monad_executor_test.cpp Outdated
Emit a standardised log for every native ETH movement, so indexers can track
native value the way they already track ERC-20 transfers.

Transfer(address,address,uint256) is emitted on ordinary value transfers, at
the four sites Monad already wired: top-level tx transfer, value-carrying CALL,
SELFDESTRUCT, and CREATE/CREATE2.

EIP-7708's earlier Burn(address,uint256) log is deliberately not implemented.
It reported ETH destroyed by a same-tx-created account self-destructing to
itself, or left on such an account at finalization. EIP-8246, a prerequisite
of the current EIP-7708 text on the same fork, removes both burns at the
source -- the balance stays with the account -- so there is nothing left to
report, and EIP-7708 drops the case for exactly that reason. destruct_suicides
therefore keeps its void signature and no Burn helper or emit site exists.

Consensus logs use ETH_SYSTEM_ADDRESS. eth_simulate's traceTransfers keeps the
ERC-7528 native-token address and is emitted alongside the consensus log rather
than replacing it, and ahead of it: geth's simulate tracer records the
synthetic on frame entry, before the consensus log, for calls and creates. Its
SELFDESTRUCT differs and no spec fixes either order, so the comments cite it as
observed behaviour only. Only the eth_simulateV1 path sets the flag; with it
clear a single log exists and consensus never sees the order. Where it is set,
the order fixes each log's position and logIndex in the simulate output.

Also in this commit:

- Make the simulate_v1 trace expectations revision-aware. Four tests asserted
  log counts and contents that predate 7708. Two carried
  static_assert(!eip_7708_active()) and failed to compile; two had no guard and
  failed at runtime. None had been noticed because MONAD_NEXT was filtered out
  of the typed-test matrices, so they never ran at the only revision where 7708
  is active. They now run there, with the doubling expressed by helpers derived
  from the emitter rather than from observed output.

- Assert the receipt side of the transfer logs (State::store_log) from the
  shared TraitsTest matrix, at every revision, instead of a fixture pinned to
  Amsterdam: for a top-level transfer the receipt's logs are the call frame's
  logs in the same order, so one helper checks that in execute_success and
  simulate_v1_trace and the pinned fixture is gone.

Co-Authored-By: Bruce Collie <brucecollie82@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants