Fhenix provides a Foundry-based template for developing Solidity smart contracts and includes sensible defaults. Links are provided to specific topics for further exploration.
- Forge: Tools to compile, test, fuzz, format, and deploy smart contracts.
- Forge Std: A collection of helpful contracts and utilities for testing.
- Prettier: A code formatter for non-Solidity files.
- Solhint: A linter for Solidity code.
- PermissionHelper.sol: Utilities for managing permissions related to FHE operations.
- FheHelper.sol: Utilities for simulating FHE operations.
To create a new repository using this template, click the
Use this template
button at the top of the page.
Alternatively, install the template manually as follows:
$ mkdir my-project
$ cd my-project
$ forge init --template fhenixprotocol/fhenix-foundry-template
$ bun install # install Solhint, Prettier, and other Node.js deps
If this is your first time using Foundry, refer to the installation instructions for guidance.
- Simulated FHE Operations: All FHE operations, including encryption, decryption, and encrypted data handling, are simulated to replicate their behavior in a network environment. This approach facilitates seamless development and testing without requiring a fully operational FHE network.
- Permissions: The template includes utilities (PermissionHelper.sol) for creating permissions related to FHE operations. These utilities enable users to test and verify that contracts correctly implement access-controlled actions, such as viewing balances of encrypted tokens. For more about permissions, see the [Fhenix Documentation] https://docs.fhenix.zone/docs/devdocs/Writing%20Smart%20Contracts/Permissions) section.
Follow these steps to install dependencies:
- Install the dependency using your preferred package manager, for example:
bun install dependency-name
- If installing from Github, use:
bun install github:username/repo-name
- If installing from Github, use:
- Add a remapping for the dependency in remappings.txt, for example:
dependency-name=node_modules/dependency-name
Note that OpenZeppelin Contracts is pre-installed as an example.
To write a new test contract:
- Start by importing
Test
fromforge-std
. - Inherit the test contract.
Note that: Forge Std comes with a pre-instantiated cheatcodes environment, which is accessible via the vm property. To view the logs in the terminal output, add the -vvv flag and use console.log.
This template includes an example test contract FHERC20.t.sol.
For contracts utilizing FHE operations, insert FHE mock operations using the FheEnabled
contract. By inheriting the
FheEnabled
contract in the test contract, you gain access to FHE operations. The following code demonstrates this.
import { FheEnabled } from "./util/FheHelper.sol";
contract MyTestContract is Test, FheEnabled {
// Your test contract code here
}
During test setup, initializeFhe
the FHE environment using the initializeFhe function:
function setUp() public {
initializeFhe();
}
For a complete example, including mocked encryption, decryption, sealing and permission usage, refer to the example tests provided in the tests directory.
The PermissionHelper contract provides utilities for managing permissions related to FHE operations. These utilities enable users to test and verify that contracts correctly implement access-controlled actions, such as viewing balances of encrypted tokens.
Consider using the following code as an example for a PermissionHelper contract in a test contract:
import { Test } from "forge-std/src/Test.sol";
import { ContractWeAreTesting } from "./src/ContractWeAreTesting.sol";
import { PermissionHelper } from "./util/PermissionHelper.sol";
contract MyContract is Test {
ContractWeAreTesting private contractToTest;
PermissionHelper private permitHelper;
function setUp() public {
// The contract we are testing must be deployed first
contractToTest = new ContractWeAreTesting();
// The PermissionHelper contract must be deployed with the address of the contract we are testing
// otherwise the permission generated will not match the address of the contract being tested
permitHelper = new PermissionHelper(address(contractToTest));
}
function testOnlyOwnerCanViewBalance() public {
// Owner key and address
uint256 ownerPrivateKey = 0xA11CE;
address owner = vm.addr(ownerPrivateKey);
// Generate a permission for the owner using the permitHelper and the private key
Permission memory permission = permitHelper.generatePermission(ownerPrivateKey);
// Call function with permission
uint256 result = permissions.someFunctionWithOnlyPermitted(owner, permission);
}
}
Note that the PermissionHelper
contract is initialized only after we know the address of the contract being tested.
The reason is that the permission generated by the PermissionHelper
contract is tied to the address of the contract
that is being tested.
FHE operations in this template simulate the behavior of a real FHE network. Instead of processing encrypted data, operations are performed on plaintext data, which enables seamless development and testing without the need for a fully operational FHE network. However, there are important differences between these mocked FHE operations and actual FHE operations:
- Gas Costs – Gas costs associated with the mocked FHE operations do not accurately reflect those of real FHE operations. Instead, they are based on gas costs of equivalent non-FHE operations.
- Security Zones – In this mocked environment, security zones are not enforced. Thus, any user can perform operations between ciphertexts, which would otherwise fail in a real FHE setting.
- Ciphertext Access – The mocked FHE operations do not enforce access control restrictions on ciphertexts, which allows any user to access any mocked "ciphertext." On a real network, such operations could fail.
- Decrypts during Gas Estimations: When performing a decrypt (or other data revealing operations) during gas estimation on the Helium testnet or Localfhenix, the operation returns a default value, as the gas estimation process does not have access to the precise decrypted data. This can cause the transaction to fail at this stage, if the decrypted data is used in a way that would trigger a transaction revert (e.g., when a require statement depends on it).
- Security – The security provided by the mocked FHE operations does not represent the high level of security offered by real FHE operations. The mocked operations do not involve actual encryption or decryption.
- Performance – The performance of mocked FHE operations is not indicative of the real FHE operation speed. Mocked operations will be significantly faster due to their simplified nature.
The following list contains the most frequently used commands.
Compile and build the contracts:
$ forge build
Delete the build artifacts and cache directories:
$ forge clean
Compile the contracts:
$ forge build
Get a test coverage report:
$ forge coverage
Note: Anvil does not currently support FHE operations. Stay tuned for future updates on Anvil support.
Deploy to Anvil:
$ forge script script/Deploy.s.sol --broadcast --fork-url http://localhost:8545
For this script to work, it is necessary to have a MNEMONIC environment variable set to a valid BIP39 mnemonic.
For instructions on how to deploy to a testnet or mainnet, refer to the Solidity Scripting tutorial.
Format the contracts:
$ forge fmt
Note: Gas usage for FHE operations will be inaccurate due to the mocked nature of these operations. To see the gas-per-operation for FHE operations, refer to the Gas Costs section in our documentation.
Get a gas report:
$ forge test --gas-report
Lint the contracts:
$ bun run lint
Run the tests:
$ forge test
Generate test coverage and output result to the terminal:
$ bun run test:coverage
Generate test coverage with lcov report (you have to open the ./coverage/index.html
file in your browser, to do so
simply copy paste the path):
$ bun run test:coverage:report
- This project is licensed under MIT.
- This project is based on the Foundry Template
Copyright (c) 2024 Paul Razvan Berg License (MIT) https://github.com/PaulRBerg/foundry-template/blob/main/LICENSE.md