本關是一個模擬的遊戲,要求玩家把遊戲中的國王寶座強佔,強制令任何人都不能搶回國王寶座來中止遊戲。
觀看合約代碼,問題在於fallback函數裏
king.transfer(msg.value);
這裏同樣是犯了常見的錯誤︰未有考慮呼叫者為另一智能合約的情況。
基本上只要king的地址為一智能合約,而該智能合約又並未定義fallback或是receive,transfer就會失敗。參考官方文檔︰
The receive function is executed on a call to the contract with empty calldata. This is the function that is executed on plain Ether transfers (e.g. via
.send()
or.transfer()
). If no such function exists, but a payable fallback function exists, the fallback function will be called on a plain Ether transfer. If neither a receive Ether nor a payable fallback function is present, the contract cannot receive Ether through regular transactions and throws an exception.
未定義fallback或是receive的智能合約收到transfer傳送的以太幣會以exception處理。
因此只要一個未定義fallback或是receive的智能合約佔用king,合約就會在transfer時出現錯誤,令king的地址永遠屬於該智能合約。
本關另一難點在於關卡合約把邏輯定義在fallback裏面,因為transfer和send都只限使用2300 gas,因此只能用call的方法,可以參考這裏的官方文檔。
首先打開Console (F12),輸入
> instance
下方會回傳當前關卡合約地址。
然後打開Remix IDE,新建檔案King.sol貼上:
pragma solidity ^0.8.0;
contract King {
address levelInstance;
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
function give() public payable {
levelInstance.call{value: msg.value}("");
}
}
在constructor填入關卡合約地址後發佈。需注意兩點的是︰
- Value 需大於或等於1 ether (關卡合約要求)
- 需要手動調高Gas limit,一次調到100000就可以了(不然呼叫關卡合約的fallback時會因Out of gas 失敗)
需注意使用call來傳送以太幣在編寫正常合約時是一種極差的寫法,因為很可能會引致下一關介紹的Re-entrancy attack。有關傳送以太幣的三種方法及使用call可能導致的Re-entrancy attack可以參考這篇文章。
最後回關卡頁面按提交,本關完成。