Summary
evmone-statetest diverges from geth on a valid Cancun state test when env.currentNumber is above INT64_MAX.
The test executes BLOCKHASH(currentNumber - 1) and then stores ISZERO(result).
- geth returns non-zero
BLOCKHASH (in-range lookup), so ISZERO is 0.
- evmone-statetest returns
0x0 for the same in-range lookup, so ISZERO is 1.
This produces different post-state roots.
Reproducer
files/case12-statetest-blockhash-highnum/blockhash_highnum_state_test.json
Commands
# geth: passes
~/go/bin/evm statetest --statetest.fork Cancun files/case12-statetest-blockhash-highnum/blockhash_highnum_state_test.json
# evmone: fails
build/debug/bin/evmone-statetest files/case12-statetest-blockhash-highnum/blockhash_highnum_state_test.json
Observed:
- geth state root:
0x27ebe6aaf81779a3f3516512f8bdd8d543be038c26b9bba4bba5d85cce73f02f (PASS)
- evmone state root:
0x48e2b3fe41a421a38716c88d2e14ad390b55ff6301db7d3b24079e55dc01e768 (FAIL)
Trace difference
For the same bytecode (0x6780000000000000ff401560005500):
- geth trace shows
BLOCKHASH pushes non-zero.
- evmone trace shows
BLOCKHASH pushes 0x0.
Likely root cause
The statetest path stores block numbers as signed 64-bit and BLOCKHASH range checks with signed bounds:
test/utils/statetest_loader.cpp: currentNumber parsed via from_json<int64_t>.
test/state/block.hpp: BlockInfo::number is int64_t.
lib/evmone/instructions.hpp blockhash():
upper_bound = state.get_tx_context().block_number (signed)
lower_bound = std::max(upper_bound - 256, 0)
n = static_cast<int64_t>(number)
- signed comparison path rejects what should be valid
currentNumber-1 lookups above INT64_MAX.
The EVM BLOCKHASH semantics are based on unsigned block numbers modulo 2^256 on stack values, with recent-256 window check relative to the current block number. Using signed block numbers in this path causes incorrect behavior for values above 2^63-1.
Summary
evmone-statetestdiverges from geth on a valid Cancun state test whenenv.currentNumberis aboveINT64_MAX.The test executes
BLOCKHASH(currentNumber - 1)and then storesISZERO(result).BLOCKHASH(in-range lookup), soISZEROis0.0x0for the same in-range lookup, soISZEROis1.This produces different post-state roots.
Reproducer
files/case12-statetest-blockhash-highnum/blockhash_highnum_state_test.jsonCommands
Observed:
0x27ebe6aaf81779a3f3516512f8bdd8d543be038c26b9bba4bba5d85cce73f02f(PASS)0x48e2b3fe41a421a38716c88d2e14ad390b55ff6301db7d3b24079e55dc01e768(FAIL)Trace difference
For the same bytecode (
0x6780000000000000ff401560005500):BLOCKHASHpushes non-zero.BLOCKHASHpushes0x0.Likely root cause
The statetest path stores block numbers as signed 64-bit and
BLOCKHASHrange checks with signed bounds:test/utils/statetest_loader.cpp:currentNumberparsed viafrom_json<int64_t>.test/state/block.hpp:BlockInfo::numberisint64_t.lib/evmone/instructions.hppblockhash():upper_bound = state.get_tx_context().block_number(signed)lower_bound = std::max(upper_bound - 256, 0)n = static_cast<int64_t>(number)currentNumber-1lookups aboveINT64_MAX.The EVM
BLOCKHASHsemantics are based on unsigned block numbers modulo 2^256 on stack values, with recent-256 window check relative to the current block number. Using signed block numbers in this path causes incorrect behavior for values above2^63-1.