TVM Upgrade 2023.07
This upgrade launched run on the Mainnet from December 2023.
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 neighbor 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 checks proofs that some transaction occurred 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 (or less if masterchain seqno is less than 16), 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 take no more than 20 ns per gas unit.
Opcodes to work with new c7 values
26 gas for each, except for PREVMCBLOCKS
and PREVKEYBLOCK
(34 gas).
xxxxxxxxxxxxxxxxxxxxxx Fift syntax | xxxxxxxxx Stack | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Description |
---|---|---|
MYCODE | - c | Retrieves code of smart-contract from c7 |
INCOMINGVALUE | - t | Retrieves value of incoming message from c7 |
STORAGEFEES | - i | Retrieves value of storage phase fees from c7 |
PREVBLOCKSINFOTUPLE | - t | Retrieves PrevBlocksInfo: [last_mc_blocks, prev_key_block] from c7 |
PREVMCBLOCKS | - t | Retrieves only last_mc_blocks |
PREVKEYBLOCK | - t | Retrieves only prev_key_block |
GLOBALID | - i | Retrieves global_id from 19 network config |
Gas
xxxxxxxxxxxxxx Fift syntax | xxxxxxxx Stack | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Description |
---|---|---|
GASCONSUMED | - g_c | Returns gas consumed by VM so far (including this instruction). 26 gas |
Arithmetics
New variants of the division opcode (A9mscdf
) are added:
d=0
takes one additional integer from stack and adds it to the intermediate value before division/rshift. These operations return both the quotient and the remainder (just like d=3
).
Quiet variants are also available (e.g. QMULADDDIVMOD
or QUIET MULADDDIVMOD
).
If return values don't fit into 257-bit integers or the divider is zero, non-quiet operation throws an integer overflow exception. Quiet operations return NaN
instead of the value that doesn't fit (two NaN
s if the divider is zero).
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 |
---|---|
MULADDDIVMOD | x y w z - q=floor((xy+w)/z) r=(xy+w)-zq |
MULADDDIVMODR | x y w z - q=round((xy+w)/z) r=(xy+w)-zq |
MULADDDIVMODC | x y w z - q=ceil((xy+w)/z) r=(xy+w)-zq |
ADDDIVMOD | x w z - q=floor((x+w)/z) r=(x+w)-zq |
ADDDIVMODR | x w z - q=round((x+w)/z) r=(x+w)-zq |
ADDDIVMODC | x w y - q=ceil((x+w)/z) r=(x+w)-zq |
ADDRSHIFTMOD | x w z - q=floor((x+w)/2^z) r=(x+w)-q*2^z |
ADDRSHIFTMODR | x w z - q=round((x+w)/2^z) r=(x+w)-q*2^z |
ADDRSHIFTMODC | x w z - q=ceil((x+w)/2^z) r=(x+w)-q*2^z |
z ADDRSHIFT#MOD | x w - q=floor((x+w)/2^z) r=(x+w)-q*2^z |
z ADDRSHIFTR#MOD | x w - q=round((x+w)/2^z) r=(x+w)-q*2^z |
z ADDRSHIFTC#MOD | x w - q=ceil((x+w)/2^z) r=(x+w)-q*2^z |
MULADDRSHIFTMOD | x y w z - q=floor((xy+w)/2^z) r=(xy+w)-q*2^z |
MULADDRSHIFTRMOD | x y w z - q=round((xy+w)/2^z) r=(xy+w)-q*2^z |
MULADDRSHIFTCMOD | x y w z - q=ceil((xy+w)/2^z) r=(xy+w)-q*2^z |
z MULADDRSHIFT#MOD | x y w - q=floor((xy+w)/2^z) r=(xy+w)-q*2^z |
z MULADDRSHIFTR#MOD | x y w - q=round((xy+w)/2^z) r=(xy+w)-q*2^z |
z MULADDRSHIFTC#MOD | x y w - q=ceil((xy+w)/2^z) r=(xy+w)-q*2^z |
LSHIFTADDDIVMOD | x w z y - q=floor((x*2^y+w)/z) r=(x*2^y+w)-zq |
LSHIFTADDDIVMODR | x w z y - q=round((x*2^y+w)/z) r=(x*2^y+w)-zq |
LSHIFTADDDIVMODC | x w z y - q=ceil((x*2^y+w)/z) r=(x*2^y+w)-zq |
y LSHIFT#ADDDIVMOD | x w z - q=floor((x*2^y+w)/z) r=(x*2^y+w)-zq |
y LSHIFT#ADDDIVMODR | x w z - q=round((x*2^y+w)/z) r=(x*2^y+w)-zq |
y LSHIFT#ADDDIVMODC | x w z - q=ceil((x*2^y+w)/z) r=(x*2^y+w)-zq |
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 linearly depends on argument value) gas cost also linearly 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 ismax(arg-255,0)
(for argument less than 256 the gas consumption is constant and corresponds to the current behavior)- For
BLKSWX
, additional cost ismax(arg1+arg2-255,0)
(this does not correspond to the current behavior, since currently botharg1
andarg2
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 - h | Calculates and returns hash of the concatenation of slices (or builders) s_1...s_n . |
HASHEXTR_(HASH) | s_n ... s_1 n - h | Same thing, but arguments are given in reverse order. |
HASHEXTA_(HASH) | b s_1 ... s_n n - b' | Appends the resulting hash to a builder b instead of pushing it to the stack. |
HASHEXTAR_(HASH) | b s_n ... s_1 n - b' | Arguments are given in reverse order, appends hash to builder. |
Only the bits from root cells of s_i
are used.
Each chunk s_i
may contain non-integer number of bytes. However, the sum of bits of all chunks should be divisible by 8.
Note that TON uses most-significant bit ordering, so when two slices with non-integer number of bytes are concatenated, bits from the first slice become most-significant bits.
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.
Gas usage is rounded down.
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 also need checking
secp256k1
signatures. - 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.
xxxxxxxxxxxxx Fift syntax | xxxxxxxxxxxxxxxxx Stack | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Description |
---|---|---|
ECRECOVER | hash v r s - 0 or h x1 x2 -1 | Recovers 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_CHKSIGNS | d 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_CHKSIGNU | h 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 operations 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_FROMHASH | h1 h2 - x | Deterministically generates a valid point x from a 512-bit hash (given as two 256-bit integers).626 gas |
RIST255_VALIDATE | x - | Checks that integer x is a valid representation of some curve point. Throws range_chk on error.226 gas |
RIST255_ADD | x y - x+y | Addition of two points on a curve. 626 gas |
RIST255_SUB | x y - x-y | Subtraction of two points on curve. 626 gas |
RIST255_MUL | x n - x*n | Multiplies point x by a scalar n .Any n is valid, including negative.2026 gas |
RIST255_MULBASE | n - g*n | Multiplies the generator point g by a scalar n .Any n is valid, including negative.776 gas |
RIST255_PUSHL | - l | Pushes integer l=2^252+27742317777372353535851937790883648493 , which is the order of the group.26 gas |
RIST255_QVALIDATE | x - 0 or -1 | Quiet version of RIST255_VALIDATE .234 gas |
RIST255_QADD | x y - 0 or x+y -1 | Quiet version of RIST255_ADD . 634 gas |
RIST255_QSUB | x y - 0 or x-y -1 | Quiet version of RIST255_SUB .634 gas |
RIST255_QMUL | x n - 0 or x*n -1 | Quiet version of RIST255_MUL .2034 gas |
RIST255_QMULBASE | n - 0 or g*n -1 | Quiet version of RIST255_MULBASE .784 gas |