back

Rick · · Updated

The comeback of Hardhat

Dev tooling

Recently, I noticed that 1inch was moving projects to Hardhat 3. The reason they gave was not the one I expected.

1inch describes its path as Hardhat 2, then Forge, then back to Hardhat 3. The team published a three-part account of the migration of its open-source Merkle Distribution project. They had left Hardhat 2 for Forge a couple of years earlier because tests could be written in Solidity. Then, in their words, "Forge caused us considerable pain in practice. While powerful in some areas, the overall experience didn't work well for our workflow." Hardhat 3 brought native Solidity tests, fuzzing, and invariant testing into a framework their existing TypeScript tooling already targeted, so they moved back.

Note what that reason is not. It is not a published speed measurement. It is workflow fit.

The Hardhat side of the story is deliberately understated. Nomic Foundation's stable-release announcement says the contracts and upgrades teams at OpenZeppelin "collaborated very closely with us throughout this process, and the team at 1inch have been providing very valuable feedback as they migrate their projects to Hardhat 3." That is not the old Hardhat waiting for plugins to fill the gaps. The team is working directly with major protocol developers around real production workloads.

That caught my attention because my previous article was called Foundry vs Hardhat: why Foundry won.

I argued that Foundry understood Solidity developers better. It was faster. Tests were written in Solidity. Fuzzing and cheatcodes were part of the core experience. Hardhat had the larger ecosystem, but Foundry had the better insight into what protocol engineers actually needed.

After publishing it, I wanted to see how much had changed.

The answer is: a lot.

I still think Foundry won the last era of Solidity tooling. But Hardhat learned from that loss, rebuilt the parts that held it back, and has become a serious competitor again. In some workflows, it may now be the better tool.

One aside before the argument. As part of my DevRel work with the Ethereum Foundation, I spend a lot of time thinking about how developers get from understanding Solidity to shipping real applications, and Speedrun Ethereum is one of the better bridges: hands-on challenges covering tokenization, crowdfunding, DEXs, oracles, lending, stablecoins, and prediction markets. Its introductory challenge still uses Hardhat to compile and deploy, so it is a way to feel the workflow this article is about rather than only read about it.

Update — September 8, 2026

The original publication date is July 17, 2026. The argument below stands, with three changes made during this review.

The opening was wrong about 1inch. It said the migration happened because Hardhat 3 was faster for their workflow. Their own write-up does not claim that, and neither does Nomic's announcement. The stated reasons are workflow friction with Forge and the return of Solidity testing to a framework 1inch already had TypeScript infrastructure around. I have corrected it.

The section on performance said I could not find a rigorous, reproducible comparison. That was true in July. It is not true now, because I built one and published it in August. It is linked below.

Two things happened after publication that change the practical decision. Hardhat 2 now has an end-of-life date, and Nomic has moved up the stack into the compiler itself. Both have their own sections at the end.

The feature claims below were re-checked against current documentation on September 8, 2026, against Hardhat 3.16.0, Foundry 1.8.1, and forge-std v1.16.2. I also built a small project to run the same Solidity test files through both frameworks, because "supports most of Foundry's interface" is the kind of claim that deserves an actual command.

Timeline of Foundry and Hardhat milestones from December 2021 to September 2026: Foundry announced in December 2021 and declared stable in February 2025; Hardhat ships the Rust EDR runtime in March 2024, enters Hardhat 3 beta in August 2025, reaches stable on 1 June 2026, announces Hardhat 2 end of life on 22 June 2026, and announces the slang-solx compiler backend on 7 September 2026; 1inch publishes its migration series between November 2025 and March 2026.

Each entry is a primary announcement from the project that shipped it, or a first-hand report from the team that did the migration.

Hardhat did the hard thing

The easiest response to Foundry would have been a faster JavaScript test runner and a few official plugins.

Hardhat did something much more fundamental. It replaced its Ethereum simulation layer with EDR, a new runtime written in Rust and built on REVM.

This matters because the old performance comparison was architectural. Foundry executed Solidity tests through a native Rust toolchain. Hardhat ran JavaScript or TypeScript tests through Node.js, a client library, and a simulated network. Even if both tools compiled the same contracts, the testing path was completely different.

EDR changed that path.

Hardhat began shipping EDR in version 2.21.0 in March 2024. Nomic Foundation reported that most projects on that release or newer "saw at least a 2x increase in test running speed, with some experiencing up to a 10x improvement." That is a vendor figure from before the Hardhat 3 redesign, and it describes the JavaScript test path, not Solidity tests.

Hardhat still uses Node.js for orchestration, configuration, plugins, and TypeScript workflows. But the performance-critical EVM execution is no longer a JavaScript implementation. Solidity tests run on an in-process Rust runtime without going through Ethers.js or making a JSON-RPC request for every interaction.

The old claim that Hardhat is slow because Node.js is slow is no longer a useful explanation.

Solidity tests are now first-class

The biggest reason many protocol teams moved to Foundry was not raw speed. It was the ability to stay in Solidity.

Before Foundry, testing a Solidity contract usually meant switching languages:

  • Write the contract in Solidity
  • Write the test in JavaScript or TypeScript
  • Send calls through Ethers.js
  • Handle asynchronous execution and JavaScript number types
  • Translate EVM behavior into a different programming model

Foundry removed that context switch. A test was just another Solidity contract.

Hardhat 3 now does the same. Solidity tests are a first-class part of the framework rather than a community plugin bolted on afterward. Its Solidity testing guide describes the same conventions Foundry established: a contract is a test contract if it has at least one function starting with test, setUp() runs before each test, and fuzz tests are ordinary test functions that take parameters. Test discovery covers Solidity files in test/ and .t.sol files next to your contracts, and both locations are configurable. Cheatcodes are built into the runtime; the forge-std assertion helpers are a normal dependency you install.

To see how far that goes, I put three Solidity files in one project and ran them through both frameworks without editing them: a small ether ledger, a test contract using forge-std assertions and cheatcodes, and a stateful invariant with a bounded handler registered through StdInvariant.targetContract.

// examples/articles/the-comeback-of-hardhat/contracts/Ledger.t.sol
// Solidity 0.8.25, forge-std v1.16.2
contract LedgerTest is Test {
    Ledger internal ledger;
    address internal alice = address(0xA11CE);

    function setUp() public {
        ledger = new Ledger();
        vm.deal(alice, 100 ether);
    }

    function test_depositCreditsSender() public {
        vm.prank(alice);
        ledger.deposit{value: 1 ether}();

        assertEq(ledger.balanceOf(alice), 1 ether);
        assertEq(ledger.total(), 1 ether);
    }

    function test_withdrawAboveBalanceReverts() public {
        vm.prank(alice);
        vm.expectRevert(Ledger.InsufficientBalance.selector);
        ledger.withdraw(1 wei);
    }

    /// hardhat-config: fuzz.runs = 1000
    function testFuzz_depositThenWithdrawIsBalanceNeutral(uint96 amount) public {
        vm.assume(amount > 0);
        vm.deal(alice, amount);

        vm.startPrank(alice);
        ledger.deposit{value: amount}();
        ledger.withdraw(amount);
        vm.stopPrank();

        assertEq(ledger.balanceOf(alice), 0);
        assertEq(ledger.total(), 0);
        assertEq(alice.balance, amount);
    }
}

Both runners accept the files as they are. forge test reports four passing tests, and so does npx hardhat test solidity:

  contracts/Ledger.t.sol:LedgerTest
    ✔ test_withdrawAboveBalanceReverts()
    ✔ test_depositCreditsSender()
    ✔ testFuzz_depositThenWithdrawIsBalanceNeutral(uint96) (runs: 1000)

  contracts/LedgerInvariant.t.sol:LedgerInvariantTest
    ✔ invariant_trackedTotalMatchesHeldEther() (runs: 32)

One difference is worth knowing before you assume portability. The hardhat-config comment above the fuzz test is Hardhat's inline configuration syntax, and it is why the run above reports 1,000 fuzz runs instead of the 256 set in hardhat.config.js. Forge ignores it, because its own inline syntax is forge-config. Test bodies move between the two frameworks; per-test configuration comments do not.

More importantly, Hardhat did not remove TypeScript testing to get there. It lets teams use both approaches in the same project:

  • Solidity for fast unit tests, fuzzing, invariants, and contract-level behavior
  • TypeScript for integrations, offchain services, deployments, bots, and multi-transaction scenarios

That combination is compelling. Foundry was right that Solidity is usually the best language for testing isolated Solidity logic. Hardhat is right that not every meaningful test stops at the contract boundary.

Fuzzing and invariants moved into the core

My previous argument was especially critical of Hardhat's security tooling.

Foundry made fuzz testing almost unavoidable. Add parameters to a test function and Forge generates inputs, explores edge cases, shrinks failures, and gives you a counterexample. Stateful invariant testing lets the runner execute randomized sequences of calls and continuously check properties that should never break.

Hardhat 3 now has both built in, implemented in EDR rather than delegated to an unrelated plugin.

The configuration reference shows how deep it goes. Fuzzing exposes runs, a seed, maxTestRejects, dictionary weighting, and a directory for persisting failures so a counterexample re-runs first next time. Invariant campaigns expose runs, depth, failOnRevert, and a shrinkRunLimit governing how hard the runner works to minimize a failing sequence. Fork tests, the ffi cheatcode, and filesystem permissions each have an explicit switch, and any of it can be overridden per test function.

The strategic gap has closed. Hardhat no longer treats fuzzing and invariant testing as specialist features that developers must assemble themselves.

That does not make the two implementations identical. Foundry has a longer track record, more battle-tested workflows, and the broadest security-focused community. Equal run counts are also not equal bug-finding ability: two engines generating different inputs against the same property can reach very different conclusions. But choosing Hardhat no longer means giving up the testing techniques that made Foundry attractive.

Cheatcodes came with it

Solidity-native testing works because cheatcodes give tests control over the environment.

You need to change msg.sender, move time forward, modify balances, fork a live chain, expect a revert, sign data, read files, or generate addresses. Foundry turned those operations into a coherent testing interface through vm cheatcodes.

Hardhat 3 documents its own cheatcode reference covering that surface, plus gas snapshots and EIP-712 hashing cheatcodes for typed-data signatures.

Compatibility is still not perfect. Hardhat's own Foundry migration guide states plainly that "a small number of Foundry cheatcodes aren't supported," and it does not enumerate them, so a large existing suite needs a real trial rather than an assumption. The guide is also candid that there is no direct equivalent of forge script.

But the fact that the official documentation now explains how to move a Foundry test suite into Hardhat tells you how much the competitive landscape has changed.

The performance gap is no longer obvious

My previous article cited a benchmark where Foundry compiled and ran tests in 1.44 seconds while Hardhat took 5.17 seconds. With caches enabled, the result was 0.45 seconds against 3.98 seconds.

Those numbers were real, but they came from an old comparison of Foundry Solidity tests against pre-EDR Hardhat JavaScript tests. They should not be used to describe Hardhat 3.

The two frameworks now have much more similar execution architectures. Both use native Rust components. Both can execute tests written in Solidity. Both avoid the JavaScript-to-RPC round trip for those tests.

When I wrote this article, I could not find a rigorous, reproducible, apples-to-apples benchmark across compilation, unit tests, fuzzing, and invariants. So I built one: Foundry vs Hardhat 3: I benchmarked the same Solidity tests compiles a single shared Solidity project with the same compiler binary and runs the same .t.sol files through both frameworks.

Two caveats travel with that result, and they matter more than the ranking. It is one small synthetic workload, not a production import graph with plugins, coverage, fork tests, and CI hardware. And it is a measurement with a date, a compiler, a Node version, and a machine attached to it, so read the setup at the destination before treating any ordering as the current state of the world.

Vendor numbers deserve the same treatment in both directions. Foundry's v1.0 announcement in February 2025 reported compilation 2.1x to 5.2x faster than Hardhat depending on caching. That was measured before Hardhat 3 existed.

The honest claim today is not that Hardhat has definitely beaten Foundry on speed. It is that Hardhat has improved enough that speed alone is no longer an automatic reason to reject it. The 1inch migration is especially interesting in that context: a production DeFi team with demanding contract workflows is helping shape Hardhat 3 while moving projects onto it, and it is doing so for reasons that have little to do with a stopwatch.

Hardhat kept the things Foundry never tried to replace

Hardhat did not become Foundry with an npm package. It kept the parts of its identity that were already valuable.

Its plugin system remains useful for teams with custom build, deployment, verification, and integration requirements. Dependencies can be managed through npm with lockfiles, semantic versions, and transitive dependencies, while Hardhat 3 also reads Foundry-style remappings.txt files, including ones inside git submodules and npm packages.

Hardhat Ignition provides declarative deployments that batch and parallelize transactions and recover from a partial or failed run instead of restarting it. 1inch's second migration article is a useful reality check here: hardhat-deploy does not work on Hardhat 3, so an established deployment setup has to be rewritten as Ignition modules.

Verification can target Etherscan, Blockscout, and Sourcify, with an API key required only for Etherscan. Gas statistics can be written to JSON for CI and attribute usage separately when a contract is called directly and through a proxy. Coverage is built in, works across Solidity and TypeScript tests with a --coverage flag, and emits LCOV and HTML alongside a terminal summary.

Hardhat 3 also introduced native multichain simulation. Tests can run against Ethereum L1 or an OP Stack environment with chain-specific behavior instead of assuming every EVM chain behaves exactly like mainnet, and a project can configure several simulated networks with different chain types.

These are not features that make a five-line unit test better. They matter when a contract repository becomes part of a larger engineering organization.

That may explain why direct work with teams like 1inch and OpenZeppelin matters so much. Hardhat is optimizing for the point where a Solidity project stops being a collection of contracts and becomes production infrastructure.

Hardhat 2 now has an expiry date

This is the part that turns an interesting comparison into a scheduling problem.

On June 22, 2026, Nomic Foundation announced that Hardhat 2 is being replaced by Hardhat 3. Hardhat 2 will be upgraded for the Glamsterdam hardfork but "will not be upgraded to the Hegota hardfork or any of its EIPs." End of life is whichever comes first: Hegota activating on mainnet, or June 1, 2027. Bug fixes and security fixes continue until then, and after that date "no further releases should be expected, including for security fixes."

If you maintain a Hardhat 2 repository, that is a real deadline rather than an upgrade suggestion. And it changes the shape of the choice for those teams. The question is no longer whether to leave Hardhat for Foundry; it is whether the next migration goes to Hardhat 3 or to Foundry, since standing still is not an option for much longer.

1inch's three-part series is the most useful preparation I have found for that decision, because it is specific about the friction: moving the project to ES modules, loadFixture behaving differently once tests are split into separate modules, missing await calls producing unhelpful errors, replacing hardhat-deploy with Ignition, and rewriting custom tasks against a new builder API.

The next front is the compiler

The day before this update, Nomic announced slang-solx, an LLVM-based Solidity backend that compiles in parallel while still using solc as its frontend. Hardhat 3.16.0, published the same day, added a plugin for it.

The reported numbers are the project's own: roughly 1.7x faster than solc in legacy mode and roughly 5x faster in via-IR mode, measured as wall-clock time across five cold compiles of complete repositories. Building nine repositories in sequence took solc 749 seconds and slang-solx 151 seconds. It also claims to avoid "stack too deep" in 99.85% of its test cases, and 99.56% of 112,587 Solidity 0.8.34 contracts from Sourcify compiled without issues.

Two limits keep it away from production. This first release supports Solidity 0.8.34 only, with other versions falling back to solc, and the announcement is unusually direct about the rest: "it is not safe for mainnet deployments," pending a hardening effort that has not started.

So not near a deployment pipeline yet. Worth watching anyway, because it says something about where the competition has moved. Compilation was the one place where Foundry's lead was least controversial, and the Hardhat team's answer was not a faster cache. It was a new compiler backend.

Foundry still has real advantages

This is not a reversal where Hardhat wins and Foundry loses.

Foundry is still the cleaner choice for many Solidity-first teams. Forge, Cast, Anvil, and Chisel are fast and focused: a test runner, a chain interaction CLI, a local node, and a Solidity REPL. forge script provides a Solidity-native scripting and broadcasting workflow for which Hardhat has no direct equivalent.

Foundry has not been standing still either. Its v1.0 release stabilized the toolkit's APIs, rewrote invariant shrinking, and added per-campaign metrics that separate successful calls from reverts and discarded calls. Releases have continued steadily since; 1.8.1 shipped in late August 2026.

It also has the deeper culture around fuzzing, invariant testing, and protocol security. Its cheatcode surface is broader, its workflows are familiar to auditors, and it avoids bringing Node and npm into a repository that may not need either.

If I were starting a self-contained protocol repository with no meaningful offchain application layer, Foundry would still be my default.

But if the project includes TypeScript services, complex integrations, custom deployment infrastructure, chain-specific simulation, or internal tooling, Hardhat 3 now deserves a serious evaluation. The ability to use Solidity for low-level tests and TypeScript for realistic system tests is a strength, not a compromise.

Foundry won, and Hardhat listened

The most interesting part of Hardhat's comeback is that it validates Foundry's original insight.

Solidity developers wanted native speed. They wanted to test in Solidity. They wanted fuzzing, invariants, forks, cheatcodes, traces, gas reports, and coverage without constructing a fragile tower of plugins.

Foundry proved that those expectations were correct.

Hardhat could have defended the old model. Instead, it rewrote its execution engine in Rust, made Solidity testing first-class, brought security tooling into the runtime, added Foundry compatibility, and started working closely with teams migrating real production repositories.

That is what a good comeback looks like.

My previous conclusion was that Hardhat had the ecosystem advantage while Foundry had the insight, and insight won.

Today, Hardhat still has the ecosystem. But now it has absorbed the insight too.

Foundry won the argument. Hardhat may be winning its way back into the workflow.