Solidity

Solidity 編譯器是否可以刪除外部呼叫?

  • December 5, 2021

我遇到了一種奇怪的情況,似乎 Solidity 編譯器(或優化器)正在刪除外部呼叫。

我正在使用 Hardhat 在分叉的主網上執行 ERC20 代幣轉移。在安全帽單元測試中,我檢查了在交易執行後令牌是否正確轉移。這是相關的程式碼片段:

// Solidity version 0.8.4

let SIG_TRANSFER := 0xa9059cbb00000000000000000000000000000000000000000000000000000000
let RECIPIENT_ADDR := 0xabc...
let TOKEN_ADDR := 0xdef...
let WITHDRAW_AMOUNT := 1000000000000000000

let free_mem_ptr := mload(0x40)
mstore(free_mem_ptr, SIG_TRANSFER) // transfer(address,uint256) function signature
mstore(add(free_mem_ptr, 0x4), RECIPIENT_ADDR) // recipient address
mstore(add(free_mem_ptr, 0x24), WITHDRAW_AMOUNT) // amount to transfer
let r := call(gas(), TOKEN_ADDR, 0x0, free_mem_ptr, 0x44, 0x0, 0x0)
if eq(r, 0x0) {
   revert(0, 0)
}

此程式碼按預期工作,當我執行它時,令牌會被轉移。revert()執行,表示call成功。然而有趣的是,如果我註釋掉revert, 即

...
if eq(r, 0x0) {
   //revert(0, 0)
}
...

交易仍然成功,但代幣餘額沒有改變。由於在第一次執行期間revert沒有被執行,因此call()必須仍然成功,因為它沒有以任何方式更改。

這裡發生了什麼?編譯器/優化器是否call因為我沒有使用返回值而刪除了我?同樣的事情也發生在pop

pop(call(gas(), TOKEN_ADDR, 0x0, free_mem_ptr, 0x44, 0x0, 0x0))

沒有什麼是不可能的,但是如果CALL您的範例中的實際被刪除,那將是編譯器中的錯誤。

我知道編譯器(我的意思是程式碼生成器)可能會跳過發出程式碼的唯一情況是,如果你有一個永遠不會被呼叫的內部函式。但是,刪除了整個功能,而不是單獨的指令。

至於優化器(可以修改已經發出的程式碼),它確實有一組內置註釋,其中一個是canBeRemoved但只有那些沒有副作用的才會得到這個註釋-CALL絕對不是其中之一。

您可以通過檢查--asm輸出來檢查指令是否實際被刪除(請參閱Analyzing the Compiler Output)。它是以人類可讀的彙編形式直接呈現字節碼,因此如果指令存在,它也必須存在於您編譯的合約中。

revert() 未執行,表示呼叫成功。

不幸的是,這並不總是正確的。在進行低級呼叫時需要注意一個重要的問題(來自Units and Globally Available Variables > Members of Address Types):

由於 EVM 認為對不存在的合約的呼叫總是成功的,Solidity 包括extcodesize在執行外部呼叫時使用操作碼的額外檢查。這確保了即將被呼叫的合約要麼實際存在(它包含程式碼),要麼引發異常。

.call()對地址而不是合約實例(即, .delegatecall(),.staticcall()和).send()進行操作的低級呼叫.transfer()不包括此檢查,這使得它們在 gas 方面更便宜,但也不太安全。

您是否 100% 確定目標地址確實存在合約?您說令牌已轉移,所以我想確實如此,但可能值得仔細檢查。您可以添加EXTCODESIZE到您的程式碼以驗證這一點。

引用自:https://ethereum.stackexchange.com/questions/114876