datahaven/contracts/test/VetoableSlasher.t.sol
Steve Degosserie 387c056912
fix: Resolve Foundry build errors and apply code formatting (#241)
## Summary

Fixes the CI build failure in the `task-ts-build` workflow caused by
Foundry v1.4.2's Solar linter not being able to resolve Snowbridge's
context-specific import remappings.

## Problem

The Snowbridge submodule uses context-specific remappings (prefixed with
`:`) for its dependencies:
- `lib/snowbridge/contracts/:openzeppelin/` → OpenZeppelin contracts
- `lib/snowbridge/contracts/:prb/math/` → PRB Math library

Foundry v1.4.2's Solar linter doesn't understand these context-specific
remappings and fails with errors like:
```
error: file openzeppelin/utils/cryptography/MerkleProof.sol not found
error: file prb/math/src/UD60x18.sol not found
```

## Solution

Added global remappings that the linter can understand:
```toml
"openzeppelin/=lib/snowbridge/contracts/lib/openzeppelin-contracts/contracts/",
"prb/math/=lib/snowbridge/contracts/lib/prb-math/",
```

### Why This Works
- The linter can now resolve `openzeppelin/` and `prb/math/` imports
globally
- These global remappings take **lower precedence** than
context-specific ones during compilation
- The compiler still uses the context-specific remappings (with `:`)
when compiling Snowbridge contracts
- The linter uses the global remappings when checking all files

## Changes

### Commit 1: Add global remappings
- `contracts/foundry.toml`: Added 2 global remapping entries

### Commit 2: Apply forge fmt
- Applied automatic formatting via `forge fmt` to ensure code style
consistency
- Multi-line formatting for long import statements and function
signatures
- No functional changes - purely formatting updates

## Testing

 Local build succeeds with `forge build`
 No Snowbridge import resolution errors
 `forge fmt --check` passes with no formatting issues
 Only linting notes/warnings remain (not errors)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-10-20 11:20:59 +03:00

397 lines
15 KiB
Solidity

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;
/* solhint-disable func-name-mixedcase */
import {Test, console} from "forge-std/Test.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol";
import {
IRewardsCoordinator
} from "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol";
import {
IAllocationManagerErrors,
IAllocationManager,
IAllocationManagerTypes
} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
import {AVSDeployer} from "./utils/AVSDeployer.sol";
import {IServiceManager} from "../src/interfaces/IServiceManager.sol";
import {ISlasher, ISlasherErrors, ISlasherEvents} from "../src/interfaces/ISlasher.sol";
import {
IVetoableSlasher,
IVetoableSlasherTypes,
IVetoableSlasherErrors,
IVetoableSlasherEvents
} from "../src/interfaces/IVetoableSlasher.sol";
import {SlasherBase} from "../src/middleware/SlasherBase.sol";
import {VetoableSlasher} from "../src/middleware/VetoableSlasher.sol";
contract VetoableSlasherTest is AVSDeployer {
address public nonServiceManagerRole;
address public nonVetoCommittee;
// Events for testing
event SlashingRequested(
uint256 indexed requestId,
address indexed operator,
uint32 indexed operatorSetId,
uint256[] wadsToSlash,
string description
);
event SlashingRequestCancelled(
address indexed operator, uint32 operatorSetId, uint256[] wadsToSlash, string description
);
event SlashingRequestFulfilled(
address indexed operator, uint32 operatorSetId, uint256[] wadsToSlash, string description
);
function setUp() public virtual {
_deployMockEigenLayerAndAVS();
// Set up roles for testing
nonServiceManagerRole = address(0x5678);
nonVetoCommittee = address(0xdcba);
}
// Test constructor initializes state correctly
function test_constructor() public view {
assertEq(
address(vetoableSlasher.allocationManager()),
address(allocationManager),
"AllocationManager address mismatch"
);
assertEq(
address(vetoableSlasher.serviceManager()),
address(serviceManager),
"ServiceManager address mismatch"
);
assertEq(
vetoableSlasher.vetoCommittee(), vetoCommitteeMember, "Veto committee address mismatch"
);
assertEq(
vetoableSlasher.vetoWindowBlocks(), vetoWindowBlocks, "Veto window blocks mismatch"
);
assertEq(vetoableSlasher.nextRequestId(), 0, "NextRequestId should be initialized to 0");
}
// Test queueSlashingRequest reverts when called by non-ServiceManager
function test_queueSlashingRequest_nonServiceManager() public {
IAllocationManagerTypes.SlashingParams memory params;
vm.prank(nonServiceManagerRole);
vm.expectRevert(abi.encodeWithSelector(ISlasherErrors.OnlySlasher.selector));
vetoableSlasher.queueSlashingRequest(params);
}
// Test queueSlashingRequest succeeds when called by ServiceManager
function test_queueSlashingRequest_serviceManager() public {
// Setup mock params
address operator = address(0x1111);
uint32 operatorSetId = 1;
IStrategy[] memory strategies = new IStrategy[](1);
strategies[0] = deployedStrategies[0];
uint256[] memory wadsToSlash = new uint256[](1);
wadsToSlash[0] = 1e16; // 1% of the operator's stake
string memory description = "Test slashing";
IAllocationManagerTypes.SlashingParams memory params = IAllocationManagerTypes.SlashingParams({
operator: operator,
operatorSetId: operatorSetId,
strategies: strategies,
wadsToSlash: wadsToSlash,
description: description
});
uint256 requestId = 0; // First request
vm.prank(address(serviceManager));
vm.expectEmit(true, true, true, true);
emit IVetoableSlasherEvents.SlashingRequested(
requestId, operator, operatorSetId, wadsToSlash, description
);
vetoableSlasher.queueSlashingRequest(params);
// Verify request is stored correctly
(
IAllocationManagerTypes.SlashingParams memory storedParams,
uint256 requestBlock,
bool isPending
) = _getSlashingRequest(requestId);
assertEq(storedParams.operator, operator, "Operator mismatch");
assertEq(storedParams.operatorSetId, operatorSetId, "OperatorSetId mismatch");
assertEq(storedParams.wadsToSlash[0], wadsToSlash[0], "WadsToSlash mismatch");
assertEq(storedParams.description, description, "Description mismatch");
assertEq(requestBlock, block.number, "Request block mismatch");
assertEq(isPending, true, "Status mismatch");
// Verify nextRequestId is incremented
assertEq(vetoableSlasher.nextRequestId(), 1, "NextRequestId should be incremented");
}
// Test cancelSlashingRequest reverts when called by non-veto committee
function test_cancelSlashingRequest_nonVetoCommittee() public {
// First create a request
_createSlashingRequest();
vm.prank(nonVetoCommittee);
vm.expectRevert(abi.encodeWithSelector(IVetoableSlasherErrors.OnlyVetoCommittee.selector));
vetoableSlasher.cancelSlashingRequest(0);
}
// Test cancelSlashingRequest succeeds when called by veto committee within veto period
function test_cancelSlashingRequest_withinVetoPeriod() public {
// First create a request
uint256 requestId = _createSlashingRequest();
(IAllocationManagerTypes.SlashingParams memory params,,) = _getSlashingRequest(requestId);
vm.prank(vetoCommitteeMember);
vm.expectEmit(true, false, false, false);
emit IVetoableSlasherEvents.SlashingRequestCancelled(
params.operator, params.operatorSetId, params.wadsToSlash, params.description
);
vetoableSlasher.cancelSlashingRequest(requestId);
// Verify request status is updated
(,, bool isPending) = _getSlashingRequest(requestId);
assertEq(isPending, false, "Status should be Cancelled");
}
// Test cancelSlashingRequest reverts when veto period has passed
function test_cancelSlashingRequest_afterVetoPeriod() public {
// First create a request
uint256 requestId = _createSlashingRequest();
// Fast forward past veto period
vm.roll(block.number + vetoWindowBlocks + 1);
vm.prank(vetoCommitteeMember);
vm.expectRevert(abi.encodeWithSelector(IVetoableSlasherErrors.VetoPeriodPassed.selector));
vetoableSlasher.cancelSlashingRequest(requestId);
}
// Test cancelSlashingRequest reverts when request is not in Requested state
function test_cancelSlashingRequest_notRequested() public {
// First create a request
uint256 requestId = _createSlashingRequest();
// Cancel it once
vm.prank(vetoCommitteeMember);
vetoableSlasher.cancelSlashingRequest(requestId);
// Try to cancel it again
vm.prank(vetoCommitteeMember);
vm.expectRevert(
abi.encodeWithSelector(IVetoableSlasherErrors.SlashingRequestNotRequested.selector)
);
vetoableSlasher.cancelSlashingRequest(requestId);
}
// Test fulfilSlashingRequest reverts before veto period has passed
function test_fulfilSlashingRequest_beforeVetoPeriod() public {
// First create a request
uint256 requestId = _createSlashingRequest();
vm.prank(address(serviceManager));
vm.expectRevert(abi.encodeWithSelector(IVetoableSlasherErrors.VetoPeriodNotPassed.selector));
vetoableSlasher.fulfilSlashingRequest(requestId);
}
// Test fulfilSlashingRequest reverts when request is cancelled
function test_fulfilSlashingRequest_cancelled() public {
// First create a request
uint256 requestId = _createSlashingRequest();
// Cancel it
vm.prank(vetoCommitteeMember);
vetoableSlasher.cancelSlashingRequest(requestId);
// Fast forward past veto period
vm.roll(block.number + vetoWindowBlocks + 1);
vm.prank(address(serviceManager));
vm.expectRevert(
abi.encodeWithSelector(IVetoableSlasherErrors.SlashingRequestIsCancelled.selector)
);
vetoableSlasher.fulfilSlashingRequest(requestId);
}
// Test fulfilSlashingRequest succeeds after veto period has passed
function test_fulfilSlashingRequest_afterVetoPeriod() public {
// First create a request
uint256 requestId = _createSlashingRequest();
address operator = address(0x1111);
uint32 operatorSetId = 1;
// Setup the mock for slashing
IAllocationManagerTypes.SlashingParams memory params;
(params,,) = _getSlashingRequest(requestId);
vm.mockCall(
address(allocationManager),
abi.encodeWithSelector(
IAllocationManager.slashOperator.selector, serviceManager.avs(), params
),
abi.encode()
);
// Fast forward past veto period
vm.roll(block.number + vetoWindowBlocks + 1);
vm.prank(address(serviceManager));
vm.expectEmit(true, true, true, true);
emit ISlasherEvents.OperatorSlashed(
requestId, operator, operatorSetId, params.wadsToSlash, params.description
);
vm.expectEmit(true, true, true, true);
emit SlashingRequestFulfilled(
operator, operatorSetId, params.wadsToSlash, params.description
);
vetoableSlasher.fulfilSlashingRequest(requestId);
// Verify request is deleted from storage
(
IAllocationManagerTypes.SlashingParams memory emptyParams,
uint256 requestBlock,
bool isPending
) = _getSlashingRequest(requestId);
assertEq(
emptyParams.operator, address(0), "Request should be deleted - operator not zeroed"
);
assertEq(requestBlock, 0, "Request should be deleted - requestBlock not zeroed");
assertEq(isPending, false, "Request should be deleted - isPending not false");
}
// Test cancelSlashingRequest properly deletes the request from storage
function test_cancelSlashingRequest_deletesFromStorage() public {
// First create a request
uint256 requestId = _createSlashingRequest();
(IAllocationManagerTypes.SlashingParams memory params,,) = _getSlashingRequest(requestId);
vm.prank(vetoCommitteeMember);
vm.expectEmit(true, true, true, true);
emit SlashingRequestCancelled(
params.operator, params.operatorSetId, params.wadsToSlash, params.description
);
vetoableSlasher.cancelSlashingRequest(requestId);
// Verify request is deleted from storage
(
IAllocationManagerTypes.SlashingParams memory emptyParams,
uint256 requestBlock,
bool isPending
) = _getSlashingRequest(requestId);
assertEq(
emptyParams.operator, address(0), "Request should be deleted - operator not zeroed"
);
assertEq(requestBlock, 0, "Request should be deleted - requestBlock not zeroed");
assertEq(isPending, false, "Request should be deleted - isPending not false");
}
// Test multiple requests flow
function test_multipleRequests() public {
// Create first request
uint256 requestId1 = _createSlashingRequest();
// Create second request with different parameters
address operator2 = address(0x2222);
uint32 operatorSetId2 = 2;
IStrategy[] memory strategies2 = new IStrategy[](1);
strategies2[0] = deployedStrategies[1];
uint256[] memory wadsToSlash2 = new uint256[](1);
wadsToSlash2[0] = 2e16; // 2% of the operator's stake
string memory description2 = "Second slashing";
IAllocationManagerTypes.SlashingParams memory params2 =
IAllocationManagerTypes.SlashingParams({
operator: operator2,
operatorSetId: operatorSetId2,
strategies: strategies2,
wadsToSlash: wadsToSlash2,
description: description2
});
uint256 requestId2 = 1; // Second request
vm.prank(address(serviceManager));
vetoableSlasher.queueSlashingRequest(params2);
// Cancel the first request
vm.prank(vetoCommitteeMember);
vetoableSlasher.cancelSlashingRequest(requestId1);
// Setup the mock for slashing the second request
vm.mockCall(
address(allocationManager),
abi.encodeWithSelector(
IAllocationManager.slashOperator.selector, serviceManager.avs(), params2
),
abi.encode()
);
// Fast forward past veto period
vm.roll(block.number + vetoWindowBlocks + 1);
// Try to fulfil the first (cancelled) request - should revert
vm.prank(address(serviceManager));
vm.expectRevert(
abi.encodeWithSelector(IVetoableSlasherErrors.SlashingRequestIsCancelled.selector)
);
vetoableSlasher.fulfilSlashingRequest(requestId1);
// fulfil the second request - should succeed
vm.prank(address(serviceManager));
vetoableSlasher.fulfilSlashingRequest(requestId2);
// Verify states
(,, bool isPending1) = _getSlashingRequest(requestId1);
(,, bool isPending2) = _getSlashingRequest(requestId2);
assertEq(isPending1, false, "Request 1 status should be Cancelled");
assertEq(isPending2, false, "Request 2 status should be Completed");
}
// Helper function to create a standard slashing request
function _createSlashingRequest() internal returns (uint256) {
address operator = address(0x1111);
uint32 operatorSetId = 1;
IStrategy[] memory strategies = new IStrategy[](1);
strategies[0] = deployedStrategies[0];
uint256[] memory wadsToSlash = new uint256[](1);
wadsToSlash[0] = 1e16; // 1% of the operator's stake
string memory description = "Test slashing";
IAllocationManagerTypes.SlashingParams memory params = IAllocationManagerTypes.SlashingParams({
operator: operator,
operatorSetId: operatorSetId,
strategies: strategies,
wadsToSlash: wadsToSlash,
description: description
});
uint256 requestId = vetoableSlasher.nextRequestId();
vm.prank(address(serviceManager));
vetoableSlasher.queueSlashingRequest(params);
return requestId;
}
// Helper function to extract SlashingRequest from storage
function _getSlashingRequest(
uint256 requestId
)
internal
view
returns (
IAllocationManagerTypes.SlashingParams memory params,
uint256 requestBlock,
bool isPending
)
{
(params, requestBlock, isPending) = vetoableSlasher.slashingRequests(requestId);
}
}