PatrickAlphaC / hardhat-smartcontract-lottery-fcc

MIT License
118 stars 183 forks source link

Unit & staging test error issue: For async tests and hooks, ensure "done()" is called; #40

Open tujigogo opened 2 years ago

tujigogo commented 2 years ago

when i run hardhat test ( I tried both hardhat network and rinkeby network)

yarn hardhat test --network hardhat --grep "picks a winner, resets, and sends money"

this error comes out =>

Error: Timeout of 50000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (/Users/tujitongxue/Documents/TujiTech/solidity/lottery_hardhat/test/unit/Raffle.test.js)

the test code is (i just pasted from the repository)

it("picks a winner, resets, and sends money", async () => {
      const additionalEntrances = 3 // to test
      const startingIndex = 2
      for (let i = startingIndex; i < startingIndex + additionalEntrances; i++) {
          // i = 2; i < 5; i=i+1
          raffle = raffleContract.connect(accounts[i]) // Returns a new instance of the Raffle contract connected to player
          await raffle.enterRaffle({ value: raffleEntranceFee })
      }
      const startingTimeStamp = await raffle.getLastTimeStamp() // stores starting timestamp (before we fire our event)

      // This will be more important for our staging tests...
      await new Promise(async (resolve, reject) => {
          raffle.once("WinnerPicked", async () => {
              // event listener for WinnerPicked
              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
              }
          })

          const tx = await raffle.performUpkeep("0x")
          const txReceipt = await tx.wait(1)
          const startingBalance = await accounts[2].getBalance()
          await vrfCoordinatorV2Mock.fulfillRandomWords(
              txReceipt.events[1].args.requestId,
              raffle.address
          )
      })
  })

any idea for this ?

PavelNaydanov commented 2 years ago

I catched the same problem, I forgot to added timeout mocha to hardhat.config.js

mocha: {
   timeout: 500000, 
},
Sanchez7599 commented 2 years ago

I was having the same issue.

Fixed it by changing

event RequestedRaffleWinner(uint256);

to

event RequestedRaffleWinner(uint256 indexed requestId);

in my Raffle.sol.

01edison commented 2 years ago

I still have this error, please has anyone solved it?

Sanchez7599 commented 2 years ago

I still have this error, please has anyone solved it?

Can you attach your Raffle.sol?

01edison commented 2 years ago

// SPDX-License-Identifier: MIT pragma solidity ^0.8.9;

// Import this file to use console.log import "hardhat/console.sol"; import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol"; import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol"; // import "@chainlink/contracts/src/v0.8/interfaces/KeeperCompatibleInterface.sol"; import "@chainlink/contracts/src/v0.8/KeeperCompatible.sol";

error LotteryNotEnoughEthSent(); error LotteryTransferFailed(); error LotteryLotteryNotOpen(); error LotteryNoUpdateNeeded();

// in the docs, the "VRFv2Consumer" is our Contract. So what that contract is doing is what our contract should be doing plus our own added functionalities ;)

/**

contract Lottery is VRFConsumerBaseV2, KeeperCompatibleInterface { //Enter the lottery (pay some amount) //Pick a random winner (verifyably random) //Winner to be selected every X minutes -> completely automated //Chainlink oracle -> Randomness (chainlink VRF), Automated execution (chainlink keepers)

/* Type Declarations */
enum LotteryState {
    OPEN,
    CALCULATING
}

/*State variables */
VRFCoordinatorV2Interface private immutable i_coordinator; //big guy coordinating the request
uint256 private immutable i_entranceFee;
address payable[] private s_players; // because one of these address will receive money
bytes32 private immutable i_keyhash; // this is to set a max gas price so incase a request for a random number is too high, that request fails. gotten from the VRF docs
uint64 private immutable i_subscriptionId;
uint32 private immutable i_callbackGasLimit; // gotten from the VRF docs
uint16 private constant REQUEST_CONFIRMATIONS = 3;
uint32 private constant NUM_WORDS = 1;

/*Lottery Variables */
address private s_recentWinner;
LotteryState private s_lotteryState;
uint256 private s_lastTimeStamp;
uint256 private immutable i_interval;

/*Events */
event LotteryEnter(address indexed player); // "indexed" so that it's easily searchable in the logs
event RequestedLotteryWinner(uint256 indexed requestId);
event WinnerPicked(address indexed winner);

constructor(
    address vrfCoordinatorV2,
    uint256 _entranceFee,
    bytes32 keyHash,
    uint64 subscriptionId,
    uint32 callbackGasLimit,
    uint256 interval
) VRFConsumerBaseV2(vrfCoordinatorV2) {
    i_coordinator = VRFCoordinatorV2Interface(vrfCoordinatorV2);
    i_entranceFee = _entranceFee;
    i_keyhash = keyHash;
    i_subscriptionId = subscriptionId;
    i_callbackGasLimit = callbackGasLimit;
    s_lotteryState = LotteryState.OPEN;
    s_lastTimeStamp = block.timestamp;
    i_interval = interval;
}

function enterLottery() public payable {
    if (msg.value < i_entranceFee) {
        revert Lottery__NotEnoughEthSent();
    }
    if (s_lotteryState != LotteryState.OPEN) {
        revert Lottery__LotteryNotOpen();
    }
    s_players.push(payable(msg.sender));
    emit LotteryEnter(msg.sender);
}

/**
 * @dev This is the function that the Chainlink keeper node calls
 * they look for the "upkeepNeeded" to return true
 * The following should be true for upkeepNeeded to return true:
 * 1. Our time interval should have passed
 * 2. The lottery should have at least 1 player and some ETH
 * 3. Our subscription is funded with LINK
 * 4. The lottery should be in an "open" state
 */
function checkUpkeep(
    // this function is required by the chainlink Keepers nodes to check if an update is needed
    bytes memory /*checkData */ // "public" makes this funtion accessible to us in the contract so we can read from it in "performUpkeep"
)
    public
    view
    override
    returns (
        bool upkeepNeeded,
        bytes memory /*performData*/
    )
{
    bool isOpen = (s_lotteryState == LotteryState.OPEN);
    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);
}

function performUpkeep(
    /**
     * this was used to replace the "requestRandomWords()" function required by chainlink VRF nodes so that the chainlink Keepers nodes can call it automatically when an update is needed
     */
    bytes calldata /*performData */
) external override {
    (bool updateNeeded, ) = checkUpkeep("");
    if (!updateNeeded) {
        revert Lottery__NoUpdateNeeded();
    }
    s_lotteryState = LotteryState.CALCULATING;
    // Request the random Winner
    // Once we get it, do something with it
    uint256 requestId = i_coordinator.requestRandomWords(
        i_keyhash,
        i_subscriptionId,
        REQUEST_CONFIRMATIONS,
        i_callbackGasLimit,
        NUM_WORDS // number of random values we requested for
    );
    // the first event will come when the "requestRandomWords" function is fired in our Mock VRF Coordinator
    emit RequestedLotteryWinner(requestId); // this will actually be the second event emitted
}

function fulfillRandomWords(
    // this function is required and automatically called  by the chainlink VRF nodes to perform a task when valid request id is given and  a random number is generated
    uint256, /*requestId*/
    uint256[] memory randomWords
) internal override {
    uint256 indexOfWinner = randomWords[0] % s_players.length; // the vrf nodes send a random value using "randomWords" that can now be used in the contract as we please
    address payable recentWinner = s_players[indexOfWinner];
    s_recentWinner = recentWinner;
    s_lotteryState = LotteryState.OPEN;
    s_players = new address payable[](0);
    s_lastTimeStamp = block.timestamp;
    (bool success, ) = recentWinner.call{value: address(this).balance}("");
    if (!success) {
        revert Lottery__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 getLotteryState() public view returns (LotteryState) {
    return s_lotteryState;
}

function getNumWords() public pure returns (uint256) {
    return NUM_WORDS;
}

function getNumberOfPlayers() 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;
}

}

01edison commented 2 years ago

I replaced "Raffle" with "Lottery"

ObiwaleAyomideMoses commented 2 years ago

I replaced "Raffle" with "Lottery"

Check your arguments for the contract constructor especially your interval and hash value

Nikhil-blockchain commented 2 years ago

I also have the same issue. I believe the problem is in :

await vrfCoordinatorV2Mock.fulfillRandomWords(
                          txReceipt.events[1].args.requestId,
                          raffle.address
                      )
mohdziyadc commented 2 years ago

I replaced "Raffle" with "Lottery"

Also please check if you have called resolve() in your try block

laurentknauss commented 2 years ago

I also run into an issue when running a staging test. The CLI throws this error msg : For async tests and hooks, ensure "done()" is called . So the test will not finish and ends up being stopped by the mocha timeout set in hh config file.

Infatoshi commented 1 year ago

abondance68 I'm on the same page as you right now. I'm not sure if you've figured out the problem yet but what I've seen so far is that the promise isn't being resolved due to something blanking out in here: `raffle.once("WinnerPicked", async () => { console.log("WinnerPicked event fired!") try { // add our asserts here const recentWinner = await raffle.getRecentWinner() const raffleState = await raffle.getRaffleState() const winnerEndingBalance = await accounts[0].getBalance() const endingTimeStamp = await raffle.getLastTimeStamp()

                        await expect(raffle.getPlayer(0)).to.be.reverted
                        assert.equal(recentWinner.toString(), accounts[0].address)
                        assert.equal(raffleState, 0)
                        assert.equal(
                            winnerEndingBalance.toString(),
                            winnerStartingBalance.add(raffleEntranceFee).toString()
                        )
                        assert(endingTimeStamp > startingTimeStamp)
                        resolve()
                    } catch (error) {
                        console.log(error)
                        reject(error)
                    }`

I think this has to do with either fetching the random number from the chainlink oracle or could be that we can't enter the raffle. I'm interesting in hearing what you or anyone else has done with this.

naresh5033 commented 1 year ago

I had the same issue with the staging test, but i just ran the same test again with more funds(20 links) in my VRF and the test got passed.

Screenshot 2022-11-21 at 13 56 30 Screenshot 2022-11-21 at 13 55 48
naresh5033 commented 1 year ago

for the first time i ran the test i guess i had a problem in VRF saying that i'm having a low balance and add funds, i guess this might be a problem or if anyone found exactly whats going on would be helpful. nevertheless my test got passed

Screenshot 2022-11-21 at 14 05 03
bohdan-ly commented 1 year ago

One more comment about this issue. Don't paste "done" in the async (done) function, it will produce this issue as well

image
mtkonopka commented 1 year ago

Having same problem.. has anyone resolved? Marcin

GIRISHNP commented 1 year ago

Iam not able to pass staging test '// Inorder to test this in test network we need this things //1. Get our SubId for Chainlink VRF //2. Deploy our contract using the SubId //3.Register the contract with chainlink VRF and its subId //4. Register the contract with chainlink keepers //5. Run staging tests

const { assert, expect } = require("chai") const { network, deployments, ethers, getNamedAccounts } = require("hardhat") const { developmentChains, networkConfig } = require("../../helper-hardhat-config")

developmentChains.includes(network.name) ? describe.skip : describe("Raffle Unit Tests", function () { let raffle, raffleEntranceFee, deployer // , deployer const chainId = network.config.chainId

    beforeEach(async () => {
        deployer = (await getNamedAccounts()).deployer
        await deployments.fixture(["all"])
        raffle = await ethers.getContract("Raffle", deployer)
        vrfCoordinatorV2Mock = await ethers.getContract("VRFCoordinatorV2Mock", deployer)
        interval = await raffle.getInterval()
        raffleEntranceFee = await raffle.getEntranceFee()

    })
    describe("fulfillRandomWords", function () {
        it("works with live Chainlink Keepers and Chainlink VRF, we get a random winner", async function () {
            // enter the raffle
            console.log("Setting up test...")
            const startingTimeStamp = await raffle.getLastTimeStamp()
            const accounts = await ethers.getSigners()

            console.log("Setting up Listener...")
            await new Promise(async (resolve, reject) => {
                // setup listener before we enter the raffle
                // Just in case the blockchain moves REALLY fast
                raffle.once("WinnerPicked", async () => {
                    console.log("WinnerPicked event fired!")
                    try {
                        // add our asserts here
                        const recentWinner = await raffle.getRecentWinner()
                        const raffleState = await raffle.getRaffleState()
                        const winnerEndingBalance = await accounts[0].getBalance()
                        const endingTimeStamp = await raffle.getLastTimeStamp()

                        await expect(raffle.getPlayer(0)).to.be.reverted
                        assert.equal(recentWinner.toString(), accounts[0].address)
                        assert.equal(raffleState, 0)
                        assert.equal(
                            winnerEndingBalance.toString(),
                            winnerStartingBalance.add(raffleEntranceFee).toString()
                        )
                        assert(endingTimeStamp > startingTimeStamp)
                        resolve()
                    } catch (error) {
                        console.log(error)
                        reject(error)
                    }
                })
                // Then entering the raffle
                console.log("Entering Raffle...")
                const tx = await raffle.EnterRaffle({ value: raffleEntranceFee })
                await tx.wait(1)
                console.log("Ok, time to wait...")
                const winnerStartingBalance = await accounts[0].getBalance()

                // and this code WONT complete until our listener has finished listening!
            })
        })
    })
})'

Iam getting this issue 'ProviderError: Unsupported method: evm_snapshot. See available methods at https://docs.alchemy.com/alchemy/documentation/apis'

mud1tx commented 1 year ago

for the first time i ran the test i guess i had a problem in VRF saying that i'm having a low balance and add funds, i guess this might be a problem or if anyone found exactly whats going on would be helpful. nevertheless my test got passed Screenshot 2022-11-21 at 14 05 03

hey can you please tell that after creating contract of Raffel lottery how to deploy it so to get address to put in VRF consumer?As you have in your image Please reply.

I have the contract I just need its address to put in consumer address in vrf so how to get contract address what command should i use

GunjanSurti commented 1 year ago

Thanks for the help man. I was stuck at this

sthurley commented 1 year ago

Has anyone managed to fix this issue? I have tried copying code from the respoistory for the staging test, raflle.sol, and hardhat config to no avail.... Would really appreciate any input (have also increased the timeout of to 1000000ms and still no luck).

Staging test: `const { assert, expect } = require("chai") const { getNamedAccounts, ethers, network } = require("hardhat") const { developmentChains } = require("../../helper-hardhat-config")

developmentChains.includes(network.name) ? describe.skip : describe("Raffle Staging Tests", function () { let raffle, raffleEntranceFee, deployer

      beforeEach(async function () {
          deployer = (await getNamedAccounts()).deployer
          raffle = await ethers.getContract("Raffle", deployer)
          raffleEntranceFee = await raffle.getEntranceFee()
      })

      describe("fulfillRandomWords", function () {
          it("works with live Chainlink Keepers and Chainlink VRF, we get a random winner", async function () {
              // enter the raffle
              console.log("Setting up test...")
              const startingTimeStamp = await raffle.getLatestTimeStamp()
              const accounts = await ethers.getSigners()

              console.log("Setting up Listener...")
              await new Promise(async (resolve, reject) => {
                  // setup listener before we enter the raffle
                  // Just in case the blockchain moves REALLY fast
                  raffle.once("WinnerPicked", async () => {
                      console.log("WinnerPicked event fired!")
                      try {
                          // add our asserts here
                          const recentWinner = await raffle.getRecentWinner()
                          const raffleState = await raffle.getRaffleState()
                          const winnerEndingBalance = await accounts[0].getBalance()
                          const endingTimeStamp = await raffle.getLatestTimeStamp()

                          await expect(raffle.getPlayer(0)).to.be.reverted
                          assert.equal(recentWinner.toString(), accounts[0].address)
                          assert.equal(raffleState, 0)
                          assert.equal(
                              winnerEndingBalance.toString(),
                              winnerStartingBalance.add(raffleEntranceFee).toString()
                          )
                          assert(endingTimeStamp > startingTimeStamp)
                          resolve()
                      } catch (error) {
                          console.log(error)
                          reject(error)
                      }
                  })
                  // Then entering the raffle
                  console.log("Entering Raffle...")
                  const tx = await raffle.enterRaffle({ value: raffleEntranceFee })
                  await tx.wait(1)
                  console.log("Ok, time to wait...")
                  const winnerStartingBalance = await accounts[0].getBalance()

                  // and this code WONT complete until our listener has finished listening!
              })
          })
      })
  })

Raffle.sol ` // SPDX-License-Identifier: MIT

pragma solidity ^0.8.7;

import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol"; import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol"; import "@chainlink/contracts/src/v0.8/interfaces/AutomationCompatibleInterface.sol"; import "hardhat/console.sol";

/ Errors / error RaffleUpkeepNotNeeded(uint256 currentBalance, uint256 numPlayers, uint256 raffleState); error RaffleTransferFailed(); error RaffleSendMoreToEnterRaffle(); error RaffleRaffleNotOpen();

/**@title A sample Raffle Contract

Hardhat config ` require("@nomiclabs/hardhat-waffle") require("@nomiclabs/hardhat-etherscan") require("hardhat-deploy") require("solidity-coverage") require("hardhat-gas-reporter") require("hardhat-contract-sizer") require("dotenv").config()

const GOERLI_RPC_URL = process.env.GOERLI_RPC_URL || "https://eth-rinkeby" //do process.env.goerly_rpc_url, and if that doesn't work, do the other seperated by || const PRIVATE_KEY = process.env.PRIVATE_KEY || "0xkey" const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY || "key" const COINMARKETCAP_API_KEY = process.env.COINMARKETCAP_API_KEY || "key"

/* @type import('hardhat/config').HardhatUserConfig / module.exports = { defaultNetwork: "hardhat", networks: { hardhat: { chainId: 31337, blockConfirmations: 1 }, goerli: { url: GOERLI_RPC_URL, accounts: [PRIVATE_KEY], chainId: 5, blockConfirmations: 6, }, }, etherscan: { apiKey: ETHERSCAN_API_KEY, }, gasReporter: { enabled: false, currency: "USD", outputFile: "gas-reporter.txt", noColors: true, }, solidity: "0.8.7", namedAccounts: { deployer: { default: 0, }, player: { default: 1, }, }, //set time out for how long it takes an event to be emitted. 200 seconds mocha:{ timeout: 1000000 //500 seconds max for testing }

}; `

stevegee1 commented 1 year ago

The async part of your test is simply not resolving before exceeding the timeout set by your "mocha">>hardhat.config due to one or more of the following reasons:

  1. To get it fixed, you have to look at your performUpkeep function.
  2. Your fulfillRandomWords (chainlink VRF)
  3. If both emitted the expected events using "expect" check from {chai} library. Then, your event listener - "raffle.once" is not listening to specified emitted event. Hence the timeout