Skip to content

Commit

Permalink
feat: pass tests, use coherent timestamps
Browse files Browse the repository at this point in the history
  • Loading branch information
merklefruit committed Feb 4, 2025
1 parent 76fccd0 commit 5fb70e7
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 49 deletions.
50 changes: 29 additions & 21 deletions contracts/src/contracts/EigenLayerMiddlewareV4.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 <timestamp>
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);
Expand All @@ -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;
}

Expand Down Expand Up @@ -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.
Expand All @@ -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 ========= //
Expand Down Expand Up @@ -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 <timestamp>
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);

Expand Down Expand Up @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand Down Expand Up @@ -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();
}
}
38 changes: 20 additions & 18 deletions contracts/src/contracts/OperatorsRegistryV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand Down Expand Up @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand Down
2 changes: 1 addition & 1 deletion contracts/test/holesky/EigenLayerMiddlewareV1.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion contracts/test/holesky/SymbioticMiddleware.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion contracts/test/mainnet/EigenLayerMiddlewareV1.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ interface IDelegationManagerPreELIP002 {
) external;
}

contract EigenLayerMiddlewareV1Test is Test {
contract EigenLayerMiddlewareV1MainnetTest is Test {
OperatorsRegistryV1 registry;
EigenLayerMiddlewareV1 middleware;

Expand Down
81 changes: 75 additions & 6 deletions contracts/test/mainnet/EigenLayerMiddlewareV4.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ interface IDelegationManagerPreELIP002 {
) external;
}

contract EigenLayerMiddlewareV4Test is Test {
contract EigenLayerMiddlewareV4MainnetTest is Test {
OperatorsRegistryV2 registry;
EigenLayerMiddlewareV4 middleware;

Expand Down Expand Up @@ -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
Expand All @@ -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);
}
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 5fb70e7

Please sign in to comment.