docs/examples/fheadd.md
This example demonstrates how to write a simple "a + b" contract using FHEVM.
{% hint style="info" %} To run this example correctly, make sure the files are placed in the following directories:
.sol file → <your-project-root-dir>/contracts/.ts file → <your-project-root-dir>/test/This ensures Hardhat can compile and test your contracts as expected. {% endhint %}
{% tabs %}
{% tab title="FHEAdd.sol" %}
// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.24;
import { FHE, euint8, externalEuint8 } from "@fhevm/solidity/lib/FHE.sol";
import { ZamaEthereumConfig } from "@fhevm/solidity/config/ZamaConfig.sol";
contract FHEAdd is ZamaEthereumConfig {
euint8 private _a;
euint8 private _b;
// solhint-disable-next-line var-name-mixedcase
euint8 private _a_plus_b;
// solhint-disable-next-line no-empty-blocks
constructor() {}
function setA(externalEuint8 inputA, bytes calldata inputProof) external {
_a = FHE.fromExternal(inputA, inputProof);
FHE.allowThis(_a);
}
function setB(externalEuint8 inputB, bytes calldata inputProof) external {
_b = FHE.fromExternal(inputB, inputProof);
FHE.allowThis(_b);
}
function computeAPlusB() external {
// The sum `a + b` is computed by the contract itself (`address(this)`).
// Since the contract has FHE permissions over both `a` and `b`,
// it is authorized to perform the `FHE.add` operation on these values.
// It does not matter if the contract caller (`msg.sender`) has FHE permission or not.
_a_plus_b = FHE.add(_a, _b);
// At this point the contract itself (`address(this)`) has been granted ephemeral FHE permission
// over `_a_plus_b`. This FHE permission will be revoked when the function exits.
//
// Now, to make sure `_a_plus_b` can be decrypted by the contract caller (`msg.sender`),
// we need to grant permanent FHE permissions to both the contract itself (`address(this)`)
// and the contract caller (`msg.sender`)
FHE.allowThis(_a_plus_b);
FHE.allow(_a_plus_b, msg.sender);
}
function result() public view returns (euint8) {
return _a_plus_b;
}
}
{% endtab %}
{% tab title="FHEAdd.ts" %}
import { FHEAdd, FHEAdd__factory } from "../../../types";
import type { Signers } from "../../types";
import { FhevmType, HardhatFhevmRuntimeEnvironment } from "@fhevm/hardhat-plugin";
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
import { expect } from "chai";
import { ethers } from "hardhat";
import * as hre from "hardhat";
async function deployFixture() {
// Contracts are deployed using the first signer/account by default
const factory = (await ethers.getContractFactory("FHEAdd")) as FHEAdd__factory;
const fheAdd = (await factory.deploy()) as FHEAdd;
const fheAdd_address = await fheAdd.getAddress();
return { fheAdd, fheAdd_address };
}
/**
* This trivial example demonstrates the FHE encryption mechanism
* and highlights a common pitfall developers may encounter.
*/
describe("FHEAdd", function () {
let contract: FHEAdd;
let contractAddress: string;
let signers: Signers;
let bob: HardhatEthersSigner;
before(async function () {
// Check whether the tests are running against an FHEVM mock environment
if (!hre.fhevm.isMock) {
throw new Error(`This hardhat test suite cannot run on Sepolia Testnet`);
}
const ethSigners: HardhatEthersSigner[] = await ethers.getSigners();
signers = { owner: ethSigners[0], alice: ethSigners[1] };
bob = ethSigners[2];
});
beforeEach(async function () {
// Deploy a new contract each time we run a new test
const deployment = await deployFixture();
contractAddress = deployment.fheAdd_address;
contract = deployment.fheAdd;
});
it("a + b should succeed", async function () {
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
let tx;
// Let's compute 80 + 123 = 203
const a = 80;
const b = 123;
// Alice encrypts and sets `a` as 80
const inputA = await fhevm.createEncryptedInput(contractAddress, signers.alice.address).add8(a).encrypt();
tx = await contract.connect(signers.alice).setA(inputA.handles[0], inputA.inputProof);
await tx.wait();
// Alice encrypts and sets `b` as 203
const inputB = await fhevm.createEncryptedInput(contractAddress, signers.alice.address).add8(b).encrypt();
tx = await contract.connect(signers.alice).setB(inputB.handles[0], inputB.inputProof);
await tx.wait();
// Why Bob has FHE permissions to execute the operation in this case ?
// See `computeAPlusB()` in `FHEAdd.sol` for a detailed answer
tx = await contract.connect(bob).computeAPlusB();
await tx.wait();
const encryptedAplusB = await contract.result();
const clearAplusB = await fhevm.userDecryptEuint(
FhevmType.euint8, // Specify the encrypted type
encryptedAplusB,
contractAddress, // The contract address
bob, // The user wallet
);
expect(clearAplusB).to.equal(a + b);
});
});
{% endtab %}
{% endtabs %}