Low-level fees overview
This section describes instructions and manuals for interacting with TON at a low level.
Here you will find the raw formulas for calculating commissions and fees on TON.
However, most of them are already implemented through opcodes! So, you use them instead of manual calculations.
This document provides a general idea of transaction fees on TON and particularly computation fees for the FunC code. There is also a detailed specification in the TVM whitepaper.
Transactions and phases
As was described in the TVM overview, transaction execution consists of a few phases. During those phases, the corresponding fees may be deducted. There is a high-level fees overview.
Storage fee
TON validators collect storage fees from smart contracts.
Storage fees are collected from the smart contract balance at the Storage phase of any transaction due storage payments for the account state (including smart-contract code and data, if present) up to the present time. The smart contract may be frozen as a result.
It’s important to keep in mind that on TON you pay for both the execution of a smart contract and for the used storage (check @thedailyton article). storage fee
depends on you contract size: number of cells and sum of number of bits from that cells. Only unique hash cells are counted for storage and fwd fees i.e. 3 identical hash cells are counted as one. It means you have to pay a storage fee for having TON Wallet (even if it's very-very small).
If you have not used your TON Wallet for a significant period of time (1 year), you will have to pay a significantly larger commission than usual because the wallet pays commission on sending and receiving transactions.
Formula
You can approximately calculate storage fees for smart contracts using this formula:
storage_fee = (cells_count * cell_price + bits_count * bit_price)
* time_delta / 2^16
Let's examine each value more closely:
storage_fee
—price for storage fortime_delta
secondscells_count
—count of cells used by smart contractbits_count
—count of bits used by smart contractcell_price
—price of single cellbit_price
—price of single bit
Both cell_price
and bit_price
could be obtained from Network Config param 18.
Current values are:
- Workchain.
bit_price_ps:1
cell_price_ps:500 - Masterchain.
mc_bit_price_ps:1000
mc_cell_price_ps:500000
Calculator Example
You can use this JS script to calculate storage price for 1 MB in the workchain for 1 year
// Welcome to LIVE editor! // feel free to change any variables // Source code uses RoundUp for the fee amount, so does the calculator function storageFeeCalculator() { const size = 1024 * 1024 * 8 // 1MB in bits const duration = 60 * 60 * 24 * 365 // 1 Year in secs const bit_price_ps = 1 const cell_price_ps = 500 const pricePerSec = size * bit_price_ps + + Math.ceil(size / 1023) * cell_price_ps let fee = Math.ceil(pricePerSec * duration / 2**16) * 10**-9 let mb = (size / 1024 / 1024 / 8).toFixed(2) let days = Math.floor(duration / (3600 * 24)) let str = `Storage Fee: ${fee} TON (${mb} MB for ${days} days)` return str }
Forward fees
Internal messages define an ihr_fee
in Toncoins, which is subtracted from the value attached to the message and awarded to the validators of the destination shardchain if they include the message by the IHR mechanism. The fwd_fee
is the original total forwarding fee paid for using the HR mechanism; it is automatically computed from some configuration parameters and the size of the message at the time the message is generated. Notice that the total value carried by a newly-created internal outbound message equals the sum of value, ihr_fee
, and fwd_fee
. This sum is deducted from the balance of the source account. Of these components, only value is always credited to the destination account on message delivery. The fwd_fee
is collected by the validators on the HR path from the source to the destination, and the ihr_fee
is either collected by the validators of the destination shardchain (if the message is delivered via IHR), or credited to the destination account.
fwd_fee
covers 2/3 of the cost, as 1/3 is allocated to the action_fee
when the message is created.
auto fwd_fee_mine = msg_prices.get_first_part(fwd_fee);
auto fwd_fee_remain = fwd_fee - fwd_fee_mine;
fees_total = fwd_fee + ihr_fee;
fees_collected = fwd_fee_mine;
ap.total_action_fees += fees_collected;
ap.total_fwd_fees += fees_total;
Computation fees
Gas
All computation costs are nominated in gas units. The price of gas units is determined by this chain config (Config 20 for masterchain and Config 21 for basechain) and may be changed only by consensus of validators. Note that unlike in other systems, the user cannot set his own gas price, and there is no fee market.
Current settings in basechain are as follows: 1 unit of gas costs 400 nanotons.
TVM instructions cost
On the lowest level (TVM instruction execution) the gas price for most primitives
equals the basic gas price, computed as P_b := 10 + b + 5r
,
where b
is the instruction length in bits and r
is the
number of cell references included in the instruction.
Apart from those basic fees, the following fees appear:
Instruction | GAS price | Description |
---|---|---|
Creation of cell | 500 | Operation of transforming builder to cell. |
Parsing cell firstly | 100 | Operation of transforming cells into slices first time during current transaction. |
Parsing cell repeatedly | 25 | Operation of transforming cells into slices, which already has parsed during same transaction. |
Throwing exception | 50 | |
Operation with tuple | 1 | This price will multiply by the quantity of tuple's elements. |
Implicit Jump | 10 | It is paid when all instructions in the current continuation cell are executed. However, there are references in that continuation cell, and the execution flow jumps to the first reference. |
Implicit Back Jump | 5 | It is paid when all instructions in the current continuation are executed and execution flow jumps back to the continuation from which the just finished continuation was called. |
Moving stack elements | 1 | Price for moving stack elements between continuations. It will charge correspond gas price for every element. However, the first 32 elements moving is free. |
FunC constructions gas fees
Almost all FunC functions used in this article are defined in stablecoin stdlib.fc contract (actually, stdlib.fc with new opcodes is currently under development and not yet presented on the mainnet repos, but you can use stdlib.fc
from stablecoin source code as reference) which maps FunC functions to Fift assembler instructions. In turn, Fift assembler instructions are mapped to bit-sequence instructions in asm.fif. So if you want to understand how much exactly the instruction call will cost you, you need to find asm
representation in stdlib.fc
, then find bit-sequence in asm.fif
and calculate instruction length in bits.
However, generally, fees related to bit-lengths are minor in comparison with fees related to cell parsing and creation, as well as jumps and just number of executed instructions.
So, if you try to optimize your code start with architecture optimization, the decreasing number of cell parsing/creation operations, and then with the decreasing number of jumps.
Operations with cells
Just an example of how proper cell work may substantially decrease gas costs.
Let's imagine that you want to add some encoded payload to the outgoing message. Straightforward implementation will be as follows:
slice payload_encoding(int a, int b, int c) {
return
begin_cell().store_uint(a,8)
.store_uint(b,8)
.store_uint(c,8)
.end_cell().begin_parse();
}
() send_message(slice destination) impure {
slice payload = payload_encoding(1, 7, 12);
var msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(destination)
.store_coins(0)
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; default message headers (see sending messages page)
.store_uint(0x33bbff77, 32) ;; op-code (see smart-contract guidelines)
.store_uint(cur_lt(), 64) ;; query_id (see smart-contract guidelines)
.store_slice(payload)
.end_cell();
send_raw_message(msg, 64);
}