Skip to content

Public /evm RPC returns latest state for unsupported block numbers in eth_call and eth_getCode #158

Description

@shyhaeky95

Summary

The public /evm endpoint appears to ignore non-latest block numbers passed to eth_call and eth_getCode, evaluating the requests against current state instead. Unsupported historical block numbers — and block numbers above the current head — return well-formed results rather than a JSON-RPC error.

The documentation already states that for these state methods "only the latest block is supported". This report is therefore not a request for archival state support. The issue is that an unsupported block parameter is silently accepted and answered from a different state than the caller asked for.

This report covers only https://rpc.hyperliquid.xyz/evm. I have not tested a locally running hl-node, so I cannot tell whether the behaviour originates in the node implementation or in the hosted RPC layer.

Environment

  • Endpoint: https://rpc.hyperliquid.xyz/evm (no key)
  • eth_chainId: 0x3e7 (999)
  • web3_clientVersion: hyperliquid evm Mainnet
  • Measured 2026-07-28T14:46:02Z, head block 41,677,284 (0x27bf1e4)
  • Contract used: WHYPE 0x5555555555555555555555555555555555555555 (genesis predeploy), totalSupply() = 0x18160ddd

Controls first

RPC=https://rpc.hyperliquid.xyz/evm
W=0x5555555555555555555555555555555555555555

# positive — the endpoint serves real state
curl -s $RPC -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_getCode","params":["'$W'","latest"]}'
# → 2041 bytes of code

# negative — "0x" is an answer this endpoint can actually produce
curl -s $RPC -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_getCode","params":["0x00000000000000000000000000000000deadbe01","latest"]}'
# → "0x"

1. Same fixed block number, two calls, 30 seconds apart

This is the decisive test. Block 0x26cafa4 (head − 1,000,000), identical request both times:

curl -s $RPC -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"'$W'","data":"0x18160ddd"},"0x26cafa4"]}'
read raw result
t = 0s 0x00000000000000000000000000000000000000000004cda11f6182186624fe31
t = 30s 0x00000000000000000000000000000000000000000004cda16e395bf78a8a39b1

Δ ≈ +5.68 WHYPE for the same block number. The magnitude varies with wrap/unwrap traffic — a run 20 minutes earlier gave +39.46 — and the magnitude is not the point. A block number that yields two different answers is not being applied.

2. A block number above the current head

curl -s $RPC -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_getCode","params":["'$W'","0x3148864"]}'

0x3148864 is head + 10,000,000. Expected a JSON-RPC error; actual: no error, 2041 bytes, identical to latest.

3. eth_call at head − 1,000,000 matches latest byte for byte

Both latest and 0x26cafa4 return 0x00000000000000000000000000000000000000000004cda12f10131bba55fe31.

Suggestive rather than conclusive on its own — two points in time can coincide — but consistent with (1) and (2).

4. Blocks are retained; state is not

curl -s $RPC -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["0x26cafa4",false]}'

Returns the block, echoing "number":"0x26cafa4". So a caller can fetch a real historical block and a "state at that block" in the same script, and only the second one is wrong.

Why this matters

An explicit unsupported-block error is easy for callers to detect and handle. A valid-looking response from the wrong state is not: standard RPC libraries accept it, and downstream datasets can silently label current state as historical state.

I ran into this while pinning token supply measurements to fixed blocks. The values looked valid and stayed valid across two independent read paths, until repeated calls at the same block number returned different results.

Suggested fix

Reject unsupported non-latest block parameters with a JSON-RPC error, including block numbers above the current head — for example Resource not found, or whatever error the endpoint already uses for an invalid block height. The endpoint should not silently substitute latest state.

As a temporary mitigation the documentation could state explicitly that non-latest block parameters are currently ignored rather than rejected, though rejecting them would be substantially safer.

What I did not test

eth_getBalance, eth_getStorageAt, eth_getTransactionCount; a locally running hl-node; testnet. This is one hosted endpoint, two state methods, one point in time.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions