TVM vs EVM

Reference for the differences between the TRON Virtual Machine and the Ethereum Virtual Machine — opcodes, precompiles, native units, address encoding, and TRON-specific extensions.

📘

Prerequisites

The TVM is fundamentally compatible with the Ethereum Virtual Machine (EVM), with notable distinctions described on this page. Most ERC-style Solidity contracts compile and run on TRON without modification — but a small set of opcodes, precompiles, and protocol-level details differ. Use this page as the reference; for a step-by-step migration walkthrough, see Migrating from Ethereum.

Core differences

Energy model in place of gas

The TVM meters execution through Energy rather than gas. The energyPrice is a network parameter that the SR committee can change through proposals. On the Mainnet it is currently 100 sun. Unlike Ethereum's fluctuating gas prices and the EIP-1559 basefee, on TRON both GASPRICE and BASEFEE opcodes return the same energyPrice.

Energy consumption for major opcodes aligns with the EVM, with some opcodes (notably SLOAD and CALL) costing less Energy on TRON.

No EIP-1559 priority-fee market. Every transaction pays the same flat energyPrice; there is no priorityFeePerGas / maxFeePerGas distinction. Wallets and SDKs that surface tip-vs-base-fee bidding controls on Ethereum should hide them on TRON.

80 ms wall-clock execution cap

Each TVM execution is bounded by a per-transaction CPU-time limit of 80 ms (chain parameter #13 getMaxCpuTimeOfOneTx). Transactions that exceed this halt with OUT_OF_TIME. The EVM has no equivalent — Ethereum bounds execution only through gas. This means a contract that runs to completion on Ethereum (within gas) can still fail on TRON if it spends too long inside the VM, even with Energy headroom remaining.

The cap can be bypassed only via the --debug CLI flag on a private chain. Using --debug on a Mainnet-syncing node produces different resultCode errors and breaks sync.

Address representation

Inside the TVM, addresses are 20 bytes, left-padded to a 32-byte DataWord — identical to the EVM. Opcodes ADDRESS, CALLER, ORIGIN, and BALANCE push or accept addresses in this 20-byte form. The TRON-specific 0x41 network prefix is added only when an address leaves the VM (storage, RPC responses, base58check encoding).

Source: actuator/.../vm/OperationActions.java:307 truncates to the last 20 bytes with the inline comment "since we use 21 bytes address instead of 20 as ethereum, we need to make sure the address length in vm is matching with 20".

Practical consequence: assembly code that extracts 20-byte addresses from a 32-byte word (a common pattern in ERC libraries) works unchanged on TVM. The 21-byte form only appears at chain boundaries — wallets, SDKs, and explorer APIs.

Account model — no nonce

TRON accounts have no nonce field. Replay protection is provided by TAPOS: every transaction references a recent block ID and expires 60 seconds after construction by default (constant TRANSACTION_DEFAULT_EXPIRATION_TIME).

Practical consequence: opcodes and patterns that depend on the sender's nonce behave differently. CREATE still generates addresses deterministically, but the deterministic input is the contract creator's address + a sequence counter maintained at the TVM level, not the account nonce. Off-chain code that pre-computes a CREATE address using the Ethereum formula will be wrong on TRON — use CREATE2 (with the TRON 0x41 prefix) for deterministic deployments.

CREATE2 prefix differs

Contract addresses generated by CREATE2 use a different prefix byte on TVM. TRON uses 0x41, computed as:

keccak256(0x41 ++ address ++ salt ++ keccak256(init_code))[12:]

Ethereum uses 0xff for the same purpose. If your contract derives expected addresses with CREATE2, the prefix byte must be changed when porting. The high-level Solidity new {salt: …} syntax is handled transparently by the TRON solc — see Migrating from Ethereum, Step 3 for both cases.

CHAINID semantics

The CHAINID opcode (0x46) does not return a static integer on TVM. It returns the last 4 bytes of the genesis block hash, padded to a 32-byte DataWord:

// actuator/.../vm/program/Program.java:1391-1396
public DataWord getChainId() {
  byte[] chainId = getContractState().getBlockByNum(0).getBlockId().getBytes();
  if (VMConfig.allowEnergyAdjustment()) {
    chainId = Arrays.copyOfRange(chainId, chainId.length - 4, chainId.length);
  }
  return new DataWord(chainId).clone();
}

This matters for cross-chain signing (EIP-712 domain separators, signed orders bridged between chains, replay protection that uses chainId). Code ported from Ethereum that hardcodes chainId = 1 will compile and run, but signature verification against a peer system will fail until you replace the constant with a real block.chainid read.

SELFDESTRUCT restricted behavior

When the chain parameter getAllowTvmSelfdestructRestriction is enabled, SELFDESTRUCT no longer deletes the contract account in the general case:

  • New contract (created in the current transaction) — full deletion; balance and assets transferred to the obtainer.
  • Existing contract — balance, TRC-10 holdings, delegated resources, and any stake-for-self are transferred to the obtainer, but the account is NOT deleted and the contract code remains callable.
  • Operation reverts if the contract has any of the following obligations that cannot be cleanly transferred:
    • Stake 1.0 frozen balance still within its lock period
    • Stake 1.0 resources delegated to another account
    • Stake 2.0 resources delegated to another account
    • Stake 2.0 with pending unfreeze entries before their expiry
    A contract with only self-staked Stake 2.0 (no delegation, no pending unfreeze) can self-destruct; the stake transfers to the obtainer.

This restricted behavior matches EIP-6780's "created-this-tx → delete; otherwise transfer balance only, no deletion" rule. TRON adds one additional constraint EIP-6780 does not have: the pre-flight revert on active stake or delegation, since the EVM has no native staking on contracts.

Practical impact: do not rely on SELFDESTRUCT as an upgrade mechanism or storage-clearing primitive on TRON. Source: Program.javasuicide2() / canSuicide2(), EnergyCost.javaSUICIDE / SUICIDE_V2 (cost 0 → 5,000).

TRX transfer paths

TRX reaches a contract through two protocol-level mechanisms:

  1. TransferContract (system contract) — bypasses the contract's fallback function. The TVM is not invoked at all.
  2. TriggerSmartContract with callValue — runs the contract's payable function or fallback like a normal EVM call.

This is different from Ethereum, where any value transfer to a contract triggers its receive / fallback. For the porting checklist, see Migrating from Ethereum — common gotchas.

Differences in standard instructions

InstructionTVMEVM
DIFFICULTY (0x44)Returns 0Returns the current block difficulty
GASLIMIT (0x45)Returns 0Returns the current block's gaslimit
GASPRICE (0x3A)Returns energyPriceReturns the current gasPrice
BASEFEE (0x48)Returns energyPrice (no separate base-fee market)Returns the current block's baseFee (EIP-1559)
CHAINID (0x46)Last 4 bytes of the genesis block hash, padded to 32 bytesStatic chain-id constant (1 Mainnet, etc.)
BLOBHASH (0x49)Returns 0 (no blob transactions on TRON)Returns the versioned hash of the indexed blob
BLOBBASEFEE (0x4A)Returns 0Returns the current block's blob base fee
SELFDESTRUCT (0xFF)Cost 5,000 Energy; deletes account only if created in current txEIP-6780: cost 5,000 gas; deletes account only if created in current tx, otherwise transfers balance
CREATE2 (0xF5)Contract address prefix: 0x41Contract address prefix: 0xff

Differences in precompiled contracts

Precompiled contract addressTVMEVM
Ripemd160 (0x03)Computes SHA-256 twice (legacy behavior, kept for backward compatibility). For real RIPEMD-160, call the EthRipemd160 precompile at 0x00020003.RIPEMD-160 hash of the input (32-byte left-padded result)
0x09BatchValidateSign — batch signature verifyBlake2F

TRON-specific extensions

The TVM adds a number of opcodes and precompiles for TRC-10 tokens, staking, voting, and contract introspection. These do not exist on the EVM. For the full opcode reference (including standard EVM opcodes), see Opcodes.

TRC-10 opcodes

These instructions handle the protocol-level TRC-10 token type (TIP-43):

OpcodeFunction
CALLTOKEN (0xd0)Invokes a contract with an attached TRC-10 token
TOKENBALANCE (0xd1)Queries the TRC-10 token balance of an address
CALLTOKENVALUE (0xd2)Returns the TRC-10 token value attached to the current call
CALLTOKENID (0xd3)Returns the TRC-10 token ID attached to the current call

Contract introspection

OpcodeFunction
ISCONTRACT (0xd4)Returns whether a given address is a contract address (TIP-44)

Staking 1.0 opcodes (legacy, TIP-157)

These are the legacy Stake 1.0 opcodes. New code should use the V2 opcodes below.

OpcodeFunction
FREEZE (0xd5)Stake TRX to acquire resources
UNFREEZE (0xd6)Unstake TRX, releasing acquired resources
FREEZEEXPIRETIME (0xd7)Queries the expiration time of a stake

Stake 2.0 opcodes (TIP-467)

The current resource-staking opcodes. For the full Solidity SDK and lifecycle details, see Stake 2.0 Solidity SDK reference.

OpcodeFunction
FREEZEBALANCEV2 (0xda)Stake TRX to acquire resources
UNFREEZEBALANCEV2 (0xdb)Begin unstaking TRX; the waiting period is set by chain parameter #70 getUnfreezeDelayDays (currently 14 days on Mainnet)
CANCELALLUNFREEZEV2 (0xdc)Cancel all pending unstake operations
WITHDRAWEXPIREUNFREEZE (0xdd)Withdraw TRX whose unstake waiting period has elapsed
DELEGATERESOURCE (0xde)Delegate resources to another address
UNDELEGATERESOURCE (0xdf)Cancel resource delegation

Voting opcodes (TIP-271)

OpcodeFunction
VOTEWITNESS (0xd8)Vote for a Super Representative
WITHDRAWREWARD (0xd9)Withdraw accumulated voting rewards

TRON-specific precompiled contracts

Precompiled contractFunction
BatchValidateSign (0x09)Batch signature verification (TIP-43)
ValidateMultiSign (0x0a)Account Permission Management verification (TIP-60)
RewardBalance (0x1000005)Voting reward balance (TIP-271)
IsSrCandidate (0x1000006)Whether an address is a Super Representative Candidate
VoteCount (0x1000007)Number of votes from one address to a specific SR
GetChainParameter (0x100000b)Query a chain parameter (TIP-467)
AvailableUnfreezeV2Size (0x100000c)Available length of the unstake queue for an address
UnfreezableBalanceV2 (0x100000d)Unstakeable balance for a resource type
ExpireUnfreezeBalanceV2 (0x100000e)Withdrawable amount at a given timestamp
DelegatableResource (0x100000f)Delegatable amount of a resource type
ResourceV2 (0x1000010)Delegated resources from one address to another
CheckUnDelegateResource (0x1000011)Whether a contract can reclaim delegated resources
ResourceUsage (0x1000012)Resource usage and recovery time
TotalResource (0x1000013)Total available resources for an address
TotalDelegatedResource (0x1000014)Total resources delegated from an address
TotalAcquiredResource (0x1000015)Total resources acquired through delegation

Ethereum hardfork tracking

TRON adopts EVM hardfork features incrementally through chain-parameter gates. Each hardfork's feature set is activated only after a committee proposal flips its gate. Until the gate is enabled, the corresponding opcodes are treated as ILLEGAL_OPERATION.

HardforkTVM equivalentChain-parameter gateWhat it adds (when enabled)
ConstantinopleTVM ConstantinoplegetAllowTvmConstantinopleSHL (0x1B), SHR (0x1C), SAR (0x1D), EXTCODEHASH (0x3F), CREATE2 (0xf5)
IstanbulTVM IstanbulgetAllowTvmIstanbulCHAINID (0x46), SELFBALANCE (0x47), Energy-cost adjustments for SLOAD, BALANCE, EXTCODEHASH
LondonTVM LondongetAllowTvmLondonBASEFEE (0x48) — returns energyPrice on TVM (no EIP-1559 dynamic fee market)
ShanghaiTVM ShanghaigetAllowTvmShangHaiPUSH0 (0x5F)
CancunTVM CancungetAllowTvmCancunTLOAD (0x5C), TSTORE (0x5D), MCOPY (0x5E); MCOPY cost mirrors EVM
Cancun (blob)TVM BlobgetAllowTvmBlobBLOBHASH (0x49), BLOBBASEFEE (0x4A) — both return 0 on TVM (no blob transactions); the opcodes exist for bytecode compatibility

Checking which gates are currently enabled

Hardfork rollout is independent per network. Mainnet typically leads, with Nile and Shasta enabling new gates earlier for testing. Always check the live state rather than assuming a feature is on:

# Mainnet
curl -s https://api.trongrid.io/wallet/getchainparameters | \
  jq '.chainParameter[] | select(.key | startswith("getAllowTvm"))'

# Nile testnet
curl -s https://nile.trongrid.io/wallet/getchainparameters | \
  jq '.chainParameter[] | select(.key | startswith("getAllowTvm"))'

# Shasta testnet
curl -s https://api.shasta.trongrid.io/wallet/getchainparameters | \
  jq '.chainParameter[] | select(.key | startswith("getAllowTvm"))'

A value of 1 means the gate is enabled. Discrepancies between Mainnet and a testnet are common during rollout windows — verify before assuming code that runs on one will run on the other.

TVM-specific gates (no Ethereum equivalent)

Two chain-parameter gates affect opcode behavior independently of any Ethereum hardfork:

Chain parameterWhat it controls
getAllowEnergyAdjustmentBranches the Energy formula for VOTEWITNESS and SUICIDE — see Opcodes — Energy cost calculations
getAllowDynamicEnergyActivates the per-contract Dynamic Energy Model penalty — see FeeLimit & Energy cost — Dynamic Energy Model

Related resources