Skip to main content
CofheTest is the abstract test base shipped by @cofhe/foundry-plugin. It already inherits forge-std/Test, so inherit CofheTest directly — inheriting both is a redeclaration error.
import { CofheTest } from "@cofhe/foundry-plugin/contracts/CofheTest.sol";

contract MyTest is CofheTest {
    function setUp() public {
        deployMocks();
        // ... your contract deploys
    }
}

deployMocks()

Call once in setUp(). Deploys the full CoFHE mock stack and wires the contracts together:
MockRoleAddress
MockTaskManagerManages FHE operations; stores plaintext values on-chainTASK_MANAGER_ADDRESS (fixed)
MockACLAccess control list for encrypted handles(deployed)
MockZkVerifierVerifies encrypted-input ZK proofs0x…5001 (fixed)
MockZkVerifierSignerSigns encrypted inputs (for the mock verifier)(funded with 10 ether)
MockThresholdNetworkHandles decryption requests0x…5002 (fixed)
MockThresholdNetworkSignerSigns decrypt-result outputs(deployed)
After deployMocks() runs, the following public state vars are available on CofheTest:
mockTaskManager
mockAcl
mockZkVerifier
mockZkVerifierSigner
mockThresholdNetwork
mockThresholdNetworkSigner
Reach into them when you need behavior expectPlaintext doesn’t cover (e.g. permission-denied assertions — see Testing).

createCofheClient()

Returns a fresh CofheClient. You typically follow it with connect(pkey):
CofheClient bob = createCofheClient();
bob.connect(0xB0B);
One client per scenario address. Connecting with a deterministic plaintext private key keeps test output debuggable.

Reading plaintext values

Because MockTaskManager stores plaintext values on-chain, you can read the underlying plaintext of any encrypted handle directly — no permit needed.

getPlaintext(handle)

Returns the on-chain plaintext from MockTaskManager.mockStorage. Typed overloads exist for every encrypted Solidity type:
bytes32 plaintext   = getPlaintext(handle);            // raw
bool    flag        = getPlaintext(eboolHandle);
uint8   v8          = getPlaintext(euint8Handle);
uint16  v16         = getPlaintext(euint16Handle);
uint32  v32         = getPlaintext(euint32Handle);
uint64  v64         = getPlaintext(euint64Handle);
uint128 v128        = getPlaintext(euint128Handle);
address addr        = getPlaintext(eaddressHandle);
Reverts if the handle isn’t in mock storage.

expectPlaintext(handle, value) and (handle, value, "msg")

Assertion variant — typed overloads for the same type set. Faster than decryptForView (no SDK round-trip, no permit needed). Use it whenever you only care about the value, not the SDK code path.
expectPlaintext(counter.count(), uint32(42));
expectPlaintext(counter.count(), uint32(42), "after increment");
Prefer expectPlaintext over decryptForView for state assertions. Reserve the SDK path for tests where the SDK behavior itself is under test.

Logging

The mocks log every FHE operation to the console when log mode is on. Toggle it locally with:
FunctionEffect
enableLogs()Calls mockTaskManager.setLogOps(true)
disableLogs()Calls mockTaskManager.setLogOps(false)
Logging is verbose — enable it only around the operation you’re investigating.
function test_DebuggingIncrement() public {
    enableLogs();
    vm.prank(bob.account());
    counter.increment();
    disableLogs();
}
Each operation prints in a formatted block:
├ FHE.add          | euint32(4473..3424)[0] + euint32(1157..3648)[1]  =>  euint32(1106..1872)[1]
├ FHE.allowThis    | euint32(1106..1872)[1] -> 0x663f..6602

Common pitfalls

Solidity LSPs (Wake, Cursor, etc.) often resolve imports from the monorepo root rather than the package’s node_modules. The plugin’s remappings are package-local, so the LSP may flag valid imports. forge is authoritative — if forge build and forge test succeed, the test is correct.
That import path is the old API from @cofhe/mock-contracts@0.4.x. Migrate to:
import { CofheTest } from "@cofhe/foundry-plugin/contracts/CofheTest.sol";
See the migration mapping for the full rename table.
The remappings are relative to the foundry package’s root. Running forge test from a parent monorepo directory won’t resolve them — forge resolves from the working directory.

Next steps

  • CofheClient — per-user encrypt / decrypt / permit shim.
  • Testing — canonical test-writing patterns and migration mapping.