> ## Documentation Index
> Fetch the complete documentation index at: https://cofhe-docs.fhenix.zone/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing

> A complete Hardhat 3 test for an FHE contract, and the patterns around it

This page builds one test file for an encrypted counter. It then covers the patterns you add as a contract grows: plaintext assertions, ACPs (Access Control Permissions), and decryption for a transaction. It assumes the setup from [Getting Started](/client-sdk/hardhat-3-plugin/getting-started).

## The contract

The examples use this contract. It accepts an encrypted input, increments the stored value, and can publish a decryption result onchain.

```solidity contracts/Counter.sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import '@fhenixprotocol/cofhe-contracts/FHE.sol';

contract Counter {
  euint32 public count;

  function setCount(externalEuint32 inValue, bytes calldata inputProof) external {
    count = FHE.asEuint32(inValue, inputProof);
    FHE.allowThis(count);
    FHE.allowSender(count);
  }

  function increment() external {
    count = FHE.add(count, FHE.asEuint32(1));
    FHE.allowThis(count);
    FHE.allowSender(count);
  }

  function revealCount() external {
    FHE.allowPublic(count);
  }

  function publishCount(uint32 plaintext, bytes calldata signature) external {
    FHE.publishDecryptResult(count, plaintext, signature);
  }
}
```

## Encrypt, store, and decrypt

The core test loop encrypts a value, sends it to the contract, and decrypts the stored handle:

```typescript test/Counter.test.ts theme={null}
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { network } from 'hardhat';
import { Encryptable, FheTypes } from '@cofhe/sdk';

describe('Counter', async () => {
  const { viem, cofhe } = await network.create();
  const [walletClient] = await viem.getWalletClients();

  it('stores and decrypts an encrypted count', async () => {
    const counter = await viem.deployContract('Counter');
    const client = await cofhe.createClientWithBatteries(walletClient);

    // The signature is bound to the contract that consumes the input
    const [countHash, signature] = await client
      .encryptInputs([Encryptable.uint32(42n)])
      .setConsumingContract(counter.address)
      .execute();

    await counter.write.setCount([countHash, signature]);

    const ctHash = await counter.read.count();
    const count = await client.decryptForView(ctHash, FheTypes.Uint32).execute();

    assert.equal(count, 42n);
  });
});
```

Run it:

```bash theme={null}
npx hardhat test
```

<Check>
  The output lists the `stores and decrypts an encrypted count` test under `Counter`, and the summary reads `1 passing (1 nodejs)`.
</Check>

The `describe` callback is `async` so that it can `await network.create()` at the top. Every `it` inside it shares that connection and its mocks, without a `before` hook.

## Start each test from a clean chain

A connection is one chain, so tests in the same `describe` see each other's state. When a test needs a clean chain, open a new connection inside it:

```typescript theme={null}
it('starts from a clean chain', async () => {
  const { viem, cofhe } = await network.create();
  const counter = await viem.deployContract('Counter');
  // ...
});
```

Each `network.create()` call starts a new simulated chain and deploys a new set of mocks. Contracts from other connections do not exist on it.

## Assert on plaintext

Reading the plaintext from the mocks is faster than a decryption and needs no ACP:

```typescript theme={null}
await counter.write.increment();

await cofhe.mocks.expectPlaintext(await counter.read.count(), 43n);
```

Use `decryptForView` when the SDK's own behavior is under test. See [Mock contracts](/client-sdk/hardhat-3-plugin/mock-contracts) for both plaintext helpers.

## Named ACPs

`createClientWithBatteries` creates and selects a self ACP. For a test that needs a second ACP, create it and select it:

```typescript theme={null}
import { ACPUtils } from '@cofhe/sdk/acps';

const acp = await client.acp.createSelf({
  issuer: walletClient.account.address,
  name: 'Counter test ACP',
});

client.acp.selectActiveACP(ACPUtils.getHash(acp));
```

For several signers, create one client per wallet, as shown on the [Client](/client-sdk/hardhat-3-plugin/client#several-signers) page.

## Decrypt for a transaction

[`decryptForTx`](/client-sdk/guides/decrypt-to-tx) resolves to `{ ctHash, decryptedValue, signature }`. Choose the ACP mode before `execute()`.

### Public handles

After the contract calls `FHE.allowPublic`, anyone can decrypt the handle without an ACP:

```typescript theme={null}
await counter.write.increment();
await counter.write.revealCount();

const result = await client
  .decryptForTx(await counter.read.count())
  .withoutACP()
  .execute();

assert.equal(result.decryptedValue, 1n);
```

On a handle that is not public, `withoutACP()` rejects with `mocks decryptForTx call failed: NotAllowed`.

### Handles restricted by the ACL

Pass an ACP, or call `withACP()` with no argument to use the active one:

<CodeGroup>
  ```typescript Active ACP theme={null}
  const result = await client
    .decryptForTx(ctHash)
    .withACP()
    .execute();
  ```

  ```typescript Explicit ACP theme={null}
  const result = await client
    .decryptForTx(ctHash)
    .withACP(acp)
    .execute();
  ```
</CodeGroup>

### Publish the result onchain

Send the value and the signature to the contract, which verifies and stores them with `FHE.publishDecryptResult`:

```typescript theme={null}
await counter.write.publishCount([Number(result.decryptedValue), result.signature]);
```

`decryptedValue` is a `bigint`. viem expects a `number` for a `uint32` argument, so convert it first.

## Common pitfalls

<AccordionGroup>
  <Accordion title="Every test fails with HHE1000" icon="box">
    `HHE1000: Artifact for contract "MockTaskManager" not found` means the mock contracts are missing from `solidity.npmFilesToBuild`. Add them as shown in [Getting Started](/client-sdk/hardhat-3-plugin/getting-started#install-the-plugin).
  </Accordion>

  <Accordion title="TypeScript rejects the wallet client" icon="code">
    Error `TS2345` on `createClientWithBatteries(walletClient)` or `client.connect(...)` means two copies of viem are installed. Add the `viem` override from [Getting Started](/client-sdk/hardhat-3-plugin/getting-started#install-the-plugin) and reinstall from a clean `node_modules`.
  </Accordion>

  <Accordion title="setCount reverts with 0x7ba5ffb5" icon="user">
    The input signature binds the encrypting account and the consuming contract. The call reverts when a different wallet sends the transaction, or when `setConsumingContract` named a different contract. viem reports the revert by its selector, `0x7ba5ffb5`, which is `InvalidSigner(address,address)`.
  </Accordion>

  <Accordion title="encryptInputs has no execute method" icon="lock">
    The SDK types only offer `execute()` after `setConsumingContract()`, so TypeScript reports `Property 'execute' does not exist`. At runtime, the same omission throws `CONSUMING_CONTRACT_UNINITIALIZED`.
  </Accordion>

  <Accordion title="A second operation reverts with 0x4d13139e" icon="key">
    The contract did not call `FHE.allowThis` on a stored handle, so its next transaction cannot use it. viem reports the revert by its selector, `0x4d13139e`, which is `ACLNotAllowed(uint256,address)`. Wrap the call in [`withLogs`](/client-sdk/hardhat-3-plugin/logging) to see which grants each operation made.
  </Accordion>
</AccordionGroup>

## Related

* [Hardhat 2 plugin testing](/client-sdk/hardhat-plugin/testing): the same patterns with Mocha and ethers.
* [Decrypt to transact](/client-sdk/guides/decrypt-to-tx): the full `decryptForTx` guide.
