Skip to main content

Network Fees

Fee calculation

Introduction

Transaction fees consist of a few types of different fees connected to the execution of a single transaction. Transactions itself are complex processes, and fees are paid relative to different stages of executing them.

In this document, we explain how the fees are calculated.

We shall define transaction_fee as a sum of all fees for a single transaction.

transaction_fee = inbound_external_message_fee
+ storage_fees
+ gas_fees
+ total_action_fees
+ outbound_internal_messages_fee

Where:

inbound_external_message_fee — is deducted, if an inbound external message is imported in the transaction.

storage_feesstorage costs since the moment of the last transaction.

gas_fees — include all gas fees associated with the transaction. You can find more info in the Gas calculation basics section.

total_action_fees — fees for performing send message actions.

outbound_internal_messages_fee — is calculated as a sum of fees for all outbound internal messages generated by the transaction.

Depending on the nature of the transaction, all of these except storage fees may not be applicable.

Below we examine these types of fees in detail.

Note: Block creation fee is not to be confused with the fees discussed in this document. Block creation fee is the new coins minted by the elector contract and distributed among validators as reward for creating blocks. It is not part of transaction fees.

Storage fees

Every transaction in Everscale has a storage phase that implies a certain storage fee charged on an account balance. This fee is charged for the period between transactions and is calculated according to the following formula:

storage_fees = CEIL(
(
account.bits
* global_bit_price
+ account.cells
* global_cell_price
) * period / 2 ^ 16
)

Where:

account.bits and account.cells — stand for a number of bits and cells in the Account structure represented as tree of cells (including code and data).

global_bit_price — is a global configuration parameter (p18 for both masterchain and workchains), price for storing one bit.

global_cell_price — another global configuration parameter (p18 for both masterchain and workchains), price for storing one cell.

period — number of seconds since previous storage fee payment.

Note: While account.bits are generally easy to estimate, the account.cells value can vary greatly for different types of data. A cell can contain no more than 1023 bits and 4 references to other cells. Contract code and numerical variables tend to be packed into cells effectively, resulting in mostly full cells, and thus a minimal number of cells needed to store the data. More complex data structures can be packed into cells less efficiently, taking up more cells to store the same amount of data.

Example: Let's calculate a minimal fee for storing 1KB of data for the duration of one day on a workchain:

global_bit_price = 1

global_cell_price = 500

period = 86400 seconds

account.bits = 8192

The minimal account.cells value for 8192 bits of data is 9 (rounding 8192/1023 up to the nearest integer).

Thus the minimum storage fee would be calculated as follows:

storage_fees = CEIL(
(
8192
* 1
+ 9
* 500
) * 86400 / 65536
) = 16733 nanotokens = 0.000016733 tokens

Real storage fees for 1KB account can be higher, depending on the specific features of the contract.

If the account balance is less than the due storage fee, the account is frozen and its balance is subtracted from storage fee and reduced to zero. Remaining storage fee is stored in account as debt.

Note: Current global configuration can be always reviewed on [ever.live]( https:/ /ever.live/) in the master config section of the latest key block details (example) FIXME broken link. It can only be changed by a vote of validators.

Message fees

Every message is subject to a forwarding fee, which is calculated according to the following formula:

msg_fwd_fee = (
lump_price + CEIL(
(
bit_price
* msg.bits
+ cell_price
* msg.cells
) / 2 ^ 16
)
)

msg.bits and msg.cells are calculated from message represented as a tree of cells. Root cell is not counted. lump_price, bit_price, cell_price are contained in global config parameters p24 and p25, and can and can only be changed by a vote of validators.

Note: Like in storage fees, msg.bits are generally easy to estimate, while the msg.cells value can vary for different types of messages.

Example: Let's calculate a minimal forward fee for sending a 1KB message on a workchain:

lump_price = 10000000

bit_price = 655360000

cell_price = 65536000000

To calculate msg.bits we subtract the root cell bits from the total message bits. For this example we'll assume that the root cell is filled completely (usually this is not the case, and the subtracted value is smaller, which results in a higher fee):

msg.bits = 8192 - 1023 = 7169

To calculate msg.cells we subtract the root cell from the total umber of cells. The minimal number of cells in a 1 KB message is 9 (rounding 8192/1023 up to the nearest integer). Thus msg.cells is calculated as follows:

msg.bits = 9 - 1 = 8

The minimum forward fee for a 1KB message would be calculated as follows:

msg_fwd_fee = (
10000000 + CEIL(
(
655360000
* 7169
+ 65536000000
* 8
) / 65536
)
) = 89690000 nanotokens = 0.08969 tokens

Real forward fees for 1 KB messages may be higher, depending on the type and contents of the message.

Note: Current global configuration can be always reviewed on ever.live in the master config section of the latest key block details (example) FIXME broken link.

Outbound messages

outbound_internal_messages_fee is calculated as a sum of outbound internal message fees for every message generated as result of transaction execution:

outbound_internal_messages_fee = SUM(
out_int_msg.header.fwd_fee
+ out_int_msg.header.ihr_fee
)

Where

out_int_msg.header.fwd_fee is a part of the standard forward fee for the outbound internal message.

out_int_msg.header.ihr_fee is currently disabled.

Routing

The forward fee for outbound internal message is split into int_msg_mine_fee and int_msg_remain_fee:

msg_forward_fee = int_msg_mine_fee + int_msg_remain_fee

Where:

int_msg_mine_fee = msg_forward_fee * first_frac / 2 ^ 16

first_frac — is contained in global config parameters p24 and p25, and determines the fraction of the fee, that the current set of validators receive.

Note: Current global configuration can be always reviewed on ever.live in the master config section of the latest key block details (example) FIXME broken link.

int_msg_mine_fee then becomes part of transaction action fees (see below).

The remaining int_msg_remain_fee is placed in the header of outbound internal message (becoming out_int_msg.header.fwd_fee) and will go to validators who will process the message.

If, while being forwarded to the destination address, the message passes through additional validator sets (i.e. if the validator set changes more than once while the message is being forwarded), a part of out_int_msg.header.fwd_fee is payed to the relevant validator set every time and the remaining fee in the message header is reduced by this amount:

intermediate_fee = out_int_msg.header.fwd_fee * next_frac / 2 ^ 16

next_frac — is contained in global config parameters p24 and p25, and determines the fraction of the remaining forward fee, that intermediary validators receive.

Note: Current global configuration can be always reviewed on ever.live in the master config section of the latest key block details (example) FIXME broken link.

Note: Length of route does not affect the initial calculation of the forward fee. The fee is simply split between all involved validators according to global config parameters.

Note: If an exception is thrown, and a bounce message is generated, it is subject to fees, just like a single regular outbound message.

Inbound external messages

Whenever an inbound external message needs to be imported for transaction execution, the for this action fee is calculated according to the standard forwarding fee formula, and paid to the current validators.

Action fees

Action fees pay for performing 'send message' actions. They consist of all fees for external outbound messages, and the first fraction of internal outbound message fees. They are calculated as follows:

total_action_fees = total_out_ext_msg_fwd_fee + total_int_msg_mine_fee

where:

total_out_ext_msg_fwd_fee — sum of implicit forward fee for all generated outbound external messages. total_int_msg_mine_fee — sum of 'mine' parts of message forward fees for outbound internal messages. total_fwd_fees — is a separate way to calculate total forwarding fees.

total_fwd_fees = total_action_fees + SUM(
int_msg_remain_fee
+ out_int_msg.header.ihr_fee
)

out_int_msg.header.ihr_fee — this fee is currently zero.

The action fee might be absent if no actions are performed during the transaction.

Gas fees

trans.gas_fees includes all gas fees associated with the transaction.

As with Action fees, Gas fees are not always applicable. They can be skipped if the TVM compute phase is not initialized for a transaction.

In order to study the calculation of Gas fees in detail please consult this page.

Managing gas

Gas calculation basics

Specification Overview

The entire state of TVM consists of the five components:

  • Stack
  • Control registers
  • Current continuation
  • Current codepage
  • Gas limits

Collectively these are called SCCCG.

Check out section 1.4 of the TVM specification.

The Gas component limits gas usage and сontains four signed 64-bit integers:

  • the remaining gas: gr
  • the current gas limit: gl
  • the maximal gas limit: gm
  • the gas credit: gc

The following is always true:

0glgm,gc0,andgrgl+gc0 ≤ gl ≤ gm, gc ≥ 0, and gr ≤ gl + gc

gc is initialized by zero for internal messages, gr is initialized by gl + gc and gradually decreases, as the TVM runs. When gr becomes negative or if contract terminates with gc > 0, an out of gas exception is triggered.

Gas prices

As stated in A.1 of the TVM specification.

According to the original TON, for most primitives gas is calculated according to the following formula:

Pb:=10+bPb := 10 + b

where b is the instruction length in bits. The same is true for EverX implementation.

For example: the gas required for A0 (ADD) instruction is 10 + 8 = 18 gas, while the gas for A6cc (ADDCONST cc) instruction is 10 + 16 = 26 gas.

For some instructions this rule does not apply. TVM specification lists either total gas prices, or prices in addition to the basic Pb for them explicitly.

Instruction list with additional information may be obtained in A.2 through A.13 of the TVM specification.

Apart from integer constants, the following expressions may appear:

  • The total price of loading cells. Currently it is 100 gas units per cell. Reloading a cell again now costs 25 gas units.
  • The total price of creating new Cells from Builders. Currently it is 500 gas units.
  • Exception throwing. 50 gas units per exception.
  • Exiting the block costs 5 gas units per implicit RET. Jumping to the first link costs 10 gas units - implicit JUMP.
  • Moving to a new continuation with transferring parameters costs gas if there are more then 32 parameters. It costs N-32 gas, where N is the number of parameters.
  • Tuple gas price. 1 gas unit for every tuple element.

Note: that the most expensive operations are dictionary read/write operations. Dictionaries are stored in the form of trees of cells, where each cell can only be linked to four others. As result, these trees can grow quite large, depending on the data that needs to be stored. To read data in any cell, all its parent cells need to be read first, at the price of 100 gas per cell, and to write data in a cell, similarly all its parent cells need to be (re)created at the price of 500 gas per cell.

Global gas limits

Global gas limits are values stored in the masterchain configuration contract. Global values are standard and do not change at contract deployment. Only validator consensus can modify them.

The values currently used can always be reviewed on ever.live in the latest key block details (example FIXME broken link). p20 config parameter values are used for masterchain and p21 values are used for workchain.

These is the list of official TVM primitives used for gas-related operations:

  • F800ACCEPT, sets current gas limit gl to its maximal allowed value gm, and resets the gas credit gc to zero, decreasing the value of gr by gc in the process. In other words, the current smart contract agrees to buy some gas to finish the current transaction. This action is required to process external messages, which bring no value (hence no gas) with themselves.
  • F801SETGASLIMIT (g – ), sets current gas limit gl to the minimum of g and gm, and resets the gas credit gc to zero. If the gas consumed so far (including the present instruction) exceeds the resulting value of gl, an (unhandled) out of gas exception is thrown before setting new gas limits. Notice that SETGASLIMIT with an argument g ≥ 2 63 − 1 is equivalent to ACCEPT.
  • F802BUYGAS (x – ), computes the amount of gas that can be bought for x nanotokens, and sets gl accordingly in the same way as SETGASLIMIT.
  • F804GRAMTOGAS (x – g), computes the amount of gas that can be bought for x nanotokens. If x is negative, returns 0. If g exceeds 2 63−1, it is replaced with this value.
  • F805GASTOGRAM (g – x), computes the price of g gas in nanotokens.
  • F806–F80F — Reserved for gas-related primitives. These are yet to be released.

Note: F802, F804, F805 are not implemented in Telegram TON node.

In Evernode, the general gas formula is the same as specified by TON specifications. Overall, Evernode operate in compliance with the specification.

For every executed primitive, the amount of gas is added to the virtual machine according to the specification formula. Gas value for every primitive is based on gr.

Gas initialization types

1. Calling contract from another contract

An internal message with a balance value is received. In this case, the following formulas are applied to determine limits:

gm = MIN(account balance / gas price, global_gas_limit)
gl = MIN(message value / gas price, global_gas_limit)
gc = 0
gr = gc + gl

By default, gas costs are allocated to the caller contract that triggers the transaction with a message. Accepting is also available for internal contracts. If ACCEPT is not called, gas is taken from the caller contract according to the message value. In other words, the message value defines the current limit. The message value determines the starting TVM gas limit.

So, to put it plain, if ACCEPT is not called, the message pays, if ACCEPT is used, additional gas can be bought by the target contract. This approach enables flexible contract design where either total gas is paid by the caller contract (but in this case it has to have enough gas at any moment of time) or the target contract also incurs costs.

2. Offchain contract call

External messages do not carry balance values. In this case, the values are calculated according to the following formulas:

gm = MIN(account balance / gas price, global_gas_limit)
gl = 0
gc = MIN(gm, global_gas_credit)
gr = gc + gl

As external messages have no gas value, gas is credited to execute it. Target contracts have to cover costs by calling Accept to buy gas.

If a contract returns an exception before the credit is given, no gas fee applies.

As the public code for node has just been released this documentation is likely to be updated.

Managing Gas in Solidity Some Theory Anyone can send external message to your contract. When a message arrives, the contract initial gas limit is equal to 10,000 units of credit gas that should be bought later by the ACCEPT TVM primitive. Otherwise when credit gas falls to zero, the TVM throws the out of gas exception. The contract is supposed to spend these 10,000 units of 'free' gas to check the body of an inbound message tp make sure that it is valid and can be processed by contract successfully.

The idea of credit gas allowance is that as long as it is beyond zero, any exception thrown by contract prevents all further gas charges. But once the contract accepts a message, all gas consumed by contract is converted to gas fees regardless of whether a transaction is aborted or not.

ACCEPT is useful in internal messages too. When another contract sends an internal message to your contract, initial gas limit is equal to an inbound message value divided by the gas_price or global gas limit, if it is smaller. If this value is not enough to finish execution, the contract then can increase its gas limit by calling ACCEPT or SETGASLIMIT primitive. The ACCEPT primitive increases the limit to the value of its balance divided by the gas_price, and the SETGASLIMIT primitive sets the current gas limit to the value popped from the TVM stack (the value cannot be bigger than the gm limit).

With the ACCEPT command a contract can choose whether gas for its execution is paid by the caller contract or by the contract itself.

Implementation In EverX the ACCEPT primitive is implemented in Solidity as a private function called by public functions.

Find below actual usage examples. All can be compiled using EverX Solidity compiler.

Accept gas inside function To avoid gas payment when the foo function is called by another contract, we can use the following code:

Remember that the caller contract should attach enough tokens to its message to cover all gas that will be spend by foo function.

Accept gas inside modifier

contract AcceptExample2 {
uint _sum = 0;

modifier AlwaysAccept() {
tvm.accept();
_;
}

function foo(uint a, uint b) AlwaysAccept() public {
_sum = a + b;
}
}

Important: modifier is called before arguments are deserialized from inbound message body. In the example above AlwaysAccept() will be called before a and b are decoded.