Files
hardhat-smartcontract-lottery/test/unit/Raffle.test.js
T
2024-08-13 04:13:15 +08:00

331 lines
18 KiB
JavaScript

const { ethers, deployments, getNamedAccounts, network } = require("hardhat");
const { expect, assert } = require("chai");
const {
networkConfig,
developmentChains,
VERIFICATION_BLOCK_CONFIRMATIONS,
} = require("../../helper-hardhat-config");
// Run on development chain
!developmentChains.includes(network.name)
? describe.skip
: describe("Raffle Unit Test", function () {
let addressMock, addressRaffle, addressDeployer, addressPlayer;
let raffle, vrfCoordinatorV2_5Mock, namedAccounts, subscriptionId;
let chainId, raffleEntranceFee, interval;
beforeEach(async () => {
namedAccounts = await getNamedAccounts();
addressDeployer = namedAccounts.deployer;
addressPlayer = namedAccounts.player;
await deployments.fixture(["all"]);
// Implementation for dependencies without "@nomiclabs/hardhat-ethers": "npm:hardhat-deploy-ethers@^0.3.0-beta.13",
addressMock = (await deployments.get("VRFCoordinatorV2_5Mock")).address;
addressRaffle = (await deployments.get("Raffle")).address;
vrfCoordinatorV2_5Mock = await ethers.getContractAt(
"VRFCoordinatorV2_5Mock",
addressMock,
);
raffle = await ethers.getContractAt("Raffle", addressRaffle); // Default
//raffle = await ethers.getContractAt("Raffle", addressPlayer);
//raffle = await raffle.connect(addressPlayer); // Connect the player to the Raffle contract
//raffleContract = await ethers.getContract("Raffle"); // Returns a new connection to the Raffle contract
// raffle = raffleContract.connect(addressPlayer); // Returns a new instance of the Raffle contract connected to player
/*
// Add fund to Mock for fulfillRandomWords
const tx = await vrfCoordinatorV2_5Mock.createSubscription();
const txReceipt = await tx.wait(1);
subscriptionId = txReceipt.events[0].args.subId;
//0x8d03209e7b30987dddca60349de1dc942195aadc9d6c6b5ab324388762f3b57e
await vrfCoordinatorV2_5Mock.addConsumer(subscriptionId, raffle.address);
const FUND_AMOUNT = ethers.utils.parseEther("1"); // 1 Ether
await vrfCoordinatorV2_5Mock.fundSubscription(subscriptionId, FUND_AMOUNT);
// Retrieve subscription details
const subscription = await vrfCoordinatorV2_5Mock.getSubscription(subscriptionId);
// Verify if the consumer is added correctly
console.log(`Consumers: ${subscription.consumers}`); // Should now include raffle.address
// Verify the balance
const balance = subscription.balance;
console.log(`Subscription balance: ${ethers.utils.formatEther(balance)} Ether`);
// Assert the balance to ensure it matches the FUND_AMOUNT
assert.equal(
balance.toString(),
FUND_AMOUNT.toString(),
"Subscription balance should match the fund amount",
);
*/
// raffle = await deployments.get("Raffle"); // Wrong, not the contract instance
// raffle = await ethers.getContract("Raffle"); // With hardaht-ethers dependency override
chainId = network.config.chainId;
raffleEntranceFee = await raffle.getEntranceFee();
interval = await raffle.getInterval();
});
describe("constructor", function () {
it("initial correct raffle state", async () => {
const raffleState = await raffle.getRaffleState();
assert.equal(raffleState, 0, "Incorrect raffle state");
});
it("initial correct interval", async () => {
const interval = (await raffle.getInterval()).toString();
const networkConfigInterval = networkConfig[chainId]["keepersUpdateInterval"];
assert.equal(interval, networkConfigInterval, "Incorrect interval");
});
}); //endof constructor
describe("enterRaffle", function () {
it("should revert if not enough payment", async () => {
const entranceFee = await raffle.getEntranceFee();
const entranceFeeString = ethers.utils.formatEther(entranceFee);
console.log(entranceFeeString); // 0.01
await expect(raffle.enterRaffle()).to.be.revertedWith(
"Raffle__NotEnoughETHEntered",
);
});
it("records player when they enter", async () => {
await raffle.enterRaffle({ value: raffleEntranceFee });
const contractPlayer = await raffle.getPlayer(0);
assert.equal(contractPlayer, addressDeployer);
});
it("should emit the correct event on enter", async () => {
await expect(raffle.enterRaffle({ value: raffleEntranceFee })).to.emit(
raffle,
"RaffleEnter",
);
});
it("not allow entrance when raffle is calculating state", async () => {
await raffle.enterRaffle({ value: raffleEntranceFee });
await network.provider.send("evm_increaseTime", [interval.toNumber() + 1]);
await network.provider.send("evm_mine", []);
// Pretend to be a chainlink upkeep
await raffle.performUpkeep([]);
await expect(raffle.enterRaffle({ value: raffleEntranceFee })).to.be.revertedWith(
"Raffle__RaffleNotOpen",
);
});
}); //endof enterRaffle
describe("checkUpkeep", function () {
it("should return false if nobody send any ETH", async () => {
await network.provider.send("evm_increaseTime", [interval.toNumber() + 1]);
await network.provider.send("evm_mine", []);
// Static call to not spend gas & modify state
const { upkeepNeeded } = await raffle.callStatic.checkUpkeep([]);
assert.isFalse(upkeepNeeded);
});
it("should return true if raffle is in calculating state", async () => {
await raffle.enterRaffle({ value: raffleEntranceFee });
await network.provider.send("evm_increaseTime", [interval.toNumber() + 1]);
await network.provider.send("evm_mine", []);
// Pretend to be a chainlink upkeep
const { upkeepNeeded } = await raffle.callStatic.checkUpkeep([]);
assert.isTrue(upkeepNeeded);
});
it("should return false if raffle is not open", async () => {
await raffle.enterRaffle({ value: raffleEntranceFee });
await network.provider.send("evm_increaseTime", [interval.toNumber() + 1]);
await network.provider.send("evm_mine", []);
await raffle.performUpkeep("0x");
const raffleState = await raffle.getRaffleState();
const { upkeepNeeded } = await raffle.callStatic.checkUpkeep([]);
assert.isFalse(upkeepNeeded);
});
it("returns false if enough time hasn't passed", async () => {
await raffle.enterRaffle({ value: raffleEntranceFee });
await network.provider.send("evm_increaseTime", [interval.toNumber() - 5]); // use a higher number here if this test fails
await network.provider.request({ method: "evm_mine", params: [] }); // Alternative to write
const { upkeepNeeded } = await raffle.callStatic.checkUpkeep("0x"); // upkeepNeeded = (timePassed && isOpen && hasBalance && hasPlayers)
assert.isFalse(upkeepNeeded);
});
it("returns true if enough time has passed, has players, eth, and is open", async () => {
await raffle.enterRaffle({ value: raffleEntranceFee });
await network.provider.send("evm_increaseTime", [interval.toNumber() + 1]);
await network.provider.send("evm_mine", []);
const { upkeepNeeded } = await raffle.callStatic.checkUpkeep("0x"); // upkeepNeeded = (timePassed && isOpen && hasBalance && hasPlayers)
assert(upkeepNeeded);
});
}); //endof checkUpkeep
describe("performUpkeep", function () {
it("it can only run if checkupkeep is true", async () => {
await raffle.enterRaffle({ value: raffleEntranceFee });
await network.provider.send("evm_increaseTime", [interval.toNumber() + 1]);
await network.provider.send("evm_mine", []);
const tx = await raffle.performUpkeep("0x");
assert(tx);
});
it("it should revert if checkupkeep is false", async () => {
await expect(raffle.performUpkeep("0x")).to.be.revertedWith(
"Raffle__UpkeepNotNeeded",
);
});
it("update the raffle sate, emits and event, and call the vrf coordinator", async () => {
await raffle.enterRaffle({ value: raffleEntranceFee });
await network.provider.send("evm_increaseTime", [interval.toNumber() + 1]);
await network.provider.request({ method: "evm_mine", params: [] });
const txResponse = await raffle.performUpkeep("0x");
const txReceipt = await txResponse.wait(1);
const requestId = txReceipt.events[1].args.requestId; // 2nd Event
const currentRaffleState = await raffle.getRaffleState();
assert(requestId.toNumber() > 0);
assert(currentRaffleState == 1);
});
});
describe("fulfillRandomWords", function () {
let subscriptionId;
beforeEach(async () => {
//raffle = await raffle.connect(addressPlayer); // Test
await raffle.enterRaffle({ value: raffleEntranceFee });
await network.provider.send("evm_increaseTime", [interval.toNumber() + 1]);
await network.provider.send("evm_mine", []);
const subIdArr = await vrfCoordinatorV2_5Mock.getActiveSubscriptionIds(0, 100);
subscriptionId = subIdArr[0];
//subscriptionId =
// "91765082012550290938840732836101412737423938684399204465191383082124961026867";
/*
vrfCoordinatorV2_5Address = vrfCoordinatorV2_5Mock.address;
const transactionResponse = await vrfCoordinatorV2_5Mock.createSubscription();
const createSubReceipt = await transactionResponse.wait(1);
subscriptionId = createSubReceipt.events[0].args.subId; // Keep as BigNumber
await vrfCoordinatorV2_5Mock.fundSubscription(subscriptionId, FUND_AMOUNT);
const FUND_AMOUNT = ethers.utils.parseEther("1"); // 1 Ether, or 1e18 (10^18) Wei
const subscription = await vrfCoordinatorV2_5Mock.getSubscription(subscriptionId);
const balance = subscription.balance;
console.log(`Subscription balance: ${ethers.utils.formatEther(balance)} Ether`);
console.log(`Consumers: ${subscription.consumers}`); // Should now include raffle.address
*/
//console.log(subscriptionId);
});
it("can only be called after performUpKeep", async () => {
const requestId = 0; // Not using
await expect(
vrfCoordinatorV2_5Mock.fulfillRandomWords(0, raffle.address),
).to.be.revertedWith("InvalidRequest");
await expect(
vrfCoordinatorV2_5Mock.fulfillRandomWords(1, raffle.address), // reverts if not fulfilled
).to.be.revertedWith("InvalidRequest");
});
// Complete Test
it("picks a winner, reset the raffle, and send money to the winner", async () => {
const additionalEntrances = 3;
const startingAccountIndex = 2; // deployer = 0, player = 1
let accounts = await ethers.getSigners(); // Many accounts
// getNamedAccounts() is not working here, because only 2 accounts are named
for (
let i = startingAccountIndex;
i < startingAccountIndex + additionalEntrances;
i++
) {
const accountConnectedRaffle = await raffle.connect(accounts[i]);
await accountConnectedRaffle.enterRaffle({ value: raffleEntranceFee });
}
const startingTimeStamp = await raffle.getLastTimeStamp();
// This will be more important for our staging tests...
await new Promise(async (resolve, reject) => {
// just a promise to handle the event listener
raffle.once("WinnerPicked", async () => {
console.log("WinnerPicked event fired!");
// assert throws an error if it fails, so we need to wrap
// it in a try/catch so that the promise returns event
// if it fails.
try {
// Now lets get the ending values...
const recentWinner = await raffle.getRecentWinner();
const raffleState = await raffle.getRaffleState();
const winnerBalance = await accounts[2].getBalance();
const endingTimeStamp = await raffle.getLastTimeStamp();
await expect(raffle.getPlayer(0)).to.be.reverted;
// Comparisons to check if our ending values are correct:
assert.equal(recentWinner.toString(), accounts[2].address);
assert.equal(raffleState, 0);
assert.equal(
winnerBalance.toString(),
startingBalance // startingBalance + ( (raffleEntranceFee * additionalEntrances) + raffleEntranceFee )
.add(
raffleEntranceFee
.mul(additionalEntrances)
.add(raffleEntranceFee),
)
.toString(),
);
assert(endingTimeStamp > startingTimeStamp);
resolve(); // if try passes, resolves the promise
} catch (e) {
reject(e); // if try fails, rejects the promise
}
});
// kicking off the event by mocking the chainlink keepers and vrf coordinator
try {
const tx = await raffle.performUpkeep("0x");
const txReceipt = await tx.wait(1);
startingBalance = await accounts[2].getBalance();
const subscription =
await vrfCoordinatorV2_5Mock.getSubscription(subscriptionId);
const balance = subscription.balance;
const owner = subscription.owner;
const consumers = subscription.consumers;
console.log(`Subscription ID: ${subscriptionId}`);
console.log(`Subscription Owner: ${owner}`);
console.log(
`Subscription Balance: ${ethers.utils.formatEther(balance)} ETH`,
);
console.log(`Consumers: ${consumers}`); // This should include your Raffle contract address
// Verify the Raffle contract address matches the consumer
assert(
consumers.includes(raffle.address),
"Raffle contract not authorized as a consumer!",
);
// Log post-fulfillment details
const updatedSubscription =
await vrfCoordinatorV2_5Mock.getSubscription(subscriptionId);
console.log(
`Updated Subscription Balance: ${ethers.utils.formatEther(updatedSubscription.balance)} ETH`,
);
// Here cannot run, always InsufficientBalance()
await vrfCoordinatorV2_5Mock.fulfillRandomWords(
txReceipt.events[1].args.requestId,
raffle.address,
);
} catch (e) {
reject(e);
}
});
});
});
});