fix(tests): execute the empty-account EXTCODEHASH hash assertion#4
Merged
marioevz merged 1 commit intoJun 23, 2026
Conversation
`test_extcodehash_empty_account` asserts both the surcharge-free access gas (slot 0) and the empty-account hash (slot 1). The slot 1 assertion was dead code: `CodeGasMeasure` appends a `STOP` by default, which halts the frame before the trailing `Op.SSTORE(hash_slot, EXTCODEHASH(...))`, so slot 1 was never written and kept the EVM default of `0`. Because the expected value is also `0`, the post-state check passed without ever observing the opcode's return value. Pass `stop=False` so execution flows from the gas `SSTORE` into the hash `SSTORE`, then halts at the end of the code. The hash store deliberately stays after the measured access, where the account is already warm, so it cannot warm `0xDEAD` and perturb the cold-case gas measurement. Also add a defensive sentinel: slot 1 is poisoned with a non-zero value before the measured region, which the real store must overwrite back to `0`. The empty-account hash `0` is indistinguishable from an unwritten slot, so this is what keeps the assertion regression-proof: if the hash store is ever stranded again, slot 1 keeps the sentinel and the test fails instead of passing vacuously. The poison precedes the measured region, so it does not perturb the gas measurement. Expected post-state is unchanged: slot 0 stays `0xbb8` (cold) / `0x64` (warm) and slot 1 stays `0`. Verified with `--traces` (the sentinel store runs first, the gas store is unchanged, the real hash store overwrites slot 1 back to `0`); reverting `stop=False` with the sentinel in place now fails both cases.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🗒️ Description
test_extcodehash_empty_accountasserts two things aboutEXTCODEHASHon an empty account: the access gas cost (no EIP-8038 code-read surcharge), and that the returned hash is0. The gas assertion (storage slot0) was always exercised, but the hash assertion (storage slot1) was dead code and never ran.The test concatenates the hash store after the gas measurement:
CodeGasMeasureappendsOp.STOPby default (stop=True), so the measure block halts before the trailingSSTORE. Slot1was therefore never written and kept the EVM default of0. Because the expected value registered for slot1is also0, the post-state check passed trivially: the test never observed the actualEXTCODEHASHreturn value. This is well hidden, since0is simultaneously the expected empty-account hash and the never-written storage default.Fix
This PR passes
stop=FalsetoCodeGasMeasureso execution flows from the gasSSTORE(slot0) into the trailing hashSSTORE(slot1), then halts at the end of the code. Slot1now genuinely holds the opcode's return value, so the hash assertion has teeth: a client returning a non-zero hash for an empty account would now fail the test.The hash store must run after the measured access, not before: in the cold case a pre-measure
EXTCODEHASH(empty_addr)would warm0xDEADand turn the measured access warm, breaking the gas assertion.stop=Falsekeeps the store after the measured region, where the account is already warm and only the return value matters.This PR also adds a defensive sentinel so the assertion cannot silently regress. Before the measured region, slot
1is poisoned with a non-zero value that the real store must overwrite back to0. Because the empty-account hash0is indistinguishable from an unwritten slot, this is what makes the guard durable: if the trailing hash store is ever stranded again (for example by a future revert ofstop=False), slot1keeps the sentinel and the test fails instead of passing vacuously. The poison is written before the firstGASreading, so it does not affect the measured gas.Expected post-state values are unchanged. Slot
0(gas) stays0xbb8(cold) /0x64(warm) and slot1stays0; the only behavioral change is that slot1is now actually written. Execution traces confirm this: before the fix the trace shows a singleSSTORE(slot0) followed immediately bySTOP. After the fix the trace shows the sentinel store to slot1, then the unchanged gas store to slot0, then the real hash store overwriting slot1back to0. Revertingstop=Falsewith the sentinel in place makes both the cold and warm cases fail, confirming the guard.🔗 Related Issues or PRs
Fix-up to ethereum#3033 (EIP-8038 state-access gas-cost test suite), which introduced this test.