Smart contracts are extremely flexible, capable of both holding large quantities of tokens and running immutable logic based on previously deployed smart contract code. While this has created a vibrant and creative ecosystem of trustless, interconnected smart contracts, it is also the perfect ecosystem to attract attackers looking to profit by exploiting vulnerabilities in smart contracts and unexpected behavior in TRON network. Smart contract code usually cannot be changed to patch security flaws, assets that have been stolen from smart contracts are irrecoverable, and stolen assets are extremely difficult to track.
Before launching any code to Mainnet, it is important to take sufficient precaution to protect anything of value your smart contract is entrusted with. In this article, we will discuss a few specific attacks and best practice to ensure your contracts function correctly and securely.
# Smart Contract Development Process
Security starts with a proper design and development process. There are many things to keep in mind about the smart contract development process, but at least ensure the following:
All code stored in a version control system, such as git
All code modifications made via Pull Requests
All Pull Requests have at least one reviewer
A single command compiles, deploys, and runs a suite of tests against your code using a development tool, for example, tronbox
You have run your code through basic code analysis tools such as Mythril and Slither, ideally before each pull request is merged, comparing differences in output
Solidity does not emit ANY compiler warnings
Your code is well-documented
# Attacks And Vulnerabilities
Here are some common vulnerabilities:
## Re-entrancy
Re-entrancy is one of the largest and most significant security issue to consider when developing Smart Contracts. While the TVM cannot run multiple contracts at the same time, a contract calling a different contract pauses the calling contract's execution and memory state until the call returns, at which point execution proceeds normally. This pausing and re-starting can create a vulnerability known as "re-entrancy".
Here is a simple version of a contract that is vulnerable to re-entrancy:
To allow a user to withdraw TRX they have previously stored on the contract, Withdraw function will do the following in sequence:
Reads how much balance a user has
Sends them that balance amount in TRX
Resets their balance to 0, so they cannot withdraw their balance again.
If called from a regular external account (such as your own Tronlink account), this functions as expected: msg.sender.call.value() simply sends your account TRX. However, smart contracts can make calls as well. If a custom, malicious contract is the one calling withdraw(), msg.sender.call.value() will not only send amount of TRX, it will also implicitly call the contract to begin executing code. Imagine this malicious contract:
Calling Attacker.beginAttack() will start a cycle that looks something like:
Calling Attacker.beginAttack with 1 TRX will re-entrancy attack Victim, withdrawing more TRX than it provided. That is, Attacker takes TRX from other users' balances.
### How to deal with re-entrancy
By simply switching the order of the storage update and external call, we prevent the re-entrancy condition that enabled the attack. In the following example, the withdraw function first sets the stored balance information to 0, and then transfers TRX to avoid malicious code reentrancy attacks.
Any time you are sending TRX to an untrusted address or interacting with an unknown contract (such as calling transfer() of a user-provided token address), you open yourself up to the possibility of re-entrancy. By designing contracts that neither send TRX nor call untrusted contracts, you prevent the possibility of re-entrancy!
## More Attack Types
In addition to the above-mentioned re-entrancy attacks caused by smart contract coding, there are many other types of attacks, such as:
TRX send rejection
Integer overflow/underflow