blockchainsv
Developer Tools & Infrastructure·August 22, 2026·15 min read

Foundry migration for Hardhat projects: is it worth it?

If a medium-sized Hardhat project takes roughly 18–25 seconds to run a suite of around 50 tests, the delay is not catastrophic.

Foundry migration for Hardhat projects: is it worth it?

It is, however, large enough to change how engineers work: tests are run less often, fuzzing is postponed, and contract-level feedback becomes something developers wait for rather than use continuously.

Foundry approaches the same problem from a different layer. Its compiler and test runner are implemented in Rust and execute against an embedded EVM, while test suites are written in Solidity rather than JavaScript or TypeScript. In representative benchmarks, Foundry completes comparable test workloads in roughly 2–4 seconds, with some compilation-and-test measurements showing 1.44 seconds versus 5.17 seconds for Hardhat. Larger practical comparisons often place the advantage somewhere between 5x and 20x, depending on project structure, cache state, and test composition.

That makes the performance case straightforward. The migration case is not.

Moving from Hardhat to Foundry means rewriting tests, changing artifact paths, adapting deployment workflows, and deciding which parts of the existing JavaScript toolchain still matter. For core smart contract development, the transition can be highly valuable. For a full-stack DApp, a complete replacement is often less rational than a deliberate hybrid architecture.

The first bottleneck is feedback, not compilation

Hardhat and Foundry are both capable development environments, but they optimize for different workflows.

Hardhat is closely aligned with the JavaScript and TypeScript ecosystem. Its test suites commonly use Mocha, Chai, and ethers.js, while deployment scripts and application integrations can remain in the same language as the frontend. That makes it approachable for teams building the entire DApp inside one repository.

Foundry is more contract-centric. The main development loop is built around Solidity compilation, Solidity test contracts, fast local execution, fuzzing, and invariant testing. The toolchain is designed to reduce the distance between the contract under test and the test logic itself.

This difference matters most when the team is iterating on protocol behavior. A lending market, automated market maker, vault, bridge component, or account-abstraction module can have hundreds of state transitions that are expensive to represent as isolated examples. If every test run takes long enough to interrupt the development flow, engineers naturally narrow the test surface. The system becomes tested according to what is convenient to execute, not necessarily according to where the risk is concentrated.

Foundry reduces that friction. The benefit is not simply that a command finishes sooner. Faster execution changes the number of feedback cycles a developer can afford during a design session.

A useful comparison looks like this:

DimensionHardhatFoundry
Primary runtimeNode.js-based JavaScript/TypeScript environmentRust-based native tooling with an embedded EVM
Test languageUsually JavaScript or TypeScriptSolidity
Typical integration styleStrong fit for ethers.js, frontend tooling, and JS automationStrong fit for contract-level development and protocol testing
FuzzingUsually requires additional setup or librariesNative fuzzing and invariant testing are built into the workflow
Migration costNo migration if the project already uses HardhatExisting JS/TS tests must be rewritten as Solidity test contracts
Artifact conventions/contracts, /test, /artifacts/src, /test or /src/test, /out
Best operational fitFull-stack DApps and TypeScript-heavy repositoriesContract-heavy repositories where execution speed and state exploration dominate

The performance advantage is therefore a systems trade-off. Foundry moves the critical test loop closer to the EVM and removes a JavaScript runtime from that path. Conversely, Hardhat keeps more of the surrounding application ecosystem in one language and one execution model.

Foundry’s main advantage is not that it makes Solidity fashionable. It makes contract feedback cheap enough to use continuously.

What the benchmark numbers actually mean

The headline figures — 5x to 20x faster test execution — are useful, but they should not be treated as a universal conversion rate.

A test suite dominated by contract calls, storage updates, event assertions, and repeated fixture setup is likely to benefit substantially from Foundry’s native execution model. A suite that spends much of its time coordinating external processes, launching application services, querying TypeScript helpers, or validating frontend behavior will see a smaller improvement because those operations remain outside the core Foundry runtime.

The same applies to caching. A reported example of approximately 1.44 seconds for Foundry compilation plus testing versus 5.17 seconds for Hardhat is informative, but cached and uncached runs are not equivalent. In another medium-project comparison, Foundry completed tests in around 2–4 seconds while Hardhat required approximately 18–25 seconds for a suite of about 50 tests. These numbers describe a class of workload, not a guaranteed result for every repository.

The more important question is where the current delay sits.

If the bottleneck is contract execution, test discovery, compilation, or repeated EVM setup, a Foundry migration addresses the problem directly. If the bottleneck is an RPC-backed integration test, Dockerized infrastructure, database synchronization, or a frontend build, changing the contract test runner will not remove the dominant latency.

Before migrating, classify the existing suite by function:

  • Pure contract unit tests that deploy contracts, call methods, manipulate balances, and assert storage or events.
  • Protocol interaction tests that exercise several contracts and model users, operators, liquidators, or governance actors.
  • Fuzz and property tests that vary calldata, token amounts, timestamps, permissions, or market states.
  • Deployment tests that validate scripts, network configuration, proxy behavior, and verification flows.
  • Application integration tests that connect contracts to a TypeScript SDK, frontend, indexer, wallet, or external service.

Foundry is most compelling for the first three categories. The latter two are where Hardhat often remains useful, even in a repository that has adopted Foundry for contract development.

The real migration cost is rewriting the test model

A Hardhat-to-Foundry transition is not a file-format conversion. Existing Mocha and Chai tests written in JavaScript or TypeScript have to be re-expressed as Solidity test contracts, usually using forge-std/Test.sol.

That rewrite changes more than syntax. It changes how the test describes actors, fixtures, assertions, reverts, time, balances, and transaction context.

A typical Hardhat test may rely on JavaScript objects representing signers, asynchronous promise chains, ethers.js contract instances, and helper functions that assemble deployment state. In Foundry, the same scenario is expressed through Solidity calls, test addresses, cheatcodes, and direct interaction with deployed instances. The underlying protocol behavior is unchanged, but the test’s control plane is different.

This creates several migration decisions.

Actor modeling

Hardhat tests often obtain signers from the runtime and pass them into contract calls. Foundry usually makes actor control explicit through test utilities that impersonate or select an address for a call. That is more verbose in some cases, but it can also make authorization assumptions easier to inspect.

For example, a test for an access-controlled function should make clear whether the call originates from the deployer, an operator, a governance address, or an arbitrary unauthorized account. In a Solidity test, that context is close to the call itself. The result is not automatically safer, but it can reduce the amount of indirection between the scenario and the EVM state being exercised.

Revert assertions

Hardhat and Chai assertions tend to be readable at the JavaScript level, particularly for teams already comfortable with ethers.js. Foundry places revert expectations inside the Solidity test flow. This is a small conceptual shift, but across a large suite it affects helper design and how failure messages are interpreted.

The migration is an opportunity to separate protocol invariants from incidental implementation details. A test should not be rewritten line by line merely to preserve every old assertion. Some tests are valuable because they define security properties; others only encode the current shape of a helper or deployment fixture.

Fixtures and setup

Large Hardhat repositories frequently accumulate fixture utilities that deploy a graph of contracts once and reuse the resulting state. Foundry supports similarly structured setup, but the implementation and lifecycle are different. A direct translation can produce Solidity tests that are technically valid yet difficult to maintain because all scenarios depend on one oversized setup function.

A better migration groups setup by protocol boundary:

  • minimal deployment for isolated unit tests;
  • multi-contract deployment for accounting and permission flows;
  • fork-based state for integrations with existing protocols;
  • adversarial state for liquidation, oracle, or settlement behavior.

This is slower at the beginning, but it preserves test isolation and makes failures more local. The objective is not to reproduce the old directory tree. It is to preserve the test suite’s security meaning while removing unnecessary runtime overhead.

JavaScript helpers do not always have a Solidity equivalent

Some Hardhat utilities exist because JavaScript makes them convenient: generating structured inputs, reading external fixtures, composing deployment metadata, or coordinating multiple network clients. These should not be forced into Solidity simply to achieve conceptual purity.

The practical boundary is simple: move contract semantics into Foundry, and keep application orchestration in TypeScript where it still provides real leverage.

A successful migration preserves the properties the tests prove, not the exact shape of the old test files.

Native fuzzing changes what counts as adequate coverage

The strongest reason to adopt Foundry is often not raw speed. It is the way native fuzzing and invariant testing change the testing strategy.

Example-based tests answer questions such as: given this account, this amount, and this sequence of calls, does the protocol produce the expected result? Those tests remain necessary because they document concrete behavior and protect known regressions.

They are less effective at discovering combinations that the author did not anticipate. Smart contract failures frequently emerge from boundaries and interactions: rounding at a particular decimal scale, a zero-value path after a partial withdrawal, a permission transition followed by a callback, or an accounting discrepancy that only appears after several state changes.

Foundry’s fuzzing workflow can execute functions across thousands of randomized inputs in seconds. The exact useful volume depends on the function, constraints, and state setup, but the important shift is that input exploration becomes a normal part of the test suite rather than a separate security exercise.

Fuzzing is not random button pressing

A poorly designed fuzz test can generate a large number of irrelevant calls and prove very little. The value comes from defining the property and constraining the domain without accidentally removing the failure case.

For a token vault, useful properties might include:

  • shares and assets remain internally consistent after deposits and withdrawals;
  • a user cannot withdraw more assets than their balance permits;
  • total supply and underlying asset accounting do not diverge beyond an explicitly defined rounding tolerance;
  • unauthorized callers cannot change critical configuration;
  • a liquidation operation does not create assets from nothing;
  • protocol solvency remains true after arbitrary valid sequences of user actions.

The test should be framed around these relationships, not around the expectation that a particular randomly selected input will revert.

Invariant testing takes this further by applying sequences of operations and checking that the system remains within defined rules. This is especially relevant for stateful protocols where the vulnerability is not located in one function call but in the transition between states.

Foundry’s native support makes this style easier to place near the contract code. However, it does not automatically define meaningful invariants. Engineers still have to understand the accounting model, privilege boundaries, oracle assumptions, and acceptable rounding behavior.

Where Hardhat still has an advantage

Hardhat’s JavaScript environment can be more convenient when the property depends on application-level behavior. A test that signs a typed message through a wallet library, sends the result through an SDK, checks indexer output, and validates a frontend-facing response may be more naturally expressed in TypeScript.

The trade-off is therefore not fuzzing versus no fuzzing. A mature repository may use Foundry for state-machine and contract invariants, then use Hardhat or a TypeScript test runner for end-to-end flows. Both layers answer different questions.

The common failure is to migrate unit tests for speed while leaving the integration boundary undefined. That produces a fast contract suite but a fragile deployment and application pipeline. Foundry improves the inner loop; it does not replace the need to test the outer system.

Repository structure is simple; repository assumptions are not

The visible directory changes are easy to describe. Foundry generally places contracts under /src, tests under /test or /src/test, and compiled output under /out. Hardhat projects commonly use /contracts, /test, and /artifacts.

The directory rename itself is not the difficult part. The problem is that scripts, CI jobs, deployment tooling, import paths, artifact readers, and verification tasks may have encoded the old assumptions.

A migration plan should account for at least four classes of dependency:

1. Compilation and artifact consumers. Frontend packages, deployment scripts, SDK generators, and verification commands may read ABI and bytecode from Hardhat’s /artifacts tree. Foundry’s output layout is different, and the artifact format or path expected by existing tooling may need an adapter.

2. Network and environment configuration. RPC endpoints, private keys, chain IDs, fork settings, and deployment environments may currently be loaded through Hardhat configuration. Moving only the tests while leaving deployment configuration untouched can be the correct choice, but it must be intentional.

3. Continuous integration. CI should run the new Foundry suite independently before the old suite is removed. Otherwise, a migration failure can be confused with a protocol regression. In practice, parallel execution for a period gives the team a way to compare coverage and failure semantics.

4. Developer documentation and onboarding. A repository that requires contributors to understand two test systems needs clear ownership boundaries. Otherwise, new tests will be added according to personal preference, and the project will gradually accumulate duplicated fixtures and inconsistent assertions.

A clean layout helps, but architectural clarity matters more than folder symmetry. A single repository can contain Foundry contracts and tests alongside Hardhat deployment scripts and TypeScript tooling, as long as the boundary between them is explicit and stable.

The hybrid reality: why many teams keep Hardhat for deployment

The cleanest mental model is not Hardhat or Foundry. It is contract development in Foundry and application orchestration in Hardhat.

In practice, a large protocol team often ends up with a structure roughly like this:

  • contracts live under /src and are compiled with Foundry;
  • contract-level tests live as Solidity test contracts and run through forge test;
  • deployment scripts, network configuration, proxy upgrade flows, and verification jobs remain in TypeScript under hardhat/ or a similar directory, using Hardhat as a deployment and integration runner;
  • frontend and off-chain services consume whichever artifact format is easiest for them, with a thin adapter if necessary;
  • CI runs the Foundry suite on every pull request and the deployment scripts in a separate job that targets testnets or forks.

This is not a compromise born of laziness. It is the most coherent separation of concerns the two toolchains currently allow. Foundry is strongest where the developer is reasoning about EVM state. Hardhat remains strongest where the developer is reasoning about external services, wallet flows, or end-to-end pipelines.

Teams that have tried to force everything through a single tool usually end up rebuilding the missing pieces. JavaScript cheatcode wrappers, TypeScript libraries that produce calldata for Foundry scripts, or hardhat plugins that emulate forge output all point to the same conclusion: the boundaries are easier to maintain than the abstractions.

When a full migration still makes sense

There are still projects where the right move is to commit fully to Foundry. Smaller teams, greenfield protocols, libraries that ship only Solidity, audit-focused repositories, and any codebase where deployment is handled by a separate operations stack all fit the profile. If the application layer is thin and the protocol layer is the product, the cost of rewriting tests is paid back quickly through faster iteration and a more aggressive fuzzing posture.

The opposite case is also real. A team whose bottleneck is TypeScript-heavy integration testing, whose deployment story is deeply wired into Hardhat plugins, or whose developers are far more productive in JavaScript will not gain much by moving contract tests into Solidity. They may even lose velocity during the migration, only to discover that the original delay was in the integration layer anyway.

A reasonable decision rule

A migration to Foundry is worth it when:

  • the dominant test latency is contract execution and compilation, not orchestration;
  • the team is willing to rewrite tests as Solidity contracts, accepting that no automated path produces a clean, maintainable Foundry suite from existing TypeScript or JavaScript code;
  • the protocol layer is the place where most security risk concentrates;
  • the team wants fuzzing and invariant tests to be a normal part of the development loop, not a separate security exercise;
  • the deployment and integration layers can either remain on Hardhat or be moved to a standalone TypeScript runner without losing functionality.

A migration is not worth it when:

  • the bottleneck is in the application or deployment layer;
  • the existing test suite already runs in a few seconds and is stable;
  • the team has little protocol complexity and few invariants worth fuzzing;
  • the cost of rewriting tests is high relative to the expected gain in iteration speed.

Foundry is not a stylistic upgrade. It is a tool that reorganizes the inner loop around the EVM. For teams whose inner loop already lives there, the migration is one of the highest-leverage changes available. For teams whose loop is dominated by orchestration, integration, or shipping velocity, the same migration becomes an expensive distraction.

The honest answer to whether Foundry migration from Hardhat is worth it is therefore not yes or no. It is yes for the contracts, and it depends for everything else.

FAQ

How much faster is Foundry than Hardhat for testing?
Representative comparisons place Foundry at roughly 2–4 seconds for comparable workloads, while Hardhat may take about 18–25 seconds for a suite of around 50 tests. Other compilation-and-test measurements show 1.44 seconds for Foundry versus 5.17 seconds for Hardhat, but results depend on project structure, cache state, and test composition.
Is migrating from Hardhat to Foundry just a file-format conversion?
No. Existing Mocha and Chai tests written in JavaScript or TypeScript generally need to be rewritten as Solidity test contracts, often using forge-std/Test.sol. The migration also changes how tests model actors, fixtures, assertions, reverts, time, balances, and transaction context.
Which tests benefit most from Foundry?
Foundry is most compelling for pure contract unit tests, protocol interaction tests, and fuzz or property tests. Deployment tests and application integration tests may still be better suited to Hardhat or a TypeScript test runner.
Can Hardhat and Foundry be used in the same project?
Yes. A common hybrid architecture uses Foundry for Solidity compilation, contract-level tests, fuzzing, and invariant testing, while Hardhat remains responsible for deployment scripts, network configuration, proxy upgrades, verification, and TypeScript-based integration flows.
What happens to Hardhat artifacts after migrating to Foundry?
Foundry commonly places compiled output under /out, while Hardhat projects typically use /artifacts. Deployment scripts, frontend packages, SDK generators, and verification commands that read the old artifact paths may need an adapter or other changes.
When is a Foundry migration not worth it?
Migration is less attractive when the main bottleneck is application or deployment orchestration, the existing suite already runs in a few seconds, or the project has limited protocol complexity and few invariants worth fuzzing. Rewriting tests may otherwise cost more than the expected improvement in iteration speed.

By Lucas Meade