Write Solidity unit tests for EigenLayer contracts. Use when the user asks to write tests, add test coverage, create unit tests, or test a function. Follows project conventions with per-function test contracts and mock dependencies.
Write Solidity unit tests for EigenLayer contracts. Use when the user asks to write tests, add test coverage, create unit tests, or test a function. Follows project conventions with per-function test contracts and mock dependencies.
allowed-tools
Read, Glob, Grep, Edit, Write, Bash(forge:*)
Unit Test Writer
Write comprehensive unit tests for EigenLayer Solidity contracts following the project's established conventions.
Test File Structure
Each test file follows this structure:
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;
// Import the contract under test
import "src/contracts/path/to/ContractUnderTest.sol";
// Import the appropriate test setup
import "src/test/utils/EigenLayerUnitTestSetup.sol";
// Import any required mocks
import "src/test/mocks/SomeMock.sol";
/// @title ContractUnderTestUnitTests
/// @notice Base contract for all ContractUnderTest unit tests
contract ContractUnderTestUnitTests is EigenLayerUnitTestSetup, IContractErrors, IContractEvents, IContractTypes {
// Test state variables
ContractUnderTest contractUnderTest;
function setUp() public virtual override {
EigenLayerUnitTestSetup.setUp();
// Deploy and initialize contract under test
// Set up default test values
// Configure mocks
}
// Helper functions
}
/// @title ContractUnderTestUnitTests_functionName
/// @notice Unit tests for ContractUnderTest.functionName
contract ContractUnderTestUnitTests_functionName is ContractUnderTestUnitTests {
function setUp() public override {
super.setUp();
// Function-specific setup
}
// Revert tests
function test_Revert_Paused() public { }
function test_Revert_NotPermissioned() public { }
function test_Revert_InvalidInput() public { }
// Success tests
function test_functionName_Success() public { }
// Fuzz tests
function testFuzz_functionName_VariableName(uint256 value) public { }
}
Permissioned functions: Test InvalidPermissions or NotOwner revert
Input validation: Test each require/revert condition
State checks: Test precondition failures
2. Happy Path (Line Coverage)
Call function with valid inputs
Verify emitted events with cheats.expectEmit(true, true, true, true, address(contract))
Verify state changes with assertions
3. Fuzz Tests
Use bound() to constrain fuzz inputs to valid ranges
Test edge cases and variable inputs
For complex tests or when standard fuzz inputs are too slow, use the Randomness type from src/test/utils/Random.sol
Using Mocks
External contract calls should use mocks from src/test/mocks/:
// In setUp()
allocationManagerMock.setIsOperatorSet(operatorSet, true);
// Mock pattern: Mocks expose setters to control return values
mock.setSomeValue(expectedValue);
// Then the contract under test calls mock.getSomeValue() and gets expectedValue
Creating New Mock Contracts
If a mock doesn't exist in src/test/mocks/, create one following this pattern:
Location: src/test/mocks/{ContractName}Mock.sol
Structure:
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;
import "forge-std/Test.sol";
import "src/contracts/interfaces/IContractName.sol";
contract ContractNameMock is Test {
receive() external payable {}
fallback() external payable {}
// Storage for mock return values
mapping(bytes32 => bool) public _someMapping;
// Setter to configure mock behavior
function setSomeValue(bytes32 key, bool value) external {
_someMapping[key] = value;
}
// Interface method that returns configured value
function someValue(bytes32 key) external view returns (bool) {
return _someMapping[key];
}
}
Key principles:
Inherit from Test - Gives access to cheatcodes if needed
Include receive() and fallback() - Allows the mock to receive ETH and handle unknown calls gracefully
Only implement what's needed - Add functions on a need-to-implement basis as tests require them
Prefix storage with _ - Use _variableName for internal mock storage to distinguish from interface getters
Create setters for each value - Pattern: setX() to configure, getX() or x() to return the configured value
Test Setup Inheritance
Choose the appropriate base setup:
EigenLayerUnitTestSetup - Standard core contract tests
// Expect event emission BEFORE the call
cheats.expectEmit(true, true, true, true, address(contractUnderTest));
emit SomeEvent(param1, param2);
// Make the call
contractUnderTest.someFunction(param1, param2);
State Verification
// After the call, verify state
assertEq(contract.getValue(), expectedValue, "Value mismatch");
assertTrue(contract.isEnabled(), "Should be enabled");
assertFalse(contract.isDisabled(), "Should not be disabled");
Fuzz Test Patterns
Standard Fuzz Tests (using bound())
function testFuzz_functionName_Amount(uint256 amount) public {
// Bound to valid range
amount = bound(amount, 1, type(uint128).max);
// Or for uint8
uint8 smallValue = uint8(bound(value, 1, 100));
// Test with bounded value
contractUnderTest.functionName(amount);
// Verify
assertEq(contractUnderTest.getAmount(), amount, "Amount mismatch");
}
Randomness generation for Fuzz Tests
For tests that need multiple random values or complex random data structures, use the Randomness type from src/test/utils/Random.sol. This is preferred when:
You need multiple correlated random values
Standard fuzz inputs reject too many cases
You need random arrays or complex types (addresses, bytes32, OperatorSets, etc.)
Setup: The base test contract must have the rand modifier and random() helper (already in EigenLayerUnitTestSetup):
Read the contract under test to understand all functions
Identify all external dependencies (need mocks)
Identify all revert conditions (modifiers, requires)
Identify all events emitted
Identify all state changes
Check if similar tests exist
Running Tests
# Run all unit tests
forge test --no-match-contract Integration
# Run specific test file
forge test --match-path src/test/unit/ContractUnit.t.sol
# Run specific test
forge test --match-test test_functionName_Success
# Run with verbosity
forge test --match-path src/test/unit/ContractUnit.t.sol -vvv
# Check coverage
forge coverage --match-path src/test/unit/ContractUnit.t.sol