
unit-test-writer
by Layr-Labs
Contracts of EigenLayer
SKILL.md
name: unit-test-writer description: 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 { }
}
Test Contract Naming Convention
- Base contract:
{ContractName}UnitTests - Per-function contracts:
{ContractName}UnitTests_{functionName}
Test Function Naming Convention
| Pattern | Purpose |
|---|---|
test_Revert_Paused | Test function reverts when paused |
test_Revert_NotPermissioned | Test function reverts for unauthorized callers |
test_Revert_NotOwner | Test function reverts for non-owner |
test_Revert_Invalid{Thing} | Test function reverts for invalid inputs |
test_Revert_{ErrorName} | Test function reverts with specific error |
test_{functionName}_Success | Test successful execution (happy path) |
test_{functionName}_{Scenario} | Test specific scenario |
testFuzz_{functionName}_{Scenario} | Fuzz test with bounded variable |
Coverage Requirements
For each function, ensure:
1. Revert Cases (Branch Coverage)
- Pausable functions: Test
CurrentlyPausedrevert - Permissioned functions: Test
InvalidPermissionsorNotOwnerrevert - 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
Randomnesstype fromsrc/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()andfallback()- 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_variableNamefor internal mock storage to distinguish from interface getters - Create setters for each value - Pattern:
setX()to configure,getX()orx()to return the configured value
Test Setup Inheritance
Choose the appropriate base setup:
EigenLayerUnitTestSetup- Standard core contract testsEigenLayerMultichainUnitTestSetup- Multichain/cross-chain tests
Event Verification
// 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):
modifier rand(Randomness r) {
r.set();
_;
}
function random() internal returns (Randomness) {
return Randomness.wrap(Random.SEED).shuffle();
}
Usage Pattern:
function testFuzz_functionName_ComplexScenario(Randomness r) public rand(r) {
// Generate random values using r.Type() or r.Type(min, max)
address staker = r.Address();
bytes32 salt = r.Bytes32();
uint256 shares = r.Uint256(1, MAX_SHARES);
uint64 magnitude = r.Uint64(1, WAD);
uint32 count = r.Uint32(1, 32);
bool flag = r.Boolean();
// Use random values in test
contractUnderTest.someFunction(staker, shares);
// Verify behavior
assertEq(contractUnderTest.getShares(staker), shares);
}
Available Random Methods:
| Method | Description |
|---|---|
r.Uint256() | Random uint256 |
r.Uint256(min, max) | Random uint256 in range [min, max) |
r.Uint128(), r.Uint64(), r.Uint32() | Smaller uint types |
r.Int256(), r.Int128(), etc. | Signed integers |
r.Address() | Random non-zero address |
r.Bytes32() | Random bytes32 |
r.Boolean() | Random true/false |
r.StrategyArray(len) | Array of random strategy addresses |
r.StakerArray(len) | Array of random staker addresses |
r.Uint256Array(len, min, max) | Array of random uint256 values |
Helper Functions for Complex Random Data:
When you need multiple correlated random values (e.g., deposit/withdrawal amounts), create helper functions:
/// @notice Generate correlated random amounts for deposits and withdrawals
function _fuzzDepositWithdrawalAmounts(Randomness r, uint32 numStrategies)
internal
returns (uint[] memory depositAmounts, uint[] memory withdrawalAmounts)
{
depositAmounts = new uint[](numStrategies);
withdrawalAmounts = new uint[](numStrategies);
for (uint i = 0; i < numStrategies; i++) {
depositAmounts[i] = r.Uint256(1, MAX_STRATEGY_SHARES);
// Withdrawal must be <= deposit
withdrawalAmounts[i] = r.Uint256(1, depositAmounts[i]);
}
}
// Usage in test:
function testFuzz_queueWithdrawals(Randomness r) public rand(r) {
uint32 numStrategies = r.Uint32(1, 5);
(uint[] memory deposits, uint[] memory withdrawals) =
_fuzzDepositWithdrawalAmounts(r, numStrategies);
// ... rest of test
}
Example: Complete Test Contract
Reference: src/test/unit/CrossChainRegistryUnit.t.sol
This file demonstrates:
- Base test contract with setUp and helpers
- Per-function test contracts
- Comprehensive revert testing
- Event verification
- State verification
- Fuzz testing
Checklist Before Writing Tests
- 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
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 500以上
3ヶ月以内に更新
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon