TON (The Open Network) is a decentralized blockchain platform originally designed and developed by the Telegram team. It has gained attention since its launch. TON's goal is to provide a high-performance and scalable blockchain platform to support large-scale decentralized applications (DApps) and smart contracts. For the basics of TON, please refer to Getting to know TON: Accounts, Tokens, Transactions, and Asset Security.
It is worth noting that TON has a completely different architecture from other blockchains. In addition to using the FunC language to program TON's smart contracts, it also uses the more advanced Tact or the more basic Fift. These are highly original languages, so it is critical to ensure the security of smart contracts.
The SlowMist security team has integrated and absorbed the security development practices shared by the TON community, and combined with its own years of accumulated security audit experience, released "Toncoin Smart Contract Security Best Practices", aiming to help developers better understand the security risks of Toncoin smart contracts and provide practical solutions to reduce potential security threats.
Due to space limitations, this article only lists part of the "Toncoin Smart Contract Security Best Practices". You are welcome to Watch, Fork and Star on GitHub: https://github.com/slowmist/Toncoin-Smart-Contract-Security-Best-Practices.
Common pitfalls of Toncoin smart contracts
1. Missing the impure modifier
Severity: High
Description: An attacker could discover that the "authorize" function was not marked as "impure". The absence of this modifier would allow the compiler to skip calling the function if it had no return value or the return value was unused.
Attack scenario:
Recommendation: Make sure your function uses the "impure" modifier.
2. Incorrect use of modifying/non-modifying methods
Severity: High
Description: "udict_delete_get?" was incorrectly called with "." instead of "~", so the actual dictionary was not modified.
Attack scenario:
Recommendation: Always check if a method is a modifying/non-modifying method.
3. Improper use of signed/unsigned integers
Severity: High
Description: Voting power is stored as an integer in the message. Therefore, an attacker can send negative values during power transfer and obtain unlimited voting power.
Attack scenario:
Recommendation: In some cases, unsigned integers are safer because they throw errors when overflow occurs. Use signed integers only when you really need them.
4. Insecure Random Numbers
Severity: High
Description: The seed is derived from the logical time of the transaction. An attacker can win by brute-forcing the logical time in the current block (because the logical time is continuous within the boundary of a block).
Attack scenario:
Recommendation: Always randomize the seed before doing "rand()" and preferably never use on-chain randomness as validators can control or influence the seed.
5. Sending private data on-chain
Severity: High
Description: Remember, all data will be stored on the blockchain.
Attack scenario: The wallet is protected by a password, and its hash is stored in the contract data. However, the blockchain records everything - the password will appear in the transaction history.
Recommendation: Do not send private data on-chain.
6. Forgetting to check bounce messages
Severity: High
Description: Vault has no bounce handler or proxy message to the database when user sends "check" request. We can set "msg_addr_none" to the reward address in the database because "load_msg_address" allows this. We request Vault to check, and the database tries to parse "msg_addr_none" using "parse_std_addr", but the parse fails. The message is bounced from the database to Vault, and the operation is not "op_not_winner".
Attack scenario: Vault contains the following code in the database message handler:
Recommendation: always check returned messages, don't forget about errors raised by standard functions, make your conditions as strict as possible.
7. Risk of account destruction under competitive conditions
8. Avoid executing third-party code
Severity: High
Description: Developers have no way to safely execute third-party code in the contract because CATCH cannot handle the problem of insufficient gas, and the attacker can implement the attack by simply submitting any state of the contract and causing insufficient gas.
Attack scenario:
Recommendation: Avoid executing third-party code in your contracts.
9. Name Conflicts
Severity: Medium
Description: Func variables and functions may contain almost any legal characters.
Attack scenarios: "var++", "~bits", "foo-bar+baz" and the comma "," are all valid variable and function names.
Recommendation: When writing and checking Func code, you should use a linter tool.
10. Check the value of throw
Severity: Medium
Description: Every time TVM execution stops normally, it stops with exit code "0" or "1". Although it is done automatically, TVM execution can be directly interrupted in unexpected ways if "throw(0)" or "throw(1)" commands directly throw exit codes "0" and "1".
Attack scenario:
Recommendation: Do not use "0" or "1" as throw values.
11. Read/write data of the correct type
Severity: Medium
Description: Reading the value of an unexpected variable and calling a method on a data type that should not have such a method (or whose return value is not stored correctly) is an error and is not skipped as a "warning" or "notice", but results in unreachable code.
Attack scenarios: Keep in mind that storing an unexpected value might be ok, but reading it might cause problems. For example, for an integer variable, error code 5 (integer out of expected range) might be thrown.
Advice: Keep close track of what your code does and what values it might return. Remember, the compiler only cares about your code and its initial state.
12. Contract code can be updated
Severity: Medium
Description: TON fully implements the Actor model, which means that the code of the contract can be changed. The code can be permanently changed via the "SETCODE" TVM instruction, or by setting the TVM code register to a new unit value at runtime until the end of execution.
Attack scenario: An unethical developer could maliciously update the code to steal funds.
Recommendation: Be aware that contract code can be updated, ensure any updates follow security practices, and use mechanisms such as governance models or multi-signature approvals to make changes.
13. Transactions and Phases
Severity: Medium
Description: The computation phase executes the smart contract code and then performs actions (such as sending messages, modifying code, changing libraries, etc.). Unlike Ethereum-based blockchains, if you expect the message you send to fail, you will not see an exit code from the computation phase because the message is not executed in the computation phase, but in the later action phase.
Attack scenario: Unexpected behavior when messages fail during the operation phase, leading to incorrect assumptions about the state of the transaction.
Recommendation: Understand that each transaction consists of up to five phases: storage phase, credit phase, calculation phase, operation phase, and rebound phase.
14. Cannot pull data from other contracts
Severity: Medium
Description: Contracts on a blockchain can reside on different shards and be processed by different validators. Therefore, developers cannot pull data from other contracts on demand. Communication is asynchronous and occurs by sending messages.
Attack scenario:
Recommendation: Design contract logic around asynchronous messaging and avoid assumptions about synchronous data availability.
15. Two predefined method_ids
Severity: Medium
Description: There are two predefined method_ids: one for receiving messages from within the blockchain "(0)", usually named "recv_internal", and one for receiving messages from the outside world "(-1)", named "recv_external".
Attack scenario:
Recommendation: Use a method such as "force_chain(to_address)" to verify that the address is on the correct chain.
16. Use bounce messages
Severity: High
Description: The TON blockchain is asynchronous and messages do not have to arrive in order. Failed messages should be handled correctly.
Attack scenario:
Recommendation: Always use bounceable messages ("0x18") to properly handle message failures.
17. Replay Protection
Severity: High
Description: Implement replay protection for wallets (contracts that store user funds), either using sequence numbers ("seqno") to ensure messages are not processed duplicately, or using unique transaction identifiers with expiration.
Attack scenario:
Recommendation: Use a replay protection method like a sequence number or message unique identifier to prevent replay attacks.
18. Race Conditions in Messages
Severity: High
Description: Message cascades can be processed across multiple blocks, and an attacker could start a parallel stream, leading to a race condition.
Attack scenario: An attacker may exploit time differences to manipulate contract behavior.
Recommendation: Prevent race conditions by validating state at each step and not assuming consistent state across message flows.
19. Use the carry value pattern
Severity: High
Description: In token transfers (e.g. TON Jetton), balances should be transferred using the carry value mode. The sender deducts the balance and the receiver adds it back or bounces it.
Attack scenario: If not handled properly, Jetton balances can be manipulated.
Recommendation: Use carry value mode to ensure correct value transfer.
20. Be careful about refunding excess fuel costs
Severity: High
Description: If excess gas fees are not returned to the sender, funds may accumulate in the contract over time. In principle, this is not terrible, but it is a suboptimal approach. A function could be added to clear excess fees, but popular contracts like TON Jetton still return excess fee messages "op::excesses" to the sender.
21. Check function return value
Severity: High
Description: A function always returns a value or an error. If you ignore the return value check, it may lead to fatal logic errors.
Attack scenario:
Recommendation: Always check the return value of a function.
22. Check for fake Jetton tokens
Severity: High
Description: Jetton tokens consist of two parts: "jetton-minter" and "jetton-wallet". If the vault contract is not properly validated, an attacker could drain the funds in the vault by depositing fake tokens and withdrawing valuable tokens.
Attack scenario:
Recommendation: Check if the sender sent fake Jetton tokens by calculating the user's jetton wallet address.
Final Thoughts
For developers, following these best practices can effectively improve the security of smart contracts and reduce potential security risks. As blockchain technology is changing with each passing day, security is always the top priority. I hope this best practice can help more developers build safe and reliable smart contracts and promote the healthy development of blockchain technology.
Reference Links:
[1] https://dev.to/dvlkv/drawing-conclusions-from-ton-hack-challenge-1aep
[2] https://docs.ton.org/develop/smart-contracts/security/ton-hack-challenge-1
[3] https://docs.ton.org/learn/tvm-instructions/tvm-overview
[4] https://docs.ton.org/develop/smart-contracts/messages
[5] https://docs.ton.org/develop/smart-contracts/security/secure-programming
[6] https://docs.ton.org/develop/smart-contracts/security/things-to-focus
Author | Johan
Editor | Lisa
Layout | Liz