Skip to main content

TVM Upgrade 2023.07

Work in progress:

Some operation codes may be removed, some gas prices are likely to be changed. It is expected that no new opcodes will be added to the list.

This upgrade expected to be shipped in testnet by the end of the May and in mainnet by the end of the June.

c7

c7 is the register in which local context information needed for contract execution (such as time, lt, network configs, etc) is stored.

c7 tuple extended from 10 to 14 elements:

  • 10: cell with code of the smart contract itself.
  • 11: [integer, maybe_dict]: TON value of the incoming message, extracurrency.
  • 12: integer, fees collected in the storage phase.
  • 13: tuple with information about previous blocks.

10 Currently code of the smart contract is presented on TVM level only as executable continuation and can not be transformed to cell. This code is often used to authorize a neighbour contract of the same kind, for instance jetton-wallet authorizes jetton-wallet. For now we need to explicitly store code cell in storage which make storage and init_wrapper more cumbersome than it could be. Using 10 for code is compatible to Everscale update of tvm.

11 Currently value of the incoming message is presented on stack after TVM initiation, so if needed during execution, one either need to store it to global variable or pass through local variables (at funC level it looks like additional msg_value argument in all functions). By putting it to 11 element we will repeat behavior of contract balance: it is presented both on stack and in c7.

12 Currently the only way to calculate storage fees is to store balance in the previous transaction, somehow calculate gas usage in prev transaction and then compare to current balance minus message value. Meanwhile, is often desired to account storage fees.

13 Currently there is no way to retrieve data on previous blocks. One of the kill features of TON is that every structure is a Merkle-proof friendly bag (tree) of cells, moreover TVM is cell and merkle-proof friendly as well. That way, if we include information on the blocks to TVM context it will be possible to make many trustless scenarios: contract A may check transactions on contract B (without B's cooperation), it is possible to recover broken chains of messages (when recover-contract gets and cheks proofs that some transaction occured but reverted), knowing masterchain block hashes is also required to make some validation fisherman functions onchain.

Block ids are presented in the following format:

[ wc:Integer shard:Integer seqno:Integer root_hash:Integer file_hash:Integer ] = BlockId;
[ last_mc_blocks:[BlockId0, BlockId1, ..., BlockId15]
prev_key_block:BlockId ] : PrevBlocksInfo

Ids of the last 16 blocks of masterchain are included, as well as the last key block. Inclusion of data on shardblocks may cause some data availability issues (due to merge/split events), it is not necessarily required (since any event/data can by proven using masterchain blocks) and thus we decided not to include it.

New opcodes

Rule of thumb when choosing gas cost on new opcodes is that it should not be less than normal (calculated from opcode length) and should spend no more than 20 ns per gas unit.

Opcodes to work with new c7 values

26 gas for each

xxxxxxxxxxxxxxxxxxxxxx
Fift syntax
xxxxxxxxx
Stack
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Description
MYCODE- cRetrieve code of smart-contract from c7
INCOMINGVALUE- tRetrieve value of incoming message from c7
STORAGEFEES- iRetrieve value of storage phase fees from c7
PREVBLOCKSINFOTUPLE- tRetrive PrevBlocksInfo: [last_mc_blocks, prev_key_block] from c7
PREVMCBLOCKS- tRetrive only last_mc_blocks
PREVKEYBLOCK- tRetrieve only prev_key_block
GLOBALID- iRetrieve global_id from 19 network config

Gas

xxxxxxxxxxxxxx
Fift syntax
xxxxxxxx
Stack
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Description
GASCONSUMED- g_cReturns gas consumed by VM so far
26 gas

Arithmetics

Gas cost is equal to 10 plus opcode length: 26 for most opcodes, +8 for LSHIFT#/RSHIFT#, +8 for quiet.

xxxxxxxxxxxxxxxxxxxxxx
Fift syntax
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Stack
MULADDDIVMODx y w z - q=floor((xy+w)/z) r=(xy+w)-zq
MULADDDIVMODRx y w z - q=round((xy+w)/z) r=(xy+w)-zq
MULADDDIVMODCx y w z - q=ceil((xy+w)/z) r=(xy+w)-zq
ADDDIVMODx w z - q=floor((x+w)/z) r=(x+w)-zq
ADDDIVMODRx w z - q=round((x+w)/z) r=(x+w)-zq
ADDDIVMODCx w y - q=ceil((x+w)/z) r=(x+w)-zq
ADDRSHIFTMODx w z - q=floor((x+w)/2^z) r=(x+w)-q*2^z
ADDRSHIFTMODRx w z - q=round((x+w)/2^z) r=(x+w)-q*2^z
ADDRSHIFTMODCx w z - q=ceil((x+w)/2^z) r=(x+w)-q*2^z
z ADDRSHIFT#MODx w - q=floor((x+w)/2^z) r=(x+w)-q*2^z
z ADDRSHIFTR#MODx w - q=round((x+w)/2^z) r=(x+w)-q*2^z
z ADDRSHIFTC#MODx w - q=ceil((x+w)/2^z) r=(x+w)-q*2^z
MULADDRSHIFTMODx y w z - q=floor((xy+w)/2^z) r=(xy+w)-q*2^z
MULADDRSHIFTRMODx y w z - q=round((xy+w)/2^z) r=(xy+w)-q*2^z
MULADDRSHIFTCMODx y w z - q=ceil((xy+w)/2^z) r=(xy+w)-q*2^z
z MULADDRSHIFT#MODx y w - q=floor((xy+w)/2^z) r=(xy+w)-q*2^z
z MULADDRSHIFTR#MODx y w - q=round((xy+w)/2^z) r=(xy+w)-q*2^z
z MULADDRSHIFTC#MODx y w - q=ceil((xy+w)/2^z) r=(xy+w)-q*2^z
LSHIFTADDDIVMODx w z y - q=floor((x*2^y+w)/z) r=(x*2^y+w)-zq
LSHIFTADDDIVMODRx w z y - q=round((x*2^y+w)/z) r=(x*2^y+w)-zq
LSHIFTADDDIVMODCx w z y - q=ceil((x*2^y+w)/z) r=(x*2^y+w)-zq
y LSHIFT#ADDDIVMODx w z - q=floor((x*2^y+w)/z) r=(x*2^y+w)-zq
y LSHIFT#ADDDIVMODRx w z - q=round((x*2^y+w)/z) r=(x*2^y+w)-zq
y LSHIFT#ADDDIVMODCx w z - q=ceil((x*2^y+w)/z) r=(x*2^y+w)-zq

Quiet versions of these instructions are available: QUIET ADDDIVMOD, QUIET 1 MULADDRSHIFTR#MOD etc.

Stack operations

Currently arguments of all stack operations are bounded by 256. That means that if stack become deeper than 256 it becomes difficult to manage deep stack elements. In most cases there are no safety reasons for that limit, i.e. arguments are not limited to prevent too expensive operations. For some mass stack operations, such as ROLLREV (where computation time lineary depends on argument value) gas cost also lineary depends on argument value.

  • Arguments of PICK, ROLL, ROLLREV, BLKSWX, REVX, DROPX, XCHGX, CHKDEPTH, ONLYTOPX, ONLYX are now unlimited.
  • ROLL, ROLLREV, REVX, ONLYTOPX consume more gas when arguments are big: additional gas cost is max(arg-255,0) (for argument less than 256 the gas consumption is constant and corresponds to the current behavior)
  • For BLKSWX, additional cost is max(arg1+arg2-255,0) (this does not correspond to the current behavior, since currently both arg1 and arg2 are limited to 255).

Hashes

Currently only two hash operations are available in TVM: calculation of representation hash of cell/slice, and sha256 of data, but only up to 127 bytes (only that much data fits into one cell).

HASHEXT[A][R]_(HASH) family of operations is added:

xxxxxxxxxxxxxxxxxxx
Fift syntax
xxxxxxxxxxxxxxxxxxxxxx
Stack
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Description
HASHEXT_(HASH)s_1 ... s_n n - hCalculate and return hash of the concatenation of slices (or builders) s_1...s_n.
HASHEXTR_(HASH)s_n ... s_1 n - hSame thing, but arguments are given in reverse order.
HASHEXTA_(HASH)b s_1 ... s_n n - b'Append the resulting hash to a builder b instead of pushing it to the stack.
HASHEXTAR_(HASH)b s_n ... s_1 n - b'Arguments in reverse order, append hash to builder.

Only the bits from root cells of s_i are used. Note that each chunk s_i may contain non-integer number of bytes. However, the sum of bits of all chunks should be divisible by 8. Gas consumption depends on the number of hashed bytes and the chosen algorithm. Additional 1 gas unit is consumed per chunk.

If [A] is not enabled, the result of hashing will be returned as an unsigned integer if fits 256 bits or tuple of ints otherwise.

The following algorithms are available:

  • SHA256 - openssl implementation, 1/33 gas per byte, hash is 256 bits.
  • SHA512 - openssl implementation, 1/16 gas per byte, hash is 512 bits.
  • BLAKE2B - openssl implementation, 1/19 gas per byte, hash is 512 bits.
  • KECCAK256 - ethereum compatible implementation , 1/11 gas per byte, hash is 256 bits.
  • KECCAK512 - ethereum compatible implementation , 1/6 gas per byte, hash is 512 bits.

Crypto

Currently the only cryptographic algorithm available is CHKSIGN: check the Ed25519-signature of a hash h for a public key k.

For compatibility with prev generation blockchains such as Bitcoin and Ethereum we need secp256k1 signature checking algo. For modern cryptographic algorithms the bare minimum is curve addition and multiplication. For compatibility with Ethereum 2.0 PoS and some other modern cryptography we need BLS-signature scheme on bls12-381 curve. For some secure hardware secp256r1 == P256 == prime256v1 is needed.

secp256k1

Bitcoin/ethereum signatures. Uses libsecp256k1 implementation (https://github.com/bitcoin-core/secp256k1).

xxxxxxxxxxxxx
Fift syntax
xxxxxxxxxxxxxxxxx
Stack
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Description
ECRECOVERhash v r s - 0 or h x1 x2 -1Recovers public key from signature, identical to Bitcoin/Ethereum operations.
Takes 32-byte hash as uint256 hash; 65-byte signature as uint8 v and uint256 r, s.
Returns 0 on failure, public key and -1 on success.
65-byte public key is returned as uint8 h, uint256 x1, x2.
1526 gas

secp256r1

Uses OpenSSL implementation. Interface is similar to CHKSIGNS/CHKSIGNU. Compatible with Apple Secure Enclave.

xxxxxxxxxxxxx
Fift syntax
xxxxxxxxxxxxxxxxx
Stack
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Description
P256_CHKSIGNSd sig k - ?Checks seck256r1-signature sig of data portion of slice d and public key k. Returns -1 on success, 0 on failure.
Public key is a 33-byte slice (encoded according to Sec. 2.3.4 point 2 of SECG SEC 1).
Signature sig is a 64-byte slice (two 256-bit unsigned integers r and s).
3526 gas
P256_CHKSIGNUh sig k - ?Same thing, but the signed data is 32-byte encoding of 256-bit unsigned integer h.
3526 gas

Ristretto

Extended docs are here. In short, Curve25519 was developed with performance in mind, however it exhibits symmetry due to which group elements have multiple representations. Simpler protocols such as Schnorr signatures or Diffie-Hellman apply tricks at the protocol level to mitigate some issues, but break key derivation and key blinding schemes. And those tricks do not scale to more complex protocols such as Bulletproofs. Ristretto is an arithmetic abstraction over Curve25519 such that each group element corresponds to a unique point, which is the requirement for most cryptographic protocols. Ristretto is essentially a compression/decompression protocol for Curve25519 that offers the required arithmetic abstraction. As a result, crypto protocols are easy to write correctly, while benefiting from the high performance of Curve25519.

Ristretto operation allow calculating curve operations on Curve25519 (the reverse is not true), thus we can consider that we add both Ristretto and Curve25519 curve operation in one step.

libsodium implementation is used.

All ristretto-255 points are represented in TVM as 256-bit unsigned integers. Non-quiet operations throw range_chk if arguments are not valid encoded points. Zero point is represented as integer 0.

xxxxxxxxxxxxx
Fift syntax
xxxxxxxxxxxxxxxxx
Stack
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Description
RIST255_FROMHASHh1 h2 - xDeterministically generate a valid point x from a 512-bit hash (given as two 256-bit integers)
626 gas
RIST255_VALIDATEx -Check that integer x is a valid representation of some curve point. Throw range_chk on error.
226 gas
RIST255_ADDx y - x+yAddition of two points on a curve
626 gas
RIST255_SUBx y - x-ySubtraction of two points on curve
626 gas
RIST255_MULx n - x*nMultiply point x by a scalar n.
2026 gas
RIST255_MULBASEn - g*nMultiply the generator point g by a scalar n.
776 gas
RIST255_PUSHL- lPush integer l=2^252+27742317777372353535851937790883648493, which is the order of the group.
26 gas
RIST255_QVALIDATEx - 0 or -1Quiet version of RIST255_VALIDATE.
234 gas
RIST255_QADDx y - 0 or x+y -1Quiet version of RIST255_ADD.
634 gas
RIST255_QSUBx y - 0 or x-y -1Quiet version of RIST255_SUB.
634 gas
RIST255_QMULx n - 0 or x*n -1Quiet version of RIST255_MUL.
2034 gas
RIST255_QMULBASEn - 0 or g*n -1Quiet version of RIST255_MULBASE.
784 gas

BLS12-381

Operations on a pairing friendly BLS12-381 curve. BLST implementation is used. Also, ops for BLS signature scheme which is based on this curve.

BLS values are represented in TVM in the following way:

  • G1-points and public keys: 48-byte slice.
  • G2-points and signatures: 96-byte slice.
  • Elements of field FP: 48-byte slice.
  • Elements of field FP2: 96-byte slice.
  • Messages: slice. Number of bits should be divisible by 8.
xxxxxxxxxxxxx
Fift syntax
xxxxxxxxxxxxxxxxx
Stack
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Description
BLS_VERIFYpk msg sgn - boolCheck BLS signature, return true on success.
61000 gas
BLS_AGGREGATEsig_1 ... sig_n n - sigAggregate signatures. n>0.
gas=n*4350-2650
BLS_FASTAGGREGATEVERIFY-pk_1 ... pk_n msg sig - boolCheck aggregated BLS signature for keys pk_1...pk_n and message msg. n>0.
gas=58000+n*3000
BLS_AGGREGATEVERIFYpk_1 msg_1 ... pk_n msg_n n sgn - boolCheck aggregated BLS signature for kay-message pairs pk_1 msg_1...pk_n msg_n. n>0.
gas=38500+n*22500
BLS_G1_ADDx y - x+yAddition on G1.
3900 gas
BLS_G1_SUBx y - x-ySubtraction on G1.
3900 gas
BLS_G1_NEGx - -xNegation on G1.
750 gas
BLS_G1_MULx s - x*sMultiply G1 point x by scalar s.
5200 gas
BLS_G1_MULTIEXPx_1 s_1 ... x_n s_n - x_1*s_1+...+x_n*s_nCalculate x_1*s_1+...+x_n*s_n for G1 points x_i and scalars n_i.
gas=11409+n*630+n/floor(max(log2(n),4))*8820
BLS_G1_ZERO- zeroPush zero point in G1.
34 gas
BLS_MAP_TO_G1f - xConvert FP element f to a G1 point.
2350 gas
BLS_G1_INGROUPx - boolCheck that slice x represents a valid element of G1.
2950 gas
BLS_G1_ISZEROx - boolCheck that G1 point x is equal to zero.
34 gas
BLS_G2_ADDx y - x+yAddition on G2.
6134 gas
BLS_G2_SUBx y - x-ySubtraction on G2.
6134 gas
BLS_G2_NEGx - -xNegation on G2.
1584 gas
BLS_G2_MULx s - x*sMultiply G2 point x by scalar s.
10550 gas
BLS_G2_MULTIEXPx_1 s_1 ... x_n s_n - x_1*s_1+...+x_n*s_nCalculate x_1*s_1+...+x_n*s_n for G2 points x_i and scalars n_i.
gas=30422+n*1280+n/floor(max(log2(n),4))*22840
BLS_G2_ZERO- zeroPush zero point in G2.
34 gas
BLS_MAP_TO_G2f - xConvert FP2 element f to a G2 point.
7950 gas
BLS_G2_INGROUPx - boolCheck that slice x represents a valid element of G2.
4250 gas
BLS_G2_ISZEROx - boolCheck that G2 point x is equal to zero.
34 gas
BLS_PAIRINGx_1 y_1 ... x_n y_n n - boolGiven G1 points x_i and G2 points y_i, calculate and multiply pairings of x_i,y_i. Return true if the result is the multiplicative identity in FP12.
gas=20034+n*11800
BLS_PUSHR- rPush the order of G1 and G2 (approx. 2^255).
gas=34

RUNVM

Currently there is no way for code in TVM to call external untrusted code "in sandbox". In other words, external code always can irreversibly update code, data of contract, or set actions (such as sending all money). RUNVM instruction allows to spawn an independent VM instance, run desired code and get needed data (stack, registers, gas consumption etc) without risks of polluting caller's state. Running arbitrary code in a safe way may be useful for v4-style plugins, Tact's init style subcontract calculation etc.

xxxxxxxxxxxxx
Fift syntax
xxxxxxxxxxxxxxxxx
Stack
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Description
flags RUNVMx_1 ... x_n n code [r] [c4] [c7] [g_l] [g_m] - x'_1 ... x'_m exitcode [data'] [c4'] [c5] [g_c]Run child VM with code code and stack x_1...x_n. Return the resulting stack x'_1...x'_m and exitcode.
Other arguments and return values are enabled by flags, see below.
RUNVMXx_1 ... x_n n code [r] [c4] [c7] [g_l] [g_m] flags - x'_1 ... x'_m exitcode [data'] [c4'] [c5] [g_c]Same thing. but pop flags from stack.

Flags are similar to runvmx in fift:

  • +1: set c3 to code
  • +2: push an implicit 0 before running the code
  • +4: take c4 from stack (persistent data), return its final value
  • +8: take gas limit g_l from stack, return consumed gas g_c
  • +16: take c7 from stack (smart-contract context)
  • +32: return final value of c5 (actions)
  • +64: pop hard gas limit (enabled by ACCEPT) g_m from stack
  • +128: "isolated gas consumption". Child VM will have a separate set of visited cells and a separate chksgn counter.
  • +256: pop integer r, return exactly r values from the top of the stack (only if exitcode=0 or 1; if not enough then exitcode=stk_und)

Gas cost:

  • 66 gas
  • 1 gas for every stack element given to the child VM (first 32 are free)
  • 1 gas for every stack element returned from the child VM (first 32 are free)

Sending messages

Currently it is difficult to calculate cost of sending message in contract (which leads to some approximations like in jettons) and impossible to bounce request back if action phase is incorrect. It is also impossible to accurately subtract from incoming message sum of "constant fee for contract logic" and "gas expenses".

  • SENDMSG takes a cell and mode as input. Creates an output action and returns a fee for creating a message. Mode has the same effect as in the case of SENDRAWMSG. Additionally +1024 means - do not create an action, only estimate fee. Other modes affect the fee calculation as follows: +64 substitutes the entire balance of the incoming message as an outcoming value (slightly inaccurate, gas expenses that cannot be estimated before the computation is completed are not taken into account), +128 substitutes the value of the entire balance of the contract before the start of the computation phase (slightly inaccurate, since gas expenses that cannot be estimated before the completion of the computation phase are not taken into account).
  • SENDRAWMSG,RAWRESERVE,SETLIBRARY - +16 flag is added, that means in the case of action fail - bounce transaction. Won't work if +2 is used