使用返回 bool 的原型呼叫不返回任何內容的函式時,是否存在未定義行為的風險?
根據ERC-20 Token Standard,函式
transfer
和transferFrom
應該返回一個布爾值,指示成功或失敗:interface IERC20Token { function transfer(address _to, uint256 _value) public returns (bool); function transferFrom(address _from, address _to, uint256 _value) public returns (bool); }
然而,有相當多的 ERC-20 Token 合約部署在主網上不符合這個要求,即沒有返回任何東西。
假設我使用此介面在此類合約的地址上呼叫這些函式:
IERC20Token token = IERC20Token(someAddress); token.transfer(someAccount, someAmount);
當然,因為我不確定它是否返回 a
bool
,所以我只是忽略了程式碼中的返回值。然而,編譯器可能會在堆棧上為這個返回值分配一個槽。
這可能會產生任何意想不到的行為嗎?
當然,我可以通過聲明這個介面來強制我的程式碼假設這個函式不返回任何東西:
interface INonStandardERC20Token { function transfer(address _to, uint256 _value) public; function transferFrom(address _from, address _to, uint256 _value) public; }
然後使用它:
INonStandardERC20Token token = INonStandardERC20Token(someAddress); token.transfer(someAccount, someAmount);
但問題是,我不知道這些介面中的哪一個是由部署在
someAddress
.所以我更擔心第二個範例中的意外行為,因為在這種情況下,編譯器肯定不會為返回值分配任何槽,但函式可能會返回一些東西。
知道上面給出的兩種方法中哪種方法是安全的嗎?
該
returndatasize
指令可能在這裡有用嗎?非常感謝你!
好的,剛剛發現這篇非常有用的文章正是針對這個特定問題的!
提議的解決方案確實使用了該
returndatasize
指令(這就是我找到它的方式,但話又說回來,我只是在標記這個問題時才意識到這個指令,所以我想我必須按順序完成編寫它的過程得到答案)。無論如何,該文章中的解決方案是作為圖像而不是可複制粘貼的程式碼給出的,所以我在這個 GitHub 儲存庫中對其進行了跟踪,如果有人覺得它有用的話。
更新:
我剛剛意識到我實際上並沒有回答哪種方法可以安全使用的問題。
於是我做了一個小實驗。
對於給定的契約:
pragma solidity 0.4.25; contract GoodToken { event Transfer(address indexed _from, address indexed _to, uint256 _value); function transfer(address _to, uint256 _value) public returns (bool) { emit Transfer(msg.sender, _to, _value); return true; } } contract BadToken { event Transfer(address indexed _from, address indexed _to, uint256 _value); function transfer(address _to, uint256 _value) public { emit Transfer(msg.sender, _to, _value); } } contract Caller { function transferGoodToken(address _token, address _to, uint256 _value) public { GoodToken(_token).transfer(_to, _value); } function transferBadToken(address _token, address _to, uint256 _value) public { BadToken(_token).transfer(_to, _value); } }
以下測試:
contract("test", function(accounts) { it("test", async function() { const goodToken = await artifacts.require("GoodToken").new(); const badToken = await artifacts.require("BadToken" ).new(); const caller = await artifacts.require("Caller" ).new(); console.log(); await test("cast good token to good token:", caller.transferGoodToken, goodToken); await test("cast bad token to good token:", caller.transferGoodToken, badToken ); await test("cast good token to bad token:", caller.transferBadToken , goodToken); await test("cast bad token to bad token:", caller.transferBadToken , badToken ); }); async function test(title, func, token) { try { await func(token.address, accounts[0], 0); console.log(title, "passed"); } catch (error) { console.log(title, error.message); } } });
印刷:
cast good token to good token: passed cast bad token to good token: VM Exception while processing transaction: revert cast good token to bad token: passed cast bad token to bad token: passed
因此
transfer
,使用返回的介面呼叫可能不返回任何內容的函式bool
肯定是一種風險(儘管這不是意外行為的風險,而只是失敗的風險)。另一種方法(呼叫
transfer
可能bool
使用不返回任何內容的介面返回的函式)似乎無需還原即可完成,但當然,這不是安全證明。