accept_message(), but semantic acceptance, i.e., no throw and no asset returns.
The reason for this requirement is that reverting the contract system state is usually not possible, because the Toncoin is already spent.
When a contract system’s correctness depends on successful execution of the remaining transaction trace, it must guarantee that an incoming message carries enough attached Toncoin to cover all fees. This article describes how to compute those fees.
Define variables for limits and initialize them with zero. We will set them to actual values afterwards.
Use descriptive names for the operation and the contract. It’s best to store them in a dedicated file with constants.
- Run tests covering all execution paths. Missing a path might hide the most expensive one.
- Extract resource consumption from the
send()method’s return value. The sections below describe ways to compute consumption of different kinds of resources. - Use
expect(extractedValue).toBeLessThanOrEqual(hardcodedConstant)to verify that the hardcoded limit was not exceeded.
Compute fees
There are two kinds of values: gas units and Toncoin. The price of contract execution is fixed in gas units. However, the price of the gas itself is determined by the blockchain configuration. Conversion of gas to Toncoin on-chain using currently set blockchain config parameters can be performed withGETGASFEE TVM opcode.
Forward fees
Forward fee is calculated with this formula:lumpPriceis the fixed value from config paid once for the message.msgSizeInCellsis the number of cells in the message.msgSizeInBitsis the number of bits in all the cells of the message.
cell.calculateSizeStrict() can be used to compute msgSizeInCells and msgSizeInBits. In TVM, it’s implemented as CDATASIZE instruction.
In Tolk, the formula above is implemented in calculateForwardFee(). In TVM, it’s implemented as GETFORWARDFEE instruction.
msg.send() with mode 1024. In TVM, it’s implemented as SENDRAWMSG. It will consume approximately the same amount of gas.
Optimized forward fee calculation
If the size of the outgoing message is bounded by the size of the incoming message, we can estimate the forward fee of an outgoing message to be no larger than the forward fee of the incoming message, that was already computed by TVM. Thus, we don’t have to calculate it again. Note that this estimation is correct only for a contract system in the same workchain, because gas prices depend on the workchain.Tolk
Complex forward fee calculation
Assume the contract receives a message with an unknown size and forwards it further adding fields with total ofa bits and b cells to the message, e.g., StateInit.
For this case, in Tolk, there is a function calculateForwardFeeWithoutLumpPrice(). In TVM, it’s implemented as GETFORWARDFEESIMPLE. This function does not take lumpPrice into account.
Tolk
Storage fees
We cannot predict storage fees that we have to pay for sending messages because it depends on how long the target contract didn’t pay storage fee. Storage fees differ in this way from forward and compute fees, as they should be handled both in receiver and internal contracts.Maintain a positive reserve
Always keep a minimum balance on all contracts in the system. Storage fees get deducted from this reserve. The reserve get replenished with each user interaction. Do not hardcode Toncoin values for fees. Instead, hardcode the maximum possible contract size in cells and bits. This approach affects code of internal contracts.Cover storage on demand
The order of phases depends on thebounce flag of an incoming message. If all messages in the protocol are unbounceable, then the storage phase comes after the credit phase. So, the contract’s storage fees are deducted from the joint balance of the contract and incoming message. In this case the pattern where the contract’s balance is zero and incoming messages cover storage fees can be applied.
It is impossible to know in advance what the storage fee due will be on the contract, so a threshold must be selected depending on the network configuration. It is a good practice to use freeze_due_limit as the threshold. Otherwise, the contract likely is already frozen and a transaction chain is likely to fail anyway.
This pattern can be generalized to both bounceable and unbounceable messages with myStorageDue() function that returns storage_fees_due
This approach affects code of internal contracts.
n unique contracts, then it won’t take more than n freeze limits to pay their storage fees. So, in the receiver contract, the check should be: