Skip to content

Commit cb4c2cf

Browse files
committed
fix: proper ejection stake calculation
1 parent 82d91bf commit cb4c2cf

File tree

2 files changed

+87
-4
lines changed

2 files changed

+87
-4
lines changed

src/EjectionManager.sol

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ import {
88
ISlashingRegistryCoordinator,
99
IStakeRegistry
1010
} from "./EjectionManagerStorage.sol";
11-
11+
import {console} from "forge-std/console.sol";
1212
/**
1313
* @title Used for automated ejection of operators from the SlashingRegistryCoordinator under a ratelimit
1414
* @author Layr Labs, Inc.
1515
*/
16+
1617
contract EjectionManager is OwnableUpgradeable, EjectionManagerStorage {
1718
constructor(
1819
ISlashingRegistryCoordinator _slashingRegistryCoordinator,
@@ -61,12 +62,15 @@ contract EjectionManager is OwnableUpgradeable, EjectionManagerStorage {
6162
&& stakeForEjection + operatorStake > amountEjectable
6263
) {
6364
ratelimitHit = true;
64-
65+
console.log("ratelimitHit", ratelimitHit);
6566
break;
6667
}
6768

6869
stakeForEjection += operatorStake;
6970
++ejectedOperators;
71+
// Update amount ejectable after each ejection to ensure consistent behavior between single and multiple ejections
72+
amountEjectable = amountEjectableForQuorum(quorumNumber);
73+
console.log("amountEjectable", amountEjectable);
7074

7175
slashingRegistryCoordinator.ejectOperator(
7276
slashingRegistryCoordinator.getOperatorFromId(operatorIds[i][j]),

test/unit/EjectionManagerUnit.t.sol

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,91 @@ contract EjectionManagerUnitTests is MockAVSDeployer {
111111
);
112112
}
113113

114-
function testEjectOperators_MultipleOperatorInsideRatelimit() public {
114+
/// @dev Tests that ejecting multiple operators in one call h
115+
function testEjectOperators_singleCall() public {
115116
uint8 operatorsToEject = 10;
116117
uint8 numOperators = 100;
117118
uint96 stake = 1 ether;
118119
_registerOperators(numOperators, stake);
119120

121+
// Get quroums to deregister
122+
bytes32[][] memory operatorIds = new bytes32[][](numQuorums);
123+
for (uint8 i = 0; i < numQuorums; i++) {
124+
operatorIds[i] = new bytes32[](operatorsToEject);
125+
for (uint256 j = 0; j < operatorsToEject; j++) {
126+
operatorIds[i][j] =
127+
registryCoordinator.getOperatorId(_incrementAddress(defaultOperator, j));
128+
}
129+
}
130+
131+
cheats.prank(ejector);
132+
ejectionManager.ejectOperators(operatorIds);
133+
134+
for (uint8 i = 0; i < operatorsToEject - 1; i++) {
135+
assertEq(
136+
uint8(registryCoordinator.getOperatorStatus(_incrementAddress(defaultOperator, i))),
137+
uint8(ISlashingRegistryCoordinatorTypes.OperatorStatus.DEREGISTERED)
138+
);
139+
}
140+
141+
// The 10th operator should not be ejected
142+
assertEq(
143+
uint8(
144+
registryCoordinator.getOperatorStatus(
145+
_incrementAddress(defaultOperator, operatorsToEject - 1)
146+
)
147+
),
148+
uint8(ISlashingRegistryCoordinatorTypes.OperatorStatus.REGISTERED)
149+
);
150+
}
151+
152+
/// @dev Same test as above, but with multiple calls, end state should be the same
153+
function testEjectOperators_multipleCalls() public {
154+
uint8 operatorsToEject = 10;
155+
uint8 numOperators = 100;
156+
uint96 stake = 1 ether;
157+
_registerOperators(numOperators, stake);
158+
159+
for (uint8 i = 0; i < operatorsToEject; i++) {
160+
// Setup operatorIds for next call
161+
uint8 operatorsToEjectPerCall = 1;
162+
bytes32[][] memory operatorIds = new bytes32[][](numQuorums);
163+
for (uint8 j = 0; j < numQuorums; j++) {
164+
operatorIds[j] = new bytes32[](operatorsToEjectPerCall);
165+
for (uint256 k = 0; k < operatorsToEjectPerCall; k++) {
166+
operatorIds[j][k] =
167+
registryCoordinator.getOperatorId(_incrementAddress(defaultOperator, i));
168+
}
169+
}
170+
171+
cheats.prank(ejector);
172+
ejectionManager.ejectOperators(operatorIds);
173+
}
174+
175+
for (uint8 i = 0; i < operatorsToEject - 1; i++) {
176+
assertEq(
177+
uint8(registryCoordinator.getOperatorStatus(_incrementAddress(defaultOperator, i))),
178+
uint8(ISlashingRegistryCoordinatorTypes.OperatorStatus.DEREGISTERED)
179+
);
180+
}
181+
182+
// The 10th operator should not be ejected
183+
assertEq(
184+
uint8(
185+
registryCoordinator.getOperatorStatus(
186+
_incrementAddress(defaultOperator, operatorsToEject - 1)
187+
)
188+
),
189+
uint8(ISlashingRegistryCoordinatorTypes.OperatorStatus.REGISTERED)
190+
);
191+
}
192+
193+
function testEjectOperators_MultipleOperatorInsideRatelimit() public {
194+
uint8 operatorsToEject = 9;
195+
uint8 numOperators = 100;
196+
uint96 stake = 1 ether;
197+
_registerOperators(numOperators, stake);
198+
120199
bytes32[][] memory operatorIds = new bytes32[][](numQuorums);
121200
for (uint8 i = 0; i < numQuorums; i++) {
122201
operatorIds[i] = new bytes32[](operatorsToEject);
@@ -388,7 +467,7 @@ contract EjectionManagerUnitTests is MockAVSDeployer {
388467
}
389468

390469
function testEjectOperators_MultipleOperatorAfterRatelimitReset() public {
391-
uint8 operatorsToEject = 10;
470+
uint8 operatorsToEject = 9;
392471
uint8 numOperators = 100;
393472
uint96 stake = 1 ether;
394473

0 commit comments

Comments
 (0)