Hardhat mainnet forking allows developers to create local development environments based on the current state of the Ethereum mainnet or testnets. This is very useful for testing DeFi protocols and interacting with existing contracts.
Basic Usage:
Configure forking in hardhat.config.js:
javascriptnetworks: { hardhat: { forking: { url: process.env.MAINNET_RPC_URL, blockNumber: 15000000 // Optional: specify fork block } } }
Use Cases:
- Testing Interaction with Mainnet Contracts
javascriptconst uniswapRouter = await ethers.getContractAt( "IUniswapV2Router02", "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D" );
- Simulating Real Market Conditions
javascript// Get token prices on mainnet const dai = await ethers.getContractAt("IERC20", DAI_ADDRESS); const price = await someOracle.getPrice(dai.address);
- Testing DeFi Protocol Integration
javascript// Test Aave integration in forked environment const pool = await ethers.getContractAt( "IPool", "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2" );
Advanced Configuration:
javascriptnetworks: { hardhat: { forking: { url: process.env.MAINNET_RPC_URL, blockNumber: 15000000, enabled: true }, chainId: 1 // Keep same chainId as mainnet } }
Usage in Tests:
javascriptdescribe("Mainnet Fork Tests", function () { it("should interact with Uniswap", async function () { // Get test account const [signer] = await ethers.getSigners(); // Get mainnet USDC const usdc = await ethers.getContractAt("IERC20", USDC_ADDRESS); const balance = await usdc.balanceOf(signer.address); console.log("USDC Balance:", balance.toString()); }); });
Considerations:
- Requires RPC node to support archive data
- Forking increases memory usage
- Ensure sufficient test ETH
- Consider using fixed block numbers for test reproducibility
Best Practices:
- Use environment variables to store RPC URLs
- Specify fixed block numbers for test stability
- Use archive nodes in CI/CD
- Regularly update fork block numbers