Skip to content

DTVM EVM Bug: --load-state aborts (SIGABRT) on a uint256 field longer than 32 bytes — parse exception is never caught #601

Description

@erxiaozhou

Loading a state JSON whose uint256 hex field (after padding to even length) encodes more
than 32 bytes aborts the process instead of reporting an error.

1. Minimal reproduce step

Contract bytecode (contract.evm.hex): 5f00PUSH0; STOP (irrelevant; the crash
happens during --load-state, before execution).

bad.state.json — identical to ok.state.json except the contract account's balance
is 66 hex chars (33 bytes) instead of 64:

{
  "accounts": {
    "00000000000000000000000000000000000000f1": {
      "balance": "00000000000000000000000000000000000000000000000000000000ffff",
      "code": "0x5f00",
      "nonce": 0,
      "storage": {}
    },
    "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
      "balance": "000000000000000000000000000000000000000000000000000000ffffffff",
      "code": "0x",
      "nonce": 0,
      "storage": {}
    }
  },
  "tx_context": { "...": "see bad.state.json — all 64-char hex" }
}
<dtvm_bin> --format evm -m interpreter --evm-revision cancun --log-level off \
  --load-state bad.state.json \
  --sender a94f5374fce5edbc8e2a8697c15331677e6ebf0b \
  --gas-limit 100000 \
  --contract-address 00000000000000000000000000000000000000f1 \
  --save-state bad.result.json \
  contract.evm.hex
; echo "rc=$?"

Output: rc=134 (SIGABRT, "Aborted (core dumped)"). Control run with ok.state.json
(balance 64 hex chars, byte-for-byte identical elsewhere): rc=0, ok.result.json
written. The same abort is reached through any other parseUint256-parsed field:
account balance, tx_context.gas_price, block_prev_randao, block_base_fee,
chain_id, blob_base_fee.

2. What did you expect to see

The CLI rejects the malformed state with an error message and a non-zero, non-crash
exit code. parseUint256 already carries the intended message
("Invalid Uint256 hex string too long"); it just never reaches the user.

3. What did you see instead

terminate called after throwing an instance of 'zen::common::Error'
  what():  invalid raw data
Aborted (core dumped)          # rc=134

4. Error logs / Stack trace

No per-opcode EVM execution trace is involved; the crash is at state load. Chain
(this checkout, commit 338d123a):

  1. src/utils/evm.cpp:109-122parseUint256 throws Error(InvalidRawData, ...)
    when the hex encodes > 32 bytes.
  2. src/utils/evm.cpp:313 loadState(...) — parses account balance (line ~344)
    and the tx_context uint256 fields without any try/catch; the function's error
    protocol for structural problems is a bool return, so the exception escapes it.
  3. src/cli/dtvm.cpp:331-336 — the call site only checks the bool return and is
    not inside a try block.
  4. Error derives from std::exception (src/common/errors.h:56) → the exception
    escapes mainstd::terminateabort().

For contrast, the same file already wraps its address/bytes32 access-list parsing in
catch (...) (src/utils/evm.cpp:485,505).

Suggested fix: catch the parse exceptions inside loadState and return false
(matching its existing error protocol), or wrap the call site so the CLI prints the
intended message and exits cleanly.

5. What is the version

DTVM git commit 338d123a5d9d4a464d8d0151158447d500a9997a (2026-08-11), interpreter
mode binary.

6. Environment

  • OS: Ubuntu 20.04.6 LTS, Linux 6.8.0-111-generic x86_64
  • GCC 11.4.0, Clang 10.0.0-4ubuntu1, LLVM 20.1.5 (/usr/lib/llvm-20), CMake 3.28.1

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