Skip to content

Commit c572ec5

Browse files
committed
feat: instant alloc delay from dm (#1646)
**Motivation:** Newly created operators in core have to wait 17.5 days to begin allocating. This is a UX painpoint for new AVSs to onboard their operators. **Modifications:** Update the `AllocationManager` so that the allocation delay for a newly created operator is active immediately block. This allows operators to make an allocation at the very next block. **Result:** Easier onboarding
1 parent 57c37ae commit c572ec5

File tree

8 files changed

+78
-102
lines changed

8 files changed

+78
-102
lines changed

docs/core/AllocationManager.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,9 @@ Once slashing is processed for a strategy, [slashed stake is burned or redistrib
837837
* allocating magnitude to an operator set, and the magnitude becoming slashable.
838838
* @param operator The operator to set the delay on behalf of.
839839
* @param delay the allocation delay in blocks
840+
* @dev When the delay is set for a newly-registered operator (via the `DelegationManager.registerAsOperator` method),
841+
* the delay will take effect immediately, allowing for operators to allocate slashable stake immediately.
842+
* Else, the delay will take effect after `ALLOCATION_CONFIGURATION_DELAY` blocks.
840843
*/
841844
function setAllocationDelay(
842845
address operator,
@@ -847,15 +850,16 @@ function setAllocationDelay(
847850

848851
_Note: IF NOT CALLED BY THE `DelegationManager`, this method can be called directly by an operator, or by a caller authorized by the operator. See [`PermissionController.md`](../permissions/PermissionController.md) for details._
849852

850-
This function sets an operator's allocation delay, in blocks. This delay can be updated by the operator once set. Both the initial setting of this value and any further updates _take `ALLOCATION_CONFIGURATION_DELAY` blocks_ to take effect. Because having a delay is a requirement to allocating slashable stake, this effectively means that once the slashing release goes live, no one will be able to allocate slashable stake for at least `ALLOCATION_CONFIGURATION_DELAY` blocks.
853+
This function sets an operator's allocation delay, in blocks. This delay can be updated by the operator once set. The initial setting of this value by a newly created operator via [`DelegationManager.registerAsOperator`](./DelegationManager.md#registerasoperator) will take 1 block to take effect. The setting of this value for an operator who has already been registered in core or further updates _take `ALLOCATION_CONFIGURATION_DELAY` blocks_ to take effect. Because having a delay is a requirement to allocating slashable stake, this effectively means that once the slashing release goes live, no already-created operators will be able to allocate slashable stake for at least `ALLOCATION_CONFIGURATION_DELAY` blocks.
851854

852855
The `DelegationManager` calls this upon operator registration for all new operators created after the slashing release. For operators that existed in the `DelegationManager` _prior_ to the slashing release, **they will need to call this method to configure an allocation delay prior to allocating slashable stake to any AVS**.
853856

854857
The allocation delay's primary purpose is to give stakers delegated to an operator the chance to withdraw their stake before the operator can change the risk profile to something they're not comfortable with. However, operators can choose to configure this delay however they want - including setting it to 0.
855858

856859
*Effects*:
857860
* Sets the operator's `pendingDelay` to the proposed `delay`, and save the `effectBlock` at which the `pendingDelay` can be activated
858-
* `effectBlock = uint32(block.number) + ALLOCATION_CONFIGURATION_DELAY + 1`
861+
* If a newly registered operator in core, `effectBlock = uint32(block.number)`, allowing operators to allocate slashable stake immediately after registration
862+
* Else, `effectBlock = uint32(block.number) + ALLOCATION_CONFIGURATION_DELAY + 1`
859863
* If the operator has a `pendingDelay`, and if the `effectBlock` has passed, sets the operator's `delay` to the `pendingDelay` value
860864
* This also sets the `isSet` boolean to `true` to indicate that the operator's `delay`, even if 0, was set intentionally
861865
* Emits an `AllocationDelaySet` event

docs/core/DelegationManager.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ function registerAsOperator(
9696

9797
Registers the caller as an operator in EigenLayer. The new operator provides the following input parameters:
9898
* `address initDelegationApprover`: *(OPTIONAL)* if set to a non-zero address, this address must sign and approve new delegation from stakers to this operator (See [`delegateTo`](#delegateto))
99-
* `uint32 allocationDelay`: the delay (in blocks) before slashable stake allocations will take effect. This is passed to the `AllocationManager` (See [`AllocationManager.md#setAllocationDelay`](./AllocationManager.md#setallocationdelay))
99+
* `uint32 allocationDelay`: the delay (in blocks) before slashable stake allocations will take effect. This is passed to the `AllocationManager` (See [`AllocationManager.md#setAllocationDelay`](./AllocationManager.md#setallocationdelay)). Upon registration, this allocation delay is effective immediately. Further modifications directly via `AllocationManager.setAllocationDelay` take `ALLOCATION_CONFIGURATION_DELAY` blocks.
100100
* `string calldata metadataURI`: emits this input in the event `OperatorMetadataURIUpdated`. Does not store the value anywhere.
101101

102102
`registerAsOperator` cements the operator's delegation approver and allocation delay in storage, and self-delegates the operator to themselves - permanently marking the caller as an operator. They cannot "deregister" as an operator - however, if they have deposited funds, they can still withdraw them (See [Delegation and Withdrawals](#delegation-and-withdrawals)).

src/contracts/core/AllocationManager.sol

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,11 +237,17 @@ contract AllocationManager is
237237

238238
/// @inheritdoc IAllocationManager
239239
function setAllocationDelay(address operator, uint32 delay) external {
240-
if (msg.sender != address(delegation)) {
240+
/// If the caller is the delegationManager, the operator is newly registered
241+
/// This results in *newly-registered* operators in the core protocol to have their allocation delay effective immediately
242+
bool newlyRegistered = (msg.sender == address(delegation));
243+
244+
// If we're not newly registered, check that the caller (not the delegationManager) is authorized to set the allocation delay for the operator
245+
if (!newlyRegistered) {
241246
require(_checkCanCall(operator), InvalidCaller());
242247
require(delegation.isOperator(operator), InvalidOperator());
243248
}
244-
_setAllocationDelay(operator, delay);
249+
250+
_setAllocationDelay(operator, delay, newlyRegistered);
245251
}
246252

247253
/// @inheritdoc IAllocationManager
@@ -503,8 +509,9 @@ contract AllocationManager is
503509
* allocating magnitude to an operator set, and the magnitude becoming slashable.
504510
* @param operator The operator to set the delay on behalf of.
505511
* @param delay The allocation delay in blocks.
512+
* @param newlyRegistered Whether the operator is newly registered in the core protocol.
506513
*/
507-
function _setAllocationDelay(address operator, uint32 delay) internal {
514+
function _setAllocationDelay(address operator, uint32 delay, bool newlyRegistered) internal {
508515
AllocationDelayInfo memory info = _allocationDelayInfo[operator];
509516

510517
// If there is a pending delay that can be applied now, set it
@@ -514,7 +521,16 @@ contract AllocationManager is
514521
}
515522

516523
info.pendingDelay = delay;
517-
info.effectBlock = uint32(block.number) + ALLOCATION_CONFIGURATION_DELAY + 1;
524+
525+
/// If the caller is the delegationManager, the operator is newly registered
526+
/// This results in *newly-registered* operators in the core protocol to have their allocation delay effective immediately
527+
if (newlyRegistered) {
528+
// The delay takes effect immediately
529+
info.effectBlock = uint32(block.number);
530+
} else {
531+
// Wait the entire configuration delay before the delay takes effect
532+
info.effectBlock = uint32(block.number) + ALLOCATION_CONFIGURATION_DELAY + 1;
533+
}
518534

519535
_allocationDelayInfo[operator] = info;
520536
emit AllocationDelaySet(operator, delay, info.effectBlock);

src/contracts/interfaces/IAllocationManager.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,9 @@ interface IAllocationManager is IAllocationManagerErrors, IAllocationManagerEven
315315
* allocating magnitude to an operator set, and the magnitude becoming slashable.
316316
* @param operator The operator to set the delay on behalf of.
317317
* @param delay the allocation delay in blocks
318+
* @dev When the delay is set for a newly-registered operator (via the `DelegationManager.registerAsOperator` method),
319+
* the delay will take effect immediately, allowing for operators to allocate slashable stake immediately.
320+
* Else, the delay will take effect after `ALLOCATION_CONFIGURATION_DELAY` blocks.
318321
*/
319322
function setAllocationDelay(address operator, uint32 delay) external;
320323

src/test/integration/IntegrationBase.t.sol

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,6 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
8686
/// Registration flow is the same pre and post redistribution upgrade
8787
operator.registerAsOperator();
8888

89-
rollForward({blocks: ALLOCATION_CONFIGURATION_DELAY + 1});
90-
9189
assert_Snap_Added_OperatorShares(operator, strategies, addedShares, "_newRandomOperator: failed to award shares to operator");
9290
assertTrue(delegationManager.isOperator(address(operator)), "_newRandomOperator: operator should be registered");
9391
assertEq(delegationManager.delegatedTo(address(operator)), address(operator), "_newRandomOperator: should be self-delegated");
@@ -101,8 +99,6 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
10199
/// Registration flow is the same pre and post redistribution upgrade
102100
operator.registerAsOperator();
103101

104-
rollForward({blocks: ALLOCATION_CONFIGURATION_DELAY + 1});
105-
106102
assertTrue(delegationManager.isOperator(address(operator)), "_newRandomOperator: operator should be registered");
107103
assertEq(delegationManager.delegatedTo(address(operator)), address(operator), "_newRandomOperator: should be self-delegated");
108104
return operator;

src/test/integration/tests/upgrade/Redistribution.t.sol

Lines changed: 0 additions & 89 deletions
This file was deleted.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.27;
3+
4+
import "src/test/integration/UpgradeTest.t.sol";
5+
6+
contract Integration_Upgrade_ZeroRegistrationDelay is UpgradeTest {
7+
User operator;
8+
User operator2;
9+
10+
AVS avs;
11+
IStrategy[] strategies;
12+
OperatorSet operatorSet;
13+
14+
function _init() internal override {
15+
(, strategies,) = _newRandomStaker();
16+
(avs,) = _newRandomAVS();
17+
operatorSet = avs.createOperatorSet(strategies);
18+
}
19+
20+
function testFuzz_register_upgrade_registerNew(uint24 r) public rand(r) {
21+
// 1. Register operator
22+
operator = _newRandomOperator_NoAssets();
23+
24+
(bool isSetOperator1,) = allocationManager.getAllocationDelay(address(operator));
25+
// The delay should not be set since we adjusted `_newRandomOperator` to roll forward only 1 block after registration
26+
assertFalse(isSetOperator1, "isSet should be false");
27+
28+
// 2. Upgrade contracts
29+
_upgradeEigenLayerContracts();
30+
31+
// 3. Register new operator
32+
operator2 = _newRandomOperator_NoAssets();
33+
(bool isSetOperator2,) = allocationManager.getAllocationDelay(address(operator2));
34+
35+
// The delay should be set immediately
36+
assertTrue(isSetOperator2, "isSet should be true");
37+
38+
// The delay for operator1 should still not be set
39+
(isSetOperator1,) = allocationManager.getAllocationDelay(address(operator));
40+
assertFalse(isSetOperator1, "isSet should be false");
41+
42+
// 4. Assert that operator2 is set after the configuration delay
43+
rollForward({blocks: ALLOCATION_CONFIGURATION_DELAY + 1});
44+
(isSetOperator1,) = allocationManager.getAllocationDelay(address(operator));
45+
assertTrue(isSetOperator1, "isSet should be true");
46+
}
47+
}

src/test/unit/AllocationManagerUnit.t.sol

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3388,8 +3388,7 @@ contract AllocationManagerUnitTests_SetAllocationDelay is AllocationManagerUnitT
33883388
cheats.prank(address(delegationManagerMock));
33893389
allocationManager.setAllocationDelay(operatorToSet, delay);
33903390

3391-
// Warp to effect block
3392-
cheats.roll(block.number + ALLOCATION_CONFIGURATION_DELAY + 1);
3391+
// The allocation delay is set immediately
33933392
(bool isSet, uint32 returnedDelay) = allocationManager.getAllocationDelay(operatorToSet);
33943393
assertTrue(isSet, "isSet should be set");
33953394
assertEq(delay, returnedDelay, "delay not set");

0 commit comments

Comments
 (0)