The August benchmark put Foundry ahead in every scenario. The September rerun did not.
Foundry still had lower median build times. Hardhat 3 had lower median times for the unit, fuzz, and invariant test commands. The unit suite took 382ms with Foundry and 280ms with Hardhat.
That changes the conclusion worth carrying forward. This workload does not support choosing a framework on the assumption that Foundry always returns faster.
This is a substantive update to the experiment behind The comeback of Hardhat and Foundry vs Hardhat: why Foundry won. The original publication date is preserved. The measurements below were collected on September 7, 2026; the August results remain historical data.
What changed in the rerun
The experiment uses the existing shared Solidity project in benchmarks/foundry-hardhat. Both frameworks compile the same source tree and run the same .t.sol files. Hardhat's Solidity testing documentation describes this native test workflow.
Before rerunning it, the harness needed a more explicit setup. Previously, BENCH_SOLC_PATH was passed to Forge, while the checked-in Hardhat configuration selected a compiler version without referencing that path. The refresh passes the path to Hardhat too, using its documented custom compiler option.
Both configurations now explicitly target Cancun and enable test isolation. Hardhat already requested isolation; the old Foundry configuration did not explicitly enable it. The optimizer remains enabled with 200 runs. Foundry exposes the compiler and optimization controls in its compiler configuration.
The tool versions are Foundry 1.5.1 and Hardhat 3.12.0, with npm 10.9.2 and Node.js 22.17.0. These are the pinned versions tested here, not a claim that they are the newest releases. The August run used Node.js 23.7.0. The current run uses an even-major Node release consistent with Hardhat's support policy.
Both tools use the same local native Solidity 0.8.25 binary, including the same SHA-256 hash. The machine reports an Apple M3 Pro, 12 logical CPUs, 36 GiB of memory, and Darwin 25.5.0 on arm64.
These settings make the new experiment easier to audit. They also mean the two dates are not an unchanged-configuration comparison. The reversal cannot be attributed to a framework improvement, a Node change, or isolation alone: this experiment did not vary those factors independently.
The workload and the clock
The generated project contains 80 contracts in an inheritance chain plus a small vault contract. Including the three test files, each cold build reports 84 Solidity files.
The test workloads are:
- 200 unit tests, each depositing a different amount and checking balances.
- Two parameterized fuzz tests, configured for 256 runs each.
- One invariant campaign, configured for 64 runs at a depth of 32.
The invariant checks that total assets stay within a bound derived from the number of operations. The fixture deploys both a vault and a handler; it does not restrict the campaign exclusively to the handler. Forge's retained output shows calls to both contracts, including reverts.
This matters when interpreting “2,048 calls.” Forge reports that count, but those are attempted calls, not 2,048 successful state changes. Hardhat's standard output reports 64 runs without an equivalent call breakdown. The comparison therefore uses the same configured campaign budget, not a verified identical transaction sequence.
For cold builds, the harness removes Forge's output and cache and Hardhat's artifacts before each observation. This is a clean framework build, not a cold operating-system disk cache. Compiler downloads are outside the measurement.
There are seven measured cold builds per tool. Each other scenario has a preparatory build, two excluded warmups, and 15 measured observations. Execution order alternates between tools. No observations were removed as outliers.
The clock wraps the child process. It includes startup, cached compilation checks, discovery, test execution, and reporting. Logging the captured output happens after the timer stops. These numbers describe command latency; they do not isolate EVM execution speed.
The September results
All times are median wall-clock milliseconds. The difference column is Hardhat minus Foundry, calculated before rounding.
| Workload | Foundry | Hardhat 3 | Difference |
|---|---|---|---|
| Cold build | 2,112ms | 2,573ms | +462ms, +22% |
| Cached build | 244ms | 299ms | +55ms, +23% |
| 200 unit tests | 382ms | 280ms | −102ms, −27% |
| Two fuzz tests, 256 runs each | 385ms | 284ms | −101ms, −26% |
| Invariant, 64 runs at depth 32 | 398ms | 339ms | −59ms, −15% |
Original measurements from September 7, 2026. Bars show medians; marks show every observation. Each panel has its own zero-based millisecond scale. Source data and plotting code are retained with the benchmark.
The unit result is a useful example of why absolute time belongs beside the percentage. Hardhat took about 27% less time, which saved 102ms on this suite. Both commands still finished in well under half a second at the median.
Builds went the other way. Foundry saved 462ms on a clean build and 55ms on a no-change build. Using the same compiler does not make the surrounding work identical: frameworks request outputs, organize compilation, and write artifacts differently. The measurements do not identify which part accounts for the difference.
The spread also matters. Cached Foundry builds ranged from 218ms to 419ms, while Hardhat ranged from 251ms to 409ms. The unit samples were more clearly separated: 370–423ms for Foundry and 263–328ms for Hardhat. This is a descriptive result from one session, without a statistical significance claim.
Keep the August numbers in August
The historical results/latest.json remains untouched. Its medians were:
| Historical August workload | Foundry | Hardhat 3 |
|---|---|---|
| Cold build | 2,187ms | 3,012ms |
| Cached build | 122ms | 274ms |
| Unit tests | 289ms | 326ms |
| Fuzz tests | 275ms | 308ms |
| Invariant | 257ms | 315ms |
Those observations explain the original article's conclusion. They are not fresh measurements, and their ranking did not survive this rerun with the explicit settings above. The September raw data lives separately in results/R1-2026-09-07/measurements.json, with command logs under the run directory named in its metadata.
Reproduce the experiment
Use the benchmark's locked dependencies, Foundry 1.5.1, Node.js 22.17.0, and a native Solidity 0.8.25 binary for your platform. From the repository root:
cd benchmarks/foundry-hardhat
npm ci
npm run generate
export BENCH_SOLC_PATH=/absolute/path/to/solc-0.8.25
"$BENCH_SOLC_PATH" --version
npm run benchmark
Replace the compiler path with a real file. The commands generate the workload and print five scenario summaries. Each completed run writes a timestamped JSON result and retains command outputs, including failures. It does not replace the historical latest.json.
The generator and full benchmark were executed for this update using the installed locked Hardhat version. The clean npm ci installation was verified separately after measurement (57 packages installed; exit 0). Exact observations, configuration evidence, compiler hash, and the chart script are in the repository. Expect your timings to differ; reproduce the setup and inspect passing tests before comparing medians.
What I would take from this
I would measure the commands a team actually waits for before using performance to choose its framework.
This fixture is small and synthetic. It does not cover a production import graph, TypeScript integrations, coverage, plugins, fork tests, RPC latency, or CI hardware. Background machine load and power conditions were not controlled or recorded, so it is not a dedicated laboratory measurement.
Fuzz and invariant results need an additional limit. The engines can generate different inputs, target different sequences, and handle reverts differently. Seeds were not pinned. Equal run counts do not establish equal bug-finding ability, and faster completion does not prove stronger testing.
For this setup, Foundry returned sooner from builds and Hardhat returned sooner from tests. A repository dominated by compilation may value one result; a developer repeatedly running cached unit tests may value the other. The useful decision starts with that workflow, then adds compatibility and maintenance requirements. A universal speed ranking would hide the part developers actually need to know.