Skip to content

Commit 318b657

Browse files
committed
feat: escrow funds in unique clone contracts (#1387)
**Motivation:** Current implementation is broken by rebase tokens. **Modifications:** - Rename SWR -> `SlashEscrowFactory`. - Add factory logic that deploys clones unique to their slash ID. **Result:** Funds will now be stored in clone contracts unique to their slash ID.
1 parent 27f0961 commit 318b657

19 files changed

+520
-314
lines changed

script/deploy/devnet/deploy_from_scratch.s.sol

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ import "../../../src/contracts/core/AVSDirectory.sol";
1414
import "../../../src/contracts/core/RewardsCoordinator.sol";
1515
import "../../../src/contracts/core/AllocationManager.sol";
1616
import "../../../src/contracts/permissions/PermissionController.sol";
17-
import "../../../src/contracts/core/SlashingWithdrawalRouter.sol";
17+
import "../../../src/contracts/core/SlashEscrowFactory.sol";
1818
import "../../../src/contracts/strategies/StrategyBaseTVLLimits.sol";
1919
import "../../../src/contracts/strategies/StrategyFactory.sol";
2020
import "../../../src/contracts/strategies/StrategyBase.sol";
21+
import "../../../src/contracts/core/SlashEscrow.sol";
2122

2223
import "../../../src/contracts/pods/EigenPod.sol";
2324
import "../../../src/contracts/pods/EigenPodManager.sol";
@@ -63,8 +64,8 @@ contract DeployFromScratch is Script, Test {
6364
AllocationManager public allocationManager;
6465
PermissionController public permissionController;
6566
PermissionController public permissionControllerImplementation;
66-
SlashingWithdrawalRouter public slashingWithdrawalRouter;
67-
SlashingWithdrawalRouter public slashingWithdrawalRouterImplementation;
67+
SlashEscrowFactory public slashEscrowFactory;
68+
SlashEscrowFactory public slashEscrowFactoryImplementation;
6869

6970
EmptyContract public emptyContract;
7071

@@ -216,7 +217,7 @@ contract DeployFromScratch is Script, Test {
216217
permissionController = PermissionController(
217218
address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
218219
);
219-
slashingWithdrawalRouter = SlashingWithdrawalRouter(
220+
slashEscrowFactory = SlashEscrowFactory(
220221
address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
221222
);
222223

@@ -240,8 +241,7 @@ contract DeployFromScratch is Script, Test {
240241
SEMVER
241242
);
242243

243-
strategyManagerImplementation =
244-
new StrategyManager(delegation, slashingWithdrawalRouter, eigenLayerPauserReg, SEMVER);
244+
strategyManagerImplementation = new StrategyManager(delegation, slashEscrowFactory, eigenLayerPauserReg, SEMVER);
245245
avsDirectoryImplementation = new AVSDirectory(delegation, eigenLayerPauserReg, SEMVER);
246246
eigenPodManagerImplementation =
247247
new EigenPodManager(ethPOSDeposit, eigenPodBeacon, delegation, eigenLayerPauserReg, SEMVER);
@@ -270,8 +270,8 @@ contract DeployFromScratch is Script, Test {
270270
);
271271
permissionControllerImplementation = new PermissionController(SEMVER);
272272
strategyFactoryImplementation = new StrategyFactory(strategyManager, eigenLayerPauserReg, SEMVER);
273-
slashingWithdrawalRouterImplementation =
274-
new SlashingWithdrawalRouter(allocationManager, strategyManager, eigenLayerPauserReg, SEMVER);
273+
slashEscrowFactoryImplementation =
274+
new SlashEscrowFactory(allocationManager, strategyManager, eigenLayerPauserReg, new SlashEscrow(), SEMVER);
275275

276276
// Third, upgrade the proxy contracts to use the correct implementation contracts and initialize them.
277277
{

script/deploy/local/deploy_from_scratch.slashing.s.sol

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import "../../../src/contracts/core/AVSDirectory.sol";
1414
import "../../../src/contracts/core/RewardsCoordinator.sol";
1515
import "../../../src/contracts/core/AllocationManager.sol";
1616
import "../../../src/contracts/permissions/PermissionController.sol";
17-
import "../../../src/contracts/core/SlashingWithdrawalRouter.sol";
17+
import "../../../src/contracts/core/SlashEscrowFactory.sol";
18+
import "../../../src/contracts/core/SlashEscrow.sol";
1819

1920
import "../../../src/contracts/strategies/StrategyBaseTVLLimits.sol";
2021

@@ -67,8 +68,8 @@ contract DeployFromScratch is Script, Test {
6768
AllocationManager public allocationManager;
6869
PermissionController public permissionControllerImplementation;
6970
PermissionController public permissionController;
70-
SlashingWithdrawalRouter public slashingWithdrawalRouter;
71-
SlashingWithdrawalRouter public slashingWithdrawalRouterImplementation;
71+
SlashEscrowFactory public slashEscrowFactory;
72+
SlashEscrowFactory public slashEscrowFactoryImplementation;
7273

7374
EmptyContract public emptyContract;
7475

@@ -224,7 +225,7 @@ contract DeployFromScratch is Script, Test {
224225
permissionController = PermissionController(
225226
address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
226227
);
227-
slashingWithdrawalRouter = SlashingWithdrawalRouter(
228+
slashEscrowFactory = SlashEscrowFactory(
228229
address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
229230
);
230231

@@ -247,8 +248,7 @@ contract DeployFromScratch is Script, Test {
247248
MIN_WITHDRAWAL_DELAY,
248249
SEMVER
249250
);
250-
strategyManagerImplementation =
251-
new StrategyManager(delegation, slashingWithdrawalRouter, eigenLayerPauserReg, SEMVER);
251+
strategyManagerImplementation = new StrategyManager(delegation, slashEscrowFactory, eigenLayerPauserReg, SEMVER);
252252
avsDirectoryImplementation = new AVSDirectory(delegation, eigenLayerPauserReg, SEMVER);
253253
eigenPodManagerImplementation =
254254
new EigenPodManager(ethPOSDeposit, eigenPodBeacon, delegation, eigenLayerPauserReg, SEMVER);
@@ -276,8 +276,8 @@ contract DeployFromScratch is Script, Test {
276276
SEMVER
277277
);
278278
permissionControllerImplementation = new PermissionController(SEMVER);
279-
slashingWithdrawalRouterImplementation =
280-
new SlashingWithdrawalRouter(allocationManager, strategyManager, eigenLayerPauserReg, SEMVER);
279+
slashEscrowFactoryImplementation =
280+
new SlashEscrowFactory(allocationManager, strategyManager, eigenLayerPauserReg, new SlashEscrow(), SEMVER);
281281

282282
// Third, upgrade the proxy contracts to use the correct implementation contracts and initialize them.
283283
{

script/utils/ExistingDeploymentParser.sol

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import "../../src/contracts/core/AVSDirectory.sol";
1111
import "../../src/contracts/core/RewardsCoordinator.sol";
1212
import "../../src/contracts/core/AllocationManager.sol";
1313
import "../../src/contracts/permissions/PermissionController.sol";
14-
import "../../src/contracts/core/SlashingWithdrawalRouter.sol";
14+
import "../../src/contracts/core/SlashEscrowFactory.sol";
15+
import "../../src/contracts/core/SlashEscrow.sol";
1516

1617
import "../../src/contracts/strategies/StrategyFactory.sol";
1718
import "../../src/contracts/strategies/StrategyBase.sol";
@@ -137,9 +138,9 @@ contract ExistingDeploymentParser is Script, Logger {
137138
StrategyBase public baseStrategyImplementation;
138139
StrategyBase public strategyFactoryBeaconImplementation;
139140

140-
/// @dev SlashingWithdrawalRouter
141-
SlashingWithdrawalRouter public slashingWithdrawalRouter;
142-
SlashingWithdrawalRouter public slashingWithdrawalRouterImplementation;
141+
/// @dev SlashEscrowFactory
142+
SlashEscrowFactory public slashEscrowFactory;
143+
SlashEscrowFactory public slashEscrowFactoryImplementation;
143144

144145
// Token
145146
ProxyAdmin public tokenProxyAdmin;

src/contracts/core/DelegationManager.sol

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -540,10 +540,9 @@ contract DelegationManager is
540540

541541
uint256[] memory prevSlashingFactors;
542542
{
543-
// slashableUntil is block inclusive so we need to check if the current block is strictly greater than the slashableUntil block
544-
// meaning the withdrawal can be completed.
545-
// TODO: update delay blocks for redistribution + EIGEN_REDISTRIBUTION_DELAY_BLOCKS
546-
uint32 delayBlocks = withdrawal.delegatedTo == address(this) ? 3.5 days : MIN_WITHDRAWAL_DELAY_BLOCKS;
543+
// The slashableUntil block is inclusive, so we verify that the current block number exceeds it
544+
// before allowing the withdrawal to be completed.
545+
uint32 delayBlocks = withdrawal.delegatedTo == address(this) ? 4 days : MIN_WITHDRAWAL_DELAY_BLOCKS;
547546
uint32 slashableUntil = withdrawal.startBlock + delayBlocks;
548547
require(uint32(block.number) > slashableUntil, WithdrawalDelayNotElapsed());
549548

src/contracts/core/SlashEscrow.sol

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.27;
3+
4+
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
5+
import "@openzeppelin-upgrades/contracts/proxy/ClonesUpgradeable.sol";
6+
import "../interfaces/ISlashEscrow.sol";
7+
import "../interfaces/ISlashEscrowFactory.sol";
8+
9+
contract SlashEscrow is ISlashEscrow {
10+
using OperatorSetLib for *;
11+
using SafeERC20 for IERC20;
12+
13+
/// @inheritdoc ISlashEscrow
14+
function burnOrRedistributeUnderlyingTokens(
15+
ISlashEscrowFactory slashEscrowFactory,
16+
ISlashEscrow slashEscrowImplementation,
17+
OperatorSet calldata operatorSet,
18+
uint256 slashId,
19+
address recipient,
20+
IStrategy strategy
21+
) external virtual {
22+
// Assert that the deployment parameters are valid by validating against the address of this proxy.
23+
require(
24+
verifyDeploymentParameters(slashEscrowFactory, slashEscrowImplementation, operatorSet, slashId),
25+
InvalidDeploymentParameters()
26+
);
27+
28+
// Assert that the caller is the slash escrow factory.
29+
require(msg.sender == address(slashEscrowFactory), OnlySlashEscrowFactory());
30+
31+
// Burn or redistribute the underlying tokens.
32+
IERC20 underlyingToken = strategy.underlyingToken();
33+
underlyingToken.safeTransfer(recipient, underlyingToken.balanceOf(address(this)));
34+
}
35+
36+
/// @inheritdoc ISlashEscrow
37+
function verifyDeploymentParameters(
38+
ISlashEscrowFactory slashEscrowFactory,
39+
ISlashEscrow slashEscrowImplementation,
40+
OperatorSet calldata operatorSet,
41+
uint256 slashId
42+
) public view virtual returns (bool) {
43+
return ClonesUpgradeable.predictDeterministicAddress(
44+
address(slashEscrowImplementation),
45+
keccak256(abi.encodePacked(operatorSet.key(), slashId)),
46+
address(slashEscrowFactory)
47+
) == address(this);
48+
}
49+
}

0 commit comments

Comments
 (0)