Recommended resources
The freely available resources this lesson will direct you to.
Hello! Welcome back to the course.
In our last lesson, we built a comprehensive test suite for our staking contract, confirming that its core functions for staking, withdrawing, and claiming rewards behave as expected. We also introduced the concept of "time travel" using the low-level ethers.provider.send("evm_increaseTime", ...) command, which was crucial for testing reward accrual.
Today, we will refine that technique. While the low-level provider method works, it's a bit clunky. Hardhat offers a much more elegant and powerful set of tools for this exact purpose. Our goal for this lesson is to use Hardhat's advanced time manipulation features to test time-dependent reward accrual. We will be moving from the basic tool to the professional standard, making our tests cleaner, more readable, and more precise.
Specifically, we will explore the @nomicfoundation/hardhat-network-helpers library, a core part of the Hardhat testing ecosystem.
1. Introducing the Hardhat Network Helpers time Object
Hardhat provides a suite of helper functions designed to make testing common scenarios easier. For our purposes, the most important of these is the time object, which provides a clean API for manipulating the blockchain's clock in your local test environment.
This is a significant step up from the manual provider.send calls. With your backend experience, you can think of this as moving from raw system calls to using a well-documented standard library—it's more robust, readable, and less error-prone.
To use these helpers, you first import them at the top of your test file:
const { time } = require("@nomicfoundation/hardhat-network-helpers");
Let's watch a short clip that introduces these helpers and shows where they come from.
Hardhat Testing Tutorial | Solidity Smart Contract Testing Developer | Hardhat Testing Course
This video segment from 'Hardhat Testing Tutorial' by Daulat Hussain introduces the key Hardhat testing utilities we'll be using today. Pay close attention to the import of time and loadFixture.
Watch from 25:10 to 29:13. The key part is the import from @nomicfoundation/hardhat-network-helpers. The video also introduces expect and ethers, which you're already familiar with, reinforcing the overall testing setup.
The time object gives us access to several useful functions, but we'll focus on three key ones:
time.latest(): An async function that returns the timestamp of the last mined block. This is essential for calculating future points in time.time.increase(seconds): An async function that fast-forwards the blockchain's time by a specified number of seconds and mines a new block to apply the change.time.increaseTo(timestamp): An async function that advances the blockchain's time to a specific future timestamp. This is perfect for testing events that should happen at a precise moment.
2. Testing Reward Accrual with time.increase()
Let's put this into practice by re-testing our reward logic. In the previous lesson, we used evm_increaseTime and evm_mine as two separate steps. The time.increase() helper combines these into a single, readable command.
Imagine a user stakes some tokens. We want to verify that after one hour, they have earned the correct amount of rewards.
The following guide provides a perfect example of this in a staking contract test.
Build and Deploy a Simple Staking Contract
The article 'Build and Deploy a Simple Staking Contract' contains a test suite that directly demonstrates our learning goal. We will focus on the test case for calculating rewards.
In the provided code for test/SimpleStaking.js, find the test case that starts with it("Should calculate rewards correctly", ...). Notice how it uses await time.increase(3600); to simulate the passage of one hour. Then, analyze how it calculates the expectedRewards and compares them to the result from the contract's checkRewards function. This is a model example of how to test time-based logic.
Let's adapt that example to our StakingRewards contract. Here's how a refined test for reward accrual would look in test/StakingRewards.test.js:
// At the top of your test file
const { time } = require("@nomicfoundation/hardhat-network-helpers");
// Inside your describe("Rewards", ...) block
it("Should accrue rewards correctly over a set duration", async function () {
const { stakingRewards, addr1, rewardsToken } = await loadFixture(deployStakingFixture); // Assuming you've set up a fixture
const stakeAmount = ethers.utils.parseUnits("100", 18);
// User 1 stakes 100 tokens
await stakingRewards.connect(addr1).stake(stakeAmount);
// The duration we want to test
const ONE_HOUR_IN_SECONDS = 3600;
// Fast-forward time by one hour
await time.increase(ONE_HOUR_IN_SECONDS);
// Check the rewards earned by user 1
const earnedRewards = await stakingRewards.earned(addr1.address);
// Now, calculate the expected rewards manually
// This depends on your contract's rewardRate logic.
// Let's assume a rewardRate was set to 1 token per second for simplicity.
const rewardRate = await stakingRewards.rewardRate(); // Assuming this is 1e18
const expectedRewards = rewardRate.mul(ONE_HOUR_IN_SECONDS);
// Assert that the earned rewards match our calculation
// We use `closeTo` to account for potential minor rounding differences (1-2 wei)
expect(earnedRewards).to.be.closeTo(expectedRewards, ethers.utils.parseUnits("0.001", 18));
});
Notice how much cleaner await time.increase(3600); is compared to the two provider.send calls. It clearly states the intent: "increase time by one hour."

3. Testing Unlock Times with time.increaseTo()
While time.increase() is great for relative time changes ("fast forward by one week"), time.increaseTo() is better for absolute points in time ("fast forward until the unlock date").
This is extremely useful for testing vesting schedules, lock-up periods, or any feature that is tied to a specific timestamp. Let's add a test for our staking contract's withdraw function. The function should fail if the lock-up period isn't over.
The Hardhat documentation provides the canonical explanation for this.
Testing contracts | Hardhat Documentation
The official Hardhat documentation explains how to use time.increaseTo to test a time-locked contract. This is the exact pattern we need.
Read the section 'Manipulating the time of the network'. It shows how to deploy a contract with a future unlockTime and then use await time.increaseTo(unlockTime); to instantly jump to that exact moment before testing the withdraw function. This is a powerful and precise testing pattern.
Let's apply this to a hypothetical test for our StakingRewards contract. Imagine our contract has a global lock-up period and withdraw should fail before it ends.
First, in our deployment fixture, we would set the end of the lock-up period:
// Inside a deploy fixture function
async function deployStakingFixture() {
// ... other deployment logic ...
const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;
const lockupEndTime = (await time.latest()) + ONE_YEAR_IN_SECS;
// Deploy StakingRewards Contract, passing the lockup time to the constructor
const StakingRewards = await ethers.getContractFactory("StakingRewards");
const stakingRewards = await StakingRewards.deploy(
stakingToken.address,
rewardsToken.address,
lockupEndTime // Assuming constructor accepts this
);
// ... return deployed contracts and values ...
return { stakingRewards, lockupEndTime, addr1 };
}
Now, we can write a test that uses time.increaseTo():
// Inside your describe("Withdrawing", ...) block
it("Should only allow withdrawal after the lock-up period ends", async function () {
const { stakingRewards, lockupEndTime, addr1 } = await loadFixture(deployStakingFixture);
const stakeAmount = ethers.utils.parseUnits("100", 18);
await stakingRewards.connect(addr1).stake(stakeAmount);
// 1. Test that withdrawal fails BEFORE the lockup time
await expect(stakingRewards.connect(addr1).withdraw(stakeAmount))
.to.be.revertedWith("Lock-up period not over");
// 2. Jump forward in time to the exact lockup end moment
await time.increaseTo(lockupEndTime);
// 3. Test that withdrawal now succeeds
// The `not.to.be.reverted` assertion checks that the transaction completes successfully.
await expect(stakingRewards.connect(addr1).withdraw(stakeAmount))
.to.not.be.reverted;
});
This test precisely verifies the time boundary, something that would be very difficult to do without these helpers.
Test your understanding!
You want to write a test for your staking contract. The rewardRate is set to 1,000 wei per second. A user stakes 100 tokens. You want to verify that after exactly 5 minutes, the earned() amount for that user is 300,000 wei.
Which time helper function would you use, and what value would you pass to it?
Show answer
You would use await time.increase(300);.
The duration is 5 minutes, which is 5 * 60 = 300 seconds. time.increase() is perfect for simulating a specific duration passing. After this call, you would check if earned() returns 300 * 1000 = 300,000 wei.
Conclusion
You have now mastered the professional-grade tools for testing time-dependent logic in Hardhat. By replacing low-level provider calls with the high-level time helpers, you've made your tests more robust, readable, and maintainable.
Key Takeaways:
- The
@nomicfoundation/hardhat-network-helperslibrary provides essential tools for testing. - The
timeobject is the standard way to manipulate the blockchain's clock in tests. time.increase(seconds)is used to fast-forward time by a relative duration.time.increaseTo(timestamp)is used to advance time to an absolute, specific moment.- These helpers automatically mine a new block, combining
evm_increaseTimeandevm_mineinto a single, clean step.
Preview of the Next Lesson:
Our smart contracts are now feature-complete and rigorously tested. They are ready for users! In the next module, we will shift our focus from the backend to the frontend. You will learn how to set up a React application with TypeScript and use the ethers.js library to build a user interface that can connect to MetaMask and interact with your deployed contracts. This is where your application truly comes to life.