import hre from "hardhat"; import { expect } from "chai"; // import { parseEther } from "ethers"; import { DiceGame, RiggedRoll, RiggedRoll__factory } from "../typechain-types"; import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; const { ethers } = hre; describe("🚩 Challenge: 🎲 Dice Game", function () { let diceGame: DiceGame; let riggedRoll: RiggedRoll; let deployer: HardhatEthersSigner; const { provider } = ethers; const rollAmountString = "0.002"; const rollAmount = ethers.parseEther(rollAmountString); async function deployContracts() { [deployer] = await ethers.getSigners(); const DiceGame = await ethers.getContractFactory("DiceGame"); diceGame = await DiceGame.deploy(); const contractAddress = process.env.CONTRACT_ADDRESS; let contractArtifact; if (contractAddress) { contractArtifact = `contracts/download-${contractAddress}.sol:RiggedRoll`; } else { contractArtifact = "contracts/RiggedRoll.sol:RiggedRoll"; } const diceGameAddress = await diceGame.getAddress(); const RiggedRoll = (await ethers.getContractFactory(contractArtifact)) as RiggedRoll__factory; riggedRoll = await RiggedRoll.deploy(diceGameAddress); } async function fundRiggedContract() { const riggedRollAddress = await riggedRoll.getAddress(); return deployer.sendTransaction({ to: riggedRollAddress, value: rollAmount, }); } async function getRoll(getRollLessThanFive: boolean) { let expectedRoll; while (true) { const latestBlockNumber = await provider.getBlockNumber(); const block = await provider.getBlock(latestBlockNumber); if (!block) { return; } const prevHash = block.hash; const nonce = await diceGame.nonce(); const diceGameAddress = await diceGame.getAddress(); const hash = ethers.solidityPackedKeccak256( ["bytes32", "address", "uint256"], [prevHash, diceGameAddress, nonce], ); const bigInt = BigInt(hash); expectedRoll = bigInt % 16n; if (expectedRoll < 5n == getRollLessThanFive) { break; } const options = { value: rollAmount }; await diceGame.rollTheDice(options); } return expectedRoll; } describe("Checkpoint2: 🔑 Rigged Contract", function () { beforeEach(async function () { await deployContracts(); }); it("Checkpoint2: Should deploy contracts", async function () { const diceGameAddress = await diceGame.getAddress(); expect(await riggedRoll.diceGame()).to.equal(diceGameAddress); }); it(`Checkpoint2: Should revert if balance is less than ${rollAmountString} ethers`, async function () { await expect(riggedRoll.riggedRoll()) .to.be.revertedWithCustomError(riggedRoll, "NotEnoughETH") .withArgs(rollAmount, 0); }); it("Checkpoint2: Should transfer sufficient eth to RiggedRoll", async function () { console.log("\t", "💸 Funding RiggedRoll contract"); await fundRiggedContract(); const riggedRollAddress = await riggedRoll.getAddress(); const balance = await provider.getBalance(riggedRollAddress); console.log("\t", "💲 RiggedRoll balance: ", ethers.formatEther(balance)); expect(balance).to.gte(rollAmount, `Error when expecting DiceGame contract to have >= ${rollAmount} eth`); }); it("Checkpoint2: Should call diceGame.rollTheDice for a roll <= 5", async () => { console.log("\t", "💸 Funding RiggedRoll contract"); await fundRiggedContract(); const getRollLessThanFive = true; const expectedRoll = await getRoll(getRollLessThanFive); console.log("\t", "🎲 Expect roll to be less than or equal to 5. Dice Game Roll:", Number(expectedRoll)); const tx = await riggedRoll.riggedRoll(); const riggedRollAddress = await riggedRoll.getAddress(); await expect(tx).to.emit(diceGame, "Roll").withArgs(riggedRollAddress, rollAmount, expectedRoll); await expect(tx).to.emit(diceGame, "Winner"); }); it("Checkpoint2: Should not call diceGame.rollTheDice for a roll > 5", async () => { console.log("\t", "💸 Funding RiggedRoll contract"); await fundRiggedContract(); const getRollLessThanFive = false; const expectedRoll = await getRoll(getRollLessThanFive); console.log("\t", "🎲 Expect roll to be greater than 5. Dice Game Roll:", Number(expectedRoll)); console.log("\t", "◀ Expect riggedRoll to be reverted"); await expect(riggedRoll.riggedRoll()) .to.be.revertedWithCustomError(riggedRoll, "NotWinningRoll") .withArgs(expectedRoll); }); }); describe("Checkpoint3: 💵 Where's my money?!?", function () { beforeEach(async function () { await deployContracts(); }); it("Checkpoint3: Should withdraw funds", async () => { console.log("\t", "💸 Funding RiggedRoll contract"); await fundRiggedContract(); const deployerPrevBalance = await provider.getBalance(deployer.address); console.log("\t", "💲 Current RiggedRoll balance: ", ethers.formatEther(deployerPrevBalance)); const riggedRollAddress = await riggedRoll.getAddress(); const riggedRollBalance = await provider.getBalance(riggedRollAddress); await riggedRoll.withdraw(deployer.address, riggedRollBalance); const deployerCurrentBalance = await provider.getBalance(deployer.address); console.log("\t", "💲 New RiggedRoll balance: ", ethers.formatEther(deployerCurrentBalance)); expect( deployerPrevBalance < deployerCurrentBalance, "Error when expecting RiggedRoll balance to increase when calling withdraw", ).to.true; }); it("Checkpoint3: Should revert withdraw when amount exceeds contract balance", async () => { console.log("\t", "💸 Funding RiggedRoll contract"); await fundRiggedContract(); const riggedRollAddress = await riggedRoll.getAddress(); const riggedRollBalance = await provider.getBalance(riggedRollAddress); const tooMuch = riggedRollBalance + rollAmount; await expect(riggedRoll.withdraw(deployer.address, tooMuch)) .to.be.revertedWithCustomError(riggedRoll, "InsufficientBalance") .withArgs(tooMuch, riggedRollBalance); }); }); });