Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Testing

This page shows the common patterns for writing Hardhat tests with the CoFHE plugin.

Test setup

Use hre.cofhe.createClientWithBatteries in a before hook. It creates and connects a fully configured CofheClient — including a self-ACP — so the client is ready for every test in the suite:

import hre from 'hardhat';
import { CofheClient } from '@cofhe/sdk';
import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers';
 
let cofheClient: CofheClient;
let signer: HardhatEthersSigner;
 
before(async () => {
  [signer] = await hre.ethers.getSigners();
  cofheClient = await hre.cofhe.createClientWithBatteries(signer); 
});

See Client for manual setup options.

Encrypt → store → decrypt

The core test loop: encrypt a value, pass it to a contract, then decrypt the stored handle.

// 1. Encrypt the input: [hash, signature]
const [hash, signature] = await cofheClient
  .encryptInputs([Encryptable.uint32(100n)])
  .setConsumingContract(await testContract.getAddress())
  .execute();
 
// 2. Send to contract
const tx = await testContract.setValueBatch([hash], signature);
await tx.wait();
 
// 3. Read the stored handle
const ctHash = await testContract.storedValue();
 
// 4. Decrypt for display
const decrypted = await cofheClient
  .decryptForView(ctHash, FheTypes.Uint32)
  .execute();
 
expect(decrypted).to.equal(100n);

For more on each step, see Encrypting Inputs and Decrypting to View.

Reading plaintext directly

In tests you can bypass the normal decrypt flow and read the raw plaintext stored by the mock contracts. This is useful for asserting contract state without needing an ACP:

// Get raw plaintext value
const plaintext = await hre.cofhe.mocks.getPlaintext(ctHash);
 
// Or use the assertion shorthand
await hre.cofhe.mocks.expectPlaintext(ctHash, 100n);

See Mock Contracts for details.

ACPs

createClientWithBatteries pre-generates a self-ACP, so decryptForView and decryptForTx().withACP() work immediately. For tests that need named ACPs or multiple signers, create them explicitly:

const acp = await cofheClient.acp.createSelf({
  issuer: signer.address,
  name: 'My Test ACP',
});
 
// Select it as the active acp
const acpHash = ACPUtils.getHash(acp);
cofheClient.acp.selectActiveACP(acpHash);

Alternatively, you may find it easier to create a cofheClient for each of the multiple signers you need to test.

const [bob, alice] = await hre.ethers.getSigners();
const bobClient = await hre.cofhe.createClientWithBatteries(bob);
const aliceClient = await hre.cofhe.createClientWithBatteries(alice);

decryptForTx patterns

decryptForTx returns a { ctHash, decryptedValue, signature } tuple for on-chain submission. The ACP mode must be selected explicitly.

Globally allowed values (.withoutACP())

When a contract calls FHE.allowPublic(handle), anyone can decrypt without an ACP:

const result = await cofheClient
  .decryptForTx(publicCtHash)
  .withoutACP() 
  .execute();
 
expect(result.decryptedValue).to.equal(55n);

Access-controlled values (.withACP())

For handles restricted by ACL policy, supply an ACP:

Explicit acp
const result = await cofheClient
  .decryptForTx(ctHash)
  .withACP(acp) 
  .execute();
 
expect(result.decryptedValue).to.equal(99n);

Submitting the result on-chain

Pass the result directly to your contract:

await myContract.revealValue(
  result.ctHash,
  result.decryptedValue,
  result.signature
);

For a full walkthrough of decryptForTx, see Decrypting to Transact.