Smart Contract Security Class (No.001)

TRON DAO
4 min readMay 12, 2019

With the continuous development of the DApp ecosystem, the number of DApp developers and users has grown rapidly and the economic benefits have accumulated rapidly. Increasing the anti-attack capability of smart contracts is becoming an important consideration for DApp development. So, TRON is calling for DApps’ smart contract source code from the community, trying to explain their code with the safety details which need to pay. Go to the appendix for more source collection methods.

This class we collect the Bank contract from the TRON-Rich team (website, https://tronrich.me/rich). The first thing before analyzing is to verify that it is the real source through the contract verification platform. Next, we will use a small section to analyze the website https://troneye.com (referred as TRON-Eye) which is developed by the communities to select the contract verification platform.

A compiled bytecode of Solidity contract is composed of executable bytecode and meta-hash. The same contract source code when compiled multiple times in the same compilation environment, will generate the same bytecode. The correct method of contract verification should be compared to the bytecode to verify that the source code is exactly the same as the chain contract.

Figure 1 TRON-Eye source submission

TRON-Eye elaborated on its verification method, and also supported users to compile bytecodes and compare with on-chain bytecode on the Reveal page to improve their credibility. Therefore, we selected TRON-Eye as our verification platform for this class to verify whether a contract is truly open source.

Figure 2 TRON-Eye Reveal page

Figure 2 shows the Bank contract code from the TRON-Rich. Next, we will explain the source code in detail from a security perspective..

1. If not necessary, should prohibit call from other contracts.

Allowing to be called by other contracts will result in an easy fallback attack, especially a bet game that returns results immediately. Attackers can call the target contract in their contract function. If the target contract returns the result immediately, when the attack contract finds that the returned result is not good, it reverts and rolls back the transaction. Thereby achieving “only win without losing.”

/*

* only human is allowed to call this contract

*/

modifier isHuman() {

require((bytes32(msg.sender)) == (bytes32(tx.origin)));

_;

}

Bank uses the above code to determine whether it is a contract. The principle is that if it is called by contract, msg.sender is the caller contract’s address, but tx.origin is the caller self’s address. Of course, this code first transfers address to bytes32, which wastes energy. So we recommend to use msg.sender == tx.origin directly.UsdtBank.

function invest(uint256 _referrerCode, uint256 _planId, uint256 _value) public whenNotPaused isHuman {

if (_invest(msg.sender, _planId, _referrerCode, _value)) {

emit onInvest(msg.sender, _value);

}

}

2. Determine whether an address is a contract address

The following is how Bank uses this modifier. This modifier is only suitable for limiting the caller to be a normal user. Then if you need to determine that an incoming address parameter is a person, not a contract, you need to use another method.

function isContract(address account) internal view returns (bool) {

uint256 size;

assembly { size := extcodesize(account) }

return size > 0;

}

Q: So why is the isHuman() modifier use a different way?

A: This is because it is not accurate to determine whether an address is a contract address by extcodesize. This value is always 0 when reading the extcodesize in the constructor of another contract.

More: TRON has submitted a TIP for adding address.type, which can intuitively and accurately determine an address type. Welcome to participate in the TIP discussion.

Small conclusion: In order to completely limit the caller in the contract, and the address parameter in the contract is Human, the best way is to combine isHuman() modifier and isContract().

3. How to handle the transfer of TRC20 in a game contract

An important reason for choosing Bank is that it supports direct betting with USDT. Analyzing it will help promote the correct posture of using the TRC20 betting game contract.

The following codes related to tolen betting are as follows:

ITRC20 public Addr_;

function setUsdtAddr(address _usdtAddr) public onlyOwner {

require(address(usdtAddr_) == address(0x00));

require(address(_usdtAddr) != address(0x00));

usdtAddr_ = ITRC20(_usdtAddr);

}

The above code indicates that Address is only allowed to be set once when it is initialized .

function _invest(address _addr, uint256 _planId, uint256 _referrerCode, uint256 _amount)

private

notContract(_addr)

returns (bool)

{

usdtAddr_.transferFrom(_addr, address(this), _amount);

….

}

The biggest difference between TRC20 token, TRX and TRC10 token is that the balance of TRX and TRC10 is stored in the address’ account, and the balance of TRC20 token is stored in TRC20 contract. Calling the transfer function of the TRC20 contract directly, although it can transfer its balance to another address, in fact only the modification of the balance field occurs in the TRC20 contract. So, with a standard TRC20 bet, you must use the Approve and TransferFrom two-step approach. Although this might result in the user signing twice.

This class will be mentioned here first. Thanks to TRON-Eye for providing the contract verification tool and the Bank contract provided by the TRONRich team. More information about them can be found directly on their website https://tronrich.me/rich and https://troneye.com.

We continue to call for sample source code for follow-up classes. Follow our official twitter to submit your source code if you want to participate.

--

--