Since mid-September the same gap has come up in at least four ERC threads, raised by different people. It shows up in two directions, so I wanted to put it in one place for anyone writing a draft right now.
The two directions
1. A fact lives only in an event, and a contract needs it.
In the ERC-8467 (Agent Credit Backers) thread, Revinand noticed that what a backer paid on a defaulted loan was only in the BackerCharged event. The draft told backers to rebuild their records from the getters after a failed notification, but no getter returned that amount, so an insurer contract tracking payouts per loan had nothing to read. priors-agents added lossInfo(loanId) the next day.
In the ERC-8418 thread, SergeevDmitry asked the same thing about reused token IDs: an indexer can watch the mint event, but a contract filling an old signed offer can’t. And ERC-8423 is built around exactly this. Who burned an ERC-721 token is in the from of the burn Transfer, no contract can see it, so the draft adds burnedBy(tokenId).
2. The view is right, but the event history says something else.
In the ERC-8451 (Permanent Locks) thread, locked() was correct after a collection became permanent, but a token with an earlier Locked(id, false) still looked unlocked to an indexer replaying ERC-6982 events. I raised that one, and SergeevDmitry then found the same gap for an ERC-1155 ID that is fully burned and minted again. ryanio fixed both: rule 6 now requires Locked(id, true) for every ID whose last Locked event was false, whether that ID exists or not.
All of these were caught in review and fixed quickly, which is how it should work. But it’s the same question every time, and it’s cheaper to answer it before posting.
Why it keeps happening
The Solidity docs say it plainly: log and event data is not accessible from within contracts, not even from the contract that created them. A contract can only check a log if someone hands it a Merkle proof against a block header. So in practice, anything another contract has to act on needs a view.
The other direction is less obvious. An indexer that rebuilds state from events is only right if every path that changes that state emits something, and the spec says which event wins. Standards and libraries already leave gaps there, on purpose:
- ERC-721 lets a contract create any number of NFTs during contract creation without emitting
Transfer. - ERC-20 only says a token that creates new tokens SHOULD emit
Transferfrom0x0. - OpenZeppelin Contracts 5.0 removed the
Approvalevent fromtransferFrom. Their changelog says it directly: allowances are no longer reconstructible from events. - stETH balances change on every rebase without a
Transfer, as Lido’s integration guide notes.
None of these is a bug. But each one means “replay the events” and “call the view” can give different answers, and a consumer has to know which one to trust.
What I’d put in a draft
- A short split in the spec: facts a contract may need, each with the view that returns it, and facts that are event-only on purpose.
- For anything indexers will rebuild from events, the list of paths that change it (mint, burn, re-mint, forced moves, defaults, migrations) and the event each one emits. ERC-8423 has a good rule for this: the view MUST equal what the burn
Transferrecorded, so a contract and an indexer never disagree. - Reorgs. ERC-8451 has a nice consumer rule: when a reorg removes a lock event, read the view instead of reversing the event, because a removed event doesn’t tell you what the status was before it.
- Two tests in the reference implementation. A replay test: record the logs with Foundry’s
vm.recordLogs(), rebuild the state from them after each lifecycle step, and compare it with the views. ryanio added the burn and re-mint lifecycle to ERC-8451’s test cases for this reason. And a small consumer contract (the insurer, the marketplace, the vault you expect to integrate) that only uses views. If you can’t write it, a view is missing.
I’d really like to hear from people who run indexers for these drafts: which events have bitten you most? And should Security Considerations ask authors to state this split, or is it better left as review advice?