mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 01:38:32 +00:00
## Summary - sync `contracts/lib/eigenlayer-contracts` to tag `v1.8.0-testnet-final` and refresh `EIGENLAYER.md` with the new commit reference - update local/test deployment flows to deploy the upstream `EigenStrategy`, feed it into `AllocationManager`/`StrategyManager`, and adopt the revised `EigenPod` constructor - drop the obsolete `AllocationManagerMock` stub and replace its usage with targeted `vm.mockCall` stubs that return `slashOperator` share data - adjust slasher unit tests to match the new ABI so DataHaven stays aligned with EigenLayer 1.8 semantics ## Testing - forge build - forge test
399 lines
15 KiB
Solidity
399 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);
|
|
|
|
uint256[] memory slashedShares = new uint256[](params.strategies.length);
|
|
vm.mockCall(
|
|
address(allocationManager),
|
|
abi.encodeWithSelector(
|
|
IAllocationManager.slashOperator.selector, serviceManager.avs(), params
|
|
),
|
|
abi.encode(uint256(0), slashedShares)
|
|
);
|
|
|
|
// 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
|
|
uint256[] memory slashedShares = new uint256[](params2.strategies.length);
|
|
vm.mockCall(
|
|
address(allocationManager),
|
|
abi.encodeWithSelector(
|
|
IAllocationManager.slashOperator.selector, serviceManager.avs(), params2
|
|
),
|
|
abi.encode(uint256(0), slashedShares)
|
|
);
|
|
|
|
// 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);
|
|
}
|
|
}
|