Skip to content

TVM.md — Interpreter Divergences, Precompiles, Energy & Address Derivation

Living reference for internal/tvm and internal/state, and the designated reference for the S6 differential arbiter. Tier-1 authority remains docs/ARCHITECTURE.md §5.3–§5.6 (with the erratum recorded there for §5.5); this file documents what is implemented and where every fact was grounded.

Grounding pin. All opcode numbers, energy costs, precompile transforms and call semantics were transcribed from java-tron source at commit f8ff7c76f45ab41d9bb76922329657506819701b (master, 2026-07-15) — classes Op, OperationRegistry, OperationActions, EnergyCost, Program, VM, Memory, PrecompiledContracts, TransactionUtil, WalletUtil and the Stake 2.0 nativecontract processors. Nothing was written from memory (CLAUDE.md §2). Final on-chain confirmation is S6's differential suite (±0 metering, CREATE2 dual-path, precompile conformance).


1. EVM/TVM divergences implemented

Surface TVM behavior (as shipped by java-tron)
GASPRICE (0x3a) 0 for standard (version-0) contracts; the chain energy price only for EVM-compatible version-1 contracts. The sprint prose "GASPRICE → energyPrice" holds only in that mode — the version-0 zero is asserted by the conformance suite with the EVM expectation required to fail.
BASEFEE (0x48) The injected chain energy price (getEnergyFee), read via chainparams — never a constant.
DIFFICULTY (0x44), GASLIMIT (0x45) Constant 0.
Call depth 64 (MAX_DEPTH), not the EVM's 1024. Conformance pins a 65-frame recursion counter.
Call energy forwarding min(requested, available) — the 63/64 rule applies ONLY to version-1 contracts (Program.getCallEnergy). CREATE forwards the full remainder.
SSTORE pricing Keyed on ROW EXISTENCE, not value: an absent row set non-zero costs SET 20000; every other transition — including rewriting a row that holds zero — costs 5000. A row written to zero EXISTS, so zero→non-zero within a transaction is RESET (5000) where original-EVM charges SET. Asserted with a failing EVM twin.
SELFDESTRUCT (0xff) TIP-6780: full destruction only when the contract was created in the same transaction; otherwise holdings move and the account/code survive. Ineligible contracts (outstanding delegations or pending unfreezes) revert the frame. Base cost 5000 + 25000 for a dead inheritor.
Address words ADDRESS/CALLER/ORIGIN push the 20-byte form; COINBASE and the CREATE/CREATE2 result words carry the 21-byte form with 0x41 visible at byte 11. ecrecover output likewise (see §2).
Memory Word-aligned visible growth, reads extend like writes, quadratic cost 3·w + w²/512, hard 3 MB cap (memoryOverflow exception, not OOG). Under the active higher-limit adjustment MLOAD/MSTORE/MSTORE8 cost +1.
Internal-transaction nonce One transaction-wide counter shared by CALLs, CREATEs, SELFDESTRUCT and the staking ops. CREATE addresses read it pre-increment — a preceding CALL therefore shifts subsequent CREATE addresses. Pinned by the conformance suite.
TRON opcodes 0xd0–0xdf CALLTOKEN family, ISCONTRACT, Stake 1.0 trio (V2-superseded: FREEZE pushes 0), VOTEWITNESS/WITHDRAWREWARD, Stake 2.0 family — all against StateDB, mutating simulated freeze/delegation/TRC-10 state. Fixed costs from EnergyCost (V2 family 10000, VOTEWITNESS 30000+mem, WITHDRAWREWARD 20000).

Simulation notes (memState): network-wide usage weighting (delegate/unfreeze "available minus usage" checks) collapses to zero usage; nothing expires inside one transaction (WITHDRAWEXPIREUNFREEZE answers 0); Stake 1.0 legacy records don't exist in fresh state. The S6 fork backend supplies real account data for all of these.

2. Precompiles — exact transforms

Dispatch is java-tron's: the full 32-byte call-target word is compared before any code lookup.

Addr v1 status Transform / note
0x01 ecrecover implemented 3000 energy. Output word left-pads the 21-byte address — 0x41 visible at byte 11 (EVM form asserted to fail). Failure returns empty, not a zero word.
0x02 sha256 implemented 60 + 12/word.
0x03 "ripemd160" implemented SHA256(SHA256(input)[0:20]) — see the erratum below. 600 + 120/word.
0x04 identity implemented 15 + 3/word.
0x05 modexp implemented EIP-198 with divisor 20; output sized to modLen.
0x06–0x08 bn128 explicit error tvm: bn128 precompile 0xNN not supported in v1 — aborts the frame, self-explaining at S6 (user decision).
0x09 BatchValidateSign (TIP-43) implemented (bytes32, bytes[], address[]) → 32-byte result, byte i = 1 on match; ≤16 signatures; malformed input yields the zero word. 1500/signature.
0x0a ValidateMultiSign (TIP-60) implemented verified message = SHA256(account21 ‖ int32(permID) ‖ word32); recovered weights vs the account permission threshold (StateDB permission surface); ≤5 signatures. 1500/signature.
0x100 p256verify, 0x20003/0x20009 EVM-compat, 0x1000001–4 shielded, 0x1000005–15 vote/resource queries explicit error Same distinguishable failure — never a silent success.

ERRATUM — 0x03 (ARCHITECTURE §5.5)

The Tier-1 prose specifies leftPad32(RIPEMD160(SHA256(SHA256(input)))). java-tron's shipped implementation — byte-identical from at least GreatVoyage-v4.7.4 through the pinned master, and already commented-out-RIPEMD in Odyssey-v3.0.0 — computes

out32 = SHA256( SHA256(input)[0:20] )

RIPEMD-160 never runs and there is no padding. Per the Tier-3 rule (node beats prose) the interpreter implements the node's transform; docs/ARCHITECTURE.md carries the erratum entry, and the conformance suite asserts that BOTH the Ethereum-ported expectation and the §5.5 prose formula fail (TestPrecompile03_EthereumSemanticsMustFail). S6 confirms on-chain.

3. Basic energy model (layer 1 only)

E_basic is the deterministic per-opcode sum from the schedule transcribed out of EnergyCost (tiers 0/2/3/5/8/10/20/1; SLOAD 50; SSTORE 20000/5000; SHA3 30+6/word; EXP 10+10/exponent-byte; LOG 375+375/topic+8/byte; CALL 40 (+9000 value, +25000 dead account, 2300 stipend); CREATE 32000 + 200/byte code deposit; memory quadratic). Costs are charged BEFORE execution (java-tron VM.play order); any VM exception consumes the frame's whole energy; REVERT keeps the unused remainder.

The Schedule struct versions the active fork set; the default enables everything through Cancun plus the memory/energy/selfdestruct adjustments (current mainnet/Nile), with Osaka and blob support off. Pre-TIP-6780 SELFDESTRUCT semantics are not modeled.

3b. Dynamic Energy Model (layer 2, S6)

E_total = ⌊ E_basic × (10000 + energy_factor) / 10000 ⌋        (§5.4)

The energy_factor (10⁴ precision, constant within each 6-hour maintenance cycle) is a per-contract parameter read from the chain — via GetContractInfo at the fork pin, frozen for the fork's lifetime — and never simulated or hardcoded. The pure composition lives in tvm.ApplyEnergyFactor; the layer activates only when Config.EnergyFactor is set (fork mode): memState runs pay zero accounting cost.

Attribution is per contract (TIP-491 semantics): the VM tracks each contract's OWN consumed energy (children excluded, keyed by the context address — under DELEGATECALL the usage stays with the proxy) and reports

ExecResult.EnergyPenalty = Σ_c ⌊ usage_c × factor_c / 10000 ⌋
ExecResult.EnergyTotal   = EnergyUsed + EnergyPenalty

EnergyUsed remains pure E_basic — the ±0 layer is never touched by the DEM. For a single-contract call the per-contract sum reduces exactly to the §5.4 formula (⌊b×(10000+f)/10000⌋ = b + ⌊b×f/10000⌋); across contracts with different factors each is charged its own — node-faithful where a whole-call single factor would over- or under-count.

3c. Differential validation results (S6 keyless; S7 FULL run with a funded key)

Full suite run 2026-07-19 against Nile with NILE_PRIVATE_KEY — every battery green after the E4 correction:

  • Metering battery ±0 (Metering.sol deployed at block 69329340): all seven scenarios — SSTORE fresh-set (20203), reset (5224), clear (5203), loop-64 (16623), nested call (1273), keccak-calldata (1091), precompile 0x02 (1445) — matched energy_used − energy_penalty exactly, with byte-identical return data.
  • Fork metering, real contract: USDT balanceOf over the pinned fork: return data byte-identical, E_basic 935 == 935 − 0 (S6 block 69328028, re-confirmed S7 at 69329318).
  • CREATE2 duality RESOLVED (Erratum E3): real deploys via BOTH syntactic paths at block 69329318 — new C{salt} AND inline-assembly create2 observed on-chain at the 0x41 formula addresses (the 0xff formula matched neither). The interpreter's single-preimage model is confirmed; sonar create2 --mode evm is a cross-chain parity helper only.
  • Precompile 0x03 (Erratum E1 CONFIRMED ON-CHAIN): node output for inputs of length 0/3/32/100 byte-identical to SHA256(SHA256(input)[0:20]) (block 69329380) — and distinct from the Ethereum and pre-erratum formulas.
  • E_total composition ±0 + EstimateEnergy semantics (Erratum E4): ApplyEnergyFactor(E_basic_local, factor) == TriggerConstantContract total exactly (16623 == 16623, block 69329527). The node's EstimateEnergy.energy_required is NOT E_total but a fee-limit binary-search bound (1-TRX bracket) — reproduced locally ±0 (24935 == 24935). See ARCHITECTURE Erratum E4.
  • sun script plan vs receipts ±0 (S7 battery, §5.7): deploy + interaction broadcast under solidified; receipts matched the plan exactly — energy 441901/20203, bandwidth 2453/280 — and the node's ContractAddress equaled the txid-derived planned address.
  • Storage-tag probe (Erratum E2): Nile remains latest-only, surfaced via StoragePinned() == false.

4. Address derivation

CREATE  (opcode): 0x41 ‖ keccak256(txid32 ‖ nonce8_BE)[12:]      TransactionUtil.generateContractAddress
CREATE2 (opcode): 0x41 ‖ keccak256(0x41‖sender21‖salt32‖keccak256(initCode))[12:]
Root deploy:      0x41 ‖ keccak256(txid32 ‖ owner21)[12:]        WalletUtil.generateContractAddress
Local txids:      SHA-256(seed ‖ counter8_BE)                    deterministic, never wall-clock

The interpreter implements ONLY the 0x41 CREATE2 preimage; the byte 0xff never appears in internal/tvm — the EVM-emulated derivation lives solely in sun sonar create2 --mode evm (ARCHITECTURE §5.3). Vectors are cross-computed with an independent pure-Python Keccak, and the conformance suite pins that the native and 0xff derivations differ.

5. Testing and performance

Everything is hermetic on internal/state's journaled mem backend (nested snapshots, absent-vs-written-zero storage, transient storage, resource buckets). The conformance suite annotates the expected E_basic per fixture — exact sums, a 1-energy drift fails. The compiled fixture is the scaffold Counter.sol built once through the real S2 pipeline (solc-tron 0.8.23, checksum-verified) and committed as hex testdata with pinned golden energies. internal/tvm/testdata/bench_baseline.txt is the committed interpreter performance baseline (regenerate with go test -bench BenchmarkInterpreter -run XXX -benchtime=2s ./internal/tvm).