Lesson 9: AssertionError: expected undefined to equal false #6403
-
Hey all I am working on writing tests for my Smart Contract Lottery and I am running into some testing issues related to the callStatic() method. Currently, when I am testing callStatic() in tests: Raffle. sol // Enter the lottery (paying some amount)
// Pick a random winner (verfifiably random)
// Winner to be seelcted every X minutes -> completely automated
// Chainlink Oracle -> Randomness, Automated Execution (Chainlink Keepers)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";
import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/interfaces/AutomationCompatibleInterface.sol";
error Raffle__NotEnoughETHEntered();
error Raffle__TransferFailed();
error Raffle__NotOpen();
error Raffle__UpkeepNotNeeded(uint256 currentBalance, uint256 numPlayers, uint256 raffleState);
/** @title A sample Raffle Contract
* @author Ritt Dhar
* @notice This contract is for creating an untamperable decentralized smart contract
* @dev This implements Chainlink VRF v2 and Chainlink Keepers
*/
contract Raffle is VRFConsumerBaseV2, AutomationCompatibleInterface {
/** Type Declaration */
enum RaffleState {
OPEN,
CALCULATING
}
/** State Variables */
VRFCoordinatorV2Interface private immutable i_vrfCoordinator;
bytes32 private immutable i_keyHash;
uint64 private immutable i_subscriptionId;
uint32 private immutable i_callbackGasLimit;
uint16 private constant REQUEST_CONFIRMATIONS = 3;
uint32 private constant NUM_WORDS = 1;
/** Lottery Variables */
uint256 private immutable i_entranceFee; // Storage var
uint256 private immutable i_interval;
address private s_recentWinner;
RaffleState private s_raffleState;
uint256 private s_lastTimeStamp;
address payable[] private s_players; // Storage
// bool private s_isOpen; // to true, false
/** Events */
event RaffleEnter(address indexed player);
event RequestRaffleWinner(uint256 indexed requestId);
event WinnerPicked(address indexed winner);
// Constructor
constructor(
address vrfCoordinatorV2,
uint256 entranceFee,
bytes32 keyHash, //gasLane
uint64 subscriptionId,
uint32 callbackGasLimit,
uint256 interval
) VRFConsumerBaseV2(vrfCoordinatorV2) {
i_entranceFee = entranceFee;
i_vrfCoordinator = VRFCoordinatorV2Interface(vrfCoordinatorV2);
i_keyHash = keyHash;
i_subscriptionId = subscriptionId;
i_callbackGasLimit = callbackGasLimit;
s_raffleState = RaffleState.OPEN;
s_lastTimeStamp = block.timestamp;
i_interval = interval;
}
// Functions
function enterRaffle() public payable {
//require msg.value > i_entranceFee
if (msg.value < i_entranceFee) {
revert Raffle__NotEnoughETHEntered();
}
if (s_raffleState != RaffleState.OPEN) {
revert Raffle__NotOpen();
}
s_players.push(payable(msg.sender));
// Events
emit RaffleEnter(msg.sender);
// emit an even when we update a dyanmic array or mapping
// Name events with the function name reversed
}
/**
* @dev This si the function that the Chainlink Keeper nodes call
* they look for the 'upkeepNeeded' value to return true
* The following should be tru in order to return true
* 1. Time interval should have passsed
* 2. The lottery hsould have at least 1 player, and have some ETH
* 3. Our subscription is funded with Link
* 4. The lottery should be in an 'open' state
*/
function checkUpkeep(
bytes memory /*checkData*/
) public override returns (bool upkeepNeeded, bytes memory /* performData */) {
bool isOpen = RaffleState.OPEN == s_raffleState;
// block.timestamp - last block timestamp > interval
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 /*checkData*/) external override {}
function performUpkeep(bytes calldata /* performData */) external override {
//Request the Random Number
// Once we get it, do something with it
// 2 transaction process (need another funciton to send money to the winner)
(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_keyHash,
i_subscriptionId,
REQUEST_CONFIRMATIONS,
i_callbackGasLimit,
NUM_WORDS
);
emit RequestRaffleWinner(requestId);
}
function fulfillRandomWords(
uint256 /*requestId*/,
uint256[] memory randomWords
) internal override {
//s_players size 10
// random number 202
// 202 % 10 = 2
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);
}
/* View/Pure Functions */
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 getNumberOfPlayers() public view returns (uint256) {
return s_players.length;
}
function getLatestestTimeStamp() 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;
}
} 01-deploy-raffle.js const { ethers, network } = require("hardhat");
const { developmentChains, networkConfig } = require("../helper-hardhat-config");
const { verify } = require("../utils/verify");
const VRF_SUB_FUND_AMOUNT = ethers.parseEther("30");
module.exports = async function ({ getNamedAccounts, deployments }) {
const { deploy, log } = deployments;
const deployer = (await getNamedAccounts()).deployer;
const chainId = network.config.chainId;
log(`Chain Id: ${chainId}`);
// const contracts = await deployments.fixture(["all"]);
// const signer = await ethers.getSigner(deployer);
// const raffleAddress = contracts["Raffle"].address;
let vrfCoordinatorV2address, subscriptionId;
if (developmentChains.includes(network.name)) {
//const VRFCoordinatorV2Mock = await ethers.getConractAt("Raffle", raffleAddress, signer);
const vrfCoordinatorV2Mock = await ethers.getContract("VRFCoordinatorV2Mock", deployer);
vrfCoordinatorV2address = vrfCoordinatorV2Mock.target;
const transactionResponse = await vrfCoordinatorV2Mock.createSubscription();
const transactionReceipt = await transactionResponse.wait(1);
subscriptionId = transactionReceipt.logs[0].args.subId;
// Fund the subscription
// Usually you need the link token on a real network
await vrfCoordinatorV2Mock.fundSubscription(subscriptionId, VRF_SUB_FUND_AMOUNT);
} else {
vrfCoordinatorV2address = networkConfig[chainId]["vrfCoordinatorV2"];
subscriptionId = networkConfig[chainId]["subscriptionId"];
}
log(`----------------------------------------------------`);
const entranceFee = networkConfig[chainId]["entranceFee"];
const gasLaneKeyHash = networkConfig[chainId]["gasLaneKeyHash"];
log(`gasLaneKeyHash: ${gasLaneKeyHash}`);
const callbackGasLimit = networkConfig[chainId]["callbackGasLimit"];
log(`callbackGasLimit: ${callbackGasLimit}`);
const interval = networkConfig[chainId]["interval"];
log(`interval ${interval}`);
const args = [
vrfCoordinatorV2address,
entranceFee,
gasLaneKeyHash,
subscriptionId,
callbackGasLimit,
interval,
];
log(`Args: ${args}`);
const raffle = await deploy("Raffle", {
from: deployer,
args: args,
logs: true,
waitConfirmations: network.config.blockConfirmations || 1,
});
if (developmentChains.includes(network.name)) {
const vrfCoordinatorV2Mock = await ethers.getContract("VRFCoordinatorV2Mock");
await vrfCoordinatorV2Mock.addConsumer(Number(subscriptionId), raffle.address);
}
if (!developmentChains.includes(network.name) && process.env.ETHERSCAN_API_KEY) {
log("Verifying...");
await verify(raffle.address, args);
}
log("-------------------------------------");
};
module.exports.tags = ["all", "raffle"]; raffle.test.js const { getNamedAccounts, deployments, ethers, network } = require("hardhat");
const { developmentChains, networkConfig } = require("../../helper-hardhat-config");
const { assert, expect } = require("chai");
!developmentChains.includes(network.name)
? describe.skip
: describe("Raffle Unit Tests", async function () {
let raffle, vrfCoordinatorV2Mock, raffleEntranceFee, interval, player, raffleContract; // deployer
const chainId = network.config.chainId;
beforeEach(async function () {
accounts = await ethers.getSigners();
player = accounts[1];
await deployments.fixture(["mocks", "raffle"]);
//deployer = (await getNamedAccounts()).deployer;
await deployments.fixture(["all"]);
raffleContract = await ethers.getContract("Raffle");
raffle = raffleContract.connect(player);
vrfCoordinatorV2Mock = await ethers.getContract("VRFCoordinatorV2Mock");
raffleEntranceFee = await raffle.getEntranceFee();
interval = await raffle.getInterval();
});
describe("constructor", async function () {
it("initislizes the raffle correctly", async function () {
// Ideally we make out tests have just 1 assert per "it"
const raffleState = await raffle.getRaffleState();
const interval = await raffle.getInterval();
assert.equal(raffleState.toString(), "0");
assert.equal(interval.toString(), networkConfig[chainId]["interval"]);
});
});
describe("enterRaffle", async function () {
it("reverts when you dont pay enough", async function () {
await expect(raffle.enterRaffle()).to.be.revertedWith(
"Raffle__NotEnoughETHEntered",
);
});
it("Records players when they enter", async function () {
await raffle.enterRaffle({ value: raffleEntranceFee });
const playerFromContract = await raffle.getPlayer(0);
assert.equal(player.address, playerFromContract);
});
it("emits event on enter", async function () {
await expect(raffle.enterRaffle({ value: raffleEntranceFee })).to.emit(
raffle,
"RaffleEnter",
);
});
it("Doesnt 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", []);
// Pretend to be a chainlink automated node
await raffle.performUpkeep("0x");
await expect(raffle.enterRaffle({ value: raffleEntranceFee })).to.be.revertedWith(
"Raffle__NotOpen",
);
});
});
describe("checkUpkeep", async function () {
it("returns false if people haven't sent any ETH", async function () {
await network.provider.send("evm_increaseTime", [Number(interval.toString()) + 1]);
await network.provider.request({ method: "evm_mine", params: [] });
const { upkeepNeeded } = await raffle.checkUpkeep.staticCall(new Uint8Array());
//const { upkeepNeeded } = await raffle.callStatic.checkUpkeep("0x");
assert(!upkeepNeeded);
});
it("return false if raffle isn't open", async function () {
await raffle.enterRaffle({ value: raffleEntranceFee });
await network.provider.send("evm_increaseTime", [Number(interval.toString()) + 1]);
await network.provider.request({ method: "evm_mine", params: [] });
await raffle.performUpkeep("0x");
const raffleState = await raffle.getRaffleState();
const { upkeepNeeded } = raffle.checkUpkeep.staticCall(new Uint8Array());
//const { upkeepNeeded } = await raffle.callStatic.checkUpkeep("0x");
assert.equal(raffleState.toString(), "1");
assert.equal(upkeepNeeded, false);
});
it("return false if not enough time has passed", async function () {
await raffle.enterRaffle({ value: raffleEntranceFee });
await network.provider.send("evm_increaseTime", [Number(interval) - 5]);
await network.provider.request({ method: "evm_mine", params: [] });
const { upkeepNeeded } = raffle.checkUpkeep.staticCall(new Uint8Array());
//const { upkeepNeeded } = await raffle.callStatic.checkUpkeep("0x");
assert(!upkeepNeeded);
});
it("return true if enough time has passed, has player, eth, and is open", async function () {
await raffle.enterRaffle({ value: raffleEntranceFee });
await network.provider.send("evm_increaseTime", [Number(interval) + 1]);
await network.provider.request({ method: "evm_mine", params: [] });
const { upkeepNeeded } = raffle.checkUpkeep.staticCall(new Uint8Array());
//const { upkeepNeeded } = await raffle.callStatic.checkUpkeep("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.request({ method: "evm_mine", params: [] });
const transaction = await raffle.performUpkeep("0x");
assert(transaction);
});
it("reverts if checkUpkeep is false", async function () {
await expect(raffle.performUpkeep("0x")).to.be.revertedWith(
"Raffle__UpkeepNotNeeded",
);
});
it("Updates the raffle state and emits a requestId", async function () {
await raffle.enterRaffle({ value: raffleEntranceFee });
await network.provider.send("evm_increaseTime", [Number(interval) + 1]);
await network.provider.request({ method: "evm_mine", params: [] });
const transactionResponse = await raffle.performUpkeep("0x");
const transactionReceipt = await transactionResponse.wait(1);
const raffleState = await raffle.getRaffleState();
const requestId = transactionReceipt.logs[1].args.requestId;
assert(Number(requestId) > 0);
assert(raffleState == 1);
});
});
describe("fulfillRandomWords", function () {
beforeEach(async function () {
await raffle.enterRaffle({ value: raffleEntranceFee });
await network.provider.send("evm_increaseTime", [Number(interval) + 1]);
await network.provider.request({ method: "evm_mine", params: [] });
});
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 and sends money", async function () {
const additionalEntrances = 3;
const startingIndex = 2;
let startingBalance;
for (let i = startingIndex; i < startingIndex + additionalEntrances; i++) {
raffle = raffleContract.connect(accounts[i]);
await raffle.enterRaffle({ value: raffleEntranceFee });
}
const startTimeStamp = await raffle.getLatestestTimeStamp();
await new Promise(async function (resolve, reject) {
raffle.once("WinnerPicked", async function () {
console.log("Winner Picked event Fired!");
try {
const recentWinner = await raffle.getRecentWinner();
const raffleState = await raffle.getRaffleState();
const winnerBalance = await acccount[2].getBalance();
const endingTimeStamp = await raffle.getLatestestTimeStamp();
await expect(raffle.getPlayer(0)).to.be.reverted;
assert.equal(recentWinner.toString(), accounts[2].address);
assert.equal(raffleState, 0);
assert.equal(
winnerBalance.toString(),
(
startingBalance +
raffleEntranceFee * additionalEntrances +
raffleEntranceFee
).toString(),
);
assert(endingTimeStamp > startTimeStamp);
resolve();
} catch (error) {
reject(error);
}
});
try {
const transaction = await raffle.performUpkeep("0x");
const transactionReceipt = await transaction.wait(1);
startingBalance = await accounts[2].getBalance();
await vrfCoordinatorV2Mock.fulfillRandomWords(
transactionReceipt.logs[1].args.requestId,
raffle.target,
);
} catch (error) {
reject(error);
}
});
});
});
}); package.json {
"name": "hardhat-smartcontract-lottery",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"devDependencies": {
"@chainlink/contracts": "^0.5.1",
"@nomiclabs/hardhat-ethers": "npm:hardhat-deploy-ethers",
"@nomiclabs/hardhat-etherscan": "^3.1.8",
"@nomiclabs/hardhat-waffle": "^2.0.6",
"bn.js": "^5.2.1",
"chai": "^4.2.0",
"dotenv": "^16.3.1",
"ethereum-waffle": "^4.0.10",
"ethers": "^5.5.1",
"hardhat": "^2.19.4",
"hardhat-contract-sizer": "^2.10.0",
"hardhat-deploy": "^0.11.45",
"hardhat-gas-reporter": "^1.0.9",
"mocha": "^10.2.0",
"prettier": "^3.2.1",
"prettier-plugin-solidity": "^1.3.1",
"solhint": "^4.1.1",
"solidity-coverage": "^0.8.5"
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 5 replies
-
Try deploying the contract, and if you get any error please share it, cause I don't think the error is related to the contract or the test. Maybe it's issue with deployment. |
Beta Was this translation helpful? Give feedback.
Just noticed, you're not awaiting the
checkUpkeep
function, to return value hence it remainsundefined
.Update the code to this -
const { upkeepNeeded } = await raffle.checkUpkeep.staticCall(new Uint8Array());