Ethernaut – Level 14 : Gatekeeper Two

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

這一題的結構跟前一題一樣,目標也一樣,但3 個modifier的要求不同:

modifier gateOne() {
    require(msg.sender != tx.origin);
    _;
  }

  modifier gateTwo() {
    uint x;
    assembly { x := extcodesize(caller()) }
    require(x == 0);
    _;
  }

  modifier gateThree(bytes8 _gateKey) {
    require(uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == uint64(0) - 1);
    _;
  }

gateOne:

與前一關的gateOne相同,需要用一個部署的合約去呼叫enter。

gateTwo:

  • assembly: solidiy定義了assembly code(類似組合語言的作用),這個assembly code可在solidity code中做為 inline assembly,其Syntax為包在assembly{} 內,舉例包括如下:
    • functional-style opcodes: mul(1, add(2,3)) 而不須用push1 3 push1 2 add push1 1 mul
    • assembly-local variables: let x:= add(2,3)
    • (可參考這裡)
  • extcodesize(contract address): 此函數傳回該address的合約的byte code長度,而這題傳入的是caller(),要怎麼讓我們自己的寫的合約能呼叫,還能長度為零呢? 先公布答案,在consturctor中呼叫,完整說明請看文章最後備註(合約部署) 。

gateThree:

  • ^這個符號為XOR的意思。
  • A^B=C 則 A^C=B
  • uint64(0)-1 表示uint64的最大值 (0xffffffffffffffff, 亦即0-1,overflow後的結果)

總和以上,我們要解這題於是在Remix上寫上如下合約

contract goGateKeeper_Two{

    constructor(address _instance) public {
        IGateKeeper_Two two= IGateKeeper_Two(_instance);
        bytes8 _gateKey = bytes8(uint64(bytes8(keccak256(abi.encodePacked(address(this))))) ^ (uint64(0) - 1));
        two.enter(_gateKey);
    }

}

一樣,我們可以在console輸入 await contract.entrant() 確認一下是否已經變成entrant囉,上傳,過關,敲鑼,打鼓,吃雞。

*備註

在Ethereum yellow paper中,說明到智慧合約的創建:

整個創建的流程如下:

  1. 當一個合約送到以太坊網路,會產生一個transaction,這個 transaction會包含如上圖中的變數:
    • Sender (s): 創建contract(送出transaction的地址),可能是錢包位址也可能是合約位址(合約創建的合約)。
    • Original transactor (o): 原本的創建者,若合約是由合約創建的,則 o != s。(可想成 msg.sender 與 tx.origin的不同)。
    • Available gas (g): gas allocated to this contract。
    • Gas price (p): gas的市場價格,用以將gas轉成Ethers的值。
    • Endowment (v): value 轉至此合約的數量(in Wei) as seed。defalut is 0。
    • Initialization EVM code (i): 就是constructor內的code及初始化的變數,in bytecode format。
  2. 根據上述的變數,新的合約的位址將被(pre) 計算出來。此時的contract’s state still empty。
  3. 第三步,創建實體的contract。
  4. 過程中,state changed、資料儲存、gas使用。
  5. 當合約完成初始化上述步驟後,將其code存起來並關連到上述的contract address。
  6. 最後,將部署的成功/失敗訊息及剩餘的gas (asynchronously)返回給 sender(s)。

由上可發現,直到第5步之前,雖然已經有了合約的address,但並沒有任何code存在此address!因此,此時的extcodesize回傳會為0。