mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 09:50:01 +00:00
feat: ✨ initial slasher implementation (#10)
This PR: - Sets up the slasher infrastructure with the base functionality required (in `ISlasher.sol`, `SlasherBase.sol` and `SlasherBaseStorage.sol`) and adds the tests for it (in `SlasherBase.t.sol`). - Adds an implementation of a more complex slasher (in `IVetoableSlasher.sol` and `VetoableSlasher.sol`) and tests for it (in `VetoableSlasher.t.sol`). - Modifies the `ServiceManagerBase` contract to use the new `VetoableSlasher` contract to manage slashing. - Updates mocks and tests to reflect the newly added functionality. --------- Co-authored-by: Facundo Farall <37149322+ffarall@users.noreply.github.com>
This commit is contained in:
parent
e412500e61
commit
d8d792874c
14 changed files with 1099 additions and 7 deletions
|
|
@ -106,4 +106,10 @@ interface IServiceManager is IServiceManagerUI, IServiceManagerErrors, IServiceM
|
|||
address operator,
|
||||
uint32[] memory operatorSetIds
|
||||
) external;
|
||||
|
||||
/**
|
||||
* @notice Returns the address of the AVS
|
||||
* @return The address of the AVS
|
||||
*/
|
||||
function avs() external view returns (address);
|
||||
}
|
||||
|
|
|
|||
39
contracts/src/interfaces/ISlasher.sol
Normal file
39
contracts/src/interfaces/ISlasher.sol
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// SPDX-License-Identifier: BUSL-1.1
|
||||
pragma solidity ^0.8.27;
|
||||
|
||||
import {IAllocationManager} from
|
||||
"eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
|
||||
|
||||
interface ISlasherErrors {
|
||||
/// @notice Thrown when a caller without slasher privileges attempts a restricted operation
|
||||
error OnlySlasher();
|
||||
}
|
||||
|
||||
interface ISlasherTypes {
|
||||
/// @notice Structure containing details about a slashing request
|
||||
struct SlashingRequest {
|
||||
IAllocationManager.SlashingParams params;
|
||||
uint256 requestTimestamp;
|
||||
}
|
||||
}
|
||||
|
||||
interface ISlasherEvents is ISlasherTypes {
|
||||
/// @notice Emitted when an operator is successfully slashed
|
||||
event OperatorSlashed(
|
||||
uint256 indexed slashingRequestId,
|
||||
address indexed operator,
|
||||
uint32 indexed operatorSetId,
|
||||
uint256[] wadsToSlash,
|
||||
string description
|
||||
);
|
||||
}
|
||||
|
||||
/// @title ISlasher
|
||||
/// @notice Base interface containing shared functionality for all slasher implementations
|
||||
interface ISlasher is ISlasherErrors, ISlasherEvents {
|
||||
/// @notice Returns the address authorized to create and fulfil slashing requests
|
||||
function slasher() external view returns (address);
|
||||
|
||||
/// @notice Returns the next slashing request ID
|
||||
function nextRequestId() external view returns (uint256);
|
||||
}
|
||||
86
contracts/src/interfaces/IVetoableSlasher.sol
Normal file
86
contracts/src/interfaces/IVetoableSlasher.sol
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
// SPDX-License-Identifier: BUSL-1.1
|
||||
pragma solidity ^0.8.27;
|
||||
|
||||
import {IAllocationManager} from
|
||||
"eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
|
||||
import {ISlasher} from "./ISlasher.sol";
|
||||
|
||||
interface IVetoableSlasherErrors {
|
||||
/// @notice Thrown when a caller without veto committee privileges attempts a restricted operation
|
||||
error OnlyVetoCommittee();
|
||||
/// @notice Thrown when attempting to veto a slashing request after the veto period has expired
|
||||
error VetoPeriodPassed();
|
||||
/// @notice Thrown when attempting to execute a slashing request before the veto period has ended
|
||||
error VetoPeriodNotPassed();
|
||||
/// @notice Thrown when attempting to interact with a slashing request that has been cancelled
|
||||
error SlashingRequestIsCancelled();
|
||||
/// @notice Thrown when attempting to modify a slashing request that does not exist
|
||||
error SlashingRequestNotRequested();
|
||||
}
|
||||
|
||||
interface IVetoableSlasherTypes {
|
||||
/// @notice Structure containing details about a vetoable slashing request
|
||||
struct VetoableSlashingRequest {
|
||||
IAllocationManager.SlashingParams params;
|
||||
uint256 requestBlock;
|
||||
bool isPending;
|
||||
}
|
||||
}
|
||||
|
||||
interface IVetoableSlasherEvents {
|
||||
/// @notice Emitted when a new slashing request is created
|
||||
event SlashingRequested(
|
||||
uint256 indexed requestId,
|
||||
address indexed operator,
|
||||
uint32 operatorSetId,
|
||||
uint256[] wadsToSlash,
|
||||
string description
|
||||
);
|
||||
|
||||
/// @notice Emitted when a slashing request is cancelled by the veto committee
|
||||
event SlashingRequestCancelled(
|
||||
address indexed operator, uint32 operatorSetId, uint256[] wadsToSlash, string description
|
||||
);
|
||||
|
||||
/// @notice Emitted when a slashing request is fulfilled
|
||||
event SlashingRequestFulfilled(
|
||||
address indexed operator, uint32 operatorSetId, uint256[] wadsToSlash, string description
|
||||
);
|
||||
}
|
||||
|
||||
/// @title IVetoableSlasher
|
||||
/// @notice A slashing contract that implements a veto mechanism allowing a designated committee to cancel slashing requests
|
||||
/// @dev Extends base interfaces and adds a veto period during which slashing requests can be cancelled
|
||||
interface IVetoableSlasher is
|
||||
ISlasher,
|
||||
IVetoableSlasherErrors,
|
||||
IVetoableSlasherTypes,
|
||||
IVetoableSlasherEvents
|
||||
{
|
||||
/// @notice Duration of the veto period during which the veto committee can cancel slashing requests
|
||||
function vetoWindowBlocks() external view returns (uint32);
|
||||
|
||||
/// @notice Address of the committee that has veto power over slashing requests
|
||||
function vetoCommittee() external view returns (address);
|
||||
|
||||
/// @notice Queues a new slashing request
|
||||
/// @param params Parameters defining the slashing request including operator and amount
|
||||
/// @dev Can only be called by the authorized slasher
|
||||
function queueSlashingRequest(
|
||||
IAllocationManager.SlashingParams calldata params
|
||||
) external;
|
||||
|
||||
/// @notice Cancels a pending slashing request
|
||||
/// @param requestId The ID of the slashing request to cancel
|
||||
/// @dev Can only be called by the veto committee during the veto period
|
||||
function cancelSlashingRequest(
|
||||
uint256 requestId
|
||||
) external;
|
||||
|
||||
/// @notice Executes a slashing request after the veto period has passed
|
||||
/// @param requestId The ID of the slashing request to fulfil
|
||||
/// @dev Can only be called by the authorized slasher after the veto period
|
||||
function fulfilSlashingRequest(
|
||||
uint256 requestId
|
||||
) external;
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ import {IPermissionController} from
|
|||
"eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol";
|
||||
|
||||
import {IServiceManager, IServiceManagerUI} from "../interfaces/IServiceManager.sol";
|
||||
|
||||
import {IVetoableSlasher} from "../interfaces/IVetoableSlasher.sol";
|
||||
import {ServiceManagerBaseStorage} from "./ServiceManagerBaseStorage.sol";
|
||||
|
||||
/**
|
||||
|
|
@ -56,6 +56,17 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage, IAVSRegistrar
|
|||
_setRewardsInitiator(_rewardsInitiator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sets the slasher contract
|
||||
* @param slasher The slasher contract address
|
||||
* @dev Only callable by the owner
|
||||
*/
|
||||
function setSlasher(
|
||||
IVetoableSlasher slasher
|
||||
) external virtual onlyOwner {
|
||||
_slasher = slasher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Updates the metadata URI for the AVS
|
||||
* @param _metadataURI is the metadata URI for the AVS
|
||||
|
|
@ -97,12 +108,27 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage, IAVSRegistrar
|
|||
}
|
||||
|
||||
/**
|
||||
* Forwards the call to the AllocationManager.slashOperator() function
|
||||
* Queue a slashing request in the vetoable slasher
|
||||
* @param params Parameters defining the slashing request
|
||||
* @dev Can only be called by the owner
|
||||
*/
|
||||
function slashOperator(
|
||||
function queueSlashingRequest(
|
||||
IAllocationManager.SlashingParams calldata params
|
||||
) external virtual onlyOwner {
|
||||
_allocationManager.slashOperator(address(this), params);
|
||||
require(address(_slasher) != address(0), "Slasher not set");
|
||||
_slasher.queueSlashingRequest(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* fulfils a slashing request that has passed the veto period
|
||||
* @param requestId The ID of the slashing request to fulfil
|
||||
* @dev Can be called by anyone
|
||||
*/
|
||||
function fulfilSlashingRequest(
|
||||
uint256 requestId
|
||||
) external virtual {
|
||||
require(address(_slasher) != address(0), "Slasher not set");
|
||||
_slasher.fulfilSlashingRequest(requestId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -161,9 +187,9 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage, IAVSRegistrar
|
|||
|
||||
/// @inheritdoc IAVSRegistrar
|
||||
function supportsAVS(
|
||||
address avs
|
||||
address avsAddress
|
||||
) external view virtual override returns (bool) {
|
||||
return avs == address(this);
|
||||
return avsAddress == this.avs();
|
||||
}
|
||||
|
||||
/// @inheritdoc IAVSRegistrar
|
||||
|
|
@ -232,6 +258,11 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage, IAVSRegistrar
|
|||
});
|
||||
}
|
||||
|
||||
/// @inheritdoc IServiceManager
|
||||
function avs() external view virtual returns (address) {
|
||||
return address(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Forwards a call to Eigenlayer's RewardsCoordinator contract to set the address of the entity that can call `processClaim` on behalf of this contract.
|
||||
* @param claimer The address of the entity that can call `processClaim` on behalf of the earner
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {IAllocationManager} from
|
|||
import {IPermissionController} from
|
||||
"eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol";
|
||||
|
||||
import {IVetoableSlasher} from "../interfaces/IVetoableSlasher.sol";
|
||||
import {IServiceManager} from "../interfaces/IServiceManager.sol";
|
||||
|
||||
/**
|
||||
|
|
@ -35,10 +36,13 @@ abstract contract ServiceManagerBaseStorage is IServiceManager, OwnableUpgradeab
|
|||
*
|
||||
*/
|
||||
|
||||
/// @notice The slasher contract that handles operator slashing
|
||||
IVetoableSlasher internal _slasher;
|
||||
|
||||
/// @notice The address of the entity that can initiate rewards
|
||||
address public rewardsInitiator;
|
||||
|
||||
/// @notice Sets the (immutable) `_avsDirectory`, `_rewardsCoordinator`, `_registryCoordinator`, `_stakeRegistry`, and `_allocationManager` addresses
|
||||
/// @notice Sets the (immutable) rewardsCoordinator`, `_permissionController`, and `_allocationManager` addresses
|
||||
constructor(
|
||||
IRewardsCoordinator __rewardsCoordinator,
|
||||
IPermissionController __permissionController,
|
||||
|
|
|
|||
61
contracts/src/middleware/SlasherBase.sol
Normal file
61
contracts/src/middleware/SlasherBase.sol
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// SPDX-License-Identifier: BUSL-1.1
|
||||
pragma solidity ^0.8.27;
|
||||
|
||||
import {SlasherStorage, IServiceManager} from "./SlasherBaseStorage.sol";
|
||||
import {
|
||||
IAllocationManagerTypes,
|
||||
IAllocationManager
|
||||
} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
|
||||
import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol";
|
||||
|
||||
/// @title SlasherBase
|
||||
/// @notice Base contract for implementing slashing functionality in an EigenLayer AVS
|
||||
/// @dev Provides core slashing functionality and interfaces with EigenLayer's AllocationManager
|
||||
abstract contract SlasherBase is SlasherStorage {
|
||||
/// @notice Ensures only the authorized slasher can call certain functions
|
||||
modifier onlySlasher() {
|
||||
_checkSlasher(msg.sender);
|
||||
_;
|
||||
}
|
||||
|
||||
/// @notice Constructs the base slasher contract
|
||||
/// @param _allocationManager The EigenLayer allocation manager contract
|
||||
/// @param _serviceManager The service manager that will manage this slasher
|
||||
constructor(
|
||||
IAllocationManager _allocationManager,
|
||||
IServiceManager _serviceManager
|
||||
) SlasherStorage(_allocationManager, _serviceManager) {}
|
||||
|
||||
/// @notice Internal function to execute a slashing request
|
||||
/// @param _requestId The ID of the slashing request to fulfil
|
||||
/// @param _params Parameters defining the slashing request including operator, strategies, and amounts
|
||||
/// @dev Calls AllocationManager.slashOperator to perform the actual slashing
|
||||
function _fulfilSlashingRequest(
|
||||
uint256 _requestId,
|
||||
IAllocationManager.SlashingParams memory _params
|
||||
) internal virtual {
|
||||
allocationManager.slashOperator({avs: serviceManager.avs(), params: _params});
|
||||
emit OperatorSlashed(
|
||||
_requestId,
|
||||
_params.operator,
|
||||
_params.operatorSetId,
|
||||
_params.wadsToSlash,
|
||||
_params.description
|
||||
);
|
||||
}
|
||||
|
||||
/// @notice Internal function to verify if an account is the authorized slasher
|
||||
/// @param account The address to check
|
||||
/// @dev Reverts if the account is not the ServiceManager
|
||||
function _checkSlasher(
|
||||
address account
|
||||
) internal view virtual {
|
||||
require(account == address(serviceManager), OnlySlasher());
|
||||
}
|
||||
|
||||
/// @notice Returns the address of the ServiceManager
|
||||
/// @return The address of the ServiceManager
|
||||
function slasher() external view returns (address) {
|
||||
return address(serviceManager);
|
||||
}
|
||||
}
|
||||
32
contracts/src/middleware/SlasherBaseStorage.sol
Normal file
32
contracts/src/middleware/SlasherBaseStorage.sol
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// SPDX-License-Identifier: BUSL-1.1
|
||||
pragma solidity ^0.8.27;
|
||||
|
||||
import {IAllocationManager} from
|
||||
"eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
|
||||
import {ISlasher} from "../interfaces/ISlasher.sol";
|
||||
import {IServiceManager} from "../interfaces/IServiceManager.sol";
|
||||
/// @title SlasherStorage
|
||||
/// @notice Base storage contract for slashing functionality
|
||||
/// @dev Provides storage variables and events for slashing operations
|
||||
|
||||
abstract contract SlasherStorage is ISlasher {
|
||||
/**
|
||||
*
|
||||
* CONSTANTS AND IMMUTABLES
|
||||
*
|
||||
*/
|
||||
|
||||
/// @notice the AllocationManager that tracks OperatorSets and Slashing in EigenLayer
|
||||
IAllocationManager public immutable allocationManager;
|
||||
/// @notice the ServiceManager of the AVS
|
||||
IServiceManager public immutable serviceManager;
|
||||
|
||||
uint256 public nextRequestId;
|
||||
|
||||
constructor(IAllocationManager _allocationManager, IServiceManager _serviceManager) {
|
||||
allocationManager = _allocationManager;
|
||||
serviceManager = _serviceManager;
|
||||
}
|
||||
|
||||
uint256[49] private __gap;
|
||||
}
|
||||
128
contracts/src/middleware/VetoableSlasher.sol
Normal file
128
contracts/src/middleware/VetoableSlasher.sol
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
// SPDX-License-Identifier: BUSL-1.1
|
||||
pragma solidity ^0.8.27;
|
||||
|
||||
import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol";
|
||||
import {IServiceManager} from "../interfaces/IServiceManager.sol";
|
||||
import {IAllocationManager} from
|
||||
"eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
|
||||
import {SlasherBase} from "./SlasherBase.sol";
|
||||
import {IVetoableSlasher, IVetoableSlasherTypes} from "../interfaces/IVetoableSlasher.sol";
|
||||
|
||||
/// @title VetoableSlasher
|
||||
/// @notice A slashing contract that implements a veto mechanism allowing a designated committee to cancel slashing requests
|
||||
/// @dev Extends SlasherBase and adds a veto period during which slashing requests can be cancelled
|
||||
contract VetoableSlasher is IVetoableSlasher, SlasherBase {
|
||||
/// @inheritdoc IVetoableSlasher
|
||||
uint32 public immutable override vetoWindowBlocks;
|
||||
|
||||
/// @inheritdoc IVetoableSlasher
|
||||
address public immutable override vetoCommittee;
|
||||
|
||||
/// @notice Mapping of request IDs to their corresponding slashing request details
|
||||
mapping(uint256 => IVetoableSlasherTypes.VetoableSlashingRequest) public slashingRequests;
|
||||
|
||||
/// @notice Modifier to restrict function access to only the veto committee
|
||||
modifier onlyVetoCommittee() {
|
||||
_checkVetoCommittee(msg.sender);
|
||||
_;
|
||||
}
|
||||
|
||||
constructor(
|
||||
IAllocationManager _allocationManager,
|
||||
IServiceManager _serviceManager,
|
||||
address _vetoCommittee,
|
||||
uint32 _vetoWindowBlocks
|
||||
) SlasherBase(_allocationManager, _serviceManager) {
|
||||
vetoWindowBlocks = _vetoWindowBlocks;
|
||||
vetoCommittee = _vetoCommittee;
|
||||
}
|
||||
|
||||
/// @inheritdoc IVetoableSlasher
|
||||
function queueSlashingRequest(
|
||||
IAllocationManager.SlashingParams calldata params
|
||||
) external override onlySlasher {
|
||||
_queueSlashingRequest(params);
|
||||
}
|
||||
|
||||
/// @inheritdoc IVetoableSlasher
|
||||
function cancelSlashingRequest(
|
||||
uint256 requestId
|
||||
) external override onlyVetoCommittee {
|
||||
_cancelSlashingRequest(requestId);
|
||||
}
|
||||
|
||||
/// @inheritdoc IVetoableSlasher
|
||||
function fulfilSlashingRequest(
|
||||
uint256 requestId
|
||||
) external override {
|
||||
_fulfilSlashingRequestAndMarkAsCompleted(requestId);
|
||||
}
|
||||
|
||||
/// @notice Internal function to create and store a new slashing request
|
||||
/// @param params Parameters defining the slashing request
|
||||
function _queueSlashingRequest(
|
||||
IAllocationManager.SlashingParams memory params
|
||||
) internal virtual {
|
||||
uint256 requestId = nextRequestId++;
|
||||
slashingRequests[requestId] = IVetoableSlasherTypes.VetoableSlashingRequest({
|
||||
params: params,
|
||||
requestBlock: block.number,
|
||||
isPending: true
|
||||
});
|
||||
|
||||
emit SlashingRequested(
|
||||
requestId, params.operator, params.operatorSetId, params.wadsToSlash, params.description
|
||||
);
|
||||
}
|
||||
|
||||
/// @notice Internal function to mark a slashing request as cancelled
|
||||
/// @param requestId The ID of the slashing request to cancel
|
||||
function _cancelSlashingRequest(
|
||||
uint256 requestId
|
||||
) internal virtual {
|
||||
IVetoableSlasherTypes.VetoableSlashingRequest storage request = slashingRequests[requestId];
|
||||
require(block.number < request.requestBlock + vetoWindowBlocks, VetoPeriodPassed());
|
||||
require(request.isPending, SlashingRequestNotRequested());
|
||||
|
||||
emit SlashingRequestCancelled(
|
||||
request.params.operator,
|
||||
request.params.operatorSetId,
|
||||
request.params.wadsToSlash,
|
||||
request.params.description
|
||||
);
|
||||
|
||||
delete slashingRequests[requestId];
|
||||
}
|
||||
|
||||
/// @notice Internal function to fullfill a slashing request and mark it as completed
|
||||
/// @param requestId The ID of the slashing request to fulfil
|
||||
function _fulfilSlashingRequestAndMarkAsCompleted(
|
||||
uint256 requestId
|
||||
) internal virtual {
|
||||
IVetoableSlasherTypes.VetoableSlashingRequest storage request = slashingRequests[requestId];
|
||||
require(block.number >= request.requestBlock + vetoWindowBlocks, VetoPeriodNotPassed());
|
||||
require(request.isPending, SlashingRequestIsCancelled());
|
||||
|
||||
request.isPending = false;
|
||||
|
||||
_fulfilSlashingRequest(requestId, request.params);
|
||||
|
||||
emit SlashingRequestFulfilled(
|
||||
request.params.operator,
|
||||
request.params.operatorSetId,
|
||||
request.params.wadsToSlash,
|
||||
request.params.description
|
||||
);
|
||||
|
||||
delete slashingRequests[requestId];
|
||||
}
|
||||
|
||||
/// @notice Internal function to verify if an account is the veto committee
|
||||
/// @param account The address to check
|
||||
/// @dev Reverts if the account is not the veto committee
|
||||
function _checkVetoCommittee(
|
||||
address account
|
||||
) internal view virtual {
|
||||
require(account == vetoCommittee, OnlyVetoCommittee());
|
||||
}
|
||||
}
|
||||
|
|
@ -61,4 +61,8 @@ contract ServiceManagerBaseTest is MockAVSDeployer {
|
|||
new IAllocationManager.CreateSetParams[](0);
|
||||
ServiceManagerBase(address(serviceManager)).createOperatorSets(emptyParams);
|
||||
}
|
||||
|
||||
function test_returnsAVSAddress() public view {
|
||||
assertEq(serviceManager.avs(), address(serviceManager));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
234
contracts/test/SlasherBase.t.sol
Normal file
234
contracts/test/SlasherBase.t.sol
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.27;
|
||||
|
||||
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 {MockAVSDeployer} from "./utils/MockAVSDeployer.sol";
|
||||
import {IServiceManager} from "../src/interfaces/IServiceManager.sol";
|
||||
import {ISlasher, ISlasherErrors, ISlasherEvents} from "../src/interfaces/ISlasher.sol";
|
||||
import {SlasherBase} from "../src/middleware/SlasherBase.sol";
|
||||
import {SlasherMock} from "./mocks/SlasherBaseMock.sol";
|
||||
|
||||
contract SlasherBaseTest is MockAVSDeployer {
|
||||
SlasherMock public slasherContract;
|
||||
address public nonServiceManagerRole;
|
||||
|
||||
function setUp() public virtual {
|
||||
_deployMockEigenLayerAndAVS();
|
||||
_setUpDefaultStrategiesAndMultipliers();
|
||||
|
||||
// Set up roles for testing
|
||||
nonServiceManagerRole = address(0x5678);
|
||||
|
||||
// Deploy the SlasherMock contract, to specifically test the SlasherBase contract
|
||||
slasherContract = new SlasherMock(allocationManager, serviceManager);
|
||||
}
|
||||
|
||||
// Test constructor initializes state correctly
|
||||
function test_constructor() public view {
|
||||
assertEq(
|
||||
address(slasherContract.allocationManager()),
|
||||
address(allocationManager),
|
||||
"AllocationManager address mismatch"
|
||||
);
|
||||
assertEq(
|
||||
address(slasherContract.serviceManager()),
|
||||
address(serviceManager),
|
||||
"ServiceManager address mismatch"
|
||||
);
|
||||
assertEq(slasherContract.nextRequestId(), 0, "NextRequestId should be initialized to 0");
|
||||
}
|
||||
|
||||
// Test that a function with the onlySlasher modifier reverts when called by non-ServiceManager
|
||||
function test_onlySlasherModifier_nonSlasher() public {
|
||||
vm.prank(nonServiceManagerRole);
|
||||
vm.expectRevert(abi.encodeWithSelector(ISlasherErrors.OnlySlasher.selector));
|
||||
slasherContract.restrictedFunction();
|
||||
}
|
||||
|
||||
// Test that a function with the onlySlasher modifier allows access when called by ServiceManager
|
||||
function test_onlySlasherModifier_slasher() public {
|
||||
vm.prank(address(serviceManager));
|
||||
// This should not revert
|
||||
slasherContract.restrictedFunction();
|
||||
}
|
||||
|
||||
// Test that fulfilSlashingRequest can be called by anyone now that the onlySlasher modifier has been removed
|
||||
function test_fulfilSlashingRequest_anyoneCanCall() public {
|
||||
// Setup mock params
|
||||
address operator = address(0xabcd);
|
||||
uint32 operatorSetId = 1;
|
||||
IStrategy[] memory strategies = new IStrategy[](1);
|
||||
strategies[0] = strategyMock1;
|
||||
uint256[] memory wadsToSlash = new uint256[](1);
|
||||
wadsToSlash[0] = 1e16;
|
||||
string memory description = "Test slashing by non-ServiceManager";
|
||||
|
||||
IAllocationManagerTypes.SlashingParams memory params = IAllocationManagerTypes
|
||||
.SlashingParams({
|
||||
operator: operator,
|
||||
operatorSetId: operatorSetId,
|
||||
strategies: strategies,
|
||||
wadsToSlash: wadsToSlash,
|
||||
description: description
|
||||
});
|
||||
|
||||
// Mock the allocationManager.slashOperator call
|
||||
vm.mockCall(
|
||||
address(allocationManager),
|
||||
abi.encodeWithSelector(
|
||||
IAllocationManager.slashOperator.selector, serviceManager.avs(), params
|
||||
),
|
||||
abi.encode()
|
||||
);
|
||||
|
||||
uint256 requestId = 5;
|
||||
|
||||
// A random address should be able to call fulfilSlashingRequest
|
||||
vm.prank(nonServiceManagerRole);
|
||||
vm.expectEmit(true, true, true, true);
|
||||
emit ISlasherEvents.OperatorSlashed(
|
||||
requestId, operator, operatorSetId, wadsToSlash, description
|
||||
);
|
||||
slasherContract.fulfilSlashingRequest(requestId, params);
|
||||
}
|
||||
|
||||
// Test the _checkSlasher internal function
|
||||
function test_checkSlasher() public {
|
||||
// Should succeed for ServiceManager
|
||||
vm.prank(address(serviceManager));
|
||||
slasherContract.checkSlasher(address(serviceManager));
|
||||
|
||||
// Should revert for non-ServiceManager
|
||||
vm.expectRevert(abi.encodeWithSelector(ISlasherErrors.OnlySlasher.selector));
|
||||
slasherContract.checkSlasher(nonServiceManagerRole);
|
||||
}
|
||||
|
||||
// Test the _fulfilSlashingRequest internal function with different parameters
|
||||
function test_fulfilSlashingRequest_withMultipleStrategies() public {
|
||||
// Setup mock params with multiple strategies
|
||||
address operator = address(0xabcd);
|
||||
uint32 operatorSetId = 1;
|
||||
IStrategy[] memory strategies = new IStrategy[](2);
|
||||
strategies[0] = strategyMock1;
|
||||
strategies[1] = strategyMock2;
|
||||
uint256[] memory wadsToSlash = new uint256[](2);
|
||||
wadsToSlash[0] = 1e16; // 1% of the operator's stake
|
||||
wadsToSlash[1] = 2e16; // 2% of the operator's stake
|
||||
string memory description = "Multiple strategy slashing";
|
||||
|
||||
IAllocationManagerTypes.SlashingParams memory params = IAllocationManagerTypes
|
||||
.SlashingParams({
|
||||
operator: operator,
|
||||
operatorSetId: operatorSetId,
|
||||
strategies: strategies,
|
||||
wadsToSlash: wadsToSlash,
|
||||
description: description
|
||||
});
|
||||
|
||||
// Mock the allocationManager.slashOperator call
|
||||
vm.mockCall(
|
||||
address(allocationManager),
|
||||
abi.encodeWithSelector(
|
||||
IAllocationManager.slashOperator.selector, serviceManager.avs(), params
|
||||
),
|
||||
abi.encode()
|
||||
);
|
||||
|
||||
uint256 requestId = 2;
|
||||
|
||||
// ServiceManager should be able to call fulfilSlashingRequest
|
||||
vm.prank(address(serviceManager));
|
||||
vm.expectEmit(true, true, true, true);
|
||||
emit ISlasherEvents.OperatorSlashed(
|
||||
requestId, operator, operatorSetId, wadsToSlash, description
|
||||
);
|
||||
slasherContract.fulfilSlashingRequest(requestId, params);
|
||||
}
|
||||
|
||||
// Test fulfilSlashingRequest with zero wads to slash
|
||||
function test_fulfilSlashingRequest_zeroWadsToSlash() public {
|
||||
// Setup mock params with zero wads
|
||||
address operator = address(0xabcd);
|
||||
uint32 operatorSetId = 1;
|
||||
IStrategy[] memory strategies = new IStrategy[](1);
|
||||
strategies[0] = strategyMock1;
|
||||
uint256[] memory wadsToSlash = new uint256[](1);
|
||||
wadsToSlash[0] = 0; // Zero tokens
|
||||
string memory description = "Zero wad slashing";
|
||||
|
||||
IAllocationManagerTypes.SlashingParams memory params = IAllocationManagerTypes
|
||||
.SlashingParams({
|
||||
operator: operator,
|
||||
operatorSetId: operatorSetId,
|
||||
strategies: strategies,
|
||||
wadsToSlash: wadsToSlash,
|
||||
description: description
|
||||
});
|
||||
|
||||
// Mock the allocationManager.slashOperator call
|
||||
vm.mockCall(
|
||||
address(allocationManager),
|
||||
abi.encodeWithSelector(
|
||||
IAllocationManager.slashOperator.selector, serviceManager.avs(), params
|
||||
),
|
||||
abi.encode()
|
||||
);
|
||||
|
||||
uint256 requestId = 3;
|
||||
|
||||
// ServiceManager should be able to call fulfilSlashingRequest
|
||||
vm.prank(address(serviceManager));
|
||||
vm.expectEmit(true, true, true, true);
|
||||
emit ISlasherEvents.OperatorSlashed(
|
||||
requestId, operator, operatorSetId, wadsToSlash, description
|
||||
);
|
||||
slasherContract.fulfilSlashingRequest(requestId, params);
|
||||
}
|
||||
|
||||
// Test error handling when allocationManager.slashOperator reverts
|
||||
function test_fulfilSlashingRequest_allocationManagerReverts() public {
|
||||
// Setup mock params
|
||||
address operator = address(0xabcd);
|
||||
uint32 operatorSetId = 1;
|
||||
IStrategy[] memory strategies = new IStrategy[](1);
|
||||
strategies[0] = strategyMock1;
|
||||
uint256[] memory wadsToSlash = new uint256[](1);
|
||||
wadsToSlash[0] = 1e16; // 1% of the operator's stake
|
||||
string memory description = "Revert test";
|
||||
|
||||
IAllocationManagerTypes.SlashingParams memory params = IAllocationManagerTypes
|
||||
.SlashingParams({
|
||||
operator: operator,
|
||||
operatorSetId: operatorSetId,
|
||||
strategies: strategies,
|
||||
wadsToSlash: wadsToSlash,
|
||||
description: description
|
||||
});
|
||||
|
||||
// Mock the allocationManager.slashOperator call to revert
|
||||
vm.mockCallRevert(
|
||||
address(allocationManager),
|
||||
abi.encodeWithSelector(
|
||||
IAllocationManager.slashOperator.selector, serviceManager.avs(), params
|
||||
),
|
||||
abi.encodeWithSignature("SomeError()")
|
||||
);
|
||||
|
||||
uint256 requestId = 4;
|
||||
|
||||
// ServiceManager should be able to call fulfilSlashingRequest
|
||||
vm.prank(address(serviceManager));
|
||||
vm.expectRevert(abi.encodeWithSignature("SomeError()"));
|
||||
slasherContract.fulfilSlashingRequest(requestId, params);
|
||||
}
|
||||
}
|
||||
397
contracts/test/VetoableSlasher.t.sol
Normal file
397
contracts/test/VetoableSlasher.t.sol
Normal file
|
|
@ -0,0 +1,397 @@
|
|||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.27;
|
||||
|
||||
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 {MockAVSDeployer} from "./utils/MockAVSDeployer.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 MockAVSDeployer {
|
||||
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();
|
||||
_setUpDefaultStrategiesAndMultipliers();
|
||||
|
||||
// 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] = strategyMock1;
|
||||
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] = strategyMock2;
|
||||
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] = strategyMock1;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import {IAllocationManager} from
|
|||
|
||||
import {ServiceManagerBase} from "../../src/middleware/ServiceManagerBase.sol";
|
||||
import {ServiceManagerBaseStorage} from "../../src/middleware/ServiceManagerBaseStorage.sol";
|
||||
import {IVetoableSlasher} from "../../src/interfaces/IVetoableSlasher.sol";
|
||||
|
||||
/**
|
||||
* @title Minimal implementation of a ServiceManager-type contract.
|
||||
|
|
@ -31,4 +32,15 @@ contract ServiceManagerMock is ServiceManagerBase {
|
|||
) public virtual initializer {
|
||||
__ServiceManagerBase_init(initialOwner, rewardsInitiator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sets the slasher contract
|
||||
* @param slasher The slasher contract address
|
||||
* @dev Only callable by the owner
|
||||
*/
|
||||
function setSlasher(
|
||||
IVetoableSlasher slasher
|
||||
) external override onlyOwner {
|
||||
_slasher = slasher;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
37
contracts/test/mocks/SlasherBaseMock.sol
Normal file
37
contracts/test/mocks/SlasherBaseMock.sol
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.27;
|
||||
|
||||
import {SlasherBase} from "../../src/middleware/SlasherBase.sol";
|
||||
import {
|
||||
IAllocationManager,
|
||||
IAllocationManagerTypes
|
||||
} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
|
||||
import {IServiceManager} from "../../src/interfaces/IServiceManager.sol";
|
||||
|
||||
// SlasherMock implementation for testing
|
||||
contract SlasherMock is SlasherBase {
|
||||
constructor(
|
||||
IAllocationManager _allocationManager,
|
||||
IServiceManager _serviceManager
|
||||
) SlasherBase(_allocationManager, _serviceManager) {}
|
||||
|
||||
// Expose the internal _fulfilSlashingRequest function for testing
|
||||
function fulfilSlashingRequest(
|
||||
uint256 _requestId,
|
||||
IAllocationManagerTypes.SlashingParams memory _params
|
||||
) external {
|
||||
_fulfilSlashingRequest(_requestId, _params);
|
||||
}
|
||||
|
||||
// Function with the onlySlasher modifier for testing
|
||||
function restrictedFunction() external onlySlasher {
|
||||
// Do nothing, just for testing the modifier
|
||||
}
|
||||
|
||||
// Expose the internal _checkSlasher function for testing
|
||||
function checkSlasher(
|
||||
address account
|
||||
) external view {
|
||||
_checkSlasher(account);
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,8 @@ import {StrategyBase} from "eigenlayer-contracts/src/contracts/strategies/Strate
|
|||
|
||||
import {ERC20FixedSupply} from "./ERC20FixedSupply.sol";
|
||||
import {IServiceManager} from "../../src/interfaces/IServiceManager.sol";
|
||||
import {VetoableSlasher} from "../../src/middleware/VetoableSlasher.sol";
|
||||
import {IVetoableSlasher} from "../../src/interfaces/IVetoableSlasher.sol";
|
||||
|
||||
// Mocks
|
||||
import {StrategyManagerMock} from "eigenlayer-contracts/src/test/mocks/StrategyManagerMock.sol";
|
||||
|
|
@ -46,6 +48,11 @@ contract MockAVSDeployer is Test {
|
|||
// AVS contracts
|
||||
ServiceManagerMock public serviceManager;
|
||||
ServiceManagerMock public serviceManagerImplementation;
|
||||
VetoableSlasher public vetoableSlasher;
|
||||
|
||||
// Roles and parameters
|
||||
address public vetoCommitteeMember = address(uint160(uint256(keccak256("vetoCommitteeMember"))));
|
||||
uint32 public vetoWindowBlocks = 100; // 100 blocks veto window for tests
|
||||
|
||||
// EigenLayer contracts
|
||||
StrategyManagerMock public strategyManagerMock;
|
||||
|
|
@ -202,6 +209,19 @@ contract MockAVSDeployer is Test {
|
|||
);
|
||||
cheats.stopPrank();
|
||||
console.log("ServiceManager implementation deployed");
|
||||
|
||||
// Deploy and configure the VetoableSlasher
|
||||
cheats.startPrank(regularDeployer);
|
||||
vetoableSlasher = new VetoableSlasher(
|
||||
allocationManager, serviceManager, vetoCommitteeMember, vetoWindowBlocks
|
||||
);
|
||||
cheats.stopPrank();
|
||||
|
||||
// Set the slasher in the ServiceManager
|
||||
cheats.prank(avsOwner);
|
||||
serviceManager.setSlasher(vetoableSlasher);
|
||||
|
||||
console.log("VetoableSlasher deployed and configured");
|
||||
}
|
||||
|
||||
function _setUpDefaultStrategiesAndMultipliers() internal virtual {
|
||||
|
|
@ -283,6 +303,7 @@ contract MockAVSDeployer is Test {
|
|||
vm.label(address(allocationManagerImplementation), "AllocationManagerImplementation");
|
||||
vm.label(address(serviceManager), "ServiceManager");
|
||||
vm.label(address(serviceManagerImplementation), "ServiceManagerImplementation");
|
||||
vm.label(address(vetoableSlasher), "VetoableSlasher");
|
||||
}
|
||||
|
||||
/// @dev Sort to ensure that the array is in ascending order for strategies
|
||||
|
|
|
|||
Loading…
Reference in a new issue