Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit c67f034

Browse files
committedMay 27, 2025·
fix: enumerable map overwrite (#1399)
**Motivation:** Currently, there's a bug in the `SM` where if you loop through the burnable shares queue, you may not clear all due to swap and pop of an Enumerable Map. Furthermore, we also are constrained by a token transfer taking too much gas and blocking transfer out of funds. **Modifications:** - Iterate backwards on `decreaseBurnOrRedistributableShares ` - Overloaded `decreaseBurnableShares` with a version to pass in an index. This function will escrow a single share (called by above too). Now, we do not need a max strategy per opSet requirement - Unit tests for both `increaseBurnOrRedistributableShares` and `decreaseBurnOrRedistributableShares` - Added the following introspection: -- `getBurnOrRedistributableShares(operatorSet, slashId) returns (Strategy[] Strats, uint256[] shares) -- `getBurnOrRedistributableShares(operatorSet, slashId, strategy) returns (shares) -- `getBurnOrRedistributableCount(operaotrSet, slashed) returns (count)` **Result:** Correct code with unit tests fix: enumerable map ovewrite chore: format chore: address rebase issues test: increase burnable shares chore: add tests feat: simplify escrow delay chore: format chore: clarify natspec chore: fix typos feat: add convenience view functions (#1407) chore: remove commented code chore: format chore: format chore: add regression chore: naming chore: naming fix: build fix: slaky test chore: format
1 parent b0a0b20 commit c67f034

File tree

6 files changed

+304
-185
lines changed

6 files changed

+304
-185
lines changed
 

‎src/contracts/core/SlashEscrow.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ contract SlashEscrow is ISlashEscrow {
1111
using SafeERC20 for IERC20;
1212

1313
/// @inheritdoc ISlashEscrow
14-
function burnOrRedistributeUnderlyingTokens(
14+
function releaseTokens(
1515
ISlashEscrowFactory slashEscrowFactory,
1616
ISlashEscrow slashEscrowImplementation,
1717
OperatorSet calldata operatorSet,

‎src/contracts/core/SlashEscrowFactory.sol

Lines changed: 79 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ contract SlashEscrowFactory is Initializable, SlashEscrowFactoryStorage, Ownable
4242
) external initializer {
4343
_transferOwnership(initialOwner);
4444
_setPausedStatus(initialPausedStatus);
45-
_setGlobalBurnOrRedistributionDelay(initialGlobalDelayBlocks);
45+
_setGlobalEscrowDelay(initialGlobalDelayBlocks);
4646
}
4747

4848
/**
@@ -72,14 +72,14 @@ contract SlashEscrowFactory is Initializable, SlashEscrowFactoryStorage, Ownable
7272

7373
// Set the start block for the slash ID.
7474
_slashIdToStartBlock[operatorSet.key()][slashId] = uint32(block.number);
75-
emit StartBurnOrRedistribution(operatorSet, slashId, strategy, uint32(block.number));
75+
emit StartEscrow(operatorSet, slashId, strategy, uint32(block.number));
7676
}
7777

7878
/// @inheritdoc ISlashEscrowFactory
7979
function releaseSlashEscrow(
8080
OperatorSet calldata operatorSet,
8181
uint256 slashId
82-
) external virtual onlyWhenNotPaused(PAUSED_BURN_OR_REDISTRIBUTE_SHARES) {
82+
) external virtual onlyWhenNotPaused(PAUSED_RELEASE_ESCROW) {
8383
address redistributionRecipient = allocationManager.getRedistributionRecipient(operatorSet);
8484

8585
// If the redistribution recipient is not the default burn address...
@@ -88,10 +88,10 @@ contract SlashEscrowFactory is Initializable, SlashEscrowFactoryStorage, Ownable
8888
}
8989

9090
// Assert that the slash ID is not paused
91-
require(!isBurnOrRedistributionPaused(operatorSet, slashId), IPausable.CurrentlyPaused());
91+
require(!isEscrowPaused(operatorSet, slashId), IPausable.CurrentlyPaused());
9292

9393
// Assert that the escrow delay has elapsed
94-
require(block.number >= getBurnOrRedistributionCompleteBlock(operatorSet, slashId), EscrowDelayNotElapsed());
94+
require(block.number >= getEscrowCompleteBlock(operatorSet, slashId), EscrowDelayNotElapsed());
9595

9696
// Calling `decreaseBurnOrRedistributableShares` will transfer the underlying tokens to the `SlashEscrow`.
9797
// NOTE: While `decreaseBurnOrRedistributableShares` may have already been called, we call it again to ensure that the
@@ -184,7 +184,7 @@ contract SlashEscrowFactory is Initializable, SlashEscrowFactoryStorage, Ownable
184184
address strategy = pendingStrategiesForSlashId.at(i - 1);
185185

186186
// Burn or redistribute the underlying tokens for the strategy.
187-
slashEscrow.burnOrRedistributeUnderlyingTokens(
187+
slashEscrow.releaseTokens(
188188
ISlashEscrowFactory(address(this)),
189189
slashEscrowImplementation,
190190
operatorSet,
@@ -195,7 +195,7 @@ contract SlashEscrowFactory is Initializable, SlashEscrowFactoryStorage, Ownable
195195

196196
// Remove the strategy and underlying amount from the pending burn or redistributions map.
197197
pendingStrategiesForSlashId.remove(strategy);
198-
emit BurnOrRedistributionComplete(operatorSet, slashId, IStrategy(strategy), redistributionRecipient);
198+
emit EscrowComplete(operatorSet, slashId, IStrategy(strategy), redistributionRecipient);
199199
}
200200

201201
// Remove the slash ID from the pending slash IDs set.
@@ -210,12 +210,69 @@ contract SlashEscrowFactory is Initializable, SlashEscrowFactoryStorage, Ownable
210210
}
211211
}
212212

213-
/// @notice Sets the global burn or redistribution delay.
214-
function _setGlobalBurnOrRedistributionDelay(
213+
/// @inheritdoc ISlashEscrowFactory
214+
function deploySlashEscrow(OperatorSet calldata operatorSet, uint256 slashId) public returns (ISlashEscrow) {
215+
ISlashEscrow slashEscrow = getSlashEscrow(operatorSet, slashId);
216+
217+
// If the slash escrow is not deployed...
218+
if (!isDeployedSlashEscrow(slashEscrow)) {
219+
return ISlashEscrow(
220+
address(slashEscrowImplementation).cloneDeterministic(computeSlashEscrowSalt(operatorSet, slashId))
221+
);
222+
}
223+
224+
return slashEscrow;
225+
}
226+
227+
/**
228+
*
229+
* PAUSABLE ACTIONS
230+
*
231+
*/
232+
233+
/// @inheritdoc ISlashEscrowFactory
234+
function pauseEscrow(OperatorSet calldata operatorSet, uint256 slashId) external virtual onlyPauser {
235+
_checkNewPausedStatus(operatorSet, slashId, true);
236+
_paused[operatorSet.key()][slashId] = true;
237+
emit EscrowPaused(operatorSet, slashId);
238+
}
239+
240+
/// @inheritdoc ISlashEscrowFactory
241+
function unpauseEscrow(OperatorSet calldata operatorSet, uint256 slashId) external virtual onlyUnpauser {
242+
_checkNewPausedStatus(operatorSet, slashId, false);
243+
_paused[operatorSet.key()][slashId] = false;
244+
emit EscrowUnPaused(operatorSet, slashId);
245+
}
246+
247+
/**
248+
*
249+
* OWNER ACTIONS
250+
*
251+
*/
252+
253+
/// @inheritdoc ISlashEscrowFactory
254+
function setGlobalEscrowDelay(
255+
uint32 delay
256+
) external onlyOwner {
257+
_setGlobalEscrowDelay(delay);
258+
}
259+
260+
/// @inheritdoc ISlashEscrowFactory
261+
function setStrategyEscrowDelay(IStrategy strategy, uint32 delay) external onlyOwner {
262+
_strategyEscrowDelayBlocks[address(strategy)] = delay;
263+
emit StrategyEscrowDelaySet(strategy, delay);
264+
}
265+
266+
/**
267+
*
268+
* HELPERS
269+
*
270+
*/
271+
function _setGlobalEscrowDelay(
215272
uint32 delay
216273
) internal {
217-
_globalBurnOrRedistributionDelayBlocks = delay;
218-
emit GlobalBurnOrRedistributionDelaySet(delay);
274+
_globalEscrowDelayBlocks = delay;
275+
emit GlobalEscrowDelaySet(delay);
219276
}
220277

221278
/// @notice Checks that the new paused status is not the same as the current paused status.
@@ -300,7 +357,7 @@ contract SlashEscrowFactory is Initializable, SlashEscrowFactoryStorage, Ownable
300357
// For each slashId, get the complete block.
301358
completeBlocks[i] = new uint32[](slashIds[i].length);
302359
for (uint256 j = 0; j < slashIds[i].length; j++) {
303-
completeBlocks[i][j] = getBurnOrRedistributionCompleteBlock(operatorSets[i], slashIds[i][j]);
360+
completeBlocks[i][j] = getEscrowCompleteBlock(operatorSets[i], slashIds[i][j]);
304361
}
305362
}
306363
}
@@ -362,55 +419,46 @@ contract SlashEscrowFactory is Initializable, SlashEscrowFactoryStorage, Ownable
362419
}
363420

364421
/// @inheritdoc ISlashEscrowFactory
365-
function isBurnOrRedistributionPaused(
366-
OperatorSet calldata operatorSet,
367-
uint256 slashId
368-
) public view returns (bool) {
422+
function isEscrowPaused(OperatorSet calldata operatorSet, uint256 slashId) public view returns (bool) {
369423
return _paused[operatorSet.key()][slashId];
370424
}
371425

372426
/// @inheritdoc ISlashEscrowFactory
373-
function getBurnOrRedistributionStartBlock(
374-
OperatorSet memory operatorSet,
375-
uint256 slashId
376-
) public view returns (uint256) {
427+
function getEscrowStartBlock(OperatorSet memory operatorSet, uint256 slashId) public view returns (uint256) {
377428
return _slashIdToStartBlock[operatorSet.key()][slashId];
378429
}
379430

380431
/// @inheritdoc ISlashEscrowFactory
381-
function getBurnOrRedistributionCompleteBlock(
382-
OperatorSet memory operatorSet,
383-
uint256 slashId
384-
) public view returns (uint32) {
432+
function getEscrowCompleteBlock(OperatorSet memory operatorSet, uint256 slashId) public view returns (uint32) {
385433
IStrategy[] memory strategies = getPendingStrategiesForSlashId(operatorSet, slashId);
386434

387435
// Loop through all strategies and return the max delay
388436
uint32 maxStrategyDelay;
389437
for (uint256 i = 0; i < strategies.length; ++i) {
390-
uint32 delay = getStrategyBurnOrRedistributionDelay(IStrategy(address(strategies[i])));
438+
uint32 delay = getStrategyEscrowDelay(IStrategy(address(strategies[i])));
391439
if (delay > maxStrategyDelay) {
392440
maxStrategyDelay = delay;
393441
}
394442
}
395443

396444
// The escrow can be released once the max strategy delay has elapsed.
397-
return uint32(getBurnOrRedistributionStartBlock(operatorSet, slashId) + maxStrategyDelay + 1);
445+
return uint32(getEscrowStartBlock(operatorSet, slashId) + maxStrategyDelay + 1);
398446
}
399447

400448
/// @inheritdoc ISlashEscrowFactory
401-
function getStrategyBurnOrRedistributionDelay(
449+
function getStrategyEscrowDelay(
402450
IStrategy strategy
403451
) public view returns (uint32) {
404-
uint32 globalDelay = _globalBurnOrRedistributionDelayBlocks;
405-
uint32 strategyDelay = _strategyBurnOrRedistributionDelayBlocks[address(strategy)];
452+
uint32 globalDelay = _globalEscrowDelayBlocks;
453+
uint32 strategyDelay = _strategyEscrowDelayBlocks[address(strategy)];
406454

407455
// Return whichever delay is greater.
408456
return strategyDelay > globalDelay ? strategyDelay : globalDelay;
409457
}
410458

411459
/// @inheritdoc ISlashEscrowFactory
412-
function getGlobalBurnOrRedistributionDelay() external view returns (uint32) {
413-
return _globalBurnOrRedistributionDelayBlocks;
460+
function getGlobalEscrowDelay() external view returns (uint32) {
461+
return _globalEscrowDelayBlocks;
414462
}
415463

416464
/// @inheritdoc ISlashEscrowFactory

‎src/contracts/core/SlashEscrowFactoryStorage.sol

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ abstract contract SlashEscrowFactoryStorage is ISlashEscrowFactory {
1515
address internal constant DEFAULT_BURN_ADDRESS = 0x00000000000000000000000000000000000E16E4;
1616

1717
/// @notice The pause status for the `releaseSlashEscrow` function.
18-
/// @dev Allows all burn or redistribution outflows to be temporarily halted.
19-
uint8 public constant PAUSED_BURN_OR_REDISTRIBUTE_SHARES = 0;
18+
/// @dev Allows all escrow outflows to be temporarily halted.
19+
uint8 public constant PAUSED_RELEASE_ESCROW = 0;
2020

2121
// Immutable Storage
2222

@@ -48,11 +48,11 @@ abstract contract SlashEscrowFactoryStorage is ISlashEscrowFactory {
4848
/// @notice Returns the paused status for a given operator set and slash ID.
4949
mapping(bytes32 operatorSetKey => mapping(uint256 slashId => bool paused)) internal _paused;
5050

51-
/// @dev Returns the burn or redistribution delay for a given strategy.
52-
uint32 internal _globalBurnOrRedistributionDelayBlocks;
51+
/// @dev Returns the escrow delay for a given strategy.
52+
uint32 internal _globalEscrowDelayBlocks;
5353

5454
/// @dev Returns the operator set delay for a given strategy.
55-
mapping(address strategy => uint32 delay) internal _strategyBurnOrRedistributionDelayBlocks;
55+
mapping(address strategy => uint32 delay) internal _strategyEscrowDelayBlocks;
5656

5757
// Constructor
5858

‎src/contracts/interfaces/ISlashEscrow.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ interface ISlashEscrow {
1919
/// @param slashId The slash ID that was used to create the slash escrow.
2020
/// @param recipient The recipient of the underlying tokens.
2121
/// @param strategy The strategy that was used to create the slash escrow.
22-
function burnOrRedistributeUnderlyingTokens(
22+
function releaseTokens(
2323
ISlashEscrowFactory slashEscrowFactory,
2424
ISlashEscrow slashEscrowImplementation,
2525
OperatorSet calldata operatorSet,
@@ -32,7 +32,7 @@ interface ISlashEscrow {
3232
/// @dev Validates that the provided parameters deterministically generate this contract's address using CREATE2.
3333
/// - Uses ClonesUpgradeable.predictDeterministicAddress() to compute the expected address from the parameters.
3434
/// - Compares the computed address against this contract's address to validate parameter integrity.
35-
/// - Provides a stateless validation mechanism for burnOrRedistributeUnderlyingTokens() inputs.
35+
/// - Provides a stateless validation mechanism for releaseTokens() inputs.
3636
/// - Security relies on the cryptographic properties of CREATE2 address derivation.
3737
/// - Attack vector would require finding a hash collision in the CREATE2 address computation.
3838
/// @param slashEscrowFactory The factory contract that created the slash escrow.
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Please sign in to comment.