Skip to content

Conversation

@0xClandestine
Copy link
Owner

@0xClandestine 0xClandestine commented May 14, 2025

Motivation:

We want to hold slashed funds in escrow temporarily to maintain protocol security (prevent malicious actors from draining the contracts).

Modifications:

  • Add SlashingWithdrawalRouter.

Result:

The SlashingWithdrawalRouter contract is added for managing slashed funds in the EigenLayer protocol. It provides a mechanism to handle the burning or redistribution of slashed shares after a delay period, ensuring proper handling of slashed funds while maintaining protocol security.

@0xClandestine 0xClandestine changed the title feat(draft): immutable redistribution escrow feat(draft): add SlashingWithdrawalRouter May 16, 2025
Copy link

@non-fungible-nelson non-fungible-nelson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments and clarifications -- looks good for a first cut. Thanks!

// Assert that the redistribution is not paused.
require(!escrow.paused, RedistributionCurrentlyPaused());

// QUESTION: How do we want to check maturity? Are we storing it during initiation or parsing JIT?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ypatil12 @0xClandestine I think its OK to just store during initiation some future a block number. We are anticipating a programmatic call here from the ALM right? if so, we can just parse against the maturity block number >= some number of days in blocks (4)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to store the block that it was queued and then read the delay. We should have a function where the delay can be set by the ops multisig

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This delay applies to a list of strategies passed in. The delay can be set on a per strategy basis, if not set use the global

@non-fungible-nelson
Copy link

Another note we need to discern on immutability:

  • do we allow the admin role to update the pauser at will?
  • rescuing the contract should only be done through an upgrade through governance (community multi-sig)

// Assert that the redistribution is not paused.
require(!escrow.paused, RedistributionCurrentlyPaused());

// QUESTION: How do we want to check maturity? Are we storing it during initiation or parsing JIT?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to store the block that it was queued and then read the delay. We should have a function where the delay can be set by the ops multisig

// Assert that the redistribution is not paused.
require(!escrow.paused, RedistributionCurrentlyPaused());

// QUESTION: How do we want to check maturity? Are we storing it during initiation or parsing JIT?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This delay applies to a list of strategies passed in. The delay can be set on a per strategy basis, if not set use the global

Copy link
Collaborator

@ypatil12 ypatil12 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add introspection on:

  1. List of redistributions for an opSet/slashID
  2. Whether the redistribution is completable

Copy link
Collaborator

@ypatil12 ypatil12 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also need a way for us to introspect storage to get the slashes that need to be burnt (send to 0xEigen)

mapping(bytes32 operatorSetKey => mapping(uint256 slashId => RedistributionEscrow escrow)) internal _escrow;

/// @notice Returns the paused status for a given operator set and slash ID.
mapping(bytes32 operatorSetKey => mapping(uint256 slashId => bool paused)) internal _paused;
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel like this could be improved, thinking we pass in a range of slashIds as upper and lower bounds, can pass type(uint).max in to pause an entire operator set.

Copy link
Collaborator

@ypatil12 ypatil12 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One pass, will review again once we have escrow logic

// EnumerableMap.set(burnableShares, address(strategy), currentShares + addedSharesToBurn);
emit BurnableSharesIncreased(operatorSet, slashId, strategy, addedSharesToBurn);
emit BurnableSharesIncreased(operatorSet, slashId, strategy, sharesToBurn);

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing I noticed is that we have 1 call from ALM to DM if multiple strategies are burned, but n calls from DM to SM. Just pointing out - I do think the pattern is fine

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah originally we had n calls from ALM to DM too, originally planned to optimize both to reduce external calls with but later found out it's not possible for the DM to SM call. Should reduce the complexity of logs a bit, and make them more readable.

Copy link
Collaborator

@ypatil12 ypatil12 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style comments

@ypatil12
Copy link
Collaborator

LGTM

@0xClandestine 0xClandestine changed the title feat(draft): add SlashingWithdrawalRouter feat: add SlashingWithdrawalRouter May 21, 2025
@0xClandestine 0xClandestine merged commit 60fb3f5 into feat/sm-redistribution-2 May 21, 2025
7 of 12 checks passed
0xClandestine added a commit that referenced this pull request May 21, 2025
* wip

* wip

* feat: add `SlashingWithdrawalRouter`

* perf: optimize `Pausable` for codesize

* chore: renaming

* refactor: use `Pauseable` instead of OZ `AccessControl`

* perf: reduce `Pauseble` codesize

* refactor: arrayified inputs

* wip

* refactor: make struct params arrays

* fix: remove unused import

* refactor: `redistributionRecipient` == `releaser`

* refactor: cleanup

* wip

* wip

* refactor: cleanup

* refactor: remove uneeded code

* refactor: remove unused error

* wip

* refactor: review changes

* feat: add `getPendingSlashIdsForOperatorSet`

* test: wip

* test: wip

* test: wip

* doc: todos

* test: wip

* refactor: iterate in reverse order to avoid skipping elements

* test: passing

* feat: add `getPendingBurnOrRedistributions` nested alias

* chore: renaming

* fix: update gap

* fix: dm mock

* feat: full introspection

* fix: `pendingOperatorSets` removal

* fix: update visibility

* feat: add delay logic

* fix: enforce minimum delay

* refactor: review changes

* refactor: remove `_checkBurnOrRedistributionDelay`

* fix: gap

* nit: natspec style

* refactor: update headers

* refactor: review changes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants