Web3 Development
Foundry vs Hardhat in 2026: Which Framework Should You Use
TL;DR
If you are starting a new Solidity project in 2026, use Foundry. It compiles faster, tests faster, and the ability to write tests in Solidity instead of JavaScript eliminates an entire class of bugs where your test logic diverges from your contract logic. Fuzz testing is built in, not bolted on. The forge CLI is ruthlessly fast. I switched my primary workflow to Foundry two years ago and have not looked back for any serious contract work. That said, Hardhat still has a place. If your team is JavaScript-heavy, if you need deep plugin ecosystem support, or if you are maintaining a legacy codebase that already runs on Hardhat, ripping it out is not always worth the cost. But for greenfield projects, for security-critical contracts, for anything where testing rigor matters, Foundry is the better tool. This article breaks down exactly why.
The Current State of Solidity Tooling
The foundry vs hardhat debate is no longer theoretical. In 2026, both frameworks are mature, battle-tested, and used in production by major protocols. But they have diverged significantly in philosophy.
Foundry took the approach of keeping everything in Solidity. Your contracts, your tests, your deployment scripts — all written in the same language that runs on the EVM. Hardhat took the opposite path: contracts in Solidity, everything else in JavaScript or TypeScript.
Two years ago, this was a matter of preference. Today, after shipping contracts on Ethereum, Arbitrum, Base, and Optimism using both tools, I have a clear opinion. The single-language approach wins for contract development. Not because JavaScript is bad, but because context switching between Solidity semantics and JavaScript semantics introduces subtle mistakes that cost real money on mainnet.
Let me show you what I mean.
Foundry: The Rust-Powered Beast
Foundry is a toolkit built in Rust that includes forge (testing and compilation), cast (chain interaction), anvil (local node), and chisel (Solidity REPL). Everything is fast because everything compiles to native code.
What Foundry Gets Right
Speed is not a feature — it is a requirement. Compiling a 50-contract project with forge build takes under 2 seconds on my machine. The same project under Hardhat takes 8-12 seconds. When you are iterating on a contract, running tests hundreds of times a day, that difference compounds into hours of saved time per week.
Tests in Solidity. This is the big one. When your tests run in the same language and the same execution environment as your contracts, you eliminate translation bugs. Here is a simple token transfer test in Foundry:
// test/Token.t.sol
pragma solidity ^0.8.24;
import {Test, console2} from "forge-std/Test.sol";
import {MyToken} from "../src/MyToken.sol";
contract TokenTest is Test {
MyToken public token;
address public alice = makeAddr("alice");
address public bob = makeAddr("bob");
function setUp() public {
token = new MyToken("Test", "TST", 1_000_000e18);
token.transfer(alice, 100e18);
}
function test_transfer() public {
vm.prank(alice);
token.transfer(bob, 50e18);
assertEq(token.balanceOf(alice), 50e18);
assertEq(token.balanceOf(bob), 50e18);
}
function testFuzz_transfer(uint256 amount) public {
amount = bound(amount, 1, 100e18);
vm.prank(alice);
token.transfer(bob, amount);
assertEq(token.balanceOf(alice), 100e18 - amount);
assertEq(token.balanceOf(bob), amount);
}
}Notice testFuzz_transfer. That is a fuzz test. Foundry will automatically generate hundreds of random inputs, bounded by the constraints you define, and run them against your contract. No extra dependencies. No configuration. Just prefix your test with testFuzz_ and add parameters.
Cheatcodes are powerful. vm.prank() lets you impersonate any address. vm.warp() lets you time-travel. vm.roll() moves blocks forward. deal() sets ETH balances. makeAddr() creates labeled addresses for readable traces. These are not hacks — they are first-class testing primitives designed for the EVM.
Built-in invariant testing. Beyond fuzz testing, Foundry supports invariant tests that define properties which must always hold true, no matter what sequence of actions is performed. This is how you catch the edge cases that unit tests miss.
function invariant_totalSupplyMatchesBalances() public {
uint256 totalFromBalances = token.balanceOf(alice)
+ token.balanceOf(bob)
+ token.balanceOf(address(this));
assertEq(token.totalSupply(), totalFromBalances);
}Where Foundry Falls Short
The learning curve is real. If your team writes JavaScript all day, asking them to write Solidity tests requires genuine upskilling. Solidity is not a scripting language. Error messages from forge test can be cryptic when you hit edge cases with cheatcodes.
Deployment scripting is less flexible. Foundry's forge script works, but complex multi-chain deployment sequences with conditional logic, API calls, or database writes are awkward in Solidity. You end up needing a JavaScript or Python wrapper anyway.
Plugin ecosystem is smaller. Hardhat has hundreds of plugins. Foundry has forge-std and a growing but still limited set of libraries. If you need something niche, you might have to build it yourself.
Hardhat: The JavaScript Ecosystem Play
Hardhat wraps Solidity compilation in a Node.js runtime with a rich plugin architecture. It uses Mocha for testing, Ethers.js (or viem) for chain interaction, and has deep integrations with the JavaScript ecosystem.
What Hardhat Gets Right
JavaScript/TypeScript is familiar. Most Web3 teams already have frontend developers who write TypeScript daily. Writing deployment scripts and tests in the same language as the frontend reduces friction. There is value in that.
Here is the equivalent token test in Hardhat:
// test/Token.test.ts
import { expect } from "chai";
import { ethers } from "hardhat";
import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers";
describe("MyToken", function () {
async function deployFixture() {
const [owner, alice, bob] = await ethers.getSigners();
const Token = await ethers.getContractFactory("MyToken");
const token = await Token.deploy("Test", "TST", ethers.parseEther("1000000"));
await token.transfer(alice.address, ethers.parseEther("100"));
return { token, owner, alice, bob };
}
it("should transfer tokens between accounts", async function () {
const { token, alice, bob } = await loadFixture(deployFixture);
await token.connect(alice).transfer(bob.address, ethers.parseEther("50"));
expect(await token.balanceOf(alice.address)).to.equal(ethers.parseEther("50"));
expect(await token.balanceOf(bob.address)).to.equal(ethers.parseEther("50"));
});
});The plugin ecosystem is massive. Gas reporters, coverage tools, contract verification, upgradeable proxy helpers, Tenderly integration, custom network providers — Hardhat has a plugin for nearly everything. hardhat-deploy alone handles complex deployment scenarios that would take hundreds of lines of Foundry scripting.
Hardhat Ignition is genuinely good for deployment. It handles deployment plans declaratively, tracks deployed addresses across chains, and manages upgrades. For teams shipping to multiple chains with complex dependency graphs, this is a real advantage.
Console.log in Solidity. Hardhat pioneered console.log() inside Solidity contracts for debugging. Foundry has console2.log() now, but Hardhat did it first and it works seamlessly with the Hardhat Network.
Where Hardhat Falls Short
Speed. There is no way around it. Hardhat is slower. Compilation is slower. Test execution is slower. The Node.js overhead is measurable on any non-trivial project. Running a full test suite of 500 tests takes minutes on Hardhat and seconds on Foundry.
No native fuzz testing. This is the dealbreaker for me. Fuzz testing is not optional for production smart contracts. With Hardhat, you need to bolt on tools like Echidna or write property-based tests with external libraries. With Foundry, it is built into the test runner. The friction difference means teams using Hardhat simply fuzz less, and that means more bugs reach mainnet.
JavaScript-Solidity impedance mismatch. When you write tests in JavaScript, you are testing through an abstraction layer. BigNumber handling, gas estimation, transaction receipt parsing — all of these introduce opportunities for your test to pass while your contract is broken, or your test to fail while your contract is correct.
Speed Comparison
Real numbers from a project I worked on with 47 contracts and 312 tests:
| Metric | Foundry | Hardhat |
|---|---|---|
| Cold compilation | 1.8s | 11.2s |
| Warm compilation (1 file changed) | 0.4s | 3.1s |
| Full test suite (312 tests) | 4.2s | 67s |
| Single test file | 0.3s | 5.8s |
| Fuzz test (1000 runs) | 2.1s | N/A (requires Echidna) |
| Gas snapshot generation | 1.1s | 8.4s (hardhat-gas-reporter) |
These are not cherry-picked. Foundry is consistently 5-15x faster for compilation and testing. On larger codebases, the gap widens.
Testing Comparison: Fuzz Testing vs Mocha
This is where the frameworks diverge most sharply.
Foundry's approach: Tests are Solidity functions. Fuzz testing is a first-class primitive. Add parameters to your test function, and the fuzzer generates inputs automatically. Invariant testing lets you define properties that must hold across random sequences of contract interactions.
// Foundry: Fuzz + Invariant testing
function testFuzz_withdrawNeverExceedsBalance(
uint256 depositAmount,
uint256 withdrawAmount
) public {
depositAmount = bound(depositAmount, 1e18, 100e18);
withdrawAmount = bound(withdrawAmount, 0, depositAmount);
vm.deal(alice, depositAmount);
vm.startPrank(alice);
vault.deposit{value: depositAmount}();
vault.withdraw(withdrawAmount);
assertGe(
address(vault).balance,
vault.balanceOf(alice)
);
vm.stopPrank();
}Hardhat's approach: Tests are JavaScript/TypeScript functions using Mocha's describe/it pattern. For fuzz testing, you need external tools. The most common path is using @chainlink/hardhat-starter-kit patterns or integrating Echidna separately.
// Hardhat: Manual property test (no native fuzzing)
it("should never allow withdrawal exceeding balance", async function () {
const { vault, alice } = await loadFixture(deployFixture);
const depositAmount = ethers.parseEther("10");
await vault.connect(alice).deposit({ value: depositAmount });
await expect(
vault.connect(alice).withdraw(depositAmount + 1n)
).to.be.revertedWith("Insufficient balance");
});The Hardhat test checks one specific case. The Foundry fuzz test checks hundreds of random cases automatically. One of these approaches finds more bugs. It is not close.
Developer Experience
Foundry DX Wins
forge fmtformats Solidity code consistentlyforge snapshotgenerates gas reports and diffs between commitschiselgives you a Solidity REPL for quick experimentscastlets you interact with any chain from the command line- Stack traces show exact Solidity line numbers on failure
forge coveragegenerates LCOV reports for CI integration
Hardhat DX Wins
- TypeScript autocompletion for contract interaction
npx hardhat consolewith full JavaScript runtime- Network forking with
hardhat_resetfor mainnet simulation - Rich error messages with Solidity stack traces (since Hardhat 2.x)
hardhat-verifyhandles Etherscan verification across chains- Task system for custom CLI commands
Shared Ground
Both support mainnet forking. Both support Solidity 0.8.24+. Both have VS Code extensions. Both integrate with CI/CD pipelines. Both can deploy to any EVM chain.
When to Choose Foundry
Pick Foundry when:
- Security is paramount. Fuzz testing and invariant testing are non-negotiable for contracts handling real value. Foundry makes them trivially easy.
- You are starting fresh. No legacy JavaScript test suite to migrate. No Hardhat plugins you depend on.
- Speed matters to your workflow. If you run tests constantly during development (you should), Foundry's speed is transformative.
- Your team writes Solidity daily. If your developers think in Solidity, let them test in Solidity.
- You are building DeFi. AMMs, lending protocols, vaults — anything where a single bug means millions lost. Foundry's testing primitives were designed for this.
When to Choose Hardhat
Pick Hardhat when:
- Your team is JavaScript-first. Retraining a team of TypeScript developers on Foundry-style testing has a real cost. If velocity matters more than testing depth, Hardhat gets you shipping.
- You need specific plugins.
hardhat-deploy,hardhat-upgrades, or niche integrations that have no Foundry equivalent. - Complex deployment orchestration. Multi-chain deployments with API calls, database writes, and conditional logic are easier in JavaScript.
- You are maintaining an existing Hardhat project. Migration has a cost. If your test suite is comprehensive and your contracts are audited, switching frameworks for the sake of it is not productive.
My Setup
I use both. Here is how.
Foundry is my primary framework. All new contracts start with forge init. All tests are written in Solidity. All fuzz testing and invariant testing runs through forge test. Gas snapshots are tracked with forge snapshot.
Hardhat handles deployment orchestration on projects where I need complex multi-chain scripts with conditional logic. I use Hardhat Ignition for deployment plans and hardhat-verify for Etherscan verification.
My foundry.toml configuration:
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
optimizer = true
optimizer_runs = 200
via_ir = false
fuzz = { runs = 1000, max_test_rejects = 65536 }
invariant = { runs = 256, depth = 50 }
[profile.ci]
fuzz = { runs = 10000 }
invariant = { runs = 512, depth = 100 }For CI, I crank fuzz runs to 10,000 and invariant depth to 100. Locally, 1,000 fuzz runs gives fast feedback. The CI profile catches edge cases that quick local runs miss.
The hybrid approach works. Use the best tool for each job. But if I had to pick one, if you forced me to delete one from my machine — Hardhat goes. Foundry stays.
Comparison Table
| Feature | Foundry | Hardhat |
|---|---|---|
| Language | Solidity (tests + scripts) | JavaScript/TypeScript |
| Compilation speed | Very fast (Rust-based) | Moderate (Node.js) |
| Test speed | Very fast | Moderate |
| Fuzz testing | Built-in | Requires external tools |
| Invariant testing | Built-in | Not available natively |
| Plugin ecosystem | Growing | Extensive |
| Deployment scripting | Solidity scripts | JavaScript/TypeScript |
| Debugging | Stack traces + console2 | Stack traces + console.log |
| Mainnet forking | Yes (anvil) | Yes (hardhat network) |
| Gas reporting | forge snapshot | hardhat-gas-reporter |
| Contract verification | forge verify-contract | hardhat-verify |
| Learning curve | Steeper for JS devs | Gentle for JS devs |
| Community size | Growing fast | Large and established |
Key Takeaways
- Foundry is faster at everything. Compilation, testing, gas reporting. The Rust foundation pays dividends daily.
- Fuzz testing is the differentiator. Native fuzz testing in Foundry catches bugs that unit tests in Hardhat miss. For security-critical contracts, this alone justifies the switch.
- Hardhat's plugin ecosystem is still unmatched. If you need a specific integration, Hardhat probably has it.
- The best setup is hybrid. Foundry for contracts and testing, Hardhat for deployment orchestration when needed.
- New projects should default to Foundry. The speed advantage and testing capabilities make it the stronger starting point in 2026.
- Existing Hardhat projects do not need to migrate immediately. If your tests are solid and your contracts are audited, the cost of migration may not justify the benefits.
If you are building smart contracts and need help choosing the right framework or setting up a testing pipeline that actually catches bugs before mainnet, check out my services. I have shipped contracts on Ethereum, Arbitrum, Base, and Optimism using both tools — and I know exactly where each one shines.
*Written by Uvin Vindula↗ — Web3 engineer, ethical hacker, and builder at @IAMUVIN↗. Building production-grade smart contracts and decentralized applications from Sri Lanka and the UK.*
Working on a Web3 or AI project?

Uvin Vindula
Web3 and AI engineer based in Sri Lanka and the UK. Author of The Rise of Bitcoin. Director of Blockchain and Software Solutions at Terra Labz. Founder of uvin.lk — Sri Lanka's Bitcoin education platform with 10,000+ learners.