Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ejection policy change #313

Merged
merged 2 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 42 additions & 7 deletions src/EjectionManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ contract EjectionManager is IEjectionManager, OwnableUpgradeable{
/// @notice Ratelimit parameters for each quorum
mapping(uint8 => QuorumEjectionParams) public quorumEjectionParams;

/// @notice mapping from quorum number to operator stake cap percentage
mapping(uint8 => uint256) public operatorStakeCapPercent;

constructor(
IRegistryCoordinator _registryCoordinator,
IStakeRegistry _stakeRegistry
Expand All @@ -46,7 +49,8 @@ contract EjectionManager is IEjectionManager, OwnableUpgradeable{
function initialize(
address _owner,
address[] memory _ejectors,
QuorumEjectionParams[] memory _quorumEjectionParams
QuorumEjectionParams[] memory _quorumEjectionParams,
uint256[] memory _operatorStakeCapPercent
) external initializer {
_transferOwnership(_owner);
for(uint8 i = 0; i < _ejectors.length; i++) {
Expand All @@ -55,6 +59,9 @@ contract EjectionManager is IEjectionManager, OwnableUpgradeable{
for(uint8 i = 0; i < _quorumEjectionParams.length; i++) {
_setQuorumEjectionParams(i, _quorumEjectionParams[i]);
}
for(uint8 i = 0; i < _operatorStakeCapPercent.length; i++) {
_setOperatorStakeCapPercent(i, _operatorStakeCapPercent[i]);
}
}

/**
Expand All @@ -69,14 +76,25 @@ contract EjectionManager is IEjectionManager, OwnableUpgradeable{
for(uint i = 0; i < _operatorIds.length; ++i) {
uint8 quorumNumber = uint8(i);

uint256 amountEjectable = amountEjectableForQuorum(quorumNumber);
(uint256 amountEjectable, uint256 totalQuorumStake) = amountEjectableForQuorum(quorumNumber);

uint256 operatorStakeCap = type(uint256).max;
if(operatorStakeCapPercent[quorumNumber] > 0) {
operatorStakeCap = operatorStakeCapPercent[quorumNumber] * totalQuorumStake / uint256(BIPS_DENOMINATOR);
}

uint256 stakeForEjection;
uint32 ejectedOperators;

bool ratelimitHit;
for(uint8 j = 0; j < _operatorIds[i].length; ++j) {
uint256 operatorStake = stakeRegistry.getCurrentStake(_operatorIds[i][j], quorumNumber);

if(operatorStake > operatorStakeCap) {
emit OperatorStakeCapHit(_operatorIds[i][j], operatorStake, operatorStakeCap);
operatorStake = operatorStakeCap;
}

//if caller is ejector enforce ratelimit
if(
isEjector[msg.sender] &&
Expand Down Expand Up @@ -132,6 +150,15 @@ contract EjectionManager is IEjectionManager, OwnableUpgradeable{
_setEjector(_ejector, _status);
}

/**
* @notice Sets the operator stake cap percent for a quorum
* @param _quorumNumber The quorum number to set the operator stake cap percent for
* @param _operatorStakeCapPercent The operator stake cap percent to set for the given quorum
*/
function setOperatorStakeCapPercent(uint8 _quorumNumber, uint256 _operatorStakeCapPercent) external onlyOwner() {
_setOperatorStakeCapPercent(_quorumNumber, _operatorStakeCapPercent);
}

///@dev internal function to set the quorum ejection params
function _setQuorumEjectionParams(uint8 _quorumNumber, QuorumEjectionParams memory _quorumEjectionParams) internal {
quorumEjectionParams[_quorumNumber] = _quorumEjectionParams;
Expand All @@ -144,17 +171,25 @@ contract EjectionManager is IEjectionManager, OwnableUpgradeable{
emit EjectorUpdated(_ejector, _status);
}

/// @dev internal function to set the operator stake cap percent
function _setOperatorStakeCapPercent(uint8 _quorumNumber, uint256 _operatorStakeCapPercent) internal {
operatorStakeCapPercent[_quorumNumber] = _operatorStakeCapPercent;
emit OperatorStakeCapPercentSet(_quorumNumber, _operatorStakeCapPercent);
}

/**
* @notice Returns the amount of stake that can be ejected for a quorum at the current block.timestamp
* @param _quorumNumber The quorum number to view ejectable stake for
*/
function amountEjectableForQuorum(uint8 _quorumNumber) public view returns (uint256) {
function amountEjectableForQuorum(uint8 _quorumNumber) public view returns (uint256 ejectableStake, uint256 totalStake) {
stevennevins marked this conversation as resolved.
Show resolved Hide resolved
uint256 cutoffTime = block.timestamp - quorumEjectionParams[_quorumNumber].rateLimitWindow;
uint256 totalEjectable = uint256(quorumEjectionParams[_quorumNumber].ejectableStakePercent) * uint256(stakeRegistry.getCurrentTotalStake(_quorumNumber)) / uint256(BIPS_DENOMINATOR);
totalStake = uint256(stakeRegistry.getCurrentTotalStake(_quorumNumber));
uint256 totalEjectable = uint256(quorumEjectionParams[_quorumNumber].ejectableStakePercent) * totalStake / uint256(BIPS_DENOMINATOR);

uint256 totalEjected;
uint256 i;
if (stakeEjectedForQuorum[_quorumNumber].length == 0) {
return totalEjectable;
return (totalEjectable, totalStake);
}
i = stakeEjectedForQuorum[_quorumNumber].length - 1;

Expand All @@ -168,8 +203,8 @@ contract EjectionManager is IEjectionManager, OwnableUpgradeable{
}

if(totalEjected >= totalEjectable){
return 0;
return (0, totalStake);
}
return totalEjectable - totalEjected;
return (totalEjectable - totalEjected, totalStake);
}
}
13 changes: 12 additions & 1 deletion src/interfaces/IEjectionManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ interface IEjectionManager {
event OperatorEjected(bytes32 operatorId, uint8 quorumNumber);
///@notice Emitted when operators are ejected for a quroum
event QuorumEjection(uint32 ejectedOperators, bool ratelimitHit);
///@notice Emitted when the operator stake cap percent for a quorum is set
event OperatorStakeCapPercentSet(uint8 quorumNumber, uint256 operatorStakeCapPercent);
///@notice Emitted when the operator stake cap is hit
event OperatorStakeCapHit(bytes32 operatorId, uint256 operatorStake, uint256 operatorStakeCap);

/**
* @notice Ejects operators from the AVSs registryCoordinator under a ratelimit
Expand All @@ -47,9 +51,16 @@ interface IEjectionManager {
*/
function setEjector(address _ejector, bool _status) external;

/**
* @notice Sets the operator stake cap percent for a quorum
* @param _quorumNumber The quorum number to set the operator stake cap percent for
* @param _operatorStakeCapPercent The operator stake cap percent to set for the given quorum
*/
function setOperatorStakeCapPercent(uint8 _quorumNumber, uint256 _operatorStakeCapPercent) external;

/**
* @notice Returns the amount of stake that can be ejected for a quorum at the current block.timestamp
* @param _quorumNumber The quorum number to view ejectable stake for
*/
function amountEjectableForQuorum(uint8 _quorumNumber) external view returns (uint256);
function amountEjectableForQuorum(uint8 _quorumNumber) external view returns (uint256 ejectableStake, uint256 totalStake);
}
35 changes: 31 additions & 4 deletions test/unit/EjectionManagerUnit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,25 @@ contract EjectionManagerUnitTests is MockAVSDeployer {
event EjectorUpdated(address ejector, bool status);
event QuorumEjectionParamsSet(uint8 quorumNumber, uint32 rateLimitWindow, uint16 ejectableStakePercent);
event OperatorEjected(bytes32 operatorId, uint8 quorumNumber);
event FailedOperatorEjection(bytes32 operatorId, uint8 quorumNumber, bytes err);
event OperatorStakeCapHit(bytes32 operatorId, uint256 operatorStake, uint256 operatorStakeCap);

EjectionManager public ejectionManager;
IEjectionManager public ejectionManagerImplementation;

IEjectionManager.QuorumEjectionParams[] public quorumEjectionParams;
uint256[] public operatorStakeCapPercents;

uint32 public ratelimitWindow = 1 days;
uint16 public ejectableStakePercent = 1000;
uint16 public ejectableStakePercent = 3300;
uint256 public operatorStakeCapPercent = 2000;

function setUp() virtual public {
for(uint8 i = 0; i < numQuorums; i++) {
quorumEjectionParams.push(IEjectionManager.QuorumEjectionParams({
rateLimitWindow: ratelimitWindow,
ejectableStakePercent: ejectableStakePercent
}));
operatorStakeCapPercents.push(operatorStakeCapPercent);
}

defaultMaxOperatorCount = 200;
Expand All @@ -54,7 +57,8 @@ contract EjectionManagerUnitTests is MockAVSDeployer {
EjectionManager.initialize.selector,
registryCoordinatorOwner,
ejectors,
quorumEjectionParams
quorumEjectionParams,
operatorStakeCapPercents
)
);

Expand Down Expand Up @@ -127,7 +131,7 @@ contract EjectionManagerUnitTests is MockAVSDeployer {
}

function testEjectOperators_MultipleOperatorOutsideRatelimit() public {
uint8 operatorsCanEject = 1;
uint8 operatorsCanEject = 3;
uint8 operatorsToEject = 10;
uint8 numOperators = 10;
uint96 stake = 1 ether;
Expand Down Expand Up @@ -389,6 +393,29 @@ contract EjectionManagerUnitTests is MockAVSDeployer {
ejectionManager.amountEjectableForQuorum(1);
}

function testOperatorStakeCapHit() public {
_registerOperaters(100, 1 ether);
BN254.G1Point memory pubKey = BN254.hashToG1(keccak256(abi.encodePacked(uint256(420))));
address operator = _incrementAddress(defaultOperator, 420);
_registerOperatorWithCoordinator(operator, MAX_QUORUM_BITMAP, pubKey, 100 ether);

bytes32[][] memory operatorIds = new bytes32[][](numQuorums);
for (uint8 i = 0; i < numQuorums; i++) {
operatorIds[i] = new bytes32[](1);
operatorIds[i][0] = registryCoordinator.getOperatorId(operator);
}

for(uint8 i = 0; i < numQuorums; i++) {
cheats.expectEmit(true, true, true, true, address(ejectionManager));
emit OperatorStakeCapHit(operatorIds[i][0], 100 ether, 40 ether); //total stake 200 ether at 20% stake cap is 40 ether
cheats.expectEmit(true, true, true, true, address(ejectionManager));
emit OperatorEjected(operatorIds[i][0], i);
}

cheats.prank(ejector);
ejectionManager.ejectOperators(operatorIds);
}

function _registerOperaters(uint8 numOperators, uint96 stake) internal {
for (uint i = 0; i < numOperators; i++) {
BN254.G1Point memory pubKey = BN254.hashToG1(keccak256(abi.encodePacked(i)));
Expand Down
Loading