> ## 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

> Canonical test-writing patterns for FHE contracts under Foundry

This page shows the load-bearing patterns for writing FHE contract tests under Foundry with `@cofhe/foundry-plugin`. Hardhat counterpart: [Hardhat Plugin → Testing](/client-sdk/hardhat-plugin/testing).

## Skeleton

```solidity test/Counter.t.sol theme={null}
import { CofheTest } from "@cofhe/foundry-plugin/contracts/CofheTest.sol";
import { CofheClient } from "@cofhe/foundry-plugin/contracts/CofheClient.sol";
import { InEuint32 } from "@fhenixprotocol/cofhe-contracts/FHE.sol";
import { Counter } from "../src/Counter.sol";

contract CounterTest is CofheTest {
    Counter public counter;
    CofheClient bob;
    CofheClient alice;

    uint256 constant BOB_PKEY   = 0xB0B;
    uint256 constant ALICE_PKEY = 0xA11CE;

    function setUp() public {
        deployMocks();

        bob   = createCofheClient();   bob.connect(BOB_PKEY);
        alice = createCofheClient();   alice.connect(ALICE_PKEY);

        vm.prank(bob.account());
        counter = new Counter();
    }

    function test_Increments() public {
        vm.prank(bob.account());
        counter.increment();
        expectPlaintext(counter.count(), uint32(1));
    }
}
```

That's the load-bearing shape. Everything below is what to add when the contract gets non-trivial.

## Rules

### 1. Inherit `CofheTest`, not `Test`

`CofheTest` already inherits `forge-std/Test` and exposes the mock state vars (`mockTaskManager`, `mockAcl`, `mockThresholdNetwork`, …) you'll occasionally reach into. Inheriting both is a redeclaration error.

### 2. One `CofheClient` per scenario address

Each user with their own permit/encrypted inputs gets their own client. Connect with a deterministic plaintext private key — don't recycle real keys; these are visible in test output.

```solidity theme={null}
CofheClient bob = createCofheClient();
bob.connect(0xB0B);         // bob.account() == vm.addr(0xB0B)
```

You don't pass `account` to `createInEuintN` — the client is bound at `connect`.

### 3. `vm.prank(client.account())` to act on-chain as that user

```solidity theme={null}
vm.prank(bob.account());
counter.reset(bob.createInEuint32(2000));
```

Mismatching the prank address and the client that produced the input fails the ZK-verifier signature check.

### 4. Assert with `expectPlaintext` whenever possible

```solidity theme={null}
expectPlaintext(counter.count(), uint32(2000));    // typed overload
```

Faster than `decryptForView` and needs no permit. Reserve the SDK path for tests where the SDK behavior itself is under test.

### 5. Test the public-decrypt 3-step flow with `decryptForTx_withoutPermit`

When the contract calls `FHE.publishDecryptResult`:

```solidity theme={null}
// Step 1: contract grants public decrypt permission
vm.prank(bob.account());
counter.allowCounterPublicly();   // FHE.allowPublic(handle)

// Step 2: SDK fetches plaintext + threshold-network signature
bytes32 ctHash = euint32.unwrap(counter.count());
(, uint256 plaintext, bytes memory sig) = bob.decryptForTx_withoutPermit(ctHash);

// Step 3: contract verifies signature and stores plaintext
counter.revealCounter(uint32(plaintext), sig);
assertEq(counter.getDecryptedValue(), plaintext);
```

Same shape runs unmodified against real CoFHE on testnet — the mock signature is produced by the same `MockThresholdNetworkSigner` that `FHE.verifyDecryptResult` accepts.

### 6. Permit-based unseal: `decryptForView` for the success path

```solidity theme={null}
import { Permission } from "@cofhe/mock-contracts/contracts/Permissioned.sol";

Permission memory bobPermit = bob.permit_createSelf();
uint256 value = bob.decryptForView(ctHash, bobPermit);
assertEq(value, expected);
```

`permit_createSelf` builds the EIP-712 typed-data, derives a sealing key from the connected account, and signs — no manual `signPermissionSelf` boilerplate.

### 7. Deny path: asserting "Alice cannot decrypt Bob's value"

`decryptForView` reverts when the caller isn't on the ACL. To assert the deny path, drop down to the mock directly:

```solidity theme={null}
Permission memory alicePermit = alice.permit_createSelf();
(bool allowed, string memory err, ) = mockThresholdNetwork.querySealOutput(
    uint256(ctHash), block.chainid, alicePermit
);
assertFalse(allowed, "Alice should NOT be allowed");
assertEq(err, "NotAllowed");
```

`mockThresholdNetwork` is a public field on `CofheTest`.

### 8. Fuzz tests inherit normally

```solidity theme={null}
function testFuzz_Reset(uint32 v) public {
    InEuint32 memory enc = bob.createInEuint32(v);
    vm.prank(bob.account());
    counter.reset(enc);
    expectPlaintext(counter.count(), v);
}
```

`createInEuintN` accepts the full `uintN` range — no shaping needed.

## Migration from the old `mock-contracts` API

If you're upgrading from `@cofhe/mock-contracts@0.4.x` (where `CoFheTest` lived inside the mocks package), here's the rename table:

| Old API (mock-contracts ≤ 0.4)                                            | New API (`@cofhe/foundry-plugin`)                                              |
| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `import { CoFheTest } from "@cofhe/mock-contracts/foundry/CoFheTest.sol"` | `import { CofheTest } from "@cofhe/foundry-plugin/contracts/CofheTest.sol"`    |
| `is Test, CoFheTest`                                                      | `is CofheTest` (Test already inherited)                                        |
| `assertHashValue(handle, value)`                                          | `expectPlaintext(handle, value)`                                               |
| `mockStorage(ctHash)`                                                     | `getPlaintext(ctHash)`                                                         |
| `createInEuint32(v, bob)`                                                 | `bob.createInEuint32(v)`                                                       |
| `createPermissionSelf(bob)` + `signPermissionSelf(perm, bobKey)`          | `bob.permit_createSelf()` (auto-signs)                                         |
| `createSealingKey(seed)`                                                  | `bob.createSealingKey(seed)` (rarely needed — `permit_createSelf` derives one) |
| `queryDecrypt(hash, chainId, permit)`                                     | `bob.decryptForView(hash, permit)` (reverts on deny)                           |
| Same, asserting deny                                                      | `mockThresholdNetwork.querySealOutput(hash, block.chainid, permit)`            |
| `querySealOutput` + `unseal`                                              | `bob.decryptForView` (does both)                                               |
| `decryptForTxWithoutPermit(ct)` returns `(allowed, error, plaintext)`     | `bob.decryptForTx_withoutPermit(ct)` returns `(ctHash, plaintext, signature)`  |

## Common pitfalls

<AccordionGroup>
  <Accordion title="IDE Solidity errors but `forge test` passes" icon="triangle-exclamation">
    LSPs like Wake/Cursor often resolve from the monorepo root; the plugin's remappings are package-local. **`forge` is the truth** — if `forge build` and `forge test` succeed, the test is correct.
  </Accordion>

  <Accordion title="Forgetting `FHE.allowThis` in the contract" icon="key">
    Tests pass on the first op, then a second op reverts with `ACLNotAllowed` because the contract itself isn't on the ACL. Toggle `enableLogs()` to see the missing grant — every op prints a line showing whether `allowThis`/`allow` was called.
  </Accordion>

  <Accordion title="Wrong client for the prank" icon="user">
    `vm.prank(bob.account())` while the input came from `alice.createInEuintN(...)` fails ZK verification. Always match the client to the prank.
  </Accordion>

  <Accordion title="Stale handle reads" icon="rotate">
    `euint32.unwrap(counter.count())` returns the *current* handle. Storing it in a local then asserting after a write reads the old handle:

    ```solidity theme={null}
    bytes32 oldHandle = euint32.unwrap(counter.count());
    counter.increment();
    expectPlaintext(oldHandle, uint32(0));   // passes by accident
    expectPlaintext(counter.count(), uint32(1)); // ✅ re-fetch
    ```

    Re-fetch after each state change.
  </Accordion>

  <Accordion title="Permit issuer must derive from the connected key" icon="signature">
    The `pkey` passed to `connect` must derive the address used as `permit.issuer`. `bob.permit_createSelf()` after `bob.connect(0xB0B)` produces `issuer == vm.addr(0xB0B)`. Trying to forge a mismatch fails signature verification.
  </Accordion>
</AccordionGroup>

## Related

* [CofheTest](/client-sdk/foundry-plugin/cofhe-test) — the test base contract API.
* [CofheClient](/client-sdk/foundry-plugin/cofhe-client) — the per-user shim API.
* [Hardhat → Testing](/client-sdk/hardhat-plugin/testing) — the same patterns under Hardhat.
