回顧 3 個底層調用 call()
, delegatecall()
, callcode()
和 3 個轉幣函數 call.value()()
, send()
, transfer()
:
- call()
call()
用於 Solidity 進行外部調用,例如調用外部合約函數 <address>.call(bytes4(keccak("somefunc(params)"), params))
,外部調用 call()
返回一個 bool 值來表明外部調用成功與否:
- delegatecall()
除了 delegatecall()
會將外部代碼作直接作用於合約上下文以外,其他與 call()
一致,同樣也是隻能獲取一個 bool 值來表示調用成功或者失敗(發生異常)。
- callcode()
callcode()
其實是 delegatecall()
之前的一個版本,兩者都是將外部代碼加載到當前上下文中進行執行,但是在 msg.sender
和 msg.value
的指向上卻有差異。
例如 Alice 通過 callcode()
調用了 Bob 合約裏同時 delegatecall()
了 Wendy 合約中的函數,這麼說可能有點抽象,看下面的代碼:
如果還是不明白 callcode()
與 delegatecall()
的區別,可以將上述代碼在 remix-ide 裏測試一下,觀察兩種調用方式在 msg.sender
和 msg.value
上的差異。
- call.value()()
在合約中直接發起 TX 的函數之一(相當危險),
- send()
通過 send()
函數發送 Ether 失敗時直接返回 false;這裏需要注意的一點就是,send()
的目標如果是合約賬戶,則會嘗試調用它的 fallbcak() 函數,fallback() 函數中執行失敗,send()
同樣也只會返回 false。但由於只會提供 2300 Gas 給 fallback() 函數,所以可以防重入漏洞(惡意遞歸調用)。
- transfer()
transfer()
也可以發起 Ether 交易,但與 send()
不同的時,transfer()
是一個較爲安全的轉幣操作,當發送失敗時會自動回滾狀態,該函數調用沒有返回值。同樣的,如果 transfer()
的目標是合約賬戶,也會調用合約的 fallback() 函數,並且只會傳遞 2300 Gas 用於 fallback() 函數執行,可以防止重入漏洞(惡意遞歸調用)。
這裏以一個簡單的示例來說明嚴格驗證底層調用返回值的重要性:
function withdraw(uint256 _amount) public {
require(balances[msg.sender] >= _amount);
balances[msg.sender] -= _amount;
etherLeft -= _amount;
msg.sender.send(_amount); // 未驗證 send() 返回值,若 msg.sender 爲合約賬戶 fallback() 調用失敗,則 send() 返回 false
}
上面給出的提幣流程中使用 send()
函數進行轉賬,因爲這裏沒有驗證 send()
返回值,如果 msg.sender 爲合約賬戶 fallback() 調用失敗,則 send() 返回 false,最終導致賬戶餘額減少了,錢卻沒有拿到。
這裏先提供一些鏈接便於參考學習:
https://news.bitcoinworld.com/a/2599?locale=zh_HANT
https://github.com/ConsenSys/smart-contract-best-practices/blob/master/README-zh.md
後續會有更多有關細節方面的介紹......