With the rapid development of blockchain technology, the TON ecosystem has gradually emerged and attracted the attention of a large number of developers and investors. However, the security issues that come with it cannot be ignored. In order to ensure the security and reliability of the TON ecosystem, professional security audits have become an indispensable part. This article will explore in depth how Beosin conducts a comprehensive security audit of the TON ecosystem to protect its entire ecological security.
Permission check
Since the TON blockchain has achieved the separation of ledger and logic to a certain extent, the call of TON contract is usually more complicated than that of Ethereum contract. Taking Jetton wallet as an example, a Jetton Master contract manages multiple user wallets, and each user's wallet contract records their respective balances. When performing coin minting operations, it usually involves the call of multiple contracts, so special attention should be paid to the permission verification between contracts.
Here are two examples:
The first is when the Master contract calls the Wallet contract. Since one Master contract corresponds to multiple Wallet contracts, the Master address can be used directly in the Wallet contract for judgment. For example, the sender_address is required to be equal to the master_address, as shown in the following code:
throw_unless(error::unauthorized_transfer, equal_slice_bits(master_address, sender_address));
The second is the case where the Wallet contract calls the Master contract. Since one Master contract corresponds to multiple Wallet contracts, the above method cannot be used directly for authentication. Therefore, when the Wallet contract calls the Master contract, the Wallet contract address is calculated through from_address, my_address and jetton_wallet_code, and then it is determined whether sender_address is equal to the calculated Wallet contract address.
throw_unless(error::unauthorized_burn_request, equal_slice_bits(calc_user_wallet(from_address, my_address(), jetton_wallet_code), sender_address) );
overflow
In the Func language of the TON blockchain, since it only supports int types, it is important to consider this during the audit process. Through auditing, we found that most of the code is checked after the transfer, so we recommend checking before the transfer. Below is a simple example we wrote.
() send_tokens (slice in_msg_body, slice sender_address, int msg_value, int fwd_fee) impure inline_ref {int query_id = in_msg_body~load_query_id();int jetton_amount = in_msg_body~load_coins();slice to_owner_address = in_msg_body~load_msg_addr();force_chain(to_owner_address); (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data();throw_unless(error::not_enough_jettons, balance >= jetton_amount);throw_unless(error::not_enough_jettons, jetton_amount >= 0);balance -= jetton_amount;
Of course, you can also use store_coins in deserialization to save amount. store_coins requires that the input amount must be a positive number, which can also effectively prevent overflow.
Safety of concurrent message calls and locks
Concurrent message calls are supported on the TON blockchain. Concurrent calls refer to the existence of multiple users operating on the ledger at the same time, without being restricted by the order of priority. However, in some cases, problems may arise if multiple users operate on the ledger at the same time. At this time, a locking mechanism is required to ensure that the user calls are in order.
The following is a brief demonstration of the process of concurrent message calls and lock mechanism prevention in the TON blockchain:
1. User A and User B send messages to the Master Contract respectively.
2. The Master Contract manages multiple wallet contracts (Wallet Contract A and Wallet Contract B) and contains locking mechanism logic to ensure orderliness.
3. Wallet Contract A and Wallet Contract B process transactions respectively.
The arrows indicate the flow of messages and calls, and the locking mechanism implemented in the Master Contract ensures safe concurrent operations.
Cell Data Analysis
In the TON blockchain, Cell is a flexible and efficient tree-like data structure used to store and transmit data. It is the basic unit for building and parsing TON blockchain data.
Therefore, special attention should be paid to the correctness of Cell data parsing during auditing. In the Func language, parsing of Cell data is performed sequentially.
Common parsing operations and functions include: read bits (Bits) operations, such as load_bits(n) read n bits of data from a slice and skip_bits(n) skip n bits of data; read integers (Integers) operations, such as load_uint(n) read n-bit unsigned integers and load_int(n) read n-bit signed integers; read bytes (Bytes) operations, such as load_bytes(n) read n bytes from a slice; read references (References) operations, such as load_ref() read a reference and return a new Cell object; read addresses (Address) operations, such as load_msg_addr() read a message address; read coins (Coins) operations, such as load_coins() read a token value. In addition, it also includes Slice operations, such as slice_empty?() check whether the slice is empty and begin_parse() create a new slice for parsing the referenced Cell.
Here is an example:
(int, slice, int) parse_header(cell message) inline_ref {slice cs = message.begin_parse();int flags = cs~load_uint(4);slice sender_address = cs~load_msg_addr();cs~load_msg_addr();cs~load_coins();cs~skip_bits(1);cs~load_coins();int fwd_fee = muldiv(cs~load_coins(), 3, 2);return (flags, sender_address, fwd_fee);}
Start parsing: Call the message.begin_parse() method to create a slice object cs for parsing.
Read flags: Use cs~load_uint(4) to read a 4-bit unsigned integer and store it in the flags variable.
Read sender address: Use cs~load_msg_addr() to read the address of the message sender and store it in the sender_address variable.
Skip the destination address: call cs~load_msg_addr() again to read but ignore the destination address.
Skip message value: Use cs~load_coins() to read but ignore the message value.
Skip extra currency collection flag: Use cs~skip_bits(1) to skip 1 bit of extra currency collection flag.
Skip IHR Fee: Use cs~load_coins() to read but ignore IHR fee.
Read and calculate the forwarding fee: Use cs~load_coins() to read the coin value, and then calculate the forwarding fee fwd_fee by muldiv(cs~load_coins(), 3, 2).
Cost Recovery
In the TON blockchain, "excesses" refer to unused fees generated during message processing. When a user sends a transaction, they reserve a certain amount of gas fees to pay for computing resources and possible storage fees. If the actual fees consumed are lower than the reserved amount, the unused portion becomes excesses. These excesses may come from unused gas fees or storage fees that are lower than the reserved amount. During the audit process, special attention needs to be paid to the handling of excesses to ensure that the excess fees can be correctly refunded to users.
Message Fee
The handling fee in Ton depends on many factors, and the final handling fee is composed of multiple different transaction fees. During the audit process, special attention should be paid to the accuracy of the handling fee calculation to avoid message failure due to insufficient gas.
transaction_fee = storage_fees + in_fwd_fees + computation_fees + action_fees + out_fwd_fees Name Description Example storage_fees The amount paid for storing smart contracts on the blockchain TON wallet is also a smart contract, and storage fees are paid every time a transaction is received or sent in_fwd_fees The fee for importing messages from outside the blockchain. Every time a transaction is made, it must be transmitted to the validator who will process it Every transaction made by each wallet application (such as Tonkeeper) needs to be distributed among the verification nodes first, paying this fee computation_fees The amount paid for executing code in the virtual machine. The larger the code, the more fees must be paid. Every time a transaction is sent using a wallet (i.e. a smart contract), the code of the wallet contract is executed and paid for. action_fees The fee charged by the smart contract for sending external messages. Smart contracts need to pay for this when calling each other. out_fwd_fees The fee for sending messages from the TON blockchain to external services (e.g., logs) and external blockchains. It is not currently used because it has not yet been implemented. Therefore, it is currently equal to 0.
The following are some common fees:
The average cost of sending any amount of TON is 0.0055 TON. The average cost of sending any amount of custom Jettons is 0.037 TON. The average cost of minting an NFT is 0.08 TON. The cost of storing 1 MB of data on TON for a year is 6.01 TON.
For detailed calculation formulas of each fee, please refer to:
https://docs.ton.org/mandarin/develop/smart-contracts/fee-calculation
Consider the case where a message fails
In TON's function calls, one operation may involve the sending of multiple messages. When a message fails, the results of previously executed messages will not be affected. If the data updated by the previous message is not rolled back, data inconsistency problems may occur. For example, in a mortgage operation, the user first transfers funds to the contract, but due to the failure of subsequent messages and the code not considering the situation of message failure, the funds are not returned to the user. Unlike EVM, TON's function calls will not automatically roll back to the initial state when they fail, which requires developers to manually handle the rollback logic to ensure data consistency.
Data import order
In the TON environment, due to the asynchronous nature of message transmission, if the order of data import is not considered, this will lead to data inconsistency. For example, when a user performs a redemption operation, if other users have performed similar asset operations before, new asset address data may be imported. If the relevant address information is not updated in time when the redemption is performed, the redeemed assets may not match the actual situation.
News rebound
In TON, handling message bounces is an important task, because message delivery failures can affect the normal operation and business logic of smart contracts. The handling of when to bounce and when not to bounce needs to be carefully audited based on the current business logic.
Almost all internal messages sent between smart contracts should be bounceable, i.e. their "bounce" bit should be set. All smart contracts should check the "bounced" flag of all inbound messages and either accept them silently (by terminating immediately with exit code 0) or perform some special processing to detect which outbound query failed. Queries contained in the body of a bounce message should never be executed.
But in some cases, non-bounce internal messages must be used. For example, new accounts cannot be created without sending at least one non-bounce internal message to them. Unless this message contains a StateInit with the code and data of the new smart contract, it does not make sense to have a non-empty body in a non-bounce internal message. Non-bounce messages are designed to avoid resource waste and processing complexity in certain situations.
As an open and decentralized platform, the TON ecosystem has attracted a large number of developers and users. However, its complex technical architecture and wide range of application scenarios also bring potential security risks.
Beosin has many successful cases in the TON ecosystem. It has previously conducted a detailed security audit on Aqua Protocol, the leading Stablecoins project in the TON ecosystem. The audit content covers the security of smart contract code, the correctness of business implementation logic, contract code gas optimization, potential vulnerability discovery and vulnerability repair, and other aspects.
After multiple rounds of rigorous audits and reviews, Beosin confirmed that Aqua Protocol's code implementation is safe and reliable, all security vulnerabilities have been fixed, and the business logic implementation matches the design documentation. At the same time, the Aqua Protocol team actively responded to potential risks raised during the audit process, quickly repaired and optimized, and ensured that the protocol meets the highest security standards in the industry.
As blockchain technology continues to develop, security issues have become an important factor restricting its development. Beosin hopes that through professional security audits, it can not only help project parties discover and fix potential security issues, but also provide strong support for the healthy development of the TON ecosystem. In the future, as the TON ecosystem continues to grow, Beosin will continue to leverage its professional advantages to provide it with more comprehensive and in-depth security protection.