diff --git a/contracts/src/contracts/EigenLayerMiddlewareV4.sol b/contracts/src/contracts/EigenLayerMiddlewareV4.sol index 99030af..b17abf8 100644 --- a/contracts/src/contracts/EigenLayerMiddlewareV4.sol +++ b/contracts/src/contracts/EigenLayerMiddlewareV4.sol @@ -138,8 +138,9 @@ contract EigenLayerMiddlewareV4 is OwnableUpgradeable, UUPSUpgradeable, IAVSRegi function getOperatorCollaterals( address operator ) public view returns (address[] memory, uint256[] memory) { - // Only take strategies that are active at - IStrategy[] memory activeStrategies = _getActiveStrategiesAt(_now()); + // Only take strategies that are currently active + uint48 time = Time.timestamp(); + IStrategy[] memory activeStrategies = _getActiveStrategiesAt(time); address[] memory collateralTokens = new address[](activeStrategies.length); uint256[] memory amounts = new uint256[](activeStrategies.length); @@ -161,11 +162,14 @@ contract EigenLayerMiddlewareV4 is OwnableUpgradeable, UUPSUpgradeable, IAVSRegi /// @param collateral The collateral token address /// @return The amount staked by the operator for the collateral function getOperatorStake(address operator, address collateral) public view returns (uint256) { + // Only take strategies that are currently active + uint48 time = Time.timestamp(); + uint256 activeStake = 0; for (uint256 i = 0; i < _strategyWhitelist.length(); i++) { (address strategy, uint48 enabledAt, uint48 disabledAt) = _strategyWhitelist.at(i); - if (!_wasEnabledAt(enabledAt, disabledAt, _now())) { + if (!_wasEnabledAt(enabledAt, disabledAt, time)) { continue; } @@ -211,8 +215,9 @@ contract EigenLayerMiddlewareV4 is OwnableUpgradeable, UUPSUpgradeable, IAVSRegi /// @notice Get the active strategies for this AVS /// @return The active strategies function getActiveWhitelistedStrategies() public view returns (IStrategy[] memory) { - // Use the beginning of the current epoch to check which strategies were enabled at that time. - return _getActiveStrategiesAt(_now()); + // get the strategies that are currently active + uint48 time = Time.timestamp(); + return _getActiveStrategiesAt(time); } /// @notice Get the number of whitelisted strategies for this AVS. @@ -227,7 +232,8 @@ contract EigenLayerMiddlewareV4 is OwnableUpgradeable, UUPSUpgradeable, IAVSRegi function isStrategyActive( address strategy ) public view returns (bool) { - return _strategyWhitelist.wasActiveAt(_now(), strategy); + uint48 time = Time.timestamp(); + return _strategyWhitelist.wasActiveAt(time, strategy); } // ========= [pre-ELIP-002] IServiceManager functions ========= // @@ -285,8 +291,9 @@ contract EigenLayerMiddlewareV4 is OwnableUpgradeable, UUPSUpgradeable, IAVSRegi function getOperatorRestakedStrategies( address operator ) public view returns (address[] memory) { - // Only take strategies that are active at - IStrategy[] memory activeStrategies = _getActiveStrategiesAt(_now()); + // get the strategies that are currently active + uint48 time = Time.timestamp(); + IStrategy[] memory activeStrategies = _getActiveStrategiesAt(time); address[] memory restakedStrategies = new address[](activeStrategies.length); @@ -373,7 +380,8 @@ contract EigenLayerMiddlewareV4 is OwnableUpgradeable, UUPSUpgradeable, IAVSRegi require(!_strategyWhitelist.contains(strategy), StrategyAlreadyWhitelisted()); require(STRATEGY_MANAGER.strategyIsWhitelistedForDeposit(IStrategy(strategy)), UnauthorizedStrategy()); - _strategyWhitelist.register(_now(), strategy); + uint48 time = Time.timestamp(); + _strategyWhitelist.register(time, strategy); emit StrategyWhitelisted(strategy); } @@ -384,9 +392,10 @@ contract EigenLayerMiddlewareV4 is OwnableUpgradeable, UUPSUpgradeable, IAVSRegi ) public onlyOwner { require(_strategyWhitelist.contains(strategy), UnauthorizedStrategy()); - // NOTE: we use _now() - 1 to ensure that the strategy is paused in the current epoch. - // If we didn't do this, we would have to wait until the next epoch until the strategy was actually paused. - _strategyWhitelist.pause(_now() - 1, strategy); + // Pause the strategy at the end of the current epoch. + // Strategies are still considered active until the start of the next epoch. + uint48 time = OPERATORS_REGISTRY.getCurrentEpochStartTimestamp() - 1; + _strategyWhitelist.pause(time, strategy); emit StrategyPaused(strategy); } @@ -397,7 +406,10 @@ contract EigenLayerMiddlewareV4 is OwnableUpgradeable, UUPSUpgradeable, IAVSRegi ) public onlyOwner { require(_strategyWhitelist.contains(strategy), UnauthorizedStrategy()); - _strategyWhitelist.unpause(_now(), OPERATORS_REGISTRY.EPOCH_DURATION(), strategy); + // Unpause the strategy at the start of the next epoch. + // Strategies are still considered paused until the end of the current epoch. + uint48 time = OPERATORS_REGISTRY.getCurrentEpochStartTimestamp() - 1; + _strategyWhitelist.unpause(time, OPERATORS_REGISTRY.EPOCH_DURATION(), strategy); emit StrategyUnpaused(strategy); } @@ -409,8 +421,10 @@ contract EigenLayerMiddlewareV4 is OwnableUpgradeable, UUPSUpgradeable, IAVSRegi ) public onlyOwner { require(_strategyWhitelist.contains(strategy), UnauthorizedStrategy()); - // NOTE: we use _now() - 1 to ensure that the strategy is removed in the current epoch. - _strategyWhitelist.unregister(_now() - 1, OPERATORS_REGISTRY.EPOCH_DURATION(), strategy); + // Remove the strategy in 1 epoch + // Strategies must be paused for at least 1 epoch before they can be removed + uint48 time = Time.timestamp(); + _strategyWhitelist.unregister(time, OPERATORS_REGISTRY.EPOCH_DURATION(), strategy); emit StrategyRemoved(strategy); } @@ -528,10 +542,4 @@ contract EigenLayerMiddlewareV4 is OwnableUpgradeable, UUPSUpgradeable, IAVSRegi function _wasEnabledAt(uint48 enabledAt, uint48 disabledAt, uint48 timestamp) internal pure returns (bool) { return enabledAt != 0 && enabledAt <= timestamp && (disabledAt == 0 || disabledAt >= timestamp); } - - /// @notice Returns the timestamp of the current epoch. - /// @return timestamp The current epoch timestamp. - function _now() internal view returns (uint48) { - return OPERATORS_REGISTRY.getCurrentEpochStartTimestamp(); - } } diff --git a/contracts/src/contracts/OperatorsRegistryV2.sol b/contracts/src/contracts/OperatorsRegistryV2.sol index 963ee4f..d8a42bb 100644 --- a/contracts/src/contracts/OperatorsRegistryV2.sol +++ b/contracts/src/contracts/OperatorsRegistryV2.sol @@ -145,10 +145,9 @@ contract OperatorsRegistryV2 is IOperatorsRegistryV2, OwnableUpgradeable, UUPSUp ) external onlyMiddleware { require(_operatorAddresses.contains(operator), UnknownOperator()); - // NOTE: we use the current epoch start timestamp - 1 to ensure that the operator is deregistered - // at the end of the current epoch. If we didn't do this, we would have to wait until the next - // epoch until the operator was actually deregistered. - uint48 time = getCurrentEpochStartTimestamp() - 1; + // Deregister the operator in 1 epoch + // Operators must be paused for at least 1 epoch before they can be deregistered + uint48 time = Time.timestamp(); _operatorAddresses.unregister(time, EPOCH_DURATION, operator); delete operators[operator]; delete _authorizedSignersByOperator[operator]; @@ -165,9 +164,9 @@ contract OperatorsRegistryV2 is IOperatorsRegistryV2, OwnableUpgradeable, UUPSUp ) external onlyMiddleware { require(_operatorAddresses.contains(operator), UnknownOperator()); - // Pause the operator at the end of the current epoch. - // Operators are still considered active until the start of the next epoch. - uint48 time = getCurrentEpochStartTimestamp() - 1; + // Pause the operator in 1 epoch + // Operators are still considered active until a full epoch has passed + uint48 time = Time.timestamp(); _operatorAddresses.pause(time, operator); emit OperatorPaused(operator, msg.sender); } @@ -181,9 +180,9 @@ contract OperatorsRegistryV2 is IOperatorsRegistryV2, OwnableUpgradeable, UUPSUp ) external onlyMiddleware { require(_operatorAddresses.contains(operator), UnknownOperator()); - // Unpause the operator at the start of the next epoch. - // Operators are still considered paused until the end of the current epoch. - uint48 time = getCurrentEpochStartTimestamp() - 1; + // Unpause the operator in 1 epoch + // Operators are still considered paused until a full epoch has passed + uint48 time = Time.timestamp(); _operatorAddresses.unpause(time, EPOCH_DURATION, operator); emit OperatorUnpaused(operator, msg.sender); } @@ -213,8 +212,8 @@ contract OperatorsRegistryV2 is IOperatorsRegistryV2, OwnableUpgradeable, UUPSUp require(_operatorAddresses.contains(operator), UnknownOperator()); require(signer != address(0), InvalidSigner()); - // Register the signer as active from the current epoch onwards. - uint48 time = getCurrentEpochStartTimestamp(); + // Register the signer as active from now onwards + uint48 time = Time.timestamp(); _authorizedSignersByOperator[operator].register(time, signer); } @@ -228,10 +227,9 @@ contract OperatorsRegistryV2 is IOperatorsRegistryV2, OwnableUpgradeable, UUPSUp require(_operatorAddresses.contains(operator), UnknownOperator()); require(_authorizedSignersByOperator[operator].contains(signer), UnknownSigner()); - // Pause the signer from the next epoch onwards. This ensures that the signer must wait for - // one full epoch before they aren't considered active anymore (to prevent trying to avoid - // being slashed). - uint48 time = getCurrentEpochStartTimestamp() - 1; + // Pause the signer in 1 epoch. + // Signers are still considered active until a full epoch has passed + uint48 time = Time.timestamp(); _authorizedSignersByOperator[operator].pause(time, signer); } @@ -245,7 +243,9 @@ contract OperatorsRegistryV2 is IOperatorsRegistryV2, OwnableUpgradeable, UUPSUp require(_operatorAddresses.contains(operator), UnknownOperator()); require(_authorizedSignersByOperator[operator].contains(signer), UnknownSigner()); - uint48 time = getCurrentEpochStartTimestamp() - 1; + // Unpause the signer in 1 epoch. + // Signers are still considered paused until a full epoch has passed + uint48 time = Time.timestamp(); _authorizedSignersByOperator[operator].unpause(time, EPOCH_DURATION, signer); } @@ -259,7 +259,9 @@ contract OperatorsRegistryV2 is IOperatorsRegistryV2, OwnableUpgradeable, UUPSUp require(_operatorAddresses.contains(operator), UnknownOperator()); require(_authorizedSignersByOperator[operator].contains(signer), UnknownSigner()); - uint48 time = getCurrentEpochStartTimestamp() - 1; + // Unregister the signer from now onwards. The signer must have been + // paused for at least 1 epoch before it can be unregistered. + uint48 time = Time.timestamp(); _authorizedSignersByOperator[operator].unregister(time, EPOCH_DURATION, signer); } diff --git a/contracts/test/holesky/EigenLayerMiddlewareV1.t.sol b/contracts/test/holesky/EigenLayerMiddlewareV1.t.sol index 783b249..4599553 100644 --- a/contracts/test/holesky/EigenLayerMiddlewareV1.t.sol +++ b/contracts/test/holesky/EigenLayerMiddlewareV1.t.sol @@ -20,7 +20,7 @@ import {IOperatorsRegistryV1} from "../../src/interfaces/IOperatorsRegistryV1.so import {EigenLayerMiddlewareV1} from "../../src/contracts/EigenLayerMiddlewareV1.sol"; import {IWETH} from "../util/IWETH.sol"; -contract EigenLayerMiddlewareV1Test is Test { +contract EigenLayerMiddlewareV1HoleskyTest is Test { OperatorsRegistryV1 registry; EigenLayerMiddlewareV1 middleware; diff --git a/contracts/test/holesky/SymbioticMiddleware.t.sol b/contracts/test/holesky/SymbioticMiddleware.t.sol index cce4d67..f206c68 100644 --- a/contracts/test/holesky/SymbioticMiddleware.t.sol +++ b/contracts/test/holesky/SymbioticMiddleware.t.sol @@ -21,7 +21,7 @@ import {Subnetwork} from "@symbiotic/core/contracts/libraries/Subnetwork.sol"; import {IERC20} from "@openzeppelin-v4.9.0/contracts/interfaces/IERC20.sol"; -contract SymbioticMiddlewareHoleskyTest is Test { +contract SymbioticMiddlewareV1HoleskyTest is Test { using Subnetwork for address; uint48 EPOCH_DURATION = 1 days; diff --git a/contracts/test/mainnet/EigenLayerMiddlewareV1.t.sol b/contracts/test/mainnet/EigenLayerMiddlewareV1.t.sol index fdffe00..ef78ba8 100644 --- a/contracts/test/mainnet/EigenLayerMiddlewareV1.t.sol +++ b/contracts/test/mainnet/EigenLayerMiddlewareV1.t.sol @@ -33,7 +33,7 @@ interface IDelegationManagerPreELIP002 { ) external; } -contract EigenLayerMiddlewareV1Test is Test { +contract EigenLayerMiddlewareV1MainnetTest is Test { OperatorsRegistryV1 registry; EigenLayerMiddlewareV1 middleware; diff --git a/contracts/test/mainnet/EigenLayerMiddlewareV4.t.sol b/contracts/test/mainnet/EigenLayerMiddlewareV4.t.sol index 81e77b9..d1f4f68 100644 --- a/contracts/test/mainnet/EigenLayerMiddlewareV4.t.sol +++ b/contracts/test/mainnet/EigenLayerMiddlewareV4.t.sol @@ -34,7 +34,7 @@ interface IDelegationManagerPreELIP002 { ) external; } -contract EigenLayerMiddlewareV4Test is Test { +contract EigenLayerMiddlewareV4MainnetTest is Test { OperatorsRegistryV2 registry; EigenLayerMiddlewareV4 middleware; @@ -214,8 +214,9 @@ contract EigenLayerMiddlewareV4Test is Test { vm.prank(operator); registry.pauseOperatorAuthorizedSigner(authorizedSigner); (opData, isActive, authSigners) = registry.getOperator(operator); - // The authorized signer should be paused immediately and will not show up in the authorized signers list - assertEq(authSigners.length, 0); + // The authorized signer should not be paused immediately, but rather in 1 epoch + assertEq(authSigners.length, authorizedSigners.length); + assertEq(authSigners[0], authorizedSigner); // 8. try to unpause the authorized signer and check its status again // this should fail because the operator needs to wait for the next epoch to @@ -224,11 +225,20 @@ contract EigenLayerMiddlewareV4Test is Test { vm.expectRevert(PauseableEnumerableSet.ImmutablePeriodNotPassed.selector); registry.unpauseOperatorAuthorizedSigner(authorizedSigner); - // 9. wait for the next epoch and try to unpause the authorized signer again + // 9. wait for 1 epoch and check the status vm.warp(block.timestamp + 1 days); + (opData, isActive, authSigners) = registry.getOperator(operator); + assertEq(authSigners.length, 0); + + // 10. try to unpause the authorized signer again. it will be unpaused in 1 epoch vm.prank(operator); registry.unpauseOperatorAuthorizedSigner(authorizedSigner); (opData, isActive, authSigners) = registry.getOperator(operator); + assertEq(authSigners.length, 0); + + // 11. wait more than 1 epoch and check the status again + vm.warp(block.timestamp + 1 days + 1); + (opData, isActive, authSigners) = registry.getOperator(operator); assertEq(authSigners.length, authorizedSigners.length); assertEq(authSigners[0], authorizedSigner); } @@ -244,14 +254,73 @@ contract EigenLayerMiddlewareV4Test is Test { vm.prank(operator); middleware.deregisterOperatorFromAVS(); - // 3. check that the operator is paused immediately + // 3. check that the operator is still active for 1 epoch (IOperatorsRegistryV2.Operator memory opData, bool isActive, address[] memory authSigners) = registry.getOperator(operator); - assertEq(isActive, false); + assertEq(isActive, true); // 4. try to unpause the operator and check that it fails vm.prank(operator); vm.expectRevert(PauseableEnumerableSet.ImmutablePeriodNotPassed.selector); middleware.unpauseOperator(); + + // 5. wait for more than 1 epoch and check the status + vm.warp(block.timestamp + 1 days + 1); + (opData, isActive, authSigners) = registry.getOperator(operator); + assertEq(isActive, false); + + // 6. try to unpause the operator again. it should now succeed + // but the operator should still be paused for 1 epoch + vm.prank(operator); + middleware.unpauseOperator(); + (opData, isActive, authSigners) = registry.getOperator(operator); + assertEq(isActive, false); + + // 7. wait more than 1 epoch and check the status again + vm.warp(block.timestamp + 1 days + 2); + (opData, isActive, authSigners) = registry.getOperator(operator); + assertEq(isActive, true); + } + + function testPauseUnpauseOperatorSigner() public { + address[] memory authorizedSigners = new address[](1); + authorizedSigners[0] = authorizedSigner; + + // 1. register the operator + _registerOperator(operator, "http://stopjava.com", "operator1", authorizedSigners); + + // 2. pause the authorized signer + vm.prank(operator); + registry.pauseOperatorAuthorizedSigner(authorizedSigner); + + // 3. check that the signer is still active for 1 epoch + (IOperatorsRegistryV2.Operator memory opData, bool isActive, address[] memory authSigners) = + registry.getOperator(operator); + assertEq(isActive, true); + assertEq(authSigners.length, 1); + assertEq(authSigners[0], authorizedSigner); + + // 4. try to unpause the signer and check that it fails + vm.prank(operator); + vm.expectRevert(PauseableEnumerableSet.ImmutablePeriodNotPassed.selector); + registry.unpauseOperatorAuthorizedSigner(authorizedSigner); + + // 5. wait for 1 epoch and check the status + vm.warp(block.timestamp + 1 days); + (opData, isActive, authSigners) = registry.getOperator(operator); + assertEq(authSigners.length, 0); + + // 6. try to unpause the signer again. it should now succeed + // and the signer should be active again in 1 epoch + vm.prank(operator); + registry.unpauseOperatorAuthorizedSigner(authorizedSigner); + (opData, isActive, authSigners) = registry.getOperator(operator); + assertEq(authSigners.length, 0); + + // 7. wait more than 1 epoch and check the status again + vm.warp(block.timestamp + 1 days + 1); + (opData, isActive, authSigners) = registry.getOperator(operator); + assertEq(authSigners.length, 1); + assertEq(authSigners[0], authorizedSigner); } } diff --git a/contracts/test/mainnet/SymbioticMiddleware.t.sol b/contracts/test/mainnet/SymbioticMiddlewareV1.t.sol similarity index 99% rename from contracts/test/mainnet/SymbioticMiddleware.t.sol rename to contracts/test/mainnet/SymbioticMiddlewareV1.t.sol index 0aa5b8e..3249b32 100644 --- a/contracts/test/mainnet/SymbioticMiddleware.t.sol +++ b/contracts/test/mainnet/SymbioticMiddlewareV1.t.sol @@ -22,7 +22,7 @@ import {Subnetwork} from "@symbiotic/core/contracts/libraries/Subnetwork.sol"; import {IERC20} from "@openzeppelin-v4.9.0/contracts/interfaces/IERC20.sol"; -contract SymbioticMiddlewareMainnetTest is Test { +contract SymbioticMiddlewareV1MainnetTest is Test { using Subnetwork for address; uint48 EPOCH_DURATION = 1 days;