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(op sets): update stakes when forceUnregister #300

Merged
Merged
Show file tree
Hide file tree
Changes from 4 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
28 changes: 24 additions & 4 deletions src/StakeRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.12;

import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol";
import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol";

import {StakeRegistryStorage, IStrategy} from "./StakeRegistryStorage.sol";

Expand Down Expand Up @@ -40,8 +41,9 @@ contract StakeRegistry is StakeRegistryStorage {

constructor(
IRegistryCoordinator _registryCoordinator,
IDelegationManager _delegationManager
) StakeRegistryStorage(_registryCoordinator, _delegationManager) {}
IDelegationManager _delegationManager,
IAVSDirectory _avsDirectory
) StakeRegistryStorage(_registryCoordinator, _delegationManager, _avsDirectory) {}

/*******************************************************************************
EXTERNAL FUNCTIONS - REGISTRY COORDINATOR
Expand Down Expand Up @@ -148,6 +150,12 @@ contract StakeRegistry is StakeRegistryStorage {
) external onlyRegistryCoordinator returns (uint192) {
uint192 quorumsToRemove;

address avs = address(
ypatil12 marked this conversation as resolved.
Show resolved Hide resolved
IRegistryCoordinator(registryCoordinator).serviceManager());
bool isOperatorSetAVS = avsDirectory.isOperatorSetAVS(
address(avs)
);

/**
* For each quorum, update the operator's stake and record the delta
* in the quorum's total stake.
Expand All @@ -163,9 +171,21 @@ contract StakeRegistry is StakeRegistryStorage {
// Fetch the operator's current stake, applying weighting parameters and checking
// against the minimum stake requirements for the quorum.
(uint96 stakeWeight, bool hasMinimumStake) = _weightOfOperatorForQuorum(quorumNumber, operator);

// If the operator no longer meets the minimum stake, set their stake to zero and mark them for removal
if (!hasMinimumStake) {
/// also handle setting the operator's stake to 0 and remove them from the quorum
/// if they directly unregistered from the AVSDirectory bubbles up info via registry coordinator to deregister them
bool operatorRegistered;
// Convert quorumNumber to operatorSetId
uint32 operatorSetId = uint32(quorumNumber);

// Get the AVSDirectory address from the RegistryCoordinator
// Query the AVSDirectory to check if the operator is directly unregistered
operatorRegistered = avsDirectory.isMember(
operator,
IAVSDirectory.OperatorSet(address(avs), operatorSetId)
);

if (!hasMinimumStake || (isOperatorSetAVS && !operatorRegistered)) {
stakeWeight = 0;
quorumsToRemove = uint192(quorumsToRemove.setBit(quorumNumber));
}
Expand Down
8 changes: 7 additions & 1 deletion src/StakeRegistryStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.12;

import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol";
import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol";
import {IStrategyManager, IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol";

import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol";
Expand All @@ -24,6 +25,9 @@ abstract contract StakeRegistryStorage is IStakeRegistry {
/// @notice The address of the Delegation contract for EigenLayer.
IDelegationManager public immutable delegation;

/// @notice The address of the Delegation contract for EigenLayer.
IAVSDirectory public immutable avsDirectory;

/// @notice the coordinator contract that this registry is associated with
address public immutable registryCoordinator;

Expand All @@ -47,10 +51,12 @@ abstract contract StakeRegistryStorage is IStakeRegistry {

constructor(
IRegistryCoordinator _registryCoordinator,
IDelegationManager _delegationManager
IDelegationManager _delegationManager,
IAVSDirectory _avsDirectory
) {
registryCoordinator = address(_registryCoordinator);
delegation = _delegationManager;
avsDirectory = _avsDirectory;
}

// storage gap for upgradeability
Expand Down
3 changes: 3 additions & 0 deletions src/interfaces/IRegistryCoordinator.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.12;

import {IServiceManager} from "./IServiceManager.sol";
import {IBLSApkRegistry} from "./IBLSApkRegistry.sol";
import {IStakeRegistry} from "./IStakeRegistry.sol";
import {IIndexRegistry} from "./IIndexRegistry.sol";
Expand Down Expand Up @@ -150,4 +151,6 @@ interface IRegistryCoordinator {

/// @notice The owner of the registry coordinator
function owner() external view returns (address);

function serviceManager() external view returns (IServiceManager);
}
5 changes: 3 additions & 2 deletions test/harnesses/StakeRegistryHarness.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import "../../src/StakeRegistry.sol";
contract StakeRegistryHarness is StakeRegistry {
constructor(
IRegistryCoordinator _registryCoordinator,
IDelegationManager _delegationManager
) StakeRegistry(_registryCoordinator, _delegationManager) {
IDelegationManager _delegationManager,
IAVSDirectory _avsDirectory
) StakeRegistry(_registryCoordinator, _delegationManager, _avsDirectory) {
}

function recordOperatorStakeUpdate(bytes32 operatorId, uint8 quorumNumber, uint96 newStake) external returns(int256) {
Expand Down
2 changes: 1 addition & 1 deletion test/integration/IntegrationDeployer.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ abstract contract IntegrationDeployer is Test, IUserDeployer {
cheats.stopPrank();

StakeRegistry stakeRegistryImplementation = new StakeRegistry(
IRegistryCoordinator(registryCoordinator), IDelegationManager(delegationManager)
IRegistryCoordinator(registryCoordinator), IDelegationManager(delegationManager), IAVSDirectory(avsDirectory)
);
BLSApkRegistry blsApkRegistryImplementation =
new BLSApkRegistry(IRegistryCoordinator(registryCoordinator));
Expand Down
4 changes: 3 additions & 1 deletion test/mocks/RegistryCoordinatorMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,6 @@ contract RegistryCoordinatorMock is IRegistryCoordinator {
function quorumUpdateBlockNumber(uint8 quorumNumber) external view returns (uint256) {}

function owner() external view returns (address) {}
}

function serviceManager() external view returns (IServiceManager){}
}
85 changes: 85 additions & 0 deletions test/unit/RegistryCoordinatorMigration.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -197,4 +197,89 @@ contract RegistryCoordinatorMigrationUnit is MockAVSDeployer, IServiceManagerBas
assertTrue(avsDirectory.isOperatorSet(address(serviceManager), quorumNumber), "Operator set was not created for the quorum");

}

function test_updateOperatorsForQuorumsAfterDirectUnregister() public {
vm.prank(proxyAdmin.owner());
proxyAdmin.upgrade(
TransparentUpgradeableProxy(payable(address(avsDirectory))),
address(avsDirectoryMock)
);
uint256 pseudoRandomNumber = uint256(keccak256("pseudoRandomNumber"));
_registerRandomOperators(pseudoRandomNumber);

vm.prank(proxyAdmin.owner());
proxyAdmin.upgrade(
TransparentUpgradeableProxy(payable(address(avsDirectory))),
address(avsDirectoryHarness)
);

uint256 quorumCount = registryCoordinator.quorumCount();
for (uint256 i = 0; i < quorumCount; i++) {
uint256 operatorCount = indexRegistry.totalOperatorsForQuorum(uint8(i));
bytes32[] memory operatorIds =
indexRegistry.getOperatorListAtBlockNumber(uint8(i), uint32(block.number));
assertEq(operatorCount, operatorIds.length, "Operator Id length mismatch"); // sanity check
for (uint256 j = 0; j < operatorCount; j++) {
address operatorAddress =
registryCoordinator.blsApkRegistry().getOperatorFromPubkeyHash(operatorIds[j]);
AVSDirectoryHarness(address(avsDirectory)).setAvsOperatorStatus(
address(serviceManager),
operatorAddress,
IAVSDirectory.OperatorAVSRegistrationStatus.REGISTERED
);
}
}

(
uint32[] memory operatorSetsToCreate,
uint32[][] memory operatorSetIdsToMigrate,
address[] memory operators
) = serviceManager.getOperatorsToMigrate();
cheats.startPrank(serviceManagerOwner);
serviceManager.migrateAndCreateOperatorSetIds(operatorSetsToCreate);
serviceManager.migrateToOperatorSets(operatorSetIdsToMigrate, operators);
cheats.stopPrank();

bytes32[] memory registeredOperators = indexRegistry.getOperatorListAtBlockNumber(defaultQuorumNumber, uint32(block.number));
uint256 preNumOperators = registeredOperators.length;
address[] memory registeredOperatorAddresses = new address[](registeredOperators.length);
for (uint256 i = 0; i < registeredOperators.length; i++) {
registeredOperatorAddresses[i] = registryCoordinator.blsApkRegistry().pubkeyHashToOperator(registeredOperators[i]);
}

uint32[] memory operatorSetsToUnregister = new uint32[](1);
operatorSetsToUnregister[0] = defaultQuorumNumber;

vm.prank(operators[0]);
avsDirectory.forceDeregisterFromOperatorSets(
operators[0],
address(serviceManager),
operatorSetsToUnregister,
ISignatureUtils.SignatureWithSaltAndExpiry({
signature: new bytes(0),
salt: bytes32(0),
expiry: 0
})
);
// sanity check if the operator was unregistered from the intended operator set
bool operatorIsUnRegistered = !avsDirectory.isMember(operators[0], IAVSDirectory.OperatorSet({
avs: address(serviceManager),
operatorSetId: defaultQuorumNumber
}));
bool isOperatorSetAVS = avsDirectory.isOperatorSetAVS(address(serviceManager));
assertTrue(isOperatorSetAVS, "ServiceManager is not an operator set AVS");
assertTrue(operatorIsUnRegistered, "Operator wasnt unregistered from op set");

address[][] memory registeredOperatorAddresses2D = new address[][](1);
registeredOperatorAddresses2D[0] = registeredOperatorAddresses;
bytes memory quorumNumbers = new bytes(1);
quorumNumbers[0] = bytes1(defaultQuorumNumber);
registryCoordinator.updateOperatorsForQuorum(registeredOperatorAddresses2D, quorumNumbers);

registeredOperators = indexRegistry.getOperatorListAtBlockNumber(defaultQuorumNumber, uint32(block.number));
uint256 postRegisteredOperators = registeredOperators.length;

assertEq(preNumOperators-1, postRegisteredOperators, "");

}
}
2 changes: 1 addition & 1 deletion test/unit/StakeRegistryUnit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ contract StakeRegistryUnitTests is MockAVSDeployer, IStakeRegistryEvents {
);

stakeRegistryImplementation = new StakeRegistryHarness(
IRegistryCoordinator(address(registryCoordinator)), delegationMock
IRegistryCoordinator(address(registryCoordinator)), delegationMock, avsDirectoryMock
);

stakeRegistry = StakeRegistryHarness(
Expand Down
2 changes: 1 addition & 1 deletion test/utils/MockAVSDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ contract MockAVSDeployer is Test {
cheats.startPrank(proxyAdminOwner);

stakeRegistryImplementation =
new StakeRegistryHarness(IRegistryCoordinator(registryCoordinator), delegationMock);
new StakeRegistryHarness(IRegistryCoordinator(registryCoordinator), delegationMock, avsDirectory);

proxyAdmin.upgrade(
TransparentUpgradeableProxy(payable(address(stakeRegistry))),
Expand Down
Loading