以太坊虛擬機

訊息呼叫

訊息呼叫是一份合約在交易執行期間與另一份合約對話的方式。從使用者帳戶發起的交易是最外層的呼叫,但在它內部,合約能再發出更多呼叫——傳送價值、傳遞資料、觸發其他合約的程式碼——這些巢狀的呼叫就是訊息呼叫。它們是可組合性背後的機制:一個聚合器呼叫一個 DEX、再呼叫一個代幣,全在同一筆原子交易內完成。

主力操作碼是 CALL。它接收目標地址、要轉移的 ETH 數量(以 wei 計)、一段存放輸入資料的記憶體區、一段接收回傳資料的記憶體區,以及一個明確要轉發的 gas 額度。EVM 接著為被呼叫者開啟一個全新的執行框架:自己的堆疊、自己歸零的記憶體、自己的 calldata(你傳入的位元組)。被呼叫者結束時,CALL 在呼叫者的堆疊上推入單一個字——成功為 1、失敗為 0——並把任何回傳的位元組複製到指定的記憶體區。

有兩條規則一再讓人栽跟頭。其一,被呼叫者在自己的上下文中執行:在它內部,msg.sender 是發起呼叫的那份合約,msg.value 是該次呼叫轉發的 ETH,而非原交易的。其二,EVM 不能轉發全部剩餘 gas——依 EIP-150 的「63/64 規則」,一次呼叫至多轉發剩餘 gas 的 63/64,保留一小段,好讓呼叫者即使被呼叫者耗盡 gas 仍能反應(例如處理失敗)。這意味著深度巢狀的呼叫會以幾何級數損失可用的 gas。

關鍵在於,失敗的訊息呼叫本身並不會中止呼叫者——CALL 只是回傳 0。檢查這個回傳值、決定是否回滾,是呼叫者的責任;忽略它正是經典的「未檢查外部呼叫」臭蟲。同族的操作碼讓行為各有專長:DELEGATECALL 以呼叫者自己的儲存與身分執行被呼叫者的程式碼,STATICCALL 禁止任何狀態變更,而舊的 CALLCODE 已被棄用。

用 .transfer 或 .send 送 ETH 只轉發固定的 2,300 gas 津貼,夠發一個日誌但不夠做儲存寫入;這個曾被推薦的做法如今對耗 gas 的收款方可能失效,因此一般偏好使用經檢查的低階呼叫。