TRON-Rich 團隊的 UsdtBank合約

買賣虛擬貨幣
隨著波場 DApp 生態的不斷髮展, DApp開發者和使用者的數量急速增長,經濟利益的迅速累積,提高智慧合約的防攻擊能力,越來越成DApp 開發的一個重要考量。因此,波場面向社羣,徵集DApp 的開原始碼,結合其合約原始碼,以實戰的方式,講解波場智慧合約開發時,需要注意的一些安全細節。更多原始碼徵集方式請參見附錄。本期小課堂徵集到的是 TRON-Rich 團隊的 UsdtBank合約。在分析合約之前的首要事情,就是透過合約驗證平臺,驗證其為真的開源合約。接下來先用小段篇幅對社羣的https://troneye.com (以下簡稱 TRON-Eye)進行解析,以選定合約驗證平臺。

合約驗證的原理在於,Solidity 合約編譯後的 bytecode 由可執行bytecode 以及meta-hash兩部分組成,同一份合約原始碼在相同編譯環境下多次編譯,產生的 bytecode 相同,正確的合約驗證方法,應該比對bytecode,從而驗證原始碼是否和鏈上合約完全一致。

TRON-Eye詳細闡述了其驗證思路,同時還在合約原始碼展示頁支援使用者自行編譯bytecode並比對,提高了公信力。因此,我們選定 TRON-Eye 作為小課堂的驗證平臺,校驗合約是否真正開源。

圖2所示的,即為本次待考察合約 ,TRON-Rich 團隊的UsdtBank合約程式碼。接下來就對其原始碼,進行安全形度的詳細解讀。

如非必要, 應該禁止被其他合約呼叫

允許被其他合約呼叫, 容易被髮起回退攻擊,尤其是即時返回結果的下注類遊戲。攻擊合約可以在其合約函式中呼叫目標合約,如果目標合約立即返回結果,當攻擊合約發現返回的結果對自己不利時,主動 revert,回退交易。從而實現“只贏不輸”。

  /*
    * only human is allowed to call this contract
    */
   modifier isHuman() {
       require((bytes32(msg.sender)) == (bytes32(tx.origin)));
       _;
   }

UsdtBank 採用了上述程式碼,判斷是否是合約,其原理就是,如果是合約呼叫的話,msg.sender 是外層合約地址,但是 tx.origin 是合約呼叫者。當然這段程式碼先將 address強轉 bytes32,浪費了能量,建議直接採用 msg.sender == tx.origin 即可。


function invest(uint256 _referrerCode, uint256 _planId, uint256 _value) public whenNotPaused isHuman {
       if (_invest(msg.sender, _planId, _referrerCode, _value)) {
           emit onInvest(msg.sender, _value);
       }
}

判斷一個地址是否是合約地址

下面是 UsdtBank 使用這個modifier 的方式,可以發現,這個 modifier 僅適合用來限制被呼叫方是普通使用者。那麼如果需要判斷某個傳入的address 引數是人,而不是合約,則需要使用另外一種方式。

function isContract(address account) internal view returns (bool) {
       uint256 size;
       assembly { size := extcodesize(account) }
       return size > 0;
   }

Q: 那麼為什麼 isHuman() 這個 modifier 不使用這種方式呢?
A: 這是因為,透過 extcodesize 方式判斷一個地址是否是合約地址,並不準確。當在其他合約的建構函式中讀取extcodesize時,這個值總是0.
More: 波場已經提交了一個關於增加 address.type 的 TIP,可以直觀準確的判斷一個地址型別,歡迎參與該 TIP 的討論。

小結論:要想完整限制合約中的呼叫者,以及合約中的地址引數為 Human,目前最好的方式,是結合前述的 isHuman() modifier 以及 isContract().

怎麼透過合約,處理TRC20的轉賬

本期選擇 UsdtBank 講解的一個重要原因是,UsdtBank 是一個支援 USDT參與投注的合約,有助於推廣使用TRC20投注遊戲合約的正確姿勢。
UsdtBank 和 USDT 投注相關的有如下一些程式碼:

ITRC20  public  usdtAddr_;

function setUsdtAddr(address _usdtAddr) public onlyOwner {
       require(address(usdtAddr_) == address(0x00));
       require(address(_usdtAddr) != address(0x00));
       usdtAddr_ = ITRC20(_usdtAddr);
   }

上述程式碼表示,usdtAddress 僅允許初始化的時候,設定一次(謝絕跑路 ^_^)。

function _invest(address _addr, uint256 _planId, uint256 _referrerCode, uint256 _amount)
private
notContract(_addr)
returns (bool)
       {
       usdtAddr_.transferFrom(_addr, address(this), _amount);
     ….
}

由於 TRC20 token 相對 TRX以及 TRC10 token 最大的區別在於,TRX 和 TRC10  的balance儲存於address 的 account 中,而 TRC20 token 的 balance儲存在 TRC20合約裡。直接呼叫 TRC20合約的 transfer 函式,雖然能夠將自己的餘額轉到另外一個地址名下,但事實上只是在 TRC20合約裡發生了兩者balance 欄位值的修改。所以採用 標準TRC20下注,必須使用 Approve 和 TransferFrom 兩步分開的方式。雖然這會導致使用者簽名兩次。

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;