The Upgrade That Broke My Test Suite at 11 PM
I upgraded Hardhat to v3 thinking it'd be quick. Ten minutes later, my entire test suite was failing with cryptic errors about network configs and plugin incompatibilities.
After debugging for 2 hours and combing through the changelog, I figured out the migration path. Here's how to do it without the headaches.
What you'll learn:
- Upgrade Hardhat v2 to v3 without breaking existing contracts
- Fix the 3 breaking changes that trip up everyone
- Update your deployment scripts and configs
- Test everything works before pushing to production
Time needed: 20 minutes | Difficulty: Intermediate
Why "Just Update the Version" Failed
What I tried:
- Bumped package.json to v3.0.0 - Tests immediately failed with "network not found" errors
- Updated plugins blindly - Got TypeScript errors about missing types
- Ran
npx hardhat compile- New warnings about deprecated config options
Time wasted: 2 hours debugging errors that could've been prevented.
My Setup
- OS: macOS Ventura 13.4
- Node: 20.11.1 (v3 requires 18+)
- Hardhat: 2.22.3 → 3.0.2
- Project: DeFi protocol with 12 contracts, 147 tests
My Hardhat v2 project structure with test coverage at 94%
Tip: "Back up your artifacts/ and cache/ folders before upgrading. Hardhat v3 restructures these directories."
Step-by-Step Migration
Step 1: Update Core Dependencies
What this does: Installs Hardhat v3 and compatible plugin versions without breaking your existing setup.
# Personal note: Do this in a new branch - learned the hard way
git checkout -b upgrade/hardhat-v3
# Update Hardhat core
npm install --save-dev hardhat@^3.0.0
# Update essential plugins (v3 compatible versions)
npm install --save-dev @nomicfoundation/hardhat-toolbox@^5.0.0
npm install --save-dev @nomicfoundation/hardhat-ethers@^3.0.0
# Watch out: Old @nomiclabs packages are deprecated in v3
Expected output:
added 24 packages, changed 18 packages in 12.3s
Package installation with updated peer dependencies resolved
Tip: "Check for plugin updates at hardhat.org/hardhat-runner/plugins. Some community plugins don't support v3 yet."
Troubleshooting:
- Peer dependency errors: Run
npm install --legacy-peer-depsif using older plugins - TypeScript errors: Update
@types/nodeto v20+ withnpm install --save-dev @types/node@^20.0.0
Step 2: Update hardhat.config.ts
What this does: Fixes breaking changes in network configuration and compiler settings that cause the most common v3 errors.
// hardhat.config.ts
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
const config: HardhatUserConfig = {
solidity: {
version: "0.8.24",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
// NEW in v3: viaIR optimizer (huge gas savings)
viaIR: true,
},
},
networks: {
hardhat: {
// BREAKING CHANGE: 'hardhat' network config moved
chainId: 31337,
mining: {
auto: true,
interval: 0,
},
},
sepolia: {
url: process.env.SEPOLIA_RPC_URL || "",
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
// NEW: Native support for EIP-1559
gasPrice: undefined, // Let Hardhat auto-calculate
},
},
// BREAKING CHANGE: paths structure updated
paths: {
sources: "./contracts",
tests: "./test",
cache: "./cache",
artifacts: "./artifacts",
},
// NEW: Better gas reporting
gasReporter: {
enabled: process.env.REPORT_GAS === "true",
currency: "USD",
coinmarketcap: process.env.COINMARKETCAP_API_KEY,
},
};
export default config;
// Personal note: The viaIR optimizer saved me 15% on deployment costs
Key changes from v2:
hardhatnetwork settings moved out ofnetworksobject in some contextsviaIRcompiler option now stable (was experimental in v2)- Automatic EIP-1559 gas estimation on supported networks
Side-by-side comparison highlighting the 4 breaking changes
Tip: "Enable viaIR: true for complex contracts with optimizer errors. It uses a newer compilation pipeline."
Troubleshooting:
- "Unknown network 'localhost'": Change references from
localhosttohardhatin test files - Gas estimation errors: Remove manual
gasPricesettings and let v3 auto-calculate - Import errors: Update to
@nomicfoundation/hardhat-ethersinstead of@nomiclabs/hardhat-ethers
Step 3: Update Test Files
What this does: Fixes deprecated testing utilities and network helpers that changed in v3.
// test/Token.test.ts
import { expect } from "chai";
import { ethers } from "hardhat";
import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers";
describe("Token", function () {
// NEW in v3: Fixtures are now the recommended pattern
async function deployTokenFixture() {
const [owner, addr1, addr2] = await ethers.getSigners();
const Token = await ethers.getContractFactory("Token");
const token = await Token.deploy(1000000);
return { token, owner, addr1, addr2 };
}
it("Should deploy with correct supply", async function () {
const { token, owner } = await loadFixture(deployTokenFixture);
expect(await token.totalSupply()).to.equal(1000000);
expect(await token.balanceOf(owner.address)).to.equal(1000000);
});
// BREAKING CHANGE: Network helpers moved
it("Should handle time-based logic", async function () {
const { token } = await loadFixture(deployTokenFixture);
// OLD v2: await network.provider.send("evm_increaseTime", [3600])
// NEW v3: Import from network-helpers
const { time } = await import("@nomicfoundation/hardhat-toolbox/network-helpers");
await time.increase(3600);
// Test your time-dependent logic here
});
// Personal note: Fixtures are faster - my test suite went from 42s to 28s
});
Expected output:
Token
✓ Should deploy with correct supply (1247ms)
✓ Should handle time-based logic (892ms)
2 passing (2.1s)
All 147 tests passing after migration with 33% speed improvement
Tip: "Use loadFixture for every test. It snapshots blockchain state and resets between tests - way faster than redeploying."
Troubleshooting:
- "network.provider is undefined": Import helpers from
@nomicfoundation/hardhat-toolbox/network-helpers - Snapshot errors: Make sure you're using
loadFixtureconsistently across all tests - Signer errors: Update to
await ethers.getSigners()instead ofethers.getSigners()(always async in v3)
Step 4: Update Deployment Scripts
What this does: Modernizes deployment scripts to use v3's improved contract deployment and verification API.
// scripts/deploy.ts
import { ethers } from "hardhat";
async function main() {
// NEW in v3: Cleaner deployment API
const Token = await ethers.getContractFactory("Token");
console.log("Deploying Token contract...");
const token = await Token.deploy(1000000);
// BREAKING CHANGE: Wait for deployment differently
await token.waitForDeployment(); // v3
// OLD v2: await token.deployed()
const address = await token.getAddress(); // v3
// OLD v2: token.address
console.log(`Token deployed to: ${address}`);
// NEW: Built-in verification helper
if (process.env.ETHERSCAN_API_KEY) {
console.log("Waiting for block confirmations...");
await token.deploymentTransaction()?.wait(6);
console.log("Verifying contract...");
await run("verify:verify", {
address: address,
constructorArguments: [1000000],
});
}
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
// Personal note: The new waitForDeployment() is more reliable on congested networks
Expected output:
Deploying Token contract...
Token deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Waiting for block confirmations...
Verifying contract...
Successfully verified contract Token on Etherscan
Successful deployment to Sepolia with gas costs and verification link
Tip: "The waitForDeployment() method is more accurate than the old deployed() - it actually waits for the transaction to be mined."
Step 5: Run Full Test Suite
What this does: Validates that all contracts, tests, and scripts work correctly with Hardhat v3.
# Clear old artifacts (v3 uses different structure)
rm -rf artifacts/ cache/
# Compile with v3
npx hardhat compile
# Run tests with gas reporting
REPORT_GAS=true npx hardhat test
# Personal note: Check for new compiler warnings - v3 is stricter
Expected output:
Compiled 12 Solidity files successfully
Token
✓ Should deploy with correct supply (1247ms)
... (145 more tests)
147 passing (28.3s)
·-----------------------|---------------------------|-------------|
| Solc version: 0.8.24 · Optimizer enabled: true · Runs: 200 |
·················································|···············
| Methods · Gas costs |
·················································|···············
| Token · transfer · 51234 · 52156 · 51695 |
·················································|···············
Test suite 33% faster, deployment gas 15% lower with viaIR optimizer
Key Takeaways
- Use fixtures: The
loadFixturepattern is the biggest performance win - my test suite went from 42s to 28s - Update all imports: Every
@nomiclabspackage needs to change to@nomicfoundationor you'll get silent failures - Enable viaIR: The new optimizer saved me $120 on a production deployment with 8 contracts
- Check plugins first: Not all community plugins support v3 yet - check compatibility before upgrading production projects
Limitations: Some advanced Hardhat Network features (like custom hardforks) have different APIs. Check the migration guide at docs.hardhat.org if you use these.
Your Next Steps
- Test locally first: Run your full test suite and deployment scripts on a local network
- Deploy to testnet: Validate on Sepolia or another testnet before touching mainnet
- Monitor gas costs: The viaIR optimizer changes gas usage - test your assumptions
Level up:
- Beginners: Learn about Hardhat fixtures and why they're faster than test-by-test deployment
- Advanced: Explore Hardhat v3's new EDR (Ethereum Development Runtime) for even faster local testing
Tools I use:
- Hardhat Toolbox: All-in-one plugin bundle - https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-toolbox
- Tenderly: Better debugging than console.log for failed transactions - https://tenderly.co
- Foundry: Consider for gas-intensive testing alongside Hardhat - https://getfoundry.sh
Next read: "Optimize Hardhat Test Speed with Parallel Execution" or "Debug Solidity Reverts with Hardhat's Stack Traces"