1

I am trying to unit test my smart contract and I am struggling to figure out why I am getting an error for invalid dates for a specific describe and if statement. I have been struggling all day to figure out why it is saying the dates I am setting are invalid, when clearly it is days in advance and should be valid by the logic of my contract.

This type of error is causing issues for every it statement I am trying to describe going forward.

Here is my testing script thus far:

const { ethers } = require("hardhat");
const { expect } = require("chai");
require("@nomicfoundation/hardhat-chai-matchers");
const { time } = require("@nomicfoundation/hardhat-network-helpers");

describe("CrowdGaming", function () {
  let crowdGaming; // contract instance
  let contractOwner; // contract owner for deployment
  let campaignOwner; // campaign owner
  let user; // user for owner checks
  let user2; // user for campaign deploys
  let donor; // donor for donation checks
  const boostPrice = ethers.utils.parseEther("0.1"); // boost price of 0.1 ETH
  const rateLimit = 86400; // rate limit for 24 hours in seconds
  const maxCampaignLength = 2592000; // max campaign length of 30 days in seconds
  const currentTime = Math.floor(new Date().getTime() / 1000); // current time for launching campaigns in seconds

  before(async function () {
    [contractOwner, campaignOwner, user, user2, donor] =
      await ethers.getSigners();

    const CrowdGaming = await ethers.getContractFactory("CrowdGaming");
    crowdGaming = await CrowdGaming.connect(contractOwner).deploy(
      contractOwner.address,
      boostPrice, // 0.1 ETH
      rateLimit, // 1 day
      maxCampaignLength // 30 days
    );
    await crowdGaming.deployed();
  });

  // Launching
  describe("if a user creates a campaign it", function () {
    it("should not allow the user to create a campaign in the past", async function () {
      await expect(
        crowdGaming.connect(campaignOwner).launchCampaign(
          "Campaign 2",
          "Description",
          ethers.utils.parseEther("1"),
          currentTime - 3600,
          currentTime + 86400 // end time is 1 day from now
        )
      ).to.be.revertedWithCustomError(crowdGaming, "InvalidStartDate");
    });
    it("should not allow the user to set an end date in the past", async function () {
      await expect(
        crowdGaming.connect(campaignOwner).launchCampaign(
          "Campaign3",
          "Description",
          ethers.utils.parseEther("1"),
          currentTime + 3600,
          currentTime - 86400 // end time is one day before the start date
        )
      );
    });
    // Should not allow user to create campaign that's longer than 30 days
    it("should not allow the user to create a campaign past the given length for campaigns", async function () {
      await expect(
        crowdGaming.connect(campaignOwner).launchCampaign(
          "Campaign 2",
          "Description",
          ethers.utils.parseEther("1"),
          currentTime + 3600,
          currentTime + 2692000 // end time is over 30 days
        )
      ).to.be.revertedWithCustomError(crowdGaming, "InvalidLength");
    });
    // Should not allow the goal to be less than or equal to 0
    it("should not allow the goal to be less than or equal to 0", async function () {
      await expect(
        crowdGaming.connect(campaignOwner).launchCampaign(
          "Campaign 2",
          "Description",
          ethers.utils.parseEther("0"),
          currentTime + 3600, // start time is 1 hour from now
          currentTime + 86400 // end time is 1 day from now
        )
      ).to.be.revertedWithCustomError(crowdGaming, "InvalidAmount");
    });
  });

  // Cancelling
  describe("if a campaign owner cancels a campaign it", function () {
    // should not allow campaigns that have started to be cancelled
    it("should only allow campaigns that haven't started to be cancelled", async function () {
      await crowdGaming.connect(campaignOwner).launchCampaign(
        "Campaign",
        "Description",
        ethers.utils.parseEther("1"),
        currentTime + 3600, // start time is 1 hour from now to allow cancellation
        currentTime + 86400 // end time is 1 day from now
      );

      // Cancel campaign
      expect(await crowdGaming.connect(campaignOwner).cancelCampaign(1));
      const campaign = await crowdGaming.campaigns(1);
      expect(campaign.cancelled).to.be.true;
    });
    // should allow only the campaign owner to cancel
    it("should only allow campaign owner to cancel", async function () {
      await crowdGaming.connect(user).launchCampaign(
        "Campaign",
        "Description",
        ethers.utils.parseEther("1"),
        currentTime + 3600, // start time is 1 hour from now to allow cancellation
        currentTime + 86400 // end time is 1 day from now
      );
      // Try to cancel the campaign
      await expect(
        crowdGaming.connect(user2).cancelCampaign(1)
      ).to.be.revertedWithCustomError(crowdGaming, "NotOwner");
      const campaign = await crowdGaming.campaigns(1);
      expect(campaign.cancelled).to.be.true;
    });
  });

  // Pledging
  describe("if a donor pledges to a campaign it", function () {
    // should only allow donations above 0
    it("should only allow donations above 0", async function () {
      await crowdGaming.connect(user2).launchCampaign(
        "Campaign",
        "Description",
        ethers.utils.parseEther("1"),
        currentTime + 3600,
        currentTime + 604800 // 1 week from start time
      );

      // Try to pledge 0
      const amount = ethers.utils.parseEther("0");
      await expect(
        crowdGaming.connect(donor).pledgeTo(1, { value: amount })
      ).to.be.revertedWithCustomError(crowdGaming, "InvalidAmount");
    });
  });

  // Refunding
  describe("if a donor refunds from a campaign it", function () {
    it("should refund the correct amount and reset amount to 0", async function () {
      // Launch a campaign with goal of 1 ETH, start time 1 hour from now, end time 1 day from now
      await crowdGaming
        .connect(user2)
        .launchCampaign(
          "Campaign",
          "Description",
          ethers.utils.parseEther("1"),
          currentTime + 3600,
          currentTime + 86400
        );

      // Pledge 0.5 ETH to the campaign
      const amount = ethers.utils.parseEther("0.5");
      await crowdGaming.connect(donor).pledgeTo(4, { value: amount });

      // Increase time to 1 day after campaign end time
      await time.increase(time.duration.days(1));

      // Refund donation
      const initialBalance = await ethers.provider.getBalance(donor.address);
      const tx = await crowdGaming.connect(donor).refund(1);
      const receipt = await tx.wait();
      const gasUsed = receipt.gasUsed.mul(tx.gasPrice);
      const finalBalance = await ethers.provider.getBalance(donor.address);

      // Check that the refund was successful
      expect(finalBalance).to.equal(initialBalance.add(amount).sub(gasUsed));
      // Check that the donor's amount pledged is reset to 0
      expect(await crowdGaming.pledgedAmount(1, donor.address)).to.equal(0);
    });
  });

The errors I am getting lie in the last two it statements:

it("should only allow donations above 0", async function () {
      await crowdGaming.connect(user2).launchCampaign(
        "Campaign",
        "Description",
        ethers.utils.parseEther("1"),
        currentTime + 3600,
        currentTime + 604800 // 1 week from start time
      );

      // Try to pledge 0
      const amount = ethers.utils.parseEther("0");
      await expect(
        crowdGaming.connect(donor).pledgeTo(1, { value: amount })
      ).to.be.revertedWithCustomError(crowdGaming, "InvalidAmount");
    });
    ```
    
and

it("should refund the correct amount and reset amount to 0", async function () { // Launch a campaign with goal of 1 ETH, start time 1 hour from now, end time 1 day from now await crowdGaming .connect(user2) .launchCampaign( "Campaign", "Description", ethers.utils.parseEther("1"), currentTime + 3600, currentTime + 86400 );

  // Pledge 0.5 ETH to the campaign
  const amount = ethers.utils.parseEther("0.5");
  await crowdGaming.connect(donor).pledgeTo(4, { value: amount });

  // Increase time to 1 day after campaign end time
  await time.increase(time.duration.days(1));

  // Refund donation
  const initialBalance = await ethers.provider.getBalance(donor.address);
  const tx = await crowdGaming.connect(donor).refund(1);
  const receipt = await tx.wait();
  const gasUsed = receipt.gasUsed.mul(tx.gasPrice);
  const finalBalance = await ethers.provider.getBalance(donor.address);

  // Check that the refund was successful
  expect(finalBalance).to.equal(initialBalance.add(amount).sub(gasUsed));
  // Check that the donor's amount pledged is reset to 0
  expect(await crowdGaming.pledgedAmount(1, donor.address)).to.equal(0);
});
```

The error it is throwing is: VM Exception while processing transaction: reverted with custom error 'InvalidStartDate()'

And here are the functions from the contract and I have custom errors to throw to decrease gas:

    function pledgeTo(uint _id) external payable {
        Campaign storage campaign = campaigns[_id];
        if (block.timestamp < campaign.startAt) {
            revert InvalidStartDate();
        }
        if (block.timestamp > campaign.endAt) {
            revert InvalidEndDate();
        }
        if (msg.value <= 0) {
            revert InvalidAmount();
        }
        // Prevent reentry
        campaign.pledged += msg.value;
        pledgedAmount[_id][msg.sender] += msg.value;
        donorCampaigns[msg.sender].push(_id);
        emit Pledge(_id, msg.sender, msg.value);
    }

and

    function refund(uint _id) external {
        Campaign storage campaign = campaigns[_id];
        if (block.timestamp < campaign.endAt) {
            revert InvalidEndDate();
        }
        if (campaign.pledged >= campaign.goal) {
            revert GoalMet();
        }
        if (campaign.owner == address(0)) {
            revert InvalidCampaign();
        }
        // Prevent reentry
        uint balance = pledgedAmount[_id][msg.sender];
        pledgedAmount[_id][msg.sender] = 0;
        (bool success, ) = msg.sender.call{value: balance}("");
        require(success, "Failed to withdraw funds");
        emit Refund(_id, msg.sender, balance);
    }

What is the issue here? I am driving myself insane.

1 Answer 1

1

Your code looks fine in principle. Probably here, the problem is the block time of the hardhat node instance is in the past. In order to check the block time, you could do:

 const blockNr =await  hre.ethers.provider.getBlockNumber()
 const lastBlock  = await  hre.ethers.provider.getBlock(blockNr)
 console.log(new Date(lastBlock.timestamp*1000))

Here you will already see that the block time is in the past, so therefore, your contract was reverting.

To solve the problem you can do two approaches:

1- Declare currenTime = lastBlock.timestampor

2- Incresae the time of the hardhat node doing:

hre.ethers.provider.send("evm_increaseTime", [TIME_DIFFERENCE])   // add TIME_DIFFERENCE in seconds
hre.ethers.provider.send("evm_mine")      // mine the next block

Hope it helps!

4
  • Thanks for the reply! I will check in these today and see if they work. I am still fairly new to unit testing contracts so when it comes to time based testing, I get confused
    – OhItsPM
    Commented Apr 15, 2023 at 14:29
  • great, feed back if it works!!
    – donoso.eth
    Commented Apr 15, 2023 at 14:59
  • Thanks again! Using the following worked: ``` const blockNumber = await hre.ethers.provider.getBlockNumber(); const lastBlock = await hre.ethers.provider.getBlock(blockNumber); const currentTime = lastBlock.timestamp; ``` I added it to every it statement and decided to use loadFixture for my testing. everything seems to be configured properly now with time!
    – OhItsPM
    Commented Apr 15, 2023 at 16:52
  • glad to have helped!
    – donoso.eth
    Commented Apr 16, 2023 at 17:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.