Smart Contract Security Class (No. 003)

In this class, we will use the two contracts from TRON-Eye to talk about the characteristics of the this.balance in smart contracts on TRON. At the same time, everyone is welcome to follow @tron official twitter, and submit your contract code.

The following is the contract code from https://troneye.com (hereinafter referred to as TRON-Eye). TRON-Eye is a TRON verification platform from the community. The previous classes have introduced the TRON-Eye verification platform in detail. Figure 1 shows the contract for this issue, MileStoneAlpha (contract address: TVDPg45gS1UYi4GMhhcjsiVKTMBUveVDbM).

Figure 1 MileStoneAlpha contract on TRON-Eye

The MileStoneAlpha contract represents a simple game where players can send 500 trx to the contract, hoping to be the first player to reach one of three milestones. When the game is over, the first person to reach the milestone can get part of the contract trx. When the final milestone (10000 trx) is reached, the game ends and the user can apply for a reward.

  1. Fallback function.

Figure 2 Code snippet for MileStoneAlpha

The problem with the MileStoneAlpha contract comes from the incorrect use of this.balance in line [16] and the associated line [20] (and the following lines [23] and [26]). An attacker can forcedly send a small amount of trx, such as 1 trx, to contract in several ways to prevent future players from reaching a milestone. Since all legitimate players can only send 500 trx increments and the contract receives 1 trx, the contract’s this.balance is no longer divisible by 500, which prevents all conditions of the [20], [23], and [26] lines from being true. It is possible to permanently lock all the rewards in the contract. This is because the claimReward() function always reverts because the require() in the [34] line fails.

First we noticed that MileStoneAlpha does not provide a payable fallback() function. Then in other contracts it is not possible to transfer()/send() to this contract address any trx. However, there are currently 3 ways for TRON (two existing and one potential) to forcedly send trx to a contract. they are:

1. Simply transfer the contract address directly. TRON is relative to Ethereum, supporting a “free” simple transfer that does not consume energy. Since this operation consumes only bandwidth, it does not consume energy, it does not execute the fallback() function. In other words, you can force the contract trx.

2. Take advantage of the self-destructive nature of the contract. Any contract can implement the selfdestruct(address) function, which removes all bytecodes from the contract address and sends all trx stored there to the address specified by the argument. If this specified address is also a contract, no functions will be called. Therefore, using the selfdestruct() function can forcedly send any trx to any target contract, including any contract without any payable function, regardless of any code present in the target contract.

3. [Potential Way] TRON’s address is generated by sha3omit12 (transactionId, nonce). In theory, the attacker cannot predict the contract address before the contract is deployed. However, TRON will soon support create2 instruction to help build state channels on TRON main-net. The nature of the create2 determines that its address can be predicted in advance before the contract is deployed. Then, the attacker can first transfer some trx to the contract address, and then wait for the contract to be deployed (it sounds that the contract creator can use this feature to do honeypot attacks).

How to prevent

This problem is caused by the incorrect use of this.balance. Then, the best way to prevent it is that the contract logic should avoid relying on the exact value of the contract balance because it can be manipulated artificially. If you apply a logic statement based on the this.balance function, be sure to consider the unpredictable trx coming in.

Here is the contract to fix this problem, MileStoneGame (contract address: TBw24TjBjp5fQi5ezghyi6SQJfZsaRcJQR).

Figure 3 MileStoneGame contract on TRON-Eye

Here, we create a new variable, depositedWei, which tracks the amount of trx the user has invested in pay(), and this is the variable we used to execute the requirements and tests. Please note that we no longer use this.balance directly.

This class will be end here. Thanks to TRON-Eye for providing the contract verification tool. More information about them can be found directly on their website https://troneye.com.

We continue to call for source code for follow-up classes, and we welcome your official twitter submissions.

The official Medium of Tron Foundation. Learn more: https://tron.network