Skip to main content
Set up a Foundry project with @cofhe/foundry-plugin, write a contract that stores an encrypted uint32, and test the encrypt → store → decrypt flow under forge test.
Want to skip the setup? Clone the cofhe-foundry-starter template to get a pre-configured project with everything ready to go.

Prerequisites

  • Foundry (forge, cast, anvil)
  • Node.js 18+ with npm/pnpm/yarn (for installing the plugin’s npm packages)

1. Install dependencies

The plugin and its CoFHE dependencies are distributed via npm. Install them as dev dependencies, then forge install for forge-std:
npm install -D @cofhe/foundry-plugin@^0.5.1 @cofhe/mock-contracts@^0.5.1 @fhenixprotocol/cofhe-contracts@^0.1.3 @openzeppelin/contracts
forge install foundry-rs/forge-std

2. Configure foundry.toml

foundry.toml
[profile.default]
src = "src"
out = "out"
test = "test"
script = "script"
libs = ["node_modules"]
solc_version = "0.8.25"
evm_version = "cancun"
auto_detect_remappings = false
code_size_limit = 100000
  • evm_version = "cancun" is required — MockACL uses transient storage opcodes.
  • code_size_limit = 100000 is required — the mock contracts exceed the EIP-170 24 KB limit.

3. Configure remappings.txt

remappings.txt
forge-std/=node_modules/forge-std/src/
hardhat/=node_modules/forge-std/src/
@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/
@fhenixprotocol/cofhe-contracts/=node_modules/@fhenixprotocol/cofhe-contracts/
@cofhe/mock-contracts/=node_modules/@cofhe/mock-contracts/
@cofhe/foundry-plugin/=node_modules/@cofhe/foundry-plugin/
The hardhat/=node_modules/forge-std/src/ line is load-bearingMockCoFHE.sol imports hardhat/console.sol, and this alias resolves it to forge-std’s compatible console.sol.

4. Write a contract

src/MyContract.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.25;

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

contract MyContract {
    euint32 public storedValue;

    function setValue(InEuint32 memory inValue) external {
        storedValue = FHE.asEuint32(inValue);
        FHE.allowThis(storedValue);
        FHE.allowSender(storedValue);
    }
}
  • euint32 — an encrypted uint32 stored on-chain as a ciphertext handle.
  • InEuint32 — the encrypted input struct produced by CofheClient.
  • FHE.allowThis / FHE.allowSender — grant the contract and caller permission to read the encrypted value (required by the ACL).

5. Write a test

test/MyContract.t.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.25;

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 { MyContract } from "../src/MyContract.sol";

contract MyContractTest is CofheTest {
    MyContract public myContract;
    CofheClient public bob;

    uint256 constant BOB_PKEY = 0xB0B;

    function setUp() public {
        deployMocks();

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

        vm.prank(bob.account());
        myContract = new MyContract();
    }

    function test_StoresAndDecryptsAnEncryptedValue() public {
        // 1. Encrypt the input
        InEuint32 memory encrypted = bob.createInEuint32(42);

        // 2. Send to contract
        vm.prank(bob.account());
        myContract.setValue(encrypted);

        // 3. Assert the stored plaintext directly (mock-only, no permit needed)
        expectPlaintext(myContract.storedValue(), uint32(42));
    }
}

6. Run

forge test -vvv
[PASS] test_StoresAndDecryptsAnEncryptedValue() (gas: …)

Test result: ok. 1 passed; 0 failed; 0 skipped; finished in …

What just happened?

  1. deployMocks() deployed the full CoFHE coprocessor mock stack (TaskManager, ACL, ZK verifier, threshold network) to the in-process EVM.
  2. createCofheClient() + bob.connect(BOB_PKEY) spun up an in-Solidity SDK shim bound to vm.addr(BOB_PKEY).
  3. bob.createInEuint32(42) produced a signed InEuint32 — the same shape your contract receives on testnet, signed by MockZkVerifierSigner.
  4. vm.prank(bob.account()) + setValue(...) called the contract as Bob. The contract stored the ciphertext handle and granted ACL access to itself and Bob.
  5. expectPlaintext(myContract.storedValue(), 42) read the on-chain plaintext directly from MockTaskManager.mockStorage — no permit, no SDK round-trip.

Next steps