When I try run my unit test ("hh test" or "yarn hardhat test") in ethersV6, for some reason it doesn't enter in my Promise's test because even doesn't appear the console.log().
The scripts are the following:
package.json
{
"devDependencies": {
"@chainlink/contracts": "^1.1.0",
"@nomicfoundation/hardhat-chai-matchers": "^2.0.6",
"@nomicfoundation/hardhat-ethers": "^3.0.2",
"@nomicfoundation/hardhat-toolbox": "2",
"@nomiclabs/hardhat-ethers": "npm:hardhat-deploy-ethers",
"@nomiclabs/hardhat-etherscan": "^3.1.8",
"@nomiclabs/hardhat-waffle": "^2.0.6",
"chai": "4.3.8",
"dotenv": "^16.4.5",
"ethereum-waffle": "^4.0.10",
"ethers": "^6.12.1",
"hardhat": "^2.22.4",
"hardhat-contract-sizer": "^2.10.0",
"hardhat-deploy": "^0.12.2",
"hardhat-gas-reporter": "^2.1.1",
"prettier": "^3.2.5",
"prettier-plugin-solidity": "^1.3.1",
"solhint": "^4.5.4",
"solidity-coverage": "^0.8.12"
}
}
hardhat.config.js
require("@nomiclabs/hardhat-etherscan")
require("hardhat-deploy")
require("solidity-coverage")
require("hardhat-gas-reporter")
require("hardhat-contract-sizer")
require("dotenv").config()
require("@nomicfoundation/hardhat-chai-matchers")
require("@nomiclabs/hardhat-ethers")
const SEPOLIA_RPC_URL = process.env.SEPOLIA_RPC_URL
const PRIVATE_KEY = process.env.PRIVATE_KEY
const COINMARKETCAP_API_KEY = process.env.COINMARKETCAP_API_KEY
const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
defaultNetwork: "hardhat",
networks: {
hardhat: {
chainId: 31337,
blockConfirmations: 1,
},
sepolia: {
chainId: 11155111,
blockConfirmations: 6,
url: SEPOLIA_RPC_URL,
accounts: [PRIVATE_KEY],
},
},
gasReporter: {
enabled: false,
currency: "USD",
outputFile: "gas-report.txt",
noColors: true,
},
solidity: "0.8.7",
namedAccounts: {
deployer: {
default: 0,
},
player: {
default: 1,
},
},
mocha: {
timeout: 2400000,
},
}
Raffle.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol";
import "@chainlink/contracts/src/v0.8/vrf/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/vrf/KeepersVRFConsumer.sol";
error Raffle__NotEnoughETHEntered();
error Raffle__TransferFailed();
error Raffle__NotOpen();
error Raffle__UpkeepNotNeeded(uint256 currentBalnce, uint256 numPlayers, uint256 raffleState);
/**
* @title A sample raffle contract
* @author Tucan
* @notice This contract is for creating an untamperable decentralized smart contract
* @dev This implements Chainlink VRF v2 and Chainlink Keepers
*/
contract Raffle is VRFConsumerBaseV2, KeeperCompatibleInterface {
enum RaffleState {
OPEN,
CALCULATING
}
uint256 private immutable i_entranceFee;
address payable[] private s_players;
VRFCoordinatorV2Interface private immutable i_vrfCoordinator;
bytes32 private immutable i_gasLane;
uint64 private immutable i_subscriptionId;
uint32 private immutable i_callbackGasLimit;
uint16 private constant REQUEST_CONFIRMATIONS = 3;
uint32 private constant NUM_WORDS = 1;
address private s_recentWinner;
RaffleState private s_raffleState;
uint256 private s_lastTimeStamp;
uint256 private immutable i_interval;
event RaffleEnter(address indexed player);
event RequestedRaffleWinner(uint256 indexed requestId);
event WinnerPicked(address indexed winner);
constructor(
address vrfCoordinatorV2,
uint256 entranceFee,
bytes32 gasLane,
uint64 subscriptionId,
uint32 callbackGasLimit,
uint256 interval
) VRFConsumerBaseV2(vrfCoordinatorV2) {
i_entranceFee = entranceFee;
i_vrfCoordinator = VRFCoordinatorV2Interface(vrfCoordinatorV2);
i_gasLane = gasLane;
i_subscriptionId = subscriptionId;
i_callbackGasLimit = callbackGasLimit;
s_raffleState = RaffleState.OPEN;
s_lastTimeStamp = block.timestamp;
i_interval = interval;
}
function enterRaffle() public payable {
if (msg.value < i_entranceFee) {
revert Raffle__NotEnoughETHEntered();
}
if (s_raffleState != RaffleState.OPEN) {
revert Raffle__NotOpen();
}
s_players.push(payable(msg.sender));
emit RaffleEnter(msg.sender);
}
/**
* @dev This is the function that the Chainlink Keeper nodes call
* they look for the `upkeepNeeded` to return true.
* The following should be true in order to be return true;
* 1. Our time interval should have passed
* 2. The lottery should have at least 1 player, and have some ETH
* 3. Our subscription is funded with LINK
* 4. The lottery should be in "open" state
*/
function checkUpkeep(
bytes memory /*checkData*/
) public view override returns (bool upkeepNeeded, bytes memory /* performData */) {
bool isOpen = (RaffleState.OPEN == s_raffleState);
bool timePassed = (block.timestamp - s_lastTimeStamp) > i_interval;
bool hasPlayers = (s_players.length > 0);
bool hasBalance = address(this).balance > 0;
upkeepNeeded = (isOpen && timePassed && hasPlayers && hasBalance);
return (upkeepNeeded, "0x0");
}
function performUpkeep(bytes calldata /*performData*/) external override {
(bool upkeepNeeded, ) = checkUpkeep("");
if (!upkeepNeeded) {
revert Raffle__UpkeepNotNeeded(
address(this).balance,
s_players.length,
uint256(s_raffleState)
);
}
s_raffleState = RaffleState.CALCULATING;
uint256 requestId = i_vrfCoordinator.requestRandomWords(
i_gasLane,
i_subscriptionId,
REQUEST_CONFIRMATIONS,
i_callbackGasLimit,
NUM_WORDS
);
emit RequestedRaffleWinner(requestId);
}
function fulfillRandomWords(
uint256 /*requestId*/,
uint256[] memory randomWords
) internal override {
uint256 indexOfWinner = randomWords[0] % s_players.length;
address payable recentWinner = s_players[indexOfWinner];
s_recentWinner = recentWinner;
s_raffleState = RaffleState.OPEN;
s_players = new address payable[](0);
s_lastTimeStamp = block.timestamp;
(bool success, ) = recentWinner.call{value: address(this).balance}("");
if (!success) {
revert Raffle__TransferFailed();
}
emit WinnerPicked(recentWinner);
}
function getEntranceFee() public view returns (uint256) {
return i_entranceFee;
}
function getPlayer(uint256 index) public view returns (address) {
return s_players[index];
}
function getRecentWinner() public view returns (address) {
return s_recentWinner;
}
function getRaffleState() public view returns (RaffleState) {
return s_raffleState;
}
function getNumWords() public pure returns (uint256) {
return NUM_WORDS;
}
function getNumberPlayers() public view returns (uint256) {
return s_players.length;
}
function getLatestTimeStamp() public view returns (uint256) {
return s_lastTimeStamp;
}
function getRequestConfirmations() public pure returns (uint256) {
return REQUEST_CONFIRMATIONS;
}
function getInterval() public view returns (uint256) {
return i_interval;
}
}
Raffle.test.js
const { network, getNamedAccounts, deployments, ethers } = require("hardhat")
const { developmentChains, networkConfig } = require("../../helper-hardhat-config")
const { assert, expect } = require("chai")
const { Resolver } = require("hardhat-gas-reporter/dist/lib/resolvers")
!developmentChains.includes(network.name)
? describe.skip
: describe("Raffle Unit Tests", function () {
let raffle, vrfCoordinatorV2Mock, raffleEntranceFee, deployer, interval
const chainId = network.config.chainId
beforeEach(async function () {
deployer = (await getNamedAccounts()).deployer
await deployments.fixture(["all"])
raffle = await ethers.getContract("Raffle", deployer)
vrfCoordinatorV2Mock = await ethers.getContract("VRFCoordinatorV2Mock", deployer)
raffleEntranceFee = await raffle.getEntranceFee()
interval = await raffle.getInterval()
})
describe("constructor", function () {
it("initializes the raffle correctly", async function () {
const raffleState = await raffle.getRaffleState()
assert.equal(raffleState.toString(), "0")
assert.equal(interval.toString(), networkConfig[chainId]["interval"])
})
})
describe("enterRaffle", function () {
it("reverts when you don't pay enough", async function () {
await expect(raffle.enterRaffle()).to.be.revertedWithCustomError(
raffle,
"Raffle__NotEnoughETHEntered",
)
})
it("records players when they enter", async function () {
await raffle.enterRaffle({ value: raffleEntranceFee })
const playerFromContract = await raffle.getPlayer(0)
assert.equal(playerFromContract, deployer)
})
it("emits event on enter", async function () {
await expect(raffle.enterRaffle({ value: raffleEntranceFee })).to.emit(
raffle,
"RaffleEnter",
)
})
it("doesn't allow entrance when raffle is calculating", async function () {
await raffle.enterRaffle({ value: raffleEntranceFee })
await network.provider.send("evm_increaseTime", [Number(interval) + 1])
await network.provider.send("evm_mine", [])
await raffle.performUpkeep("0x")
await expect(
raffle.enterRaffle({ value: raffleEntranceFee }),
).to.be.revertedWithCustomError(raffle, "Raffle__NotOpen")
})
})
describe("checkUpkeep", function () {
it("returns false if people haven't sent yet any ETH", async function () {
await network.provider.send("evm_increaseTime", [Number(interval) + 1])
await network.provider.send("evm_mine", [])
const { upkeepNeeded } = await raffle.checkUpkeep.staticCall("0x")
assert(!upkeepNeeded)
})
it("returns false if raffle isn't open", async function () {
await raffle.enterRaffle({ value: raffleEntranceFee })
await network.provider.send("evm_increaseTime", [Number(interval) + 1])
await network.provider.send("evm_mine", [])
await raffle.performUpkeep("0x")
const raffleState = await raffle.getRaffleState()
const { upkeepNeeded } = await raffle.checkUpkeep.staticCall("0x")
assert.equal(raffleState.toString(), "1")
assert.equal(upkeepNeeded, false)
})
it("returns false if enough times hasn't passed", async function () {
await raffle.enterRaffle({ value: raffleEntranceFee })
await network.provider.send("evm_increaseTime", [Number(interval) - 2])
await network.provider.request({ method: "evm_mine", params: [] })
const { upkeepNeeded } = await raffle.checkUpkeep.staticCall("0x")
assert(!upkeepNeeded)
})
it("returns true if enough time passed, has pleayers, ETH and is open", async function () {
await raffle.enterRaffle({ value: raffleEntranceFee })
await network.provider.send("evm_increaseTime", [Number(interval) + 1])
await network.provider.send("evm_mine", [])
const { upkeepNeeded } = await raffle.checkUpkeep.staticCall("0x")
assert(upkeepNeeded)
})
})
describe("performUpkeep", function () {
it("can only run if checkUpkeep is true", async function () {
await raffle.enterRaffle({ value: raffleEntranceFee })
await network.provider.send("evm_increaseTime", [Number(interval) + 1])
await network.provider.send("evm_mine", [])
const tx = await raffle.performUpkeep("0x")
assert(tx)
})
it("reverts when checkUpkeep is false", async function () {
await expect(raffle.performUpkeep("0x")).to.be.revertedWithCustomError(
raffle,
"Raffle__UpkeepNotNeeded",
)
})
it("updates the raffle state, emits and event, and calls the vrf coordinator", async function () {
await raffle.enterRaffle({ value: raffleEntranceFee })
await network.provider.send("evm_increaseTime", [Number(interval) + 1])
await network.provider.send("evm_mine", [])
const txResponse = await raffle.performUpkeep("0x")
const txReceipt = await txResponse.wait(1)
const requestId = txReceipt.logs[1].args.requestId
const raffleState = await raffle.getRaffleState()
assert(Number(requestId) > 0)
assert(raffleState.toString() == "1")
})
})
describe("fulfillRandomWords", function () {
beforeEach(async function () {
await raffle.enterRaffle({ value: raffleEntranceFee })
await network.provider.send("evm_increaseTime", [Number(interval) + 1])
await network.provider.send("evm_mine", [])
})
it("can only be called after performUpkeep", async function () {
await expect(
vrfCoordinatorV2Mock.fulfillRandomWords(0, raffle.target),
).to.be.revertedWith("nonexistent request")
await expect(
vrfCoordinatorV2Mock.fulfillRandomWords(1, raffle.target),
).to.be.revertedWith("nonexistent request")
})
it("picks a winner, resets the lottery and sends money", async function () {
const additionalEntrance = 3
const startingAccountIndex = 1 // deployer = 0
const accounts = await ethers.getSigners()
for (
let i = startingAccountIndex;
i < startingAccountIndex + additionalEntrance;
i++
) {
const accountConnectedRaffle = raffle.connect(accounts[i])
await accountConnectedRaffle.enterRaffle({ value: raffleEntranceFee })
}
const startingTimeStamp = await raffle.getLatestTimeStamp()
await new Promise(async (resolve, reject) => {
raffle.once("WinnerPicked", async () => {
console.log("Found the event")
try {
const recentWinner = await raffle.getRecentWinner()
const raffleState = await raffle.getRaffleState()
const endingTimeStamp = await raffle.getLatestTimeStamp()
const numPlayers = await raffle.getNumberOfPlayers()
const winnerEndingBalance = await accounts[1].getBalance()
assert.equal(numPlayers.toString(), "0")
assert.equal(raffleState.toString(), "0")
assert(endingTimeStamp > startingTimeStamp)
assert.equal(
winnerEndingBalance.toString(),
winnerStartingBalance.add(
raffleEntranceFee
.mul(additionalEntrance)
.add(raffleEntranceFee)
.toString(),
),
)
} catch (e) {
reject(e)
}
resolve()
})
const tx = await raffle.performUpkeep("0x")
const txReceipt = await tx.wait(1)
const winnerStartingBalance = await accounts[1].getBalance()
await vrfCoordinatorV2Mock.fulfillRandomWords(
txReceipt.logs[1].args.requestId,
raffle.target,
)
})
})
})
})
I think the problem must be in the last script in the last test, but I put more of them in case the issue is there. I read more questions about this problem, but most of them didn't work. I can add my deploy scripts if it would be necessary, but I think they aren't.