This page shows the load-bearing patterns for writing FHE contract tests under Hardhat 3 withDocumentation Index
Fetch the complete documentation index at: https://fhenix-docs-hardhat-3-plugin.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
@cofhe/hardhat-3-plugin. Hardhat v2 counterpart: Hardhat Plugin → Testing.
Skeleton
test/Counter.test.ts
Rules
1. async describe runs network.connect() once per file
Hardhat 3’s node:test runner supports top-level await inside the describe callback. Resolve the connection (and deploy mocks) at the top of the describe — every it inside the same block shares the same fresh deployment.
await cofhe.mocks.deployMocks() inside a beforeEach.
2. Use cofhe.createClientWithBatteries() for most tests
It handles config + connect + self-permit in one call. The self permit lets decryptForView work immediately:
cofhe.createConfig / cofhe.createClient only when you need to override defaults (e.g. a non-zero mocks.encryptDelay).
3. Assert plaintext with expectPlaintext whenever possible
decryptForView and needs no permit. Reserve the SDK path for tests where the SDK behavior itself is under test.
4. Call mock contracts directly through their descriptors
For state setup or assertions that don’t need the SDK, spread the descriptor into Viem:5. Wrap noisy code in withLogs(name, fn) for debugging
When a test is misbehaving, scope mock logging to the suspicious block:
Common pitfalls
Connection re-use between describes
Connection re-use between describes
network.connect() returns a fresh deployment each call. If you await network.connect() once and share the result across describe blocks, those blocks share mock state — which is sometimes what you want and sometimes not. When in doubt, call network.connect() per describe.Forgetting `FHE.allowThis` in the contract
Forgetting `FHE.allowThis` in the contract
Tests pass on the first op, then a second op reverts with
ACLNotAllowed because the contract itself isn’t on the ACL. Toggle logging (await cofhe.mocks.enableLogs()) to see the missing grant — every op prints a line showing whether allowThis / allow was called.Wrong wallet client for the SDK input
Wrong wallet client for the SDK input
The client is bound to whichever
walletClient you passed to createClientWithBatteries. Calling the contract from a different wallet client (e.g. via vm.prank-style impersonation) will fail the ZK-verifier signature check — the input was signed for the original wallet, not the new caller.Related
- Hardhat 3 Plugin → Getting Started — install + config.
- Hardhat 3 Plugin → Mock Contracts — descriptors + plaintext inspection.
- Hardhat Plugin (v2) → Testing — the same patterns under Hardhat v2.