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
CHAINID semanticsThe 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
SELFDESTRUCT restricted behaviorWhen 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
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.java — suicide2() / canSuicide2(), EnergyCost.java — SUICIDE / SUICIDE_V2 (cost 0 → 5,000).
TRX transfer paths
TRX reaches a contract through two protocol-level mechanisms:
TransferContract(system contract) — bypasses the contract'sfallbackfunction. The TVM is not invoked at all.TriggerSmartContractwithcallValue— runs the contract's payable function orfallbacklike 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
| Instruction | TVM | EVM |
|---|---|---|
DIFFICULTY (0x44) | Returns 0 | Returns the current block difficulty |
GASLIMIT (0x45) | Returns 0 | Returns the current block's gaslimit |
GASPRICE (0x3A) | Returns energyPrice | Returns 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 bytes | Static 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 0 | Returns the current block's blob base fee |
SELFDESTRUCT (0xFF) | Cost 5,000 Energy; deletes account only if created in current tx | EIP-6780: cost 5,000 gas; deletes account only if created in current tx, otherwise transfers balance |
CREATE2 (0xF5) | Contract address prefix: 0x41 | Contract address prefix: 0xff |
Differences in precompiled contracts
| Precompiled contract address | TVM | EVM |
|---|---|---|
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) |
| 0x09 | BatchValidateSign — batch signature verify | Blake2F |
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):
| Opcode | Function |
|---|---|
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
| Opcode | Function |
|---|---|
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.
| Opcode | Function |
|---|---|
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.
| Opcode | Function |
|---|---|
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)
| Opcode | Function |
|---|---|
VOTEWITNESS (0xd8) | Vote for a Super Representative |
WITHDRAWREWARD (0xd9) | Withdraw accumulated voting rewards |
TRON-specific precompiled contracts
| Precompiled contract | Function |
|---|---|
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.
| Hardfork | TVM equivalent | Chain-parameter gate | What it adds (when enabled) |
|---|---|---|---|
| Constantinople | TVM Constantinople | getAllowTvmConstantinople | SHL (0x1B), SHR (0x1C), SAR (0x1D), EXTCODEHASH (0x3F), CREATE2 (0xf5) |
| Istanbul | TVM Istanbul | getAllowTvmIstanbul | CHAINID (0x46), SELFBALANCE (0x47), Energy-cost adjustments for SLOAD, BALANCE, EXTCODEHASH |
| London | TVM London | getAllowTvmLondon | BASEFEE (0x48) — returns energyPrice on TVM (no EIP-1559 dynamic fee market) |
| Shanghai | TVM Shanghai | getAllowTvmShangHai | PUSH0 (0x5F) |
| Cancun | TVM Cancun | getAllowTvmCancun | TLOAD (0x5C), TSTORE (0x5D), MCOPY (0x5E); MCOPY cost mirrors EVM |
| Cancun (blob) | TVM Blob | getAllowTvmBlob | BLOBHASH (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 parameter | What it controls |
|---|---|
getAllowEnergyAdjustment | Branches the Energy formula for VOTEWITNESS and SUICIDE — see Opcodes — Energy cost calculations |
getAllowDynamicEnergy | Activates the per-contract Dynamic Energy Model penalty — see FeeLimit & Energy cost — Dynamic Energy Model |
Related resources
- Opcodes — full TVM opcode reference
- TVM — the TRON Virtual Machine
- Migrating from Ethereum — practical porting walkthrough
- Stake 2.0 Solidity SDK reference — staking, delegation, and voting from contracts
- Solidity on TRON — TRON-specific Solidity language extensions
- TIP-272 — ongoing EVM-compatibility improvements (discussion)
Updated 7 days ago