1. Problem description
DTVM's --load-state parser (src/utils/evm.cpp, loadState()) validates block_timestamp and block_number with IsUint64() but assigns the result to int64_t fields:
// src/utils/evm.cpp:421-424 (block_number)
if (TxContext.HasMember("block_number") &&
TxContext["block_number"].IsUint64()) {
Host.tx_context.block_number = TxContext["block_number"].GetUint64();
}
// src/utils/evm.cpp:426-430 (block_timestamp)
if (TxContext.HasMember("block_timestamp") &&
TxContext["block_timestamp"].IsUint64()) {
Host.tx_context.block_timestamp =
TxContext["block_timestamp"].GetUint64();
}
IsUint64() accepts values up to 2^64-1, but evmc_tx_context::block_number and evmc_tx_context::block_timestamp are both int64_t (max 2^63-1). For inputs in [2^63, 2^64-1], the IsUint64() check passes, GetUint64() succeeds, and the implicit uint64_t -> int64_t assignment silently wraps the value to negative.
The EVMC ABI (evmc.h) defines both fields as int64_t:
struct evmc_tx_context {
...
int64_t block_number; /**< The block number. */ // evmc.h:219
int64_t block_timestamp; /**< The block timestamp. */ // evmc.h:220
...
};
The output serialization (src/utils/evm.cpp:278-280) writes the raw int64_t value directly as a bare decimal integer, producing invalid (negative) JSON values:
File << " \"block_number\": " << Host.tx_context.block_number << ",\n";
File << " \"block_timestamp\": " << Host.tx_context.block_timestamp << ",\n";
This is the same bug pattern as block_gas_limit (documented separately in POCS/dtvm_gas_limit_parsing.md), now confirmed to also affect block_timestamp and block_number.
Impact
block_timestamp
Input block_timestamp |
IsUint64 |
Stored int64_t value |
TIMESTAMP opcode return |
result.json output |
| 1000 |
✓ |
1000 |
0x0000…03E8 ✓ |
1000 ✓ |
| 2^63-1 (9223372036854775807) |
✓ |
9223372036854775807 |
0x7fff…ffff ✓ |
9223372036854775807 ✓ |
| 2^63 (9223372036854775808) |
✓ |
-9223372036854775808 |
0x8000…0000 (accidentally correct) |
-9223372036854775808 ✗ |
| 2^64-1 (18446744073709551615) |
✓ |
-1 |
0xffff…ffff (accidentally correct) |
-1 ✗ |
| 2^64 (18446744073709551616) |
✗ |
0 (silent default) |
0x0000…0000 ✗ |
0 ✗ |
The TIMESTAMP opcode (0x42) accidentally returns the correct value because intx::uint256(int64_t) reinterprets via implicit uint64_t conversion, undoing the truncation. However, the stored block_timestamp is negative, which is semantically incorrect and would break any int64_t-level comparison or downstream consumer that reads result.json.
block_number
Input block_number |
IsUint64 |
Stored int64_t value |
NUMBER opcode return |
result.json output |
| 1 |
✓ |
1 |
0x0000…0001 ✓ |
1 ✓ |
| 2^63 (9223372036854775808) |
✓ |
-9223372036854775808 |
0x8000…0000 (accidentally correct) |
-9223372036854775808 ✗ |
Same pattern: NUMBER opcode (0x43) accidentally returns correct bits, but result.json reports a negative block number.
Minimal reproduce step
Reproduce 1: block_timestamp = 2^63 (negative wrap)
Contract bytecode (contract_ts.evm.hex) — calls TIMESTAMP and returns the result:
Disassembly: TIMESTAMP PUSH0 MSTORE PUSH1 0x20 PUSH0 RETURN
state.json (sets block_timestamp to 2^63, which passes IsUint64()):
{
"accounts": {
"a94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
"balance": "000000000000000000000000000000000000000000000000000000ffffffffff",
"nonce": 0, "code": "0x", "storage": {}
},
"00000000000000000000000000000000000000f1": {
"balance": "0000000000000000000000000000000000000000000000000000000000000000",
"nonce": 0, "code": "0x425f5260205ff3", "storage": {}
}
},
"tx_context": {
"gas_price": "0000000000000000000000000000000000000000000000000000000000000010",
"block_number": 1,
"block_timestamp": 9223372036854775808,
"block_coinbase": "b94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"block_prev_randao": "0000000000000000000000000000000000000000000000000000000000200000",
"block_gas_limit": 10944489199640098,
"block_base_fee": "0000000000000000000000000000000000000000000000000000000000000010"
}
}
Run:
dtvm --enable-evm-gas --format evm -m multipass --evm-revision cancun \
--load-state state.json \
--sender a94f5374fce5edbc8e2a8697c15331677e6ebf0b \
--gas-limit 8000000 \
--contract-address 00000000000000000000000000000000000000f1 \
--save-state result.json \
contract_ts.evm.hex
Verified output:
output: 0x0000000000000000000000000000000000000000000000008000000000000000
result.json contains:
"block_timestamp": -9223372036854775808,
The TIMESTAMP return value's bit pattern is accidentally correct (0x8000…0000 = 2^63), but block_timestamp in result.json is negative.
2. What did you expect to see
For block_timestamp and block_number values that pass IsUint64() but exceed INT64_MAX, DTVM should either:
- Reject the input with an error (since
evmc_tx_context::block_timestamp and block_number are int64_t, values > 2^63-1 are out of range), or
- Clamp to
INT64_MAX.
For values that exceed uint64_t (>= 2^64), DTVM should report a parse error rather than silently defaulting to 0.
At minimum, the stored values should never be negative, and the TIMESTAMP/NUMBER opcodes should never silently return 0 for a non-zero configured input.
3. What did you see instead
DTVM accepted the input without any warning:
- For values in [2^63, 2^64-1]:
result.json reported negative block_timestamp / block_number.
- For values >= 2^64:
result.json reported 0 and the TIMESTAMP opcode returned 0.
4. Error logs / Stack trace
No error output at any log level. DTVM silently produces the incorrect values.
5. What is the version
commit 5d64911f76bfe0b47fdc958a07bdcafd9e9d5343
Both interpreter and multipass builds are affected (verified with -m interpreter and -m multipass).
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 at
/usr/lib/llvm-20, CMake 3.28.1
- Build:
bash build_evm_interpreter.sh / bash build_evm_multipass.sh
1. Problem description
DTVM's
--load-stateparser (src/utils/evm.cpp,loadState()) validatesblock_timestampandblock_numberwithIsUint64()but assigns the result toint64_tfields:IsUint64()accepts values up to 2^64-1, butevmc_tx_context::block_numberandevmc_tx_context::block_timestampare bothint64_t(max 2^63-1). For inputs in [2^63, 2^64-1], theIsUint64()check passes,GetUint64()succeeds, and the implicituint64_t -> int64_tassignment silently wraps the value to negative.The EVMC ABI (
evmc.h) defines both fields asint64_t:The output serialization (
src/utils/evm.cpp:278-280) writes the rawint64_tvalue directly as a bare decimal integer, producing invalid (negative) JSON values:This is the same bug pattern as
block_gas_limit(documented separately inPOCS/dtvm_gas_limit_parsing.md), now confirmed to also affectblock_timestampandblock_number.Impact
block_timestampblock_timestampIsUint64int64_tvalueTIMESTAMPopcode returnresult.jsonoutput0x0000…03E8✓0x7fff…ffff✓0x8000…0000(accidentally correct)0xffff…ffff(accidentally correct)0x0000…0000✗The
TIMESTAMPopcode (0x42) accidentally returns the correct value becauseintx::uint256(int64_t)reinterprets via implicituint64_tconversion, undoing the truncation. However, the storedblock_timestampis negative, which is semantically incorrect and would break anyint64_t-level comparison or downstream consumer that readsresult.json.block_numberblock_numberIsUint64int64_tvalueNUMBERopcode returnresult.jsonoutput0x0000…0001✓0x8000…0000(accidentally correct)Same pattern:
NUMBERopcode (0x43) accidentally returns correct bits, butresult.jsonreports a negative block number.Minimal reproduce step
Reproduce 1:
block_timestamp= 2^63 (negative wrap)Contract bytecode (
contract_ts.evm.hex) — callsTIMESTAMPand returns the result:Disassembly:
TIMESTAMP PUSH0 MSTORE PUSH1 0x20 PUSH0 RETURNstate.json(setsblock_timestampto 2^63, which passesIsUint64()):{ "accounts": { "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { "balance": "000000000000000000000000000000000000000000000000000000ffffffffff", "nonce": 0, "code": "0x", "storage": {} }, "00000000000000000000000000000000000000f1": { "balance": "0000000000000000000000000000000000000000000000000000000000000000", "nonce": 0, "code": "0x425f5260205ff3", "storage": {} } }, "tx_context": { "gas_price": "0000000000000000000000000000000000000000000000000000000000000010", "block_number": 1, "block_timestamp": 9223372036854775808, "block_coinbase": "b94f5374fce5edbc8e2a8697c15331677e6ebf0b", "block_prev_randao": "0000000000000000000000000000000000000000000000000000000000200000", "block_gas_limit": 10944489199640098, "block_base_fee": "0000000000000000000000000000000000000000000000000000000000000010" } }Run:
Verified output:
result.jsoncontains:The
TIMESTAMPreturn value's bit pattern is accidentally correct (0x8000…0000= 2^63), butblock_timestampinresult.jsonis negative.2. What did you expect to see
For
block_timestampandblock_numbervalues that passIsUint64()but exceedINT64_MAX, DTVM should either:evmc_tx_context::block_timestampandblock_numberareint64_t, values > 2^63-1 are out of range), orINT64_MAX.For values that exceed
uint64_t(>= 2^64), DTVM should report a parse error rather than silently defaulting to 0.At minimum, the stored values should never be negative, and the
TIMESTAMP/NUMBERopcodes should never silently return 0 for a non-zero configured input.3. What did you see instead
DTVM accepted the input without any warning:
result.jsonreported negativeblock_timestamp/block_number.result.jsonreported0and theTIMESTAMPopcode returned0.4. Error logs / Stack trace
No error output at any log level. DTVM silently produces the incorrect values.
5. What is the version
Both interpreter and multipass builds are affected (verified with
-m interpreterand-m multipass).6. Environment
/usr/lib/llvm-20, CMake 3.28.1bash build_evm_interpreter.sh/bash build_evm_multipass.sh