Skip to content

Commit cb4df12

Browse files
authored
feat: slasher templates / examples (#310)
* chore: bump to slashing branch * chore: bump compiler version * fix: dep interface changes * fix: compiler errors from interface changes and type changes * fix: compiler errors * chore: bump dependencies * chore: bump core dependency and resolve issues * chore: bump core dependency and fix compiler errors * feat: integrate AllocationManager * feat: add a slashing permission to the service manager * chore: remove unneeded casting * feat: implement a slasher permission and forward call to AllocationManager * feat: add simiple slasher starting point * feat: slashers * chore: change around slashed event * fix: call dm * feat: add proposal mechanism for updating slasher * fix: set to completed instead of delete * chore: use struct instead of params directly * chore: clean up params more * chore: simplify and organize files * chore: cleanup logic and couple event with internal func * fix: pass correct params * chore: organize and add interface * chore: nits * chore: cleanup more nits * fix: storage gap * chore: nits refactor * chore: go back to fulfill being onlySlasher * test: fixes from core updates * fix: use delegated stake per operator set instead of per AVS * fix: update to 14 days * feat: configurable lookahead and stake type
1 parent eb0d6ad commit cb4df12

23 files changed

+319
-70
lines changed

src/ServiceManagerBase.sol

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol";
1414
import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol";
1515
import {BitmapUtils} from "./libraries/BitmapUtils.sol";
1616
import {LibMergeSort} from "./libraries/LibMergeSort.sol";
17-
import {console} from "forge-std/Test.sol";
1817

1918
/**
2019
* @title Minimal implementation of a ServiceManager-type contract.
@@ -24,6 +23,8 @@ import {console} from "forge-std/Test.sol";
2423
abstract contract ServiceManagerBase is ServiceManagerBaseStorage {
2524
using BitmapUtils for *;
2625

26+
uint256 public constant SLASHER_PROPOSAL_DELAY = 7 days;
27+
2728
/// @notice when applied to a function, only allows the RegistryCoordinator to call it
2829
modifier onlyRegistryCoordinator() {
2930
require(
@@ -177,12 +178,25 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage {
177178
}
178179

179180
/**
180-
* @notice Sets the slasher address
181+
* @notice Proposes a new slasher address
181182
* @param newSlasher The new slasher address
182183
* @dev only callable by the owner
183184
*/
184-
function setSlasher(address newSlasher) external onlyOwner {
185-
_setSlasher(newSlasher);
185+
function proposeNewSlasher(address newSlasher) external onlyOwner {
186+
_proposeNewSlasher(newSlasher);
187+
}
188+
189+
/**
190+
* @notice Accepts the proposed slasher address after the delay period
191+
* @dev only callable by the owner
192+
*/
193+
function acceptProposedSlasher() external onlyOwner {
194+
require(
195+
block.timestamp >= slasherProposalTimestamp + SLASHER_PROPOSAL_DELAY,
196+
"ServiceManager: Slasher proposal delay not met"
197+
);
198+
_setSlasher(proposedSlasher);
199+
delete proposedSlasher;
186200
}
187201

188202
/**
@@ -342,6 +356,12 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage {
342356
rewardsInitiator = newRewardsInitiator;
343357
}
344358

359+
function _proposeNewSlasher(address newSlasher) internal {
360+
proposedSlasher = newSlasher;
361+
slasherProposalTimestamp = block.timestamp;
362+
emit SlasherProposed(newSlasher, slasherProposalTimestamp);
363+
}
364+
345365
function _setSlasher(address newSlasher) internal {
346366
emit SlasherUpdated(slasher, newSlasher);
347367
slasher = newSlasher;
@@ -425,6 +445,10 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage {
425445
return address(_avsDirectory);
426446
}
427447

448+
function allocationManager() external view override returns (address) {
449+
return address(_allocationManager);
450+
}
451+
428452
function _checkRewardsInitiator() internal view {
429453
require(
430454
msg.sender == rewardsInitiator,

src/ServiceManagerBaseStorage.sol

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ abstract contract ServiceManagerBaseStorage is IServiceManager, OwnableUpgradeab
4040
/// @notice The address of the slasher account
4141
address public slasher;
4242

43+
/// @notice The address of the proposed slasher account
44+
address public proposedSlasher;
45+
46+
/// @notice The timestamp when the slasher was proposed
47+
uint256 public slasherProposalTimestamp;
48+
49+
/// @notice Boolean indicating if the migration has been finalized
4350
bool public migrationFinalized;
4451

4552
/// @notice Sets the (immutable) `_avsDirectory`, `_rewardsCoordinator`, `_registryCoordinator`, `_stakeRegistry`, and `_allocationManager` addresses
@@ -58,5 +65,5 @@ abstract contract ServiceManagerBaseStorage is IServiceManager, OwnableUpgradeab
5865
}
5966

6067
// storage gap for upgradeability
61-
uint256[48] private __GAP;
68+
uint256[46] private __GAP;
6269
}

src/StakeRegistry.sol

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pragma solidity ^0.8.12;
33

44
import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol";
55
import {IAVSDirectory, OperatorSet} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol";
6+
import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
67
import {IServiceManager} from "./interfaces/IServiceManager.sol";
78

89
import {StakeRegistryStorage, IStrategy} from "./StakeRegistryStorage.sol";
@@ -229,6 +230,18 @@ contract StakeRegistry is StakeRegistryStorage {
229230
_setMinimumStakeForQuorum(quorumNumber, minimumStake);
230231
}
231232

233+
/**
234+
* @notice Sets the stake type for the registry
235+
* @param _stakeType The type of stake to track (TOTAL_DELEGATED, TOTAL_SLASHABLE, or BOTH)
236+
*/
237+
function setStakeType(StakeType _stakeType) external onlyCoordinatorOwner {
238+
_setStakeType(_stakeType);
239+
}
240+
241+
242+
function setSlashableStakeLookahead(uint32 _lookAheadPeriod) external onlyCoordinatorOwner {
243+
_setLookAheadPeriod(_lookAheadPeriod);
244+
}
232245
/**
233246
* @notice Adds strategies and weights to the quorum
234247
* @dev Checks to make sure that the *same* strategy cannot be added multiple times (checks against both against existing and new strategies).
@@ -491,15 +504,17 @@ contract StakeRegistry is StakeRegistryStorage {
491504
uint256 stratsLength = strategyParamsLength(quorumNumber);
492505
StrategyParams memory strategyAndMultiplier;
493506

494-
uint256[] memory strategyShares;
495-
// = delegation.getDelegatableShares(operator, strategiesPerQuorum[quorumNumber]);
507+
address[] memory operators = new address[](1);
508+
operators[0] = operator;
509+
uint32 beforeTimestamp = uint32(block.timestamp + slashableStakeLookAhead);
510+
(uint256[][] memory strategyShares, ) = IAllocationManager(serviceManager.allocationManager()).getMinDelegatedAndSlashableOperatorShares(OperatorSet(address(serviceManager), quorumNumber), operators ,strategiesPerQuorum[quorumNumber], beforeTimestamp);
496511
for (uint256 i = 0; i < stratsLength; i++) {
497512
// accessing i^th StrategyParams struct for the quorumNumber
498513
strategyAndMultiplier = strategyParams[quorumNumber][i];
499514

500515
// add the weight from the shares for this strategy to the total weight
501-
if (strategyShares[i] > 0) {
502-
weight += uint96(strategyShares[i] * strategyAndMultiplier.multiplier / WEIGHTING_DIVISOR);
516+
if (strategyShares[i][0] > 0) {
517+
weight += uint96(strategyShares[i][0] * strategyAndMultiplier.multiplier / WEIGHTING_DIVISOR);
503518
}
504519
}
505520

@@ -731,6 +746,27 @@ contract StakeRegistry is StakeRegistryStorage {
731746
return indices;
732747
}
733748

749+
/**
750+
* @notice Sets the stake type for the registry
751+
* @param _stakeType The type of stake to track (TOTAL_DELEGATED, TOTAL_SLASHABLE, or BOTH)
752+
*/
753+
function _setStakeType(StakeType _stakeType) internal {
754+
StakeType oldStakeType = stakeType;
755+
stakeType = _stakeType;
756+
emit StakeTypeSet(oldStakeType, _stakeType);
757+
}
758+
759+
/**
760+
* @notice Sets the look ahead time for checking operator shares
761+
* @param _lookAheadDays The number of days to look ahead when checking shares
762+
*/
763+
function _setLookAheadPeriod(uint32 _lookAheadDays) internal {
764+
uint32 oldLookAheadDays = slashableStakeLookAhead;
765+
slashableStakeLookAhead = _lookAheadDays;
766+
emit LookAheadPeriodChanged(oldLookAheadDays, _lookAheadDays);
767+
}
768+
769+
734770
function _checkRegistryCoordinator() internal view {
735771
require(
736772
msg.sender == address(registryCoordinator),

src/StakeRegistryStorage.sol

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ abstract contract StakeRegistryStorage is IStakeRegistry {
5252
mapping(uint8 => StrategyParams[]) public strategyParams;
5353
mapping(uint8 => IStrategy[]) public strategiesPerQuorum;
5454

55+
StakeType public stakeType;
56+
57+
uint32 public slashableStakeLookAhead;
5558

5659
constructor(
5760
IRegistryCoordinator _registryCoordinator,
@@ -67,5 +70,5 @@ abstract contract StakeRegistryStorage is IStakeRegistry {
6770

6871
// storage gap for upgradeability
6972
// slither-disable-next-line shadowing-state
70-
uint256[45] private __GAP;
73+
uint256[44] private __GAP;
7174
}

src/interfaces/IServiceManager.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {IRewardsCoordinator} from "eigenlayer-contracts/src/contracts/interfaces
55
import {IServiceManagerUI} from "./IServiceManagerUI.sol";
66
import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol";
77
import {IAllocationManagerTypes} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
8+
import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
89

910
/**
1011
* @title Minimal interface for a ServiceManager-type contract that forms the single point for an AVS to push updates to EigenLayer
@@ -50,4 +51,5 @@ interface IServiceManager is IServiceManagerUI {
5051
// EVENTS
5152
event RewardsInitiatorUpdated(address prevRewardsInitiator, address newRewardsInitiator);
5253
event SlasherUpdated(address prevSlasher, address newSlasher);
54+
event SlasherProposed(address newSlasher, uint256 slasherProposalTimestamp);
5355
}

src/interfaces/IServiceManagerUI.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,7 @@ interface IServiceManagerUI {
5858

5959
/// @notice Returns the EigenLayer AVSDirectory contract.
6060
function avsDirectory() external view returns (address);
61+
62+
/// @notice Returns the EigenLayer AllocationManager contract.
63+
function allocationManager() external view returns (address);
6164
}

src/interfaces/ISlasher.sol

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
2+
// SPDX-License-Identifier: BUSL-1.1
3+
pragma solidity ^0.8.12;
4+
5+
import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol";
6+
import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
7+
8+
interface ISlasherEvents {
9+
event SlashingRequested(
10+
uint256 indexed requestId,
11+
address indexed operator,
12+
uint32 indexed operatorSetId,
13+
uint256 wadToSlash,
14+
string description
15+
);
16+
17+
event SlashingRequestCancelled(uint256 indexed requestId);
18+
19+
event OperatorSlashed(
20+
uint256 indexed slashingRequestId,
21+
address indexed operator,
22+
uint32 indexed operatorSetId,
23+
IStrategy[] strategies,
24+
uint256 wadToSlash,
25+
string description
26+
);
27+
}
28+
29+
interface ISlasherTypes {
30+
enum SlashingStatus {
31+
Null,
32+
Requested,
33+
Completed,
34+
Cancelled
35+
}
36+
37+
struct SlashingRequest {
38+
IAllocationManager.SlashingParams params;
39+
uint256 requestTimestamp;
40+
SlashingStatus status;
41+
}
42+
43+
}
44+
45+
interface ISlasher is ISlasherEvents, ISlasherTypes{}

src/interfaces/IStakeRegistry.sol

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ import {IRegistry} from "./IRegistry.sol";
1111
* @author Layr Labs, Inc.
1212
*/
1313
interface IStakeRegistry is IRegistry {
14+
15+
enum StakeType {
16+
TOTAL_DELEGATED,
17+
TOTAL_SLASHABLE,
18+
BOTH
19+
}
1420

1521
// DATA STRUCTURES
1622

@@ -42,6 +48,13 @@ interface IStakeRegistry is IRegistry {
4248
uint8 quorumNumber,
4349
uint96 stake
4450
);
51+
52+
53+
/// @notice emitted when the look ahead time for checking operator shares is updated
54+
event LookAheadPeriodChanged(uint32 oldLookAheadDays, uint32 newLookAheadDays);
55+
56+
/// @notice emitted when the stake type is updated
57+
event StakeTypeSet(StakeType previousStakeType, StakeType newStakeType);
4558
/// @notice emitted when the minimum stake for a quorum is updated
4659
event MinimumStakeForQuorumUpdated(uint8 indexed quorumNumber, uint96 minimumStake);
4760
/// @notice emitted when a new quorum is created

src/slashers/InstantSlasher.sol

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.12;
3+
4+
import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol";
5+
import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
6+
import {SlasherBase} from "./base/SlasherBase.sol";
7+
8+
contract InstantSlasher is SlasherBase {
9+
10+
function initialize(address _serviceManager, address _slasher) external initializer {
11+
__SlasherBase_init(_serviceManager, _slasher);
12+
}
13+
14+
function fulfillSlashingRequest(
15+
IAllocationManager.SlashingParams memory _slashingParams
16+
) external virtual onlySlasher {
17+
uint256 requestId = nextRequestId++;
18+
_fulfillSlashingRequest(requestId, _slashingParams);
19+
}
20+
21+
22+
}

src/slashers/SimpleSlasher.sol

Lines changed: 0 additions & 33 deletions
This file was deleted.

0 commit comments

Comments
 (0)