@diamondslab/diamonds-hardhat-foundry
v2.4.0
Published
Hardhat plugin that integrates Foundry testing with Diamond proxy contracts, providing deployment helpers and fuzzing support
Maintainers
Readme
@diamondslab/diamonds-hardhat-foundry
Hardhat plugin that seamlessly integrates Foundry testing with ERC-2535 Diamond proxy contracts. This plugin provides deployment helpers, test scaffolding, and automated test generation for Diamond-based smart contracts using Foundry's powerful testing framework.
Production Ready: 141/141 tests passing (100% success rate) with comprehensive coverage across unit, integration, fuzz, and invariant testing.
Features
- 🚀 Automated Diamond Deployment - Deploy Diamond contracts with a single command
- 📝 Helper Generation - Automatically generate Solidity helpers with deployment data
- 🧪 Test Scaffolding - Create unit, integration, and fuzz test templates
- � Coverage Testing - Run forge coverage with full Diamond integration and multiple report formats
- 🔧 Hardhat Tasks - CLI tasks for init, deploy, generate, test, and coverage workflows
- 🎯 Programmatic API - Use framework classes directly in scripts
- 📚 Base Contracts - Reusable Solidity utilities and test base classes
- ⚡ Foundry Integration - Leverage Forge's speed and fuzzing capabilities
- 🔗 Diamonds Ecosystem - Works seamlessly with
@diamondslab/diamondsand@diamondslab/hardhat-diamonds
Installation
npm install --save-dev @diamondslab/diamonds-hardhat-foundry
# or
yarn add -D @diamondslab/diamonds-hardhat-foundry
# or
pnpm add -D @diamondslab/diamonds-hardhat-foundryPrerequisites
- Foundry: Install from getfoundry.sh
- Hardhat:
^2.26.0or later - Required Peer Dependencies:
@diamondslab/diamonds- Core Diamond deployment library@diamondslab/hardhat-diamonds- Hardhat Diamond configuration and LocalDiamondDeployer@nomicfoundation/hardhat-ethers- Ethers.js integrationethers- Ethereum library
npm install --save-dev @diamondslab/diamonds @diamondslab/hardhat-diamonds @nomicfoundation/hardhat-ethers ethers hardhatNote: Version 2.0.0+ requires
@diamondslab/hardhat-diamondsas a peer dependency for LocalDiamondDeployer. See MIGRATION.md for upgrade instructions from v1.x.
Quick Start
1. Configure Hardhat
Import the plugin in your hardhat.config.ts:
import "@diamondslab/diamonds-hardhat-foundry";
import type { HardhatUserConfig } from "hardhat/config";
const config: HardhatUserConfig = {
solidity: "0.8.28",
// Optional: Configure diamonds-foundry settings
diamondsFoundry: {
helpersDir: "test/foundry/helpers",
generateExamples: true,
exampleTests: ["unit", "integration", "fuzz"],
defaultNetwork: "hardhat",
reuseDeployment: false,
forgeTestArgs: ["-vvv"],
},
};
export default config;2. Initialize Test Structure
npx hardhat diamonds-forge:initThis creates:
test/foundry/
├── helpers/ # Generated Diamond deployment helpers
├── unit/ # Unit test examples
├── integration/ # Integration test examples
└── fuzz/ # Fuzz test examples3. Deploy Your Diamond
npx hardhat diamonds-forge:deploy --diamond-name YourDiamond --network hardhat4. Generate Helpers
npx hardhat diamonds-forge:generate-helpers --diamond-name YourDiamondThis generates test/foundry/helpers/DiamondDeployment.sol with:
- Diamond contract address
- All facet addresses
- Helper functions for test setup
Importing Helper Contracts
Version 2.0.0+ provides importable Solidity helper contracts for your tests:
DiamondFuzzBase - Base Contract for Fuzz Tests
Extend DiamondFuzzBase to create Diamond fuzz tests with built-in helpers:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "@diamondslab/diamonds-hardhat-foundry/contracts/DiamondFuzzBase.sol";
import "../helpers/DiamondDeployment.sol";
contract MyDiamondFuzzTest is DiamondFuzzBase {
/// Override to load your deployed Diamond
function _loadDiamondAddress() internal view override returns (address) {
return DiamondDeployment.diamond();
}
function setUp() public override {
super.setUp(); // Loads Diamond and ABI
// Your additional setup
}
function testFuzz_SomeFunction(address user, uint256 amount) public {
// Use built-in helpers
vm.assume(DiamondForgeHelpers.isValidTestAddress(user));
vm.assume(DiamondForgeHelpers.isValidTestAmount(amount));
bytes4 selector = bytes4(keccak256("transfer(address,uint256)"));
bytes memory data = abi.encode(user, amount);
(bool success, ) = _callDiamond(selector, data);
assertTrue(success);
}
}Built-in DiamondFuzzBase Methods:
_loadDiamondAddress()- Override to provide Diamond address_getDiamondABIPath()- Override to customize ABI file path_callDiamond(selector, data)- Call Diamond function_callDiamondWithValue(selector, data, value)- Call payable function_expectDiamondRevert(selector, data, expectedError)- Test reverts_verifyFacetRouting(selector, expectedFacet)- Check selector routing_measureDiamondGas(selector, data)- Measure gas consumption_getDiamondOwner()- Get Diamond owner address_hasRole(role, account)- Check role-based access control_grantRole(role, account)- Grant role (requires permissions)_revokeRole(role, account)- Revoke role
DiamondForgeHelpers - Utility Library
Use DiamondForgeHelpers for validation, assertions, and DiamondLoupe queries:
import "@diamondslab/diamonds-hardhat-foundry/contracts/DiamondForgeHelpers.sol";
contract MyTest is Test {
using DiamondForgeHelpers for address;
function setUp() public {
address diamond = deployDiamond();
// Validate Diamond deployment
DiamondForgeHelpers.assertValidDiamond(diamond);
// Validate facet
address facet = getFacet();
DiamondForgeHelpers.assertValidFacet(facet, "MyFacet");
}
function testFuzz_ValidInputs(address addr, uint256 amount) public {
// Filter fuzz inputs
vm.assume(DiamondForgeHelpers.isValidTestAddress(addr));
vm.assume(DiamondForgeHelpers.isValidTestAmount(amount));
// Your test logic
}
function testSelectorRouting() public {
bytes4 selector = bytes4(keccak256("someFunction()"));
// Assert selector exists
DiamondForgeHelpers.assertSelectorExists(diamond, selector);
// Assert routing to expected facet
DiamondForgeHelpers.assertSelectorRouting(diamond, selector, expectedFacet);
// Get facet address
address facet = DiamondForgeHelpers.getFacetAddress(diamond, selector);
}
}DiamondForgeHelpers Functions:
assertValidDiamond(address)- Validate Diamond deploymentassertValidFacet(address, name)- Validate facet deploymentisValidTestAddress(address)- Filter fuzz addressesisValidTestAmount(uint256)- Filter fuzz amountsassertSelectorExists(diamond, selector)- Verify selector registeredassertSelectorRouting(diamond, selector, facet)- Verify routinggetFacetAddress(diamond, selector)- Get facet for selectorgetFacetAddresses(diamond)- Get all facet addressesgetFacetSelectors(diamond, facet)- Get selectors for facetgetDiamondOwner(diamond)- Get owner addressboundAddress(seed)- Generate valid fuzz addressboundAmount(seed, min, max)- Generate valid fuzz amountselectorsEqual(a, b)- Compare selector arrays
DiamondABILoader - ABI File Parser
Load and parse Diamond ABI files in your tests:
import "@diamondslab/diamonds-hardhat-foundry/contracts/DiamondABILoader.sol";
contract MyIntegrationTest is Test {
using DiamondABILoader for string;
function testABILoading() public {
// Load Diamond ABI
string memory abiJson = DiamondABILoader.loadDiamondABI("./diamond-abi/MyDiamond.json");
// Extract selectors and signatures
bytes4[] memory selectors = abiJson.extractSelectors();
string[] memory signatures = abiJson.extractSignatures();
console.log("Functions found:", selectors.length);
// Get function info
for (uint i = 0; i < selectors.length; i++) {
(bytes4 sel, string memory sig) = abiJson.getFunctionInfo(i);
console.log("Function:", sig);
}
}
}DiamondABILoader Functions:
loadDiamondABI(path)- Load ABI JSON from fileextractSelectors(abiJson)- Extract all function selectorsextractSignatures(abiJson)- Extract all function signaturesgetFunctionInfo(abiJson, index)- Get selector and signatureverifySelectorsMatch(abiJson, expectedSelectors)- Validate selectors
5. Run Foundry Tests
npx hardhat diamonds-forge:testHardhat Tasks
diamonds-forge:init
Initialize the Foundry test directory structure.
Options:
--helpers-dir <path>- Custom directory for helpers (default:test/foundry/helpers)--examples- Generate example tests (default: from config)--force- Overwrite existing files
Example:
npx hardhat diamonds-forge:init --helpers-dir test/forge/helpers --examplesdiamonds-forge:deploy
Deploy a Diamond contract to the specified network.
Options:
--diamond-name <name>- Name of the Diamond to deploy (default: from config)--network <name>- Network to deploy to (default:hardhat)--reuse- Reuse existing deployment if found (default: from config)--force- Force new deployment even if one exists
Example:
npx hardhat diamonds-forge:deploy --diamond-name MyDiamond --network sepoliadiamonds-forge:generate-helpers
Generate Solidity helper contract with deployment data.
Options:
--diamond-name <name>- Name of the Diamond (default: from config)--output-dir <path>- Output directory for helpers (default: from config)--network <name>- Network to use for deployment data (default:hardhat)
Example:
npx hardhat diamonds-forge:generate-helpers --diamond-name MyDiamond --output-dir test/foundry/helpersdiamonds-forge:test
Run Foundry tests with optional deployment and helper generation.
Options:
--diamond-name <name>- Diamond to deploy/test (default: from config)--network <name>- Network for deployment (default: from config)--skip-deployment- Skip deployment step--skip-helpers- Skip helper generation step--match-test <pattern>- Run tests matching pattern--match-contract <pattern>- Run tests in matching contracts--verbosity <level>- Forge verbosity level (1-5)--gas-report- Show gas usage report
Example:
# Run all tests with gas reporting
npx hardhat diamonds-forge:test --gas-report
# Run specific tests with high verbosity
npx hardhat diamonds-forge:test --match-test "testOwnership" --verbosity 4
# Skip deployment (use existing)
npx hardhat diamonds-forge:test --skip-deployment --match-contract "MyTest"diamonds-forge:coverage
Run forge coverage for your Diamond contracts with full integration support.
Important: Coverage requires a deployed Diamond on a persistent network (like localhost). See workflow below.
Options:
--diamond-name <name>- Diamond to analyze (default: from config)--network <name>- Network for deployment (default: from config)--report <format>- Report format: summary, lcov, debug, bytecode (default: summary)--report-file <path>- Output file for coverage report--lcov-version <version>- LCOV version for LCOV reports (default: v1)--match-test <pattern>- Run tests matching pattern--match-contract <pattern>- Run tests in matching contracts--match-path <glob>- Run tests in files matching glob--no-match-test <pattern>- Skip tests matching pattern--no-match-contract <pattern>- Skip tests in matching contracts--no-match-path <glob>- Skip tests in files matching glob--no-match-coverage <pattern>- Exclude contracts from coverage--verbosity <level>- Verbosity level: 0-5 (default: 0)--color <mode>- Color output: auto, always, never (default: auto)- And many more options for filtering, optimization, and EVM configuration
Workflow (Required):
# Step 1: Start Hardhat node (persistent network)
npx hardhat node
# Step 2: Deploy Diamond to localhost network (in another terminal)
npx hardhat diamonds-forge:deploy --diamond-name MyDiamond --network localhost
# Step 3: Run coverage against deployed Diamond
npx hardhat diamonds-forge:coverage --diamond-name MyDiamond --network localhostExamples:
# Basic coverage with default summary
npx hardhat diamonds-forge:coverage --diamond-name MyDiamond --network localhost
# Generate LCOV report for CI/CD
npx hardhat diamonds-forge:coverage \
--diamond-name MyDiamond \
--network localhost \
--report lcov \
--report-file coverage/lcov.info
# Coverage for specific test patterns
npx hardhat diamonds-forge:coverage \
--diamond-name MyDiamond \
--network localhost \
--match-contract "Unit" \
--verbosity 2
# Multiple report formats
npx hardhat diamonds-forge:coverage \
--diamond-name MyDiamond \
--network localhost \
--report summary \
--report lcov \
--report debugImportant Notes:
- Always specify
--network localhost(coverage needs deployed contracts) - Cannot use
--network hardhat(ephemeral in-memory network) - Diamond must be deployed before running coverage
- Same workflow as
diamonds-forge:test- both require persistent network
See the Coverage Guide for complete documentation, CI/CD integration examples, and best practices.
Dynamic Helper Generation
Version 2.0.0+ introduces dynamic helper generation that creates deployment-specific Solidity helpers without hardcoded addresses.
How It Works
When you deploy a Diamond and generate helpers, the plugin creates test/foundry/helpers/DiamondDeployment.sol with:
library DiamondDeployment {
/// @notice Get the deployed Diamond address
function getDiamondAddress() internal pure returns (address) {
return 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512;
}
/// @notice Get the deployer address
function getDeployerAddress() internal pure returns (address) {
return 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266;
}
// Additional deployment-specific data...
}Key Benefits:
- ✅ No hardcoded addresses in test files - All addresses come from deployment records
- ✅ Network-specific helpers - Different helpers for different networks
- ✅ Automatic regeneration - Helpers update when you redeploy
- ✅ Type-safe - Solidity library ensures compile-time validation
Using Generated Helpers
import "../helpers/DiamondDeployment.sol";
contract MyTest is Test {
address diamond;
address deployer;
function setUp() public {
// Load from generated helpers
diamond = DiamondDeployment.getDiamondAddress();
deployer = DiamondDeployment.getDeployerAddress();
// Use in tests
vm.prank(deployer);
IDiamond(diamond).someFunction();
}
}Deployment Management
The plugin provides flexible deployment management with two modes:
Ephemeral Deployments (Default)
For quick testing without persisting deployment records:
# Deploy Diamond for this test run only
npx hardhat diamonds-forge:test --diamond-name MyDiamond
# Deployment is cached in memory but not saved to file
# Helpers are generated from cached data
# Next run will deploy fresh DiamondUse ephemeral mode when:
- Running tests in CI/CD
- Testing with default Hardhat network
- You don't need to reuse deployments
- Each test run should start clean
Persistent Deployments
Save deployment records for reuse across test runs:
# Deploy and save deployment record
npx hardhat diamonds-forge:deploy --diamond-name MyDiamond --network localhost
# Run tests using saved deployment
npx hardhat diamonds-forge:test --network localhost --use-deployment
# Deployment is loaded from file
# Subsequent runs reuse the same DiamondUse persistent mode when:
- Testing on persistent networks (localhost, testnets)
- Developing against specific Diamond deployment
- Testing upgrade scenarios
- Running integration tests
Task Flags Reference
Deployment Control:
--save-deployment- Save deployment record to file (persistent mode)--use-deployment- Load existing deployment instead of deploying new one--force-deploy- Force new deployment even if one exists
Helper Control:
--skip-helpers- Don't generate DiamondDeployment.sol--helpers-dir <path>- Custom output directory for helpers
Test Filtering:
--match-test <pattern>- Run only tests matching name pattern--match-contract <contract>- Run only tests in specified contract--match-path <path>- Run only tests in files matching path
Output Control:
--verbosity <1-5>- Set Forge output verbosity (default: 2)--gas-report- Display detailed gas usage report--coverage- Generate test coverage report
Network Control:
--network <name>- Network to use (hardhat, localhost, sepolia, etc.)--fork-url <url>- Custom RPC URL for forking
Examples
# Quick ephemeral test (default)
npx hardhat diamonds-forge:test
# Save deployment for reuse
npx hardhat diamonds-forge:test --save-deployment --network localhost
# Reuse saved deployment
npx hardhat diamonds-forge:test --use-deployment --network localhost
# Test specific functionality with gas report
npx hardhat diamonds-forge:test --match-test "testOwnership" --gas-report
# Run only fuzz tests with high verbosity
npx hardhat diamonds-forge:test --match-contract "Fuzz" --verbosity 4
# Force redeploy even if deployment exists
npx hardhat diamonds-forge:test --force-deploy --network localhostSnapshot and Restore
The DiamondForgeHelpers library provides snapshot/restore functionality for advanced testing scenarios.
Basic Usage
import "@diamondslab/diamonds-hardhat-foundry/contracts/DiamondForgeHelpers.sol";
contract MyTest is Test {
using DiamondForgeHelpers for *;
function test_MultipleScenarios() public {
// Save initial state
uint256 snapshot = DiamondForgeHelpers.snapshotState();
// Test scenario A
runScenarioA();
// Restore to initial state
DiamondForgeHelpers.revertToSnapshot(snapshot);
// Test scenario B from same starting point
runScenarioB();
}
}When to Use Snapshots
Good Use Cases:
- Testing multiple outcomes from same initial state
- Expensive setup that you want to reuse
- Testing state transitions and rollbacks
- Benchmarking gas costs across scenarios
Don't Use For:
- Normal test isolation (Forge does this automatically)
- Production/testnet testing (snapshots only work locally)
Snapshot Examples
See comprehensive examples in test/foundry/integration/SnapshotExample.t.sol:
forge test --match-path "**/SnapshotExample.t.sol" -vvAvailable in examples:
- Basic snapshot/restore workflow
- Multiple snapshots with different states
- Snapshot with contract state changes
- Test isolation patterns
For detailed snapshot documentation, see TESTING.md.
Programmatic API
Use the framework classes directly in your scripts:
import hre from "hardhat";
import {
ForgeFuzzingFramework,
DeploymentManager,
HelperGenerator,
} from "@diamondslab/diamonds-hardhat-foundry";
// Deploy a Diamond
const deployer = new DeploymentManager(hre);
await deployer.deploy("MyDiamond", "hardhat");
// Generate helpers
const generator = new HelperGenerator(hre);
await generator.scaffoldProject();
await generator.generateDeploymentHelpers("MyDiamond", "hardhat");
// Run tests
const framework = new ForgeFuzzingFramework(hre);
await framework.runTests({
diamondName: "MyDiamond",
networkName: "hardhat",
skipDeployment: false,
skipHelpers: false,
forgeTestOptions: {
matchTest: "testFuzz",
verbosity: 3,
},
});DeploymentManager
Manages Diamond contract deployments.
Methods:
deploy(diamondName, networkName, force?)- Deploy a DiamondgetDeployment(diamondName, networkName, chainId?)- Get deployment dataensureDeployment(diamondName, networkName, force?)- Deploy if neededhasDeploymentRecord(diamondName, networkName)- Check if deployment exists
HelperGenerator
Generates Solidity test helpers and examples.
Methods:
scaffoldProject(outputDir?, generateExamples?)- Create test directory structuregenerateDeploymentHelpers(diamondName, networkName, outputDir?)- Generate DiamondDeployment.solgenerateExampleTests(testTypes?, outputDir?)- Generate example test files
ForgeFuzzingFramework
Orchestrates the complete test workflow.
Methods:
runTests(options)- Run complete test workflowdeployOnly(diamondName?, networkName?, force?)- Deploy onlygenerateHelpersOnly(diamondName?, networkName?, outputDir?)- Generate helpers only
Configuration
Configure the plugin in hardhat.config.ts:
export default {
diamondsFoundry: {
// Directory for generated helper contracts
helpersDir: "test/foundry/helpers",
// Whether to generate example tests during init
generateExamples: true,
// Types of example tests to generate
exampleTests: ["unit", "integration", "fuzz"],
// Default network for deployments
defaultNetwork: "hardhat",
// Reuse existing deployments instead of redeploying
reuseDeployment: false,
// Default arguments to pass to forge test
forgeTestArgs: ["-vv", "--gas-report"],
},
};Configuration Options:
| Option | Type | Default | Description |
| ------------------ | ---------- | --------------------------------- | ------------------------------- |
| helpersDir | string | "test/foundry/helpers" | Directory for generated helpers |
| generateExamples | boolean | true | Generate example tests on init |
| exampleTests | array | ["unit", "integration", "fuzz"] | Types of examples to generate |
| defaultNetwork | string | "hardhat" | Default deployment network |
| reuseDeployment | boolean | false | Reuse existing deployments |
| forgeTestArgs | string[] | [] | Default forge test arguments |
Base Contracts
The plugin provides base Solidity contracts for your tests:
DiamondForgeHelpers
Utility functions for Diamond testing:
import "@diamondslab/diamonds-hardhat-foundry/contracts/DiamondForgeHelpers.sol";
contract MyTest is Test {
using DiamondForgeHelpers for *;
function testDiamond() public {
address diamond = DiamondForgeHelpers.getDiamondAddress();
DiamondForgeHelpers.logDiamondInfo(diamond);
DiamondForgeHelpers.assertValidDiamond(diamond);
}
}DiamondFuzzBase
Base contract for fuzz tests:
import "@diamondslab/diamonds-hardhat-foundry/contracts/DiamondFuzzBase.sol";
contract MyFuzzTest is DiamondFuzzBase {
function setUp() public override {
super.setUp();
// Your setup
}
function testFuzz_Transfer(address to, uint256 amount) public {
assumeValidAddress(to);
amount = boundValue(amount, 1, 1000 ether);
// Test logic
}
}Usage Examples
Example 1: Deploy and Test
# Initialize project
npx hardhat diamonds-forge:init
# Deploy Diamond
npx hardhat diamonds-forge:deploy --diamond-name MyDiamond
# Run tests
npx hardhat diamonds-forge:testExample 2: Custom Workflow
import hre from "hardhat";
import { ForgeFuzzingFramework } from "@diamondslab/diamonds-hardhat-foundry";
async function main() {
const framework = new ForgeFuzzingFramework(hre);
// Deploy only
await framework.deployOnly("MyDiamond", "sepolia");
// Generate helpers
await framework.generateHelpersOnly("MyDiamond", "sepolia");
// Run specific tests
await framework.runTests({
skipDeployment: true,
skipHelpers: true,
forgeTestOptions: {
matchContract: "MyIntegrationTest",
verbosity: 4,
},
});
}
main().catch(console.error);Example 3: CI/CD Integration
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
foundry-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: foundry-rs/foundry-toolchain@v1
- uses: actions/setup-node@v3
with:
node-version: "18"
- run: npm ci
- run: npx hardhat diamonds-forge:deploy
- run: npx hardhat diamonds-forge:test --gas-reportIntegration with Existing Diamonds Projects
This plugin is designed to work seamlessly with existing @diamondslab/diamonds projects:
- Use your existing Diamond configuration - The plugin reads from your project's Diamond deployment configuration
- LocalDiamondDeployer integration - Works with your project's
LocalDiamondDeployerclass - Deployment records - Uses the same deployment record format as
@diamondslab/diamonds - Hardhat configuration - Integrates with
@diamondslab/hardhat-diamondssettings
Example with existing Diamond:
// Your existing hardhat.config.ts
import "@diamondslab/hardhat-diamonds";
import "@diamondslab/diamonds-hardhat-foundry";
export default {
diamonds: {
diamondsPath: "./diamonds",
deploymentsPath: "./diamonds/ExampleDiamond/deployments",
},
diamondsFoundry: {
helpersDir: "test/foundry/helpers",
defaultNetwork: "hardhat",
},
};Troubleshooting
Common Issues
Foundry Not Found
Error: Foundry not installed or forge: command not found
Solution: Install Foundry from getfoundry.sh:
curl -L https://foundry.paradigm.xyz | bash
foundryupVerify installation:
forge --versionLocalDiamondDeployer Not Found
Error: LocalDiamondDeployer not found or Cannot find module
Solution: Ensure you have the @diamondslab/hardhat-diamonds package installed:
npm install --save-dev @diamondslab/hardhat-diamondsImport it in your hardhat.config.ts:
import "@diamondslab/hardhat-diamonds";
import "@diamondslab/diamonds-hardhat-foundry";Deployment Record Not Found
Error: No deployment record found for MyDiamond-hardhat-31337.json
Solution: Deploy your Diamond first:
npx hardhat diamonds-forge:deploy --diamond-name MyDiamond --network localhostOr use ephemeral deployment (default) which doesn't require saved records:
npx hardhat diamonds-forge:testDiamond Has No Code
Error: DiamondFuzzBase: Diamond has no code when running tests
Cause: Test is trying to use a Diamond address that doesn't have deployed code.
Solutions:
For tests using hardhat network (ephemeral):
- Tests should deploy their own Diamond in
setUp() - See
BasicDiamondIntegration.t.solfor self-deploying pattern
- Tests should deploy their own Diamond in
For tests using deployed Diamond:
- Start Hardhat node:
npx hardhat node - Deploy Diamond:
npx hardhat diamonds-forge:deploy --network localhost - Run tests:
npx hardhat diamonds-forge:test --network localhost
- Start Hardhat node:
Make tests fork-aware:
function setUp() public {
diamond = DiamondDeployment.getDiamondAddress();
// Check if Diamond is deployed (forking)
if (diamond.code.length == 0) {
// Skip test or deploy in test
return;
}
// Proceed with test setup
}Generated Helpers Not Compiling
Error: Compilation errors in test/foundry/helpers/DiamondDeployment.sol
Solutions:
- Ensure Foundry remappings are correct in
foundry.toml:
[profile.default]
src = "contracts"
test = "test/foundry"
remappings = [
"@diamondslab/diamonds-hardhat-foundry/=node_modules/@diamondslab/diamonds-hardhat-foundry/",
]- Verify Diamond deployed successfully before generating helpers:
npx hardhat diamonds-forge:deploy --diamond-name MyDiamond
npx hardhat diamonds-forge:generate-helpers --diamond-name MyDiamond- Check that deployment data is valid:
- Look in
diamonds/MyDiamond/deployments/ - Verify JSON file contains
DiamondAddressfield
- Look in
Peer Dependency Warnings
Warning: ERESOLVE unable to resolve dependency tree or peer dependency conflicts
Solution: Install all required peer dependencies:
npm install --save-dev \
@diamondslab/diamonds \
@diamondslab/hardhat-diamonds \
@nomicfoundation/hardhat-ethers \
ethers \
hardhatFor package.json, specify compatible versions:
{
"devDependencies": {
"@diamondslab/diamonds": "^3.0.0",
"@diamondslab/hardhat-diamonds": "^2.0.0",
"@diamondslab/diamonds-hardhat-foundry": "^2.0.0",
"@nomicfoundation/hardhat-ethers": "^3.0.0",
"ethers": "^6.0.0",
"hardhat": "^2.26.0"
}
}Tests Pass Locally But Fail in CI
Cause: CI environment differences (network, timing, dependencies)
Solutions:
- Ensure Foundry is installed in CI:
# GitHub Actions example
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: Run tests
run: npx hardhat diamonds-forge:test- Use ephemeral deployments for CI (default):
# This works in CI without persistent network
npx hardhat diamonds-forge:test- For persistent network testing in CI:
- name: Start Hardhat node
run: npx hardhat node &
- name: Wait for node
run: sleep 5
- name: Run tests
run: npx hardhat diamonds-forge:test --network localhostImport Resolution Errors
Error: File import callback not supported or module resolution failures
Solution: Check your hardhat.config.ts has correct imports:
import "@nomicfoundation/hardhat-ethers";
import "@diamondslab/hardhat-diamonds";
import "@diamondslab/diamonds-hardhat-foundry";
export default {
solidity: "0.8.28",
// ... config
};Ensure TypeScript compilation targets CommonJS:
// tsconfig.json
{
"compilerOptions": {
"module": "CommonJS",
"moduleResolution": "node"
}
}Getting Help
If you encounter issues not covered here:
- Check the TESTING.md guide for detailed testing workflows
- Review MIGRATION.md if upgrading from v1.x
- See DESIGN.md for architecture details
- Open an issue on GitHub
Foundry Not Found
Error: Foundry not installed
Solution: Install Foundry from getfoundry.sh:
curl -L https://foundry.paradigm.xyz | bash
foundryupLocalDiamondDeployer Not Found
Error: LocalDiamondDeployer not found
Solution: Ensure you have a LocalDiamondDeployer class in your project at scripts/setup/LocalDiamondDeployer.ts. This is required for Diamond deployments.
Deployment Record Not Found
Error: No deployment record found
Solution: Deploy your Diamond first:
npx hardhat diamonds-forge:deploy --diamond-name YourDiamondGenerated Helpers Not Compiling
Error: Compilation errors in DiamondDeployment.sol
Solution: Ensure:
- Foundry is properly installed
- Your
foundry.tomlincludes the correct remappings - The Diamond was successfully deployed before generating helpers
Peer Dependency Warnings
Warning: @diamondslab/diamonds not found
Solution: Install required peer dependencies:
npm install --save-dev @diamondslab/diamonds @diamondslab/hardhat-diamondsTest Suite
This module maintains a comprehensive test suite with 100% pass rate across multiple testing categories:
Test Statistics
- Total Tests: 144
- Passing: 141 (98%)
- Skipped: 3 (intentional - deployment-dependent)
- Failed: 0
- Execution Time: ~8-9 seconds
Test Categories
Unit Tests (3 tests)
Basic functionality validation:
- Diamond deployment verification
- Deployer address validation
- Example functionality tests
Integration Tests (14 tests)
Real-world workflow validation:
- Multi-facet interaction workflows
- Cross-facet state management
- Diamond deployment validation
- Facet introspection and enumeration
- On-chain selector verification
- Gas measurement and profiling
Fuzz Tests (93 tests)
Property-based testing with randomized inputs:
- Access Control (19 tests): Role granting, revocation, enumeration
- Ownership (7 tests): Transfer, renounce, unauthorized access
- Routing (11 tests): Selector routing, facet lookup, consistency
- ABI Loader (11 tests): ABI parsing, selector extraction, signature verification
- Example Fuzz (5 tests): Address/amount validation, bounded values
Invariant Tests (24 tests)
State invariants and Diamond integrity:
- Diamond Invariants (13 tests): Facet validity, selector consistency, role hierarchy
- Proxy Invariants (11 tests): ABI matching, facet existence, storage consistency
Running Tests
Run the complete test suite:
npx hardhat diamonds-forge:test --network localhostRun specific test categories:
# Unit tests only
forge test --match-path "test/foundry/unit/**/*.t.sol"
# Fuzz tests only
forge test --match-path "test/foundry/fuzz/**/*.t.sol"
# Invariant tests only
forge test --match-path "test/foundry/invariant/**/*.t.sol"
# Integration tests only
forge test --match-path "test/foundry/integration/**/*.t.sol"Test Patterns
All tests follow best practices:
- Role Setup: Access control tests grant necessary roles in
setUp() - Invariant Targeting: Invariant tests use
targetContract()for fuzzing - Selector Filtering: Tests skip undeployed selectors (facetAddress returns address(0))
- Gas Profiling: Gas measurements included in relevant tests
- Comprehensive Coverage: Edge cases, error conditions, and happy paths
See TESTING.md for detailed testing guide and patterns.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT © DiamondsLab
