Simulating Production State with Hardhat Mainnet Forking
Learn how to use Hardhat Mainnet Forking to simulate production environments, pin block numbers for deterministic testing, and impersonate live accounts for administrative testing.
08 Jul 2026, 07:24 UTC

The Problem: Testing Against Live State Without Live Risk
Testing smart contracts on a local network or a public testnet often fails to capture the complexity of production. You cannot easily simulate interactions with existing liquidity pools, DAO governance vaults, or specific "whale" accounts without deploying massive amounts of mock data or spending real assets.
The solution is Mainnet Forking. This allows you to run a local instance of the Hardhat Network that mirrors the state of a live blockchain. When your local node needs data it doesn't have, it fetches it from a remote JSON-RPC provider, allowing you to interact with real contracts as if they were deployed locally, without spending actual ETH.
How Forking Works
Hardhat Network implements a caching mechanism. When you perform a call to a contract address on a forked network, the node checks its local cache. If the data is missing, it makes a request to the remote provider (e.g., Alchemy or Infura) and stores the result locally. This ensures that subsequent calls to the same block or state are fast, while still providing a window into the live chain.
Configuration Example
To implement forking, modify your hardhat.config.js. It is critical to specify a blockNumber; otherwise, the fork will track the very latest block, which changes constantly and will cause your tests to be non-deterministic (failing randomly as the live state shifts).
const { HardhatUserConfig } = require("hardhat/config");
const config: HardhatUserConfig = {
solidity: "0.8.24",
networks: {
hardhat: {
forking: {
url: "https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY",
blockNumber: 19000000 // Pinning the block ensures test consistency
}
}
}
};
module.exports = config;
Advanced Simulation: Account Impersonation
A common requirement when forking is the ability to trigger functions that only specific addresses (like a multisig owner or a protocol admin) can call. Hardhat provides the hardhat_impersonateAccount RPC method for this purpose.
Run the following logic within a Hardhat test or script to send a transaction from a live address you do not own:
const { ethers } = require("hardhat");
async function simulateAdminAction() {
const adminAddress = "0x..."; // The live address you want to impersonate
// 1. Tell the local node to allow transactions from this address
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [adminAddress],
});
// 2. Get a signer for that address
const signer = await ethers.getSigner(adminAddress);
// 3. Execute a transaction as that user
const contract = await ethers.getContractAt("MyContract", "0x...");
await contract.connect(signer).restrictedFunction();
console.log("Transaction executed as impersonated account");
}
Operational Limits and Risks
- Disk Space: The local node caches state data. If you are running extensive tests across many different blocks, your
node_modulesor temporary system folders can grow significantly. - RPC Rate Limits: Every uncached request hits your remote provider. If your test suite performs thousands of unique state reads, you may hit your API provider's rate limit, causing the local node to hang or return 429 errors.
- Latency: The first time a test touches a specific block, there is a network round-trip delay. This makes the first run of a test suite significantly slower than subsequent runs.
Diagnostic Check: Verifying the Fork
To verify that your fork is functioning correctly, run the local node via npx hardhat node and use a script to call a getter on a well-known contract (like the WETH or USDC contract). If the returned balance or decimal value matches the live chain at your pinned blockNumber, the fork is active.
Rollback and State Reset
Because forking happens in memory (and a local cache), there is no "rollback" required for the live mainnet—you are not actually changing the live chain. To reset your local state, simply stop the npx hardhat node process and restart it. This clears all impersonated accounts and local transaction history.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.