隨著波場 DApp 生態(tài)的不斷發(fā)展, DApp開發(fā)者和用戶的數(shù)量急速增長,經(jīng)濟(jì)利益的迅速累積,提高智能合約的防攻擊能力,越來越成DApp 開發(fā)的一個(gè)
隨著波場 DApp 生態(tài)的不斷發(fā)展, DApp開發(fā)者和用戶的數(shù)量急速增長,經(jīng)濟(jì)利益的迅速累積,提高智能合約的防攻擊能力,越來越成DApp 開發(fā)的一個(gè)重要考量。因此,波場面向社區(qū),征集DApp 的開源代碼,結(jié)合其合約源碼,以實(shí)戰(zhàn)的方式,講解波場智能合約開發(fā)時(shí),需要注意的一些安全細(xì)節(jié)。
本期小課堂征集到的是 TRON-Rich 團(tuán)隊(duì)的 UsdtBank合約。在分析合約之前的首要事情,就是通過合約驗(yàn)證平臺(tái),驗(yàn)證其為真的開源合約。接下來先用小段篇幅對社區(qū)的https://troneye.com (以下簡稱 TRON-Eye)進(jìn)行解析,以選定合約驗(yàn)證平臺(tái)。
合約驗(yàn)證的原理在于,Solidity 合約編譯后的 bytecode 由可執(zhí)行bytecode 以及meta-hash兩部分組成,同一份合約源碼在相同編譯環(huán)境下多次編譯,產(chǎn)生的 bytecode 相同,正確的合約驗(yàn)證方法,應(yīng)該比對bytecode,從而驗(yàn)證源碼是否和鏈上合約完全一致。
TRON-Eye詳細(xì)闡述了其驗(yàn)證思路,同時(shí)還在合約源碼展示頁支持用戶自行編譯bytecode并比對,提高了公信力。因此,我們選定 TRON-Eye 作為小課堂的驗(yàn)證平臺(tái),校驗(yàn)合約是否真正開源。
圖2所示的,即為本次待考察合約 ,TRON-Rich 團(tuán)隊(duì)的UsdtBank合約代碼。接下來就對其源碼,進(jìn)行安全角度的詳細(xì)解讀。
如非必要, 應(yīng)該禁止被其他合約調(diào)用
允許被其他合約調(diào)用, 容易被發(fā)起回退攻擊,尤其是即時(shí)返回結(jié)果的下注類游戲。攻擊合約可以在其合約函數(shù)中調(diào)用目標(biāo)合約,如果目標(biāo)合約立即返回結(jié)果,當(dāng)攻擊合約發(fā)現(xiàn)返回的結(jié)果對自己不利時(shí),主動(dòng) revert,回退交易。從而實(shí)現(xiàn)“只贏不輸”。
/*
* only human is allowed to call this contract
*/
modifier isHuman() {
require((bytes32(msg.sender)) == (bytes32(tx.origin)));
_;
}
UsdtBank 采用了上述代碼,判斷是否是合約,其原理就是,如果是合約調(diào)用的話,msg.sender 是外層合約地址,但是 tx.origin 是合約調(diào)用者。當(dāng)然這段代碼先將 address強(qiáng)轉(zhuǎn) bytes32,浪費(fèi)了能量,建議直接采用 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);
}
}
判斷一個(gè)地址是否是合約地址
下面是 UsdtBank 使用這個(gè)modifier 的方式,可以發(fā)現(xiàn),這個(gè) modifier 僅適合用來限制被調(diào)用方是普通用戶。那么如果需要判斷某個(gè)傳入的address 參數(shù)是人,而不是合約,則需要使用另外一種方式。
function isContract(address account) internal view returns (bool) {
uint256 size;
assembly { size := extcodesize(account) }
return size > 0;
}
Q: 那么為什么 isHuman() 這個(gè) modifier 不使用這種方式呢?
A: 這是因?yàn)?,通過 extcodesize 方式判斷一個(gè)地址是否是合約地址,并不準(zhǔn)確。當(dāng)在其他合約的構(gòu)造函數(shù)中讀取extcodesize時(shí),這個(gè)值總是0.
More: 波場已經(jīng)提交了一個(gè)關(guān)于增加 address.type 的 TIP,可以直觀準(zhǔn)確的判斷一個(gè)地址類型,歡迎參與該 TIP 的討論。
小結(jié)論:要想完整限制合約中的調(diào)用者,以及合約中的地址參數(shù)為 Human,目前最好的方式,是結(jié)合前述的 isHuman() modifier 以及 isContract().
怎么通過合約,處理TRC20的轉(zhuǎn)賬
本期選擇 UsdtBank 講解的一個(gè)重要原因是,UsdtBank 是一個(gè)支持 USDT參與投注的合約,有助于推廣使用TRC20投注游戲合約的正確姿勢。
UsdtBank 和 USDT 投注相關(guān)的有如下一些代碼:
ITRC20 public usdtAddr_;
function setUsdtAddr(address _usdtAddr) public onlyOwner {
require(address(usdtAddr_) == address(0x00));
require(address(_usdtAddr) != address(0x00));
usdtAddr_ = ITRC20(_usdtAddr);
}
上述代碼表示,usdtAddress 僅允許初始化的時(shí)候,設(shè)置一次(謝絕跑路 ^_^)。
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 最大的區(qū)別在于,TRX 和 TRC10 的balance存儲(chǔ)于address 的 account 中,而 TRC20 token 的 balance存儲(chǔ)在 TRC20合約里。直接調(diào)用 TRC20合約的 transfer 函數(shù),雖然能夠?qū)⒆约旱挠囝~轉(zhuǎn)到另外一個(gè)地址名下,但事實(shí)上只是在 TRC20合約里發(fā)生了兩者balance 字段值的修改。所以采用 標(biāo)準(zhǔn)TRC20下注,必須使用 Approve 和 TransferFrom 兩步分開的方式。雖然這會(huì)導(dǎo)致用戶簽名兩次。
關(guān)鍵詞: UsdtBank合約 回退攻擊 余額