331 lines
18 KiB
JavaScript
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);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});
|