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: ejector allowlist mapping #226

Merged
merged 1 commit into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
36 changes: 19 additions & 17 deletions src/EjectionManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ contract EjectionManager is IEjectionManager, OwnableUpgradeable{
/// @notice the StakeRegistry contract that keeps track of quorum stake
IStakeRegistry public immutable stakeRegistry;

/// @notice Address permissioned to eject operators under a ratelimit
address public ejector;
/// @notice Addresses permissioned to eject operators under a ratelimit
mapping(address => bool) public isEjector;

/// @notice Keeps track of the total stake ejected for a quorum
mapping(uint8 => StakeEjection[]) public stakeEjectedForQuorum;
Expand All @@ -40,17 +40,18 @@ contract EjectionManager is IEjectionManager, OwnableUpgradeable{

/**
* @param _owner will hold the owner role
* @param _ejector will hold the ejector role
* @param _ejectors will hold the ejector role
* @param _quorumEjectionParams are the ratelimit parameters for the quorum at each index
*/
function initialize(
address _owner,
address _ejector,
address[] memory _ejectors,
QuorumEjectionParams[] memory _quorumEjectionParams
) external initializer {
_transferOwnership(_owner);
_setEjector(_ejector);

for(uint8 i = 0; i < _ejectors.length; i++) {
_setEjector(_ejectors[i], true);
}
for(uint8 i = 0; i < _quorumEjectionParams.length; i++) {
_setQuorumEjectionParams(i, _quorumEjectionParams[i]);
}
Expand All @@ -63,7 +64,7 @@ contract EjectionManager is IEjectionManager, OwnableUpgradeable{
* @dev The owner can eject operators without recording of stake ejection
*/
function ejectOperators(bytes32[][] memory _operatorIds) external {
require(msg.sender == ejector || msg.sender == owner(), "Ejector: Only owner or ejector can eject");
require(isEjector[msg.sender] || msg.sender == owner(), "Ejector: Only owner or ejector can eject");

for(uint i = 0; i < _operatorIds.length; ++i) {
uint8 quorumNumber = uint8(i);
Expand All @@ -77,7 +78,7 @@ contract EjectionManager is IEjectionManager, OwnableUpgradeable{

//if caller is ejector enforce ratelimit
if(
msg.sender == ejector &&
isEjector[msg.sender] &&
quorumEjectionParams[quorumNumber].rateLimitWindow > 0 &&
stakeForEjection + operatorStake > amountEjectable
){
Expand All @@ -88,7 +89,7 @@ contract EjectionManager is IEjectionManager, OwnableUpgradeable{
broke = true;
break;
}

//try-catch used to prevent race condition of operator deregistering before ejection
try registryCoordinator.ejectOperator(
registryCoordinator.getOperatorFromId(_operatorIds[i][j]),
Expand All @@ -102,7 +103,7 @@ contract EjectionManager is IEjectionManager, OwnableUpgradeable{
}

//record the stake ejected if ejector and ratelimit enforced
if(!broke && msg.sender == ejector){
if(!broke && isEjector[msg.sender]){
stakeEjectedForQuorum[quorumNumber].push(StakeEjection({
timestamp: block.timestamp,
stakeEjected: stakeForEjection
Expand All @@ -124,9 +125,10 @@ contract EjectionManager is IEjectionManager, OwnableUpgradeable{
/**
* @notice Sets the address permissioned to eject operators under a ratelimit
* @param _ejector The address to permission
* @param _status The status to set for the given address
*/
function setEjector(address _ejector) external onlyOwner() {
_setEjector(_ejector);
function setEjector(address _ejector, bool _status) external onlyOwner() {
_setEjector(_ejector, _status);
}

///@dev internal function to set the quorum ejection params
Expand All @@ -136,9 +138,9 @@ contract EjectionManager is IEjectionManager, OwnableUpgradeable{
}

///@dev internal function to set the ejector
function _setEjector(address _ejector) internal {
emit EjectorUpdated(ejector, _ejector);
ejector = _ejector;
function _setEjector(address _ejector, bool _status) internal {
isEjector[_ejector] = _status;
emit EjectorUpdated(_ejector, _status);
}

/**
Expand All @@ -154,7 +156,7 @@ contract EjectionManager is IEjectionManager, OwnableUpgradeable{
return totalEjectable;
}
i = stakeEjectedForQuorum[_quorumNumber].length - 1;

while(stakeEjectedForQuorum[_quorumNumber][i].timestamp > cutoffTime) {
totalEjected += stakeEjectedForQuorum[_quorumNumber][i].stakeEjected;
if(i == 0){
Expand All @@ -169,4 +171,4 @@ contract EjectionManager is IEjectionManager, OwnableUpgradeable{
}
return totalEjectable - totalEjected;
}
}
}
8 changes: 4 additions & 4 deletions src/interfaces/IEjectionManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ interface IEjectionManager {
}

///@notice Emitted when the ejector address is set
event EjectorUpdated(address previousAddress, address newAddress);
event EjectorUpdated(address ejector, bool status);
///@notice Emitted when the ratelimit parameters for a quorum are set
event QuorumEjectionParamsSet(uint8 quorumNumber, uint32 rateLimitWindow, uint16 ejectableStakePercent);
///@notice Emitted when an operator is ejected
event OperatorEjected(bytes32 operatorId, uint8 quorumNumber);
///@notice Emitted when an operator ejection fails
event FailedOperatorEjection(bytes32 operatorId, uint8 quorumNumber, bytes err);

/**
* @notice Ejects operators from the AVSs registryCoordinator under a ratelimit
* @param _operatorIds The ids of the operators to eject for each quorum
Expand All @@ -45,11 +45,11 @@ interface IEjectionManager {
* @notice Sets the address permissioned to eject operators under a ratelimit
* @param _ejector The address to permission
*/
function setEjector(address _ejector) external;
function setEjector(address _ejector, bool _status) 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);
}
}
17 changes: 10 additions & 7 deletions test/unit/EjectionManagerUnit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import "../utils/MockAVSDeployer.sol";

contract EjectionManagerUnitTests is MockAVSDeployer {

event EjectorUpdated(address previousAddress, address newAddress);
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);
Expand Down Expand Up @@ -42,14 +42,17 @@ contract EjectionManagerUnitTests is MockAVSDeployer {

ejectionManagerImplementation = new EjectionManager(registryCoordinator, stakeRegistry);

address[] memory ejectors = new address[](1);
ejectors[0] = ejector;

cheats.prank(proxyAdminOwner);
proxyAdmin.upgradeAndCall(
TransparentUpgradeableProxy(payable(address(ejectionManager))),
address(ejectionManagerImplementation),
abi.encodeWithSelector(
EjectionManager.initialize.selector,
registryCoordinatorOwner,
ejector,
ejectors,
quorumEjectionParams
)
);
Expand Down Expand Up @@ -82,7 +85,7 @@ contract EjectionManagerUnitTests is MockAVSDeployer {
emit OperatorEjected(operatorIds[i][j], i);
}
}

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

Expand Down Expand Up @@ -352,12 +355,12 @@ contract EjectionManagerUnitTests is MockAVSDeployer {

function testSetEjector() public {
cheats.expectEmit(true, true, true, true, address(ejectionManager));
emit EjectorUpdated(ejector, address(0));
emit EjectorUpdated(address(0), true);

cheats.prank(registryCoordinatorOwner);
ejectionManager.setEjector(address(0));
ejectionManager.setEjector(address(0), true);

assertEq(ejectionManager.ejector(), address(0));
assertEq(ejectionManager.isEjector(address(0)), true);
}

function test_Revert_NotPermissioned() public {
Expand All @@ -370,7 +373,7 @@ contract EjectionManagerUnitTests is MockAVSDeployer {
ejectionManager.setQuorumEjectionParams(0, _quorumEjectionParams);

cheats.expectRevert("Ownable: caller is not the owner");
ejectionManager.setEjector(address(0));
ejectionManager.setEjector(address(0), true);
}

function _registerOperaters(uint8 numOperators, uint96 stake) internal {
Expand Down
Loading