Smart Contract Security Class (No.002)

TRON DAO
4 min readMay 27, 2019

--

In this class, we will use the RichBank contract of the TRON-Rich team to talk about the characteristics of the TRON smart contract fallback function and how to effectively avoid reentry attacks. At the same time, everyone is welcome to follow TRON official twitter, and submit your contract code.

Old rules, before starting the lecture, first verify this contract is truly open-source through https://troneye.com (hereinafter referred to as TRON-Eye). TRON-Eye is a TRON verification platform from the community. The previous class gave a detailed introduction to the TRON-Eye verification platform. The source code for the TRON-Rich team’s TeoSMNi7WvVHXe2NxxDw2AeLhiwLJXxQYL RichBank contract is shown below.

Figure 1 RichBank contract on TRON-Eye

The RichBank contract is a ROI contract, and most of the logic is similar to the UsdtBank introduced in the previous issue. There are two details to focus on here.

  1. Fallback function.

Figure 2 RichBank’s fallback function

Figure 2 shows the fallback function of RichBank, in which it records the transfer from an address or a withdrawal request for that address. The target contract’s fallback function is executed when a non-existing function is called through the GRPC interface, or when a function that does not exist is directly called in solidity, or when the TRX is directly transfer() or send() to a contract address in Solidity. This feature is used by many developers to record user transfers.

However, there are two points that need to be attention.

The first, the energy requirement of the fallback function. Due to the transfer() and send() functions, only 2300 Energy can be included, and for a 32-byte data read (SSLOAD instruction), modification (SSTORE instruction), and creation (SSTORE instruction), 400, 5000, and 20000 Energy are used, respectively. Therefore, 2300 Energy is not enough to create and modify data. That is, if you’ve wrote an fallback function that need more than 2,300, it will cause the transfer() and send() to the address to fail in solidity.

The second, TRON relative to Ethereum, also provides a “free” ordinary transfer transaction, a transfercontract type of transaction in GRPC. The transaction only consumes bandwidth and does not consume Energy. Since the transaction does not consume Energy, the contract’s fallback function cannot be triggered. That is, a normal TRX transfer to the contract address directly via tronscan does not trigger the fallback function.

The above are all two issues that must be known for all contracts that rely on the fallback function to record all user transfers. They determine that all transfer transactions that need to be recorded must use the triggersmartcontract method or send TRX in another contract. The first of these is Solidity’s basis for re-entry attacks, which will not change in the foreseeable future; the second is the TRON “free transfer” feature, we are also discussing at TIP37 (link: https://github.com/ Tronprotocol/TIPs/issues/37) whether to stop supporting the transfercontract type of transaction for the contract address, and welcome to participate in the discussion.

2. How to effectively avoid re-entry attacks

In Solidity, calling a function at another address or transfer TRX to a contract address will pass its own address as the msg.sender. At this point, if the energy passed is enough, it may be attacked by the other party.

The easiest and most rude way to prevent reentry attacks is to disable the operation of contract addresses in solidity. This can be basically achieved by the require(msg.sender == tx.origin) and extcodesize() > 0 mentioned in the previous issue. RichBank uses msg.sender == tx.origin , but leaves gaps in two places. They are as follows:

function grant(address addr, uint256 _planId) public whenNotPaused isHuman payable {

uint256 grantorUid = address2UID[msg.sender];

bool isAutoAddReferrer = true;

uint256 referrerCode = 0;

if (grantorUid != 0 && isAutoAddReferrer) {

referrerCode = grantorUid;

}

if (_invest(addr,_planId,referrerCode,msg.value)) {

emit onGrant(msg.sender, addr, msg.value);

}

}

This code only checks whether the caller is a contract, and does not test whether the incoming addr parameter is a contract. At the same time, this problem is more serious for the fallback function, which does not even verify if the caller is a contract. Both of these are places where the contract can be improved.

function() external payable {

if (msg.value == 0) {

withdraw();

} else {

invest(0, 0); //default to buy plan 0, no referrer

}

}

However, allowing other contracts to participate does not necessarily constitute a reentry attack. As mentioned earlier, calling transfer()/sender() on the contract address in Solidity will only pass through 2300 Energy, which is not enough to launch a reentry attack. So be sure to avoid calling the unknown contract address’s function or send TRX by .value().call(). Because call() can pass in energy that is much larger than 2300.

This class will be mentioned here. Thanks to TRON-Eye for providing the contract verification tool and the TRONRich team for the RichBank contract.

Through our review, the TRON-Rich team’s RichBank contract found no obvious security issues. More information about them can be found directly on their website https://tronrich.me and https://troneye.com.

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

--

--

TRON DAO
TRON DAO

Written by TRON DAO

The official Medium of TRON DAO.

No responses yet