From 8c1a688b77a6ae59045898ede9a390466b4f4d4d Mon Sep 17 00:00:00 2001 From: wadealexc Date: Wed, 6 Nov 2024 18:13:22 +0000 Subject: [PATCH] refactor: move beacon chain slashing out of slashing lib --- lib/forge-std | 2 +- script/deploy/devnet/Upgrade.s.sol | 2 +- .../deploy/devnet/deploy_from_scratch.s.sol | 12 +- .../holesky/M2_Deploy_From_Scratch.s.sol | 4 +- ...3-upgrade_testnet_rewardsCoordinator.s.sol | 2 +- script/deploy/local/Deploy_From_Scratch.s.sol | 6 +- .../v0.4.3-upgrade_rewardsCoordinator.s.sol | 2 +- script/utils/ExistingDeploymentParser.sol | 2 - src/contracts/core/AVSDirectory.sol | 365 +-- src/contracts/core/AVSDirectoryStorage.sol | 36 +- src/contracts/core/AllocationManager.sol | 737 +++-- .../core/AllocationManagerStorage.sol | 83 +- src/contracts/core/DelegationManager.sol | 235 +- .../core/DelegationManagerStorage.sol | 11 +- src/contracts/core/RewardsCoordinator.sol | 5 +- src/contracts/interfaces/IAVSDirectory.sol | 257 +- src/contracts/interfaces/IAVSRegistrar.sol | 22 + .../interfaces/IAllocationManager.sol | 448 ++- .../interfaces/IDelegationManager.sol | 17 + src/contracts/libraries/OperatorSetLib.sol | 25 + src/contracts/libraries/SlashingLib.sol | 123 +- src/contracts/strategies/StrategyBase.sol | 11 +- src/test/DevnetLifecycle.t.sol | 97 +- src/test/integration/IntegrationBase.t.sol | 37 +- .../integration/IntegrationDeployer.t.sol | 8 +- src/test/mocks/AVSDirectoryMock.sol | 37 +- src/test/mocks/AllocationManagerMock.sol | 6 +- src/test/mocks/EigenPodManagerMock.sol | 6 +- src/test/mocks/MockAVSRegistrar.sol | 6 + src/test/mocks/StrategyManagerMock.sol | 14 +- src/test/tree/AllocationManagerUnit.tree | 12 +- src/test/unit/AVSDirectoryUnit.t.sol | 1663 +--------- src/test/unit/AllocationManagerUnit.t.sol | 2844 ++++++++++------- src/test/unit/DelegationUnit.t.sol | 135 +- src/test/unit/EigenPodManagerUnit.t.sol | 12 +- src/test/unit/EigenPodUnit.t.sol | 4 +- src/test/unit/PausableUnit.t.sol | 4 +- src/test/unit/RewardsCoordinatorUnit.t.sol | 4 +- src/test/unit/StrategyBaseUnit.t.sol | 2 +- src/test/unit/StrategyFactoryUnit.t.sol | 2 +- src/test/unit/StrategyManagerUnit.t.sol | 6 +- src/test/utils/EigenLayerUnitTestSetup.sol | 16 + src/test/utils/Operators.sol | 8 +- src/test/utils/Owners.sol | 4 +- src/test/utils/ProofParsing.sol | 24 +- src/test/utils/Random.sol | 131 + src/test/utils/SingleItemArrayLib.sol | 56 + 47 files changed, 3230 insertions(+), 4315 deletions(-) create mode 100644 src/contracts/interfaces/IAVSRegistrar.sol create mode 100644 src/contracts/libraries/OperatorSetLib.sol create mode 100644 src/test/mocks/MockAVSRegistrar.sol create mode 100644 src/test/utils/Random.sol create mode 100644 src/test/utils/SingleItemArrayLib.sol diff --git a/lib/forge-std b/lib/forge-std index 4f57c59f0..1eea5bae1 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 4f57c59f066a03d13de8c65bb34fca8247f5fcb2 +Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262 diff --git a/script/deploy/devnet/Upgrade.s.sol b/script/deploy/devnet/Upgrade.s.sol index 8610a2557..b9b430512 100644 --- a/script/deploy/devnet/Upgrade.s.sol +++ b/script/deploy/devnet/Upgrade.s.sol @@ -16,7 +16,7 @@ contract Upgrade is ExistingDeploymentParser { _parseDeployedContracts("script/output/holesky/pre_preprod_slashing.holesky.json"); vm.startBroadcast(); - AVSDirectory newAVSDirectoryImplementation = new AVSDirectory(delegationManager, eigenLayerPauserReg, DEALLOCATION_DELAY); + AVSDirectory newAVSDirectoryImplementation = new AVSDirectory(delegationManager, eigenLayerPauserReg); eigenLayerProxyAdmin.upgrade(ITransparentUpgradeableProxy(payable(address(avsDirectory))), address(newAVSDirectoryImplementation)); vm.stopBroadcast(); diff --git a/script/deploy/devnet/deploy_from_scratch.s.sol b/script/deploy/devnet/deploy_from_scratch.s.sol index cd79b7cc1..a9ee32313 100644 --- a/script/deploy/devnet/deploy_from_scratch.s.sol +++ b/script/deploy/devnet/deploy_from_scratch.s.sol @@ -230,7 +230,7 @@ contract DeployFromScratch is Script, Test { delegationImplementation = new DelegationManager(avsDirectory, strategyManager, eigenPodManager, allocationManager, eigenLayerPauserReg, MIN_WITHDRAWAL_DELAY); strategyManagerImplementation = new StrategyManager(delegation, eigenLayerPauserReg); - avsDirectoryImplementation = new AVSDirectory(delegation, eigenLayerPauserReg, DEALLOCATION_DELAY); + avsDirectoryImplementation = new AVSDirectory(delegation, eigenLayerPauserReg); eigenPodManagerImplementation = new EigenPodManager( ethPOSDeposit, eigenPodBeacon, @@ -248,13 +248,11 @@ contract DeployFromScratch is Script, Test { REWARDS_COORDINATOR_MAX_FUTURE_LENGTH, REWARDS_COORDINATOR_GENESIS_REWARDS_TIMESTAMP ); - allocationManagerImplementation = new AllocationManager(delegation, avsDirectory, eigenLayerPauserReg, DEALLOCATION_DELAY, ALLOCATION_CONFIGURATION_DELAY); + allocationManagerImplementation = new AllocationManager(delegation, eigenLayerPauserReg, DEALLOCATION_DELAY, ALLOCATION_CONFIGURATION_DELAY); strategyFactoryImplementation = new StrategyFactory(strategyManager, eigenLayerPauserReg); // Third, upgrade the proxy contracts to use the correct implementation contracts and initialize them. { - IStrategy[] memory _strategies; - uint256[] memory _withdrawalDelayBlocks; eigenLayerProxyAdmin.upgradeAndCall( ITransparentUpgradeableProxy(payable(address(delegation))), address(delegationImplementation), @@ -495,10 +493,6 @@ contract DeployFromScratch is Script, Test { allocationManagerContract.delegation() == delegation, "allocationManager: delegation address not set correctly" ); - require( - allocationManagerContract.avsDirectory() == avsDirectory, - "allocationManager: avsDirectory address not set correctly" - ); } function _verifyImplementationsSetCorrectly() internal view { @@ -609,7 +603,7 @@ contract DeployFromScratch is Script, Test { // require(eigenPodManager.paused() == 30, "eigenPodManager: init paused status set incorrectly"); } - function _verifyInitializationParams() internal { + function _verifyInitializationParams() internal view { // // one week in blocks -- 50400 // uint32 STRATEGY_MANAGER_INIT_WITHDRAWAL_DELAY_BLOCKS = 7 days / 12 seconds; // require(strategyManager.withdrawalDelayBlocks() == 7 days / 12 seconds, diff --git a/script/deploy/holesky/M2_Deploy_From_Scratch.s.sol b/script/deploy/holesky/M2_Deploy_From_Scratch.s.sol index c8f1c8e15..244bfc59d 100644 --- a/script/deploy/holesky/M2_Deploy_From_Scratch.s.sol +++ b/script/deploy/holesky/M2_Deploy_From_Scratch.s.sol @@ -76,7 +76,7 @@ contract M2_Deploy_Holesky_From_Scratch is ExistingDeploymentParser { ); eigenPodBeacon = new UpgradeableBeacon(address(eigenPodImplementation)); - avsDirectoryImplementation = new AVSDirectory(delegationManager, eigenLayerPauserReg, DEALLOCATION_DELAY); + avsDirectoryImplementation = new AVSDirectory(delegationManager, eigenLayerPauserReg); delegationManagerImplementation = new DelegationManager(avsDirectory, strategyManager, eigenPodManager, allocationManager, eigenLayerPauserReg, MIN_WITHDRAWAL_DELAY); strategyManagerImplementation = new StrategyManager(delegationManager, eigenLayerPauserReg); eigenPodManagerImplementation = new EigenPodManager( @@ -86,7 +86,7 @@ contract M2_Deploy_Holesky_From_Scratch is ExistingDeploymentParser { delegationManager, eigenLayerPauserReg ); - allocationManagerImplementation = new AllocationManager(delegationManager, avsDirectory, eigenLayerPauserReg, DEALLOCATION_DELAY, ALLOCATION_CONFIGURATION_DELAY); + allocationManagerImplementation = new AllocationManager(delegationManager, eigenLayerPauserReg, DEALLOCATION_DELAY, ALLOCATION_CONFIGURATION_DELAY); // Third, upgrade the proxy contracts to point to the implementations IStrategy[] memory initializeStrategiesToSetDelayBlocks = new IStrategy[](0); diff --git a/script/deploy/holesky/v0.4.3-upgrade_testnet_rewardsCoordinator.s.sol b/script/deploy/holesky/v0.4.3-upgrade_testnet_rewardsCoordinator.s.sol index 67d43871a..bc7d0a9f1 100644 --- a/script/deploy/holesky/v0.4.3-upgrade_testnet_rewardsCoordinator.s.sol +++ b/script/deploy/holesky/v0.4.3-upgrade_testnet_rewardsCoordinator.s.sol @@ -64,7 +64,7 @@ contract Upgrade_Testnet_RewardsCoordinator is Deploy_Test_RewardsCoordinator, T _verifyInitializationParams(); } - function _sanityCheckImplementations(RewardsCoordinator oldRc, RewardsCoordinator newRc) internal { + function _sanityCheckImplementations(RewardsCoordinator oldRc, RewardsCoordinator newRc) internal view { // Verify configs between both rewardsCoordinatorImplementations assertEq( address(oldRc.delegationManager()), diff --git a/script/deploy/local/Deploy_From_Scratch.s.sol b/script/deploy/local/Deploy_From_Scratch.s.sol index 7bfae7382..a12b1b2f3 100644 --- a/script/deploy/local/Deploy_From_Scratch.s.sol +++ b/script/deploy/local/Deploy_From_Scratch.s.sol @@ -236,7 +236,7 @@ contract DeployFromScratch is Script, Test { delegationImplementation = new DelegationManager(avsDirectory, strategyManager, eigenPodManager, allocationManager, eigenLayerPauserReg, MIN_WITHDRAWAL_DELAY); strategyManagerImplementation = new StrategyManager(delegation, eigenLayerPauserReg); - avsDirectoryImplementation = new AVSDirectory(delegation, eigenLayerPauserReg, DEALLOCATION_DELAY); + avsDirectoryImplementation = new AVSDirectory(delegation, eigenLayerPauserReg); eigenPodManagerImplementation = new EigenPodManager( ethPOSDeposit, eigenPodBeacon, @@ -254,7 +254,7 @@ contract DeployFromScratch is Script, Test { REWARDS_COORDINATOR_MAX_FUTURE_LENGTH, REWARDS_COORDINATOR_GENESIS_REWARDS_TIMESTAMP ); - allocationManagerImplementation = new AllocationManager(delegation, avsDirectory, eigenLayerPauserReg, DEALLOCATION_DELAY, ALLOCATION_CONFIGURATION_DELAY); + allocationManagerImplementation = new AllocationManager(delegation, eigenLayerPauserReg, DEALLOCATION_DELAY, ALLOCATION_CONFIGURATION_DELAY); // Third, upgrade the proxy contracts to use the correct implementation contracts and initialize them. { @@ -585,7 +585,7 @@ contract DeployFromScratch is Script, Test { // require(eigenPodManager.paused() == 30, "eigenPodManager: init paused status set incorrectly"); } - function _verifyInitializationParams() internal { + function _verifyInitializationParams() internal view { // // one week in blocks -- 50400 // uint32 STRATEGY_MANAGER_INIT_WITHDRAWAL_DELAY_BLOCKS = 7 days / 12 seconds; // require(strategyManager.withdrawalDelayBlocks() == 7 days / 12 seconds, diff --git a/script/deploy/mainnet/v0.4.3-upgrade_rewardsCoordinator.s.sol b/script/deploy/mainnet/v0.4.3-upgrade_rewardsCoordinator.s.sol index 83c4899c3..059b47c57 100644 --- a/script/deploy/mainnet/v0.4.3-upgrade_rewardsCoordinator.s.sol +++ b/script/deploy/mainnet/v0.4.3-upgrade_rewardsCoordinator.s.sol @@ -147,7 +147,7 @@ contract Upgrade_Mainnet_RewardsCoordinator is ExistingDeploymentParser, Timeloc _verifyInitializationParams(); } - function _sanityCheckImplementations(RewardsCoordinator oldRc, RewardsCoordinator newRc) internal { + function _sanityCheckImplementations(RewardsCoordinator oldRc, RewardsCoordinator newRc) internal view { // Verify configs between both rewardsCoordinatorImplementations assertEq( address(oldRc.delegationManager()), diff --git a/script/utils/ExistingDeploymentParser.sol b/script/utils/ExistingDeploymentParser.sol index 38d584146..874b12715 100644 --- a/script/utils/ExistingDeploymentParser.sol +++ b/script/utils/ExistingDeploymentParser.sol @@ -458,8 +458,6 @@ contract ExistingDeploymentParser is Script, Test { ); // DelegationManager vm.expectRevert(bytes("Initializable: contract is already initialized")); - IStrategy[] memory initializeStrategiesToSetDelayBlocks = new IStrategy[](0); - uint256[] memory initializeWithdrawalDelayBlocks = new uint256[](0); delegationManager.initialize( address(0), 0 diff --git a/src/contracts/core/AVSDirectory.sol b/src/contracts/core/AVSDirectory.sol index daf56ffce..0e9af8741 100644 --- a/src/contracts/core/AVSDirectory.sol +++ b/src/contracts/core/AVSDirectory.sol @@ -17,9 +17,6 @@ contract AVSDirectory is ReentrancyGuardUpgradeable, SignatureUtils { - using EnumerableSet for EnumerableSet.Bytes32Set; - using EnumerableSet for EnumerableSet.AddressSet; - /** * * INITIALIZING FUNCTIONS @@ -32,9 +29,8 @@ contract AVSDirectory is */ constructor( IDelegationManager _delegation, - IPauserRegistry _pauserRegistry, - uint32 _DEALLOCATION_DELAY - ) AVSDirectoryStorage(_delegation, _DEALLOCATION_DELAY) Pausable(_pauserRegistry) { + IPauserRegistry _pauserRegistry + ) AVSDirectoryStorage(_delegation) Pausable(_pauserRegistry) { _disableInitializers(); } @@ -50,150 +46,6 @@ contract AVSDirectory is * */ - /// @inheritdoc IAVSDirectory - function createOperatorSets( - uint32[] calldata operatorSetIds - ) external { - for (uint256 i = 0; i < operatorSetIds.length; ++i) { - require(!isOperatorSet[msg.sender][operatorSetIds[i]], InvalidOperatorSet()); - isOperatorSet[msg.sender][operatorSetIds[i]] = true; - emit OperatorSetCreated(OperatorSet({avs: msg.sender, operatorSetId: operatorSetIds[i]})); - } - } - - /// @inheritdoc IAVSDirectory - function becomeOperatorSetAVS() external { - require(!isOperatorSetAVS[msg.sender], InvalidAVS()); - isOperatorSetAVS[msg.sender] = true; - emit AVSMigratedToOperatorSets(msg.sender); - } - - /// @inheritdoc IAVSDirectory - function migrateOperatorsToOperatorSets( - address[] calldata operators, - uint32[][] calldata operatorSetIds - ) external override onlyWhenNotPaused(PAUSED_OPERATOR_SET_REGISTRATION_AND_DEREGISTRATION) { - // Assert that the AVS is an operator set AVS. - require(isOperatorSetAVS[msg.sender], InvalidAVS()); - - for (uint256 i = 0; i < operators.length; i++) { - // Assert that the operator is registered & has not been migrated. - require( - avsOperatorStatus[msg.sender][operators[i]] == OperatorAVSRegistrationStatus.REGISTERED, - InvalidOperator() - ); - - // Migrate operator to operator sets. - _registerToOperatorSets(operators[i], msg.sender, operatorSetIds[i]); - - // Deregister operator from AVS - this prevents the operator from being migrated again since - // the AVS can no longer use the legacy M2 registration path - avsOperatorStatus[msg.sender][operators[i]] = OperatorAVSRegistrationStatus.UNREGISTERED; - emit OperatorAVSRegistrationStatusUpdated( - operators[i], msg.sender, OperatorAVSRegistrationStatus.UNREGISTERED - ); - emit OperatorMigratedToOperatorSets(operators[i], msg.sender, operatorSetIds[i]); - } - } - - /// @inheritdoc IAVSDirectory - function registerOperatorToOperatorSets( - address operator, - uint32[] calldata operatorSetIds, - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature - ) external override onlyWhenNotPaused(PAUSED_OPERATOR_SET_REGISTRATION_AND_DEREGISTRATION) { - // Assert `operator` is actually an operator. - require(delegation.isOperator(operator), OperatorNotRegisteredToEigenLayer()); - // Assert that the AVS is an operator set AVS. - require(isOperatorSetAVS[msg.sender], InvalidAVS()); - // Assert operator's signature `salt` has not already been spent. - require(!operatorSaltIsSpent[operator][operatorSignature.salt], SaltSpent()); - - // Assert that `operatorSignature.signature` is a valid signature for operator set registrations. - _checkIsValidSignatureNow({ - signer: operator, - signableDigest: calculateOperatorSetRegistrationDigestHash({ - avs: msg.sender, - operatorSetIds: operatorSetIds, - salt: operatorSignature.salt, - expiry: operatorSignature.expiry - }), - signature: operatorSignature.signature, - expiry: operatorSignature.expiry - }); - - // Mutate `operatorSaltIsSpent` to `true` to prevent future respending. - operatorSaltIsSpent[operator][operatorSignature.salt] = true; - - _registerToOperatorSets(operator, msg.sender, operatorSetIds); - } - - /// @inheritdoc IAVSDirectory - function forceDeregisterFromOperatorSets( - address operator, - address avs, - uint32[] calldata operatorSetIds, - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature - ) external override onlyWhenNotPaused(PAUSED_OPERATOR_SET_REGISTRATION_AND_DEREGISTRATION) { - if (operatorSignature.signature.length == 0) { - require(msg.sender == operator, InvalidOperator()); - } else { - // Assert operator's signature `salt` has not already been spent. - require(!operatorSaltIsSpent[operator][operatorSignature.salt], SaltSpent()); - - // Assert that `operatorSignature.signature` is a valid signature for operator set deregistrations. - _checkIsValidSignatureNow({ - signer: operator, - signableDigest: calculateOperatorSetForceDeregistrationTypehash({ - avs: avs, - operatorSetIds: operatorSetIds, - salt: operatorSignature.salt, - expiry: operatorSignature.expiry - }), - signature: operatorSignature.signature, - expiry: operatorSignature.expiry - }); - - // Mutate `operatorSaltIsSpent` to `true` to prevent future respending. - operatorSaltIsSpent[operator][operatorSignature.salt] = true; - } - _deregisterFromOperatorSets(avs, operator, operatorSetIds); - } - - /// @inheritdoc IAVSDirectory - function deregisterOperatorFromOperatorSets( - address operator, - uint32[] calldata operatorSetIds - ) external override onlyWhenNotPaused(PAUSED_OPERATOR_SET_REGISTRATION_AND_DEREGISTRATION) { - _deregisterFromOperatorSets(msg.sender, operator, operatorSetIds); - } - - /// @inheritdoc IAVSDirectory - function addStrategiesToOperatorSet(uint32 operatorSetId, IStrategy[] calldata strategies) external override { - OperatorSet memory operatorSet = OperatorSet(msg.sender, operatorSetId); - require(isOperatorSet[msg.sender][operatorSetId], InvalidOperatorSet()); - bytes32 encodedOperatorSet = _encodeOperatorSet(operatorSet); - for (uint256 i = 0; i < strategies.length; i++) { - require( - _operatorSetStrategies[encodedOperatorSet].add(address(strategies[i])), StrategyAlreadyInOperatorSet() - ); - emit StrategyAddedToOperatorSet(operatorSet, strategies[i]); - } - } - - /// @inheritdoc IAVSDirectory - function removeStrategiesFromOperatorSet(uint32 operatorSetId, IStrategy[] calldata strategies) external override { - OperatorSet memory operatorSet = OperatorSet(msg.sender, operatorSetId); - require(isOperatorSet[msg.sender][operatorSetId], InvalidOperatorSet()); - bytes32 encodedOperatorSet = _encodeOperatorSet(operatorSet); - for (uint256 i = 0; i < strategies.length; i++) { - require( - _operatorSetStrategies[encodedOperatorSet].remove(address(strategies[i])), StrategyNotInOperatorSet() - ); - emit StrategyRemovedFromOperatorSet(operatorSet, strategies[i]); - } - } - /// @inheritdoc IAVSDirectory function updateAVSMetadataURI( string calldata metadataURI @@ -220,9 +72,6 @@ contract AVSDirectory is address operator, ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature ) external override onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS) { - // Assert that the AVS is not an operator set AVS. - require(!isOperatorSetAVS[msg.sender], InvalidAVS()); - // Assert that the `operator` is not actively registered to the AVS. require( avsOperatorStatus[msg.sender][operator] != OperatorAVSRegistrationStatus.REGISTERED, @@ -266,8 +115,6 @@ contract AVSDirectory is avsOperatorStatus[msg.sender][operator] == OperatorAVSRegistrationStatus.REGISTERED, OperatorNotRegisteredToAVS() ); - // Assert that the AVS is not an operator set AVS. - require(!isOperatorSetAVS[msg.sender], InvalidAVS()); // Set the operator as deregistered avsOperatorStatus[msg.sender][operator] = OperatorAVSRegistrationStatus.UNREGISTERED; @@ -275,176 +122,12 @@ contract AVSDirectory is emit OperatorAVSRegistrationStatusUpdated(operator, msg.sender, OperatorAVSRegistrationStatus.UNREGISTERED); } - /** - * - * INTERNAL FUNCTIONS - * - */ - - /** - * @notice Helper function used by migration & registration functions to register an operator to operator sets. - * @param avs The AVS that the operator is registering to. - * @param operator The operator to register. - * @param operatorSetIds The IDs of the operator sets. - */ - function _registerToOperatorSets(address operator, address avs, uint32[] calldata operatorSetIds) internal { - // Loop over `operatorSetIds` array and register `operator` for each item. - for (uint256 i = 0; i < operatorSetIds.length; ++i) { - OperatorSet memory operatorSet = OperatorSet(avs, operatorSetIds[i]); - - require(isOperatorSet[avs][operatorSetIds[i]], InvalidOperatorSet()); - - bytes32 encodedOperatorSet = _encodeOperatorSet(operatorSet); - - _operatorSetsMemberOf[operator].add(encodedOperatorSet); - - _operatorSetMembers[encodedOperatorSet].add(operator); - - OperatorSetRegistrationStatus storage registrationStatus = - operatorSetStatus[avs][operator][operatorSetIds[i]]; - - require(!registrationStatus.registered, InvalidOperator()); - - registrationStatus.registered = true; - - emit OperatorAddedToOperatorSet(operator, operatorSet); - } - } - - /** - * @notice Internal function to deregister an operator from an operator set. - * - * @param avs The AVS that the operator is deregistering from. - * @param operator The operator to deregister. - * @param operatorSetIds The IDs of the operator sets. - */ - function _deregisterFromOperatorSets(address avs, address operator, uint32[] calldata operatorSetIds) internal { - // Loop over `operatorSetIds` array and deregister `operator` for each item. - for (uint256 i = 0; i < operatorSetIds.length; ++i) { - OperatorSet memory operatorSet = OperatorSet(avs, operatorSetIds[i]); - - bytes32 encodedOperatorSet = _encodeOperatorSet(operatorSet); - - _operatorSetsMemberOf[operator].remove(encodedOperatorSet); - - _operatorSetMembers[encodedOperatorSet].remove(operator); - - OperatorSetRegistrationStatus storage registrationStatus = - operatorSetStatus[avs][operator][operatorSetIds[i]]; - - require(registrationStatus.registered, InvalidOperator()); - - registrationStatus.registered = false; - registrationStatus.lastDeregisteredTimestamp = uint32(block.timestamp); - - emit OperatorRemovedFromOperatorSet(operator, operatorSet); - } - } - /** * * VIEW FUNCTIONS * */ - /// @inheritdoc IAVSDirectory - function operatorSetsMemberOfAtIndex(address operator, uint256 index) external view returns (OperatorSet memory) { - return _decodeOperatorSet(_operatorSetsMemberOf[operator].at(index)); - } - - /// @inheritdoc IAVSDirectory - function operatorSetMemberAtIndex(OperatorSet memory operatorSet, uint256 index) external view returns (address) { - return _operatorSetMembers[_encodeOperatorSet(operatorSet)].at(index); - } - - /// @inheritdoc IAVSDirectory - function getNumOperatorSetsOfOperator( - address operator - ) external view returns (uint256) { - return _operatorSetsMemberOf[operator].length(); - } - - /// @inheritdoc IAVSDirectory - function getOperatorSetsOfOperator( - address operator, - uint256 start, - uint256 length - ) public view returns (OperatorSet[] memory operatorSets) { - uint256 maxLength = _operatorSetsMemberOf[operator].length() - start; - if (length > maxLength) length = maxLength; - operatorSets = new OperatorSet[](length); - for (uint256 i; i < length; ++i) { - operatorSets[i] = _decodeOperatorSet(_operatorSetsMemberOf[operator].at(start + i)); - } - } - - /// @inheritdoc IAVSDirectory - function getOperatorsInOperatorSet( - OperatorSet memory operatorSet, - uint256 start, - uint256 length - ) external view returns (address[] memory operators) { - bytes32 encodedOperatorSet = _encodeOperatorSet(operatorSet); - uint256 maxLength = _operatorSetMembers[encodedOperatorSet].length() - start; - if (length > maxLength) length = maxLength; - operators = new address[](length); - for (uint256 i; i < length; ++i) { - operators[i] = _operatorSetMembers[encodedOperatorSet].at(start + i); - } - } - - /// @inheritdoc IAVSDirectory - function getStrategiesInOperatorSet( - OperatorSet memory operatorSet - ) external view returns (IStrategy[] memory strategies) { - bytes32 encodedOperatorSet = _encodeOperatorSet(operatorSet); - uint256 length = _operatorSetStrategies[encodedOperatorSet].length(); - - strategies = new IStrategy[](length); - for (uint256 i; i < length; ++i) { - strategies[i] = IStrategy(_operatorSetStrategies[encodedOperatorSet].at(i)); - } - } - - /// @inheritdoc IAVSDirectory - function getNumOperatorsInOperatorSet( - OperatorSet memory operatorSet - ) external view returns (uint256) { - return _operatorSetMembers[_encodeOperatorSet(operatorSet)].length(); - } - - /// @inheritdoc IAVSDirectory - function inTotalOperatorSets( - address operator - ) external view returns (uint256) { - return _operatorSetsMemberOf[operator].length(); - } - - /// @inheritdoc IAVSDirectory - function isMember(address operator, OperatorSet memory operatorSet) public view returns (bool) { - return _operatorSetsMemberOf[operator].contains(_encodeOperatorSet(operatorSet)); - } - - /// @inheritdoc IAVSDirectory - function isOperatorSlashable(address operator, OperatorSet memory operatorSet) public view returns (bool) { - if (isMember(operator, operatorSet)) return true; - - OperatorSetRegistrationStatus memory status = - operatorSetStatus[operatorSet.avs][operator][operatorSet.operatorSetId]; - - return block.timestamp < status.lastDeregisteredTimestamp + DEALLOCATION_DELAY; - } - - /// @inheritdoc IAVSDirectory - function isOperatorSetBatch( - OperatorSet[] calldata operatorSets - ) public view returns (bool) { - for (uint256 i = 0; i < operatorSets.length; ++i) { - if (!isOperatorSet[operatorSets[i].avs][operatorSets[i].operatorSetId]) return false; - } - return true; - } - /// @inheritdoc IAVSDirectory function calculateOperatorAVSRegistrationDigestHash( address operator, @@ -456,48 +139,4 @@ contract AVSDirectory is keccak256(abi.encode(OPERATOR_AVS_REGISTRATION_TYPEHASH, operator, avs, salt, expiry)) ); } - - /// @inheritdoc IAVSDirectory - function calculateOperatorSetRegistrationDigestHash( - address avs, - uint32[] calldata operatorSetIds, - bytes32 salt, - uint256 expiry - ) public view override returns (bytes32) { - return _calculateSignableDigest( - keccak256(abi.encode(OPERATOR_SET_REGISTRATION_TYPEHASH, avs, operatorSetIds, salt, expiry)) - ); - } - - /// @inheritdoc IAVSDirectory - function calculateOperatorSetForceDeregistrationTypehash( - address avs, - uint32[] calldata operatorSetIds, - bytes32 salt, - uint256 expiry - ) public view returns (bytes32) { - return _calculateSignableDigest( - keccak256(abi.encode(OPERATOR_SET_FORCE_DEREGISTRATION_TYPEHASH, avs, operatorSetIds, salt, expiry)) - ); - } - - /// @dev Returns an `OperatorSet` encoded into a 32-byte value. - /// @param operatorSet The `OperatorSet` to encode. - function _encodeOperatorSet( - OperatorSet memory operatorSet - ) internal pure returns (bytes32) { - return bytes32(abi.encodePacked(operatorSet.avs, uint96(operatorSet.operatorSetId))); - } - - /// @dev Returns an `OperatorSet` decoded from an encoded 32-byte value. - /// @param encoded The encoded `OperatorSet` to decode. - /// @dev Assumes `encoded` is encoded via `_encodeOperatorSet(operatorSet)`. - function _decodeOperatorSet( - bytes32 encoded - ) internal pure returns (OperatorSet memory) { - return OperatorSet({ - avs: address(uint160(uint256(encoded) >> 96)), - operatorSetId: uint32(uint256(encoded) & type(uint96).max) - }); - } } diff --git a/src/contracts/core/AVSDirectoryStorage.sol b/src/contracts/core/AVSDirectoryStorage.sol index d262220b0..9198d246f 100644 --- a/src/contracts/core/AVSDirectoryStorage.sol +++ b/src/contracts/core/AVSDirectoryStorage.sol @@ -1,15 +1,10 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.27; -import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; - import "../interfaces/IAVSDirectory.sol"; import "../interfaces/IDelegationManager.sol"; abstract contract AVSDirectoryStorage is IAVSDirectory { - using EnumerableSet for EnumerableSet.Bytes32Set; - using EnumerableSet for EnumerableSet.AddressSet; - // Constants /// @notice The EIP-712 typehash for the `Registration` struct used by the contract @@ -35,10 +30,6 @@ abstract contract AVSDirectoryStorage is IAVSDirectory { /// @notice The DelegationManager contract for EigenLayer IDelegationManager public immutable delegation; - /// @notice Delay before deallocations are completable and can be added back into freeMagnitude - /// In this window, deallocations still remain slashable by the operatorSet they were allocated to. - uint32 public immutable DEALLOCATION_DELAY; - // Mutatables /// @dev Do not remove, deprecated storage. @@ -51,33 +42,12 @@ abstract contract AVSDirectoryStorage is IAVSDirectory { /// @notice Returns whether a `salt` has been used by a given `operator`. mapping(address operator => mapping(bytes32 salt => bool isSpent)) public operatorSaltIsSpent; - /// @notice Returns whether a given `avs` is an operator set avs. - mapping(address avs => bool) public isOperatorSetAVS; - - /// @notice Returns whether an `operatorSetId` has been created for a given `avs`. - mapping(address avs => mapping(uint32 operatorSetId => bool)) public isOperatorSet; - - /// @notice Returns the list of operator sets that an `operator` is registered to. - /// @dev Each item is formatted as `bytes32(abi.encodePacked(avs, uint96(operatorSetId)))`. - mapping(address operator => EnumerableSet.Bytes32Set operatorSets) internal _operatorSetsMemberOf; - - /// @notice Returns the list of `operators` that are members of a given operator set. - /// @dev Each key is formatted as `bytes32(abi.encodePacked(avs, uint96(operatorSetId)))`. - mapping(bytes32 operatorSetKey => EnumerableSet.AddressSet operators) internal _operatorSetMembers; - - /// @notice Returns the list of `strategies` associated with a given operator set. - /// @dev Each key is formatted as `bytes32(abi.encodePacked(avs, uint96(operatorSetId)))`. - mapping(bytes32 operatorSetKey => EnumerableSet.AddressSet strategies) internal _operatorSetStrategies; - - /// @notice Returns the registration status of an `operator` for a given `avs` and `operatorSetId`. - mapping(address operator => mapping(address avs => mapping(uint32 operatorSetId => OperatorSetRegistrationStatus))) - public operatorSetStatus; - // Construction - constructor(IDelegationManager _delegation, uint32 _DEALLOCATION_DELAY) { + constructor( + IDelegationManager _delegation + ) { delegation = _delegation; - DEALLOCATION_DELAY = _DEALLOCATION_DELAY; } /** diff --git a/src/contracts/core/AllocationManager.sol b/src/contracts/core/AllocationManager.sol index b8eee6f15..767e194f1 100644 --- a/src/contracts/core/AllocationManager.sol +++ b/src/contracts/core/AllocationManager.sol @@ -7,6 +7,7 @@ import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol import "../permissions/Pausable.sol"; import "../libraries/SlashingLib.sol"; +import "../libraries/OperatorSetLib.sol"; import "./AllocationManagerStorage.sol"; contract AllocationManager is @@ -16,8 +17,11 @@ contract AllocationManager is AllocationManagerStorage, ReentrancyGuardUpgradeable { - using Snapshots for Snapshots.DefaultWadHistory; using DoubleEndedQueue for DoubleEndedQueue.Bytes32Deque; + using EnumerableSet for *; + + using Snapshots for Snapshots.DefaultWadHistory; + using OperatorSetLib for OperatorSet; using SlashingLib for uint256; /** @@ -27,17 +31,15 @@ contract AllocationManager is */ /** - * @dev Initializes the immutable addresses of the strategy mananger, delegationManage, - * and eigenpodManager contracts + * @dev Initializes the DelegationManager address, the deallocation delay, and the allocation configuration delay. */ constructor( IDelegationManager _delegation, - IAVSDirectory _avsDirectory, IPauserRegistry _pauserRegistry, uint32 _DEALLOCATION_DELAY, uint32 _ALLOCATION_CONFIGURATION_DELAY ) - AllocationManagerStorage(_delegation, _avsDirectory, _DEALLOCATION_DELAY, _ALLOCATION_CONFIGURATION_DELAY) + AllocationManagerStorage(_delegation, _DEALLOCATION_DELAY, _ALLOCATION_CONFIGURATION_DELAY) Pausable(_pauserRegistry) { _disableInitializers(); @@ -55,138 +57,139 @@ contract AllocationManager is ) external onlyWhenNotPaused(PAUSED_OPERATOR_SLASHING) { require(0 < params.wadToSlash && params.wadToSlash <= WAD, InvalidWadToSlash()); - // Check that the operator is registered and slashable - OperatorSet memory operatorSet = OperatorSet({avs: msg.sender, operatorSetId: params.operatorSetId}); - bytes32 operatorSetKey = _encodeOperatorSet(operatorSet); - require(avsDirectory.isOperatorSlashable(params.operator, operatorSet), InvalidOperator()); - - // Record the proportion of 1e18 that the operator's total shares that are being slashed - uint256[] memory wadSlashed = new uint256[](params.strategies.length); - - for (uint256 i = 0; i < params.strategies.length; ++i) { - PendingMagnitudeInfo memory info = - _getPendingMagnitudeInfo(params.operator, params.strategies[i], operatorSetKey); + // Check that the operator set exists and the operator is registered to it + OperatorSet memory operatorSet = OperatorSet(msg.sender, params.operatorSetId); + bool isRegistered = _isRegistered(params.operator, operatorSet); + require(_operatorSets[operatorSet.avs].contains(operatorSet.id), InvalidOperatorSet()); + require(isRegistered, NotMemberOfSet()); + + uint256 length = _operatorSetStrategies[operatorSet.key()].length(); + IStrategy[] memory strategiesSlashed = new IStrategy[](length); + uint256[] memory wadSlashed = new uint256[](length); + + // For each strategy in the operator set, slash any existing allocation + for (uint256 i = 0; i < length; i++) { + // 1. Get the operator's allocation info for the strategy and operator set + IStrategy strategy = IStrategy(_operatorSetStrategies[operatorSet.key()].at(i)); + (StrategyInfo memory info, Allocation memory allocation) = + _getUpdatedAllocation(params.operator, operatorSet.key(), strategy); + strategiesSlashed[i] = strategy; + + // 2. Skip if the operator does not have a slashable allocation + // NOTE: this "if" is equivalent to: `if (!_isAllocationSlashable)`, because the other + // conditions in this method are already true (isRegistered + operatorSetStrategies.contains) + if (allocation.currentMagnitude == 0) { + continue; + } - require(info.currentMagnitude > 0, OperatorNotAllocated()); + // 3. Calculate the amount of magnitude being slashed, and subtract from + // the operator's currently-allocated magnitude, as well as the strategy's + // max and encumbered magnitudes + uint64 slashedMagnitude = uint64(uint256(allocation.currentMagnitude).mulWadRoundUp(params.wadToSlash)); + uint256 sharesWadSlashed = uint256(slashedMagnitude).divWad(info.maxMagnitude); + wadSlashed[i] = sharesWadSlashed; - // 1. Calculate slashing amount and update current/encumbered magnitude - uint64 slashedMagnitude = uint64(uint256(info.currentMagnitude).mulWadRoundUp(params.wadToSlash)); - info.currentMagnitude -= slashedMagnitude; + allocation.currentMagnitude -= slashedMagnitude; + info.maxMagnitude -= slashedMagnitude; info.encumberedMagnitude -= slashedMagnitude; - // 2. If there is a pending deallocation, reduce pending deallocation proportionally. - // This ensures that when the deallocation is cleared, less magnitude is freed. - if (info.pendingDiff < 0) { - uint64 slashedPending = uint64(uint256(uint128(-info.pendingDiff)).mulWadRoundUp(params.wadToSlash)); - info.pendingDiff += int128(uint128(slashedPending)); + // 4. If there is a pending deallocation, reduce the pending deallocation proportionally. + // This ensures that when the deallocation is completed, less magnitude is freed. + if (allocation.pendingDiff < 0) { + uint64 slashedPending = + uint64(uint256(uint128(-allocation.pendingDiff)).mulWadRoundUp(params.wadToSlash)); + allocation.pendingDiff += int128(uint128(slashedPending)); - emit OperatorSetMagnitudeUpdated( + emit AllocationUpdated( params.operator, operatorSet, - params.strategies[i], - _addInt128(info.currentMagnitude, info.pendingDiff), - info.effectTimestamp + strategy, + _addInt128(allocation.currentMagnitude, allocation.pendingDiff), + allocation.effectBlock ); } - // 3. Update the operator's allocation in storage - _updateMagnitudeInfo({ - operator: params.operator, - strategy: params.strategies[i], - operatorSetKey: operatorSetKey, - info: info - }); - - emit OperatorSetMagnitudeUpdated( - params.operator, operatorSet, params.strategies[i], info.currentMagnitude, uint32(block.timestamp) - ); + // 5. Update state + _updateAllocationInfo(params.operator, operatorSet.key(), strategy, info, allocation); + _updateMaxMagnitude(params.operator, strategy, info.maxMagnitude); - // 4. Reduce the operator's max magnitude - uint64 maxMagnitudeBeforeSlash = _maxMagnitudeHistory[params.operator][params.strategies[i]].latest(); - uint64 maxMagnitudeAfterSlash = maxMagnitudeBeforeSlash - slashedMagnitude; - _maxMagnitudeHistory[params.operator][params.strategies[i]].push({ - key: uint32(block.timestamp), - value: maxMagnitudeAfterSlash - }); - emit MaxMagnitudeUpdated(params.operator, params.strategies[i], maxMagnitudeAfterSlash); - - // 5. Decrease operators shares in the DelegationManager - uint256 sharesWadSlashed = uint256(slashedMagnitude).divWad(maxMagnitudeBeforeSlash); + // 6. Decrease operators shares in the DelegationManager delegation.decreaseOperatorShares({ operator: params.operator, - strategy: params.strategies[i], + strategy: strategy, wadSlashed: sharesWadSlashed }); - - // 6. Record the proportion of shares slashed - wadSlashed[i] = sharesWadSlashed; } - emit OperatorSlashed(params.operator, operatorSet, params.strategies, wadSlashed, params.description); + emit OperatorSlashed(params.operator, operatorSet, strategiesSlashed, wadSlashed, params.description); } /// @inheritdoc IAllocationManager function modifyAllocations( - MagnitudeAllocation[] calldata allocations + AllocateParams[] calldata params ) external onlyWhenNotPaused(PAUSED_MODIFY_ALLOCATIONS) { + // Check that the operator exists and has configured an allocation delay (bool isSet, uint32 operatorAllocationDelay) = getAllocationDelay(msg.sender); require(isSet, UninitializedAllocationDelay()); - for (uint256 i = 0; i < allocations.length; ++i) { - MagnitudeAllocation calldata allocation = allocations[i]; - require(allocation.operatorSets.length == allocation.magnitudes.length, InputArrayLengthMismatch()); - require(avsDirectory.isOperatorSetBatch(allocation.operatorSets), InvalidOperatorSet()); - - // 1. Check current maxMagnitude matches expected value. This is to check for slashing race conditions - // where an operator gets slashed from an operatorSet and as a result all the configured allocations have larger - // proprtional magnitudes relative to each other. - uint64 maxMagnitude = _maxMagnitudeHistory[msg.sender][allocation.strategy].latest(); - require(maxMagnitude == allocation.expectedMaxMagnitude, InvalidExpectedMaxMagnitude()); - - // 2. For the given (operator,strategy) clear any clearable pending deallocations to free up encumberedMagnitude - _clearDeallocationQueue({operator: msg.sender, strategy: allocation.strategy, numToClear: type(uint16).max}); - - for (uint256 j = 0; j < allocation.operatorSets.length; ++j) { - bytes32 operatorSetKey = _encodeOperatorSet(allocation.operatorSets[j]); - - // Ensure there is not already a pending modification - PendingMagnitudeInfo memory info = - _getPendingMagnitudeInfo(msg.sender, allocation.strategy, operatorSetKey); - require(info.pendingDiff == 0, ModificationAlreadyPending()); - - info.pendingDiff = _calcDelta(info.currentMagnitude, allocation.magnitudes[j]); - require(info.pendingDiff != 0, SameMagnitude()); - - // Calculate the effectTimestamp for the modification - if (info.pendingDiff < 0) { - info.effectTimestamp = uint32(block.timestamp) + DEALLOCATION_DELAY; - - // Add the operatorSet to the deallocation queue - deallocationQueue[msg.sender][allocation.strategy].pushBack(operatorSetKey); - } else if (info.pendingDiff > 0) { - info.effectTimestamp = uint32(block.timestamp) + operatorAllocationDelay; - - // For allocations, immediately add to encumberedMagnitude to ensure the operator - // can't allocate more than their maximum - info.encumberedMagnitude = _addInt128(info.encumberedMagnitude, info.pendingDiff); - require(info.encumberedMagnitude <= maxMagnitude, InsufficientAllocatableMagnitude()); + for (uint256 i = 0; i < params.length; i++) { + require(params[i].strategies.length == params[i].newMagnitudes.length, InputArrayLengthMismatch()); + + // Check that the operator set exists and get the operator's registration status + // Operators do not need to be registered for an operator set in order to allocate + // slashable magnitude to the set. In fact, it is expected that operators will + // allocate magnitude before registering, as AVS's will likely only accept + // registrations from operators that are already slashable. + OperatorSet calldata operatorSet = params[i].operatorSet; + bool isRegistered = _isRegistered(msg.sender, operatorSet); + require(_operatorSets[operatorSet.avs].contains(operatorSet.id), InvalidOperatorSet()); + + for (uint256 j = 0; j < params[i].strategies.length; j++) { + IStrategy strategy = params[i].strategies[j]; + + // 1. If the operator has any pending deallocations for this strategy, clear them + // to free up magnitude for allocation. Fetch the operator's up to date allocation + // info and ensure there is no remaining pending modification. + _clearDeallocationQueue(msg.sender, strategy, type(uint16).max); + + (StrategyInfo memory info, Allocation memory allocation) = + _getUpdatedAllocation(msg.sender, operatorSet.key(), strategy); + require(allocation.pendingDiff == 0, ModificationAlreadyPending()); + + // 2. Check whether the operator's allocation is slashable. If not, we allow instant + // deallocation. + bool isSlashable = _isAllocationSlashable(operatorSet, strategy, allocation, isRegistered); + + // 3. Calculate the change in magnitude + allocation.pendingDiff = _calcDelta(allocation.currentMagnitude, params[i].newMagnitudes[j]); + require(allocation.pendingDiff != 0, SameMagnitude()); + + // 4. Handle deallocation/allocation + if (allocation.pendingDiff < 0) { + if (isSlashable) { + // If the operator is slashable, deallocated magnitude will be freed after + // the deallocation delay. This magnitude remains slashable until then. + deallocationQueue[msg.sender][strategy].pushBack(operatorSet.key()); + + allocation.effectBlock = uint32(block.number) + DEALLOCATION_DELAY; + } else { + // Deallocation immediately updates/frees magnitude if the operator is not slashable + info.encumberedMagnitude = _addInt128(info.encumberedMagnitude, allocation.pendingDiff); + + allocation.currentMagnitude = params[i].newMagnitudes[j]; + allocation.pendingDiff = 0; + } + } else if (allocation.pendingDiff > 0) { + // Allocation immediately consumes available magnitude, but the additional + // magnitude does not become slashable until after the allocation delay + info.encumberedMagnitude = _addInt128(info.encumberedMagnitude, allocation.pendingDiff); + require(info.encumberedMagnitude <= info.maxMagnitude, InsufficientMagnitude()); + + allocation.effectBlock = uint32(block.number) + operatorAllocationDelay; } - // Update the modification in storage - _updateMagnitudeInfo({ - operator: msg.sender, - strategy: allocation.strategy, - operatorSetKey: operatorSetKey, - info: info - }); - - emit OperatorSetMagnitudeUpdated( - msg.sender, - allocation.operatorSets[j], - allocation.strategy, - _addInt128(info.currentMagnitude, info.pendingDiff), - info.effectTimestamp - ); + // 5. Update state + _updateAllocationInfo(msg.sender, operatorSet.key(), strategy, info, allocation); } } } @@ -198,13 +201,68 @@ contract AllocationManager is uint16[] calldata numToClear ) external onlyWhenNotPaused(PAUSED_MODIFY_ALLOCATIONS) { require(strategies.length == numToClear.length, InputArrayLengthMismatch()); - require(delegation.isOperator(operator), OperatorNotRegistered()); for (uint256 i = 0; i < strategies.length; ++i) { _clearDeallocationQueue({operator: operator, strategy: strategies[i], numToClear: numToClear[i]}); } } + /// @inheritdoc IAllocationManager + function registerForOperatorSets( + RegisterParams calldata params + ) external onlyWhenNotPaused(PAUSED_OPERATOR_SET_REGISTRATION_AND_DEREGISTRATION) { + // Check that the operator exists + require(delegation.isOperator(msg.sender), InvalidOperator()); + + for (uint256 i = 0; i < params.operatorSetIds.length; i++) { + // Check the operator set exists and the operator is not currently registered to it + OperatorSet memory operatorSet = OperatorSet(params.avs, params.operatorSetIds[i]); + require(_operatorSets[operatorSet.avs].contains(operatorSet.id), InvalidOperatorSet()); + require(!_isRegistered(msg.sender, operatorSet), AlreadyMemberOfSet()); + + // Add operator to operator set + registeredSets[msg.sender].add(operatorSet.key()); + _operatorSetMembers[operatorSet.key()].add(msg.sender); + emit OperatorAddedToOperatorSet(msg.sender, operatorSet); + + // Mark the operator registered + registrationStatus[msg.sender][operatorSet.key()].registered = true; + } + + // Call the AVS to complete registration. If the AVS reverts, registration will fail. + getAVSRegistrar(params.avs).registerOperator(msg.sender, params.operatorSetIds, params.data); + } + + /// @inheritdoc IAllocationManager + function deregisterFromOperatorSets( + DeregisterParams calldata params + ) external onlyWhenNotPaused(PAUSED_OPERATOR_SET_REGISTRATION_AND_DEREGISTRATION) { + require(msg.sender == params.operator || msg.sender == params.avs, InvalidCaller()); + + for (uint256 i = 0; i < params.operatorSetIds.length; i++) { + // Check the operator set exists and the operator is registered to it + OperatorSet memory operatorSet = OperatorSet(params.avs, params.operatorSetIds[i]); + require(_operatorSets[params.avs].contains(operatorSet.id), InvalidOperatorSet()); + require(registrationStatus[params.operator][operatorSet.key()].registered, NotMemberOfSet()); + + // Remove operator from operator set + registeredSets[params.operator].remove(operatorSet.key()); + _operatorSetMembers[operatorSet.key()].remove(params.operator); + emit OperatorRemovedFromOperatorSet(params.operator, operatorSet); + + // Mark operator deregistered until the DEALLOCATION_DELAY passes + // forgefmt: disable-next-item + registrationStatus[params.operator][operatorSet.key()] = RegistrationStatus({ + registered: false, + registeredUntil: uint32(block.number) + DEALLOCATION_DELAY + }); + } + + // Call the AVS to complete deregistration. Even if the AVS reverts, the operator is + // considered deregistered + try getAVSRegistrar(params.avs).deregisterOperator(params.operator, params.operatorSetIds) {} catch {} + } + /// @inheritdoc IAllocationManager function setAllocationDelay(address operator, uint32 delay) external { require(msg.sender == address(delegation), OnlyDelegationManager()); @@ -219,6 +277,65 @@ contract AllocationManager is _setAllocationDelay(msg.sender, delay); } + /// @inheritdoc IAllocationManager + function setAVSRegistrar( + IAVSRegistrar registrar + ) external { + _avsRegistrar[msg.sender] = registrar; + emit AVSRegistrarSet(msg.sender, getAVSRegistrar(msg.sender)); + } + + /// @inheritdoc IAllocationManager + function updateAVSMetadataURI( + string calldata metadataURI + ) external { + emit AVSMetadataURIUpdated(msg.sender, metadataURI); + } + + /// @inheritdoc IAllocationManager + function createOperatorSets( + CreateSetParams[] calldata params + ) external { + for (uint256 i = 0; i < params.length; i++) { + OperatorSet memory operatorSet = OperatorSet(msg.sender, params[i].operatorSetId); + + // Create the operator set, ensuring it does not already exist + require(_operatorSets[msg.sender].add(operatorSet.id) == true, InvalidOperatorSet()); + emit OperatorSetCreated(OperatorSet(msg.sender, operatorSet.id)); + + // Add strategies to the operator set + bytes32 operatorSetKey = operatorSet.key(); + for (uint256 j = 0; j < params[i].strategies.length; j++) { + _operatorSetStrategies[operatorSetKey].add(address(params[i].strategies[j])); + emit StrategyAddedToOperatorSet(operatorSet, params[i].strategies[j]); + } + } + } + + /// @inheritdoc IAllocationManager + function addStrategiesToOperatorSet(uint32 operatorSetId, IStrategy[] calldata strategies) external { + OperatorSet memory operatorSet = OperatorSet(msg.sender, operatorSetId); + require(_operatorSets[msg.sender].contains(operatorSet.id), InvalidOperatorSet()); + + bytes32 operatorSetKey = operatorSet.key(); + for (uint256 i = 0; i < strategies.length; i++) { + require(_operatorSetStrategies[operatorSetKey].add(address(strategies[i])), StrategyAlreadyInOperatorSet()); + emit StrategyAddedToOperatorSet(operatorSet, strategies[i]); + } + } + + /// @inheritdoc IAllocationManager + function removeStrategiesFromOperatorSet(uint32 operatorSetId, IStrategy[] calldata strategies) external { + OperatorSet memory operatorSet = OperatorSet(msg.sender, operatorSetId); + require(_operatorSets[msg.sender].contains(operatorSet.id), InvalidOperatorSet()); + + bytes32 operatorSetKey = operatorSet.key(); + for (uint256 i = 0; i < strategies.length; i++) { + require(_operatorSetStrategies[operatorSetKey].remove(address(strategies[i])), StrategyNotInOperatorSet()); + emit StrategyRemovedFromOperatorSet(operatorSet, strategies[i]); + } + } + /** * * INTERNAL FUNCTIONS @@ -237,16 +354,18 @@ contract AllocationManager is while (length > 0 && numCleared < numToClear) { bytes32 operatorSetKey = deallocationQueue[operator][strategy].front(); - PendingMagnitudeInfo memory info = _getPendingMagnitudeInfo(operator, strategy, operatorSetKey); + (StrategyInfo memory info, Allocation memory allocation) = + _getUpdatedAllocation(msg.sender, operatorSetKey, strategy); - // If we've reached a pending deallocation that isn't clearable yet, - // we can stop. Any subsequent deallocation will also be unclearable. - if (block.timestamp < info.effectTimestamp) { + // If we've reached a pending deallocation that isn't completable yet, + // we can stop. Any subsequent deallocation will also be uncompletable. + if (block.number < allocation.effectBlock) { break; } - // Update the operator's allocation in storage - _updateMagnitudeInfo(operator, strategy, operatorSetKey, info); + // Update state. This completes the deallocation, because `_getUpdatedAllocation` + // gave us strategy/allocation info as if the deallocation was already completed. + _updateAllocationInfo(operator, operatorSetKey, strategy, info, allocation); // Remove the deallocation from the queue deallocationQueue[operator][strategy].popFront(); @@ -256,82 +375,133 @@ contract AllocationManager is } /** - * @dev Sets the operator's allocation delay. This is the time between an operator + * @dev Sets the operator's allocation delay. This is the number of blocks between an operator * allocating magnitude to an operator set, and the magnitude becoming slashable. * @param operator The operator to set the delay on behalf of. - * @param delay The allocation delay in seconds. + * @param delay The allocation delay in blocks. */ function _setAllocationDelay(address operator, uint32 delay) internal { AllocationDelayInfo memory info = _allocationDelayInfo[operator]; // If there is a pending delay that can be applied now, set it - if (info.effectTimestamp != 0 && block.timestamp >= info.effectTimestamp) { + if (info.effectBlock != 0 && block.number >= info.effectBlock) { info.delay = info.pendingDelay; info.isSet = true; } info.pendingDelay = delay; - info.effectTimestamp = uint32(block.timestamp + ALLOCATION_CONFIGURATION_DELAY); + info.effectBlock = uint32(block.number) + ALLOCATION_CONFIGURATION_DELAY; _allocationDelayInfo[operator] = info; - emit AllocationDelaySet(operator, delay, info.effectTimestamp); + emit AllocationDelaySet(operator, delay, info.effectBlock); + } + + function _isRegistered(address operator, OperatorSet memory operatorSet) internal view returns (bool) { + RegistrationStatus memory status = registrationStatus[operator][operatorSet.key()]; + + return status.registered || block.number < status.registeredUntil; + } + + function _isAllocationSlashable( + OperatorSet memory operatorSet, + IStrategy strategy, + Allocation memory allocation, + bool isRegistered + ) internal view returns (bool) { + // If the operator set does not use this strategy, any allocation from it is not slashable + if (!_operatorSetStrategies[operatorSet.key()].contains(address(strategy))) { + return false; + } + + // If the operator is not registered to the operator set, any allocation is not slashable + if (!isRegistered) { + return false; + } + + // The allocation is not slashable if there is nothing allocated + if (allocation.currentMagnitude == 0) { + return false; + } + + return true; } /** * @dev For an operator set, get the operator's effective allocated magnitude. - * If the operator set has a pending deallocation that can be cleared at the - * current timestamp, this method returns a view of the allocation as if the deallocation - * was cleared. + * If the operator set has a pending deallocation that can be completed at the + * current block number, this method returns a view of the allocation as if the deallocation + * was completed. * @return info the effective allocated and pending magnitude for the operator set, and * the effective encumbered magnitude for all operator sets belonging to this strategy */ - function _getPendingMagnitudeInfo( + function _getUpdatedAllocation( address operator, - IStrategy strategy, - bytes32 operatorSetKey - ) internal view returns (PendingMagnitudeInfo memory info) { - MagnitudeInfo memory mInfo = _operatorMagnitudeInfo[operator][strategy][operatorSetKey]; - uint64 _encumberedMagnitude = encumberedMagnitude[operator][strategy]; - - // If the pending change can't be cleared yet - if (block.timestamp < mInfo.effectTimestamp) { - return PendingMagnitudeInfo({ - encumberedMagnitude: _encumberedMagnitude, - currentMagnitude: mInfo.currentMagnitude, - pendingDiff: mInfo.pendingDiff, - effectTimestamp: mInfo.effectTimestamp - }); + bytes32 operatorSetKey, + IStrategy strategy + ) internal view returns (StrategyInfo memory, Allocation memory) { + StrategyInfo memory info = StrategyInfo({ + maxMagnitude: _maxMagnitudeHistory[operator][strategy].latest(), + encumberedMagnitude: encumberedMagnitude[operator][strategy] + }); + + Allocation memory allocation = allocations[operator][operatorSetKey][strategy]; + + // If the pending change can't be completed yet, return as-is + if (block.number < allocation.effectBlock) { + return (info, allocation); } - // Pending change can be cleared - add delta to current magnitude - info.currentMagnitude = _addInt128(mInfo.currentMagnitude, mInfo.pendingDiff); - info.encumberedMagnitude = _encumberedMagnitude; - info.effectTimestamp = 0; - info.pendingDiff = 0; + // Otherwise, complete the pending change and return updated info + allocation.currentMagnitude = _addInt128(allocation.currentMagnitude, allocation.pendingDiff); - // If the cleared change was a deallocation, update encumbered magnitude - if (mInfo.pendingDiff < 0) { - info.encumberedMagnitude = _addInt128(_encumberedMagnitude, mInfo.pendingDiff); + // If the completed change was a deallocation, update used magnitude + if (allocation.pendingDiff < 0) { + info.encumberedMagnitude = _addInt128(info.encumberedMagnitude, allocation.pendingDiff); } - return info; + allocation.effectBlock = 0; + allocation.pendingDiff = 0; + + return (info, allocation); } - /// @notice Update the operator's magnitude info in storage and their encumbered magnitude. - function _updateMagnitudeInfo( + function _updateAllocationInfo( address operator, - IStrategy strategy, bytes32 operatorSetKey, - PendingMagnitudeInfo memory info + IStrategy strategy, + StrategyInfo memory info, + Allocation memory allocation ) internal { - _operatorMagnitudeInfo[operator][strategy][operatorSetKey] = MagnitudeInfo({ - currentMagnitude: info.currentMagnitude, - pendingDiff: info.pendingDiff, - effectTimestamp: info.effectTimestamp - }); - + // Update encumbered magnitude encumberedMagnitude[operator][strategy] = info.encumberedMagnitude; emit EncumberedMagnitudeUpdated(operator, strategy, info.encumberedMagnitude); + + // Update allocation for this operator set from the strategy + allocations[operator][operatorSetKey][strategy] = allocation; + emit AllocationUpdated( + operator, OperatorSetLib.decode(operatorSetKey), strategy, allocation.currentMagnitude, uint32(block.number) + ); + + // Note: these no-op if the sets already contain the added values (or do not contain removed ones) + if (allocation.pendingDiff != 0) { + // If we have a pending modification, ensure the allocation is in the operator's + // list of enumerable strategies/sets. + allocatedStrategies[operator][operatorSetKey].add(address(strategy)); + allocatedSets[operator].add(operatorSetKey); + } else if (allocation.currentMagnitude == 0) { + // If we do NOT have a pending modification, and no existing magnitude, remove the + // allocation from the operator's lists. + allocatedStrategies[operator][operatorSetKey].remove(address(strategy)); + + if (allocatedStrategies[operator][operatorSetKey].length() == 0) { + allocatedSets[operator].remove(operatorSetKey); + } + } + } + + function _updateMaxMagnitude(address operator, IStrategy strategy, uint64 newMaxMagnitude) internal { + _maxMagnitudeHistory[operator][strategy].push({key: uint32(block.number), value: newMaxMagnitude}); + emit MaxMagnitudeUpdated(operator, strategy, newMaxMagnitude); } function _calcDelta(uint64 currentMagnitude, uint64 newMagnitude) internal pure returns (int128) { @@ -342,26 +512,6 @@ contract AllocationManager is return uint64(uint128(int128(uint128(a)) + b)); } - /// @dev Returns an `OperatorSet` encoded into a 32-byte value. - /// @param operatorSet The `OperatorSet` to encode. - function _encodeOperatorSet( - OperatorSet memory operatorSet - ) internal pure returns (bytes32) { - return bytes32(abi.encodePacked(operatorSet.avs, uint96(operatorSet.operatorSetId))); - } - - /// @dev Returns an `OperatorSet` decoded from an encoded 32-byte value. - /// @param encoded The encoded `OperatorSet` to decode. - /// @dev Assumes `encoded` is encoded via `_encodeOperatorSet(operatorSet)`. - function _decodeOperatorSet( - bytes32 encoded - ) internal pure returns (OperatorSet memory) { - return OperatorSet({ - avs: address(uint160(uint256(encoded) >> 96)), - operatorSetId: uint32(uint256(encoded) & type(uint96).max) - }); - } - /** * * VIEW FUNCTIONS @@ -369,90 +519,103 @@ contract AllocationManager is */ /// @inheritdoc IAllocationManager - function getAllocationInfo( + function getAllocatedSets( + address operator + ) external view returns (OperatorSet[] memory) { + uint256 length = allocatedSets[operator].length(); + + OperatorSet[] memory operatorSets = new OperatorSet[](length); + for (uint256 i = 0; i < length; i++) { + operatorSets[i] = OperatorSetLib.decode(allocatedSets[operator].at(i)); + } + + return operatorSets; + } + + /// @inheritdoc IAllocationManager + function getAllocatedStrategies( address operator, - IStrategy strategy - ) external view returns (OperatorSet[] memory, MagnitudeInfo[] memory) { - OperatorSet[] memory operatorSets = - avsDirectory.getOperatorSetsOfOperator({operator: operator, start: 0, length: type(uint256).max}); - MagnitudeInfo[] memory infos = getAllocationInfo(operator, strategy, operatorSets); - return (operatorSets, infos); + OperatorSet memory operatorSet + ) external view returns (IStrategy[] memory) { + address[] memory values = allocatedStrategies[operator][operatorSet.key()].values(); + IStrategy[] memory strategies; + + assembly { + strategies := values + } + + return strategies; } /// @inheritdoc IAllocationManager - function getAllocationInfo( + function getAllocation( address operator, - IStrategy strategy, - OperatorSet[] memory operatorSets - ) public view returns (MagnitudeInfo[] memory) { - MagnitudeInfo[] memory infos = new MagnitudeInfo[](operatorSets.length); + OperatorSet memory operatorSet, + IStrategy strategy + ) public view returns (Allocation memory) { + (, Allocation memory allocation) = _getUpdatedAllocation(operator, operatorSet.key(), strategy); - for (uint256 i = 0; i < operatorSets.length; ++i) { - PendingMagnitudeInfo memory info = _getPendingMagnitudeInfo({ - operator: operator, - strategy: strategy, - operatorSetKey: _encodeOperatorSet(operatorSets[i]) - }); + return allocation; + } - infos[i] = MagnitudeInfo({ - currentMagnitude: info.currentMagnitude, - pendingDiff: info.pendingDiff, - effectTimestamp: info.effectTimestamp - }); + /// @inheritdoc IAllocationManager + function getAllocations( + address[] memory operators, + OperatorSet memory operatorSet, + IStrategy strategy + ) external view returns (Allocation[] memory) { + Allocation[] memory _allocations = new Allocation[](operators.length); + + for (uint256 i = 0; i < operators.length; i++) { + _allocations[i] = getAllocation(operators[i], operatorSet, strategy); } - return infos; + return _allocations; } /// @inheritdoc IAllocationManager - function getAllocationInfo( - OperatorSet calldata operatorSet, - IStrategy[] calldata strategies, - address[] calldata operators - ) public view returns (MagnitudeInfo[][] memory) { - MagnitudeInfo[][] memory infos = new MagnitudeInfo[][](operators.length); - for (uint256 i = 0; i < operators.length; ++i) { - for (uint256 j = 0; j < strategies.length; ++j) { - PendingMagnitudeInfo memory info = _getPendingMagnitudeInfo({ - operator: operators[i], - strategy: strategies[j], - operatorSetKey: _encodeOperatorSet(operatorSet) - }); - - infos[i][j] = MagnitudeInfo({ - currentMagnitude: info.currentMagnitude, - pendingDiff: info.pendingDiff, - effectTimestamp: info.effectTimestamp - }); - } + function getStrategyAllocations( + address operator, + IStrategy strategy + ) external view returns (OperatorSet[] memory, Allocation[] memory) { + uint256 length = allocatedSets[operator].length(); + + OperatorSet[] memory operatorSets = new OperatorSet[](length); + Allocation[] memory _allocations = new Allocation[](length); + + for (uint256 i = 0; i < length; i++) { + OperatorSet memory operatorSet = OperatorSetLib.decode(allocatedSets[operator].at(i)); + + operatorSets[i] = operatorSet; + _allocations[i] = getAllocation(operator, operatorSet, strategy); } - return infos; + return (operatorSets, _allocations); } /// @inheritdoc IAllocationManager function getAllocatableMagnitude(address operator, IStrategy strategy) external view returns (uint64) { // This method needs to simulate clearing any pending deallocations. // This roughly mimics the calculations done in `_clearDeallocationQueue` and - // `_getPendingMagnitudeInfo`, while operating on a `curEncumberedMagnitude` + // `_getUpdatedAllocation`, while operating on a `curEncumberedMagnitude` // rather than continually reading/updating state. uint64 curEncumberedMagnitude = encumberedMagnitude[operator][strategy]; uint256 length = deallocationQueue[operator][strategy].length(); for (uint256 i = 0; i < length; ++i) { bytes32 operatorSetKey = deallocationQueue[operator][strategy].at(i); - MagnitudeInfo memory info = _operatorMagnitudeInfo[operator][strategy][operatorSetKey]; + Allocation memory allocation = allocations[operator][operatorSetKey][strategy]; // If we've reached a pending deallocation that isn't completable yet, // we can stop. Any subsequent modificaitons will also be uncompletable. - if (block.timestamp < info.effectTimestamp) { + if (block.number < allocation.effectBlock) { break; } // The diff is a deallocation. Add to encumbered magnitude. Note that this is a deallocation // queue and allocations aren't considered because encumbered magnitude // is updated as soon as the allocation is created. - curEncumberedMagnitude = _addInt128(curEncumberedMagnitude, info.pendingDiff); + curEncumberedMagnitude = _addInt128(curEncumberedMagnitude, allocation.pendingDiff); } // The difference between the operator's max magnitude and its encumbered magnitude @@ -463,7 +626,7 @@ contract AllocationManager is /// @inheritdoc IAllocationManager function getMaxMagnitudes( address operator, - IStrategy[] calldata strategies + IStrategy[] memory strategies ) external view returns (uint64[] memory) { uint64[] memory maxMagnitudes = new uint64[](strategies.length); @@ -475,15 +638,26 @@ contract AllocationManager is } /// @inheritdoc IAllocationManager - function getMaxMagnitudesAtTimestamp( + function getMaxMagnitudes(address[] memory operators, IStrategy strategy) external view returns (uint64[] memory) { + uint64[] memory maxMagnitudes = new uint64[](operators.length); + + for (uint256 i = 0; i < operators.length; ++i) { + maxMagnitudes[i] = _maxMagnitudeHistory[operators[i]][strategy].latest(); + } + + return maxMagnitudes; + } + + /// @inheritdoc IAllocationManager + function getMaxMagnitudesAtBlock( address operator, - IStrategy[] calldata strategies, - uint32 timestamp + IStrategy[] memory strategies, + uint32 blockNumber ) external view returns (uint64[] memory) { uint64[] memory maxMagnitudes = new uint64[](strategies.length); for (uint256 i = 0; i < strategies.length; ++i) { - maxMagnitudes[i] = _maxMagnitudeHistory[operator][strategies[i]].upperLookup(timestamp); + maxMagnitudes[i] = _maxMagnitudeHistory[operator][strategies[i]].upperLookup(blockNumber); } return maxMagnitudes; @@ -492,61 +666,76 @@ contract AllocationManager is /// @inheritdoc IAllocationManager function getAllocationDelay( address operator - ) public view returns (bool isSet, uint32 delay) { + ) public view returns (bool, uint32) { AllocationDelayInfo memory info = _allocationDelayInfo[operator]; - if (info.effectTimestamp != 0 && block.timestamp >= info.effectTimestamp) { + uint32 delay = info.delay; + bool isSet = info.isSet; + + // If there is a pending delay that can be applied, apply it + if (info.effectBlock != 0 && block.number >= info.effectBlock) { delay = info.pendingDelay; - } else { - delay = info.delay; + isSet = true; } - // Check that the operator has a configured delay that has taken effect. - // This is true if isSet is true OR block.timestamp >= effectTimestamp - // meaning either a delay has been applied or there is a delay set and - // the effectTimestamp has been reached - isSet = info.isSet == true || (info.effectTimestamp != 0 && block.timestamp >= info.effectTimestamp); return (isSet, delay); } /// @inheritdoc IAllocationManager - function getCurrentDelegatedAndSlashableOperatorShares( - OperatorSet calldata operatorSet, - address[] calldata operators, - IStrategy[] calldata strategies - ) external view returns (uint256[][] memory, uint256[][] memory) { - return - getMinDelegatedAndSlashableOperatorSharesBefore(operatorSet, operators, strategies, uint32(block.timestamp)); + function getRegisteredSets( + address operator + ) public view returns (OperatorSet[] memory) { + uint256 length = registeredSets[operator].length(); + OperatorSet[] memory operatorSets = new OperatorSet[](length); + + for (uint256 i = 0; i < length; ++i) { + operatorSets[i] = OperatorSetLib.decode(registeredSets[operator].at(i)); + } + + return operatorSets; } /// @inheritdoc IAllocationManager - function getMinDelegatedAndSlashableOperatorSharesBefore( - OperatorSet calldata operatorSet, - address[] calldata operators, - IStrategy[] calldata strategies, - uint32 beforeTimestamp - ) public view returns (uint256[][] memory, uint256[][] memory) { - require(beforeTimestamp >= block.timestamp, InvalidTimestamp()); - bytes32 operatorSetKey = _encodeOperatorSet(operatorSet); - uint256[][] memory delegatedShares = delegation.getOperatorsShares(operators, strategies); - uint256[][] memory slashableShares = new uint256[][](operators.length); + function isOperatorSet( + OperatorSet memory operatorSet + ) external view returns (bool) { + return _operatorSets[operatorSet.avs].contains(operatorSet.id); + } - for (uint256 i = 0; i < operators.length; ++i) { - address operator = operators[i]; - slashableShares[i] = new uint256[](strategies.length); - for (uint256 j = 0; j < strategies.length; ++j) { - IStrategy strategy = strategies[j]; - MagnitudeInfo memory mInfo = _operatorMagnitudeInfo[operator][strategy][operatorSetKey]; - uint64 slashableMagnitude = mInfo.currentMagnitude; - if (mInfo.effectTimestamp <= beforeTimestamp) { - slashableMagnitude = _addInt128(slashableMagnitude, mInfo.pendingDiff); - } - slashableShares[i][j] = delegatedShares[i][j].mulWad(slashableMagnitude).divWad( - _maxMagnitudeHistory[operator][strategy].latest() - ); - } + /// @inheritdoc IAllocationManager + function getMembers( + OperatorSet memory operatorSet + ) external view returns (address[] memory) { + return _operatorSetMembers[operatorSet.key()].values(); + } + + /// @inheritdoc IAllocationManager + function getMemberCount( + OperatorSet memory operatorSet + ) external view returns (uint256) { + return _operatorSetMembers[operatorSet.key()].length(); + } + + /// @inheritdoc IAllocationManager + function getAVSRegistrar( + address avs + ) public view returns (IAVSRegistrar) { + IAVSRegistrar registrar = _avsRegistrar[avs]; + + return address(registrar) == address(0) ? IAVSRegistrar(avs) : registrar; + } + + /// @inheritdoc IAllocationManager + function getStrategiesInOperatorSet( + OperatorSet memory operatorSet + ) external view returns (IStrategy[] memory) { + address[] memory values = _operatorSetStrategies[operatorSet.key()].values(); + IStrategy[] memory strategies; + + assembly { + strategies := values } - return (delegatedShares, slashableShares); + return strategies; } } diff --git a/src/contracts/core/AllocationManagerStorage.sol b/src/contracts/core/AllocationManagerStorage.sol index 0d21681a5..c7a3a401f 100644 --- a/src/contracts/core/AllocationManagerStorage.sol +++ b/src/contracts/core/AllocationManagerStorage.sol @@ -1,10 +1,12 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.27; +import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import "@openzeppelin/contracts/utils/structs/DoubleEndedQueue.sol"; + import "../interfaces/IAllocationManager.sol"; -import "../interfaces/IAVSDirectory.sol"; import "../interfaces/IDelegationManager.sol"; -import "@openzeppelin/contracts/utils/structs/DoubleEndedQueue.sol"; + import {Snapshots} from "../libraries/Snapshots.sol"; abstract contract AllocationManagerStorage is IAllocationManager { @@ -18,20 +20,14 @@ abstract contract AllocationManagerStorage is IAllocationManager { /// @dev Index for flag that pauses operator register/deregister to operator sets when set. uint8 internal constant PAUSED_OPERATOR_SLASHING = 1; - /// @dev BIPS factor for slashable bips - uint256 internal constant BIPS_FACTOR = 10_000; - - /// @dev Maximum number of pending updates that can be queued for allocations/deallocations - uint256 internal constant MAX_PENDING_UPDATES = 1; + /// @dev Index for flag that pauses operator register/deregister to operator sets when set. + uint8 internal constant PAUSED_OPERATOR_SET_REGISTRATION_AND_DEREGISTRATION = 2; // Immutables /// @notice The DelegationManager contract for EigenLayer IDelegationManager public immutable delegation; - /// @notice The AVSDirectory contract for EigenLayer - IAVSDirectory public immutable avsDirectory; - /// @notice Delay before deallocations are clearable and can be added back into freeMagnitude /// In this window, deallocations still remain slashable by the operatorSet they were allocated to. uint32 public immutable DEALLOCATION_DELAY; @@ -41,35 +37,62 @@ abstract contract AllocationManagerStorage is IAllocationManager { // Mutatables - /// @notice Returns snapshots of max magnitude for each `operator` for a given `strategy`. - /// @dev This value starts at 100% (1e18) and decreases with slashing. - mapping(address operator => mapping(IStrategy strategy => Snapshots.DefaultWadHistory)) internal - _maxMagnitudeHistory; + /// AVS => OPERATOR SET - /// @notice Returns the amount of magnitude that is not available for allocation for each `operator` for a given `strategy`. - /// @dev This value increases with allocations and slashing, and decreases with deallocations; should never exceed 100% (1e18). - mapping(address operator => mapping(IStrategy strategy => uint64)) public encumberedMagnitude; + /// @dev Contains the AVS's configured registrar contract that handles registration/deregistration + /// Note: if set to 0, defaults to the AVS's address + mapping(address avs => IAVSRegistrar) internal _avsRegistrar; - /// @notice Returns the magnitude info for each `operator` for a given `strategy` and operator set (`operatorSetKey`). - mapping(address operator => mapping(IStrategy strategy => mapping(bytes32 operatorSetKey => MagnitudeInfo))) - internal _operatorMagnitudeInfo; + /// @dev Lists the operator sets an AVS has created + mapping(address avs => EnumerableSet.UintSet) internal _operatorSets; - /// @notice Returns pending deallocations for each `operator` for a given `strategy`. - mapping(address operator => mapping(IStrategy strategy => DoubleEndedQueue.Bytes32Deque)) internal deallocationQueue; + /// @dev Lists the strategies an AVS supports for an operator set + mapping(bytes32 operatorSetKey => EnumerableSet.AddressSet) internal _operatorSetStrategies; + + /// @dev Lists the members of an AVS's operator set + mapping(bytes32 operatorSetKey => EnumerableSet.AddressSet) internal _operatorSetMembers; + + /// OPERATOR => OPERATOR SET (REGISTRATION/DEREGISTRATION) /// @notice Returns the allocation delay info for each `operator`; the delay and whether or not it's previously been set. mapping(address operator => AllocationDelayInfo) internal _allocationDelayInfo; + /// @dev Lists the operator sets the operator is registered for. Note that an operator + /// can be registered without allocated stake. Likewise, an operator can allocate + /// without being registered. + mapping(address operator => EnumerableSet.Bytes32Set) internal registeredSets; + + /// @dev Lists the operator sets the operator has outstanding allocations in. + mapping(address operator => EnumerableSet.Bytes32Set) internal allocatedSets; + + /// @dev Contains the operator's registration status for an operator set. + mapping(address operator => mapping(bytes32 operatorSetKey => RegistrationStatus)) internal registrationStatus; + + /// @dev For an operator set, lists all strategies an operator has outstanding allocations from. + mapping(address operator => mapping(bytes32 operatorSetKey => EnumerableSet.AddressSet)) internal + allocatedStrategies; + + /// @dev For an operator set and strategy, the current allocated magnitude and any pending modification + mapping(address operator => mapping(bytes32 operatorSetKey => mapping(IStrategy strategy => Allocation))) internal + allocations; + + /// OPERATOR => STRATEGY (MAX/USED AND DEALLOCATIONS) + + /// @dev Contains a history of the operator's maximum magnitude for a given strategy + mapping(address operator => mapping(IStrategy strategy => Snapshots.DefaultWadHistory)) internal + _maxMagnitudeHistory; + + /// @dev For a strategy, contains the amount of magnitude an operator has allocated to operator sets + mapping(address operator => mapping(IStrategy strategy => uint64)) public encumberedMagnitude; + + /// @dev For a strategy, keeps an ordered queue of operator sets that have pending deallocations + /// These must be completed in order to free up magnitude for future allocation + mapping(address operator => mapping(IStrategy strategy => DoubleEndedQueue.Bytes32Deque)) internal deallocationQueue; + // Construction - constructor( - IDelegationManager _delegation, - IAVSDirectory _avsDirectory, - uint32 _DEALLOCATION_DELAY, - uint32 _ALLOCATION_CONFIGURATION_DELAY - ) { + constructor(IDelegationManager _delegation, uint32 _DEALLOCATION_DELAY, uint32 _ALLOCATION_CONFIGURATION_DELAY) { delegation = _delegation; - avsDirectory = _avsDirectory; DEALLOCATION_DELAY = _DEALLOCATION_DELAY; ALLOCATION_CONFIGURATION_DELAY = _ALLOCATION_CONFIGURATION_DELAY; } @@ -79,5 +102,5 @@ abstract contract AllocationManagerStorage is IAllocationManager { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[45] private __gap; + uint256[37] private __gap; } diff --git a/src/contracts/core/DelegationManager.sol b/src/contracts/core/DelegationManager.sol index 33d9464a2..4aee2e86c 100644 --- a/src/contracts/core/DelegationManager.sol +++ b/src/contracts/core/DelegationManager.sol @@ -102,8 +102,7 @@ contract DelegationManager is _setOperatorDetails(msg.sender, registeringOperatorDetails); // delegate from the operator to themselves - SignatureWithExpiry memory emptySignatureAndExpiry; - _delegate(msg.sender, msg.sender, emptySignatureAndExpiry, bytes32(0)); + _delegate(msg.sender, msg.sender); emit OperatorRegistered(msg.sender, registeringOperatorDetails); emit OperatorMetadataURIUpdated(msg.sender, metadataURI); @@ -134,8 +133,26 @@ contract DelegationManager is require(!isDelegated(msg.sender), ActivelyDelegated()); require(isOperator(operator), OperatorNotRegistered()); - // go through the internal delegation flow, checking the `approverSignatureAndExpiry` if applicable - _delegate(msg.sender, operator, approverSignatureAndExpiry, approverSalt); + // Checking the `approverSignatureAndExpiry` if applicable + address approver = _operatorDetails[operator].delegationApprover; + if (approver != address(0) && msg.sender != approver && msg.sender != operator) { + // check that the salt hasn't been used previously, then mark the salt as spent + require(!delegationApproverSaltIsSpent[approver][approverSalt], SaltSpent()); + // actually check that the signature is valid + _checkIsValidSignatureNow({ + signer: approver, + signableDigest: calculateDelegationApprovalDigestHash( + msg.sender, operator, approver, approverSalt, approverSignatureAndExpiry.expiry + ), + signature: approverSignatureAndExpiry.signature, + expiry: approverSignatureAndExpiry.expiry + }); + + delegationApproverSaltIsSpent[approver][approverSalt] = true; + } + + // Delegate msg.sender to the operator + _delegate(msg.sender, operator); } /// @inheritdoc IDelegationManager @@ -173,42 +190,31 @@ contract DelegationManager is uint64[] memory maxMagnitudes = allocationManager.getMaxMagnitudes(operator, strategies); for (uint256 i = 0; i < strategies.length; i++) { - StakerScalingFactors storage ssf = stakerScalingFactor[staker][strategies[i]]; - // If the operator was not slashed 100% for the strategy and the staker has not been fully slashed // for native restaking (if the strategy is beaconChainStrategy) then handle a normal queued withdrawal. // Otherwise if the operator has been slashed 100% for the strategy, it implies // the staker has no available shares to withdraw and we simply decrement their entire depositShares amount. // Note the returned withdrawal root will be 0x0 in this scenario but is not actually a valid null root. - if (!ssf.isFullySlashed(maxMagnitudes[i])) { - IStrategy[] memory singleStrategy = new IStrategy[](1); - uint256[] memory singleDepositShares = new uint256[](1); - uint64[] memory singleMaxMagnitude = new uint64[](1); - singleStrategy[0] = strategies[i]; - singleDepositShares[0] = depositedShares[i]; - singleMaxMagnitude[0] = maxMagnitudes[i]; - - withdrawalRoots[i] = _removeSharesAndQueueWithdrawal({ - staker: staker, - operator: operator, - strategies: singleStrategy, - depositSharesToWithdraw: singleDepositShares, - maxMagnitudes: singleMaxMagnitude - }); - } else { - IShareManager shareManager = _getShareManager(strategies[i]); + IStrategy[] memory singleStrategy = new IStrategy[](1); + uint256[] memory singleDepositShares = new uint256[](1); + uint64[] memory singleMaxMagnitude = new uint64[](1); + singleStrategy[0] = strategies[i]; + singleDepositShares[0] = depositedShares[i]; + singleMaxMagnitude[0] = maxMagnitudes[i]; - // Remove active shares from EigenPodManager/StrategyManager - // This is to ensure that all shares are removed entirely and cannot be withdrawn - // or redelegated. - shareManager.removeDepositShares(staker, strategies[i], depositedShares[i]); - } + withdrawalRoots[i] = _removeSharesAndQueueWithdrawal({ + staker: staker, + operator: operator, + strategies: singleStrategy, + depositSharesToWithdraw: singleDepositShares, + maxMagnitudes: singleMaxMagnitude + }); // all shares are queued withdrawn with no delegated operator, so // reset staker's depositScalingFactor back to WAD default. // If this is not reset, the depositScalingFactor would be incorrect // when the staker deposits and queue withdraws in the future. - ssf.depositScalingFactor = WAD; + _depositScalingFactor[staker][strategies[i]]._scalingFactor = WAD; emit DepositScalingFactorUpdated(staker, strategies[i], WAD); } @@ -275,6 +281,7 @@ contract DelegationManager is uint256 totalQueued = withdrawalRoots.length(); numToComplete = numToComplete > totalQueued ? totalQueued : numToComplete; for (uint256 i; i < numToComplete; ++i) { + // TODO: fix .at + .remove in loop _completeQueuedWithdrawal(queuedWithdrawals[withdrawalRoots.at(i)], tokens[i], receiveAsTokens[i]); } } @@ -315,13 +322,17 @@ contract DelegationManager is address operator = delegatedTo[staker]; IStrategy[] memory strategies = new IStrategy[](1); strategies[0] = beaconChainETHStrategy; - uint64[] memory maxMagnitudes = allocationManager.getMaxMagnitudes(operator, strategies); + uint64 maxMagnitude = allocationManager.getMaxMagnitudes(operator, strategies)[0]; - StakerScalingFactors storage ssf = stakerScalingFactor[staker][beaconChainETHStrategy]; - uint256 sharesBefore = existingDepositShares.toShares(ssf, maxMagnitudes[0]); - ssf.decreaseBeaconChainScalingFactor(proportionOfOldBalance); - emit BeaconChainScalingFactorDecreased(staker, ssf.beaconChainScalingFactor); - uint256 sharesAfter = existingDepositShares.toShares(ssf, maxMagnitudes[0]); + DepositScalingFactor memory dsf = _depositScalingFactor[staker][beaconChainETHStrategy]; + + uint256 slashingFactor = _getSlashingFactor(staker, beaconChainETHStrategy, maxMagnitude); + uint256 withdrawableBefore = dsf.calcWithdrawable(existingDepositShares, slashingFactor); + + _decreaseBeaconChainSlashingFactor(staker, proportionOfOldBalance); + + slashingFactor = _getSlashingFactor(staker, beaconChainETHStrategy, maxMagnitude); + uint256 withdrawableAfter = dsf.calcWithdrawable(existingDepositShares, slashingFactor); // if the staker is delegated to an operators if (isDelegated(staker)) { @@ -330,7 +341,7 @@ contract DelegationManager is operator: operator, staker: staker, strategy: beaconChainETHStrategy, - sharesToDecrease: sharesBefore - sharesAfter + sharesToDecrease: withdrawableBefore - withdrawableAfter }); } } @@ -366,7 +377,7 @@ contract DelegationManager is function completeQueuedWithdrawal( Withdrawal calldata withdrawal, IERC20[] calldata tokens, - uint256 middlewareTimesIndex, + uint256, // middlewareTimesIndex bool receiveAsTokens ) external onlyWhenNotPaused(PAUSED_EXIT_WITHDRAWAL_QUEUE) nonReentrant { _completeQueuedWithdrawal(withdrawal, tokens, receiveAsTokens); @@ -376,7 +387,7 @@ contract DelegationManager is function completeQueuedWithdrawals( Withdrawal[] calldata withdrawals, IERC20[][] calldata tokens, - uint256[] calldata middlewareTimesIndexes, + uint256[] calldata, // middlewareTimesIndexes bool[] calldata receiveAsTokens ) external onlyWhenNotPaused(PAUSED_EXIT_WITHDRAWAL_QUEUE) nonReentrant { for (uint256 i = 0; i < withdrawals.length; ++i) { @@ -404,8 +415,6 @@ contract DelegationManager is * @notice Delegates *from* a `staker` *to* an `operator`. * @param staker The address to delegate *from* -- this address is delegating control of its own assets. * @param operator The address to delegate *to* -- this address is being given power to place the `staker`'s assets at risk on services - * @param approverSignatureAndExpiry Verifies the operator approves of this delegation - * @param approverSalt Is a salt used to help guarantee signature uniqueness. Each salt can only be used once by a given approver. * @dev Assumes the following is checked before calling this function: * 1) the `staker` is not already delegated to an operator * 2) the `operator` has indeed registered as an operator in EigenLayer @@ -413,35 +422,7 @@ contract DelegationManager is * 1) if applicable, that the approver signature is valid and non-expired * 2) new delegations are not paused (PAUSED_NEW_DELEGATION) */ - function _delegate( - address staker, - address operator, - SignatureWithExpiry memory approverSignatureAndExpiry, - bytes32 approverSalt - ) internal onlyWhenNotPaused(PAUSED_NEW_DELEGATION) { - // fetch the operator's `delegationApprover` address and store it in memory in case we need to use it multiple times - address approver = _operatorDetails[operator].delegationApprover; - /** - * Check the `approver`'s signature, if applicable. - * If the `approver` is the zero address, then the operator allows all stakers to delegate to them and this verification is skipped. - * If the `approver` or the `operator` themselves is the caller, then approval is assumed and signature verification is skipped as well. - */ - if (approver != address(0) && msg.sender != approver && msg.sender != operator) { - // check that the salt hasn't been used previously, then mark the salt as spent - require(!delegationApproverSaltIsSpent[approver][approverSalt], SaltSpent()); - // actually check that the signature is valid - _checkIsValidSignatureNow({ - signer: approver, - signableDigest: calculateDelegationApprovalDigestHash( - staker, operator, approver, approverSalt, approverSignatureAndExpiry.expiry - ), - signature: approverSignatureAndExpiry.signature, - expiry: approverSignatureAndExpiry.expiry - }); - - delegationApproverSaltIsSpent[approver][approverSalt] = true; - } - + function _delegate(address staker, address operator) internal onlyWhenNotPaused(PAUSED_NEW_DELEGATION) { // record the delegation relation between the staker and operator, and emit an event delegatedTo[staker] = operator; emit StakerDelegated(staker, operator); @@ -488,17 +469,22 @@ contract DelegationManager is // read delegated operator's maxMagnitudes at the earliest time that the withdrawal could be completed // to convert the delegatedShares to shares factoring in slashing that occured during withdrawal delay - uint64[] memory maxMagnitudes = allocationManager.getMaxMagnitudesAtTimestamp({ + uint64[] memory maxMagnitudes = allocationManager.getMaxMagnitudesAtBlock({ operator: withdrawal.delegatedTo, strategies: withdrawal.strategies, - timestamp: completableBlock //TODO: update ALM to use blocks + blockNumber: completableBlock }); for (uint256 i = 0; i < withdrawal.strategies.length; i++) { - IShareManager shareManager = _getShareManager(withdrawal.strategies[i]); - uint256 sharesToWithdraw = withdrawal.scaledShares[i].scaleSharesForCompleteWithdrawal( - stakerScalingFactor[withdrawal.staker][withdrawal.strategies[i]], maxMagnitudes[i] - ); + IStrategy strategy = withdrawal.strategies[i]; + IShareManager shareManager = _getShareManager(strategy); + + // Calculate how much slashing to apply, as well as shares to withdraw + uint256 slashingFactor = _getSlashingFactor(withdrawal.staker, strategy, maxMagnitudes[i]); + uint256 sharesToWithdraw = SlashingLib.scaleSharesForCompleteWithdrawal({ + scaledShares: withdrawal.scaledShares[i], + slashingFactor: slashingFactor + }); if (receiveAsTokens) { // Withdraws `shares` in `strategy` to `withdrawer`. If the shares are virtual beaconChainETH shares, @@ -524,7 +510,6 @@ contract DelegationManager is _stakerQueuedWithdrawalRoots[withdrawal.staker].remove(withdrawalRoot); delete queuedWithdrawals[withdrawalRoot]; - delete pendingWithdrawals[withdrawalRoot]; emit SlashingWithdrawalCompleted(withdrawalRoot); @@ -548,19 +533,20 @@ contract DelegationManager is uint256 addedShares, uint64 maxMagnitude ) internal { - StakerScalingFactors storage ssf = stakerScalingFactor[staker][strategy]; + uint256 slashingFactor = _getSlashingFactor(staker, strategy, maxMagnitude); // Ensure that the operator has not been fully slashed for a strategy // and that the staker has not been fully slashed if its the beaconChainStrategy - require(!ssf.isFullySlashed(maxMagnitude), FullySlashed()); + require(slashingFactor != 0, FullySlashed()); // Increment operator shares operatorShares[operator][strategy] += addedShares; emit OperatorSharesIncreased(operator, staker, strategy, addedShares); - // update the staker's depositScalingFactor - ssf.updateDepositScalingFactor(existingDepositShares, addedShares, maxMagnitude); - emit DepositScalingFactorUpdated(staker, strategy, ssf.depositScalingFactor); + // Update the staker's depositScalingFactor + DepositScalingFactor storage dsf = _depositScalingFactor[staker][strategy]; + dsf.update(existingDepositShares, addedShares, slashingFactor); + emit DepositScalingFactorUpdated(staker, strategy, dsf.scalingFactor()); } /** @@ -619,19 +605,23 @@ contract DelegationManager is // Each of these operations fail if we attempt to remove more shares than exist for (uint256 i = 0; i < strategies.length; ++i) { IShareManager shareManager = _getShareManager(strategies[i]); - StakerScalingFactors memory ssf = stakerScalingFactor[staker][strategies[i]]; + DepositScalingFactor memory dsf = _depositScalingFactor[staker][strategies[i]]; - // Ensure that the operator has not been fully slashed for a strategy - // and that the staker has not been slashed fully if its the beaconChainStrategy - require(!ssf.isFullySlashed(maxMagnitudes[i]), FullySlashed()); // Check withdrawing deposit shares amount doesn't exceed balance require( depositSharesToWithdraw[i] <= shareManager.stakerDepositShares(staker, strategies[i]), WithdrawalExceedsMax() ); - // Calculate the shares to withdraw - uint256 sharesToWithdraw = depositSharesToWithdraw[i].toShares(ssf, maxMagnitudes[i]); + // Calculate how much slashing to apply, as well as shares to withdraw + uint256 slashingFactor = _getSlashingFactor(staker, strategies[i], maxMagnitudes[i]); + uint256 sharesToWithdraw = dsf.calcWithdrawable(depositSharesToWithdraw[i], slashingFactor); + + // Apply slashing. If the staker or operator has been fully slashed, this will return 0 + scaledShares[i] = SlashingLib.scaleSharesForQueuedWithdrawal({ + sharesToWithdraw: sharesToWithdraw, + slashingFactor: slashingFactor + }); // Remove delegated shares from the operator if (operator != address(0)) { @@ -644,9 +634,7 @@ contract DelegationManager is }); } - scaledShares[i] = sharesToWithdraw.scaleSharesForQueuedWithdrawal(ssf, maxMagnitudes[i]); - - // Remove active shares from EigenPodManager/StrategyManager + // Remove deposit shares from EigenPodManager/StrategyManager shareManager.removeDepositShares(staker, strategies[i], depositSharesToWithdraw[i]); } @@ -667,22 +655,37 @@ contract DelegationManager is bytes32 withdrawalRoot = calculateWithdrawalRoot(withdrawal); pendingWithdrawals[withdrawalRoot] = true; - - _stakerQueuedWithdrawalRoots[staker].add(withdrawalRoot); - queuedWithdrawals[withdrawalRoot] = withdrawal; + _stakerQueuedWithdrawalRoots[staker].add(withdrawalRoot); emit SlashingWithdrawalQueued(withdrawalRoot, withdrawal, depositSharesToWithdraw); return withdrawalRoot; } - /** - * - * SHARES CONVERSION FUNCTIONS - * - */ + /// @dev Calculate the amount of slashing to apply to the staker's shares + function _getSlashingFactor( + address staker, + IStrategy strategy, + uint64 operatorMaxMagnitude + ) internal view returns (uint256) { + if (strategy == beaconChainETHStrategy) { + uint64 beaconChainSlashingFactor = getBeaconChainSlashingFactor(staker); + return operatorMaxMagnitude.mulWad(beaconChainSlashingFactor); + } + + return operatorMaxMagnitude; + } - /// @notice Depending on the strategy used, determine which ShareManager contract to make external calls to + function _decreaseBeaconChainSlashingFactor(address staker, uint64 proportionOfOldBalance) internal { + BeaconChainSlashingFactor memory bsf = _beaconChainSlashingFactor[staker]; + bsf.slashingFactor = uint64(uint256(getBeaconChainSlashingFactor(staker)).mulWad(proportionOfOldBalance)); + bsf.isSet = true; + + emit BeaconChainScalingFactorDecreased(staker, bsf.slashingFactor); + _beaconChainSlashingFactor[staker] = bsf; + } + + /// @dev Depending on the strategy used, determine which ShareManager contract to make external calls to function _getShareManager( IStrategy strategy ) internal view returns (IShareManager) { @@ -725,6 +728,19 @@ contract DelegationManager is return _operatorDetails[operator].delegationApprover; } + /// @inheritdoc IDelegationManager + function depositScalingFactor(address staker, IStrategy strategy) public view returns (uint256) { + return _depositScalingFactor[staker][strategy].scalingFactor(); + } + + /// @inheritdoc IDelegationManager + function getBeaconChainSlashingFactor( + address staker + ) public view returns (uint64) { + BeaconChainSlashingFactor memory bsf = _beaconChainSlashingFactor[staker]; + return bsf.isSet ? bsf.slashingFactor : WAD; + } + /// @inheritdoc IDelegationManager function getOperatorShares( address operator, @@ -761,15 +777,17 @@ contract DelegationManager is for (uint256 i = 0; i < strategies.length; ++i) { IShareManager shareManager = _getShareManager(strategies[i]); - // TODO: batch call for strategyManager shares? - // 1. read strategy deposit shares // forgefmt: disable-next-item depositShares[i] = shareManager.stakerDepositShares(staker, strategies[i]); + // 1. Get the staker's slashing factor + uint256 slashingFactor = _getSlashingFactor(staker, strategies[i], maxMagnitudes[i]); + // 2. Calculate the withdrawable shares - withdrawableShares[i] = - depositShares[i].toShares(stakerScalingFactor[staker][strategies[i]], maxMagnitudes[i]); + DepositScalingFactor memory dsf = _depositScalingFactor[staker][strategies[i]]; + withdrawableShares[i] = dsf.calcWithdrawable(depositShares[i], slashingFactor); } + return (withdrawableShares, depositShares); } @@ -807,8 +825,8 @@ contract DelegationManager is address staker ) external view returns (Withdrawal[] memory withdrawals, uint256[][] memory shares) { bytes32[] memory withdrawalRoots = _stakerQueuedWithdrawalRoots[staker].values(); - uint256 totalQueued = withdrawalRoots.length; + uint256 totalQueued = withdrawalRoots.length; withdrawals = new Withdrawal[](totalQueued); shares = new uint256[][](totalQueued); @@ -816,14 +834,17 @@ contract DelegationManager is for (uint256 i; i < totalQueued; ++i) { withdrawals[i] = queuedWithdrawals[withdrawalRoots[i]]; + shares[i] = new uint256[](withdrawals[i].strategies.length); uint64[] memory operatorMagnitudes = allocationManager.getMaxMagnitudes(operator, withdrawals[i].strategies); for (uint256 j; j < withdrawals[i].strategies.length; ++j) { - StakerScalingFactors memory ssf = stakerScalingFactor[staker][withdrawals[i].strategies[j]]; + uint256 slashingFactor = _getSlashingFactor(staker, withdrawals[i].strategies[j], operatorMagnitudes[i]); - shares[i][j] = - withdrawals[i].scaledShares[j].scaleSharesForCompleteWithdrawal(ssf, operatorMagnitudes[i]); + shares[i][j] = SlashingLib.scaleSharesForCompleteWithdrawal({ + scaledShares: withdrawals[i].scaledShares[j], + slashingFactor: slashingFactor + }); } } } diff --git a/src/contracts/core/DelegationManagerStorage.sol b/src/contracts/core/DelegationManagerStorage.sol index 7146c36ff..40e07893c 100644 --- a/src/contracts/core/DelegationManagerStorage.sol +++ b/src/contracts/core/DelegationManagerStorage.sol @@ -99,9 +99,12 @@ abstract contract DelegationManagerStorage is IDelegationManager { /// @dev Do not remove, deprecated storage. mapping(IStrategy strategy => uint256 delayBlocks) private __deprecated_strategyWithdrawalDelayBlocks; - /// @notice Returns the scaling factors for a `staker` for a given `strategy`. - /// @dev We do not need the `beaconChainScalingFactor` for non-beaconchain strategies, but it's nicer syntactically to keep it. - mapping(address staker => mapping(IStrategy strategy => StakerScalingFactors)) public stakerScalingFactor; + /// @notice Returns the scaling factor applied to a `staker` for a given `strategy` + mapping(address staker => mapping(IStrategy strategy => DepositScalingFactor)) internal _depositScalingFactor; + + /// @notice Returns the slashing factor applied to the `staker` for the `beaconChainETHStrategy` + /// Note: this is specifically updated when the staker's beacon chain balance decreases + mapping(address staker => BeaconChainSlashingFactor) internal _beaconChainSlashingFactor; /// @notice Returns a list of queued withdrawals for a given `staker`. /// @dev Entrys are removed when the withdrawal is completed. @@ -133,5 +136,5 @@ abstract contract DelegationManagerStorage is IDelegationManager { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[36] private __gap; + uint256[35] private __gap; } diff --git a/src/contracts/core/RewardsCoordinator.sol b/src/contracts/core/RewardsCoordinator.sol index afad1ce56..7c4e5d67a 100644 --- a/src/contracts/core/RewardsCoordinator.sol +++ b/src/contracts/core/RewardsCoordinator.sol @@ -424,7 +424,10 @@ contract RewardsCoordinator is } /// @inheritdoc IRewardsCoordinator - function operatorCommissionBips(address operator, address avs) external view returns (uint16) { + function operatorCommissionBips( + address, // operator + address // avs + ) external view returns (uint16) { return globalOperatorCommissionBips; } diff --git a/src/contracts/interfaces/IAVSDirectory.sol b/src/contracts/interfaces/IAVSDirectory.sol index ba2fdc01d..231ef2e9d 100644 --- a/src/contracts/interfaces/IAVSDirectory.sol +++ b/src/contracts/interfaces/IAVSDirectory.sol @@ -5,12 +5,6 @@ import "./ISignatureUtils.sol"; import "./IPauserRegistry.sol"; import "./IStrategy.sol"; -/// @notice Struct representing an operator set -struct OperatorSet { - address avs; - uint32 operatorSetId; -} - interface IAVSDirectoryErrors { /// Operator Status @@ -59,9 +53,6 @@ interface IAVSDirectoryTypes { } interface IAVSDirectoryEvents is IAVSDirectoryTypes { - /// @notice Emitted when an operator set is created by an AVS. - event OperatorSetCreated(OperatorSet operatorSet); - /** * @notice Emitted when an operator's registration status with an AVS id udpated * @notice Only used by legacy M2 AVSs that have not integrated with operatorSets. @@ -70,18 +61,6 @@ interface IAVSDirectoryEvents is IAVSDirectoryTypes { address indexed operator, address indexed avs, OperatorAVSRegistrationStatus status ); - /// @notice Emitted when an operator is added to an operator set. - event OperatorAddedToOperatorSet(address indexed operator, OperatorSet operatorSet); - - /// @notice Emitted when an operator is removed from an operator set. - event OperatorRemovedFromOperatorSet(address indexed operator, OperatorSet operatorSet); - - /// @notice Emitted when a strategy is added to an operator set. - event StrategyAddedToOperatorSet(OperatorSet operatorSet, IStrategy strategy); - - /// @notice Emitted when a strategy is removed from an operator set. - event StrategyRemovedFromOperatorSet(OperatorSet operatorSet, IStrategy strategy); - /// @notice Emitted when an AVS updates their metadata URI (Uniform Resource Identifier). /// @dev The URI is never stored; it is simply emitted through an event for off-chain indexing. event AVSMetadataURIUpdated(address indexed avs, string metadataURI); @@ -105,105 +84,6 @@ interface IAVSDirectory is IAVSDirectoryEvents, IAVSDirectoryErrors, ISignatureU */ function initialize(address initialOwner, uint256 initialPausedStatus) external; - /** - * @notice Called by an AVS to create a list of new operatorSets. - * - * @param operatorSetIds The IDs of the operator set to initialize. - * - * @dev msg.sender must be the AVS. - * @dev The AVS may create operator sets before it becomes an operator set AVS. - */ - function createOperatorSets( - uint32[] calldata operatorSetIds - ) external; - - /** - * @notice Sets the AVS as an operator set AVS, preventing legacy M2 operator registrations. - * - * @dev msg.sender must be the AVS. - */ - function becomeOperatorSetAVS() external; - - /** - * @notice Called by an AVS to migrate operators that have a legacy M2 registration to operator sets. - * - * @param operators The list of operators to migrate - * @param operatorSetIds The list of operatorSets to migrate the operators to - * - * @dev The msg.sender used is the AVS - * @dev The operator can only be migrated at most once per AVS - * @dev The AVS can no longer register operators via the legacy M2 registration path once it begins migration - * @dev The operator is deregistered from the M2 legacy AVS once migrated - */ - function migrateOperatorsToOperatorSets( - address[] calldata operators, - uint32[][] calldata operatorSetIds - ) external; - - /** - * @notice Called by AVSs to add an operator to a list of operatorSets. - * - * @param operator The address of the operator to be added to the operator set. - * @param operatorSetIds The IDs of the operator sets. - * @param operatorSignature The signature of the operator on their intent to register. - * - * @dev msg.sender is used as the AVS. - * @dev The operator must not have a pending deregistration from the operator set. - */ - function registerOperatorToOperatorSets( - address operator, - uint32[] calldata operatorSetIds, - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature - ) external; - - /** - * @notice Called by an operator to deregister from an operator set - * - * @param operator The operator to deregister from the operatorSets. - * @param avs The address of the AVS to deregister the operator from. - * @param operatorSetIds The IDs of the operator sets. - * @param operatorSignature the signature of the operator on their intent to deregister or empty if the operator itself is calling - * - * @dev if the operatorSignature is empty, the caller must be the operator - * @dev this will likely only be called in case the AVS contracts are in a state that prevents operators from deregistering - */ - function forceDeregisterFromOperatorSets( - address operator, - address avs, - uint32[] calldata operatorSetIds, - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature - ) external; - - /** - * @notice Called by AVSs to remove an operator from an operator set. - * - * @param operator The address of the operator to be removed from the operator set. - * @param operatorSetIds The IDs of the operator sets. - * - * @dev msg.sender is used as the AVS. - */ - function deregisterOperatorFromOperatorSets(address operator, uint32[] calldata operatorSetIds) external; - - /** - * @notice Called by AVSs to add a set of strategies to an operator set. - * - * @param operatorSetId The ID of the operator set. - * @param strategies The addresses of the strategies to be added to the operator set. - * - * @dev msg.sender is used as the AVS. - */ - function addStrategiesToOperatorSet(uint32 operatorSetId, IStrategy[] calldata strategies) external; - - /** - * @notice Called by AVSs to remove a set of strategies from an operator set. - * - * @param operatorSetId The ID of the operator set. - * @param strategies The addresses of the strategies to be removed from the operator set. - * - * @dev msg.sender is used as the AVS. - */ - function removeStrategiesFromOperatorSet(uint32 operatorSetId, IStrategy[] calldata strategies) external; - /** * @notice Called by an AVS to emit an `AVSMetadataURIUpdated` event indicating the information has updated. * @@ -227,7 +107,7 @@ interface IAVSDirectory is IAVSDirectoryEvents, IAVSDirectoryErrors, ISignatureU /** * @notice Legacy function called by the AVS's service manager contract * to register an operator with the AVS. NOTE: this function will be deprecated in a future release - * after the slashing release. New AVSs should use `registerOperatorToOperatorSets` instead. + * after the slashing release. New AVSs should use `registerForOperatorSets` instead. * * @param operator The address of the operator to register. * @param operatorSignature The signature, salt, and expiry of the operator's signature. @@ -260,105 +140,6 @@ interface IAVSDirectory is IAVSDirectoryEvents, IAVSDirectoryErrors, ISignatureU */ function operatorSaltIsSpent(address operator, bytes32 salt) external view returns (bool); - function isOperatorSetAVS( - address avs - ) external view returns (bool); - - /// @notice Returns true if the operator set is valid. - function isOperatorSet(address avs, uint32 operatorSetId) external view returns (bool); - - /** - * @notice Returns operator set an operator is registered to in the order they were registered. - * @param operator The operator address to query. - * @param index The index in the enumerated list of operator sets. - */ - function operatorSetsMemberOfAtIndex(address operator, uint256 index) external view returns (OperatorSet memory); - - /** - * @notice Retursn the operator registered to an operatorSet in the order that it was registered. - * @param operatorSet The operatorSet to query. - * @param index The index in the enumerated list of operators. - */ - function operatorSetMemberAtIndex(OperatorSet memory operatorSet, uint256 index) external view returns (address); - - /** - * @notice Returns the number of operator sets an operator is registered to. - * @param operator the operator address to query - */ - function getNumOperatorSetsOfOperator( - address operator - ) external view returns (uint256); - - /** - * @notice Returns an array of operator sets an operator is registered to. - * @param operator The operator address to query. - * @param start The starting index in the array to query. - * @param length The amount of items of the array to return. - */ - function getOperatorSetsOfOperator( - address operator, - uint256 start, - uint256 length - ) external view returns (OperatorSet[] memory operatorSets); - - /** - * @notice Returns an array of operators registered to the operatorSet. - * @param operatorSet The operatorSet to query. - * @param start The starting index in the array to query. - * @param length The amount of items of the array to return. - */ - function getOperatorsInOperatorSet( - OperatorSet memory operatorSet, - uint256 start, - uint256 length - ) external view returns (address[] memory operators); - - /** - * @notice Returns an array of strategies in the operatorSet. - * @param operatorSet The operatorSet to query. - */ - function getStrategiesInOperatorSet( - OperatorSet memory operatorSet - ) external view returns (IStrategy[] memory strategies); - - /** - * @notice Returns the number of operators registered to an operatorSet. - * @param operatorSet The operatorSet to get the member count for - */ - function getNumOperatorsInOperatorSet( - OperatorSet memory operatorSet - ) external view returns (uint256); - - /** - * @notice Returns the total number of operator sets an operator is registered to. - * @param operator The operator address to query. - */ - function inTotalOperatorSets( - address operator - ) external view returns (uint256); - - /** - * @notice Returns whether or not an operator is registered to an operator set. - * @param operator The operator address to query. - * @param operatorSet The `OperatorSet` to query. - */ - function isMember(address operator, OperatorSet memory operatorSet) external view returns (bool); - - /** - * @notice Returns whether or not an operator is slashable for an operator set. - * @param operator The operator address to query. - * @param operatorSet The `OperatorSet` to query.ß - */ - function isOperatorSlashable(address operator, OperatorSet memory operatorSet) external view returns (bool); - - /** - * @notice Returns whether or not an operator is registered to all provided operator sets. - * @param operatorSets The list of operator sets to check. - */ - function isOperatorSetBatch( - OperatorSet[] calldata operatorSets - ) external view returns (bool); - /** * @notice Calculates the digest hash to be signed by an operator to register with an AVS. * @@ -374,45 +155,9 @@ interface IAVSDirectory is IAVSDirectoryEvents, IAVSDirectoryErrors, ISignatureU uint256 expiry ) external view returns (bytes32); - /** - * @notice Calculates the digest hash to be signed by an operator to register with an operator set. - * - * @param avs The AVS that operator is registering to operator sets for. - * @param operatorSetIds An array of operator set IDs the operator is registering to. - * @param salt A unique and single use value associated with the approver signature. - * @param expiry Time after which the approver's signature becomes invalid. - */ - function calculateOperatorSetRegistrationDigestHash( - address avs, - uint32[] calldata operatorSetIds, - bytes32 salt, - uint256 expiry - ) external view returns (bytes32); - - /** - * @notice Calculates the digest hash to be signed by an operator to force deregister from an operator set. - * - * @param avs The AVS that operator is deregistering from. - * @param operatorSetIds An array of operator set IDs the operator is deregistering from. - * @param salt A unique and single use value associated with the approver signature. - * @param expiry Time after which the approver's signature becomes invalid. - */ - function calculateOperatorSetForceDeregistrationTypehash( - address avs, - uint32[] calldata operatorSetIds, - bytes32 salt, - uint256 expiry - ) external view returns (bytes32); - /// @notice The EIP-712 typehash for the Registration struct used by the contract. function OPERATOR_AVS_REGISTRATION_TYPEHASH() external view returns (bytes32); /// @notice The EIP-712 typehash for the OperatorSetRegistration struct used by the contract. function OPERATOR_SET_REGISTRATION_TYPEHASH() external view returns (bytes32); - - function operatorSetStatus( - address avs, - address operator, - uint32 operatorSetId - ) external view returns (bool registered, uint32 lastDeregisteredTimestamp); } diff --git a/src/contracts/interfaces/IAVSRegistrar.sol b/src/contracts/interfaces/IAVSRegistrar.sol new file mode 100644 index 000000000..9cc7f32ff --- /dev/null +++ b/src/contracts/interfaces/IAVSRegistrar.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.5.0; + +interface IAVSRegistrar { + /** + * @notice Called by the AllocationManager when an operator wants to register + * for one or more operator sets. This method should revert if registration + * is unsuccessful. + * @param operator the registering operator + * @param operatorSetIds the list of operator set ids being registered for + * @param data arbitrary data the operator can provide as part of registration + */ + function registerOperator(address operator, uint32[] calldata operatorSetIds, bytes calldata data) external; + + /** + * @notice Called by the AllocationManager when an operator is deregistered from + * one or more operator sets. If this method reverts, it is ignored. + * @param operator the deregistering operator + * @param operatorSetIds the list of operator set ids being deregistered from + */ + function deregisterOperator(address operator, uint32[] calldata operatorSetIds) external; +} diff --git a/src/contracts/interfaces/IAllocationManager.sol b/src/contracts/interfaces/IAllocationManager.sol index 44cb2f322..e3b447046 100644 --- a/src/contracts/interfaces/IAllocationManager.sol +++ b/src/contracts/interfaces/IAllocationManager.sol @@ -1,91 +1,118 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.5.0; -import {OperatorSet} from "./IAVSDirectory.sol"; +import {OperatorSet} from "../libraries/OperatorSetLib.sol"; import "./IPauserRegistry.sol"; import "./IStrategy.sol"; import "./ISignatureUtils.sol"; +import "./IAVSRegistrar.sol"; interface IAllocationManagerErrors { + /// Input Validation + /// @dev Thrown when `wadToSlash` is zero or greater than 1e18 error InvalidWadToSlash(); - /// @dev Thrown when `operator` is not a registered operator. - error OperatorNotRegistered(); /// @dev Thrown when two array parameters have mismatching lengths. error InputArrayLengthMismatch(); + /// @dev Thrown when calling a view function that requires a valid block number. + error InvalidBlockNumber(); + + /// Caller + + /// @dev Thrown when caller is not the delegation manager. + error OnlyDelegationManager(); + /// @dev Thrown when caller is not authorized to call a function. + error InvalidCaller(); + + /// Operator Status + + /// @dev Thrown when an invalid operator is provided. + error InvalidOperator(); + /// @dev Thrown when `operator` is not a registered operator. + error OperatorNotRegistered(); /// @dev Thrown when an operator's allocation delay has yet to be set. error UninitializedAllocationDelay(); - /// @dev Thrown when provided `expectedMaxMagnitude` for a given allocation does not match`currentMaxMagnitude`. - error InvalidExpectedMaxMagnitude(); + /// @dev Thrown when attempting to slash an operator when they are not slashable. + error OperatorNotSlashable(); + /// @dev Thrown when trying to add an operator to a set they are already a member of + error AlreadyMemberOfSet(); + /// @dev Thrown when trying to slash/remove an operator from a set they are not a member of + error NotMemberOfSet(); + + /// Operator Set Status + /// @dev Thrown when an invalid operator set is provided. error InvalidOperatorSet(); - /// @dev Thrown when an invalid operator is provided. - error InvalidOperator(); - /// @dev Thrown when caller is not the delegation manager. - error OnlyDelegationManager(); + /// @dev Thrown when a strategy is referenced that does not belong to an operator set. + error InvalidStrategy(); + /// @dev Thrown when trying to add a strategy to an operator set that already contains it. + error StrategyAlreadyInOperatorSet(); + /// @dev Thrown when trying to remove a strategy from an operator set it is not a part of. + error StrategyNotInOperatorSet(); + + /// Modifying Allocations + /// @dev Thrown when an operator attempts to set their allocation for an operatorSet to the same value error SameMagnitude(); /// @dev Thrown when an allocation is attempted for a given operator when they have pending allocations or deallocations. error ModificationAlreadyPending(); /// @dev Thrown when an allocation is attempted that exceeds a given operators total allocatable magnitude. - error InsufficientAllocatableMagnitude(); - /// @dev Thrown when attempting to spend a spent eip-712 salt. - error SaltSpent(); - /// @dev Thrown when attempting to slash an operator that has already been slashed at the given timestamp. - error AlreadySlashedForTimestamp(); - /// @dev Thrown when calling a view function that requires a valid timestamp. - error InvalidTimestamp(); - /// @dev Thrown when a slash is attempted on an operator who has not allocated to the strategy, operatorSet pair - error OperatorNotAllocated(); + error InsufficientMagnitude(); } interface IAllocationManagerTypes { /** - * @notice struct used to modify the allocation of slashable magnitude to list of operatorSets - * @param strategy the strategy to allocate magnitude for - * @param expectedMaxMagnitude the expected max magnitude of the operator (used to combat against race conditions with slashing) - * @param operatorSets the operatorSets to allocate magnitude for - * @param magnitudes the magnitudes to allocate for each operatorSet - */ - struct MagnitudeAllocation { - IStrategy strategy; - uint64 expectedMaxMagnitude; - OperatorSet[] operatorSets; - uint64[] magnitudes; - } - - /** - * @notice struct used for operator magnitude updates. Stored in _operatorMagnitudeInfo mapping - * @param currentMagnitude the current magnitude of the operator - * @param pendingDiff the pending magnitude difference of the operator - * @param effectTimestamp the timestamp at which the pending magnitude will take effect + * @notice Defines allocation information from a strategy to an operator set, for an operator + * @param currentMagnitude the current magnitude allocated from the strategy to the operator set + * @param pendingDiff a pending change in magnitude, if it exists (0 otherwise) + * @param effectBlock the block at which the pending magnitude diff will take effect */ - struct MagnitudeInfo { + struct Allocation { uint64 currentMagnitude; int128 pendingDiff; - uint32 effectTimestamp; + uint32 effectBlock; } /** * @notice Struct containing allocation delay metadata for a given operator. - * @param delay Current allocation delay if `pendingDelay` is non-zero and `pendingDelayEffectTimestamp` has elapsed. + * @param delay Current allocation delay * @param isSet Whether the operator has initially set an allocation delay. Note that this could be false but the - * block.timestamp >= effectTimestamp in which we consider their delay to be configured and active. - * @param pendingDelay Current allocation delay if it's non-zero and `pendingDelayEffectTimestamp` has elapsed. - * @param effectTimestamp The timestamp for which `pendingDelay` becomes the curren allocation delay. + * block.number >= effectBlock in which we consider their delay to be configured and active. + * @param pendingDelay The delay that will take effect after `effectBlock` + * @param effectBlock The block number after which a pending delay will take effect */ struct AllocationDelayInfo { uint32 delay; bool isSet; uint32 pendingDelay; - uint32 effectTimestamp; + uint32 effectBlock; + } + + /** + * @notice Contains registration details for an operator pertaining to an operator set + * @param registered Whether the operator is currently registered for the operator set + * @param registeredUntil If the operator is not registered, how long until the operator is no longer + * slashable by the AVS. + */ + struct RegistrationStatus { + bool registered; + uint32 registeredUntil; + } + + /** + * @notice Contains allocation info for a specific strategy + * @param maxMagnitude the maximum magnitude that can be allocated between all operator sets + * @param encumberedMagnitude the currently-allocated magnitude for the strategy + */ + struct StrategyInfo { + uint64 maxMagnitude; + uint64 encumberedMagnitude; } /** * @notice Struct containing parameters to slashing * @param operator the address to slash * @param operatorSetId the ID of the operatorSet the operator is being slashed on behalf of - * @param strategies the set of strategies to slash * @param wadToSlash the parts in 1e18 to slash, this will be proportional to the operator's * slashable stake allocation for the operatorSet * @param description the description of the slashing provided by the AVS for legibility @@ -93,34 +120,64 @@ interface IAllocationManagerTypes { struct SlashingParams { address operator; uint32 operatorSetId; - IStrategy[] strategies; uint256 wadToSlash; string description; } /** - * @param encumberedMagnitude the effective magnitude allocated to all operator sets - * for the strategy - * @param currentMagnitude the effective current magnitude allocated to a single operator set - * for the strategy - * @param pendingDiff the pending change in magnitude, if one exists - * @param effectTimestamp the time after which `pendingDiff` will take effect + * @notice struct used to modify the allocation of slashable magnitude to an operator set + * @param operatorSet the operator set to modify the allocation for + * @param strategies the strategies to modify allocations for + * @param newMagnitudes the new magnitude to allocate for each strategy to this operator set */ - struct PendingMagnitudeInfo { - uint64 encumberedMagnitude; - uint64 currentMagnitude; - int128 pendingDiff; - uint32 effectTimestamp; + struct AllocateParams { + OperatorSet operatorSet; + IStrategy[] strategies; + uint64[] newMagnitudes; + } + + /** + * @notice Parameters used to register for an AVS's operator sets + * @param avs the AVS being registered for + * @param operatorSetIds the operator sets within the AVS to register for + * @param data extra data to be passed to the AVS to complete registration + */ + struct RegisterParams { + address avs; + uint32[] operatorSetIds; + bytes data; + } + + /** + * @notice Parameters used to deregister from an AVS's operator sets + * @param operator the operator being deregistered + * @param avs the avs being deregistered from + * @param operatorSetIds the operator sets within the AVS being deregistered from + */ + struct DeregisterParams { + address operator; + address avs; + uint32[] operatorSetIds; + } + + /** + * @notice Parameters used by an AVS to create new operator sets + * @param operatorSetId the id of the operator set to create + * @param strategies the strategies to add as slashable to the operator set + */ + struct CreateSetParams { + uint32 operatorSetId; + IStrategy[] strategies; } } interface IAllocationManagerEvents is IAllocationManagerTypes { /// @notice Emitted when operator updates their allocation delay. - event AllocationDelaySet(address operator, uint32 delay, uint32 effectTimestamp); + event AllocationDelaySet(address operator, uint32 delay, uint32 effectBlock); /// @notice Emitted when an operator's magnitude is updated for a given operatorSet and strategy - event OperatorSetMagnitudeUpdated( - address operator, OperatorSet operatorSet, IStrategy strategy, uint64 magnitude, uint32 effectTimestamp + event AllocationUpdated( + address operator, OperatorSet operatorSet, IStrategy strategy, uint64 magnitude, uint32 effectBlock ); /// @notice Emitted when operator's encumbered magnitude is updated for a given strategy @@ -134,6 +191,28 @@ interface IAllocationManagerEvents is IAllocationManagerTypes { event OperatorSlashed( address operator, OperatorSet operatorSet, IStrategy[] strategies, uint256[] wadSlashed, string description ); + + /// @notice Emitted when an AVS configures the address that will handle registration/deregistration + event AVSRegistrarSet(address avs, IAVSRegistrar registrar); + + /// @notice Emitted when an AVS updates their metadata URI (Uniform Resource Identifier). + /// @dev The URI is never stored; it is simply emitted through an event for off-chain indexing. + event AVSMetadataURIUpdated(address indexed avs, string metadataURI); + + /// @notice Emitted when an operator set is created by an AVS. + event OperatorSetCreated(OperatorSet operatorSet); + + /// @notice Emitted when an operator is added to an operator set. + event OperatorAddedToOperatorSet(address indexed operator, OperatorSet operatorSet); + + /// @notice Emitted when an operator is removed from an operator set. + event OperatorRemovedFromOperatorSet(address indexed operator, OperatorSet operatorSet); + + /// @notice Emitted when a strategy is added to an operator set. + event StrategyAddedToOperatorSet(OperatorSet operatorSet, IStrategy strategy); + + /// @notice Emitted when a strategy is removed from an operator set. + event StrategyRemovedFromOperatorSet(OperatorSet operatorSet, IStrategy strategy); } interface IAllocationManager is ISignatureUtils, IAllocationManagerErrors, IAllocationManagerEvents { @@ -150,15 +229,15 @@ interface IAllocationManager is ISignatureUtils, IAllocationManagerErrors, IAllo ) external; /** - * @notice Modifies the proportions of slashable stake allocated to a list of operatorSets for a set of strategies. - * Note that deallocations remain slashable for DEALLOCATION_DELAY amount of time therefore when they are cleared they may + * @notice Modifies the proportions of slashable stake allocated to an operator set from a list of strategies + * Note that deallocations remain slashable for DEALLOCATION_DELAY blocks therefore when they are cleared they may * free up less allocatable magnitude than initially deallocated. - * @param allocations array of magnitude adjustments for multiple strategies and corresponding operator sets + * @param params array of magnitude adjustments for one or more operator sets * @dev Updates encumberedMagnitude for the updated strategies * @dev msg.sender is used as operator */ function modifyAllocations( - MagnitudeAllocation[] calldata allocations + AllocateParams[] calldata params ) external; /** @@ -178,26 +257,91 @@ interface IAllocationManager is ISignatureUtils, IAllocationManagerErrors, IAllo uint16[] calldata numToClear ) external; + /** + * @notice Allows an operator to register for one or more operator sets for an AVS. If the operator + * has any stake allocated to these operator sets, it immediately becomes slashable. + * @dev After registering within the ALM, this method calls `avs.registerOperator` to complete + * registration. This call MUST succeed in order for registration to be successful. + */ + function registerForOperatorSets( + RegisterParams calldata params + ) external; + + /** + * @notice Allows an operator or AVS to deregister the operator from one or more of the AVS's operator sets. + * If the operator has any slashable stake allocated to the AVS, it remains slashable until the + * DEALLOCATION_DELAY has passed. + * @dev After deregistering within the ALM, this method calls `avs.deregisterOperator` to complete + * deregistration. If this call reverts, it is ignored. + */ + function deregisterFromOperatorSets( + DeregisterParams calldata params + ) external; + /** * @notice Called by the delegation manager to set an operator's allocation delay. - * This is set when the operator first registers, and is the time between an operator + * This is set when the operator first registers, and is the number of blocks between an operator * allocating magnitude to an operator set, and the magnitude becoming slashable. * @param operator The operator to set the delay on behalf of. - * @param delay the allocation delay in seconds + * @param delay the allocation delay in blocks */ function setAllocationDelay(address operator, uint32 delay) external; /** - * @notice Called by an operator to set their allocation delay. This is the time between an operator + * @notice Called by an operator to set their allocation delay. This is number of blocks between an operator * allocating magnitude to an operator set, and the magnitude becoming slashable. - * @dev Note that if an operator's allocation delay is 0, it has not been set yet, - * and the operator will be unable to allocate magnitude to any operator set. - * @param delay the allocation delay in seconds + * @dev Note that if an operator's allocation delay has not been set, the operator will be unable to allocate + * slashable magnitude to any operator set. + * @param delay the allocation delay in blocks */ function setAllocationDelay( uint32 delay ) external; + /** + * @notice Called by an AVS to configure the address that is called when an operator registers + * or is deregistered from the AVS's operator sets. If not set (or set to 0), defaults + * to the AVS's address. + * @param registrar the new registrar address + */ + function setAVSRegistrar( + IAVSRegistrar registrar + ) external; + + /** + * @notice Called by an AVS to emit an `AVSMetadataURIUpdated` event indicating the information has updated. + * + * @param metadataURI The URI for metadata associated with an AVS. + * + * @dev Note that the `metadataURI` is *never stored* and is only emitted in the `AVSMetadataURIUpdated` event. + */ + function updateAVSMetadataURI( + string calldata metadataURI + ) external; + + /** + * @notice Allows an AVS to create new operator sets, defining strategies that the operator set uses + */ + function createOperatorSets( + CreateSetParams[] calldata params + ) external; + + /** + * @notice Allows an AVS to add strategies to an operator set + * @dev Strategies MUST NOT already exist in the operator set + * @param operatorSetId the operator set to add strategies to + * @param strategies the strategies to add + */ + function addStrategiesToOperatorSet(uint32 operatorSetId, IStrategy[] calldata strategies) external; + + /** + * @notice Allows an AVS to remove strategies from an operator set + * @dev Strategies MUST already exist in the operator set + * @param operatorSetId the operator set to remove strategies from + * @param strategies the strategies to remove + */ + function removeStrategiesFromOperatorSet(uint32 operatorSetId, IStrategy[] calldata strategies) external; + /** * * VIEW FUNCTIONS @@ -205,49 +349,65 @@ interface IAllocationManager is ISignatureUtils, IAllocationManagerErrors, IAllo */ /** - * @notice Returns the effective magnitude info for each of an operator's operator sets. - * This method fetches the complete list of an operator's operator sets, then applies any - * completable allocation modifications to return the effective, up-to-date current and - * pending magnitude allocations for each operator set. + * @notice Returns the list of operator sets the operator has current or pending allocations/deallocations in + * @param operator the operator to query + * @return the list of operator sets the operator has current or pending allocations/deallocations in + */ + function getAllocatedSets( + address operator + ) external view returns (OperatorSet[] memory); + + /** + * @notice Returns the list of strategies an operator has current or pending allocations/deallocations from + * given a specific operator set. * @param operator the operator to query - * @param strategy the strategy to get allocation info for - * @return the list of the operator's operator sets - * @return the corresponding allocation details for each operator set + * @param operatorSet the operator set to query + * @return the list of strategies */ - function getAllocationInfo( + function getAllocatedStrategies( address operator, - IStrategy strategy - ) external view returns (OperatorSet[] memory, MagnitudeInfo[] memory); + OperatorSet memory operatorSet + ) external view returns (IStrategy[] memory); /** - * @notice Returns the effective magnitude info for each operator set. This method - * automatically applies any completable modifications, returning the effective - * current and pending allocations for each operator set. + * @notice Returns the current/pending stake allocation an operator has from a strategy to an operator set * @param operator the operator to query - * @param strategy the strategy to get allocation info for - * @param operatorSets the operatorSets to get allocation info for - * @return The magnitude info for each operator set + * @param operatorSet the operator set to query + * @param strategy the strategy to query + * @return the current/pending stake allocation */ - function getAllocationInfo( + function getAllocation( address operator, - IStrategy strategy, - OperatorSet[] calldata operatorSets - ) external view returns (MagnitudeInfo[] memory); + OperatorSet memory operatorSet, + IStrategy strategy + ) external view returns (Allocation memory); /** - * @notice Returns the effective magnitude info for each operator for each strategy for the operatorSet This method - * automatically applies any completable modifications, returning the effective - * current and pending allocations for each operator set. + * @notice Returns the current/pending stake allocations for multiple operators from a strategy to an operator set + * @param operators the operators to query * @param operatorSet the operator set to query - * @param strategies the strategies to get allocation info for - * @param operators the operators to get allocation info for - * @return The magnitude info for each operator for each strategy + * @param strategy the strategy to query + * @return each operator's allocation */ - function getAllocationInfo( - OperatorSet calldata operatorSet, - IStrategy[] calldata strategies, - address[] calldata operators - ) external view returns (MagnitudeInfo[][] memory); + function getAllocations( + address[] memory operators, + OperatorSet memory operatorSet, + IStrategy strategy + ) external view returns (Allocation[] memory); + + /** + * @notice Given a strategy, returns a list of operator sets and corresponding stake allocations. + * @dev Note that this returns a list of ALL operator sets the operator has allocations in. This means + * some of the returned allocations may be zero. + * @param operator the operator to query + * @param strategy the strategy to query + * @return the list of all operator sets the operator has allocations for + * @return the corresponding list of allocations from the specific `strategy` + */ + function getStrategyAllocations( + address operator, + IStrategy strategy + ) external view returns (OperatorSet[] memory, Allocation[] memory); /** * @notice For a strategy, get the amount of magnitude not currently allocated to any operator set @@ -270,59 +430,91 @@ interface IAllocationManager is ISignatureUtils, IAllocationManagerErrors, IAllo IStrategy[] calldata strategies ) external view returns (uint64[] memory); + /** + * @notice Returns the maximum magnitudes each operator can allocate for the given strategy + * @dev The max magnitude of an operator starts at WAD (1e18), and is decreased anytime + * the operator is slashed. This value acts as a cap on the max magnitude of the operator. + * @param operators the operators to query + * @param strategy the strategy to get the max magnitudes for + * @return the max magnitudes for each operator + */ + function getMaxMagnitudes( + address[] calldata operators, + IStrategy strategy + ) external view returns (uint64[] memory); + /** * @notice Returns the maximum magnitude an operator can allocate for the given strategies - * at a given timestamp + * at a given block number * @dev The max magnitude of an operator starts at WAD (1e18), and is decreased anytime * the operator is slashed. This value acts as a cap on the max magnitude of the operator. * @param operator the operator to query * @param strategies the strategies to get the max magnitudes for - * @param timestamp the timestamp at which to check the max magnitudes + * @param blockNumber the blockNumber at which to check the max magnitudes * @return the max magnitudes for each strategy */ - function getMaxMagnitudesAtTimestamp( + function getMaxMagnitudesAtBlock( address operator, IStrategy[] calldata strategies, - uint32 timestamp + uint32 blockNumber ) external view returns (uint64[] memory); /** - * @notice Returns the time in seconds between an operator allocating slashable magnitude + * @notice Returns the time in blocks between an operator allocating slashable magnitude * and the magnitude becoming slashable. If the delay has not been set, `isSet` will be false. * @dev The operator must have a configured delay before allocating magnitude * @param operator The operator to query * @return isSet Whether the operator has configured a delay - * @return delay The time in seconds between allocating magnitude and magnitude becoming slashable + * @return delay The time in blocks between allocating magnitude and magnitude becoming slashable */ function getAllocationDelay( address operator ) external view returns (bool isSet, uint32 delay); /** - * @notice returns the current operatorShares and the slashableOperatorShares for an operator, list of strategies, - * and an operatorSet - * @param operatorSet the operatorSet to get the shares for - * @param operators the operators to get the shares for - * @param strategies the strategies to get the shares for + * @notice Returns a list of all operator sets the operator is registered for + * @param operator The operator address to query. */ - function getCurrentDelegatedAndSlashableOperatorShares( - OperatorSet calldata operatorSet, - address[] calldata operators, - IStrategy[] calldata strategies - ) external view returns (uint256[][] memory, uint256[][] memory); + function getRegisteredSets( + address operator + ) external view returns (OperatorSet[] memory operatorSets); /** - * @notice returns the minimum operatorShares and the slashableOperatorShares for an operator, list of strategies, - * and an operatorSet before a given timestamp. This is used to get the shares to weight operators by given ones slashing window. - * @param operatorSet the operatorSet to get the shares for - * @param operators the operators to get the shares for - * @param strategies the strategies to get the shares for - * @param beforeTimestamp the timestamp to get the shares at + * @notice Returns whether the operator set exists */ - function getMinDelegatedAndSlashableOperatorSharesBefore( - OperatorSet calldata operatorSet, - address[] calldata operators, - IStrategy[] calldata strategies, - uint32 beforeTimestamp - ) external view returns (uint256[][] memory, uint256[][] memory); + function isOperatorSet( + OperatorSet memory operatorSet + ) external view returns (bool); + + /** + * @notice Returns all the operators registered to an operator set + * @param operatorSet The operatorSet to query. + */ + function getMembers( + OperatorSet memory operatorSet + ) external view returns (address[] memory operators); + + /** + * @notice Returns the number of operators registered to an operatorSet. + * @param operatorSet The operatorSet to get the member count for + */ + function getMemberCount( + OperatorSet memory operatorSet + ) external view returns (uint256); + + /** + * @notice Returns the address that handles registration/deregistration for the AVS + * If not set, defaults to the input address (`avs`) + */ + function getAVSRegistrar( + address avs + ) external view returns (IAVSRegistrar); + + /** + * @notice Returns an array of strategies in the operatorSet. + * @param operatorSet The operatorSet to query. + */ + function getStrategiesInOperatorSet( + OperatorSet memory operatorSet + ) external view returns (IStrategy[] memory strategies); } diff --git a/src/contracts/interfaces/IDelegationManager.sol b/src/contracts/interfaces/IDelegationManager.sol index 67f29e931..fd8acd8f1 100644 --- a/src/contracts/interfaces/IDelegationManager.sol +++ b/src/contracts/interfaces/IDelegationManager.sol @@ -133,6 +133,11 @@ interface IDelegationManagerTypes { // The address of the withdrawer address withdrawer; } + + struct BeaconChainSlashingFactor { + bool isSet; + uint64 slashingFactor; + } } interface IDelegationManagerEvents is IDelegationManagerTypes { @@ -493,6 +498,18 @@ interface IDelegationManager is ISignatureUtils, IDelegationManagerErrors, IDele address staker ) external view returns (IStrategy[] memory, uint256[] memory); + /** + * @notice Returns the scaling factor applied to a staker's deposits for a given strategy + */ + function depositScalingFactor(address staker, IStrategy strategy) external view returns (uint256); + + /** + * @notice Returns the slashing factor applied to the staker's beacon chain ETH shares + */ + function getBeaconChainSlashingFactor( + address staker + ) external view returns (uint64); + /** * @notice Returns the minimum withdrawal delay in blocks to pass for withdrawals queued to be completable. * Also applies to legacy withdrawals so any withdrawals not completed prior to the slashing upgrade will be subject diff --git a/src/contracts/libraries/OperatorSetLib.sol b/src/contracts/libraries/OperatorSetLib.sol new file mode 100644 index 000000000..5b43359e3 --- /dev/null +++ b/src/contracts/libraries/OperatorSetLib.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +struct OperatorSet { + address avs; + uint32 id; +} + +library OperatorSetLib { + function key( + OperatorSet memory os + ) internal pure returns (bytes32) { + return bytes32(abi.encodePacked(os.avs, uint96(os.id))); + } + + function decode( + bytes32 _key + ) internal pure returns (OperatorSet memory) { + /// forgefmt: disable-next-item + return OperatorSet({ + avs: address(uint160(uint256(_key) >> 96)), + id: uint32(uint256(_key) & type(uint96).max) + }); + } +} diff --git a/src/contracts/libraries/SlashingLib.sol b/src/contracts/libraries/SlashingLib.sol index 88d044edf..f9f6bb681 100644 --- a/src/contracts/libraries/SlashingLib.sol +++ b/src/contracts/libraries/SlashingLib.sol @@ -23,13 +23,11 @@ uint64 constant WAD = 1e18; * Note that `withdrawal.scaledShares` is scaled for the beaconChainETHStrategy to divide by the beaconChainScalingFactor upon queueing * and multiply by the beaconChainScalingFactor upon withdrawal */ -struct StakerScalingFactors { - uint184 depositScalingFactor; - uint64 beaconChainScalingFactor; - bool isBeaconChainScalingFactorSet; +struct DepositScalingFactor { + uint256 _scalingFactor; } -using SlashingLib for StakerScalingFactors global; +using SlashingLib for DepositScalingFactor global; // TODO: validate order of operations everywhere library SlashingLib { @@ -58,130 +56,91 @@ library SlashingLib { // GETTERS - /** - * @dev We want to avoid divide by 0 situations, so if an operator's maxMagnitude is 0, we consider them - * to be "fully slashed" for that strategy and revert with a error. The same goes for a staker whose - * beaconChainScalingFactor is 0, at which point they are considered "fully slashed". - * @param ssf The staker's scaling factors for a given strategy. Here, we care about their beaconChainScalingFactor - * @param operatorMaxMagnitude The maxMagnitude of the operator for a given strategy - * @return bool true if either the operator or staker are fully slashed - */ - function isFullySlashed( - StakerScalingFactors memory ssf, - uint64 operatorMaxMagnitude - ) internal pure returns (bool) { - return operatorMaxMagnitude == 0 || (ssf.getBeaconChainScalingFactor() == 0); - } - - function getDepositScalingFactor( - StakerScalingFactors memory ssf + function scalingFactor( + DepositScalingFactor memory dsf ) internal pure returns (uint256) { - return ssf.depositScalingFactor == 0 ? WAD : ssf.depositScalingFactor; - } - - function getBeaconChainScalingFactor( - StakerScalingFactors memory ssf - ) internal pure returns (uint64) { - return ssf.isBeaconChainScalingFactorSet ? ssf.beaconChainScalingFactor : WAD; + return dsf._scalingFactor == 0 ? WAD : dsf._scalingFactor; } function scaleSharesForQueuedWithdrawal( uint256 sharesToWithdraw, - StakerScalingFactors memory ssf, - uint64 operatorMagnitude + uint256 slashingFactor ) internal pure returns (uint256) { - /// forgefmt: disable-next-item - return sharesToWithdraw - .divWad(uint256(ssf.getBeaconChainScalingFactor())) - .divWad(uint256(operatorMagnitude)); + if (slashingFactor == 0) { + return 0; + } + + return sharesToWithdraw.divWad(slashingFactor); } function scaleSharesForCompleteWithdrawal( uint256 scaledShares, - StakerScalingFactors memory ssf, - uint64 operatorMagnitude + uint256 slashingFactor ) internal pure returns (uint256) { - /// forgefmt: disable-next-item - return scaledShares - .mulWad(uint256(ssf.getBeaconChainScalingFactor())) - .mulWad(uint256(operatorMagnitude)); - } - - function calcSlashedAmount(uint256 operatorShares, uint256 wadSlashed) internal pure returns (uint256) { - return operatorShares.mulWad(wadSlashed); - } - - function decreaseBeaconChainScalingFactor( - StakerScalingFactors storage ssf, - uint64 proportionOfOldBalance - ) internal { - ssf.beaconChainScalingFactor = uint64(uint256(ssf.getBeaconChainScalingFactor()).mulWad(proportionOfOldBalance)); - ssf.isBeaconChainScalingFactorSet = true; + return scaledShares.mulWad(slashingFactor); } - function updateDepositScalingFactor( - StakerScalingFactors storage ssf, + function update( + DepositScalingFactor storage dsf, uint256 existingDepositShares, uint256 addedShares, - uint64 maxMagnitude + uint256 slashingFactor ) internal { if (existingDepositShares == 0) { // if this is their first deposit for the operator, set the scaling factor to inverse of maxMagnitude /// forgefmt: disable-next-item - ssf.depositScalingFactor = uint256(WAD) - .divWad(ssf.getBeaconChainScalingFactor()) - .divWad(maxMagnitude) - .toUint184(); + dsf._scalingFactor = uint256(WAD).divWad(slashingFactor); return; } /** * Base Equations: * (1) newShares = currentShares + addedShares * (2) newDepositShares = existingDepositShares + addedShares - * (3) newShares = newDepositShares * newStakerDepositScalingFactor * beaconChainScalingFactor * maxMagnitude + * (3) newShares = newDepositShares * newDepositScalingFactor * slashingFactor * * Plugging (1) into (3): - * (4) newDepositShares * newStakerDepositScalingFactor * beaconChainScalingFactor * maxMagnitude = currentShares + addedShares + * (4) newDepositShares * newDepositScalingFactor * slashingFactor = currentShares + addedShares * - * Solving for newStakerDepositScalingFactor - * (5) newStakerDepositScalingFactor = (currentShares + addedShares) / (newDepositShares * beaconChainScalingFactor * maxMagnitude) + * Solving for newDepositScalingFactor + * (5) newDepositScalingFactor = (currentShares + addedShares) / (newDepositShares * slashingFactor) * * Plugging in (2) into (5): - * (7) newStakerDepositScalingFactor = (currentShares + addedShares) / ((existingDepositShares + addedShares) * beaconChainScalingFactor * maxMagnitude) + * (7) newDepositScalingFactor = (currentShares + addedShares) / ((existingDepositShares + addedShares) * slashingFactor) * Note that magnitudes must be divided by WAD for precision. Thus, * - * (8) newStakerDepositScalingFactor = WAD * (currentShares + addedShares) / ((existingDepositShares + addedShares) * beaconChainScalingFactor / WAD * maxMagnitude / WAD) - * (9) newStakerDepositScalingFactor = (currentShares + addedShares) * WAD / (existingDepositShares + addedShares) * WAD / beaconChainScalingFactor * WAD / maxMagnitude + * (8) newDepositScalingFactor = WAD * (currentShares + addedShares) / ((existingDepositShares + addedShares) * slashingFactor / WAD) + * (9) newDepositScalingFactor = (currentShares + addedShares) * WAD / (existingDepositShares + addedShares) * WAD / slashingFactor */ // Step 1: Calculate Numerator - uint256 currentShares = existingDepositShares.toShares(ssf, maxMagnitude); + uint256 currentShares = dsf.calcWithdrawable(existingDepositShares, slashingFactor); // Step 2: Compute currentShares + addedShares uint256 newShares = currentShares + addedShares; - // Step 3: Calculate newStakerDepositScalingFactor + // Step 3: Calculate newDepositScalingFactor /// forgefmt: disable-next-item - uint184 newStakerDepositScalingFactor = newShares + uint256 newDepositScalingFactor = newShares .divWad(existingDepositShares + addedShares) - .divWad(maxMagnitude) - .divWad(uint256(ssf.getBeaconChainScalingFactor())) - .toUint184(); + .divWad(slashingFactor); - ssf.depositScalingFactor = newStakerDepositScalingFactor; + dsf._scalingFactor = newDepositScalingFactor; } // CONVERSION - function toShares( + function calcWithdrawable( + DepositScalingFactor memory dsf, uint256 depositShares, - StakerScalingFactors memory ssf, - uint64 magnitude - ) internal pure returns (uint256 shares) { + uint256 slashingFactor + ) internal pure returns (uint256) { /// forgefmt: disable-next-item - shares = depositShares - .mulWad(ssf.getDepositScalingFactor()) - .mulWad(uint256(ssf.getBeaconChainScalingFactor())) - .mulWad(uint256(magnitude)); + return depositShares + .mulWad(dsf.scalingFactor()) + .mulWad(slashingFactor); + } + + function calcSlashedAmount(uint256 operatorShares, uint256 wadSlashed) internal pure returns (uint256) { + return operatorShares.mulWad(wadSlashed); } } diff --git a/src/contracts/strategies/StrategyBase.sol b/src/contracts/strategies/StrategyBase.sol index 91e5a2c10..555fe6a6e 100644 --- a/src/contracts/strategies/StrategyBase.sol +++ b/src/contracts/strategies/StrategyBase.sol @@ -180,7 +180,10 @@ contract StrategyBase is Initializable, Pausable, IStrategy { * @notice Called in the external `deposit` function, before any logic is executed. Expected to be overridden if strategies want such logic. * @param token The token being deposited */ - function _beforeDeposit(IERC20 token, uint256 amount) internal virtual { + function _beforeDeposit( + IERC20 token, + uint256 // amount + ) internal virtual { require(token == underlyingToken, OnlyUnderlyingToken()); } @@ -188,7 +191,11 @@ contract StrategyBase is Initializable, Pausable, IStrategy { * @notice Called in the external `withdraw` function, before any logic is executed. Expected to be overridden if strategies want such logic. * @param token The token being withdrawn */ - function _beforeWithdrawal(address recipient, IERC20 token, uint256 amountShares) internal virtual { + function _beforeWithdrawal( + address, // recipient + IERC20 token, + uint256 // amountShares + ) internal virtual { require(token == underlyingToken, OnlyUnderlyingToken()); } diff --git a/src/test/DevnetLifecycle.t.sol b/src/test/DevnetLifecycle.t.sol index ca9972182..da97dd37c 100644 --- a/src/test/DevnetLifecycle.t.sol +++ b/src/test/DevnetLifecycle.t.sol @@ -30,9 +30,10 @@ contract Devnet_Lifecycle_Test is Test { address public operator; uint256 operatorPk = 420; address public avs = address(0x3); - uint32 public operatorSet = 1; + uint32 public operatorSetId = 1; uint256 public wethAmount = 100 ether; uint256 public wethShares = 100 ether; + OperatorSet public operatorSet; // Values uint64 public magnitudeToSet = 1e18; @@ -48,17 +49,18 @@ contract Devnet_Lifecycle_Test is Test { // Set operator operator = cheats.addr(operatorPk); + operatorSet = OperatorSet({avs: avs, id: operatorSetId}); } function _getOperatorSetArray() internal view returns (uint32[] memory) { uint32[] memory operatorSets = new uint32[](1); - operatorSets[0] = operatorSet; + operatorSets[0] = operatorSetId; return operatorSets; } function _getOperatorSetsArray() internal view returns (OperatorSet[] memory) { OperatorSet[] memory operatorSets = new OperatorSet[](1); - operatorSets[0] = OperatorSet({avs: avs, operatorSetId: operatorSet}); + operatorSets[0] = OperatorSet({avs: avs, id: operatorSetId}); return operatorSets; } @@ -137,79 +139,61 @@ contract Devnet_Lifecycle_Test is Test { function _registerAVS() internal { cheats.startPrank(avs); - avsDirectory.createOperatorSets(_getOperatorSetArray()); - avsDirectory.becomeOperatorSetAVS(); - cheats.stopPrank(); + IStrategy[] memory strategies = new IStrategy[](1); + strategies[0] = wethStrategy; - // Assert storage - assertTrue(avsDirectory.isOperatorSetAVS(avs)); + IAllocationManagerTypes.CreateSetParams memory createSetParams = IAllocationManagerTypes.CreateSetParams({ + operatorSetId: operatorSetId, + strategies: strategies + }); + + IAllocationManagerTypes.CreateSetParams[] memory array = new IAllocationManagerTypes.CreateSetParams[](1); + array[0] = createSetParams; + + allocationManager.createOperatorSets(array); + cheats.stopPrank(); } function _registerOperatorToAVS() public { - bytes32 salt = bytes32(0); - uint256 expiry = type(uint256).max; - (uint8 v, bytes32 r, bytes32 s) = cheats.sign( - operatorPk, - avsDirectory.calculateOperatorSetRegistrationDigestHash(avs, _getOperatorSetArray(), salt, expiry) - ); + cheats.prank(operator); + + uint32[] memory operatorSetIds = new uint32[](1); + operatorSetIds[0] = operatorSetId; - cheats.prank(avs); - avsDirectory.registerOperatorToOperatorSets( - operator, - _getOperatorSetArray(), - ISignatureUtils.SignatureWithSaltAndExpiry(abi.encodePacked(r, s, v), salt, expiry) - ); - - // Assert registration - assertTrue(avsDirectory.isMember( - operator, - OperatorSet({ - avs: avs, - operatorSetId: operatorSet - }) - )); - - // Assert operator is slashable - assertTrue(avsDirectory.isOperatorSlashable( - operator, - OperatorSet({ - avs: avs, - operatorSetId: operatorSet - }) - )); + allocationManager.registerForOperatorSets(IAllocationManagerTypes.RegisterParams(avs, operatorSetIds, "")); + + assertEq(allocationManager.getMembers(OperatorSet(avs, operatorSetId))[0], operator); } function _setMagnitude() public { - OperatorSet[] memory operatorSets = new OperatorSet[](1); - operatorSets[0] = OperatorSet({avs: avs, operatorSetId: operatorSet}); + IStrategy[] memory strategies = new IStrategy[](1); + strategies[0] = wethStrategy; uint64[] memory magnitudes = new uint64[](1); magnitudes[0] = magnitudeToSet; - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - new IAllocationManagerTypes.MagnitudeAllocation[](1); - allocations[0] = IAllocationManagerTypes.MagnitudeAllocation({ - strategy: wethStrategy, - expectedMaxMagnitude: 1e18, - operatorSets: operatorSets, - magnitudes: magnitudes + IAllocationManagerTypes.AllocateParams[] memory allocations = new IAllocationManagerTypes.AllocateParams[](1); + allocations[0] = IAllocationManagerTypes.AllocateParams({ + operatorSet: operatorSet, + strategies: strategies, + newMagnitudes: magnitudes }); cheats.prank(operator); allocationManager.modifyAllocations(allocations); // Assert storage - IAllocationManagerTypes.MagnitudeInfo[] memory infos = allocationManager.getAllocationInfo(operator, wethStrategy, _getOperatorSetsArray()); - assertEq(infos[0].currentMagnitude, 0); - assertEq(infos[0].pendingDiff, int128(uint128(magnitudeToSet))); - assertEq(infos[0].effectTimestamp, block.number + 1); + IAllocationManagerTypes.Allocation memory info = allocationManager.getAllocation(operator, operatorSet, wethStrategy); + assertEq(info.currentMagnitude, 0); + assertEq(info.pendingDiff, int128(uint128(magnitudeToSet))); + assertEq(info.effectBlock, block.number + 1); // Warp to effect timestamp cheats.roll(block.number + 1); // Check allocation - infos = allocationManager.getAllocationInfo(operator, wethStrategy, _getOperatorSetsArray()); - assertEq(infos[0].currentMagnitude, magnitudeToSet); + info = allocationManager.getAllocation(operator, operatorSet, wethStrategy); + assertEq(info.currentMagnitude, magnitudeToSet); } function _slashOperator() public { @@ -219,7 +203,6 @@ contract Devnet_Lifecycle_Test is Test { IAllocationManagerTypes.SlashingParams memory slashingParams = IAllocationManagerTypes.SlashingParams({ operator: operator, operatorSetId: 1, - strategies: strategies, wadToSlash: 5e17, description: "test" }); @@ -229,8 +212,8 @@ contract Devnet_Lifecycle_Test is Test { allocationManager.slashOperator(slashingParams); // Assert storage - IAllocationManagerTypes.MagnitudeInfo[] memory infos = allocationManager.getAllocationInfo(operator, wethStrategy, _getOperatorSetsArray()); - assertEq(infos[0].currentMagnitude, magnitudeToSet - 5e17); + IAllocationManagerTypes.Allocation memory info = allocationManager.getAllocation(operator, operatorSet, wethStrategy); + assertEq(info.currentMagnitude, magnitudeToSet - 5e17); } function _withdrawStaker() public { @@ -257,7 +240,7 @@ contract Devnet_Lifecycle_Test is Test { strategies: strategies, scaledShares: scaledShares }); - bytes32 withdrawalRoot = delegationManager.calculateWithdrawalRoot(withdrawal); + // bytes32 withdrawalRoot = delegationManager.calculateWithdrawalRoot(withdrawal); // Generate complete withdrawal params cheats.startPrank(staker); diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index 79c815495..65be79f26 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -184,7 +184,7 @@ abstract contract IntegrationBase is IntegrationDeployer { COMMON ASSERTIONS *******************************************************************************/ - function assert_HasNoDelegatableShares(User user, string memory err) internal { + function assert_HasNoDelegatableShares(User user, string memory err) internal view { (IStrategy[] memory strategies, uint[] memory shares) = delegationManager.getDepositedShares(address(user)); @@ -197,7 +197,7 @@ abstract contract IntegrationBase is IntegrationDeployer { IStrategy[] memory strategies, uint[] memory expectedBalances, string memory err - ) internal { + ) internal view { for (uint i = 0; i < strategies.length; i++) { IStrategy strat = strategies[i]; @@ -214,7 +214,7 @@ abstract contract IntegrationBase is IntegrationDeployer { } } - function assert_HasNoUnderlyingTokenBalance(User user, IStrategy[] memory strategies, string memory err) internal { + function assert_HasNoUnderlyingTokenBalance(User user, IStrategy[] memory strategies, string memory err) internal view { assert_HasUnderlyingTokenBalances(user, strategies, new uint[](strategies.length), err); } @@ -223,7 +223,7 @@ abstract contract IntegrationBase is IntegrationDeployer { IStrategy[] memory strategies, uint[] memory expectedShares, string memory err - ) internal { + ) internal view { for (uint i = 0; i < strategies.length; i++) { IStrategy strat = strategies[i]; @@ -251,7 +251,7 @@ abstract contract IntegrationBase is IntegrationDeployer { IStrategy[] memory strategies, uint[] memory expectedShares, string memory err - ) internal { + ) internal view { for (uint i = 0; i < strategies.length; i++) { IStrategy strat = strategies[i]; @@ -262,25 +262,25 @@ abstract contract IntegrationBase is IntegrationDeployer { } /// @dev Asserts that ALL of the `withdrawalRoots` is in `delegationManager.pendingWithdrawals` - function assert_AllWithdrawalsPending(bytes32[] memory withdrawalRoots, string memory err) internal { + function assert_AllWithdrawalsPending(bytes32[] memory withdrawalRoots, string memory err) internal view { for (uint i = 0; i < withdrawalRoots.length; i++) { assert_WithdrawalPending(withdrawalRoots[i], err); } } /// @dev Asserts that NONE of the `withdrawalRoots` is in `delegationManager.pendingWithdrawals` - function assert_NoWithdrawalsPending(bytes32[] memory withdrawalRoots, string memory err) internal { + function assert_NoWithdrawalsPending(bytes32[] memory withdrawalRoots, string memory err) internal view { for (uint i = 0; i < withdrawalRoots.length; i++) { assert_WithdrawalNotPending(withdrawalRoots[i], err); } } /// @dev Asserts that the hash of each withdrawal corresponds to the provided withdrawal root - function assert_WithdrawalPending(bytes32 withdrawalRoot, string memory err) internal { + function assert_WithdrawalPending(bytes32 withdrawalRoot, string memory err) internal view { assertTrue(delegationManager.pendingWithdrawals(withdrawalRoot), err); } - function assert_WithdrawalNotPending(bytes32 withdrawalRoot, string memory err) internal { + function assert_WithdrawalNotPending(bytes32 withdrawalRoot, string memory err) internal view { assertFalse(delegationManager.pendingWithdrawals(withdrawalRoot), err); } @@ -288,7 +288,7 @@ abstract contract IntegrationBase is IntegrationDeployer { IDelegationManagerTypes.Withdrawal[] memory withdrawals, bytes32[] memory withdrawalRoots, string memory err - ) internal { + ) internal view { for (uint i = 0; i < withdrawals.length; i++) { assert_ValidWithdrawalHash(withdrawals[i], withdrawalRoots[i], err); } @@ -298,7 +298,7 @@ abstract contract IntegrationBase is IntegrationDeployer { IDelegationManagerTypes.Withdrawal memory withdrawal, bytes32 withdrawalRoot, string memory err - ) internal { + ) internal view { assertEq(withdrawalRoot, delegationManager.calculateWithdrawalRoot(withdrawal), err); } @@ -306,7 +306,7 @@ abstract contract IntegrationBase is IntegrationDeployer { User staker, uint expectedBalance, string memory err - ) internal { + ) internal view { EigenPod pod = staker.pod(); assertEq(address(pod).balance, expectedBalance, err); } @@ -314,7 +314,7 @@ abstract contract IntegrationBase is IntegrationDeployer { function assert_ProofsRemainingEqualsActive( User staker, string memory err - ) internal { + ) internal view { EigenPod pod = staker.pod(); assertEq(pod.currentCheckpoint().proofsRemaining, pod.activeValidatorCount(), err); } @@ -323,7 +323,7 @@ abstract contract IntegrationBase is IntegrationDeployer { User staker, uint64 expectedPodBalanceGwei, string memory err - ) internal { + ) internal view { EigenPod pod = staker.pod(); assertEq(pod.currentCheckpoint().podBalanceGwei, expectedPodBalanceGwei, err); } @@ -1157,14 +1157,13 @@ abstract contract IntegrationBase is IntegrationDeployer { } /// @dev Uses timewarp modifier to get staker beacon chain scaling factor at the last snapshot - function _getPrevBeaconChainScalingFactor(User staker) internal timewarp() returns (uint64) { - return _getBeaconChainScalingFactor(staker); + function _getPrevBeaconChainSlashingFactor(User staker) internal timewarp() returns (uint64) { + return _getBeaconChainSlashingFactor(staker); } /// @dev Looks up the staker's beacon chain scaling factor - function _getBeaconChainScalingFactor(User staker) internal view returns (uint64) { - (, uint64 beaconChainScalingFactor, bool isBeaconChainScalingFactorSet)= delegationManager.stakerScalingFactor(address(staker), BEACONCHAIN_ETH_STRAT); - return isBeaconChainScalingFactorSet ? beaconChainScalingFactor : WAD; + function _getBeaconChainSlashingFactor(User staker) internal view returns (uint64) { + return delegationManager.getBeaconChainSlashingFactor(address(staker)); } function _getPrevCumulativeWithdrawals(User staker) internal timewarp() returns (uint) { diff --git a/src/test/integration/IntegrationDeployer.t.sol b/src/test/integration/IntegrationDeployer.t.sol index 21e4baa1f..ad158628e 100644 --- a/src/test/integration/IntegrationDeployer.t.sol +++ b/src/test/integration/IntegrationDeployer.t.sol @@ -263,9 +263,9 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser { delegationManager, eigenLayerPauserReg ); - avsDirectoryImplementation = new AVSDirectory(delegationManager, eigenLayerPauserReg, DEALLOCATION_DELAY); + avsDirectoryImplementation = new AVSDirectory(delegationManager, eigenLayerPauserReg); strategyFactoryImplementation = new StrategyFactory(strategyManager, eigenLayerPauserReg); - allocationManagerImplementation = new AllocationManager(delegationManager, avsDirectory, eigenLayerPauserReg, DEALLOCATION_DELAY, ALLOCATION_CONFIGURATION_DELAY); + allocationManagerImplementation = new AllocationManager(delegationManager, eigenLayerPauserReg, DEALLOCATION_DELAY, ALLOCATION_CONFIGURATION_DELAY); // Third, upgrade the proxy contracts to point to the implementations uint256 withdrawalDelayBlocks = 7 days / 12 seconds; @@ -401,7 +401,7 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser { delegationManager, eigenLayerPauserReg ); - avsDirectoryImplementation = new AVSDirectory(delegationManager, eigenLayerPauserReg, DEALLOCATION_DELAY); + avsDirectoryImplementation = new AVSDirectory(delegationManager, eigenLayerPauserReg); // Second, upgrade the proxy contracts to point to the implementations // DelegationManager @@ -488,7 +488,7 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser { delegationManager, eigenLayerPauserReg ); - avsDirectoryImplementation = new AVSDirectory(delegationManager, eigenLayerPauserReg, DEALLOCATION_DELAY); + avsDirectoryImplementation = new AVSDirectory(delegationManager, eigenLayerPauserReg); // Second, upgrade the proxy contracts to point to the implementations // DelegationManager diff --git a/src/test/mocks/AVSDirectoryMock.sol b/src/test/mocks/AVSDirectoryMock.sol index b7a3d702e..8179ecf86 100644 --- a/src/test/mocks/AVSDirectoryMock.sol +++ b/src/test/mocks/AVSDirectoryMock.sol @@ -3,44 +3,9 @@ pragma solidity ^0.8.9; import "forge-std/Test.sol"; import "src/contracts/interfaces/IAVSDirectory.sol"; +import "src/contracts/libraries/OperatorSetLib.sol"; contract AVSDirectoryMock is Test { receive() external payable {} fallback() external payable {} - - mapping(address => mapping(bytes32 => bool)) public _isOperatorSlashable; - mapping(bytes32 => bool) public _isOperatorSetBatch; - - function isOperatorSlashable(address operator, OperatorSet memory operatorSet) public virtual view returns (bool) { - return _isOperatorSlashable[operator][bytes32(abi.encode(operatorSet))]; - } - - function isOperatorSetBatch(OperatorSet[] memory operatorSets) public virtual view returns (bool) { - return _isOperatorSetBatch[keccak256(abi.encode(operatorSets))]; - } - - function setIsOperatorSlashable( - address operator, - OperatorSet memory operatorSet, - bool value - ) public virtual { - _isOperatorSlashable[operator][bytes32(abi.encode(operatorSet))] = value; - } - - function setIsOperatorSlashable( - address operator, - address avs, - uint32 operatorSetId, - bool value - ) public virtual { - OperatorSet memory operatorSet = OperatorSet({ - avs: avs, - operatorSetId: operatorSetId - }); - setIsOperatorSlashable(operator, operatorSet, value); - } - - function setIsOperatorSetBatch(OperatorSet[] memory operatorSets, bool value) public virtual { - _isOperatorSetBatch[keccak256(abi.encode(operatorSets))] = value; - } } \ No newline at end of file diff --git a/src/test/mocks/AllocationManagerMock.sol b/src/test/mocks/AllocationManagerMock.sol index 4c74ad93a..f8d7e5d90 100644 --- a/src/test/mocks/AllocationManagerMock.sol +++ b/src/test/mocks/AllocationManagerMock.sol @@ -47,15 +47,15 @@ contract AllocationManagerMock is Test { return maxMagnitudes; } - function getMaxMagnitudesAtTimestamp( + function getMaxMagnitudesAtBlock( address operator, IStrategy[] calldata strategies, - uint32 timestamp + uint32 blockNumber ) external view returns (uint64[] memory) { uint64[] memory maxMagnitudes = new uint64[](strategies.length); for (uint256 i = 0; i < strategies.length; ++i) { - maxMagnitudes[i] = _maxMagnitudeHistory[operator][strategies[i]].upperLookup(timestamp); + maxMagnitudes[i] = _maxMagnitudeHistory[operator][strategies[i]].upperLookup(blockNumber); } return maxMagnitudes; diff --git a/src/test/mocks/EigenPodManagerMock.sol b/src/test/mocks/EigenPodManagerMock.sol index 8424b05fe..77db6102b 100644 --- a/src/test/mocks/EigenPodManagerMock.sol +++ b/src/test/mocks/EigenPodManagerMock.sol @@ -29,7 +29,11 @@ contract EigenPodManagerMock is Test, Pausable { podOwnerDepositShares[podOwner] = shares; } - function removeDepositShares(address podOwner, IStrategy strategy, uint256 shares) external { + function removeDepositShares( + address podOwner, + IStrategy, // strategy + uint256 shares + ) external { podOwnerDepositShares[podOwner] -= int256(shares); } diff --git a/src/test/mocks/MockAVSRegistrar.sol b/src/test/mocks/MockAVSRegistrar.sol new file mode 100644 index 000000000..1135d0743 --- /dev/null +++ b/src/test/mocks/MockAVSRegistrar.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +contract MockAVSRegistrar { + fallback () external {} +} \ No newline at end of file diff --git a/src/test/mocks/StrategyManagerMock.sol b/src/test/mocks/StrategyManagerMock.sol index 14f74a619..ac31153b2 100644 --- a/src/test/mocks/StrategyManagerMock.sol +++ b/src/test/mocks/StrategyManagerMock.sol @@ -82,11 +82,21 @@ contract StrategyManagerMock is Test { function removeStrategiesFromDepositWhitelist(IStrategy[] calldata /*strategiesToRemoveFromWhitelist*/) external pure {} - function withdrawSharesAsTokens(address staker, IStrategy strategy, address token, uint256 shares) external { + function withdrawSharesAsTokens( + address staker, + IStrategy strategy, + address, // token + uint256 shares + ) external { strategySharesWithdrawn[staker][strategy] += shares; } - function addShares(address staker, IStrategy strategy, IERC20 token, uint256 addedShares) external { + function addShares( + address staker, + IStrategy strategy, + IERC20, // token + uint256 addedShares + ) external { // Increase the staker's shares uint256 strategyIndex = _getStrategyIndex(staker, strategy); sharesToReturn[staker][strategyIndex] += addedShares; diff --git a/src/test/tree/AllocationManagerUnit.tree b/src/test/tree/AllocationManagerUnit.tree index ee366e368..f9144e717 100644 --- a/src/test/tree/AllocationManagerUnit.tree +++ b/src/test/tree/AllocationManagerUnit.tree @@ -12,7 +12,7 @@ │ │ └── it should set the new delay to the previous delay delay │ ├── given that a previous delay is set and has not passed │ │ └── it should should overwrite the previous pending delay with the new delay -│ └── it should set the pendingDelay, update the effectTimestamp, and emit an `AllocationDelaySetEvent` +│ └── it should set the pendingDelay, update the effectBlock, and emit an `AllocationDelaySetEvent` ├── when setAllocationDelay is called by the delegationManager │ ├── given that the caller is not the delegationManager │ │ └── it should revert @@ -25,7 +25,7 @@ │ │ └── it should set the new delay to the previous delay delay │ ├── given that a previous delay is set and has not passed │ │ └── it should should overwrite the previous pending delay with the new delay -│ └── it should set the pendingDelay, update the effectTimestamp, and emit an `AllocationDelaySetEvent` +│ └── it should set the pendingDelay, update the effectBlock, and emit an `AllocationDelaySetEvent` ├── when clearModificationQueue is called │ ├── given that the length of the strategies and numToClear are not equal │ │ └── it should revert @@ -37,7 +37,7 @@ │ ├── given that the latest effect timestamp has not been reached │ │ └── it should break the loop │ └── given that the latest effect timestamp has been reached -│ ├── it should update the magnitude info to the currentMagnitude, with a pendingDiff of 0 and effectTimestamp of 0 +│ ├── it should update the magnitude info to the currentMagnitude, with a pendingDiff of 0 and effectBlock of 0 │ ├── it should change the encumbered magnitude if the pendingDiff was less than 0 │ ├── it should emit an EncumberedmagnitudeUpdated event │ ├── it should remove the modification from the queue @@ -65,7 +65,7 @@ │ │ └── it should increase the encumberedMagnitude in memory by the pendingDiff │ ├── it should push to the modification queue │ ├── it should update the magnitude info, encumbered magnitude, emit an event for encumbered magnitude -│ └── it should emit an OperatorSetMagnitudeUpdated Event +│ └── it should emit an AllocationUpdated Event └── when slashOperator is called ├── given that the wads to slash is 0 │ └── it should revert @@ -79,8 +79,8 @@ ├── it should slash the current magnitude by wads to slash ├── given that there is a pending deallocation │ ├── it should slash the pending diff - │ └── it should emit an event for OperatorSetMagnitudeUpdated with the orginial deallocation's effect timestamp + │ └── it should emit an event for AllocationUpdated with the orginial deallocation's effect timestamp ├── it should update the magnitude info, encumbered magnitude, emit an event for encumbered magnitude ├── it should decrease the operator's max magnitude ├── it should decrease the operators shares in the delegation manager - └── It should emit an OperatorSetMagnitudeUpdated event \ No newline at end of file + └── It should emit an AllocationUpdated event \ No newline at end of file diff --git a/src/test/unit/AVSDirectoryUnit.t.sol b/src/test/unit/AVSDirectoryUnit.t.sol index 2cc4cda08..71c770886 100644 --- a/src/test/unit/AVSDirectoryUnit.t.sol +++ b/src/test/unit/AVSDirectoryUnit.t.sol @@ -1,1631 +1,182 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.27; -import "@openzeppelin/contracts/mocks/ERC1271WalletMock.sol"; - -import "src/contracts/core/DelegationManager.sol"; -import "src/contracts/core/AllocationManager.sol"; import "src/contracts/core/AVSDirectory.sol"; -import "src/contracts/interfaces/IAVSDirectory.sol"; - import "src/test/utils/EigenLayerUnitTestSetup.sol"; -import "src/test/mocks/EmptyContract.sol"; -/** - * @notice Unit testing of the AVSDirectory contract. An AVSs' service manager contract will - * call this to register an operator with the AVS. - * Contracts tested: AVSDirectory - * Contracts not mocked: DelegationManager - */ -contract AVSDirectoryUnitTests is EigenLayerUnitTestSetup, IAVSDirectoryEvents, IAVSDirectoryErrors { - uint256 internal constant MAX_PRIVATE_KEY = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140; +contract AVSDirectoryUnitTests is EigenLayerUnitTestSetup, IAVSDirectoryEvents, IAVSDirectoryErrors, ISignatureUtils { + uint8 constant PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS = 0; - // Contract under test AVSDirectory avsDirectory; - AVSDirectory avsDirectoryImplementation; - - // Contract dependencies - DelegationManager delegationManager; - DelegationManager delegationManagerImplementation; - AllocationManager allocationManager; - AllocationManager allocationManagerImplementation; - - // Delegation signer - uint256 delegationSignerPrivateKey = uint256(0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80); - uint256 stakerPrivateKey = uint256(123_456_789); - // empty string reused across many tests - string emptyStringForMetadataURI; - - // reused in various tests. in storage to help handle stack-too-deep errors - address defaultAVS = address(this); - - // deallocation delay in AVSD - uint32 DEALLOCATION_DELAY = 17.5 days; - // withdrawal delay in DelegationManager - uint32 MIN_WITHDRAWAL_DELAY = 17.5 days; - uint256 minWithdrawalDelayBlocks = 216_000; - IStrategy[] public initializeStrategiesToSetDelayBlocks; - uint256[] public initializeWithdrawalDelayBlocks; - - // Index for flag that pauses registering/deregistering for AVSs - uint8 internal constant PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS = 0; - // Index for flag that pauses operator register/deregister to operator sets when set. - uint8 internal constant PAUSED_OPERATOR_SET_REGISTRATION_AND_DEREGISTRATION = 1; + address defaultAVS; + address defaultOperator; + uint256 defaultOperatorPk; + SignatureWithSaltAndExpiry defaultOperatorSignature; function setUp() public virtual override { - // Setup EigenLayerUnitTestSetup.setUp(); - // Deploy DelegationManager implmentation and proxy - initializeStrategiesToSetDelayBlocks = new IStrategy[](0); - initializeWithdrawalDelayBlocks = new uint256[](0); - - // Create empty proxys for AVSDirectory, DelegationManager, and AllocationManager. - avsDirectory = AVSDirectory( - address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) - ); - delegationManager = DelegationManager( - address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) - ); - - - // Deploy implementations for AVSDirectory, DelegationManager, and AllocationManager. - avsDirectoryImplementation = new AVSDirectory(delegationManager, pauserRegistry, DEALLOCATION_DELAY); - - delegationManagerImplementation = new DelegationManager( - avsDirectory, - IStrategyManager(address(strategyManagerMock)), - IEigenPodManager(address(eigenPodManagerMock)), - IAllocationManager(address(allocationManagerMock)), - pauserRegistry, - MIN_WITHDRAWAL_DELAY - ); - - // Upgrade the proxies to the implementations - eigenLayerProxyAdmin.upgradeAndCall( - ITransparentUpgradeableProxy(payable(address(delegationManager))), - address(delegationManagerImplementation), - abi.encodeWithSelector( - DelegationManager.initialize.selector, - address(this), - 0, // 0 is initialPausedStatus - minWithdrawalDelayBlocks, - initializeStrategiesToSetDelayBlocks, - initializeWithdrawalDelayBlocks - ) - ); - - eigenLayerProxyAdmin.upgradeAndCall( - ITransparentUpgradeableProxy(payable(address(avsDirectory))), - address(avsDirectoryImplementation), - abi.encodeWithSelector( - AVSDirectory.initialize.selector, - address(this), - 0 // 0 is initialPausedStatus - ) - ); - - isExcludedFuzzAddress[address(avsDirectory)] = true; - isExcludedFuzzAddress[address(delegationManager)] = true; - } - - /** - * INTERNAL / HELPER FUNCTIONS - */ - - /** - * @notice internal function for calculating a signature from the operator corresponding to `operatorPk`, delegating them to - * the `operator`, and expiring at `expiry`. - */ - function _getOperatorAVSRegistrationSignature( - uint256 operatorPk, - address operator, - address avs, - bytes32 salt, - uint256 expiry - ) internal view returns (ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature) { - operatorSignature.expiry = expiry; - operatorSignature.salt = salt; - { - bytes32 digestHash = avsDirectory.calculateOperatorAVSRegistrationDigestHash(operator, avs, salt, expiry); - (uint8 v, bytes32 r, bytes32 s) = cheats.sign(operatorPk, digestHash); - operatorSignature.signature = abi.encodePacked(r, s, v); - } - return operatorSignature; - } + avsDirectory = _deployAVSD(address(delegationManagerMock), pauserRegistry); - function _registerOperatorWithBaseDetails(address operator) internal { - IDelegationManagerTypes.OperatorDetails memory operatorDetails = IDelegationManagerTypes.OperatorDetails({ - __deprecated_earningsReceiver: operator, - delegationApprover: address(0), - __deprecated_stakerOptOutWindowBlocks: 0 + defaultAVS = cheats.randomAddress(); + defaultOperatorPk = cheats.randomUint(1, MAX_PRIVATE_KEY); + defaultOperator = cheats.addr(defaultOperatorPk); + defaultOperatorSignature = _newOperatorRegistrationSignature({ + operatorPk: defaultOperatorPk, + avs: defaultAVS, + salt: bytes32(cheats.randomUint()), + expiry: type(uint256).max }); - _registerOperator(operator, operatorDetails, emptyStringForMetadataURI); - } - function _registerOperatorWithDelegationApprover(address operator) internal { - IDelegationManagerTypes.OperatorDetails memory operatorDetails = IDelegationManagerTypes.OperatorDetails({ - __deprecated_earningsReceiver: operator, - delegationApprover: cheats.addr(delegationSignerPrivateKey), - __deprecated_stakerOptOutWindowBlocks: 0 - }); - _registerOperator(operator, operatorDetails, emptyStringForMetadataURI); + delegationManagerMock.setIsOperator(defaultOperator, true); } - function _registerOperatorWith1271DelegationApprover(address operator) internal returns (ERC1271WalletMock) { - address delegationSigner = cheats.addr(delegationSignerPrivateKey); - /** - * deploy a ERC1271WalletMock contract with the `delegationSigner` address as the owner, - * so that we can create valid signatures from the `delegationSigner` for the contract to check when called - */ - ERC1271WalletMock wallet = new ERC1271WalletMock(delegationSigner); - - IDelegationManagerTypes.OperatorDetails memory operatorDetails = IDelegationManagerTypes.OperatorDetails({ - __deprecated_earningsReceiver: operator, - delegationApprover: address(wallet), - __deprecated_stakerOptOutWindowBlocks: 0 - }); - _registerOperator(operator, operatorDetails, emptyStringForMetadataURI); - - return wallet; - } - - function _registerOperator( - address operator, - IDelegationManagerTypes.OperatorDetails memory operatorDetails, - string memory metadataURI - ) internal filterFuzzedAddressInputs(operator) { - _filterOperatorDetails(operator, operatorDetails); - cheats.prank(operator); - delegationManager.registerAsOperator(operatorDetails, 1, metadataURI); - } - - function _filterOperatorDetails( - address operator, - IDelegationManagerTypes.OperatorDetails memory operatorDetails - ) internal view { - // filter out zero address since people can't delegate to the zero address and operators are delegated to themselves - cheats.assume(operator != address(0)); - } - - function _registerOperatorToOperatorSet( - uint256 operatorPk, - uint32 operatorSetId, - bytes32 salt, - uint256 expiry - ) internal virtual { - uint32[] memory oids = new uint32[](1); - oids[0] = operatorSetId; - - _registerOperatorToOperatorSets(operatorPk, oids, salt, expiry); - } - - function _registerOperatorToOperatorSets( - uint256 operatorPk, - uint32[] memory operatorSetIds, - bytes32 salt, - uint256 expiry - ) internal virtual { - expiry = bound(expiry, 1, type(uint256).max); - cheats.warp(0); - - address operator = cheats.addr(operatorPk); - (uint8 v, bytes32 r, bytes32 s) = cheats.sign( - operatorPk, - avsDirectory.calculateOperatorSetRegistrationDigestHash(address(this), operatorSetIds, salt, expiry) - ); - - // Set AVS as operator set avs - avsDirectory.becomeOperatorSetAVS(); - - _registerOperatorWithBaseDetails(operator); - - avsDirectory.registerOperatorToOperatorSets( - operator, - operatorSetIds, - ISignatureUtils.SignatureWithSaltAndExpiry(abi.encodePacked(r, s, v), salt, expiry) - ); - } - - function _createOperatorSet(uint32 operatorSetId) internal { - uint32[] memory oids = new uint32[](1); - oids[0] = operatorSetId; - avsDirectory.createOperatorSets(oids); - } - - function _createOperatorSets(uint32[] memory operatorSetIds) internal { - avsDirectory.createOperatorSets(operatorSetIds); - } -} - -contract AVSDirectoryUnitTests_initialize is AVSDirectoryUnitTests { - function testFuzz_Correctness( + function _deployAVSD( address delegationManager, - address owner, - address pauserRegistry, - uint256 initialPausedStatus - ) public virtual { - AVSDirectory dir = new AVSDirectory(IDelegationManager(delegationManager), IPauserRegistry(pauserRegistry), DEALLOCATION_DELAY); - - assertEq(address(dir.delegation()), delegationManager); - - cheats.expectRevert("Initializable: contract is already initialized"); - dir.initialize(owner, initialPausedStatus); - } -} - -contract AVSDirectoryUnitTests_domainSeparator is AVSDirectoryUnitTests { - function test_domainSeparator() public virtual { - // This is just to get coverage up. - avsDirectory.domainSeparator(); - cheats.chainId(0xC0FFEE); - avsDirectory.domainSeparator(); - } -} - -contract AVSDirectoryUnitTests_registerOperatorToOperatorSet is AVSDirectoryUnitTests { - function testFuzz_revert_SignatureIsExpired( - uint256 operatorPk, - uint32 operatorSetId, - bytes32 salt, - uint256 expiry - ) public virtual { - avsDirectory.becomeOperatorSetAVS(); - operatorPk = bound(operatorPk, 1, MAX_PRIVATE_KEY); - expiry = bound(expiry, 0, block.timestamp - 1); - - _createOperatorSet(operatorSetId); - - uint32[] memory oids = new uint32[](1); - oids[0] = operatorSetId; - - address operator = cheats.addr(operatorPk); - (uint8 v, bytes32 r, bytes32 s) = cheats.sign( - operatorPk, avsDirectory.calculateOperatorSetRegistrationDigestHash(address(this), oids, salt, expiry) - ); - - _registerOperatorWithBaseDetails(operator); - - cheats.expectRevert(ISignatureUtils.SignatureExpired.selector); - avsDirectory.registerOperatorToOperatorSets( - operator, oids, ISignatureUtils.SignatureWithSaltAndExpiry(abi.encodePacked(r, s, v), salt, expiry) - ); - } - - function testFuzz_revert_notOperatorSetAVS( - uint256 operatorPk, - uint32 operatorSetId, - bytes32 salt, - uint256 expiry - ) public virtual { - operatorPk = bound(operatorPk, 1, MAX_PRIVATE_KEY); - expiry = bound(expiry, 1, type(uint256).max); - - cheats.warp(0); - - _createOperatorSet(operatorSetId); - - uint32[] memory oids = new uint32[](1); - oids[0] = operatorSetId; - - address operator = cheats.addr(operatorPk); - (uint8 v, bytes32 r, bytes32 s) = cheats.sign( - operatorPk, avsDirectory.calculateOperatorSetRegistrationDigestHash(address(this), oids, salt, expiry) - ); - - _registerOperatorWithBaseDetails(operator); - - cheats.expectRevert(IAVSDirectoryErrors.InvalidAVS.selector); - avsDirectory.registerOperatorToOperatorSets( - operator, oids, ISignatureUtils.SignatureWithSaltAndExpiry(abi.encodePacked(r, s, v), salt, expiry) - ); - } - - function testFuzz_revert_OperatorRegistered( - uint256 operatorPk, - uint32 operatorSetId, - bytes32 salt, - uint256 expiry - ) public virtual { - avsDirectory.becomeOperatorSetAVS(); - operatorPk = bound(operatorPk, 1, MAX_PRIVATE_KEY); - expiry = bound(expiry, 1, type(uint256).max); - - cheats.assume(salt != keccak256("")); - - cheats.warp(0); - - _createOperatorSet(operatorSetId); - - uint32[] memory oids = new uint32[](1); - oids[0] = operatorSetId; - - address operator = cheats.addr(operatorPk); - - (uint8 v, bytes32 r, bytes32 s) = cheats.sign( - operatorPk, avsDirectory.calculateOperatorSetRegistrationDigestHash(address(this), oids, salt, expiry) - ); - - _registerOperatorWithBaseDetails(operator); - - avsDirectory.registerOperatorToOperatorSets( - operator, oids, ISignatureUtils.SignatureWithSaltAndExpiry(abi.encodePacked(r, s, v), salt, expiry) - ); - - (v, r, s) = cheats.sign( - operatorPk, - avsDirectory.calculateOperatorSetRegistrationDigestHash(address(this), oids, keccak256(""), expiry) - ); - - cheats.expectRevert(IAVSDirectoryErrors.InvalidOperator.selector); - avsDirectory.registerOperatorToOperatorSets( - operator, oids, ISignatureUtils.SignatureWithSaltAndExpiry(abi.encodePacked(r, s, v), keccak256(""), expiry) - ); - } - - function testFuzz_revert_OperatorNotRegistered( - address operator, - uint32 operatorSetId, - bytes32 salt, - uint256 expiry - ) public virtual { - avsDirectory.becomeOperatorSetAVS(); - cheats.assume(operator != address(0)); - expiry = bound(expiry, 1, type(uint256).max); - cheats.warp(0); - - _createOperatorSet(operatorSetId); - - uint32[] memory oids = new uint32[](1); - oids[0] = operatorSetId; - - cheats.expectRevert(IAVSDirectoryErrors.OperatorNotRegisteredToEigenLayer.selector); - avsDirectory.registerOperatorToOperatorSets( - operator, oids, ISignatureUtils.SignatureWithSaltAndExpiry(new bytes(0), salt, expiry) - ); - } - - function testFuzz_revert_SaltSpent( - uint256 operatorPk, - uint32 operatorSetId, - bytes32 salt, - uint256 expiry - ) public virtual { - avsDirectory.becomeOperatorSetAVS(); - operatorSetId = uint32(bound(operatorSetId, 1, type(uint32).max)); - operatorPk = bound(operatorPk, 1, MAX_PRIVATE_KEY); - expiry = bound(expiry, 1, type(uint256).max); - - cheats.warp(0); - - _createOperatorSet(operatorSetId); - - uint32[] memory oids = new uint32[](1); - oids[0] = operatorSetId; - - address operator = cheats.addr(operatorPk); - (uint8 v, bytes32 r, bytes32 s) = cheats.sign( - operatorPk, avsDirectory.calculateOperatorSetRegistrationDigestHash(address(this), oids, salt, expiry) - ); - - _registerOperatorWithBaseDetails(operator); - - avsDirectory.registerOperatorToOperatorSets( - operator, oids, ISignatureUtils.SignatureWithSaltAndExpiry(abi.encodePacked(r, s, v), salt, expiry) - ); - - cheats.expectRevert(IAVSDirectoryErrors.SaltSpent.selector); - avsDirectory.registerOperatorToOperatorSets( - operator, new uint32[](0), ISignatureUtils.SignatureWithSaltAndExpiry(new bytes(0), salt, expiry) - ); - } - - function testFuzz_revert_WrongAVS( - address badAvs, - uint256 operatorPk, - uint32 operatorSetId, - bytes32 salt, - uint256 expiry - ) public virtual { - cheats.assume(badAvs != address(this)); - - operatorSetId = uint32(bound(operatorSetId, 1, type(uint32).max)); - operatorPk = bound(operatorPk, 1, MAX_PRIVATE_KEY); - expiry = bound(expiry, 1, type(uint256).max); - - cheats.warp(0); - - _createOperatorSet(operatorSetId); - - uint32[] memory oids = new uint32[](1); - oids[0] = operatorSetId; - - address operator = cheats.addr(operatorPk); - - (uint8 v, bytes32 r, bytes32 s) = cheats.sign( - operatorPk, avsDirectory.calculateOperatorSetRegistrationDigestHash(address(this), oids, salt, expiry) - ); - - _registerOperatorWithBaseDetails(operator); - - cheats.startPrank(badAvs); - avsDirectory.becomeOperatorSetAVS(); - cheats.expectRevert(ISignatureUtils.InvalidSignature.selector); - avsDirectory.registerOperatorToOperatorSets( - operator, oids, ISignatureUtils.SignatureWithSaltAndExpiry(abi.encodePacked(r, s, v), salt, expiry) - ); - cheats.stopPrank(); - } - - function testFuzz_revert_invalidOperatorSet( - uint256 operatorPk, - uint32 operatorSetId, - bytes32 salt, - uint256 expiry - ) public virtual { - avsDirectory.becomeOperatorSetAVS(); - operatorPk = bound(operatorPk, 1, MAX_PRIVATE_KEY); - expiry = bound(expiry, 1, type(uint256).max); - - cheats.warp(0); - - uint32[] memory oids = new uint32[](1); - oids[0] = operatorSetId; - - address operator = cheats.addr(operatorPk); - (uint8 v, bytes32 r, bytes32 s) = cheats.sign( - operatorPk, avsDirectory.calculateOperatorSetRegistrationDigestHash(address(this), oids, salt, expiry) - ); - - _registerOperatorWithBaseDetails(operator); - - cheats.expectRevert(IAVSDirectoryErrors.InvalidOperatorSet.selector); - avsDirectory.registerOperatorToOperatorSets( - operator, oids, ISignatureUtils.SignatureWithSaltAndExpiry(abi.encodePacked(r, s, v), salt, expiry) - ); - } - - function testFuzz_MultipleCorrectness( - uint256 operatorPk, - uint256 totalSets, - bytes32 salt, - uint256 expiry - ) public virtual { - avsDirectory.becomeOperatorSetAVS(); - operatorPk = bound(operatorPk, 1, MAX_PRIVATE_KEY); - totalSets = bound(totalSets, 1, 64); - expiry = bound(expiry, 1, type(uint256).max); - - cheats.warp(0); - - uint32[] memory oids = new uint32[](totalSets); - for (uint256 i; i < oids.length; ++i) { - oids[i] = uint32(uint256(keccak256(abi.encodePacked(i))) % type(uint32).max); - _createOperatorSet(oids[i]); - } - - address operator = cheats.addr(operatorPk); - (uint8 v, bytes32 r, bytes32 s) = cheats.sign( - operatorPk, avsDirectory.calculateOperatorSetRegistrationDigestHash(address(this), oids, salt, expiry) - ); - - _registerOperatorWithBaseDetails(operator); - - for (uint256 i; i < oids.length; ++i) { - cheats.expectEmit(true, false, false, false, address(avsDirectory)); - emit OperatorAddedToOperatorSet(operator, OperatorSet(address(this), oids[i])); - } - - avsDirectory.registerOperatorToOperatorSets( - operator, oids, ISignatureUtils.SignatureWithSaltAndExpiry(abi.encodePacked(r, s, v), salt, expiry) - ); - - OperatorSet[] memory operatorSets = - avsDirectory.getOperatorSetsOfOperator(operator, 0, type(uint256).max); - - for (uint256 i; i < oids.length; ++i) { - assertTrue(avsDirectory.isMember(operator, OperatorSet(address(this), oids[i]))); - assertEq(avsDirectory.getNumOperatorsInOperatorSet(OperatorSet(address(this), oids[i])), 1); - assertEq(operatorSets[i].avs, address(this)); - assertEq(operatorSets[i].operatorSetId, oids[i]); - - assertTrue(avsDirectory.isOperatorSlashable(operator, OperatorSet(address(this), oids[i]))); - - (bool registered,) = avsDirectory.operatorSetStatus(address(this), operator, oids[i]); - assertTrue(registered, "Operator not registered to operator set"); - } - - for (uint256 i; i < oids.length; ++i) { - address[] memory operators = avsDirectory.getOperatorsInOperatorSet(OperatorSet(address(this), oids[i]), 0, type(uint256).max); - assertEq(operators.length, 1); - assertEq(operators[0], operator); - } - - assertEq(operatorSets.length, totalSets); - assertEq(avsDirectory.inTotalOperatorSets(operator), totalSets); - assertTrue(avsDirectory.operatorSaltIsSpent(operator, salt)); - } - - function testFuzz_Correctness( - uint256 operatorPk, - uint32 operatorSetId, - bytes32 salt, - uint256 expiry - ) public virtual { - avsDirectory.becomeOperatorSetAVS(); - operatorPk = bound(operatorPk, 1, MAX_PRIVATE_KEY); - expiry = bound(expiry, 1, type(uint256).max); - - cheats.warp(0); - - _createOperatorSet(operatorSetId); - - uint32[] memory oids = new uint32[](1); - oids[0] = operatorSetId; - - address operator = cheats.addr(operatorPk); - (uint8 v, bytes32 r, bytes32 s) = cheats.sign( - operatorPk, avsDirectory.calculateOperatorSetRegistrationDigestHash(address(this), oids, salt, expiry) - ); - - _registerOperatorWithBaseDetails(operator); - - cheats.expectEmit(true, false, false, false, address(avsDirectory)); - emit OperatorAddedToOperatorSet(operator, OperatorSet(address(this), operatorSetId)); - avsDirectory.registerOperatorToOperatorSets( - operator, oids, ISignatureUtils.SignatureWithSaltAndExpiry(abi.encodePacked(r, s, v), salt, expiry) + IPauserRegistry pauserRegistry + ) internal returns (AVSDirectory avsd) { + avsd = AVSDirectory( + address( + new TransparentUpgradeableProxy( + address(new AVSDirectory(IDelegationManager(delegationManager), pauserRegistry)), + address(eigenLayerProxyAdmin), + abi.encodeWithSelector( + AVSDirectory.initialize.selector, + address(this), + 0 // 0 is initialPausedStatus + ) + ) + ) ); - - assertTrue(avsDirectory.isMember(operator, OperatorSet(address(this), operatorSetId))); - - OperatorSet memory operatorSet = avsDirectory.operatorSetsMemberOfAtIndex(operator, 0); - - assertEq(operatorSet.avs, address(this)); - assertEq(operatorSet.operatorSetId, oids[0]); - - address operatorInSet = avsDirectory.operatorSetMemberAtIndex(OperatorSet(address(this), operatorSetId), 0); - assertEq(operator, operatorInSet); - - assertEq(avsDirectory.inTotalOperatorSets(operator), 1); - assertTrue(avsDirectory.operatorSaltIsSpent(operator, salt)); - assertEq(avsDirectory.getNumOperatorsInOperatorSet(OperatorSet(address(this), operatorSetId)), 1); - - assertTrue(avsDirectory.isOperatorSlashable(operator, OperatorSet(address(this), operatorSetId))); - - (bool registered,) = avsDirectory.operatorSetStatus(address(this), operator, operatorSetId); - assertTrue(registered, "Operator not registered to operator set"); + isExcludedFuzzAddress[address(avsd)] = true; } - function testFuzz_Correctness_MultipleSets( + function _newOperatorRegistrationSignature( uint256 operatorPk, - uint256 totalSets, + address avs, bytes32 salt, uint256 expiry - ) public virtual { - avsDirectory.becomeOperatorSetAVS(); - operatorPk = bound(operatorPk, 1, MAX_PRIVATE_KEY); - totalSets = bound(totalSets, 1, 64); - expiry = bound(expiry, 1, type(uint256).max); - - cheats.warp(0); - - uint32[] memory oids = new uint32[](totalSets); - - for (uint32 i = 1; i < totalSets + 1; ++i) { - _createOperatorSet(i); - oids[i - 1] = i; - } - - address operator = cheats.addr(operatorPk); + ) internal view returns (SignatureWithSaltAndExpiry memory) { (uint8 v, bytes32 r, bytes32 s) = cheats.sign( - operatorPk, avsDirectory.calculateOperatorSetRegistrationDigestHash(address(this), oids, salt, expiry) - ); - - _registerOperatorWithBaseDetails(operator); - - for (uint32 i = 1; i < totalSets + 1; ++i) { - cheats.expectEmit(true, false, false, false, address(avsDirectory)); - emit OperatorAddedToOperatorSet(operator, OperatorSet(address(this), i)); - } - - avsDirectory.registerOperatorToOperatorSets( - operator, oids, ISignatureUtils.SignatureWithSaltAndExpiry(abi.encodePacked(r, s, v), salt, expiry) + operatorPk, avsDirectory.calculateOperatorAVSRegistrationDigestHash(cheats.addr(operatorPk), avs, salt, expiry) ); - - OperatorSet[] memory operatorSets = - avsDirectory.getOperatorSetsOfOperator(operator, 0, type(uint256).max); - - for (uint32 i = 1; i < totalSets + 1; ++i) { - assertTrue(avsDirectory.isMember(operator, OperatorSet(address(this), i))); - assertEq(avsDirectory.getNumOperatorsInOperatorSet(OperatorSet(address(this), i)), 1); - - assertEq(operatorSets[i - 1].avs, address(this)); - assertEq(operatorSets[i - 1].operatorSetId, i); - } - - for(uint32 i = 1; i < totalSets + 1; ++i) { - address[] memory operators = avsDirectory.getOperatorsInOperatorSet(OperatorSet(address(this), i), 0, type(uint256).max); - assertEq(operators.length, 1); - assertEq(operators[0], operator); - assertTrue(avsDirectory.isOperatorSlashable(operator, OperatorSet(address(this), i))); - (bool registered,) = avsDirectory.operatorSetStatus(address(this), operator, i); - assertTrue(registered, "Operator not registered to operator set"); - } - - assertEq(avsDirectory.inTotalOperatorSets(operator), totalSets); - assertTrue(avsDirectory.operatorSaltIsSpent(operator, salt)); - - assertEq(operatorSets.length, totalSets); - } -} - -contract AVSDirectoryUnitTests_forceDeregisterFromOperatorSets is AVSDirectoryUnitTests { - function testFuzz_revert_OperatorNotInOperatorSet(uint256 operatorPk, uint32 operatorSetId) public virtual { - operatorPk = bound(operatorPk, 1, MAX_PRIVATE_KEY); - - address operator = cheats.addr(operatorPk); - uint32[] memory oids = new uint32[](1); - oids[0] = operatorSetId; - - _createOperatorSet(operatorSetId); - - ISignatureUtils.SignatureWithSaltAndExpiry memory emptySig; - - cheats.prank(operator); - cheats.expectRevert(IAVSDirectoryErrors.InvalidOperator.selector); - - avsDirectory.forceDeregisterFromOperatorSets(operator, address(this), oids, emptySig); + return SignatureWithSaltAndExpiry({signature: abi.encodePacked(r, s, v), salt: salt, expiry: expiry}); } - function testFuzz_revert_operatorNotCaller(uint256 operatorPk, uint32 operatorSetId) public { - operatorPk = bound(operatorPk, 1, MAX_PRIVATE_KEY); - - address operator = cheats.addr(operatorPk); - uint32[] memory oids = new uint32[](1); - oids[0] = operatorSetId; - - _createOperatorSet(operatorSetId); - - ISignatureUtils.SignatureWithSaltAndExpiry memory emptySig; - - cheats.expectRevert(IAVSDirectoryErrors.InvalidOperator.selector); - avsDirectory.forceDeregisterFromOperatorSets(operator, address(this), oids, emptySig); - } - - function testFuzz_forceDeregisterFromOperatorSets( - uint256 operatorPk, - uint32 operatorSetId, - uint8 operatorSetsToAdd, - bytes32 salt - ) public { - operatorPk = bound(operatorPk, 1, MAX_PRIVATE_KEY); - operatorSetsToAdd = uint8(bound(operatorSetsToAdd, 1, 64)); - address operator = cheats.addr(operatorPk); - - // Create operator sets - operatorSetId = uint32(bound(operatorSetId, 1, type(uint32).max - uint32(operatorSetsToAdd))); - uint32[] memory oids = new uint32[](operatorSetsToAdd); - for (uint32 i = 0; i < operatorSetsToAdd; i++) { - oids[i] = operatorSetId + i; - _createOperatorSet(oids[i]); - } - - // Register operator to operator sets - _registerOperatorToOperatorSets(operatorPk, oids, salt, type(uint256).max); - - assertEq(avsDirectory.getNumOperatorsInOperatorSet(OperatorSet(address(this), operatorSetId)), 1); - - // Deregister operator from operator sets - ISignatureUtils.SignatureWithSaltAndExpiry memory emptySig; - cheats.prank(operator); - for (uint256 i = 0; i < oids.length; i++) { - cheats.expectEmit(true, true, true, true, address(avsDirectory)); - emit OperatorRemovedFromOperatorSet(operator, OperatorSet(address(this), oids[i])); - } - avsDirectory.forceDeregisterFromOperatorSets(operator, address(this), oids, emptySig); - - for (uint32 i = 0; i < operatorSetsToAdd; i++) { - assertFalse( - avsDirectory.isMember(operator, OperatorSet(address(this), oids[i])), - "operator still in operator set" - ); - - address[] memory operators = avsDirectory.getOperatorsInOperatorSet(OperatorSet(address(this), oids[i]), 0, type(uint256).max); - assertEq(operators.length, 0); - - (bool registered, uint32 lastDeregisteredTimestamp) = avsDirectory.operatorSetStatus(address(this), operator, oids[i]); - assertFalse(registered, "Operator still registered to operator set"); - assertEq(lastDeregisteredTimestamp, block.timestamp); - } - - OperatorSet[] memory operatorSets = - avsDirectory.getOperatorSetsOfOperator(operator, 0, type(uint256).max); - - assertEq(operatorSets.length, 0); - assertEq(avsDirectory.inTotalOperatorSets(operator), 0); - assertEq(avsDirectory.getNumOperatorsInOperatorSet(OperatorSet(address(this), operatorSetId)), 0); + /// ----------------------------------------------------------------------- + /// initialize() + /// ----------------------------------------------------------------------- - - // Check slashable status - for (uint32 i = 0; i < operatorSetsToAdd; i++) { - assertTrue(avsDirectory.isOperatorSlashable(operator, OperatorSet(address(this), oids[i]))); - } - cheats.warp(block.timestamp + DEALLOCATION_DELAY); - for (uint32 i = 0; i < operatorSetsToAdd; i++) { - assertFalse(avsDirectory.isOperatorSlashable(operator, OperatorSet(address(this), oids[i]))); - } - } - - function testFuzz_revert_sigExpired(uint256 operatorPk, uint32 operatorSetId, bytes32 salt) public { - operatorPk = bound(operatorPk, 1, MAX_PRIVATE_KEY); - - address operator = cheats.addr(operatorPk); - uint32[] memory oids = new uint32[](1); - oids[0] = operatorSetId; - - _createOperatorSet(operatorSetId); - - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSig = - _createForceDeregSignature(operatorPk, address(this), oids, 0, salt); - - cheats.warp(type(uint256).max); - cheats.expectRevert(ISignatureUtils.SignatureExpired.selector); - avsDirectory.forceDeregisterFromOperatorSets(operator, address(this), oids, operatorSig); - } - - function testFuzz_revert_saltAlreadySpent(uint256 operatorPk, uint32 operatorSetId, bytes32 salt) public { - operatorPk = bound(operatorPk, 1, MAX_PRIVATE_KEY); - - address operator = cheats.addr(operatorPk); - uint32[] memory oids = new uint32[](1); - oids[0] = operatorSetId; - - // Register operator to operator sets - _createOperatorSet(operatorSetId); - _registerOperatorToOperatorSets(operatorPk, oids, salt, type(uint256).max); - - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSig = - _createForceDeregSignature(operatorPk, address(this), oids, type(uint256).max, salt); - - cheats.expectRevert(IAVSDirectoryErrors.SaltSpent.selector); - avsDirectory.forceDeregisterFromOperatorSets(operator, address(this), oids, operatorSig); - } - - function testFuzz_forceDeregisterFromOperatorSets_onBehalf( - uint256 operatorPk, - uint32 operatorSetId, - uint8 operatorSetsToAdd, - bytes32 salt1, - bytes32 salt2 - ) public { - cheats.assume(salt1 != salt2); - - operatorPk = bound(operatorPk, 1, MAX_PRIVATE_KEY); - operatorSetsToAdd = uint8(bound(operatorSetsToAdd, 1, 64)); - address operator = cheats.addr(operatorPk); - - // Create operator sets - operatorSetId = uint32(bound(operatorSetId, 1, type(uint32).max - uint32(operatorSetsToAdd))); - uint32[] memory oids = new uint32[](operatorSetsToAdd); - for (uint32 i = 0; i < oids.length; i++) { - oids[i] = operatorSetId + i; - _createOperatorSet(oids[i]); - } - - // Register operator to operator sets - _registerOperatorToOperatorSets(operatorPk, oids, salt1, type(uint256).max); - - // Deregister operator from operator sets - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSig = - _createForceDeregSignature(operatorPk, address(this), oids, type(uint256).max, salt2); - - for (uint256 i = 0; i < oids.length; i++) { - cheats.expectEmit(true, true, true, true, address(avsDirectory)); - emit OperatorRemovedFromOperatorSet(operator, OperatorSet(address(this), oids[i])); - } - - avsDirectory.forceDeregisterFromOperatorSets(operator, address(this), oids, operatorSig); - - for (uint32 i = 0; i < operatorSetsToAdd; i++) { - assertFalse(avsDirectory.isMember(operator, OperatorSet(address(this), oids[i]))); - - (bool registered, uint32 lastDeregisteredTimestamp) = avsDirectory.operatorSetStatus(address(this), operator, oids[i]); - assertFalse(registered, "Operator still registered to operator set"); - assertEq(lastDeregisteredTimestamp, block.timestamp); - } - - // Check slashable status - for (uint32 i = 0; i < operatorSetsToAdd; i++) { - assertTrue(avsDirectory.isOperatorSlashable(operator, OperatorSet(address(this), oids[i]))); - } - cheats.warp(block.timestamp + DEALLOCATION_DELAY); - for (uint32 i = 0; i < operatorSetsToAdd; i++) { - assertFalse(avsDirectory.isOperatorSlashable(operator, OperatorSet(address(this), oids[i]))); - } - } - - function _createForceDeregSignature( - uint256 operatorPk, - address avs, - uint32[] memory oids, - uint256 expiry, - bytes32 salt - ) internal view returns (ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature) { - operatorSignature.expiry = expiry; - operatorSignature.salt = salt; - { - bytes32 digestHash = avsDirectory.calculateOperatorSetForceDeregistrationTypehash(avs, oids, salt, expiry); - (uint8 v, bytes32 r, bytes32 s) = cheats.sign(operatorPk, digestHash); - operatorSignature.signature = abi.encodePacked(r, s, v); - } - return operatorSignature; - } -} - -contract AVSDirectoryUnitTests_deregisterOperatorFromOperatorSets is AVSDirectoryUnitTests { - function testFuzz_revert_OperatorNotInOperatorSet(uint256 operatorPk, uint32 operatorSetId) public virtual { - operatorPk = bound(operatorPk, 1, MAX_PRIVATE_KEY); - - _createOperatorSet(operatorSetId); - - address operator = cheats.addr(operatorPk); - uint32[] memory oids = new uint32[](1); - oids[0] = operatorSetId; - - cheats.expectRevert(IAVSDirectoryErrors.InvalidOperator.selector); - avsDirectory.deregisterOperatorFromOperatorSets(operator, oids); - } - - function testFuzz_Correctness( - uint256 operatorPk, - uint32 operatorSetId, - bytes32 salt, - uint256 expiry - ) public virtual { - operatorPk = bound(operatorPk, 1, MAX_PRIVATE_KEY); - - _createOperatorSet(operatorSetId); - - _registerOperatorToOperatorSet(operatorPk, operatorSetId, salt, expiry); - - address operator = cheats.addr(operatorPk); - uint32[] memory oids = new uint32[](1); - oids[0] = operatorSetId; - - // sanity - assertEq(avsDirectory.inTotalOperatorSets(operator), 1); - assertEq(avsDirectory.getNumOperatorsInOperatorSet(OperatorSet(address(this), operatorSetId)), 1); - - cheats.expectEmit(true, false, false, false, address(avsDirectory)); - emit OperatorRemovedFromOperatorSet(operator, OperatorSet(address(this), operatorSetId)); - - avsDirectory.deregisterOperatorFromOperatorSets(operator, oids); - - // out of bounds array access - vm.expectRevert(); - avsDirectory.operatorSetsMemberOfAtIndex(operator, 0); - - assertEq(avsDirectory.inTotalOperatorSets(operator), 0); - assertEq(avsDirectory.getNumOperatorsInOperatorSet(OperatorSet(address(this), operatorSetId)), 0); - assertEq(avsDirectory.isMember(operator, OperatorSet(address(this), operatorSetId)), false); - (bool registered, uint32 lastDeregisteredTimestamp) = avsDirectory.operatorSetStatus(address(this), operator, operatorSetId); - assertFalse(registered, "Operator still registered to operator set"); - assertEq(lastDeregisteredTimestamp, block.timestamp); - - // Check slashable status - assertTrue(avsDirectory.isOperatorSlashable(operator, OperatorSet(address(this), operatorSetId))); - cheats.warp(block.timestamp + DEALLOCATION_DELAY); - assertFalse(avsDirectory.isOperatorSlashable(operator, OperatorSet(address(this), operatorSetId))); - } - - function testFuzz_Correctness_MultipleSets( - uint256 operatorPk, - uint256 totalSets, - bytes32 salt, - uint256 expiry - ) public virtual { - operatorPk = bound(operatorPk, 1, MAX_PRIVATE_KEY); - totalSets = bound(totalSets, 1, 64); - - uint32[] memory oids = new uint32[](totalSets); - - for (uint32 i = 1; i < totalSets + 1; ++i) { - _createOperatorSet(i); - oids[i - 1] = i; - } - - _registerOperatorToOperatorSets(operatorPk, oids, salt, expiry); - - for (uint32 i = 1; i < totalSets + 1; ++i) { - assertEq(avsDirectory.getNumOperatorsInOperatorSet(OperatorSet(address(this), i)), 1); - } - - address operator = cheats.addr(operatorPk); - - // sanity - assertEq(avsDirectory.inTotalOperatorSets(operator), totalSets); - - for (uint32 i = 1; i < totalSets + 1; ++i) { - cheats.expectEmit(true, false, false, false, address(avsDirectory)); - emit OperatorRemovedFromOperatorSet(operator, OperatorSet(address(this), i)); - } - - avsDirectory.deregisterOperatorFromOperatorSets(operator, oids); - - for (uint32 i = 1; i < totalSets + 1; ++i) { - assertEq(avsDirectory.getNumOperatorsInOperatorSet(OperatorSet(address(this), i)), 0); - assertEq(avsDirectory.isMember(operator, OperatorSet(address(this), i)), false); - - (bool registered, uint32 lastDeregisteredTimestamp) = avsDirectory.operatorSetStatus(address(this), operator, i); - assertFalse(registered, "Operator still registered to operator set"); - assertEq(lastDeregisteredTimestamp, block.timestamp); - } - - OperatorSet[] memory operatorSets = - avsDirectory.getOperatorSetsOfOperator(operator, 0, type(uint256).max); - - assertEq(operatorSets.length, 0); - assertEq(avsDirectory.inTotalOperatorSets(operator), 0); - - // Check slashable status - for (uint32 i = 1; i < totalSets + 1; ++i) { - assertTrue(avsDirectory.isOperatorSlashable(operator, OperatorSet(address(this), i))); - } - cheats.warp(block.timestamp + DEALLOCATION_DELAY); - for (uint32 i = 1; i < totalSets + 1; ++i) { - assertFalse(avsDirectory.isOperatorSlashable(operator, OperatorSet(address(this), i))); - } - } -} - -contract AVSDirectoryUnitTests_createOperatorSet is AVSDirectoryUnitTests { - function testFuzz_createOperatorSet(uint256 totalSets) public { - totalSets = bound(totalSets, 1, 64); - - uint32[] memory oids = new uint32[](totalSets); - - for (uint32 i; i < totalSets; ++i) { - oids[i] = i + 1; - cheats.expectEmit(true, true, true, true, address(avsDirectory)); - emit OperatorSetCreated(OperatorSet({avs: address(this), operatorSetId: i + 1})); - } - - avsDirectory.createOperatorSets(oids); - - for (uint32 i = 1; i < totalSets + 1; ++i) { - assertTrue(avsDirectory.isOperatorSet(address(this), i)); - } - } - - function test_revert_operatorSetExists() public { - _createOperatorSet(1); - cheats.expectRevert(IAVSDirectoryErrors.InvalidOperatorSet.selector); - _createOperatorSet(1); + function test_initialize_Correctness() public { + assertEq(address(avsDirectory.delegation()), address(delegationManagerMock)); + cheats.expectRevert("Initializable: contract is already initialized"); + avsDirectory.initialize(address(this), 0); } -} -contract AVSDirectoryUnitTests_becomeOperatorSetAVS is AVSDirectoryUnitTests { - function test_becomeOperatorSetAVS() public { + /// ----------------------------------------------------------------------- + /// updateAVSMetadataURI() + /// ----------------------------------------------------------------------- + function test_updateAVSMetadataURI_Correctness() public { cheats.expectEmit(true, true, true, true, address(avsDirectory)); - emit AVSMigratedToOperatorSets(address(this)); - - avsDirectory.becomeOperatorSetAVS(); - - assertTrue(avsDirectory.isOperatorSetAVS(address(this))); - } - - function test_revert_alreadyOperatorSetAVS() public { - avsDirectory.becomeOperatorSetAVS(); - cheats.expectRevert(IAVSDirectoryErrors.InvalidAVS.selector); - avsDirectory.becomeOperatorSetAVS(); - } -} - -contract AVSDirectoryUnitTests_AddStrategiesToOperatorSet is AVSDirectoryUnitTests { - function test_revert_invalidOperatorSet() public { - cheats.expectRevert(IAVSDirectoryErrors.InvalidOperatorSet.selector); - avsDirectory.addStrategiesToOperatorSet(0, new IStrategy[](0)); - } - - function test_revert_strategyAlreadyInOperatorSet() public { - IStrategy[] memory strategies = new IStrategy[](1); - strategies[0] = IStrategy(address(1)); - _createOperatorSet(1); - avsDirectory.addStrategiesToOperatorSet(1, strategies); - - cheats.expectRevert(IAVSDirectoryErrors.StrategyAlreadyInOperatorSet.selector); - avsDirectory.addStrategiesToOperatorSet(1, strategies); - } - - function test_fuzz_addStrategiesToOperatorSet(uint8 numStrategiesToAdd) public { - // Create strategies - IStrategy[] memory strategies = new IStrategy[](numStrategiesToAdd); - for (uint256 i; i < numStrategiesToAdd; ++i) { - strategies[i] = IStrategy(address(uint160(i))); - } - _createOperatorSet(1); - - for (uint256 i; i < strategies.length; ++i) { - cheats.expectEmit(true, true, true, true, address(avsDirectory)); - emit StrategyAddedToOperatorSet(OperatorSet(address(this), 1), strategies[i]); - } - avsDirectory.addStrategiesToOperatorSet(1, strategies); - - // Check storage - IStrategy[] memory operatorSetStrategies = avsDirectory.getStrategiesInOperatorSet(OperatorSet(address(this), 1)); - assertEq(operatorSetStrategies.length, strategies.length); - for (uint256 i; i < strategies.length; ++i) { - assertEq(address(operatorSetStrategies[i]), address(strategies[i])); - } - } -} - -contract AVSDirectoryUnitTests_RemoveStrategiesFromOperatorSet is AVSDirectoryUnitTests { - function test_revert_invalidOperatorSet() public { - cheats.expectRevert(IAVSDirectoryErrors.InvalidOperatorSet.selector); - avsDirectory.removeStrategiesFromOperatorSet(0, new IStrategy[](0)); + emit AVSMetadataURIUpdated(address(this), "test"); + avsDirectory.updateAVSMetadataURI("test"); } - function test_revert_strategyNotInOperatorSet() public { - IStrategy[] memory strategies = new IStrategy[](1); - strategies[0] = IStrategy(address(1)); - _createOperatorSet(1); + /// ----------------------------------------------------------------------- + /// cancelSalt() + /// ----------------------------------------------------------------------- - cheats.expectRevert(IAVSDirectoryErrors.StrategyNotInOperatorSet.selector); - avsDirectory.removeStrategiesFromOperatorSet(1, strategies); - } - - function test_fuzz_removeAllStrategies(uint8 numStrategiesToAdd) public { - // Create strategies - IStrategy[] memory strategies = new IStrategy[](numStrategiesToAdd); - for (uint256 i; i < numStrategiesToAdd; ++i) { - strategies[i] = IStrategy(address(uint160(i))); - } - - // Add strategies - _createOperatorSet(1); - avsDirectory.addStrategiesToOperatorSet(1, strategies); - - // Remove strategies - for (uint256 i; i < strategies.length; ++i) { - cheats.expectEmit(true, true, true, true, address(avsDirectory)); - emit StrategyRemovedFromOperatorSet(OperatorSet(address(this), 1), strategies[i]); - } - avsDirectory.removeStrategiesFromOperatorSet(1, strategies); - - // Check storage - IStrategy[] memory operatorSetStrategies = avsDirectory.getStrategiesInOperatorSet(OperatorSet(address(this), 1)); - assertEq(operatorSetStrategies.length, 0); - } - - - function test_fuzz_removeSetOfStrategies(uint8 numStrategiesToAdd, uint8 numStrategiesToRemove) public { - cheats.assume(numStrategiesToRemove < numStrategiesToAdd); - - // Create strategies - IStrategy[] memory strategies = new IStrategy[](numStrategiesToAdd); - for (uint256 i; i < numStrategiesToAdd; ++i) { - strategies[i] = IStrategy(address(uint160(i))); - } - - // Add strategies - _createOperatorSet(1); - avsDirectory.addStrategiesToOperatorSet(1, strategies); - - // Generate strategies to remove - IStrategy[] memory strategiesToRemove = new IStrategy[](numStrategiesToRemove); - for (uint256 i; i < numStrategiesToRemove; ++i) { - strategiesToRemove[i] = strategies[i]; - } - - // Remove strategies - for (uint256 i; i < strategiesToRemove.length; ++i) { - cheats.expectEmit(true, true, true, true, address(avsDirectory)); - emit StrategyRemovedFromOperatorSet(OperatorSet(address(this), 1), strategiesToRemove[i]); - } - avsDirectory.removeStrategiesFromOperatorSet(1, strategiesToRemove); - - // Check storage - IStrategy[] memory operatorSetStrategies = avsDirectory.getStrategiesInOperatorSet(OperatorSet(address(this), 1)); - assertEq(operatorSetStrategies.length, numStrategiesToAdd - numStrategiesToRemove); + function test_cancelSalt_Correctness() public { + bytes32 salt = bytes32(cheats.randomUint()); + cheats.prank(defaultAVS); + avsDirectory.cancelSalt(salt); + assertTrue(avsDirectory.operatorSaltIsSpent(defaultAVS, salt)); } -} -contract AVSDirectoryUnitTests_migrateOperatorsToOperatorSets is AVSDirectoryUnitTests { - address[] operators = new address[](1); - uint32[][] operatorSetIds = new uint32[][](1); + /// ----------------------------------------------------------------------- + /// registerOperatorToAVS() + /// ----------------------------------------------------------------------- - function test_revert_paused() public { + function test_registerOperatorToAVS_Paused() public { cheats.prank(pauser); - avsDirectory.pause(2 ** PAUSED_OPERATOR_SET_REGISTRATION_AND_DEREGISTRATION); - - operators = new address[](1); - operatorSetIds = new uint32[][](1); - + avsDirectory.pause(2 ** PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS); cheats.expectRevert(IPausable.CurrentlyPaused.selector); - cheats.prank(defaultAVS); - avsDirectory.migrateOperatorsToOperatorSets(operators, operatorSetIds); - } - - function test_revert_notOperatorSetAVS() public { - cheats.expectRevert(IAVSDirectoryErrors.InvalidAVS.selector); - cheats.prank(defaultAVS); - avsDirectory.migrateOperatorsToOperatorSets(operators, operatorSetIds); + avsDirectory.registerOperatorToAVS(defaultOperator, defaultOperatorSignature); } - function test_revert_operatorNotM2Registered() public { - address operator = cheats.addr(delegationSignerPrivateKey); - operators = new address[](1); - operators[0] = operator; - - avsDirectory.becomeOperatorSetAVS(); - cheats.expectRevert( - IAVSDirectoryErrors.InvalidOperator.selector - ); - avsDirectory.migrateOperatorsToOperatorSets(operators, operatorSetIds); + function test_registerOperatorToAVS_SignatureExpired() public { + defaultOperatorSignature.expiry = block.timestamp - 1; + cheats.expectRevert(SignatureExpired.selector); + avsDirectory.registerOperatorToAVS(defaultOperator, defaultOperatorSignature); } - function test_revert_operatorAlreadyMigrated(bytes32 salt) public { - // Register Operator to M2 - address operator = cheats.addr(delegationSignerPrivateKey); - _registerOperatorLegacyM2(delegationSignerPrivateKey, salt); - - // Format calldata - operators = new address[](1); - operators[0] = operator; - operatorSetIds = new uint32[][](1); - operatorSetIds[0] = new uint32[](1); - operatorSetIds[0][0] = 1; - - // Setup Operator Sets - _createOperatorSet(1); - avsDirectory.becomeOperatorSetAVS(); - - // Migrate Operator - avsDirectory.migrateOperatorsToOperatorSets(operators, operatorSetIds); - - // Revert when trying to migrate operator again - cheats.expectRevert(IAVSDirectoryErrors.InvalidOperator.selector); - avsDirectory.migrateOperatorsToOperatorSets(operators, operatorSetIds); - } - - function testFuzz_revert_invalidOperatorSet(bytes32 salt) public { - // Register Operator to M2 - address operator = cheats.addr(delegationSignerPrivateKey); - _registerOperatorLegacyM2(delegationSignerPrivateKey, salt); - - // Format calldata - operators = new address[](1); - operators[0] = operator; - operatorSetIds = new uint32[][](1); - operatorSetIds[0] = new uint32[](1); - operatorSetIds[0][0] = 1; - - // Become operator set AVS - avsDirectory.becomeOperatorSetAVS(); - - // Revert - cheats.expectRevert(IAVSDirectoryErrors.InvalidOperatorSet.selector); - avsDirectory.migrateOperatorsToOperatorSets(operators, operatorSetIds); + function test_registerOperatorToAVS_OperatorAlreadyRegistered() public { + cheats.startPrank(defaultAVS); + avsDirectory.registerOperatorToAVS(defaultOperator, defaultOperatorSignature); + cheats.expectRevert(OperatorAlreadyRegisteredToAVS.selector); + avsDirectory.registerOperatorToAVS(defaultOperator, defaultOperatorSignature); + cheats.stopPrank(); } - function testFuzz_revert_operatorAlreadyRegisteredFromMigration(bytes32 salt) public { - // Register Operator to M2 - address operator = cheats.addr(delegationSignerPrivateKey); - _registerOperatorLegacyM2(delegationSignerPrivateKey, salt); - - // Format calldata - operators = new address[](1); - operators[0] = operator; - operatorSetIds = new uint32[][](1); - operatorSetIds[0] = new uint32[](2); - operatorSetIds[0][0] = 1; - operatorSetIds[0][1] = 1; - - // Become operator set AVS - _createOperatorSet(1); - avsDirectory.becomeOperatorSetAVS(); - - // Revert - cheats.expectRevert(IAVSDirectoryErrors.InvalidOperator.selector); - avsDirectory.migrateOperatorsToOperatorSets(operators, operatorSetIds); + function test_registerOperatorToAVS_SaltSpent() public { + cheats.prank(defaultOperator); + avsDirectory.cancelSalt(defaultOperatorSignature.salt); + cheats.prank(defaultAVS); + cheats.expectRevert(SaltSpent.selector); + avsDirectory.registerOperatorToAVS(defaultOperator, defaultOperatorSignature); } - function testFuzz_revert_operatorAlreadyRegisteredFromNormalReg(bytes32 salt1, bytes32 salt2) public { - // Register Operator to M2 - address operator = cheats.addr(delegationSignerPrivateKey); - _registerOperatorLegacyM2(delegationSignerPrivateKey, salt1); - - // Format calldata - operators = new address[](1); - operators[0] = operator; - operatorSetIds = new uint32[][](1); - operatorSetIds[0] = new uint32[](1); - operatorSetIds[0][0] = 1; - - // Register Operator To Operator Set - cannot use helper method since it re-registers operator in DM - avsDirectory.becomeOperatorSetAVS(); - _createOperatorSet(1); - uint256 expiry = type(uint256).max; - (uint8 v, bytes32 r, bytes32 s) = cheats.sign( - delegationSignerPrivateKey, - avsDirectory.calculateOperatorSetRegistrationDigestHash(address(this), operatorSetIds[0], salt2, expiry) - ); - avsDirectory.registerOperatorToOperatorSets( - operator, - operatorSetIds[0], - ISignatureUtils.SignatureWithSaltAndExpiry(abi.encodePacked(r, s, v), salt2, expiry) - ); - - // Revert - cheats.expectRevert(IAVSDirectoryErrors.InvalidOperator.selector); - avsDirectory.migrateOperatorsToOperatorSets(operators, operatorSetIds); + function test_registerOperatorToAVS_OperatorNotRegisteredToEigenLayer() public { + delegationManagerMock.setIsOperator(defaultOperator, false); + cheats.expectRevert(OperatorNotRegisteredToEigenLayer.selector); + avsDirectory.registerOperatorToAVS(defaultOperator, defaultOperatorSignature); } - function testFuzz_Correctness(bytes32 salt) public { - // Register Operator to M2 - address operator = cheats.addr(delegationSignerPrivateKey); - _registerOperatorLegacyM2(delegationSignerPrivateKey, salt); - - // Format calldata - operators = new address[](1); - operators[0] = operator; - operatorSetIds = new uint32[][](1); - operatorSetIds[0] = new uint32[](1); - operatorSetIds[0][0] = 1; - - // Become operator set AVS - avsDirectory.becomeOperatorSetAVS(); - _createOperatorSet(1); + function test_registerOperatorToAVS_Correctness() public { + cheats.expectEmit(true, true, true, false, address(avsDirectory)); + emit OperatorAVSRegistrationStatusUpdated(defaultOperator, defaultAVS, OperatorAVSRegistrationStatus.REGISTERED); - // Expect Emits - cheats.expectEmit(true, true, true, true, address(avsDirectory)); - emit OperatorAddedToOperatorSet(operator, OperatorSet(address(this), 1)); - cheats.expectEmit(true, true, true, true, address(avsDirectory)); - emit OperatorAVSRegistrationStatusUpdated( - operator, address(this), IAVSDirectoryTypes.OperatorAVSRegistrationStatus.UNREGISTERED - ); - cheats.expectEmit(true, true, true, true, address(avsDirectory)); - emit OperatorMigratedToOperatorSets(operator, address(this), operatorSetIds[0]); - - // Migrate - avsDirectory.migrateOperatorsToOperatorSets(operators, operatorSetIds); + cheats.prank(defaultAVS); + avsDirectory.registerOperatorToAVS(defaultOperator, defaultOperatorSignature); - // Checks - assertTrue(avsDirectory.isMember(operator, OperatorSet(address(this), 1))); assertTrue( - avsDirectory.avsOperatorStatus(address(this), operator) - == IAVSDirectoryTypes.OperatorAVSRegistrationStatus.UNREGISTERED + avsDirectory.avsOperatorStatus(defaultAVS, defaultOperator) == OperatorAVSRegistrationStatus.REGISTERED ); - assertEq(avsDirectory.getNumOperatorsInOperatorSet(OperatorSet(address(this), 1)), 1); - } - - function testFuzz_correctness_multiple( - uint256 privateKey, - uint8 numOperators, - bytes32 salt, - uint8 numOids - ) public { - numOperators = uint8(bound(numOperators, 1, 64)); - numOids = uint8(bound(numOids, 1, 32)); - - // Create Operator Set IDs - uint32[] memory oids = new uint32[](numOids); - for (uint32 i = 0; i < numOids; i++) { - oids[i] = i; - } - - // Create Operators, Initailize Calldata, Register Operators - privateKey = bound(privateKey, 1, MAX_PRIVATE_KEY - numOperators); - operators = new address[](numOperators); - operatorSetIds = new uint32[][](numOperators); - for (uint256 i = 0; i < numOperators; i++) { - _registerOperatorLegacyM2(privateKey + i, salt); - operators[i] = cheats.addr(privateKey + i); - operatorSetIds[i] = oids; - } - - // Become operator set AVS - avsDirectory.becomeOperatorSetAVS(); - _createOperatorSets(oids); - - // Expect Emits - for (uint256 i = 0; i < numOperators; i++) { - for (uint256 j = 0; j < oids.length; j++) { - cheats.expectEmit(true, true, true, true, address(avsDirectory)); - emit OperatorAddedToOperatorSet(operators[i], OperatorSet(address(this), oids[j])); - } - cheats.expectEmit(true, true, true, true, address(avsDirectory)); - emit OperatorAVSRegistrationStatusUpdated( - operators[i], address(this), IAVSDirectoryTypes.OperatorAVSRegistrationStatus.UNREGISTERED - ); - cheats.expectEmit(true, true, true, true, address(avsDirectory)); - emit OperatorMigratedToOperatorSets(operators[i], address(this), operatorSetIds[i]); - } - - // Migrate - avsDirectory.migrateOperatorsToOperatorSets(operators, operatorSetIds); - - // Checks - for (uint256 i = 0; i < numOperators; i++) { - for (uint256 j = 0; j < oids.length; j++) { - assertTrue(avsDirectory.isMember(operators[i], OperatorSet(address(this), oids[j]))); - } - assertTrue( - avsDirectory.avsOperatorStatus(address(this), operators[i]) - == IAVSDirectoryTypes.OperatorAVSRegistrationStatus.UNREGISTERED - ); - - OperatorSet[] memory opSets = avsDirectory.getOperatorSetsOfOperator(operators[i], 0, type(uint256).max); - assertEq(oids.length, opSets.length); - } - - for(uint256 i = 0; i < oids.length; i++) { - address[] memory operatorsInSet = avsDirectory.getOperatorsInOperatorSet(OperatorSet(address(this), oids[i]), 0, type(uint256).max); - assertEq(operatorsInSet.length, operators.length); - } + assertTrue(avsDirectory.operatorSaltIsSpent(defaultOperator, defaultOperatorSignature.salt)); } - function _registerOperatorLegacyM2(uint256 privateKey, bytes32 salt) internal { - address operator = cheats.addr(privateKey); - _registerOperatorWithBaseDetails(operator); - - uint256 expiry = type(uint256).max; - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature = - _getOperatorAVSRegistrationSignature(privateKey, operator, address(this), salt, expiry); + /// ----------------------------------------------------------------------- + /// deregisterOperatorFromAVS() + /// ----------------------------------------------------------------------- - avsDirectory.registerOperatorToAVS(operator, operatorSignature); - } -} - -contract AVSDirectoryUnitTests_legacyOperatorAVSRegistration is AVSDirectoryUnitTests { - function test_revert_whenRegisterDeregisterToAVSPaused() public { - // set the pausing flag + function test_deregisterOperatorFromAVS_Paused() public { cheats.prank(pauser); avsDirectory.pause(2 ** PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS); - cheats.expectRevert(IPausable.CurrentlyPaused.selector); - avsDirectory.registerOperatorToAVS( - address(0), ISignatureUtils.SignatureWithSaltAndExpiry(abi.encodePacked(""), 0, 0) - ); - - cheats.expectRevert(IPausable.CurrentlyPaused.selector); - avsDirectory.deregisterOperatorFromAVS(address(0)); + avsDirectory.deregisterOperatorFromAVS(defaultOperator); } - function test_revert_deregisterOperatorFromAVS_operatorNotRegistered() public { - cheats.expectRevert(IAVSDirectoryErrors.OperatorNotRegisteredToAVS.selector); - avsDirectory.deregisterOperatorFromAVS(address(0)); + function test_deregisterOperatorFromAVS_OperatorNotRegisteredToAVS() public { + cheats.expectRevert(OperatorNotRegisteredToAVS.selector); + avsDirectory.deregisterOperatorFromAVS(defaultOperator); } - function test_revert_deregisterOperatorFromAVS_whenAVSISOperatorSetAVS() public { - // Register operator - bytes32 salt = bytes32(0); - address operator = cheats.addr(delegationSignerPrivateKey); - assertFalse(delegationManager.isOperator(operator), "bad test setup"); - _registerOperatorWithBaseDetails(operator); - - uint256 expiry = type(uint256).max; - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature = - _getOperatorAVSRegistrationSignature(delegationSignerPrivateKey, operator, defaultAVS, salt, expiry); - + function test_deregisterOperatorFromAVS_Correctness() public { cheats.startPrank(defaultAVS); - avsDirectory.registerOperatorToAVS(operator, operatorSignature); - - // Become operator set AVS - avsDirectory.becomeOperatorSetAVS(); - - // Deregister operator - cheats.expectRevert(IAVSDirectoryErrors.InvalidAVS.selector); - avsDirectory.deregisterOperatorFromAVS(operator); - } - - function testFuzz_deregisterOperatorFromAVS(bytes32 salt) public { - address operator = cheats.addr(delegationSignerPrivateKey); - assertFalse(delegationManager.isOperator(operator), "bad test setup"); - _registerOperatorWithBaseDetails(operator); - - uint256 expiry = type(uint256).max; - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature = - _getOperatorAVSRegistrationSignature(delegationSignerPrivateKey, operator, defaultAVS, salt, expiry); - - cheats.prank(defaultAVS); - avsDirectory.registerOperatorToAVS(operator, operatorSignature); - - cheats.expectEmit(true, true, true, true, address(avsDirectory)); - emit OperatorAVSRegistrationStatusUpdated( - operator, defaultAVS, IAVSDirectoryTypes.OperatorAVSRegistrationStatus.UNREGISTERED - ); - - cheats.prank(defaultAVS); - avsDirectory.deregisterOperatorFromAVS(operator); - - assertTrue( - avsDirectory.avsOperatorStatus(defaultAVS, operator) - == IAVSDirectoryTypes.OperatorAVSRegistrationStatus.UNREGISTERED - ); - } - - // @notice Tests that an avs who calls `updateAVSMetadataURI` will correctly see an `AVSMetadataURIUpdated` event emitted with their input - function testFuzz_UpdateAVSMetadataURI(string memory metadataURI) public { - // call `updateAVSMetadataURI` and check for event - cheats.expectEmit(true, true, true, true, address(avsDirectory)); - cheats.prank(defaultAVS); - emit AVSMetadataURIUpdated(defaultAVS, metadataURI); - avsDirectory.updateAVSMetadataURI(metadataURI); - } - - function testFuzz_revert_whenAVSIsOperatorSetAVS(bytes32 salt) public { - // set the AVS to be an operator set AVS - cheats.prank(defaultAVS); - avsDirectory.becomeOperatorSetAVS(); - - // Register Operator to EigenLayer - address operator = cheats.addr(delegationSignerPrivateKey); - assertFalse(delegationManager.isOperator(operator), "bad test setup"); - _registerOperatorWithBaseDetails(operator); - - uint256 expiry = type(uint256).max; - - cheats.prank(defaultAVS); - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature = - _getOperatorAVSRegistrationSignature(delegationSignerPrivateKey, operator, defaultAVS, salt, expiry); - cheats.expectRevert(IAVSDirectoryErrors.InvalidAVS.selector); - avsDirectory.registerOperatorToAVS(operator, operatorSignature); - } + avsDirectory.registerOperatorToAVS(defaultOperator, defaultOperatorSignature); - // @notice Verifies an operator registers successfull to avs and see an `OperatorAVSRegistrationStatusUpdated` event emitted - function testFuzz_registerOperatorToAVS(bytes32 salt) public { - address operator = cheats.addr(delegationSignerPrivateKey); - assertFalse(delegationManager.isOperator(operator), "bad test setup"); - _registerOperatorWithBaseDetails(operator); - - cheats.expectEmit(true, true, true, true, address(avsDirectory)); + cheats.expectEmit(true, true, true, false, address(avsDirectory)); emit OperatorAVSRegistrationStatusUpdated( - operator, defaultAVS, IAVSDirectoryTypes.OperatorAVSRegistrationStatus.REGISTERED + defaultOperator, defaultAVS, OperatorAVSRegistrationStatus.UNREGISTERED ); - uint256 expiry = type(uint256).max; - - cheats.prank(defaultAVS); - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature = - _getOperatorAVSRegistrationSignature(delegationSignerPrivateKey, operator, defaultAVS, salt, expiry); - - avsDirectory.registerOperatorToAVS(operator, operatorSignature); - - assertTrue( - avsDirectory.avsOperatorStatus(defaultAVS, operator) - == IAVSDirectoryTypes.OperatorAVSRegistrationStatus.REGISTERED - ); - } - - // @notice Verifies an operator registers successfull to avs and see an `OperatorAVSRegistrationStatusUpdated` event emitted - function testFuzz_revert_whenOperatorNotRegisteredToEigenLayerYet(bytes32 salt) public { - address operator = cheats.addr(delegationSignerPrivateKey); - assertFalse(delegationManager.isOperator(operator), "bad test setup"); - - cheats.prank(defaultAVS); - uint256 expiry = type(uint256).max; - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature = - _getOperatorAVSRegistrationSignature(delegationSignerPrivateKey, operator, defaultAVS, salt, expiry); - - cheats.expectRevert(IAVSDirectoryErrors.OperatorNotRegisteredToEigenLayer.selector); - avsDirectory.registerOperatorToAVS(operator, operatorSignature); - } - - // @notice Verifies an operator registers fails when the signature is not from the operator - function testFuzz_revert_whenSignatureAddressIsNotOperator(bytes32 salt) public { - address operator = cheats.addr(delegationSignerPrivateKey); - assertFalse(delegationManager.isOperator(operator), "bad test setup"); - _registerOperatorWithBaseDetails(operator); - - uint256 expiry = type(uint256).max; - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature = - _getOperatorAVSRegistrationSignature(delegationSignerPrivateKey, operator, defaultAVS, salt, expiry); - - cheats.expectRevert(ISignatureUtils.InvalidSignature.selector); - cheats.prank(operator); - avsDirectory.registerOperatorToAVS(operator, operatorSignature); - } - - // @notice Verifies an operator registers fails when the signature expiry already expires - function testFuzz_revert_whenExpiryHasExpired(bytes32 salt) - public - { - address operator = cheats.addr(delegationSignerPrivateKey); - assertFalse(delegationManager.isOperator(operator), "bad test setup"); - _registerOperatorWithBaseDetails(operator); - - uint256 expiry = block.timestamp - 1; - - cheats.prank(defaultAVS); - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature = - _getOperatorAVSRegistrationSignature(delegationSignerPrivateKey, operator, defaultAVS, salt, expiry); - - cheats.expectRevert(ISignatureUtils.SignatureExpired.selector); - avsDirectory.registerOperatorToAVS(operator, operatorSignature); - } - - // @notice Verifies an operator registers fails when it's already registered to the avs - function testFuzz_revert_whenOperatorAlreadyRegisteredToAVS(bytes32 salt) public { - address operator = cheats.addr(delegationSignerPrivateKey); - assertFalse(delegationManager.isOperator(operator), "bad test setup"); - _registerOperatorWithBaseDetails(operator); - - uint256 expiry = type(uint256).max; - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature = - _getOperatorAVSRegistrationSignature(delegationSignerPrivateKey, operator, defaultAVS, salt, expiry); - - cheats.startPrank(defaultAVS); - avsDirectory.registerOperatorToAVS(operator, operatorSignature); - - cheats.expectRevert(IAVSDirectoryErrors.OperatorAlreadyRegisteredToAVS.selector); - avsDirectory.registerOperatorToAVS(operator, operatorSignature); + avsDirectory.deregisterOperatorFromAVS(defaultOperator); cheats.stopPrank(); - } - - /// @notice Checks that cancelSalt updates the operatorSaltIsSpent mapping correctly - function testFuzz_cancelSalt(bytes32 salt) public { - address operator = cheats.addr(delegationSignerPrivateKey); - assertFalse(delegationManager.isOperator(operator), "bad test setup"); - _registerOperatorWithBaseDetails(operator); - - assertFalse(avsDirectory.operatorSaltIsSpent(operator, salt), "bad test setup"); - assertFalse(avsDirectory.operatorSaltIsSpent(defaultAVS, salt), "bad test setup"); - - cheats.prank(operator); - avsDirectory.cancelSalt(salt); - assertTrue(avsDirectory.operatorSaltIsSpent(operator, salt), "salt was not successfully cancelled"); - assertFalse( - avsDirectory.operatorSaltIsSpent(defaultAVS, salt), "salt should only be cancelled for the operator" + assertTrue( + avsDirectory.avsOperatorStatus(defaultAVS, defaultOperator) == OperatorAVSRegistrationStatus.UNREGISTERED ); - - bytes32 newSalt; - unchecked { - newSalt = bytes32(uint256(salt) + 1); - } - - assertFalse(salt == newSalt, "bad test setup"); - - cheats.prank(operator); - avsDirectory.cancelSalt(newSalt); - - assertTrue(avsDirectory.operatorSaltIsSpent(operator, salt), "original salt should still be cancelled"); - assertTrue(avsDirectory.operatorSaltIsSpent(operator, newSalt), "new salt should be cancelled"); } - - /// @notice Verifies that registration fails when the salt has been cancelled via cancelSalt - function testFuzz_revert_whenRegisteringWithCancelledSalt(bytes32 salt) public { - address operator = cheats.addr(delegationSignerPrivateKey); - assertFalse(delegationManager.isOperator(operator), "bad test setup"); - _registerOperatorWithBaseDetails(operator); - - uint256 expiry = type(uint256).max; - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature = - _getOperatorAVSRegistrationSignature(delegationSignerPrivateKey, operator, defaultAVS, salt, expiry); - - cheats.prank(operator); - avsDirectory.cancelSalt(salt); - - cheats.expectRevert(IAVSDirectoryErrors.SaltSpent.selector); - cheats.prank(defaultAVS); - avsDirectory.registerOperatorToAVS(operator, operatorSignature); - } - - /// @notice Verifies that an operator cannot cancel the same salt twice - function testFuzz_revert_whenCancellingSaltUsedToRegister(bytes32 salt) public { - address operator = cheats.addr(delegationSignerPrivateKey); - assertFalse(delegationManager.isOperator(operator), "bad test setup"); - _registerOperatorWithBaseDetails(operator); - - uint256 expiry = type(uint256).max; - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature = - _getOperatorAVSRegistrationSignature(delegationSignerPrivateKey, operator, defaultAVS, salt, expiry); - - cheats.prank(defaultAVS); - avsDirectory.registerOperatorToAVS(operator, operatorSignature); - } -} \ No newline at end of file +} diff --git a/src/test/unit/AllocationManagerUnit.t.sol b/src/test/unit/AllocationManagerUnit.t.sol index 024d917c0..250722073 100644 --- a/src/test/unit/AllocationManagerUnit.t.sol +++ b/src/test/unit/AllocationManagerUnit.t.sol @@ -3,22 +3,35 @@ pragma solidity ^0.8.27; import "src/contracts/core/AllocationManager.sol"; import "src/test/utils/EigenLayerUnitTestSetup.sol"; +import "src/test/mocks/MockAVSRegistrar.sol"; + +// TODO: Add **unique** tests for events. contract AllocationManagerUnitTests is EigenLayerUnitTestSetup, IAllocationManagerErrors, IAllocationManagerEvents { + using SingleItemArrayLib for *; + uint8 internal constant PAUSED_MODIFY_ALLOCATIONS = 0; uint8 internal constant PAUSED_OPERATOR_SLASHING = 1; - uint32 constant DEALLOCATION_DELAY = 17.5 days; - uint32 constant ALLOCATION_CONFIGURATION_DELAY = 21 days; + uint8 internal constant PAUSED_OPERATOR_SET_REGISTRATION_AND_DEREGISTRATION = 2; + + uint32 constant ASSUMED_BLOCK_TIME = 12 seconds; + uint32 constant DEALLOCATION_DELAY = 14 days / ASSUMED_BLOCK_TIME; + uint32 constant ALLOCATION_CONFIGURATION_DELAY = 21 days / ASSUMED_BLOCK_TIME; + uint32 constant DEFAULT_OPERATOR_ALLOCATION_DELAY = 1 days / ASSUMED_BLOCK_TIME; AllocationManager allocationManager; ERC20PresetFixedSupply tokenMock; StrategyBase strategyMock; - StrategyBase strategyMock2; + OperatorSet defaultOperatorSet; + IStrategy[] defaultStrategies; + RegisterParams defaultRegisterParams; + DeregisterParams defaultDeregisterParams; address defaultOperator = address(this); address defaultAVS = address(0xFEDBAD); - uint32 constant DEFAULT_OPERATOR_ALLOCATION_DELAY = 1 days; - + + /// @dev Keeps track of an AVS's created operator sets so we can create more as needed + mapping(address avs => uint32) _opSetCount; /// ----------------------------------------------------------------------- /// Setup @@ -45,21 +58,31 @@ contract AllocationManagerUnitTests is EigenLayerUnitTestSetup, IAllocationManag ) ); - strategyMock2 = StrategyBase( - address( - new TransparentUpgradeableProxy( - address(new StrategyBase(IStrategyManager(address(strategyManagerMock)), pauserRegistry)), - address(eigenLayerProxyAdmin), - abi.encodeWithSelector(StrategyBase.initialize.selector, tokenMock) - ) - ) - ); + defaultStrategies = strategyMock.toArray(); - // Set the allocation delay & warp to when it can be set + /// Set up defaultAVS, defaultOperatorSet, and defaultOperator + + // Set the allocation delay & roll to when it can be set delegationManagerMock.setIsOperator(defaultOperator, true); cheats.prank(defaultOperator); allocationManager.setAllocationDelay(DEFAULT_OPERATOR_ALLOCATION_DELAY); - cheats.warp(block.timestamp + ALLOCATION_CONFIGURATION_DELAY); + cheats.roll(block.number + ALLOCATION_CONFIGURATION_DELAY); + + // Give the default AVS a contract to receive calls + cheats.etch(defaultAVS, type(MockAVSRegistrar).runtimeCode); + // Create a default operator set for the default AVS + defaultOperatorSet = _newOperatorSet_SingleMockStrategy(defaultAVS); + // Create a default register params + defaultRegisterParams = _newRegisterParams_SingleSet(defaultAVS, defaultOperatorSet.id); + + defaultDeregisterParams = DeregisterParams({ + operator: defaultOperator, + avs: defaultAVS, + operatorSetIds: defaultOperatorSet.id.toArrayU32() + }); + + // Register the default operator with the default operator set + _registerForOperatorSet(defaultOperator, defaultOperatorSet); } /// ----------------------------------------------------------------------- @@ -77,7 +100,6 @@ contract AllocationManagerUnitTests is EigenLayerUnitTestSetup, IAllocationManag address( new AllocationManager( IDelegationManager(address(delegationManagerMock)), - IAVSDirectory(address(avsDirectoryMock)), _pauserRegistry, DEALLOCATION_DELAY, ALLOCATION_CONFIGURATION_DELAY @@ -93,365 +115,296 @@ contract AllocationManagerUnitTests is EigenLayerUnitTestSetup, IAllocationManag } /// ----------------------------------------------------------------------- - /// Generate calldata for a magnitude allocation + /// Create operator sets /// ----------------------------------------------------------------------- - /** - * @notice Generated magnitue allocation calldata for a given `avsToSet`, `strategy`, and `operatorSetId` - */ - function _generateMagnitudeAllocationCalldata_opSetAndStrategy( - address avsToSet, - IStrategy strategy, - uint32 operatorSetId, - uint64 magnitudeToSet, - uint64 expectedMaxMagnitude - ) internal returns (IAllocationManagerTypes.MagnitudeAllocation[] memory) { - OperatorSet[] memory operatorSets = new OperatorSet[](1); - operatorSets[0] = OperatorSet({avs: avsToSet, operatorSetId: operatorSetId}); - - // Set operatorSet to being valid - avsDirectoryMock.setIsOperatorSetBatch(operatorSets, true); - - uint64[] memory magnitudes = new uint64[](1); - magnitudes[0] = magnitudeToSet; - - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - new IAllocationManagerTypes.MagnitudeAllocation[](1); - allocations[0] = IAllocationManagerTypes.MagnitudeAllocation({ - strategy: strategy, - expectedMaxMagnitude: expectedMaxMagnitude, - operatorSets: operatorSets, - magnitudes: magnitudes - }); + function _newOperatorSet(address avs, IStrategy[] memory strategies) internal returns (OperatorSet memory) { + uint32 nextId = _opSetCount[avs]; + _opSetCount[avs] = nextId + 1; - return allocations; - } + OperatorSet memory operatorSet = OperatorSet(avs, nextId); - /** - * @notice Generates magnitudeAllocation calldata for a given operatorSet and avs for `strategyMock` - */ - function _generateMagnitudeAllocationCalldataForOpSet( - address avsToSet, - uint32 operatorSetId, - uint64 magnitudeToSet, - uint64 expectedMaxMagnitude - ) internal returns (IAllocationManagerTypes.MagnitudeAllocation[] memory) { - return _generateMagnitudeAllocationCalldata_opSetAndStrategy( - avsToSet, - strategyMock, - operatorSetId, - magnitudeToSet, - expectedMaxMagnitude + cheats.prank(avs); + allocationManager.createOperatorSets( + CreateSetParams({operatorSetId: operatorSet.id, strategies: strategies}).toArray() ); - } - /** - * @notice Generates magnitudeAllocation calldata for the `strategyMock` on operatorSet 1 with a provided magnitude. - */ - function _generateMagnitudeAllocationCalldata( - address avsToSet, - uint64 magnitudeToSet, - uint64 expectedMaxMagnitude - ) internal returns (IAllocationManagerTypes.MagnitudeAllocation[] memory) { - return _generateMagnitudeAllocationCalldataForOpSet(avsToSet, 1, magnitudeToSet, expectedMaxMagnitude); + return operatorSet; } - /// ----------------------------------------------------------------------- - /// Generate random slashing parameters - /// ----------------------------------------------------------------------- + function _newOperatorSet_SingleMockStrategy( + address avs + ) internal returns (OperatorSet memory) { + uint32 nextId = _opSetCount[avs]; + _opSetCount[avs] = nextId + 1; - /** - * @notice Gets random slashing parameters. Not useful unless the operatorSetID is set. See overloaded method - */ - function _randomSlashingParams( - address operator, - uint256 r, - uint256 salt - ) internal view returns (IAllocationManagerTypes.SlashingParams memory) { - r = uint256(keccak256(abi.encodePacked(r, salt))); + OperatorSet memory operatorSet = OperatorSet(avs, nextId); - IStrategy[] memory strategies = new IStrategy[](1); - strategies[0] = strategyMock; + cheats.prank(avs); + allocationManager.createOperatorSets( + CreateSetParams({operatorSetId: operatorSet.id, strategies: strategyMock.toArray()}).toArray() + ); - return IAllocationManagerTypes.SlashingParams({ - operator: operator, - operatorSetId: uint32(r), - strategies: strategies, - wadToSlash: bound(r, 1, 1e18), - description: "test" - }); + return operatorSet; } - function _randomSlashingParams( - address operator, - uint32 operatorSetId, - uint256 r, - uint256 salt - ) internal view returns (IAllocationManagerTypes.SlashingParams memory) { - r = uint256(keccak256(abi.encodePacked(r, salt))); + /// @dev Create a single operator set with multiple configured strategies + function _newOperatorSet_MultipleStrategies( + address avs, + uint256 numStrategies + ) internal returns (OperatorSet memory) { + uint32 nextId = _opSetCount[avs]; + _opSetCount[avs] = nextId + 1; - IStrategy[] memory strategies = new IStrategy[](1); - strategies[0] = strategyMock; + OperatorSet memory operatorSet = OperatorSet(avs, nextId); + IStrategy[] memory strategies = new IStrategy[](numStrategies); - return IAllocationManagerTypes.SlashingParams({ - operator: operator, - operatorSetId: operatorSetId, - strategies: strategies, - wadToSlash: bound(r, 1, 1e18), - description: "test" - }); - } + for (uint256 i = 0; i < numStrategies; i++) { + strategies[i] = IStrategy(random().Address()); + } - /// ----------------------------------------------------------------------- - /// Generated a random magnitude allocation for a single strategy and operatorSet - /// ----------------------------------------------------------------------- + cheats.prank(avs); + allocationManager.createOperatorSets( + CreateSetParams({operatorSetId: operatorSet.id, strategies: strategies}).toArray() + ); - function _completeRandomAllocation_singleStrat_singleOpset( - address operator, + return operatorSet; + } + + function _newOperatorSets_SingleUniqueStrategy( address avs, - uint256 r, - uint256 salt - ) internal returns (IAllocationManagerTypes.MagnitudeAllocation[] memory) { - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - _queueRandomAllocation_singleStrat_singleOpSet(operator, avs, r, salt); + uint256 numOpSets + ) internal returns (OperatorSet[] memory) { + OperatorSet[] memory operatorSets = new OperatorSet[](numOpSets); + CreateSetParams[] memory params = new CreateSetParams[](numOpSets); + + for (uint256 i = 0; i < numOpSets; i++) { + uint32 nextId = _opSetCount[avs]; + _opSetCount[avs] = nextId + 1; + + operatorSets[i] = OperatorSet(avs, nextId); + params[i].operatorSetId = nextId; + params[i].strategies = IStrategy(random().Address()).toArray(); + } - // Warp to allocation complete timestamp - cheats.warp(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY); + cheats.prank(avs); + allocationManager.createOperatorSets(params); - return allocations; + return operatorSets; } - function _queueRandomAllocation_singleStrat_singleOpSet( - address operator, + function _newOperatorSets_SingleMockStrategy( address avs, - uint256 r, - uint256 salt - ) internal returns (IAllocationManagerTypes.MagnitudeAllocation[] memory) { - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - _randomMagnitudeAllocation_singleStrat_singleOpSet(avs, r, salt); - cheats.prank(operator); - allocationManager.modifyAllocations(allocations); + uint8 numOpSets + ) internal returns (OperatorSet[] memory) { + OperatorSet[] memory operatorSets = new OperatorSet[](numOpSets); + CreateSetParams[] memory params = new CreateSetParams[](numOpSets); + + for (uint256 i = 0; i < numOpSets; i++) { + uint32 nextId = _opSetCount[avs]; + _opSetCount[avs] = nextId + 1; - return allocations; + operatorSets[i] = OperatorSet(avs, nextId); + params[i].operatorSetId = nextId; + params[i].strategies = strategyMock.toArray(); + } + + cheats.prank(avs); + allocationManager.createOperatorSets(params); + + return operatorSets; } - /** - * @notice Queued a random allocation for the given `operator` - * - Does NOT warp past the effect timestamp - */ - function _queueRandomAllocation_singleStrat_singleOpSet( - address operator, - uint256 r, - uint256 salt - ) internal returns (IAllocationManagerTypes.MagnitudeAllocation[] memory) { - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - _randomMagnitudeAllocation_singleStrat_singleOpSet(r, salt); - cheats.prank(operator); - allocationManager.modifyAllocations(allocations); + function _registerForOperatorSet(address operator, OperatorSet memory operatorSet) internal { + cheats.startPrank(operator); + + allocationManager.registerForOperatorSets( + RegisterParams({avs: operatorSet.avs, operatorSetIds: operatorSet.id.toArrayU32(), data: ""}) + ); - return allocations; + cheats.stopPrank(); } - /** - * @notice Create a random magnitude allocation - * Randomized Parameters: avs, opSet, magnitude - * Non-random Parameters: strategy, expectedMaxMagnitude - * In addition - * - Registers the operatorSet with the avsDirectory - */ - function _randomMagnitudeAllocation_singleStrat_singleOpSet( - uint256 r, - uint256 salt - ) internal returns (IAllocationManagerTypes.MagnitudeAllocation[] memory) { - r = uint256(keccak256(abi.encodePacked(r, salt))); - address avs = _randomAddr(r, 0); - return _randomMagnitudeAllocation_singleStrat_singleOpSet(avs, r, salt); + function _registerForOperatorSets(address operator, OperatorSet[] memory operatorSets) internal { + cheats.startPrank(operator); + + for (uint256 i = 0; i < operatorSets.length; i++) { + RegisterParams memory params = + RegisterParams({avs: operatorSets[i].avs, operatorSetIds: operatorSets[i].id.toArrayU32(), data: ""}); + + allocationManager.registerForOperatorSets(params); + } + + cheats.stopPrank(); } - /** - * @notice Create a random magnitude allocation - * Randomized Parameters: opSet, magnitude - * Non-random Parameters: strategy, expectedMaxMagnitude, avs - * In addition - * - Registers the operatorSet with the avsDirectory - */ - function _randomMagnitudeAllocation_singleStrat_singleOpSet( + function _newRegisterParams_SingleSet( + address avs, + uint32 operatorSetId, + bytes memory data + ) internal pure returns (RegisterParams memory) { + return RegisterParams({avs: avs, operatorSetIds: operatorSetId.toArrayU32(), data: data}); + } + + function _newRegisterParams_SingleSet( address avs, - uint256 r, - uint256 salt - ) internal returns (IAllocationManagerTypes.MagnitudeAllocation[] memory) { - r = uint256(keccak256(abi.encodePacked(r, salt))); - - // Mock a random operator set. - OperatorSet[] memory operatorSets = new OperatorSet[](1); - operatorSets[0] = OperatorSet({avs: avs, operatorSetId: uint32(r)}); - - // Set operatorSet to being valid - avsDirectoryMock.setIsOperatorSetBatch(operatorSets, true); - - uint64[] memory magnitudes = new uint64[](1); - magnitudes[0] = uint64(bound(r, 1, 1e18)); - - // Mock a random magnitude allocation. - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - new IAllocationManagerTypes.MagnitudeAllocation[](1); - allocations[0] = IAllocationManagerTypes.MagnitudeAllocation({ - strategy: strategyMock, - expectedMaxMagnitude: 1e18, // magnitude starts at 100% - operatorSets: operatorSets, - magnitudes: magnitudes + uint32 operatorSetId + ) internal pure returns (RegisterParams memory) { + return RegisterParams({avs: avs, operatorSetIds: operatorSetId.toArrayU32(), data: ""}); + } + + /// ----------------------------------------------------------------------- + /// Random value generation + /// ----------------------------------------------------------------------- + + function _randSlashingParams(address operator, uint32 operatorSetId) internal returns (SlashingParams memory) { + return SlashingParams({ + operator: operator, + operatorSetId: operatorSetId, + wadToSlash: random().Uint256(1, WAD), + description: "test" }); - return allocations; } /// ----------------------------------------------------------------------- - /// Generate a random allocation for a single strategy and multiple operatorSets + /// Allocate/deallocate params /// ----------------------------------------------------------------------- - function _randomMagnitudeAllocation_singleStrat_multipleOpSets( - uint256 r, - uint256 salt, - uint8 numOpSets - ) internal returns (IAllocationManagerTypes.MagnitudeAllocation[] memory) { - r = uint256(keccak256(abi.encodePacked(r, salt))); + /// @dev Create allocate params, allocating `magnitude` to each strategy in the set + function _newAllocateParams( + OperatorSet memory operatorSet, + uint64 magnitude + ) internal view returns (AllocateParams[] memory) { + IStrategy[] memory strategies = allocationManager.getStrategiesInOperatorSet(operatorSet); + uint64[] memory newMagnitudes = new uint64[](strategies.length); - // Create multiple operatorSets - OperatorSet[] memory operatorSets = new OperatorSet[](numOpSets); - for (uint8 i = 0; i < numOpSets; i++) { - operatorSets[i] = OperatorSet({avs: _randomAddr(r, i), operatorSetId: uint32(r + i)}); + for (uint256 i = 0; i < strategies.length; i++) { + newMagnitudes[i] = magnitude; + } + + return + AllocateParams({operatorSet: operatorSet, strategies: strategies, newMagnitudes: newMagnitudes}).toArray(); + } + + /// @dev Create allocate params for multiple operator sets + function _newAllocateParams( + OperatorSet[] memory operatorSets, + uint64 magnitude + ) internal view returns (AllocateParams[] memory) { + AllocateParams[] memory allocateParams = new AllocateParams[](operatorSets.length); + + for (uint256 i = 0; i < operatorSets.length; i++) { + allocateParams[i] = _newAllocateParams(operatorSets[i], magnitude)[0]; } - avsDirectoryMock.setIsOperatorSetBatch(operatorSets, true); + return allocateParams; + } + + /// @dev Create random allocation params to the default operator set and strategy + function _randAllocateParams_DefaultOpSet() internal returns (AllocateParams[] memory) { + return _randAllocateParams_SingleMockStrategy(defaultOperatorSet.toArray()); + } + + /// @dev Create allocate params for random magnitudes to the same default strategy across multiple operator sets + function _randAllocateParams_SingleMockStrategy( + OperatorSet[] memory operatorSets + ) internal returns (AllocateParams[] memory) { // Give each set a minimum of 1 magnitude - uint64[] memory magnitudes = new uint64[](numOpSets); + uint64[] memory magnitudes = new uint64[](operatorSets.length); uint64 usedMagnitude; - for (uint8 i = 0; i < numOpSets; i++) { + for (uint8 i = 0; i < magnitudes.length; ++i) { magnitudes[i] = 1; usedMagnitude++; } // Distribute remaining magnitude - uint64 maxMagnitude = 1e18; - for (uint8 i = 0; i < numOpSets; i++) { - r = uint256(keccak256(abi.encodePacked(r, i))); + uint64 maxMagnitude = WAD; + for (uint8 i = 0; i < magnitudes.length; ++i) { uint64 remainingMagnitude = maxMagnitude - usedMagnitude; if (remainingMagnitude > 0) { - magnitudes[i] += uint64(bound(r, 0, remainingMagnitude)); + magnitudes[i] += uint64(random().Uint256(0, remainingMagnitude)); usedMagnitude += magnitudes[i] - 1; } } - // Create magnitude allocation - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - new IAllocationManagerTypes.MagnitudeAllocation[](1); - allocations[0] = IAllocationManagerTypes.MagnitudeAllocation({ - strategy: strategyMock, - expectedMaxMagnitude: 1e18, // magnitude starts at 100% - operatorSets: operatorSets, - magnitudes: magnitudes - }); - return allocations; - } + AllocateParams[] memory params = new AllocateParams[](magnitudes.length); + for (uint256 i = 0; i < params.length; i++) { + params[i] = AllocateParams({ + operatorSet: operatorSets[i], + strategies: strategyMock.toArray(), + newMagnitudes: magnitudes[i].toArrayU64() + }); + } - /// ----------------------------------------------------------------------- - /// Generate a random allocation AND delllocation - /// ----------------------------------------------------------------------- + return params; + } - /** - * @notice Queued a random allocation and deallocation for the given `operator` - * - DOES NOT warp past the deallocation effect timestamp - */ - function _queueRandomAllocationAndDeallocation( - address operator, - uint8 numOpSets, - uint256 r, - uint256 salt - ) - internal - returns ( - IAllocationManagerTypes.MagnitudeAllocation[] memory, - IAllocationManagerTypes.MagnitudeAllocation[] memory - ) - { - (MagnitudeAllocation[] memory allocations, MagnitudeAllocation[] memory deallocations) = - _randomAllocationAndDeallocation_singleStrat_multipleOpSets(numOpSets, r, salt); + /// @dev Create allocate params for random magnitudes to the same default strategy across multiple operator sets + /// NOTE: this variant allocates ALL magnitude (1 WAD) + function _randAllocateParams_SingleMockStrategy_AllocAll( + OperatorSet[] memory operatorSets + ) internal returns (AllocateParams[] memory) { + // Give each set a minimum of 1 magnitude + uint64[] memory magnitudes = new uint64[](operatorSets.length); + uint64 usedMagnitude; + for (uint8 i = 0; i < magnitudes.length; ++i) { + magnitudes[i] = 1; + usedMagnitude++; + } - // Allocate - cheats.prank(operator); - allocationManager.modifyAllocations(allocations); + // Distribute remaining magnitude + uint64 maxMagnitude = WAD; + for (uint8 i = 0; i < magnitudes.length; ++i) { + uint64 remainingMagnitude = maxMagnitude - usedMagnitude; + if (remainingMagnitude > 0) { + magnitudes[i] += uint64(random().Uint64(0, remainingMagnitude)); + usedMagnitude += magnitudes[i] - 1; + } + } - // Warp to allocation complete timestamp - cheats.warp(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY); + // If there's any left, dump it on a random set + uint64 magnitudeLeft = maxMagnitude - usedMagnitude; + if (magnitudeLeft > 0) { + uint256 randIdx = random().Uint256(0, magnitudes.length - 1); + magnitudes[randIdx] += magnitudeLeft; + usedMagnitude += magnitudeLeft; + } - // Deallocate - cheats.prank(operator); - allocationManager.modifyAllocations(deallocations); + AllocateParams[] memory params = new AllocateParams[](magnitudes.length); + for (uint256 i = 0; i < params.length; i++) { + params[i] = AllocateParams({ + operatorSet: operatorSets[i], + strategies: strategyMock.toArray(), + newMagnitudes: magnitudes[i].toArrayU64() + }); + } - return (allocations, deallocations); + return params; } - /** - * @notice Generates a random allocation and deallocation for a single strategy and multiple operatorSets - * @notice Deallocations are from 0 to 1 less that the current allocated magnitude - */ - function _randomAllocationAndDeallocation_singleStrat_multipleOpSets( - uint8 numOpSets, - uint256 r, - uint256 salt - ) - internal - returns ( - IAllocationManagerTypes.MagnitudeAllocation[] memory, - IAllocationManagerTypes.MagnitudeAllocation[] memory - ) - { - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - new IAllocationManagerTypes.MagnitudeAllocation[](1); - allocations = _randomMagnitudeAllocation_singleStrat_multipleOpSets(r, salt, numOpSets); - - // Deallocate random magnitude from each of thsoe operatorSets - r = uint256(keccak256(abi.encodePacked(r, salt))); - uint64[] memory newMags = new uint64[](numOpSets); - for (uint8 i = 0; i < numOpSets; i++) { - newMags[i] = uint64(bound(r, 0, allocations[0].magnitudes[i] - 1)); - } + /// @dev Create allocate/deallocate params to the same default strategy across multiple sets + function _randAllocAndDeallocParams_SingleMockStrategy( + OperatorSet[] memory operatorSets + ) internal returns (AllocateParams[] memory, AllocateParams[] memory) { + AllocateParams[] memory allocateParams = _randAllocateParams_SingleMockStrategy(operatorSets); + AllocateParams[] memory deallocateParams = new AllocateParams[](allocateParams.length); - // Create deallocations - IAllocationManagerTypes.MagnitudeAllocation[] memory deallocations = - new IAllocationManagerTypes.MagnitudeAllocation[](1); - deallocations[0] = IAllocationManagerTypes.MagnitudeAllocation({ - strategy: strategyMock, - expectedMaxMagnitude: 1e18, // magnitude starts at 100% - operatorSets: allocations[0].operatorSets, - magnitudes: newMags - }); + // Generate a random deallocation for each operator set + for (uint256 i = 0; i < deallocateParams.length; ++i) { + deallocateParams[i] = AllocateParams({ + operatorSet: allocateParams[i].operatorSet, + strategies: allocateParams[i].strategies, + newMagnitudes: uint64(random().Uint256({min: 0, max: allocateParams[i].newMagnitudes[0] - 1})).toArrayU64() + }); + } - return (allocations, deallocations); + return (allocateParams, deallocateParams); } /// ----------------------------------------------------------------------- /// Utils /// ----------------------------------------------------------------------- - function _strategyMockArray() internal view returns (IStrategy[] memory) { - IStrategy[] memory strategies = new IStrategy[](1); - strategies[0] = strategyMock; - return strategies; - } - - function _randomAddr(uint256 r, uint256 salt) internal pure returns (address addr) { - /// @solidity memory-safe-assembly - assembly { - mstore(0x00, r) - mstore(0x20, salt) - addr := keccak256(0x00, 0x40) - } - } - - function _operatorSet(address avs, uint32 operatorSetId) internal pure returns (OperatorSet memory) { - return OperatorSet({avs: avs, operatorSetId: operatorSetId}); - } - function _maxNumToClear() internal pure returns (uint16[] memory) { uint16[] memory numToClear = new uint16[](1); numToClear[0] = type(uint16).max; @@ -468,37 +421,36 @@ contract AllocationManagerUnitTests_Initialization_Setters is AllocationManagerU /// 1. The fn can only be called once, during deployment. /// 2. The fn initializes the contract state correctly (owner, pauserRegistry, and initialPausedStatus). function testFuzz_Initialize( - uint256 r - ) public { + Randomness r + ) public rand(r) { // Generate random values for the expected initial state of the contract. - address expectedInitialOwner = _randomAddr(r, 0); - IPauserRegistry expectedPauserRegistry = IPauserRegistry(_randomAddr(r, 1)); + address expectedInitialOwner = r.Address(); + IPauserRegistry expectedPauserRegistry = IPauserRegistry(r.Address()); // Deploy the contract with the expected initial state. + uint256 initialPausedStatus = r.Uint256(); AllocationManager alm = _deployAllocationManagerWithMockDependencies( - expectedInitialOwner, - expectedPauserRegistry, - r // initialPausedStatus + expectedInitialOwner, expectedPauserRegistry, initialPausedStatus ); // Assert that the contract can only be initialized once. vm.expectRevert("Initializable: contract is already initialized"); - alm.initialize(expectedInitialOwner, r); + alm.initialize(expectedInitialOwner, initialPausedStatus); // Assert immutable state assertEq(address(alm.delegation()), address(delegationManagerMock)); - assertEq(address(alm.avsDirectory()), address(avsDirectoryMock)); assertEq(alm.DEALLOCATION_DELAY(), DEALLOCATION_DELAY); assertEq(alm.ALLOCATION_CONFIGURATION_DELAY(), ALLOCATION_CONFIGURATION_DELAY); // Assert initialiation state assertEq(alm.owner(), expectedInitialOwner); - assertEq(address(alm.pauserRegistry()), address(expectedPauserRegistry)); - assertEq(alm.paused(), r); + assertEq(alm.paused(), initialPausedStatus); } } contract AllocationManagerUnitTests_SlashOperator is AllocationManagerUnitTests { + using SingleItemArrayLib for *; + /// ----------------------------------------------------------------------- /// slashOperator() /// ----------------------------------------------------------------------- @@ -506,67 +458,61 @@ contract AllocationManagerUnitTests_SlashOperator is AllocationManagerUnitTests function test_revert_paused() public { allocationManager.pause(2 ** PAUSED_OPERATOR_SLASHING); cheats.expectRevert(IPausable.CurrentlyPaused.selector); - allocationManager.slashOperator(_randomSlashingParams(defaultOperator, 0, 0)); + allocationManager.slashOperator(_randSlashingParams(defaultOperator, 0)); } function test_revert_slashZero() public { - SlashingParams memory slashingParams = _randomSlashingParams(defaultOperator, 0, 0); + SlashingParams memory slashingParams = _randSlashingParams(defaultOperator, 0); slashingParams.wadToSlash = 0; - cheats.expectRevert(IAllocationManagerErrors.InvalidWadToSlash.selector); cheats.prank(defaultAVS); + cheats.expectRevert(InvalidWadToSlash.selector); allocationManager.slashOperator(slashingParams); } function test_revert_slashGreaterThanWAD() public { - SlashingParams memory slashingParams = _randomSlashingParams(defaultOperator, 0, 0); - slashingParams.wadToSlash = 1e18 + 1; + SlashingParams memory slashingParams = _randSlashingParams(defaultOperator, 0); + slashingParams.wadToSlash = WAD + 1; - cheats.expectRevert(IAllocationManagerErrors.InvalidWadToSlash.selector); cheats.prank(defaultAVS); + cheats.expectRevert(InvalidWadToSlash.selector); allocationManager.slashOperator(slashingParams); } function test_revert_operatorNotSlashable() public { - SlashingParams memory slashingParams = _randomSlashingParams(defaultOperator, 0, 0); - avsDirectoryMock.setIsOperatorSlashable( - slashingParams.operator, defaultAVS, slashingParams.operatorSetId, false - ); - - cheats.expectRevert(IAllocationManagerErrors.InvalidOperator.selector); - cheats.prank(defaultAVS); - allocationManager.slashOperator(slashingParams); - } - - function test_revert_operatorNotAllocated() public { - SlashingParams memory slashingParams = _randomSlashingParams(defaultOperator, 0, 0); - avsDirectoryMock.setIsOperatorSlashable(slashingParams.operator, defaultAVS, slashingParams.operatorSetId, true); - - cheats.expectRevert(IAllocationManagerErrors.OperatorNotAllocated.selector); - cheats.prank(defaultAVS); - allocationManager.slashOperator(slashingParams); - } - - function test_revert_operatorAllocated_notActive() public { - // Queue allocation - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - _queueRandomAllocation_singleStrat_singleOpSet(defaultOperator, 0, 0); - - // Setup data - SlashingParams memory slashingParams = SlashingParams({ - operator: defaultOperator, - operatorSetId: allocations[0].operatorSets[0].operatorSetId, - strategies: _strategyMockArray(), - wadToSlash: 1e18, - description: "test" - }); - avsDirectoryMock.setIsOperatorSlashable(slashingParams.operator, defaultAVS, slashingParams.operatorSetId, true); - - // Expect revert - cheats.expectRevert(IAllocationManagerErrors.OperatorNotAllocated.selector); cheats.prank(defaultAVS); - allocationManager.slashOperator(slashingParams); - } + cheats.expectRevert(NotMemberOfSet.selector); + allocationManager.slashOperator(_randSlashingParams(random().Address(), 0)); + } + + // function test_revert_operatorNotAllocated() public { + // SlashingParams memory slashingParams = _randSlashingParams(defaultOperator, 0); + // // avsDirectoryMock.setIsOperatorSlashable(slashingParams.operator, defaultAVS, slashingParams.operatorSetId, true); + + // cheats.expectRevert(OperatorNotAllocated.selector); + // cheats.prank(defaultAVS); + // allocationManager.slashOperator(slashingParams); + // } + + // function test_revert_operatorAllocated_notActive() public { + // // Queue allocation + // AllocateParams[] memory allocateParams = + // _queueRandomAllocation_singleStrat_singleOpSet(defaultOperator, 0, 0); + + // // Setup data + // SlashingParams memory slashingParams = SlashingParams({ + // operator: defaultOperator, + // operatorSetId: allocateParams[0].operatorSet.id, + // wadToSlash: WAD, + // description: "test" + // }); + // // avsDirectoryMock.setIsOperatorSlashable(slashingParams.operator, defaultAVS, slashingParams.operatorSetId, true); + + // // Expect revert + // cheats.expectRevert(OperatorNotAllocated.selector); + // cheats.prank(defaultAVS); + // allocationManager.slashOperator(slashingParams); + // } /** * Allocates all magnitude to for a single strategy to an operatorSet. Slashes 25% @@ -574,45 +520,26 @@ contract AllocationManagerUnitTests_SlashOperator is AllocationManagerUnitTests * 1. Events are emitted * 2. Encumbered mag is updated * 3. Max mag is updated - * 4. Calculations for `getAllocatableMagnitude` and `getAllocationInfo` are correct + * 4. Calculations for `getAllocatableMagnitude` and `getAllocation` are correct */ function test_slashPostAllocation() public { - // Generate allocation for `strategyMock`, we allocate max - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - _generateMagnitudeAllocationCalldata(defaultAVS, 1e18, 1e18); + // Generate allocation for this operator set, we allocate max + AllocateParams[] memory allocateParams = _newAllocateParams(defaultOperatorSet, WAD); + cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); - cheats.warp(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY); + allocationManager.modifyAllocations(allocateParams); + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); // Slash operator for 25% SlashingParams memory slashingParams = SlashingParams({ operator: defaultOperator, - operatorSetId: allocations[0].operatorSets[0].operatorSetId, - strategies: _strategyMockArray(), + operatorSetId: defaultOperatorSet.id, wadToSlash: 25e16, description: "test" }); - avsDirectoryMock.setIsOperatorSlashable(slashingParams.operator, defaultAVS, slashingParams.operatorSetId, true); + // // avsDirectoryMock.setIsOperatorSlashable(slashingParams.operator, defaultAVS, slashingParams.operatorSetId, true); // Slash Operator - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit EncumberedMagnitudeUpdated(defaultOperator, strategyMock, 75e16); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit OperatorSetMagnitudeUpdated( - defaultOperator, allocations[0].operatorSets[0], strategyMock, 75e16, uint32(block.timestamp) - ); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit MaxMagnitudeUpdated(defaultOperator, strategyMock, 75e16); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - uint256[] memory wadSlashed = new uint256[](1); - wadSlashed[0] = 25e16; - emit OperatorSlashed( - slashingParams.operator, - _operatorSet(defaultAVS, slashingParams.operatorSetId), - slashingParams.strategies, - wadSlashed, - slashingParams.description - ); cheats.prank(defaultAVS); allocationManager.slashOperator(slashingParams); @@ -623,62 +550,39 @@ contract AllocationManagerUnitTests_SlashOperator is AllocationManagerUnitTests "encumberedMagnitude not updated" ); assertEq( - 75e16, - allocationManager.getMaxMagnitudes(defaultOperator, _strategyMockArray())[0], - "maxMagnitude not updated" + 75e16, allocationManager.getMaxMagnitudes(defaultOperator, defaultStrategies)[0], "maxMagnitude not updated" ); assertEq( 0, allocationManager.getAllocatableMagnitude(defaultOperator, strategyMock), "allocatableMagnitude shoudl be 0" ); - MagnitudeInfo[] memory mInfos = - allocationManager.getAllocationInfo(defaultOperator, strategyMock, allocations[0].operatorSets); - assertEq(75e16, mInfos[0].currentMagnitude, "currentMagnitude not updated"); - assertEq(0, mInfos[0].pendingDiff, "pendingDiff should be 0"); - assertEq(0, mInfos[0].effectTimestamp, "effectTimestamp should be 0"); + Allocation memory allocation = + allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); + assertEq(75e16, allocation.currentMagnitude, "currentMagnitude not updated"); + assertEq(0, allocation.pendingDiff, "pendingDiff should be 0"); + assertEq(0, allocation.effectBlock, "effectBlock should be 0"); } /// @notice Same test as above, but fuzzes the allocation - function testFuzz_slashPostAllocation(uint256 r, uint256 salt) public { - // Complete Allocation for `strategyMock` - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - _completeRandomAllocation_singleStrat_singleOpset(defaultOperator, defaultAVS, r, 0); + function testFuzz_slashPostAllocation( + Randomness r + ) public rand(r) { + AllocateParams[] memory allocateParams = _randAllocateParams_DefaultOpSet(); + + // Allocate magnitude and roll forward to completable block + cheats.prank(defaultOperator); + allocationManager.modifyAllocations(allocateParams); + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); + + SlashingParams memory slashingParams = _randSlashingParams(defaultOperator, defaultOperatorSet.id); - // Setup data - SlashingParams memory slashingParams = - _randomSlashingParams(defaultOperator, allocations[0].operatorSets[0].operatorSetId, r, 1); - avsDirectoryMock.setIsOperatorSlashable( - slashingParams.operator, defaultAVS, allocations[0].operatorSets[0].operatorSetId, true - ); uint64 expectedSlashedMagnitude = - uint64(SlashingLib.mulWadRoundUp(allocations[0].magnitudes[0], slashingParams.wadToSlash)); - uint64 expectedEncumberedMagnitude = allocations[0].magnitudes[0] - expectedSlashedMagnitude; + uint64(SlashingLib.mulWadRoundUp(allocateParams[0].newMagnitudes[0], slashingParams.wadToSlash)); + uint64 expectedEncumberedMagnitude = allocateParams[0].newMagnitudes[0] - expectedSlashedMagnitude; uint64 maxMagnitudeAfterSlash = WAD - expectedSlashedMagnitude; - uint256[] memory wadSlashed = new uint256[](1); - wadSlashed[0] = expectedSlashedMagnitude; // Slash Operator - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit EncumberedMagnitudeUpdated(defaultOperator, strategyMock, expectedEncumberedMagnitude); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit OperatorSetMagnitudeUpdated( - defaultOperator, - allocations[0].operatorSets[0], - strategyMock, - expectedEncumberedMagnitude, - uint32(block.timestamp) - ); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit MaxMagnitudeUpdated(defaultOperator, strategyMock, maxMagnitudeAfterSlash); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit OperatorSlashed( - slashingParams.operator, - _operatorSet(defaultAVS, slashingParams.operatorSetId), - slashingParams.strategies, - wadSlashed, - slashingParams.description - ); cheats.prank(defaultAVS); allocationManager.slashOperator(slashingParams); @@ -690,14 +594,16 @@ contract AllocationManagerUnitTests_SlashOperator is AllocationManagerUnitTests ); assertEq( maxMagnitudeAfterSlash, - allocationManager.getMaxMagnitudes(defaultOperator, _strategyMockArray())[0], + allocationManager.getMaxMagnitudes(defaultOperator, defaultStrategies)[0], "maxMagnitude not updated" ); - MagnitudeInfo[] memory mInfos = - allocationManager.getAllocationInfo(defaultOperator, strategyMock, allocations[0].operatorSets); - assertEq(expectedEncumberedMagnitude, mInfos[0].currentMagnitude, "currentMagnitude not updated"); - assertEq(0, mInfos[0].pendingDiff, "pendingDiff should be 0"); - assertEq(0, mInfos[0].effectTimestamp, "effectTimestamp should be 0"); + + Allocation memory allocation = + allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); + + assertEq(expectedEncumberedMagnitude, allocation.currentMagnitude, "currentMagnitude not updated"); + assertEq(0, allocation.pendingDiff, "pendingDiff should be 0"); + assertEq(0, allocation.effectBlock, "effectBlock should be 0"); } /** @@ -706,163 +612,151 @@ contract AllocationManagerUnitTests_SlashOperator is AllocationManagerUnitTests * 1. Events are emitted * 2. Encumbered mag is updated * 3. Max mag is updated - * 4. Calculations for `getAllocatableMagnitude` and `getAllocationInfo` are correct + * 4. Calculations for `getAllocatableMagnitude` and `getAllocation` are correct * 5. The second magnitude allocation is not slashed from * TODO: Fuzz */ function test_slash_oneCompletedAlloc_onePendingAlloc() public { // Generate allocation for `strategyMock`, we allocate half - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = _generateMagnitudeAllocationCalldata(defaultAVS, 5e17, 1e18); + AllocateParams[] memory allocateParams = _newAllocateParams(defaultOperatorSet, 5e17); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); - cheats.warp(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY); + allocationManager.modifyAllocations(allocateParams); + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); // Allocate the other half - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations2 = _generateMagnitudeAllocationCalldata(defaultAVS, 1e18, 1e18); + AllocateParams[] memory allocateParams2 = _newAllocateParams(defaultOperatorSet, WAD); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations2); - uint32 secondAllocEffectTimestamp = uint32(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY); + allocationManager.modifyAllocations(allocateParams2); + uint32 secondAllocEffectBlock = uint32(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); // Slash operator for 50% SlashingParams memory slashingParams = SlashingParams({ operator: defaultOperator, - operatorSetId: allocations[0].operatorSets[0].operatorSetId, - strategies: _strategyMockArray(), + operatorSetId: defaultOperatorSet.id, wadToSlash: 50e16, description: "test" }); - avsDirectoryMock.setIsOperatorSlashable(slashingParams.operator, defaultAVS, slashingParams.operatorSetId, true); + // // avsDirectoryMock.setIsOperatorSlashable(slashingParams.operator, defaultAVS, slashingParams.operatorSetId, true); uint64 expectedEncumberedMagnitude = 75e16; // 25e16 from first allocation, 50e16 from second uint64 magnitudeAfterSlash = 25e16; uint64 maxMagnitudeAfterSlash = 75e16; - uint256[] memory wadSlashed = new uint256[](1); - wadSlashed[0] = 25e16; // Slash Operator - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit EncumberedMagnitudeUpdated(defaultOperator, strategyMock, expectedEncumberedMagnitude); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit OperatorSetMagnitudeUpdated(defaultOperator, allocations[0].operatorSets[0], strategyMock, magnitudeAfterSlash, uint32(block.timestamp)); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit MaxMagnitudeUpdated(defaultOperator, strategyMock, maxMagnitudeAfterSlash); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit OperatorSlashed(slashingParams.operator, _operatorSet(defaultAVS, slashingParams.operatorSetId), slashingParams.strategies, wadSlashed, slashingParams.description); cheats.prank(defaultAVS); allocationManager.slashOperator(slashingParams); // Check storage - assertEq(expectedEncumberedMagnitude, allocationManager.encumberedMagnitude(defaultOperator, strategyMock), "encumberedMagnitude not updated"); - assertEq(maxMagnitudeAfterSlash, allocationManager.getMaxMagnitudes(defaultOperator, _strategyMockArray())[0], "maxMagnitude not updated"); - MagnitudeInfo[] memory mInfos = allocationManager.getAllocationInfo(defaultOperator, strategyMock, allocations[0].operatorSets); - assertEq(magnitudeAfterSlash, mInfos[0].currentMagnitude, "currentMagnitude not updated"); - assertEq(5e17, mInfos[0].pendingDiff, "pendingDiff should be for second alloc"); - assertEq(secondAllocEffectTimestamp, mInfos[0].effectTimestamp, "effectTimestamp should be 0"); + assertEq( + expectedEncumberedMagnitude, + allocationManager.encumberedMagnitude(defaultOperator, strategyMock), + "encumberedMagnitude not updated" + ); + assertEq( + maxMagnitudeAfterSlash, + allocationManager.getMaxMagnitudes(defaultOperator, defaultStrategies)[0], + "maxMagnitude not updated" + ); + Allocation memory allocation = + allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); + assertEq(magnitudeAfterSlash, allocation.currentMagnitude, "currentMagnitude not updated"); + assertEq(5e17, allocation.pendingDiff, "pendingDiff should be for second alloc"); + assertEq(secondAllocEffectBlock, allocation.effectBlock, "effectBlock should be 0"); // Warp to complete second allocation - cheats.warp(secondAllocEffectTimestamp); + cheats.roll(secondAllocEffectBlock); uint64 allocatableMagnitude = allocationManager.getAllocatableMagnitude(defaultOperator, strategyMock); assertEq(0, allocatableMagnitude, "allocatableMagnitude should be 0"); - mInfos = allocationManager.getAllocationInfo(defaultOperator, strategyMock, allocations2[0].operatorSets); - assertEq(75e16, mInfos[0].currentMagnitude, "currentMagnitude not updated"); - assertEq(0, mInfos[0].pendingDiff, "pendingDiff should be 0"); - assertEq(0, mInfos[0].effectTimestamp, "effectTimestamp should be 0"); + allocation = allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); + assertEq(75e16, allocation.currentMagnitude, "currentMagnitude not updated"); + assertEq(0, allocation.pendingDiff, "pendingDiff should be 0"); + assertEq(0, allocation.effectBlock, "effectBlock should be 0"); } /** * Allocates 100% magnitude for a single strategy to an operatorSet. * First slashes 99% from the operatorSet, slashes 99.99% a second time, and on the third slash, slashes - * 99.9999999999999% which should get rounded up to 100% or 1e18 wadSlashed leaving the operator with no magnitude + * 99.9999999999999% which should get rounded up to 100% or WAD wadSlashed leaving the operator with no magnitude * in the operatorSet, 0 encumbered magnitude, and 0 max magnitude. - * + * * Asserts that: * 1. Events are emitted * 2. Encumbered mag is updated * 3. Max mag is updated - * 4. Calculations for `getAllocatableMagnitude` and `getAllocationInfo` are correct + * 4. Calculations for `getAllocatableMagnitude` and `getAllocation` are correct * 5. Slashed amounts are rounded up to ensure magnitude is always slashed */ function test_slashTwoOperatorSets() public { // Generate allocation for `strategyMock`, we allocate 100% to opSet 0 - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = _generateMagnitudeAllocationCalldataForOpSet({ - avsToSet: defaultAVS, - operatorSetId: 0, - magnitudeToSet: 1e18, - expectedMaxMagnitude: 1e18 - }); + AllocateParams[] memory allocateParams = _newAllocateParams(defaultOperatorSet, WAD); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); - cheats.warp(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY); + allocationManager.modifyAllocations(allocateParams); + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); // 1. Slash operator for 99% in opSet 0 bringing their magnitude to 1e16 SlashingParams memory slashingParams = SlashingParams({ operator: defaultOperator, - operatorSetId: allocations[0].operatorSets[0].operatorSetId, - strategies: _strategyMockArray(), + operatorSetId: defaultOperatorSet.id, wadToSlash: 99e16, description: "test" }); - avsDirectoryMock.setIsOperatorSlashable(slashingParams.operator, defaultAVS, slashingParams.operatorSetId, true); + // // avsDirectoryMock.setIsOperatorSlashable(slashingParams.operator, defaultAVS, slashingParams.operatorSetId, true); uint64 expectedEncumberedMagnitude = 1e16; // After slashing 99%, only 1% expected encumberedMagnitude uint64 magnitudeAfterSlash = 1e16; uint64 maxMagnitudeAfterSlash = 1e16; // 1e15 is maxMagnitude - uint256[] memory wadSlashed = new uint256[](1); - wadSlashed[0] = 99e16; // Slash Operator - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit EncumberedMagnitudeUpdated(defaultOperator, strategyMock, expectedEncumberedMagnitude); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit OperatorSetMagnitudeUpdated(defaultOperator, allocations[0].operatorSets[0], strategyMock, magnitudeAfterSlash, uint32(block.timestamp)); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit MaxMagnitudeUpdated(defaultOperator, strategyMock, maxMagnitudeAfterSlash); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit OperatorSlashed(slashingParams.operator, _operatorSet(defaultAVS, slashingParams.operatorSetId), slashingParams.strategies, wadSlashed, slashingParams.description); cheats.prank(defaultAVS); allocationManager.slashOperator(slashingParams); // Check storage - assertEq(expectedEncumberedMagnitude, allocationManager.encumberedMagnitude(defaultOperator, strategyMock), "encumberedMagnitude not updated"); - assertEq(maxMagnitudeAfterSlash, allocationManager.getMaxMagnitudes(defaultOperator, _strategyMockArray())[0], "maxMagnitude not updated"); - MagnitudeInfo[] memory mInfos = allocationManager.getAllocationInfo(defaultOperator, strategyMock, allocations[0].operatorSets); - assertEq(magnitudeAfterSlash, mInfos[0].currentMagnitude, "currentMagnitude not updated"); + assertEq( + expectedEncumberedMagnitude, + allocationManager.encumberedMagnitude(defaultOperator, strategyMock), + "encumberedMagnitude not updated" + ); + assertEq( + maxMagnitudeAfterSlash, + allocationManager.getMaxMagnitudes(defaultOperator, defaultStrategies)[0], + "maxMagnitude not updated" + ); + Allocation memory allocation = + allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); + assertEq(magnitudeAfterSlash, allocation.currentMagnitude, "currentMagnitude not updated"); // 2. Slash operator again for 99.99% in opSet 0 bringing their magnitude to 1e14 slashingParams = SlashingParams({ operator: defaultOperator, - operatorSetId: allocations[0].operatorSets[0].operatorSetId, - strategies: _strategyMockArray(), + operatorSetId: defaultOperatorSet.id, wadToSlash: 9999e14, description: "test" }); expectedEncumberedMagnitude = 1e12; // After slashing 99.99%, only 0.01% expected encumberedMagnitude magnitudeAfterSlash = 1e12; maxMagnitudeAfterSlash = 1e12; - wadSlashed[0] = 9999e14; - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit EncumberedMagnitudeUpdated(defaultOperator, strategyMock, expectedEncumberedMagnitude); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit OperatorSetMagnitudeUpdated(defaultOperator, allocations[0].operatorSets[0], strategyMock, magnitudeAfterSlash, uint32(block.timestamp)); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit MaxMagnitudeUpdated(defaultOperator, strategyMock, maxMagnitudeAfterSlash); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit OperatorSlashed(slashingParams.operator, _operatorSet(defaultAVS, slashingParams.operatorSetId), slashingParams.strategies, wadSlashed, slashingParams.description); cheats.prank(defaultAVS); allocationManager.slashOperator(slashingParams); // Check storage - assertEq(expectedEncumberedMagnitude, allocationManager.encumberedMagnitude(defaultOperator, strategyMock), "encumberedMagnitude not updated"); - assertEq(maxMagnitudeAfterSlash, allocationManager.getMaxMagnitudes(defaultOperator, _strategyMockArray())[0], "maxMagnitude not updated"); - mInfos = allocationManager.getAllocationInfo(defaultOperator, strategyMock, allocations[0].operatorSets); - assertEq(magnitudeAfterSlash, mInfos[0].currentMagnitude, "currentMagnitude not updated"); + assertEq( + expectedEncumberedMagnitude, + allocationManager.encumberedMagnitude(defaultOperator, strategyMock), + "encumberedMagnitude not updated" + ); + assertEq( + maxMagnitudeAfterSlash, + allocationManager.getMaxMagnitudes(defaultOperator, defaultStrategies)[0], + "maxMagnitude not updated" + ); + allocation = allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); + assertEq(magnitudeAfterSlash, allocation.currentMagnitude, "currentMagnitude not updated"); // 3. Slash operator again for 99.9999999999999% in opSet 0 slashingParams = SlashingParams({ operator: defaultOperator, - operatorSetId: allocations[0].operatorSets[0].operatorSetId, - strategies: _strategyMockArray(), - wadToSlash: 1e18 - 1e3, + operatorSetId: defaultOperatorSet.id, + wadToSlash: WAD - 1e3, description: "test" }); // Should technically be 1e3 remaining but with rounding error and rounding up slashed amounts @@ -870,26 +764,24 @@ contract AllocationManagerUnitTests_SlashOperator is AllocationManagerUnitTests expectedEncumberedMagnitude = 0; // Should technically be 1e3 remaining but with rounding error and rounding up slashed amounts. magnitudeAfterSlash = 0; maxMagnitudeAfterSlash = 0; - // wadSlashed is rounded up from the 1e18 - 1e3 amount - wadSlashed[0] = 1e18; // Slash Operator - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit EncumberedMagnitudeUpdated(defaultOperator, strategyMock, expectedEncumberedMagnitude); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit OperatorSetMagnitudeUpdated(defaultOperator, allocations[0].operatorSets[0], strategyMock, magnitudeAfterSlash, uint32(block.timestamp)); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit MaxMagnitudeUpdated(defaultOperator, strategyMock, maxMagnitudeAfterSlash); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit OperatorSlashed(slashingParams.operator, _operatorSet(defaultAVS, slashingParams.operatorSetId), slashingParams.strategies, wadSlashed, slashingParams.description); cheats.prank(defaultAVS); allocationManager.slashOperator(slashingParams); // Check storage - assertEq(expectedEncumberedMagnitude, allocationManager.encumberedMagnitude(defaultOperator, strategyMock), "encumberedMagnitude not updated"); - assertEq(maxMagnitudeAfterSlash, allocationManager.getMaxMagnitudes(defaultOperator, _strategyMockArray())[0], "maxMagnitude not updated"); - mInfos = allocationManager.getAllocationInfo(defaultOperator, strategyMock, allocations[0].operatorSets); - assertEq(magnitudeAfterSlash, mInfos[0].currentMagnitude, "currentMagnitude not updated"); + assertEq( + expectedEncumberedMagnitude, + allocationManager.encumberedMagnitude(defaultOperator, strategyMock), + "encumberedMagnitude not updated" + ); + assertEq( + maxMagnitudeAfterSlash, + allocationManager.getMaxMagnitudes(defaultOperator, defaultStrategies)[0], + "maxMagnitude not updated" + ); + allocation = allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); + assertEq(magnitudeAfterSlash, allocation.currentMagnitude, "currentMagnitude not updated"); } /** @@ -898,34 +790,33 @@ contract AllocationManagerUnitTests_SlashOperator is AllocationManagerUnitTests * 1. Events are emitted, including for deallocation * 2. Encumbered mag is updated * 3. Max mag is updated - * 4. Calculations for `getAllocatableMagnitude` and `getAllocationInfo` are correct + * 4. Calculations for `getAllocatableMagnitude` and `getAllocation` are correct * 5. The deallocation is slashed from * 6. Pending magnitude updates post deallocation are valid * TODO: Fuzz the allocation & slash amounts */ function test_allocateAll_deallocateHalf_slashWhileDeallocPending() public { - uint64 initialMagnitude = 1e18; + uint64 initialMagnitude = WAD; // Generate allocation for `strategyMock`, we allocate half - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = _generateMagnitudeAllocationCalldata(defaultAVS, initialMagnitude, 1e18); + AllocateParams[] memory allocateParams = _newAllocateParams(defaultOperatorSet, initialMagnitude); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); - cheats.warp(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY); + allocationManager.modifyAllocations(allocateParams); + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); // Deallocate half - IAllocationManagerTypes.MagnitudeAllocation[] memory deallocations = _generateMagnitudeAllocationCalldata(defaultAVS, initialMagnitude / 2, 1e18); + AllocateParams[] memory deallocateParams = _newAllocateParams(defaultOperatorSet, initialMagnitude / 2); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(deallocations); - uint32 deallocationEffectTimestamp = uint32(block.timestamp + DEALLOCATION_DELAY); + allocationManager.modifyAllocations(deallocateParams); + uint32 deallocationEffectBlock = uint32(block.number + DEALLOCATION_DELAY); // Slash operator for 25% SlashingParams memory slashingParams = SlashingParams({ operator: defaultOperator, - operatorSetId: allocations[0].operatorSets[0].operatorSetId, - strategies: _strategyMockArray(), + operatorSetId: defaultOperatorSet.id, wadToSlash: 25e16, description: "test" }); - avsDirectoryMock.setIsOperatorSlashable(slashingParams.operator, defaultAVS, slashingParams.operatorSetId, true); + // // avsDirectoryMock.setIsOperatorSlashable(slashingParams.operator, defaultAVS, slashingParams.operatorSetId, true); uint64 magnitudeAfterDeallocationSlash = 375e15; // 25% is slashed off of 5e17 uint64 expectedEncumberedMagnitude = 75e16; // 25e16 is slashed. 75e16 is encumbered uint64 magnitudeAfterSlash = 75e16; @@ -933,285 +824,288 @@ contract AllocationManagerUnitTests_SlashOperator is AllocationManagerUnitTests // Slash Operator // First event is emitted because of deallocation - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit OperatorSetMagnitudeUpdated(defaultOperator, allocations[0].operatorSets[0], strategyMock, magnitudeAfterDeallocationSlash, deallocationEffectTimestamp); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit EncumberedMagnitudeUpdated(defaultOperator, strategyMock, expectedEncumberedMagnitude); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit OperatorSetMagnitudeUpdated(defaultOperator, allocations[0].operatorSets[0], strategyMock, magnitudeAfterSlash, uint32(block.timestamp)); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit MaxMagnitudeUpdated(defaultOperator, strategyMock, maxMagnitudeAfterSlash); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - uint256[] memory wadSlashed = new uint256[](1); - wadSlashed[0] = 25e16; - emit OperatorSlashed(slashingParams.operator, _operatorSet(defaultAVS, slashingParams.operatorSetId), slashingParams.strategies, wadSlashed, slashingParams.description); cheats.prank(defaultAVS); allocationManager.slashOperator(slashingParams); // Check storage post slash - assertEq(expectedEncumberedMagnitude, allocationManager.encumberedMagnitude(defaultOperator, strategyMock), "encumberedMagnitude not updated"); - assertEq(maxMagnitudeAfterSlash, allocationManager.getMaxMagnitudes(defaultOperator, _strategyMockArray())[0], "maxMagnitude not updated"); - MagnitudeInfo[] memory mInfos = allocationManager.getAllocationInfo(defaultOperator, strategyMock, allocations[0].operatorSets); - assertEq(magnitudeAfterSlash, mInfos[0].currentMagnitude, "currentMagnitude not updated"); - assertEq(-int128(uint128((uint64(magnitudeAfterDeallocationSlash)))), mInfos[0].pendingDiff, "pendingDiff should be decreased after slash"); - assertEq(deallocationEffectTimestamp, mInfos[0].effectTimestamp, "effectTimestamp should be 0"); - - // Check storage after complete modification - cheats.warp(deallocationEffectTimestamp); - allocationManager.clearDeallocationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); - mInfos = allocationManager.getAllocationInfo(defaultOperator, strategyMock, allocations[0].operatorSets); - assertEq(magnitudeAfterDeallocationSlash, mInfos[0].currentMagnitude, "currentMagnitude not updated"); - assertEq(magnitudeAfterDeallocationSlash, maxMagnitudeAfterSlash / 2, "magnitude after deallocation should be half of max magnitude, since we originally deallocated by half"); - } - - /** - * Allocates all magnitude to a single opSet. Then slashes the entire magnitude - * Asserts that: - * 1. The operator cannot allocate again - */ - function testRevert_allocateAfterSlashedEntirely() public { + assertEq( + expectedEncumberedMagnitude, + allocationManager.encumberedMagnitude(defaultOperator, strategyMock), + "encumberedMagnitude not updated" + ); + assertEq( + maxMagnitudeAfterSlash, + allocationManager.getMaxMagnitudes(defaultOperator, defaultStrategies)[0], + "maxMagnitude not updated" + ); + Allocation memory allocation = + allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); + assertEq(magnitudeAfterSlash, allocation.currentMagnitude, "currentMagnitude not updated"); + assertEq( + -int128(uint128((uint64(magnitudeAfterDeallocationSlash)))), + allocation.pendingDiff, + "pendingDiff should be decreased after slash" + ); + assertEq(deallocationEffectBlock, allocation.effectBlock, "effectBlock should be 0"); + + // Check storage after complete modification + cheats.roll(deallocationEffectBlock); + allocationManager.clearDeallocationQueue(defaultOperator, defaultStrategies, _maxNumToClear()); + allocation = allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); + assertEq(magnitudeAfterDeallocationSlash, allocation.currentMagnitude, "currentMagnitude not updated"); + assertEq( + magnitudeAfterDeallocationSlash, + maxMagnitudeAfterSlash / 2, + "magnitude after deallocation should be half of max magnitude, since we originally deallocated by half" + ); + } + + /** + * Allocates all magnitude to a single opSet. Then slashes the entire magnitude + * Asserts that: + * 1. The operator cannot allocate again + */ + function testRevert_allocateAfterSlashedEntirely() public { // Allocate all magnitude - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = _generateMagnitudeAllocationCalldata(defaultAVS, 1e18, 1e18); + AllocateParams[] memory allocateParams = _newAllocateParams(defaultOperatorSet, WAD); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); - cheats.warp(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY); + allocationManager.modifyAllocations(allocateParams); + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); // Slash operator for 100% SlashingParams memory slashingParams = SlashingParams({ operator: defaultOperator, - operatorSetId: allocations[0].operatorSets[0].operatorSetId, - strategies: _strategyMockArray(), - wadToSlash: 1e18, + operatorSetId: allocateParams[0].operatorSet.id, + wadToSlash: WAD, description: "test" }); - avsDirectoryMock.setIsOperatorSlashable(slashingParams.operator, defaultAVS, slashingParams.operatorSetId, true); // Slash Operator cheats.prank(defaultAVS); allocationManager.slashOperator(slashingParams); + OperatorSet memory operatorSet = _newOperatorSet_SingleMockStrategy(defaultAVS); + // Attempt to allocate - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations2 = _generateMagnitudeAllocationCalldata(defaultAVS, 1, 0); - cheats.expectRevert(IAllocationManagerErrors.InsufficientAllocatableMagnitude.selector); + AllocateParams[] memory allocateParams2 = _newAllocateParams(operatorSet, 1); + + cheats.expectRevert(InsufficientMagnitude.selector); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations2); + allocationManager.modifyAllocations(allocateParams2); } /** * Allocates all magnitude to a single opSet. Deallocateas magnitude. Slashes al * Asserts that: - * 1. The MagnitudeInfo is 0 after slash + * 1. The Allocation is 0 after slash * 2. Them sotrage post slash for encumbered and maxMags ais zero */ function test_allocateAll_deallocateAll() public { // Allocate all magnitude - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = _generateMagnitudeAllocationCalldata(defaultAVS, 1e18, 1e18); + AllocateParams[] memory allocateParams = _newAllocateParams(defaultOperatorSet, WAD); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); - cheats.warp(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY); + allocationManager.modifyAllocations(allocateParams); + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); // Deallocate all - IAllocationManagerTypes.MagnitudeAllocation[] memory deallocations = _generateMagnitudeAllocationCalldata(defaultAVS, 0, 1e18); + AllocateParams[] memory deallocateParams = _newAllocateParams(defaultOperatorSet, 0); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(deallocations); - uint32 deallocationEffectTimestamp = uint32(block.timestamp + DEALLOCATION_DELAY); + allocationManager.modifyAllocations(deallocateParams); + uint32 deallocationEffectBlock = uint32(block.number + DEALLOCATION_DELAY); // Slash operator for 100% SlashingParams memory slashingParams = SlashingParams({ operator: defaultOperator, - operatorSetId: allocations[0].operatorSets[0].operatorSetId, - strategies: _strategyMockArray(), - wadToSlash: 1e18, + operatorSetId: defaultOperatorSet.id, + wadToSlash: WAD, description: "test" }); - avsDirectoryMock.setIsOperatorSlashable(slashingParams.operator, defaultAVS, slashingParams.operatorSetId, true); + // avsDirectoryMock.setIsOperatorSlashable(slashingParams.operator, defaultAVS, slashingParams.operatorSetId, true); // Slash Operator - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit OperatorSetMagnitudeUpdated(defaultOperator, allocations[0].operatorSets[0], strategyMock, 0, deallocationEffectTimestamp); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit EncumberedMagnitudeUpdated(defaultOperator, strategyMock, 0); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit OperatorSetMagnitudeUpdated(defaultOperator, allocations[0].operatorSets[0], strategyMock, 0, uint32(block.timestamp)); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit MaxMagnitudeUpdated(defaultOperator, strategyMock, 0); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - uint256[] memory wadSlashed = new uint256[](1); - wadSlashed[0] = 1e18; - emit OperatorSlashed(slashingParams.operator, _operatorSet(defaultAVS, slashingParams.operatorSetId), slashingParams.strategies, wadSlashed, slashingParams.description); - cheats.prank(defaultAVS); allocationManager.slashOperator(slashingParams); // Check storage post slash - assertEq(0, allocationManager.encumberedMagnitude(defaultOperator, strategyMock), "encumberedMagnitude not updated"); - assertEq(0, allocationManager.getMaxMagnitudes(defaultOperator, _strategyMockArray())[0], "maxMagnitude not updated"); - MagnitudeInfo[] memory mInfos = allocationManager.getAllocationInfo(defaultOperator, strategyMock, allocations[0].operatorSets); - assertEq(0, mInfos[0].currentMagnitude, "currentMagnitude not updated"); - assertEq(0, mInfos[0].pendingDiff, "pendingDiff should be zero since everything is slashed"); - assertEq(deallocationEffectTimestamp, mInfos[0].effectTimestamp, "effectTimestamp should be 0"); + assertEq( + 0, allocationManager.encumberedMagnitude(defaultOperator, strategyMock), "encumberedMagnitude not updated" + ); + assertEq( + 0, allocationManager.getMaxMagnitudes(defaultOperator, defaultStrategies)[0], "maxMagnitude not updated" + ); + Allocation memory allocation = + allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); + assertEq(0, allocation.currentMagnitude, "currentMagnitude not updated"); + assertEq(0, allocation.pendingDiff, "pendingDiff should be zero since everything is slashed"); + assertEq(deallocationEffectBlock, allocation.effectBlock, "effectBlock should be 0"); } /** * Slashes the operator after deallocation, even if the deallocation has not been cleared. Validates that: - * 1. Even if we do not clear deallocation queue, the deallocation is NOT slashed from since we're passed the deallocationEffectTimestamp + * 1. Even if we do not clear deallocation queue, the deallocation is NOT slashed from since we're passed the deallocationEffectBlock * 2. Validates storage post slash & post clearing deallocation queue * 3. Max magnitude only decreased proportionally by the magnitude set after deallocation */ function test_allocate_deallocate_slashAfterDeallocation() public { // Allocate all magnitude - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = _generateMagnitudeAllocationCalldata(defaultAVS, 1e18, 1e18); + AllocateParams[] memory allocateParams = _newAllocateParams(defaultOperatorSet, WAD); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); - cheats.warp(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY); + allocationManager.modifyAllocations(allocateParams); + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); // Deallocate half - IAllocationManagerTypes.MagnitudeAllocation[] memory deallocations = _generateMagnitudeAllocationCalldata(defaultAVS, 5e17, 1e18); + AllocateParams[] memory deallocateParams = _newAllocateParams(defaultOperatorSet, 5e17); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(deallocations); - uint32 deallocationEffectTimestamp = uint32(block.timestamp + DEALLOCATION_DELAY); + allocationManager.modifyAllocations(deallocateParams); + uint32 deallocationEffectBlock = uint32(block.number + DEALLOCATION_DELAY); // Check storage post deallocation - MagnitudeInfo[] memory mInfos = allocationManager.getAllocationInfo(defaultOperator, strategyMock, allocations[0].operatorSets); - assertEq(1e18, mInfos[0].currentMagnitude, "currentMagnitude not updated"); - assertEq(-5e17, mInfos[0].pendingDiff, "pendingDiff should be 5e17 after deallocation"); - assertEq(deallocationEffectTimestamp, mInfos[0].effectTimestamp, "effectTimestamp should be 0"); - - // Warp to deallocation effect timestamp - cheats.warp(deallocationEffectTimestamp); + Allocation memory allocation = + allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); + assertEq(WAD, allocation.currentMagnitude, "currentMagnitude not updated"); + assertEq(-5e17, allocation.pendingDiff, "pendingDiff should be 5e17 after deallocation"); + assertEq(deallocationEffectBlock, allocation.effectBlock, "effectBlock should be 0"); + // Warp to deallocation effect block + cheats.roll(deallocationEffectBlock); // Slash operator for 25% SlashingParams memory slashingParams = SlashingParams({ operator: defaultOperator, - operatorSetId: allocations[0].operatorSets[0].operatorSetId, - strategies: _strategyMockArray(), + operatorSetId: defaultOperatorSet.id, wadToSlash: 25e16, description: "test" }); - avsDirectoryMock.setIsOperatorSlashable(slashingParams.operator, defaultAVS, slashingParams.operatorSetId, true); + uint64 expectedEncumberedMagnitude = 375e15; // 25e16 is slashed. 5e17 was previously uint64 magnitudeAfterSlash = 375e15; uint64 maxMagnitudeAfterSlash = 875e15; // Operator can only allocate up to 75e16 magnitude since 25% is slashed // Slash Operator, only emit events assuming that there is no deallocation - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit EncumberedMagnitudeUpdated(defaultOperator, strategyMock, expectedEncumberedMagnitude); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit OperatorSetMagnitudeUpdated(defaultOperator, allocations[0].operatorSets[0], strategyMock, magnitudeAfterSlash, uint32(block.timestamp)); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit MaxMagnitudeUpdated(defaultOperator, strategyMock, maxMagnitudeAfterSlash); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - uint256[] memory wadSlashed = new uint256[](1); - wadSlashed[0] = 125e15; - emit OperatorSlashed(slashingParams.operator, _operatorSet(defaultAVS, slashingParams.operatorSetId), slashingParams.strategies, wadSlashed, slashingParams.description); cheats.prank(defaultAVS); allocationManager.slashOperator(slashingParams); // Check storage post slash - assertEq(expectedEncumberedMagnitude, allocationManager.encumberedMagnitude(defaultOperator, strategyMock), "encumberedMagnitude not updated"); - assertEq(maxMagnitudeAfterSlash, allocationManager.getMaxMagnitudes(defaultOperator, _strategyMockArray())[0], "maxMagnitude not updated"); - mInfos = allocationManager.getAllocationInfo(defaultOperator, strategyMock, allocations[0].operatorSets); - assertEq(magnitudeAfterSlash, mInfos[0].currentMagnitude, "currentMagnitude not updated"); - assertEq(0, mInfos[0].pendingDiff, "pendingDiff should be 0 after slash"); - assertEq(0, mInfos[0].effectTimestamp, "effectTimestamp should be 0"); + assertEq( + expectedEncumberedMagnitude, + allocationManager.encumberedMagnitude(defaultOperator, strategyMock), + "encumberedMagnitude not updated" + ); + assertEq( + maxMagnitudeAfterSlash, + allocationManager.getMaxMagnitudes(defaultOperator, defaultStrategies)[0], + "maxMagnitude not updated" + ); + allocation = allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); + assertEq(magnitudeAfterSlash, allocation.currentMagnitude, "currentMagnitude not updated"); + assertEq(0, allocation.pendingDiff, "pendingDiff should be 0 after slash"); + assertEq(0, allocation.effectBlock, "effectBlock should be 0"); uint64 allocatableMagnitudeAfterSlash = allocationManager.getAllocatableMagnitude(defaultOperator, strategyMock); // Check storage after complete modification. Expect encumberedMag to be emitted again - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit EncumberedMagnitudeUpdated(defaultOperator, strategyMock, expectedEncumberedMagnitude); - allocationManager.clearDeallocationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); - mInfos = allocationManager.getAllocationInfo(defaultOperator, strategyMock, allocations[0].operatorSets); - assertEq(allocatableMagnitudeAfterSlash, allocationManager.getAllocatableMagnitude(defaultOperator, strategyMock), "allocatable mag after slash shoudl be equal to allocatable mag after clearing queue"); - } - - /** - * Allocates to multiple operatorSets for a strategy. Only slashes from one operatorSet. Validates - * 1. The slashable shares of each operatorSet after magnitude allocation - * 2. The first operatorSet has less slashable shares post slash - * 3. The second operatorSet has the same number slashable shares post slash - * 4. The PROPORTION that is slashable for opSet 2 has increased - * 5. Encumbered magnitude, total allocatable magnitude - */ - function test_allocateMultipleOpsets_slashSingleOpset() public { - // Set 100e18 shares for operator in DM - uint256 operatorShares = 100e18; - delegationManagerMock.setOperatorShares(defaultOperator, strategyMock, operatorShares); - uint64 magnitudeToAllocate = 4e17; - - // Allocate 40% to firstOperatorSet, 40% to secondOperatorSet - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = new IAllocationManagerTypes.MagnitudeAllocation[](2); - allocations[0] = _generateMagnitudeAllocationCalldataForOpSet(defaultAVS, 1, magnitudeToAllocate, 1e18)[0]; - allocations[1] = _generateMagnitudeAllocationCalldataForOpSet(defaultAVS, 2, magnitudeToAllocate, 1e18)[0]; - cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); - cheats.warp(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY); - - // Get slashable shares for each operatorSet - address[] memory operatorArray = new address[](1); - operatorArray[0] = defaultOperator; - (, uint256[][] memory slashableSharesOpset1_preSlash) = allocationManager.getMinDelegatedAndSlashableOperatorSharesBefore( - _operatorSet(defaultAVS, 1), - operatorArray, - _strategyMockArray(), - uint32(block.timestamp + 1) - ); - (, uint256[][] memory slashableSharesOpset2_preSlash) = allocationManager.getMinDelegatedAndSlashableOperatorSharesBefore( - _operatorSet(defaultAVS, 2), - operatorArray, - _strategyMockArray(), - uint32(block.timestamp + 1) - ); - assertEq(40e18, slashableSharesOpset1_preSlash[0][0], "slashableShares of opSet_1 should be 40e18"); - assertEq(40e18, slashableSharesOpset2_preSlash[0][0], "slashableShares of opSet_2 should be 40e18"); - uint256 maxMagnitude = allocationManager.getMaxMagnitudes(defaultOperator, _strategyMockArray())[0]; - uint256 opSet2PortionOfMaxMagnitude = uint256(magnitudeToAllocate) * 1e18 / maxMagnitude; - - // Slash operator on operatorSet1 for 50% - SlashingParams memory slashingParams = SlashingParams({ - operator: defaultOperator, - operatorSetId: allocations[0].operatorSets[0].operatorSetId, - strategies: _strategyMockArray(), - wadToSlash: 5e17, - description: "test" - }); - avsDirectoryMock.setIsOperatorSlashable(slashingParams.operator, defaultAVS, slashingParams.operatorSetId, true); - - // Slash Operator - cheats.prank(defaultAVS); - allocationManager.slashOperator(slashingParams); - - // Operator should now have 80e18 shares, since half of 40e18 was slashed - delegationManagerMock.setOperatorShares(defaultOperator, strategyMock, 80e18); - - // Check storage - (, uint256[][] memory slashableSharesOpset1_postSlash) = allocationManager.getMinDelegatedAndSlashableOperatorSharesBefore( - _operatorSet(defaultAVS, 1), - operatorArray, - _strategyMockArray(), - uint32(block.timestamp + 1) - ); - (, uint256[][] memory slashableSharesOpset2_postSlash) = allocationManager.getMinDelegatedAndSlashableOperatorSharesBefore( - _operatorSet(defaultAVS, 2), - operatorArray, - _strategyMockArray(), - uint32(block.timestamp + 1) + allocationManager.clearDeallocationQueue(defaultOperator, defaultStrategies, _maxNumToClear()); + allocation = allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); + assertEq( + allocatableMagnitudeAfterSlash, + allocationManager.getAllocatableMagnitude(defaultOperator, strategyMock), + "allocatable mag after slash shoudl be equal to allocatable mag after clearing queue" ); - - assertEq(20e18, slashableSharesOpset1_postSlash[0][0], "slashableShares of opSet_1 should be 20e18"); - assertEq(slashableSharesOpset2_preSlash[0][0], slashableSharesOpset2_postSlash[0][0], "slashableShares of opSet_2 should remain unchanged"); - - // Validate encumbered and total allocatable magnitude - uint256 maxMagnitudeAfterSlash = allocationManager.getMaxMagnitudes(defaultOperator, _strategyMockArray())[0]; - uint256 expectedEncumberedMagnitude = 6e17; // 4e17 from opSet2, 2e17 from opSet1 - assertEq(expectedEncumberedMagnitude, allocationManager.encumberedMagnitude(defaultOperator, strategyMock), "encumberedMagnitude not updated"); - assertEq(maxMagnitudeAfterSlash - expectedEncumberedMagnitude, allocationManager.getAllocatableMagnitude(defaultOperator, strategyMock), "allocatableMagnitude should be diff of maxMagnitude and encumberedMagnitude"); - - // Check proportion after slash - uint256 opSet2PortionOfMaxMagnitudeAfterSlash = uint256(magnitudeToAllocate) * 1e18 / maxMagnitudeAfterSlash; - assertGt(opSet2PortionOfMaxMagnitudeAfterSlash, opSet2PortionOfMaxMagnitude, "opSet2 should have a greater proportion to slash from previous"); } + // /** + // * Allocates to multiple operatorSets for a strategy. Only slashes from one operatorSet. Validates + // * 1. The slashable shares of each operatorSet after magnitude allocation + // * 2. The first operatorSet has less slashable shares post slash + // * 3. The second operatorSet has the same number slashable shares post slash + // * 4. The PROPORTION that is slashable for opSet 2 has increased + // * 5. Encumbered magnitude, total allocatable magnitude + // */ + // function test_allocateMultipleOpsets_slashSingleOpset() public { + // // Set 100e18 shares for operator in DM + // uint256 operatorShares = 100e18; + // delegationManagerMock.setOperatorShares(defaultOperator, strategyMock, operatorShares); + // uint64 magnitudeToAllocate = 4e17; + + // // Allocate 40% to firstOperatorSet, 40% to secondOperatorSet + // AllocateParams[] memory allocateParams = new AllocateParams[](2); + // allocateParams[0] = _newAllocateParams_SingleMockStrategy(OperatorSet(defaultAVS, 1), magnitudeToAllocate)[0]; + // allocateParams[1] = _newAllocateParams_SingleMockStrategy(OperatorSet(defaultAVS, 2), magnitudeToAllocate)[0]; + // cheats.prank(defaultOperator); + // allocationManager.modifyAllocations(allocateParams); + // cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); + + // // Get slashable shares for each operatorSet + // address[] memory operatorArray = new address[](1); + // operatorArray[0] = defaultOperator; + // (, uint256[][] memory slashableSharesOpset1_preSlash) = allocationManager + // .getMinDelegatedAndSlashableOperatorSharesBefore( + // OperatorSet(defaultAVS, 1), operatorArray, defaultStrategies, uint32(block.number + 1) + // ); + // (, uint256[][] memory slashableSharesOpset2_preSlash) = allocationManager + // .getMinDelegatedAndSlashableOperatorSharesBefore( + // OperatorSet(defaultAVS, 2), operatorArray, defaultStrategies, uint32(block.number + 1) + // ); + // assertEq(40e18, slashableSharesOpset1_preSlash[0][0], "slashableShares of opSet_1 should be 40e18"); + // assertEq(40e18, slashableSharesOpset2_preSlash[0][0], "slashableShares of opSet_2 should be 40e18"); + // uint256 maxMagnitude = allocationManager.getMaxMagnitudes(defaultOperator, defaultStrategies)[0]; + // uint256 opSet2PortionOfMaxMagnitude = uint256(magnitudeToAllocate) * WAD / maxMagnitude; + + // // Slash operator on operatorSet1 for 50% + // SlashingParams memory slashingParams = SlashingParams({ + // operator: defaultOperator, + // operatorSetId: allocateParams[0].operatorSet.id, + // wadToSlash: 5e17, + // description: "test" + // }); + // // avsDirectoryMock.setIsOperatorSlashable(slashingParams.operator, defaultAVS, slashingParams.operatorSetId, true); + + // // Slash Operator + // cheats.prank(defaultAVS); + // allocationManager.slashOperator(slashingParams); + + // // Operator should now have 80e18 shares, since half of 40e18 was slashed + // delegationManagerMock.setOperatorShares(defaultOperator, strategyMock, 80e18); + + // // Check storage + // (, uint256[][] memory slashableSharesOpset1_postSlash) = allocationManager + // .getMinDelegatedAndSlashableOperatorSharesBefore( + // OperatorSet(defaultAVS, 1), operatorArray, defaultStrategies, uint32(block.number + 1) + // ); + // (, uint256[][] memory slashableSharesOpset2_postSlash) = allocationManager + // .getMinDelegatedAndSlashableOperatorSharesBefore( + // OperatorSet(defaultAVS, 2), operatorArray, defaultStrategies, uint32(block.number + 1) + // ); + + // assertEq(20e18, slashableSharesOpset1_postSlash[0][0], "slashableShares of opSet_1 should be 20e18"); + // assertEq( + // slashableSharesOpset2_preSlash[0][0], + // slashableSharesOpset2_postSlash[0][0], + // "slashableShares of opSet_2 should remain unchanged" + // ); + + // // Validate encumbered and total allocatable magnitude + // uint256 maxMagnitudeAfterSlash = allocationManager.getMaxMagnitudes(defaultOperator, defaultStrategies)[0]; + // uint256 expectedEncumberedMagnitude = 6e17; // 4e17 from opSet2, 2e17 from opSet1 + // assertEq( + // expectedEncumberedMagnitude, + // allocationManager.encumberedMagnitude(defaultOperator, strategyMock), + // "encumberedMagnitude not updated" + // ); + // assertEq( + // maxMagnitudeAfterSlash - expectedEncumberedMagnitude, + // allocationManager.getAllocatableMagnitude(defaultOperator, strategyMock), + // "allocatableMagnitude should be diff of maxMagnitude and encumberedMagnitude" + // ); + + // // Check proportion after slash + // uint256 opSet2PortionOfMaxMagnitudeAfterSlash = uint256(magnitudeToAllocate) * WAD / maxMagnitudeAfterSlash; + // assertGt( + // opSet2PortionOfMaxMagnitudeAfterSlash, + // opSet2PortionOfMaxMagnitude, + // "opSet2 should have a greater proportion to slash from previous" + // ); + // } + /** * Allocates to multiple strategies for the given operatorSetKey. Slashes from both strategies Validates a slash propogates to both strategies. * Validates that @@ -1222,67 +1116,61 @@ contract AllocationManagerUnitTests_SlashOperator is AllocationManagerUnitTests function test_allocateMultipleStrategies_slashMultiple() public { // Allocate to each strategy uint64 strategy1Magnitude = 5e17; - uint64 strategy2Magnitude = 1e18; - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = new IAllocationManagerTypes.MagnitudeAllocation[](2); - allocations[0] = _generateMagnitudeAllocationCalldata_opSetAndStrategy(defaultAVS, strategyMock, 1, strategy1Magnitude, 1e18)[0]; - allocations[1] = _generateMagnitudeAllocationCalldata_opSetAndStrategy(defaultAVS, strategyMock2, 1, strategy2Magnitude, 1e18)[0]; - cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); - cheats.warp(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY); + uint64 strategy2Magnitude = WAD; + + OperatorSet memory operatorSet = _newOperatorSet_MultipleStrategies(defaultAVS, 2); + _registerForOperatorSet(defaultOperator, operatorSet); + IStrategy[] memory strategies = allocationManager.getStrategiesInOperatorSet(operatorSet); + AllocateParams memory allocateParams = + AllocateParams({operatorSet: operatorSet, strategies: strategies, newMagnitudes: new uint64[](2)}); + allocateParams.newMagnitudes[0] = strategy1Magnitude; + allocateParams.newMagnitudes[1] = strategy2Magnitude; + + cheats.prank(defaultOperator); + allocationManager.modifyAllocations(allocateParams.toArray()); + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); // Slash operator on both strategies for 60% - IStrategy[] memory strategiesToSlash = new IStrategy[](2); - strategiesToSlash[0] = strategyMock; - strategiesToSlash[1] = strategyMock2; SlashingParams memory slashingParams = SlashingParams({ operator: defaultOperator, - operatorSetId: allocations[0].operatorSets[0].operatorSetId, - strategies: strategiesToSlash, + operatorSetId: operatorSet.id, wadToSlash: 6e17, description: "test" }); - avsDirectoryMock.setIsOperatorSlashable(slashingParams.operator, defaultAVS, slashingParams.operatorSetId, true); uint64[] memory expectedEncumberedMags = new uint64[](2); expectedEncumberedMags[0] = 2e17; // 60% of 5e17 - expectedEncumberedMags[1] = 4e17; // 60% of 1e18 + expectedEncumberedMags[1] = 4e17; // 60% of WAD uint64[] memory expectedMagnitudeAfterSlash = new uint64[](2); expectedMagnitudeAfterSlash[0] = 2e17; - expectedMagnitudeAfterSlash[1] = 4e17; + expectedMagnitudeAfterSlash[1] = 4e17; uint64[] memory expectedMaxMagnitudeAfterSlash = new uint64[](2); expectedMaxMagnitudeAfterSlash[0] = 7e17; expectedMaxMagnitudeAfterSlash[1] = 4e17; - // Expect emits - for(uint256 i = 0; i < strategiesToSlash.length; i++) { - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit EncumberedMagnitudeUpdated(defaultOperator, strategiesToSlash[i], expectedEncumberedMags[i]); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit OperatorSetMagnitudeUpdated(defaultOperator, _operatorSet(defaultAVS, slashingParams.operatorSetId), strategiesToSlash[i], expectedMagnitudeAfterSlash[i], uint32(block.timestamp)); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit MaxMagnitudeUpdated(defaultOperator, strategiesToSlash[i], expectedMaxMagnitudeAfterSlash[i]); - } - uint256[] memory wadSlashed = new uint256[](2); - wadSlashed[0] = 3e17; - wadSlashed[1] = 6e17; - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit OperatorSlashed(slashingParams.operator, _operatorSet(defaultAVS, slashingParams.operatorSetId), slashingParams.strategies, wadSlashed, slashingParams.description); - // Slash Operator cheats.prank(defaultAVS); allocationManager.slashOperator(slashingParams); // Check storage - for(uint256 i = 0; i < strategiesToSlash.length; i++) { - assertEq(expectedEncumberedMags[i], allocationManager.encumberedMagnitude(defaultOperator, strategiesToSlash[i]), "encumberedMagnitude not updated"); - assertEq(expectedMaxMagnitudeAfterSlash[i] - expectedMagnitudeAfterSlash[i], allocationManager.getAllocatableMagnitude(defaultOperator, strategiesToSlash[i]), "allocatableMagnitude not updated"); - MagnitudeInfo[] memory mInfos = allocationManager.getAllocationInfo(defaultOperator, strategiesToSlash[i], allocations[0].operatorSets); - assertEq(expectedMagnitudeAfterSlash[i], mInfos[0].currentMagnitude, "currentMagnitude not updated"); - assertEq(0, mInfos[0].pendingDiff, "pendingDiff should be 0"); - assertEq(0, mInfos[0].effectTimestamp, "effectTimestamp should be 0"); + for (uint256 i = 0; i < strategies.length; ++i) { + assertEq( + expectedEncumberedMags[i], + allocationManager.encumberedMagnitude(defaultOperator, strategies[i]), + "encumberedMagnitude not updated" + ); + assertEq( + expectedMaxMagnitudeAfterSlash[i] - expectedMagnitudeAfterSlash[i], + allocationManager.getAllocatableMagnitude(defaultOperator, strategies[i]), + "allocatableMagnitude not updated" + ); + Allocation memory allocation = allocationManager.getAllocation(defaultOperator, operatorSet, strategies[i]); + assertEq(expectedMagnitudeAfterSlash[i], allocation.currentMagnitude, "currentMagnitude not updated"); + assertEq(0, allocation.pendingDiff, "pendingDiff should be 0"); + assertEq(0, allocation.effectBlock, "effectBlock should be 0"); } } @@ -1290,10 +1178,12 @@ contract AllocationManagerUnitTests_SlashOperator is AllocationManagerUnitTests * Allocates magnitude. Deallocates some. Slashes a portion, and then allocates up to the max available magnitude * TODO: Fuzz the wadsToSlash */ - function testFuzz_allocate_deallocate_slashWhilePending_allocateMax(uint256 r) public { + function testFuzz_allocate_deallocate_slashWhilePending_allocateMax( + Randomness r + ) public rand(r) { // Bound allocation and deallocation - uint64 firstMod = uint64(bound(r, 3, 1e18)); - uint64 secondMod = uint64(bound(r, 1, firstMod - 2)); + uint64 firstMod = uint64(r.Uint256(3, WAD)); + uint64 secondMod = uint64(r.Uint256(1, firstMod - 2)); // TODO: remove these assumptions around even numbers if (firstMod % 2 != 0) { @@ -1305,77 +1195,98 @@ contract AllocationManagerUnitTests_SlashOperator is AllocationManagerUnitTests uint64 pendingDiff = firstMod - secondMod; // Allocate magnitude - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = _generateMagnitudeAllocationCalldata(defaultAVS, firstMod, 1e18); + AllocateParams[] memory allocateParams = _newAllocateParams(defaultOperatorSet, firstMod); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); - cheats.warp(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY); + allocationManager.modifyAllocations(allocateParams); + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); // Deallocate magnitude - IAllocationManagerTypes.MagnitudeAllocation[] memory deallocations = _generateMagnitudeAllocationCalldata(defaultAVS, secondMod, 1e18); + AllocateParams[] memory deallocateParams = _newAllocateParams(defaultOperatorSet, secondMod); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(deallocations); - uint32 deallocationEffectTimestamp = uint32(block.timestamp + DEALLOCATION_DELAY); + allocationManager.modifyAllocations(deallocateParams); + uint32 deallocationEffectBlock = uint32(block.number + DEALLOCATION_DELAY); - // Slash operator for 50% + // Slash operator for 50% SlashingParams memory slashingParams = SlashingParams({ operator: defaultOperator, - operatorSetId: allocations[0].operatorSets[0].operatorSetId, - strategies: _strategyMockArray(), + operatorSetId: defaultOperatorSet.id, wadToSlash: 5e17, description: "test" }); - avsDirectoryMock.setIsOperatorSlashable(slashingParams.operator, defaultAVS, slashingParams.operatorSetId, true); + // avsDirectoryMock.setIsOperatorSlashable(slashingParams.operator, defaultAVS, slashingParams.operatorSetId, true); // Slash Operator cheats.prank(defaultAVS); allocationManager.slashOperator(slashingParams); // Check storage post slash - assertEq(firstMod / 2, allocationManager.encumberedMagnitude(defaultOperator, strategyMock), "encumberedMagnitude should be half of firstMod"); - MagnitudeInfo[] memory mInfos = allocationManager.getAllocationInfo(defaultOperator, strategyMock, allocations[0].operatorSets); - assertEq(firstMod / 2, mInfos[0].currentMagnitude, "currentMagnitude should be half of firstMod"); - console.log("value of pendingDiff: ", pendingDiff - pendingDiff/2); - assertEq(-int128(uint128(pendingDiff - pendingDiff/2)), mInfos[0].pendingDiff, "pendingDiff should be -secondMod"); - assertEq(deallocationEffectTimestamp, mInfos[0].effectTimestamp, "effectTimestamp should be deallocationEffectTimestamp"); - - // Warp to deallocation effect timestamp & clear deallocation queue + assertEq( + firstMod / 2, + allocationManager.encumberedMagnitude(defaultOperator, strategyMock), + "encumberedMagnitude should be half of firstMod" + ); + Allocation memory allocation = + allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); + assertEq(firstMod / 2, allocation.currentMagnitude, "currentMagnitude should be half of firstMod"); + console.log("value of pendingDiff: ", pendingDiff - pendingDiff / 2); + assertEq( + -int128(uint128(pendingDiff - pendingDiff / 2)), allocation.pendingDiff, "pendingDiff should be -secondMod" + ); + assertEq(deallocationEffectBlock, allocation.effectBlock, "effectBlock should be deallocationEffectBlock"); + + // Warp to deallocation effect block & clear deallocation queue console.log("encumbered mag before: ", allocationManager.encumberedMagnitude(defaultOperator, strategyMock)); - cheats.warp(deallocationEffectTimestamp); - allocationManager.clearDeallocationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); + cheats.roll(deallocationEffectBlock); + allocationManager.clearDeallocationQueue(defaultOperator, defaultStrategies, _maxNumToClear()); console.log("encumbered mag after: ", allocationManager.encumberedMagnitude(defaultOperator, strategyMock)); // Check expected max and allocatable - uint64 expectedMaxMagnitude = 1e18 - firstMod / 2; - assertEq(expectedMaxMagnitude, allocationManager.getMaxMagnitudes(defaultOperator, _strategyMockArray())[0], "maxMagnitude should be expectedMaxMagnitude"); - // Allocatable is expectedMax - currentMagPostSlashing - pendingDiffOfDeallocations post slashing + uint64 expectedMaxMagnitude = WAD - firstMod / 2; + assertEq( + expectedMaxMagnitude, + allocationManager.getMaxMagnitudes(defaultOperator, defaultStrategies)[0], + "maxMagnitude should be expectedMaxMagnitude" + ); + // Allocatable is expectedMax - currentMagPostSlashing - pendingDiffOfDeallocateParams post slashing uint64 expectedAllocatable = expectedMaxMagnitude - ((firstMod / 2) - (pendingDiff - pendingDiff / 2)); - assertEq(expectedAllocatable, allocationManager.getAllocatableMagnitude(defaultOperator, strategyMock), "allocatableMagnitude should be expectedAllocatable"); + assertEq( + expectedAllocatable, + allocationManager.getAllocatableMagnitude(defaultOperator, strategyMock), + "allocatableMagnitude should be expectedAllocatable" + ); // Allocate up to max magnitude - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations2 = _generateMagnitudeAllocationCalldata(defaultAVS, expectedMaxMagnitude, expectedMaxMagnitude); + AllocateParams[] memory allocateParams2 = _newAllocateParams(defaultOperatorSet, expectedMaxMagnitude); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations2); + allocationManager.modifyAllocations(allocateParams2); // Assert that encumbered is expectedMaxMagnitude - assertEq(0, allocationManager.getAllocatableMagnitude(defaultOperator, strategyMock), "allocatableMagnitude should be 0"); + assertEq( + 0, + allocationManager.getAllocatableMagnitude(defaultOperator, strategyMock), + "allocatableMagnitude should be 0" + ); } } contract AllocationManagerUnitTests_ModifyAllocations is AllocationManagerUnitTests { + using SingleItemArrayLib for *; + using OperatorSetLib for *; + /// ----------------------------------------------------------------------- /// modifyAllocations() /// ----------------------------------------------------------------------- function test_revert_paused() public { allocationManager.pause(2 ** PAUSED_MODIFY_ALLOCATIONS); cheats.expectRevert(IPausable.CurrentlyPaused.selector); - allocationManager.modifyAllocations(new IAllocationManagerTypes.MagnitudeAllocation[](0)); + allocationManager.modifyAllocations(new AllocateParams[](0)); } function test_revert_allocationDelayNotSet() public { address invalidOperator = address(0x2); cheats.prank(invalidOperator); - cheats.expectRevert(IAllocationManagerErrors.UninitializedAllocationDelay.selector); - allocationManager.modifyAllocations(new IAllocationManagerTypes.MagnitudeAllocation[](0)); + cheats.expectRevert(UninitializedAllocationDelay.selector); + allocationManager.modifyAllocations(new AllocateParams[](0)); } function test_revert_allocationDelayNotInEffect() public { @@ -1386,357 +1297,381 @@ contract AllocationManagerUnitTests_ModifyAllocations is AllocationManagerUnitTe allocationManager.setAllocationDelay(5); // even though the operator has an allocation delay set, it is not in effect // and modifyAllocations should still be blocked - cheats.expectRevert(IAllocationManagerErrors.UninitializedAllocationDelay.selector); - allocationManager.modifyAllocations(new IAllocationManagerTypes.MagnitudeAllocation[](0)); + cheats.expectRevert(UninitializedAllocationDelay.selector); + allocationManager.modifyAllocations(new AllocateParams[](0)); } function test_revert_lengthMismatch() public { - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - _randomMagnitudeAllocation_singleStrat_singleOpSet(0, 0); - allocations[0].operatorSets = new OperatorSet[](0); + AllocateParams[] memory allocateParams = _randAllocateParams_DefaultOpSet(); + allocateParams[0].newMagnitudes = new uint64[](0); - cheats.expectRevert(IAllocationManagerErrors.InputArrayLengthMismatch.selector); + cheats.expectRevert(InputArrayLengthMismatch.selector); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); + allocationManager.modifyAllocations(allocateParams); } function test_revert_invalidOperatorSet() public { - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - _randomMagnitudeAllocation_singleStrat_singleOpSet(0, 0); + AllocateParams[] memory allocateParams = AllocateParams({ + operatorSet: OperatorSet(random().Address(), 0), + strategies: defaultStrategies, + newMagnitudes: uint64(0.5 ether).toArrayU64() + }).toArray(); - // Set operatorSet to being invalid - avsDirectoryMock.setIsOperatorSetBatch(allocations[0].operatorSets, false); - - cheats.expectRevert(IAllocationManagerErrors.InvalidOperatorSet.selector); - cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); - } - - function test_revert_invalidExpectedMaxMagnitude() public { - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - _randomMagnitudeAllocation_singleStrat_singleOpSet(0, 0); - allocations[0].expectedMaxMagnitude = 1e18 + 1; - - cheats.expectRevert(IAllocationManagerErrors.InvalidExpectedMaxMagnitude.selector); + cheats.expectRevert(InvalidOperatorSet.selector); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); + allocationManager.modifyAllocations(allocateParams); } function test_revert_multiAlloc_modificationAlreadyPending_diffTx() public { // Allocate magnitude - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - _randomMagnitudeAllocation_singleStrat_singleOpSet(0, 0); + AllocateParams[] memory allocateParams = _randAllocateParams_DefaultOpSet(); cheats.startPrank(defaultOperator); - allocationManager.modifyAllocations(allocations); + allocationManager.modifyAllocations(allocateParams); - // Warp to just before allocation complete timestamp - cheats.warp(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY - 1); + // Warp to just before allocation complete block + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY - 1); // Attempt to allocate magnitude again - cheats.expectRevert(IAllocationManagerErrors.ModificationAlreadyPending.selector); - allocationManager.modifyAllocations(allocations); + cheats.expectRevert(ModificationAlreadyPending.selector); + allocationManager.modifyAllocations(allocateParams); cheats.stopPrank(); } function test_revert_multiAlloc_modificationAlreadyPending_sameTx() public { // Allocate magnitude - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - new IAllocationManagerTypes.MagnitudeAllocation[](2); - allocations[0] = _randomMagnitudeAllocation_singleStrat_singleOpSet(0, 0)[0]; - allocations[1] = allocations[0]; + AllocateParams[] memory allocateParams = new AllocateParams[](2); + allocateParams[0] = _randAllocateParams_DefaultOpSet()[0]; + allocateParams[1] = allocateParams[0]; - cheats.expectRevert(IAllocationManagerErrors.ModificationAlreadyPending.selector); + cheats.expectRevert(ModificationAlreadyPending.selector); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); + allocationManager.modifyAllocations(allocateParams); } function test_revert_allocateZeroMagnitude() public { // Allocate exact same magnitude as initial allocation (0) - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - _randomMagnitudeAllocation_singleStrat_singleOpSet(0, 0); - allocations[0].magnitudes[0] = 0; + AllocateParams[] memory allocateParams = _randAllocateParams_DefaultOpSet(); + allocateParams[0].newMagnitudes[0] = 0; - cheats.expectRevert(IAllocationManagerErrors.SameMagnitude.selector); + cheats.expectRevert(SameMagnitude.selector); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); + allocationManager.modifyAllocations(allocateParams); } function test_revert_allocateSameMagnitude() public { // Allocate nonzero magnitude - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - _randomMagnitudeAllocation_singleStrat_singleOpSet(0, 0); + AllocateParams[] memory allocateParams = _randAllocateParams_DefaultOpSet(); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); + allocationManager.modifyAllocations(allocateParams); - // Warp to allocation complete timestamp - cheats.warp(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY); + // Warp to allocation complete block + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); // Attempt to allocate no magnitude (ie. same magnitude) - cheats.expectRevert(IAllocationManagerErrors.SameMagnitude.selector); + cheats.expectRevert(SameMagnitude.selector); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); + allocationManager.modifyAllocations(allocateParams); } - function testFuzz_revert_insufficientAllocatableMagnitude(uint256 r) public { + function testFuzz_revert_insufficientAllocatableMagnitude( + Randomness r + ) public rand(r) { // Allocate some magnitude - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - _randomMagnitudeAllocation_singleStrat_singleOpSet(r, 0); + AllocateParams[] memory allocateParams = _randAllocateParams_DefaultOpSet(); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); + allocationManager.modifyAllocations(allocateParams); - // Warp to allocation complete timestamp - cheats.warp(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY); + // Warp to allocation complete block + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); // Attempt to allocate more magnitude than the operator has - uint64 allocatedMag = allocations[0].magnitudes[0]; - allocations[0].magnitudes[0] = 1e18 + 1; - cheats.expectRevert(IAllocationManagerErrors.InsufficientAllocatableMagnitude.selector); + // uint64 allocatedMag = allocateParams[0].newMagnitudes[0]; + allocateParams[0].newMagnitudes[0] = WAD + 1; + cheats.expectRevert(InsufficientMagnitude.selector); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); + allocationManager.modifyAllocations(allocateParams); } - function testFuzz_allocate_singleStrat_singleOperatorSet(uint256 r) public { + function testFuzz_allocate_singleStrat_singleOperatorSet( + Randomness r + ) public rand(r) { // Create allocation - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - _randomMagnitudeAllocation_singleStrat_singleOpSet(r, 0); + AllocateParams[] memory allocateParams = _randAllocateParams_DefaultOpSet(); // Save vars to check against - IStrategy strategy = allocations[0].strategy; - uint64 magnitude = allocations[0].magnitudes[0]; - uint32 effectTimestamp = uint32(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY); + uint64 magnitude = allocateParams[0].newMagnitudes[0]; + uint32 effectBlock = uint32(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); - // Expect emits - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit EncumberedMagnitudeUpdated(defaultOperator, strategy, magnitude); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit OperatorSetMagnitudeUpdated( - defaultOperator, allocations[0].operatorSets[0], strategy, magnitude, effectTimestamp - ); + // Check that the operator has no allocated sets/strats before allocation + OperatorSet[] memory allocatedSets = allocationManager.getAllocatedSets(defaultOperator); + IStrategy[] memory allocatedStrats = + allocationManager.getAllocatedStrategies(defaultOperator, defaultOperatorSet); + assertEq(allocatedSets.length, 0, "should not have any allocated sets before allocation"); + assertEq(allocatedStrats.length, 0, "should not have any allocated strats before allocation"); // Allocate magnitude cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); + allocationManager.modifyAllocations(allocateParams); // Check storage + + allocatedSets = allocationManager.getAllocatedSets(defaultOperator); + allocatedStrats = allocationManager.getAllocatedStrategies(defaultOperator, defaultOperatorSet); + assertEq(allocatedSets.length, 1, "should have a single allocated set"); + assertEq(allocatedSets[0].key(), defaultOperatorSet.key(), "should be allocated to default set"); + assertEq(allocatedStrats.length, 1, "should have a single allocated strategy to default set"); + assertEq(address(allocatedStrats[0]), address(strategyMock), "should have allocated default strat"); + assertEq( magnitude, - allocationManager.encumberedMagnitude(defaultOperator, strategy), + allocationManager.encumberedMagnitude(defaultOperator, strategyMock), "encumberedMagnitude not updated" ); assertEq( WAD - magnitude, - allocationManager.getAllocatableMagnitude(defaultOperator, strategy), + allocationManager.getAllocatableMagnitude(defaultOperator, strategyMock), "allocatableMagnitude not calcualted correctly" ); - MagnitudeInfo[] memory mInfos = - allocationManager.getAllocationInfo(defaultOperator, strategy, allocations[0].operatorSets); - assertEq(0, mInfos[0].currentMagnitude, "currentMagnitude should not be updated"); - assertEq(int128(uint128(magnitude)), mInfos[0].pendingDiff, "pendingMagnitude not updated"); - assertEq(effectTimestamp, mInfos[0].effectTimestamp, "effectTimestamp not updated"); + Allocation memory allocation = + allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); + assertEq(0, allocation.currentMagnitude, "currentMagnitude should not be updated"); + assertEq(int128(uint128(magnitude)), allocation.pendingDiff, "pendingMagnitude not updated"); + assertEq(effectBlock, allocation.effectBlock, "effectBlock not updated"); - // Check storage after warp to completion - cheats.warp(effectTimestamp); - mInfos = allocationManager.getAllocationInfo(defaultOperator, strategy, allocations[0].operatorSets); - assertEq(magnitude, mInfos[0].currentMagnitude, "currentMagnitude not updated"); - assertEq(0, mInfos[0].pendingDiff, "pendingMagnitude not updated"); - assertEq(0, mInfos[0].effectTimestamp, "effectTimestamp not updated"); + // Check storage after roll to completion + cheats.roll(effectBlock); + allocation = allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); + assertEq(magnitude, allocation.currentMagnitude, "currentMagnitude not updated"); + assertEq(0, allocation.pendingDiff, "pendingMagnitude not updated"); + assertEq(0, allocation.effectBlock, "effectBlock not updated"); } - function testFuzz_allocate_singleStrat_multipleSets(uint256 r) public { - uint8 numOpSets = uint8(bound(r, 1, type(uint8).max)); + function testFuzz_allocate_singleStrat_multipleSets( + Randomness r + ) public rand(r) { + uint8 numOpSets = uint8(r.Uint256(1, type(uint8).max)); - MagnitudeAllocation[] memory allocations = - _randomMagnitudeAllocation_singleStrat_multipleOpSets(r, 0, numOpSets); + // Create and register for operator sets, each with a single default strategy + OperatorSet[] memory operatorSets = _newOperatorSets_SingleMockStrategy(defaultAVS, numOpSets); + _registerForOperatorSets(defaultOperator, operatorSets); - // Save vars to check against - IStrategy strategy = allocations[0].strategy; - uint64[] memory magnitudes = allocations[0].magnitudes; - uint32 effectTimestamp = uint32(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY); + // Get a random allocation for the operator sets + AllocateParams[] memory allocateParams = _randAllocateParams_SingleMockStrategy(operatorSets); - // Expect emits + // Save vars to check against + uint32 effectBlock = uint32(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); uint64 usedMagnitude; - for (uint256 i = 0; i < numOpSets; i++) { - usedMagnitude += magnitudes[i]; - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit EncumberedMagnitudeUpdated(defaultOperator, strategy, usedMagnitude); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit OperatorSetMagnitudeUpdated( - defaultOperator, allocations[0].operatorSets[i], strategy, magnitudes[i], effectTimestamp - ); + for (uint256 i = 0; i < allocateParams.length; ++i) { + usedMagnitude += allocateParams[i].newMagnitudes[0]; } + + // Check that the operator has no allocated sets/strats before allocation + OperatorSet[] memory allocatedSets = allocationManager.getAllocatedSets(defaultOperator); + IStrategy[] memory allocatedStrats = + allocationManager.getAllocatedStrategies(defaultOperator, defaultOperatorSet); + assertEq(allocatedSets.length, 0, "should not have any allocated sets before allocation"); + assertEq(allocatedStrats.length, 0, "should not have any allocated strats before allocation"); + cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); + allocationManager.modifyAllocations(allocateParams); // Check storage assertEq( usedMagnitude, - allocationManager.encumberedMagnitude(defaultOperator, strategy), + allocationManager.encumberedMagnitude(defaultOperator, strategyMock), "encumberedMagnitude not updated" ); assertEq( WAD - usedMagnitude, - allocationManager.getAllocatableMagnitude(defaultOperator, strategy), + allocationManager.getAllocatableMagnitude(defaultOperator, strategyMock), "allocatableMagnitude not calcualted correctly" ); - MagnitudeInfo[] memory mInfos = - allocationManager.getAllocationInfo(defaultOperator, strategy, allocations[0].operatorSets); - for (uint256 i = 0; i < numOpSets; i++) { - assertEq(0, mInfos[i].currentMagnitude, "currentMagnitude should not be updated"); - assertEq(int128(uint128(magnitudes[i])), mInfos[i].pendingDiff, "pendingMagnitude not updated"); - assertEq(effectTimestamp, mInfos[i].effectTimestamp, "effectTimestamp not updated"); + + allocatedSets = allocationManager.getAllocatedSets(defaultOperator); + assertEq(allocatedSets.length, numOpSets, "should have multiple allocated sets"); + + Allocation memory allocation; + for (uint256 i = 0; i < allocateParams.length; ++i) { + allocation = allocationManager.getAllocation(defaultOperator, operatorSets[i], strategyMock); + assertEq(0, allocation.currentMagnitude, "currentMagnitude should not be updated"); + assertEq( + int128(uint128(allocateParams[i].newMagnitudes[0])), + allocation.pendingDiff, + "pendingMagnitude not updated" + ); + assertEq(effectBlock, allocation.effectBlock, "effectBlock not updated"); + + allocatedStrats = allocationManager.getAllocatedStrategies(defaultOperator, operatorSets[i]); + assertEq(allocatedStrats.length, 1, "should have a single allocated strategy to each set"); + assertEq(address(allocatedStrats[0]), address(strategyMock), "should have allocated default strat"); + + assertEq(allocatedSets[i].key(), operatorSets[i].key(), "should be allocated to expected set"); } - // Check storage after warp to completion - cheats.warp(effectTimestamp); - mInfos = allocationManager.getAllocationInfo(defaultOperator, strategy, allocations[0].operatorSets); - for (uint256 i = 0; i < numOpSets; i++) { - assertEq(magnitudes[i], mInfos[i].currentMagnitude, "currentMagnitude not updated"); - assertEq(0, mInfos[i].pendingDiff, "pendingMagnitude not updated"); - assertEq(0, mInfos[i].effectTimestamp, "effectTimestamp not updated"); + // Check storage after roll to completion + cheats.roll(effectBlock); + for (uint256 i = 0; i < allocateParams.length; ++i) { + allocation = allocationManager.getAllocation(defaultOperator, operatorSets[i], strategyMock); + assertEq(allocateParams[i].newMagnitudes[0], allocation.currentMagnitude, "currentMagnitude not updated"); + assertEq(0, allocation.pendingDiff, "pendingMagnitude not updated"); + assertEq(0, allocation.effectBlock, "effectBlock not updated"); } } function testFuzz_allocateMultipleTimes( - uint256 r - ) public { + Randomness r + ) public rand(r) { // Assumptions - uint64 firstAlloc = uint64(bound(r, 1, type(uint64).max)); - uint64 secondAlloc = uint64(bound(r, 0, 1e18)); + uint64 firstAlloc = uint64(r.Uint256(1, type(uint64).max)); + uint64 secondAlloc = uint64(r.Uint256(0, WAD)); cheats.assume(firstAlloc < secondAlloc); + // Check that the operator has no allocated sets/strats before allocation + OperatorSet[] memory allocatedSets = allocationManager.getAllocatedSets(defaultOperator); + IStrategy[] memory allocatedStrats = + allocationManager.getAllocatedStrategies(defaultOperator, defaultOperatorSet); + assertEq(allocatedSets.length, 0, "should not have any allocated sets before allocation"); + assertEq(allocatedStrats.length, 0, "should not have any allocated strats before allocation"); + // Allocate magnitude - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - _generateMagnitudeAllocationCalldata(defaultAVS, firstAlloc, 1e18); + AllocateParams[] memory allocateParams = _newAllocateParams(defaultOperatorSet, firstAlloc); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); + allocationManager.modifyAllocations(allocateParams); - // Warp to allocation complete timestamp - cheats.warp(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY); + // Warp to allocation complete block + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); // Allocate magnitude again - allocations = _generateMagnitudeAllocationCalldata(defaultAVS, secondAlloc, 1e18); + allocateParams = _newAllocateParams(defaultOperatorSet, secondAlloc); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); + allocationManager.modifyAllocations(allocateParams); // Check storage assertEq( secondAlloc, - allocationManager.encumberedMagnitude(defaultOperator, allocations[0].strategy), + allocationManager.encumberedMagnitude(defaultOperator, strategyMock), "encumberedMagnitude not updated" ); + + allocatedSets = allocationManager.getAllocatedSets(defaultOperator); + allocatedStrats = allocationManager.getAllocatedStrategies(defaultOperator, defaultOperatorSet); + assertEq(allocatedSets.length, 1, "should have a single allocated set"); + assertEq(allocatedSets[0].key(), defaultOperatorSet.key(), "should be allocated to default set"); + assertEq(allocatedStrats.length, 1, "should have a single allocated strategy to default set"); + assertEq(address(allocatedStrats[0]), address(strategyMock), "should have allocated default strat"); } function testFuzz_revert_overAllocate( - uint256 r - ) public { - uint8 numOpSets = uint8(bound(r, 2, type(uint8).max)); - MagnitudeAllocation[] memory allocations = - _randomMagnitudeAllocation_singleStrat_multipleOpSets(r, 0, numOpSets); + Randomness r + ) public rand(r) { + uint8 numOpSets = uint8(r.Uint256(2, type(uint8).max)); - allocations[0].magnitudes[numOpSets - 1] = 1e18 + 1; + // Create and register for operator sets + OperatorSet[] memory operatorSets = _newOperatorSets_SingleMockStrategy(defaultAVS, numOpSets); + _registerForOperatorSets(defaultOperator, operatorSets); - // Overallocate - cheats.expectRevert(IAllocationManagerErrors.InsufficientAllocatableMagnitude.selector); - cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); - } + AllocateParams[] memory allocateParams = _randAllocateParams_SingleMockStrategy(operatorSets); + uint256 randIdx = r.Uint256(0, allocateParams.length - 1); - function test_allocateMaxToMultipleStrategies() public { - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - new IAllocationManagerTypes.MagnitudeAllocation[](2); - allocations[0] = _randomMagnitudeAllocation_singleStrat_singleOpSet(0, 0)[0]; - allocations[0].magnitudes[0] = 1e18; - - allocations[1] = _randomMagnitudeAllocation_singleStrat_singleOpSet(1, 1)[0]; - allocations[1].magnitudes[0] = 1e18; - allocations[1].strategy = IStrategy(address(uint160(2))); // Set a different strategy + allocateParams[randIdx].newMagnitudes[0] = WAD + 1; + // Overallocate + cheats.expectRevert(InsufficientMagnitude.selector); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); + allocationManager.modifyAllocations(allocateParams); + } + + function test_allocateMaxToMultipleStrategies( + Randomness r + ) public rand(r) { + // Create a handful of operator sets under the same AVS, each with a unique strategy + OperatorSet[] memory operatorSets = _newOperatorSets_SingleUniqueStrategy(defaultAVS, r.Uint256(2, 10)); + + // Register for each operator set + _registerForOperatorSets(defaultOperator, operatorSets); + + // Allocate max to each operator set + AllocateParams[] memory allocateParams = new AllocateParams[](operatorSets.length); + for (uint256 i = 0; i < operatorSets.length; i++) { + allocateParams[i] = AllocateParams({ + operatorSet: operatorSets[i], + strategies: allocationManager.getStrategiesInOperatorSet(operatorSets[i]), + newMagnitudes: WAD.toArrayU64() + }); + } - // Assert maxMagnitude is encumbered - assertEq( - 1e18, - allocationManager.encumberedMagnitude(defaultOperator, allocations[0].strategy), - "encumberedMagnitude not max" - ); - assertEq( - 1e18, - allocationManager.encumberedMagnitude(defaultOperator, allocations[1].strategy), - "encumberedMagnitude not max" - ); + cheats.prank(defaultOperator); + allocationManager.modifyAllocations(allocateParams); + + // Ensure encumbered magnitude is updated for each strategy + for (uint256 i = 0; i < allocateParams.length; i++) { + assertEq( + WAD, + allocationManager.encumberedMagnitude(defaultOperator, allocateParams[i].strategies[0]), + "encumberedMagnitude not max" + ); + } } function test_revert_allocateDeallocate_modificationPending() public { // Allocate - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - _randomMagnitudeAllocation_singleStrat_singleOpSet(0, 0); + AllocateParams[] memory allocateParams = _randAllocateParams_DefaultOpSet(); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); + allocationManager.modifyAllocations(allocateParams); // Deallocate - allocations[0].magnitudes[0] -= 1; - cheats.expectRevert(IAllocationManagerErrors.ModificationAlreadyPending.selector); + allocateParams[0].newMagnitudes[0] -= 1; + cheats.expectRevert(ModificationAlreadyPending.selector); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); + allocationManager.modifyAllocations(allocateParams); } function test_revert_deallocateTwice_modificationPending() public { // Allocate - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - _randomMagnitudeAllocation_singleStrat_singleOpSet(0, 0); + AllocateParams[] memory allocateParams = _randAllocateParams_DefaultOpSet(); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); + allocationManager.modifyAllocations(allocateParams); // Warp past allocation complete timestsamp - cheats.warp(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY); + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); // Deallocate - allocations[0].magnitudes[0] -= 1; + allocateParams[0].newMagnitudes[0] -= 1; cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); + allocationManager.modifyAllocations(allocateParams); // Deallocate again -> expect revert - cheats.expectRevert(IAllocationManagerErrors.ModificationAlreadyPending.selector); + cheats.expectRevert(ModificationAlreadyPending.selector); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); + allocationManager.modifyAllocations(allocateParams); } /** * Allocates to `firstMod` magnitude and then deallocate to `secondMod` magnitude * Validates the storage - * - 1. After deallocation is alled + * - 1. After deallocation is called * - 2. After the deallocationd delay is hit * - 3. After the deallocation queue is cleared */ - function testFuzz_allocate_deallocate(uint256 r) public { + function testFuzz_allocate_deallocate_whenRegistered( + Randomness r + ) public rand(r) { // Bound allocation and deallocation - uint64 firstMod = uint64(bound(r, 1, 1e18)); - uint64 secondMod = uint64(bound(r, 0, firstMod - 1)); + uint64 firstMod = uint64(r.Uint256(1, WAD)); + uint64 secondMod = uint64(r.Uint256(0, firstMod - 1)); - // Allocate - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - _generateMagnitudeAllocationCalldata(defaultAVS, firstMod, 1e18); + // Allocate magnitude to default registered set + AllocateParams[] memory allocateParams = _newAllocateParams(defaultOperatorSet, firstMod); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); + allocationManager.modifyAllocations(allocateParams); - // Warp to allocation complete timestamp - cheats.warp(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY); + // Warp to allocation complete block + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); // Deallocate - allocations = _generateMagnitudeAllocationCalldata(defaultAVS, secondMod, 1e18); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit EncumberedMagnitudeUpdated(defaultOperator, strategyMock, firstMod); - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit OperatorSetMagnitudeUpdated( - defaultOperator, - allocations[0].operatorSets[0], - strategyMock, - secondMod, - uint32(block.timestamp + DEALLOCATION_DELAY) - ); + allocateParams = _newAllocateParams(defaultOperatorSet, secondMod); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); + allocationManager.modifyAllocations(allocateParams); // Check storage after dealloc assertEq( @@ -1749,21 +1684,20 @@ contract AllocationManagerUnitTests_ModifyAllocations is AllocationManagerUnitTe allocationManager.getAllocatableMagnitude(defaultOperator, strategyMock), "allocatableMagnitude not calcualted correctly" ); - MagnitudeInfo[] memory mInfos = - allocationManager.getAllocationInfo(defaultOperator, strategyMock, allocations[0].operatorSets); - assertEq(firstMod, mInfos[0].currentMagnitude, "currentMagnitude should not be updated"); + Allocation memory allocation = + allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); + assertEq(firstMod, allocation.currentMagnitude, "currentMagnitude should not be updated"); int128 expectedDiff = -int128(uint128(firstMod - secondMod)); - assertEq(expectedDiff, mInfos[0].pendingDiff, "pendingMagnitude not updated"); - uint32 effectTimestamp = uint32(block.timestamp + DEALLOCATION_DELAY); - assertEq(effectTimestamp, mInfos[0].effectTimestamp, "effectTimestamp not updated"); - - // Check storage after warp to completion - cheats.warp(effectTimestamp); - mInfos = - allocationManager.getAllocationInfo(defaultOperator, allocations[0].strategy, allocations[0].operatorSets); - assertEq(secondMod, mInfos[0].currentMagnitude, "currentMagnitude not updated"); - assertEq(0, mInfos[0].pendingDiff, "pendingMagnitude not updated"); - assertEq(0, mInfos[0].effectTimestamp, "effectTimestamp not updated"); + assertEq(expectedDiff, allocation.pendingDiff, "pendingMagnitude not updated"); + uint32 effectBlock = uint32(block.number + DEALLOCATION_DELAY); + assertEq(effectBlock, allocation.effectBlock, "effectBlock not updated"); + + // Check storage after roll to completion + cheats.roll(effectBlock); + allocation = allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); + assertEq(secondMod, allocation.currentMagnitude, "currentMagnitude not updated"); + assertEq(0, allocation.pendingDiff, "pendingMagnitude not updated"); + assertEq(0, allocation.effectBlock, "effectBlock not updated"); assertEq( firstMod, allocationManager.encumberedMagnitude(defaultOperator, strategyMock), @@ -1783,28 +1717,172 @@ contract AllocationManagerUnitTests_ModifyAllocations is AllocationManagerUnitTe ); } + /** + * Allocates to an operator set, then fully deallocates when not registered to the set. + * Checks that deallocation is instant and can be reallocated instantly. + */ + function testFuzz_allocate_fullyDeallocate_reallocate_WhenNotRegistered( + Randomness r + ) public rand(r) { + // Bound allocation and deallocation + uint64 firstMod = r.Uint64(1, WAD); + + // Create a new operator sets that the operator is not registered for + OperatorSet memory operatorSetA = _newOperatorSet_SingleMockStrategy(defaultAVS); + OperatorSet memory operatorSetB = _newOperatorSet_SingleMockStrategy(defaultAVS); + + // Allocate magnitude to operator set + AllocateParams[] memory allocateParams = _newAllocateParams(operatorSetA, firstMod); + cheats.prank(defaultOperator); + allocationManager.modifyAllocations(allocateParams); + + assertEq( + firstMod, + allocationManager.encumberedMagnitude(defaultOperator, strategyMock), + "encumberedMagnitude should equal firstMod" + ); + + // Warp to allocation complete block + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); + + // Deallocate instantly and reallocate all magnitude to second operator set + allocateParams = new AllocateParams[](2); + allocateParams[0] = _newAllocateParams(operatorSetA, 0)[0]; + allocateParams[1] = _newAllocateParams(operatorSetB, firstMod)[0]; + + cheats.prank(defaultOperator); + allocationManager.modifyAllocations(allocateParams); + + // Check storage after dealloc + assertEq( + firstMod, + allocationManager.encumberedMagnitude(defaultOperator, strategyMock), + "encumberedMagnitude should not be changed" + ); + assertEq( + WAD - firstMod, + allocationManager.getAllocatableMagnitude(defaultOperator, strategyMock), + "allocatableMagnitude not calculated correctly" + ); + + // Check operator set A + Allocation memory allocation = allocationManager.getAllocation(defaultOperator, operatorSetA, strategyMock); + assertEq(0, allocation.currentMagnitude, "currentMagnitude should equal 0"); + assertEq(0, allocation.pendingDiff, "pendingMagnitude should be 0"); + assertEq(0, allocation.effectBlock, "effectBlock should be 0"); + + // Check operator set B + allocation = allocationManager.getAllocation(defaultOperator, operatorSetB, strategyMock); + assertEq(0, allocation.currentMagnitude, "currentMagnitude should equal 0"); + assertEq(firstMod, uint64(uint128(allocation.pendingDiff)), "pendingMagnitude should be firstMod"); + assertEq( + uint32(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY), + allocation.effectBlock, + "effectBlock should be expected" + ); + } + + /** + * Allocate to an operator set using magnitude that is only available if the deallocation + * queue is cleared + */ + function testFuzz_allocate_fromClearedDeallocQueue( + Randomness r + ) public rand(r) { + // Create multiple operator sets, register, and allocate to each. Ensure all magnitude is fully allocated. + OperatorSet[] memory deallocSets = _newOperatorSets_SingleMockStrategy(defaultAVS, uint8(r.Uint256(1, 10))); + _registerForOperatorSets(defaultOperator, deallocSets); + AllocateParams[] memory allocateParams = _randAllocateParams_SingleMockStrategy_AllocAll(deallocSets); + + cheats.prank(defaultOperator); + allocationManager.modifyAllocations(allocateParams); + + assertEq( + allocationManager.getAllocatableMagnitude(defaultOperator, strategyMock), + 0, + "operator should not have any remaining allocatable magnitude" + ); + + // Move forward to allocation completion + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); + + // Deallocate fully from each operator set + AllocateParams[] memory deallocateParams = _newAllocateParams(deallocSets, 0); + cheats.prank(defaultOperator); + allocationManager.modifyAllocations(deallocateParams); + + assertEq( + allocationManager.getAllocatableMagnitude(defaultOperator, strategyMock), + 0, + "operator should still not have any allocatable magnitude" + ); + + // Move forward to deallocation completion + cheats.roll(block.number + DEALLOCATION_DELAY); + + // Check that we now have sufficient allocatable magnitude + assertEq( + allocationManager.getAllocatableMagnitude(defaultOperator, strategyMock), + WAD, + "operator should have all magnitude allocatable" + ); + + // Create and register for a new operator set with the same default strategy. + // If we try to allocate to this new set, it should clear the deallocation queue, + // allowing all magnitude to be allocated + OperatorSet memory finalOpSet = _newOperatorSet_SingleMockStrategy(defaultAVS); + _registerForOperatorSet(defaultOperator, finalOpSet); + AllocateParams[] memory finalAllocParams = _newAllocateParams(finalOpSet, WAD); + + cheats.prank(defaultOperator); + allocationManager.modifyAllocations(finalAllocParams); + + // Check that all magnitude will be allocated to the new set, and each prior set + // has a zeroed-out allocation + Allocation memory allocation = allocationManager.getAllocation(defaultOperator, finalOpSet, strategyMock); + assertEq(allocation.currentMagnitude, 0, "should not have any currently-allocated magnitude"); + assertEq(uint64(uint128(allocation.pendingDiff)), WAD, "should have 1 WAD pending"); + assertEq( + allocation.effectBlock, + uint32(block.number) + DEFAULT_OPERATOR_ALLOCATION_DELAY, + "should be effective after default delay" + ); + assertEq( + allocationManager.getAllocatableMagnitude(defaultOperator, strategyMock), + 0, + "operator should not have any remaining allocatable magnitude" + ); + assertEq( + allocationManager.encumberedMagnitude(defaultOperator, strategyMock), + WAD, + "all magnitude should be allocated" + ); + + for (uint256 i = 0; i < deallocSets.length; i++) { + allocation = allocationManager.getAllocation(defaultOperator, deallocSets[i], strategyMock); + assertEq(allocation.currentMagnitude, 0, "should not have any currently-allocated magnitude"); + assertEq(allocation.pendingDiff, 0, "should have nothing pending"); + assertEq(allocation.effectBlock, 0, "should be zeroed out"); + } + } + function test_deallocate_all() public { // Allocate - IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = - _generateMagnitudeAllocationCalldata(defaultAVS, 1e18, 1e18); + AllocateParams[] memory allocateParams = _newAllocateParams(defaultOperatorSet, WAD); cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); + allocationManager.modifyAllocations(allocateParams); - // Warp to allocation complete timestamp - cheats.warp(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY); + // Warp to allocation complete block + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); // Deallocate - allocations[0].magnitudes[0] = 0; + allocateParams[0].newMagnitudes[0] = 0; cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); + allocationManager.modifyAllocations(allocateParams); // Warp to completion and clear deallocation queue - cheats.warp(block.timestamp + DEALLOCATION_DELAY); - IStrategy[] memory strategies = new IStrategy[](1); - strategies[0] = strategyMock; - uint16[] memory numToClear = new uint16[](1); - numToClear[0] = 1; - allocationManager.clearDeallocationQueue(defaultOperator, strategies, numToClear); + cheats.roll(block.number + DEALLOCATION_DELAY); + allocationManager.clearDeallocationQueue(defaultOperator, strategyMock.toArray(), uint16(1).toArrayU16()); // Check storage assertEq( @@ -1812,74 +1890,72 @@ contract AllocationManagerUnitTests_ModifyAllocations is AllocationManagerUnitTe allocationManager.encumberedMagnitude(defaultOperator, strategyMock), "encumberedMagnitude should be updated" ); - MagnitudeInfo[] memory mInfos = - allocationManager.getAllocationInfo(defaultOperator, strategyMock, allocations[0].operatorSets); - assertEq(0, mInfos[0].currentMagnitude, "currentMagnitude should be 0"); - assertEq(0, mInfos[0].pendingDiff, "pendingMagnitude should be 0"); - assertEq(0, mInfos[0].effectTimestamp, "effectTimestamp should be 0"); + Allocation memory allocation = + allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); + assertEq(0, allocation.currentMagnitude, "currentMagnitude should be 0"); + assertEq(0, allocation.pendingDiff, "pendingMagnitude should be 0"); + assertEq(0, allocation.effectBlock, "effectBlock should be 0"); } function testFuzz_allocate_deallocate_singleStrat_multipleOperatorSets( - uint256 r - ) public { - uint8 numOpSets = uint8(bound(r, 0, type(uint8).max)); - (MagnitudeAllocation[] memory allocations, MagnitudeAllocation[] memory deallocations) = - _randomAllocationAndDeallocation_singleStrat_multipleOpSets(numOpSets, r, 0); + Randomness r + ) public rand(r) { + uint8 numOpSets = uint8(r.Uint256(1, type(uint8).max)); + // Create and register for operator sets, each with a single default strategy + OperatorSet[] memory operatorSets = _newOperatorSets_SingleMockStrategy(defaultAVS, numOpSets); + _registerForOperatorSets(defaultOperator, operatorSets); + + (AllocateParams[] memory allocateParams, AllocateParams[] memory deallocateParams) = + _randAllocAndDeallocParams_SingleMockStrategy(operatorSets); // Allocate cheats.prank(defaultOperator); - allocationManager.modifyAllocations(allocations); - uint64 encumberedMagnitudeAfterAllocation = - allocationManager.encumberedMagnitude(defaultOperator, allocations[0].strategy); + allocationManager.modifyAllocations(allocateParams); + uint64 encumberedMagnitudeAfterAllocation = allocationManager.encumberedMagnitude(defaultOperator, strategyMock); - // Warp to allocation complete timestamp - cheats.warp(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY); + // Warp to allocation complete block + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); - // Deallocate + // Calculate post-deallocation magnitude + // We can add each entry to this value because each operator set is using the same strategy uint64 postDeallocMag; - for (uint256 i = 0; i < numOpSets; i++) { - postDeallocMag += deallocations[0].magnitudes[i]; - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit EncumberedMagnitudeUpdated( - defaultOperator, deallocations[0].strategy, encumberedMagnitudeAfterAllocation - ); - // pendingNewMags[i] = allocations[0].magnitudes[i] - deallocations[0].magnitudes[i]; - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit OperatorSetMagnitudeUpdated( - defaultOperator, - deallocations[0].operatorSets[i], - deallocations[0].strategy, - deallocations[0].magnitudes[i], - uint32(block.timestamp + DEALLOCATION_DELAY) - ); + for (uint256 i = 0; i < deallocateParams.length; ++i) { + postDeallocMag += deallocateParams[i].newMagnitudes[0]; } cheats.prank(defaultOperator); - allocationManager.modifyAllocations(deallocations); + allocationManager.modifyAllocations(deallocateParams); // Check storage after dealloc assertEq( encumberedMagnitudeAfterAllocation, - allocationManager.encumberedMagnitude(defaultOperator, allocations[0].strategy), + allocationManager.encumberedMagnitude(defaultOperator, strategyMock), "encumberedMagnitude should not be updated" ); - MagnitudeInfo[] memory mInfos = - allocationManager.getAllocationInfo(defaultOperator, strategyMock, allocations[0].operatorSets); - for (uint256 i = 0; i < mInfos.length; i++) { - assertEq(allocations[0].magnitudes[i], mInfos[i].currentMagnitude, "currentMagnitude should not be updated"); - int128 expectedDiff = -int128(uint128(allocations[0].magnitudes[i] - deallocations[0].magnitudes[i])); - assertEq(expectedDiff, mInfos[i].pendingDiff, "pendingMagnitude not updated"); - uint32 effectTimestamp = uint32(block.timestamp + DEALLOCATION_DELAY); - assertEq(effectTimestamp, mInfos[i].effectTimestamp, "effectTimestamp not updated"); + + Allocation memory allocation; + for (uint256 i = 0; i < allocateParams.length; ++i) { + allocation = allocationManager.getAllocation(defaultOperator, allocateParams[i].operatorSet, strategyMock); + assertEq( + allocateParams[i].newMagnitudes[0], + allocation.currentMagnitude, + "currentMagnitude should not be updated" + ); + int128 expectedDiff = + -int128(uint128(allocateParams[i].newMagnitudes[0] - deallocateParams[i].newMagnitudes[0])); + assertEq(expectedDiff, allocation.pendingDiff, "pendingMagnitude not updated"); + uint32 effectBlock = uint32(block.number + DEALLOCATION_DELAY); + assertEq(effectBlock, allocation.effectBlock, "effectBlock not updated"); } - // Check storage after warp to completion - cheats.warp(block.timestamp + DEALLOCATION_DELAY); - mInfos = allocationManager.getAllocationInfo(defaultOperator, strategyMock, allocations[0].operatorSets); - for (uint256 i = 0; i < mInfos.length; i++) { - assertEq(deallocations[0].magnitudes[i], mInfos[i].currentMagnitude, "currentMagnitude not updated"); - assertEq(0, mInfos[i].pendingDiff, "pendingMagnitude not updated"); - assertEq(0, mInfos[i].effectTimestamp, "effectTimestamp not updated"); + // Check storage after roll to completion + cheats.roll(block.number + DEALLOCATION_DELAY); + + for (uint256 i = 0; i < allocateParams.length; ++i) { + allocation = allocationManager.getAllocation(defaultOperator, allocateParams[i].operatorSet, strategyMock); + assertEq(deallocateParams[i].newMagnitudes[0], allocation.currentMagnitude, "currentMagnitude not updated"); + assertEq(0, allocation.pendingDiff, "pendingMagnitude not updated"); + assertEq(0, allocation.effectBlock, "effectBlock not updated"); } // Clear deallocation queue @@ -1887,8 +1963,7 @@ contract AllocationManagerUnitTests_ModifyAllocations is AllocationManagerUnitTe strategies[0] = strategyMock; uint16[] memory numToClear = new uint16[](1); numToClear[0] = numOpSets; - allocationManager.clearDeallocationQueue(defaultOperator, strategies, numToClear); - + allocationManager.clearDeallocationQueue(defaultOperator, strategyMock.toArray(), type(uint16).max.toArrayU16()); // Check storage after clearing deallocation queue assertEq( postDeallocMag, @@ -1896,9 +1971,15 @@ contract AllocationManagerUnitTests_ModifyAllocations is AllocationManagerUnitTe "encumberedMagnitude should be updated" ); } + + function testFuzz_allocate_WithDeallocationQueue( + Randomness r + ) public rand(r) {} } contract AllocationManagerUnitTests_ClearDeallocationQueue is AllocationManagerUnitTests { + using SingleItemArrayLib for *; + /// ----------------------------------------------------------------------- /// clearModificationQueue() /// ----------------------------------------------------------------------- @@ -1913,18 +1994,10 @@ contract AllocationManagerUnitTests_ClearDeallocationQueue is AllocationManagerU IStrategy[] memory strategies = new IStrategy[](1); uint16[] memory numToClear = new uint16[](2); - cheats.expectRevert(IAllocationManagerErrors.InputArrayLengthMismatch.selector); + cheats.expectRevert(InputArrayLengthMismatch.selector); allocationManager.clearDeallocationQueue(defaultOperator, strategies, numToClear); } - function test_revert_operatorNotRegistered() public { - // Deregister operator - delegationManagerMock.setIsOperator(defaultOperator, false); - - cheats.expectRevert(IAllocationManagerErrors.OperatorNotRegistered.selector); - allocationManager.clearDeallocationQueue(defaultOperator, new IStrategy[](0), new uint16[](0)); - } - /** * @notice Allocates magnitude to an operator and then * - Clears deallocation queue when only an allocation exists @@ -1932,196 +2005,202 @@ contract AllocationManagerUnitTests_ClearDeallocationQueue is AllocationManagerU * - Validates storage after the second clear */ function testFuzz_allocate( - uint256 r - ) public { + Randomness r + ) public rand(r) { + AllocateParams[] memory allocateParams = _randAllocateParams_DefaultOpSet(); + // Allocate magnitude - IAllocationManager.MagnitudeAllocation[] memory allocations = - _queueRandomAllocation_singleStrat_singleOpSet(defaultOperator, r, 0); + cheats.prank(defaultOperator); + allocationManager.modifyAllocations(allocateParams); // Attempt to clear queue, assert no events emitted - allocationManager.clearDeallocationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); + allocationManager.clearDeallocationQueue(defaultOperator, defaultStrategies, _maxNumToClear()); Vm.Log[] memory entries = vm.getRecordedLogs(); assertEq(0, entries.length, "should not have emitted any events"); - // Warp to allocation complete timestamp - cheats.warp(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY); + // Warp to allocation complete block + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); // Clear queue - this is a noop - allocationManager.clearDeallocationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); + allocationManager.clearDeallocationQueue(defaultOperator, defaultStrategies, _maxNumToClear()); + entries = vm.getRecordedLogs(); + assertEq(0, entries.length, "should not have emitted any events 2"); - // Validate storage (although this is technically tested in allocation tests, adding for sanity) - // TODO: maybe add a harness here to actually introspect storage - IAllocationManager.MagnitudeInfo[] memory mInfos = - allocationManager.getAllocationInfo(defaultOperator, strategyMock, allocations[0].operatorSets); - assertEq(allocations[0].magnitudes[0], mInfos[0].currentMagnitude, "currentMagnitude should be 0"); - assertEq(0, mInfos[0].pendingDiff, "pendingMagnitude should be 0"); - assertEq(0, mInfos[0].effectTimestamp, "effectTimestamp should be 0"); + // Validate allocation is no longer pending + Allocation memory allocation = + allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); + assertEq(allocateParams[0].newMagnitudes[0], allocation.currentMagnitude, "currentMagnitude should be 0"); + assertEq(0, allocation.pendingDiff, "pendingMagnitude should be 0"); + assertEq(0, allocation.effectBlock, "effectBlock should be 0"); } /** - * @notice Allocates magnitude to an operator and then + * @notice Allocates magnitude to an operator registered for some operator sets, and then * - Clears deallocation queue when nothing can be completed * - After the first clear, asserts the allocation info takes into account the deallocation * - Clears deallocation queue when the dealloc can be completed - * - Assert events & validates storage after the deallocations are completed + * - Assert events & validates storage after the deallocateParams are completed */ - function testFuzz_allocate_deallocate(uint256 r) public { - // Complete allocations & add a deallocation - (MagnitudeAllocation[] memory allocations, MagnitudeAllocation[] memory deallocations) = - _queueRandomAllocationAndDeallocation( - defaultOperator, - 1, // numOpSets - r, - 0 // salt - ); - - // Clear queue & check storage - allocationManager.clearDeallocationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); + function testFuzz_allocate_deallocate_whenRegistered( + Randomness r + ) public rand(r) { + // Generate a random allocation and subsequent deallocation from the default operator set + (AllocateParams[] memory allocateParams, AllocateParams[] memory deallocateParams) = + _randAllocAndDeallocParams_SingleMockStrategy(defaultOperatorSet.toArray()); + + // Allocate + cheats.prank(defaultOperator); + allocationManager.modifyAllocations(allocateParams); + + // Roll to allocation complete block + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); + + // Deallocate + cheats.prank(defaultOperator); + allocationManager.modifyAllocations(deallocateParams); + + // Clear queue - since we have not rolled forward, this should be a no-op + allocationManager.clearDeallocationQueue(defaultOperator, defaultStrategies, _maxNumToClear()); assertEq( - allocations[0].magnitudes[0], + allocateParams[0].newMagnitudes[0], allocationManager.encumberedMagnitude(defaultOperator, strategyMock), "encumberedMagnitude should not be updated" ); - // Validate storage - encumbered magnitude should just be allocations (we only have 1 allocation) - IAllocationManager.MagnitudeInfo[] memory mInfos = - allocationManager.getAllocationInfo(defaultOperator, strategyMock, allocations[0].operatorSets); - int128 pendingDiff = -int128(uint128(allocations[0].magnitudes[0] - deallocations[0].magnitudes[0])); - assertEq(allocations[0].magnitudes[0], mInfos[0].currentMagnitude, "currentMagnitude should be 0"); - assertEq(pendingDiff, mInfos[0].pendingDiff, "pendingMagnitude should be 0"); - assertEq(block.timestamp + DEALLOCATION_DELAY, mInfos[0].effectTimestamp, "effectTimestamp should be 0"); + // Validate storage - encumbered magnitude should just be allocateParams (we only have 1 allocation) + IAllocationManager.Allocation memory allocation = + allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); + int128 pendingDiff = -int128(uint128(allocateParams[0].newMagnitudes[0] - deallocateParams[0].newMagnitudes[0])); + assertEq(allocateParams[0].newMagnitudes[0], allocation.currentMagnitude, "currentMagnitude should be 0"); + assertEq(pendingDiff, allocation.pendingDiff, "pendingMagnitude should be 0"); + assertEq(block.number + DEALLOCATION_DELAY, allocation.effectBlock, "effectBlock should be 0"); - // Warp to deallocation complete timestamp - cheats.warp(block.timestamp + DEALLOCATION_DELAY); + // Warp to deallocation complete block + cheats.roll(block.number + DEALLOCATION_DELAY); // Clear queue - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit EncumberedMagnitudeUpdated(defaultOperator, strategyMock, deallocations[0].magnitudes[0]); - allocationManager.clearDeallocationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); + allocationManager.clearDeallocationQueue(defaultOperator, defaultStrategies, _maxNumToClear()); - // Validate storage - encumbered magnitude should just be deallocations (we only have 1 deallocation) + // Validate storage - encumbered magnitude should just be deallocateParams (we only have 1 deallocation) assertEq( - deallocations[0].magnitudes[0], + deallocateParams[0].newMagnitudes[0], allocationManager.encumberedMagnitude(defaultOperator, strategyMock), "encumberedMagnitude should be updated" ); - mInfos = allocationManager.getAllocationInfo(defaultOperator, strategyMock, deallocations[0].operatorSets); - assertEq(deallocations[0].magnitudes[0], mInfos[0].currentMagnitude, "currentMagnitude should be 0"); - assertEq(0, mInfos[0].pendingDiff, "pendingMagnitude should be 0"); - assertEq(0, mInfos[0].effectTimestamp, "effectTimestamp should be 0"); + allocation = allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); + assertEq(deallocateParams[0].newMagnitudes[0], allocation.currentMagnitude, "currentMagnitude should be 0"); + assertEq(0, allocation.pendingDiff, "pendingMagnitude should be 0"); + assertEq(0, allocation.effectBlock, "effectBlock should be 0"); } /** * Allocates, deallocates, and then allocates again. Asserts that * - The deallocation does not block state updates from the second allocation, even though the allocation has an earlier - * effect timestamp + * effect block */ - function test_allocate_deallocate_allocate() public { - uint32 allocationDelay = 15 days; - // Set allocation delay to be 15 days - cheats.prank(defaultOperator); - allocationManager.setAllocationDelay(allocationDelay); - cheats.warp(block.timestamp + ALLOCATION_CONFIGURATION_DELAY); - (,uint32 storedDelay) = allocationManager.getAllocationDelay(defaultOperator); - assertEq(allocationDelay, storedDelay, "allocation delay not valid"); - - // Allocate half of mag to opset1 - IAllocationManagerTypes.MagnitudeAllocation[] memory firstAllocation = - _generateMagnitudeAllocationCalldataForOpSet(defaultAVS, 1, 5e17, 1e18); + function test_allocate_deallocate_allocate_whenRegistered() public { + // Allocate half of mag to default operator set + AllocateParams[] memory firstAllocation = _newAllocateParams(defaultOperatorSet, 5e17); cheats.prank(defaultOperator); allocationManager.modifyAllocations(firstAllocation); - cheats.warp(block.timestamp + 15 days); + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); - // Deallocate half from opset1. - uint32 deallocationEffectTimestamp = uint32(block.timestamp + DEALLOCATION_DELAY); - IAllocationManagerTypes.MagnitudeAllocation[] memory firstDeallocation = - _generateMagnitudeAllocationCalldataForOpSet(defaultAVS, 1, 25e16, 1e18); + // Deallocate half from default operator set + uint32 deallocationEffectBlock = uint32(block.number + DEALLOCATION_DELAY); + AllocateParams[] memory firstDeallocation = _newAllocateParams(defaultOperatorSet, 25e16); cheats.prank(defaultOperator); allocationManager.modifyAllocations(firstDeallocation); - MagnitudeInfo[] memory mInfos = allocationManager.getAllocationInfo(defaultOperator, strategyMock, firstDeallocation[0].operatorSets); - assertEq(deallocationEffectTimestamp, mInfos[0].effectTimestamp, "effect timestamp not correct"); - - // Allocate 33e16 mag to opset2 - uint32 allocationEffectTimestamp = uint32(block.timestamp + allocationDelay); - IAllocationManagerTypes.MagnitudeAllocation[] memory secondAllocation = - _generateMagnitudeAllocationCalldataForOpSet(defaultAVS, 2, 33e16, 1e18); + Allocation memory allocation = + allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); + assertEq(deallocationEffectBlock, allocation.effectBlock, "effect block not correct"); + + // Create and register for a new operator set + OperatorSet memory newOperatorSet = _newOperatorSet_SingleMockStrategy(defaultAVS); + _registerForOperatorSet(defaultOperator, newOperatorSet); + + // Allocate 33e16 mag to new operator set + uint32 allocationEffectBlock = uint32(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); + AllocateParams[] memory secondAllocation = _newAllocateParams(newOperatorSet, 33e16); cheats.prank(defaultOperator); allocationManager.modifyAllocations(secondAllocation); - mInfos = allocationManager.getAllocationInfo(defaultOperator, strategyMock, secondAllocation[0].operatorSets); - console.log("deallocation effect timestamp: ", deallocationEffectTimestamp); - console.log("allocation effect timestamp: ", allocationEffectTimestamp); - assertEq(allocationEffectTimestamp, mInfos[0].effectTimestamp, "effect timestamp not correct"); - assertLt(allocationEffectTimestamp, deallocationEffectTimestamp, "invalid test setup"); + allocation = allocationManager.getAllocation(defaultOperator, newOperatorSet, strategyMock); + console.log("deallocation effect block: ", deallocationEffectBlock); + console.log("allocation effect block: ", allocationEffectBlock); + assertEq(allocationEffectBlock, allocation.effectBlock, "effect block not correct"); + assertLt(allocationEffectBlock, deallocationEffectBlock, "invalid test setup"); - // Warp to allocation effect timestamp & clear the queue - cheats.warp(allocationEffectTimestamp); - allocationManager.clearDeallocationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); + // Warp to allocation effect block & clear the queue + cheats.roll(allocationEffectBlock); + allocationManager.clearDeallocationQueue(defaultOperator, defaultStrategies, _maxNumToClear()); // Validate `getAllocatableMagnitude`. Allocatable magnitude should be the difference between the max magnitude and the encumbered magnitude uint64 allocatableMagnitude = allocationManager.getAllocatableMagnitude(defaultOperator, strategyMock); assertEq(WAD - 33e16 - 5e17, allocatableMagnitude, "allocatableMagnitude not correct"); // Validate that we can allocate again for opset2. This should not revert - IAllocationManagerTypes.MagnitudeAllocation[] memory thirdAllocation = - _generateMagnitudeAllocationCalldataForOpSet(defaultAVS, 2, 10e16, 1e18); + AllocateParams[] memory thirdAllocation = _newAllocateParams(newOperatorSet, 10e16); cheats.prank(defaultOperator); allocationManager.modifyAllocations(thirdAllocation); } /** * Allocates to opset1, allocates to opset2, deallocates from opset1. Asserts that the allocation, which has a higher - * effect timestamp is not blocking the deallocation. + * effect block is not blocking the deallocation. * The allocs/deallocs looks like - * 1. (allocation, opSet2, mag: 5e17, effectTimestamp: 50th day) - * 2. (deallocation, opSet1, mag: 0, effectTimestamp: 42.5 day) - * + * 1. (allocation, opSet2, mag: 5e17, effectBlock: 50th day) + * 2. (deallocation, opSet1, mag: 0, effectBlock: 42.5 day) + * * The deallocation queue looks like - * 1. (deallocation, opSet1, mag: 0, effectTimestamp: 42.5 day) + * 1. (deallocation, opSet1, mag: 0, effectBlock: 42.5 day) */ function test_regression_deallocationNotBlocked() public { - uint32 allocationDelay = 25 days; - // Set allocation delay to be 25 days, greater than the deallocation timestamp + // Set allocation delay to be longer than the deallocation delay + uint32 allocationDelay = DEALLOCATION_DELAY * 2; cheats.prank(defaultOperator); allocationManager.setAllocationDelay(allocationDelay); - cheats.warp(block.timestamp + ALLOCATION_CONFIGURATION_DELAY); - (,uint32 storedDelay) = allocationManager.getAllocationDelay(defaultOperator); + cheats.roll(block.number + ALLOCATION_CONFIGURATION_DELAY); + (, uint32 storedDelay) = allocationManager.getAllocationDelay(defaultOperator); assertEq(allocationDelay, storedDelay, "allocation delay not valid"); - // Allocate half of mag to opset1 - IAllocationManagerTypes.MagnitudeAllocation[] memory firstAllocation = - _generateMagnitudeAllocationCalldataForOpSet(defaultAVS, 1, 5e17, 1e18); + // Allocate half of mag to default operator set + AllocateParams[] memory firstAllocation = _newAllocateParams(defaultOperatorSet, 5e17); cheats.prank(defaultOperator); allocationManager.modifyAllocations(firstAllocation); - cheats.warp(block.timestamp + 25 days); + cheats.roll(block.number + allocationDelay); + + // Create and register for a second operator set + OperatorSet memory newOperatorSet = _newOperatorSet_SingleMockStrategy(defaultAVS); + _registerForOperatorSet(defaultOperator, newOperatorSet); // Allocate half of mag to opset2 - IAllocationManagerTypes.MagnitudeAllocation[] memory secondAllocation = - _generateMagnitudeAllocationCalldataForOpSet(defaultAVS, 2, 5e17, 1e18); + AllocateParams[] memory secondAllocation = _newAllocateParams(newOperatorSet, 5e17); cheats.prank(defaultOperator); allocationManager.modifyAllocations(secondAllocation); - uint32 allocationEffectTimestamp = uint32(block.timestamp + allocationDelay); - MagnitudeInfo[] memory mInfos = allocationManager.getAllocationInfo(defaultOperator, strategyMock, secondAllocation[0].operatorSets); - assertEq(allocationEffectTimestamp, mInfos[0].effectTimestamp, "effect timestamp not correct"); + uint32 allocationEffectBlock = uint32(block.number + allocationDelay); + Allocation memory allocation = allocationManager.getAllocation(defaultOperator, newOperatorSet, strategyMock); + assertEq(allocationEffectBlock, allocation.effectBlock, "effect block not correct"); // Deallocate all from opSet1 - uint32 deallocationEffectTimestamp = uint32(block.timestamp + DEALLOCATION_DELAY); - IAllocationManagerTypes.MagnitudeAllocation[] memory firstDeallocation = - _generateMagnitudeAllocationCalldataForOpSet(defaultAVS, 1, 0, 1e18); + uint32 deallocationEffectBlock = uint32(block.number + DEALLOCATION_DELAY); + AllocateParams[] memory firstDeallocation = _newAllocateParams(defaultOperatorSet, 0); cheats.prank(defaultOperator); allocationManager.modifyAllocations(firstDeallocation); - mInfos = allocationManager.getAllocationInfo(defaultOperator, strategyMock, firstDeallocation[0].operatorSets); - assertEq(deallocationEffectTimestamp, mInfos[0].effectTimestamp, "effect timestamp not correct"); - assertLt(deallocationEffectTimestamp, allocationEffectTimestamp, "invalid test setup"); + allocation = allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); + assertEq(deallocationEffectBlock, allocation.effectBlock, "effect block not correct"); + assertLt(deallocationEffectBlock, allocationEffectBlock, "invalid test setup"); - // Warp to deallocation effect timestamp & clear the queue - cheats.warp(deallocationEffectTimestamp); - allocationManager.clearDeallocationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); + // Warp to deallocation effect block & clear the queue + cheats.roll(deallocationEffectBlock); + allocationManager.clearDeallocationQueue(defaultOperator, defaultStrategies, _maxNumToClear()); // At this point, we should be able to allocate again to opSet1 AND have only 5e17 encumbered magnitude - assertEq(5e17, allocationManager.encumberedMagnitude(defaultOperator, strategyMock), "encumbered magnitude not correct"); - IAllocationManagerTypes.MagnitudeAllocation[] memory thirdAllocation = - _generateMagnitudeAllocationCalldataForOpSet(defaultAVS, 1, 5e17, 1e18); + assertEq( + 5e17, + allocationManager.encumberedMagnitude(defaultOperator, strategyMock), + "encumbered magnitude not correct" + ); + AllocateParams[] memory thirdAllocation = _newAllocateParams(defaultOperatorSet, 5e17); cheats.prank(defaultOperator); allocationManager.modifyAllocations(thirdAllocation); } @@ -2145,18 +2224,18 @@ contract AllocationManagerUnitTests_SetAllocationDelay is AllocationManagerUnitT // Deregister operator delegationManagerMock.setIsOperator(operatorToSet, false); cheats.prank(operatorToSet); - cheats.expectRevert(IAllocationManagerErrors.OperatorNotRegistered.selector); + cheats.expectRevert(OperatorNotRegistered.selector); allocationManager.setAllocationDelay(1); } function testFuzz_setDelay( - uint256 r - ) public { - uint32 delay = uint32(bound(r, 0, type(uint32).max)); + Randomness r + ) public rand(r) { + uint32 delay = uint32(r.Uint256(0, type(uint32).max)); // Set delay cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit AllocationDelaySet(operatorToSet, delay, uint32(block.timestamp + ALLOCATION_CONFIGURATION_DELAY)); + emit AllocationDelaySet(operatorToSet, delay, uint32(block.number + ALLOCATION_CONFIGURATION_DELAY)); cheats.prank(operatorToSet); allocationManager.setAllocationDelay(delay); @@ -2165,8 +2244,8 @@ contract AllocationManagerUnitTests_SetAllocationDelay is AllocationManagerUnitT assertFalse(isSet, "isSet should not be set"); assertEq(0, returnedDelay, "returned delay should be 0"); - // Warp to effect timestamp - cheats.warp(block.timestamp + ALLOCATION_CONFIGURATION_DELAY); + // Warp to effect block + cheats.roll(block.number + ALLOCATION_CONFIGURATION_DELAY); // Check values after config delay (isSet, returnedDelay) = allocationManager.getAllocationDelay(operatorToSet); @@ -2175,53 +2254,53 @@ contract AllocationManagerUnitTests_SetAllocationDelay is AllocationManagerUnitT } function test_fuzz_setDelay_multipleTimesWithinConfigurationDelay( - uint32 firstDelay, uint32 secondDelay - ) public { - firstDelay = uint32(bound(firstDelay, 1, type(uint32).max)); - secondDelay = uint32(bound(secondDelay, 1, type(uint32).max)); + Randomness r + ) public rand(r) { + uint32 firstDelay = uint32(r.Uint256(1, type(uint32).max)); + uint32 secondDelay = uint32(r.Uint256(1, type(uint32).max)); cheats.assume(firstDelay != secondDelay); // Set delay cheats.prank(operatorToSet); allocationManager.setAllocationDelay(firstDelay); - // Warp just before effect timestamp - cheats.warp(block.timestamp + ALLOCATION_CONFIGURATION_DELAY - 1); + // Warp just before effect block + cheats.roll(block.number + ALLOCATION_CONFIGURATION_DELAY - 1); // Set delay again cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit AllocationDelaySet(operatorToSet, secondDelay, uint32(block.timestamp + ALLOCATION_CONFIGURATION_DELAY)); + emit AllocationDelaySet(operatorToSet, secondDelay, uint32(block.number + ALLOCATION_CONFIGURATION_DELAY)); cheats.prank(operatorToSet); allocationManager.setAllocationDelay(secondDelay); - // Warp to effect timestamp of first delay - cheats.warp(block.timestamp + 1); + // Warp to effect block of first delay + cheats.roll(block.number + 1); // Assert that the delay is still not set (bool isSet, uint32 returnedDelay) = allocationManager.getAllocationDelay(operatorToSet); assertFalse(isSet, "isSet should not be set"); assertEq(0, returnedDelay, "returned delay should be 0"); - // Warp to effect timestamp of second delay - cheats.warp(block.timestamp + ALLOCATION_CONFIGURATION_DELAY); + // Warp to effect block of second delay + cheats.roll(block.number + ALLOCATION_CONFIGURATION_DELAY); (isSet, returnedDelay) = allocationManager.getAllocationDelay(operatorToSet); assertTrue(isSet, "isSet should be set"); assertEq(secondDelay, returnedDelay, "delay not set"); } function testFuzz_multipleDelays( - uint32 firstDelay, uint32 secondDelay - ) public { - firstDelay = uint32(bound(firstDelay, 1, type(uint32).max)); - secondDelay = uint32(bound(secondDelay, 1, type(uint32).max)); + Randomness r + ) public rand(r) { + uint32 firstDelay = uint32(r.Uint256(1, type(uint32).max)); + uint32 secondDelay = uint32(r.Uint256(1, type(uint32).max)); cheats.assume(firstDelay != secondDelay); // Set delay cheats.prank(operatorToSet); allocationManager.setAllocationDelay(firstDelay); - // Warp to effect timestamp of first delay - cheats.warp(block.timestamp + ALLOCATION_CONFIGURATION_DELAY); + // Warp to effect block of first delay + cheats.roll(block.number + ALLOCATION_CONFIGURATION_DELAY); // Set delay again cheats.prank(operatorToSet); @@ -2232,8 +2311,8 @@ contract AllocationManagerUnitTests_SetAllocationDelay is AllocationManagerUnitT assertTrue(isSet, "isSet should be set"); assertEq(firstDelay, returnedDelay, "delay not set"); - // Warp to effect timestamp of second delay - cheats.warp(block.timestamp + ALLOCATION_CONFIGURATION_DELAY); + // Warp to effect block of second delay + cheats.roll(block.number + ALLOCATION_CONFIGURATION_DELAY); // Check values after second delay (isSet, returnedDelay) = allocationManager.getAllocationDelay(operatorToSet); @@ -2242,27 +2321,322 @@ contract AllocationManagerUnitTests_SetAllocationDelay is AllocationManagerUnitT } function testFuzz_setDelay_DMCaller( - uint256 r - ) public { - uint32 delay = uint32(bound(r, 1, type(uint32).max)); + Randomness r + ) public rand(r) { + uint32 delay = uint32(r.Uint256(1, type(uint32).max)); cheats.prank(address(delegationManagerMock)); allocationManager.setAllocationDelay(operatorToSet, delay); - // Warp to effect timestamp - cheats.warp(block.timestamp + ALLOCATION_CONFIGURATION_DELAY); + // Warp to effect block + cheats.roll(block.number + ALLOCATION_CONFIGURATION_DELAY); (bool isSet, uint32 returnedDelay) = allocationManager.getAllocationDelay(operatorToSet); assertTrue(isSet, "isSet should be set"); assertEq(delay, returnedDelay, "delay not set"); } } +contract AllocationManagerUnitTests_registerForOperatorSets is AllocationManagerUnitTests { + function test_registerForOperatorSets_Paused() public { + allocationManager.pause(2 ** PAUSED_OPERATOR_SET_REGISTRATION_AND_DEREGISTRATION); + cheats.expectRevert(IPausable.CurrentlyPaused.selector); + allocationManager.registerForOperatorSets(defaultRegisterParams); + } + + function testFuzz_registerForOperatorSets_InvalidOperator( + Randomness r + ) public rand(r) { + cheats.prank(r.Address()); + cheats.expectRevert(InvalidOperator.selector); + allocationManager.registerForOperatorSets(defaultRegisterParams); + } + + function testFuzz_registerForOperatorSets_InvalidOperatorSet( + Randomness r + ) public rand(r) { + cheats.prank(defaultOperator); + cheats.expectRevert(InvalidOperatorSet.selector); + allocationManager.registerForOperatorSets(_newRegisterParams_SingleSet(defaultAVS, 1)); // invalid id + } + + function testFuzz_registerForOperatorSets_AlreadyMemberOfSet( + Randomness r + ) public rand(r) { + cheats.prank(defaultOperator); + cheats.expectRevert(AlreadyMemberOfSet.selector); + allocationManager.registerForOperatorSets(defaultRegisterParams); + } + + function testFuzz_registerForOperatorSets_Correctness( + Randomness r + ) public rand(r) { + address operator = r.Address(); + uint256 numOpSets = r.Uint256(1, 32); + uint32[] memory operatorSetIds = new uint32[](numOpSets); + CreateSetParams[] memory createSetParams = new CreateSetParams[](numOpSets); + + delegationManagerMock.setIsOperator(operator, true); + + for (uint256 i; i < numOpSets; ++i) { + operatorSetIds[i] = r.Uint32(1, type(uint32).max); + createSetParams[i].operatorSetId = operatorSetIds[i]; + createSetParams[i].strategies = defaultStrategies; + } + + cheats.prank(defaultAVS); + allocationManager.createOperatorSets(createSetParams); + + for (uint256 j; j < numOpSets; ++j) { + cheats.expectEmit(true, true, false, false, address(allocationManager)); + emit OperatorAddedToOperatorSet(operator, OperatorSet(defaultAVS, operatorSetIds[j])); + } + + cheats.expectCall( + defaultAVS, abi.encodeWithSelector(IAVSRegistrar.registerOperator.selector, operator, operatorSetIds, "") + ); + + cheats.prank(operator); + allocationManager.registerForOperatorSets(RegisterParams(defaultAVS, operatorSetIds, "")); + + require(allocationManager.getRegisteredSets(operator).length == numOpSets, "should be registered for all sets"); + + for (uint256 k; k < numOpSets; ++k) { + require( + allocationManager.getMembers(OperatorSet(defaultAVS, operatorSetIds[k]))[0] == operator, + "should be member of set" + ); + } + } +} + +contract AllocationManagerUnitTests_deregisterFromOperatorSets is AllocationManagerUnitTests { + using SingleItemArrayLib for *; + + function test_deregisterFromOperatorSets_Paused() public { + allocationManager.pause(2 ** PAUSED_OPERATOR_SET_REGISTRATION_AND_DEREGISTRATION); + cheats.expectRevert(IPausable.CurrentlyPaused.selector); + allocationManager.deregisterFromOperatorSets(defaultDeregisterParams); + } + + function testFuzz_deregisterFromOperatorSets_InvalidCaller( + Randomness r + ) public rand(r) { + cheats.prank(r.Address()); + cheats.expectRevert(InvalidCaller.selector); + allocationManager.deregisterFromOperatorSets(defaultDeregisterParams); + } + + function testFuzz_deregisterFromOperatorSets_InvalidOperatorSet( + Randomness r + ) public rand(r) { + defaultDeregisterParams.operatorSetIds = uint32(1).toArrayU32(); // invalid id + cheats.prank(defaultOperator); + cheats.expectRevert(InvalidOperatorSet.selector); + allocationManager.deregisterFromOperatorSets(defaultDeregisterParams); + } + + function testFuzz_deregisterFromOperatorSets_NotMemberOfSet( + Randomness r + ) public rand(r) { + defaultDeregisterParams.operator = r.Address(); + cheats.prank(defaultDeregisterParams.operator); + cheats.expectRevert(NotMemberOfSet.selector); + allocationManager.deregisterFromOperatorSets(defaultDeregisterParams); + } + + function testFuzz_deregisterFromOperatorSets_Correctness( + Randomness r + ) public rand(r) { + uint256 numOpSets = r.Uint256(1, 32); + uint32[] memory operatorSetIds = new uint32[](numOpSets); + CreateSetParams[] memory createSetParams = new CreateSetParams[](numOpSets); + + for (uint256 i; i < numOpSets; ++i) { + operatorSetIds[i] = r.Uint32(1, type(uint32).max); + createSetParams[i].operatorSetId = operatorSetIds[i]; + createSetParams[i].strategies = defaultStrategies; + } + + cheats.prank(defaultAVS); + allocationManager.createOperatorSets(createSetParams); + + address operator = r.Address(); + delegationManagerMock.setIsOperator(operator, true); + + cheats.prank(operator); + allocationManager.registerForOperatorSets(RegisterParams(defaultAVS, operatorSetIds, "")); + + for (uint256 j; j < numOpSets; ++j) { + cheats.expectEmit(true, true, false, false, address(allocationManager)); + emit OperatorRemovedFromOperatorSet(operator, OperatorSet(defaultAVS, operatorSetIds[j])); + } + + cheats.expectCall( + defaultAVS, abi.encodeWithSelector(IAVSRegistrar.deregisterOperator.selector, operator, operatorSetIds) + ); + + cheats.prank(operator); + allocationManager.deregisterFromOperatorSets(DeregisterParams(operator, defaultAVS, operatorSetIds)); + + require(allocationManager.getRegisteredSets(operator).length == 0, "should not be registered for any sets"); + + for (uint256 k; k < numOpSets; ++k) { + require( + allocationManager.getMemberCount(OperatorSet(defaultAVS, operatorSetIds[k])) == 0, + "should not be member of set" + ); + } + } +} + +contract AllocationManagerUnitTests_addStrategiesToOperatorSet is AllocationManagerUnitTests { + function test_addStrategiesToOperatorSet_InvalidOperatorSet() public { + cheats.prank(defaultAVS); + cheats.expectRevert(InvalidOperatorSet.selector); + allocationManager.addStrategiesToOperatorSet(1, defaultStrategies); + } + + function test_addStrategiesToOperatorSet_StrategyAlreadyInOperatorSet() public { + cheats.prank(defaultAVS); + cheats.expectRevert(StrategyAlreadyInOperatorSet.selector); + allocationManager.addStrategiesToOperatorSet(defaultOperatorSet.id, defaultStrategies); + } + + function testFuzz_addStrategiesToOperatorSet_Correctness( + Randomness r + ) public rand(r) { + uint256 numStrategies = r.Uint256(1, 32); + + IStrategy[] memory strategies = new IStrategy[](numStrategies); + + for (uint256 i; i < numStrategies; ++i) { + strategies[i] = IStrategy(r.Address()); + cheats.expectEmit(true, false, false, false, address(allocationManager)); + emit StrategyAddedToOperatorSet(defaultOperatorSet, strategies[i]); + } + + cheats.prank(defaultAVS); + allocationManager.addStrategiesToOperatorSet(defaultOperatorSet.id, strategies); + + IStrategy[] memory strategiesInSet = allocationManager.getStrategiesInOperatorSet(defaultOperatorSet); + + for (uint256 j; j < numStrategies; ++j) { + require(strategiesInSet[j + 1] == strategies[j], "should be strat of set"); + } + } +} + +contract AllocationManagerUnitTests_removeStrategiesFromOperatorSet is AllocationManagerUnitTests { + using SingleItemArrayLib for *; + + function test_removeStrategiesFromOperatorSet_InvalidOperatorSet() public { + cheats.prank(defaultAVS); + cheats.expectRevert(InvalidOperatorSet.selector); + allocationManager.removeStrategiesFromOperatorSet(1, defaultStrategies); + } + + function test_removeStrategiesFromOperatorSet_StrategyNotInOperatorSet() public { + cheats.prank(defaultAVS); + cheats.expectRevert(StrategyNotInOperatorSet.selector); + allocationManager.removeStrategiesFromOperatorSet( + defaultOperatorSet.id, IStrategy(random().Address()).toArray() + ); + } + + function testFuzz_removeStrategiesFromOperatorSet_Correctness( + Randomness r + ) public rand(r) { + uint256 numStrategies = r.Uint256(1, 32); + IStrategy[] memory strategies = r.strategyArray(numStrategies); + + cheats.prank(defaultAVS); + allocationManager.addStrategiesToOperatorSet(defaultOperatorSet.id, strategies); + + for (uint256 i; i < numStrategies; ++i) { + cheats.expectEmit(true, false, false, false, address(allocationManager)); + emit StrategyRemovedFromOperatorSet(defaultOperatorSet, strategies[i]); + } + + require( + allocationManager.getStrategiesInOperatorSet(defaultOperatorSet).length == numStrategies + 1, "sanity check" + ); + + cheats.prank(defaultAVS); + allocationManager.removeStrategiesFromOperatorSet(defaultOperatorSet.id, strategies); + + // The orginal strategy should still be in the operator set. + require( + allocationManager.getStrategiesInOperatorSet(defaultOperatorSet).length == 1, "should not be strat of set" + ); + } +} + +contract AllocationManagerUnitTests_createOperatorSets is AllocationManagerUnitTests { + using SingleItemArrayLib for *; + + function test_createOperatorSets_InvalidOperatorSet() public { + cheats.prank(defaultAVS); + cheats.expectRevert(InvalidOperatorSet.selector); + allocationManager.createOperatorSets(CreateSetParams(defaultOperatorSet.id, defaultStrategies).toArray()); + } + + function testFuzz_createOperatorSets_Correctness( + Randomness r + ) public rand(r) { + address avs = r.Address(); + uint256 numOpSets = r.Uint256(1, 32); + uint256 numStrategies = r.Uint256(1, 32); + + CreateSetParams[] memory createSetParams = new CreateSetParams[](numOpSets); + + for (uint256 i; i < numOpSets; ++i) { + createSetParams[i].operatorSetId = r.Uint32(1, type(uint32).max); + createSetParams[i].strategies = r.strategyArray(numStrategies); + cheats.expectEmit(true, false, false, false, address(allocationManager)); + emit OperatorSetCreated(OperatorSet(avs, createSetParams[i].operatorSetId)); + for (uint256 j; j < numStrategies; ++j) { + cheats.expectEmit(true, false, false, false, address(allocationManager)); + emit StrategyAddedToOperatorSet( + OperatorSet(avs, createSetParams[i].operatorSetId), createSetParams[i].strategies[j] + ); + } + } + + cheats.prank(avs); + allocationManager.createOperatorSets(createSetParams); + + for (uint256 k; k < numOpSets; ++k) { + OperatorSet memory opSet = OperatorSet(avs, createSetParams[k].operatorSetId); + require(allocationManager.isOperatorSet(opSet), "should be operator set"); + IStrategy[] memory strategiesInSet = allocationManager.getStrategiesInOperatorSet(opSet); + require(strategiesInSet.length == numStrategies, "strategiesInSet length should be numStrategies"); + for (uint256 l; l < numStrategies; ++l) { + require( + allocationManager.getStrategiesInOperatorSet(opSet)[l] == createSetParams[k].strategies[l], + "should be strat of set" + ); + } + } + } +} + +contract AllocationManagerUnitTests_setAVSRegistrar is AllocationManagerUnitTests { + function test_setAVSRegistrar_Correctness() public { + IAVSRegistrar avsRegistrar = IAVSRegistrar(random().Address()); + cheats.expectEmit(true, false, false, false, address(allocationManager)); + emit AVSRegistrarSet(defaultAVS, avsRegistrar); + cheats.prank(defaultAVS); + allocationManager.setAVSRegistrar(avsRegistrar); + assertEq(address(avsRegistrar), address(allocationManager.getAVSRegistrar(defaultAVS)), "should be set"); + } +} + /** * @notice TODO Lifecycle tests - These tests combine multiple functionalities of the AllocationManager * 1. Set allocation delay > 21 days (configuration), Allocate, modify allocation delay to < 21 days, try to allocate again once new delay is set (should be able to allocate faster than 21 deays) * 2. Allocate across multiple strategies and multiple operatorSets * 3. lifecycle fuzz test allocating/deallocating across multiple opSets/strategies - * 4. HIGH PRIO - add uint16.max allocations/deallocations and then clear them + * 4. HIGH PRIO - add uint16.max allocateParams/deallocateParams and then clear them * 5. Correctness of slashable magnitudes * 6. HIGH PRIO - get gas costs of `getSlashableMagnitudes` */ diff --git a/src/test/unit/DelegationUnit.t.sol b/src/test/unit/DelegationUnit.t.sol index ad07138d9..cfef72f14 100644 --- a/src/test/unit/DelegationUnit.t.sol +++ b/src/test/unit/DelegationUnit.t.sol @@ -23,7 +23,7 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag DelegationManager delegationManagerImplementation; // Helper to use in storage - StakerScalingFactors ssf; + DepositScalingFactor dsf; // Mocks StrategyBase strategyImplementation; @@ -361,26 +361,34 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag IStrategy[] memory strategyArray = new IStrategy[](1); strategyArray[0] = strategy; - // Set scaling factors - (uint184 depositScalingFactor, uint64 beaconChainScalingFactor, bool isBeaconChainScalingFactorSet) = delegationManager.stakerScalingFactor(staker, strategy); - StakerScalingFactors memory stakerScalingFactor = StakerScalingFactors({ - depositScalingFactor: depositScalingFactor, - isBeaconChainScalingFactorSet: isBeaconChainScalingFactorSet, - beaconChainScalingFactor: beaconChainScalingFactor - }); - + // Calculate the amount of slashing to apply uint64 maxMagnitude = allocationManagerMock.getMaxMagnitudes(operator, strategyArray)[0]; - uint256 sharesToWithdraw = depositSharesToWithdraw.toShares(stakerScalingFactor, maxMagnitude); + uint256 slashingFactor = _getSlashingFactor(staker, strategy, maxMagnitude); - uint256 scaledShares = SlashingLib.scaleSharesForQueuedWithdrawal( - sharesToWithdraw, - stakerScalingFactor, - maxMagnitude - ); + DepositScalingFactor memory _dsf = DepositScalingFactor(delegationManager.depositScalingFactor(staker, strategy)); + uint256 sharesToWithdraw = _dsf.calcWithdrawable(depositSharesToWithdraw, slashingFactor); + + uint256 scaledShares = SlashingLib.scaleSharesForQueuedWithdrawal({ + sharesToWithdraw: sharesToWithdraw, + slashingFactor: slashingFactor + }); return scaledShares; } + function _getSlashingFactor( + address staker, + IStrategy strategy, + uint64 operatorMaxMagnitude + ) internal view returns (uint256) { + if (strategy == beaconChainETHStrategy) { + uint64 beaconChainSlashingFactor = delegationManager.getBeaconChainSlashingFactor(staker); + return operatorMaxMagnitude.mulWad(beaconChainSlashingFactor); + } + + return operatorMaxMagnitude; + } + /** * Deploy and deposit staker into a single strategy, then set up a queued withdrawal for the staker * Assumptions: @@ -414,8 +422,12 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag currentAmounts[0] = depositAmount - withdrawalAmount; strategyManagerMock.setDeposits(staker, strategies, currentAmounts); - IERC20[] memory tokens = new IERC20[](1); - tokens[0] = strategies[0].underlyingToken(); + IERC20[] memory tokens = new IERC20[](strategies.length); + + for (uint i = 0; i < tokens.length; i++) { + tokens[i] = strategies[i].underlyingToken(); + } + return (withdrawal, tokens, withdrawalRoot); } @@ -461,7 +473,7 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag } contract DelegationManagerUnitTests_Initialization_Setters is DelegationManagerUnitTests { - function test_initialization() public { + function test_initialization() public view { assertEq( address(delegationManager.strategyManager()), address(strategyManagerMock), @@ -518,7 +530,6 @@ contract DelegationManagerUnitTests_RegisterModifyOperator is DelegationManagerU // @notice Verifies that someone cannot successfully call `DelegationManager.registerAsOperator(operatorDetails)` again after registering for the first time function testFuzz_registerAsOperator_revert_cannotRegisterMultipleTimes( - address operator, IDelegationManagerTypes.OperatorDetails memory operatorDetails ) public { // Register once @@ -590,6 +601,12 @@ contract DelegationManagerUnitTests_RegisterModifyOperator is DelegationManagerU cheats.stopPrank(); } + + /// TODO: Add test for registerAsOperator where the operator has existing deposits in strategies + /// Assert: + /// depositShares == operatorShares == withdrawableShares + /// check operatorDetails hash encode matches the operatorDetails hash stored (call view function) + function testFuzz_registerAsOperator_withDeposits() public {} /// TODO: Add test for registerAsOperator where the operator has existing deposits in strategies /// Assert: @@ -643,7 +660,7 @@ contract DelegationManagerUnitTests_RegisterModifyOperator is DelegationManagerU * @dev This is an important check to ensure that our definition of 'operator' remains consistent, in particular for preserving the * invariant that 'operators' are always delegated to themselves */ - function testFuzz_Revert_updateOperatorMetadataUri_revert_notOperator( + function testFuzz_Revert_updateOperatorMetadataUri_notOperator( IDelegationManagerTypes.OperatorDetails memory operatorDetails ) public { cheats.expectRevert(IDelegationManagerErrors.OperatorNotRegistered.selector); @@ -808,7 +825,6 @@ contract DelegationManagerUnitTests_delegateTo is DelegationManagerUnitTests { uint256[] memory sharesToReturn = new uint256[](1); sharesToReturn[0] = shares; strategyManagerMock.setDeposits(staker, strategiesToReturn, sharesToReturn); - uint256 operatorSharesBefore = delegationManager.operatorShares(defaultOperator, strategyMock); // Set the operators magnitude to be 0 _setOperatorMagnitude(defaultOperator, strategyMock, 0); @@ -2120,9 +2136,10 @@ contract DelegationManagerUnitTests_ShareAdjustment is DelegationManagerUnitTest if (delegationManager.isDelegated(staker)) { cheats.expectEmit(true, true, true, true, address(delegationManager)); emit OperatorSharesIncreased(defaultOperator, staker, strategyMock, shares); - ssf.updateDepositScalingFactor(0, shares, magnitude); + uint256 slashingFactor = _getSlashingFactor(staker, strategyMock, magnitude); + dsf.update(0, shares, slashingFactor); cheats.expectEmit(true, true, true, true, address(delegationManager)); - emit DepositScalingFactorUpdated(staker, strategyMock, ssf.depositScalingFactor); + emit DepositScalingFactorUpdated(staker, strategyMock, dsf.scalingFactor()); } cheats.prank(address(strategyManagerMock)); @@ -2199,7 +2216,7 @@ contract DelegationManagerUnitTests_ShareAdjustment is DelegationManagerUnitTest ) public filterFuzzedAddressInputs(staker) { // remeber to filter fuzz inputs cheats.assume(staker != defaultOperator); cheats.assume(shares > existingShares); - uint64 initialMagnitude = uint64(bound(initialMagnitude, 1, WAD)); + initialMagnitude = uint64(bound(initialMagnitude, 1, WAD)); // 1. Register operator with initial operator magnitude and delegate staker to operator _registerOperatorWithBaseDetails(defaultOperator); @@ -2269,7 +2286,7 @@ contract DelegationManagerUnitTests_ShareAdjustment is DelegationManagerUnitTest cheats.assume(staker != defaultOperator); cheats.assume(address(strategy) != address(strategyMock)); - uint64 magnitude = uint64(bound(magnitude, 1, WAD)); + magnitude = uint64(bound(magnitude, 1, WAD)); // Register operator _registerOperatorWithBaseDetails(defaultOperator); @@ -2295,9 +2312,10 @@ contract DelegationManagerUnitTests_ShareAdjustment is DelegationManagerUnitTest cheats.expectEmit(true, true, true, true, address(delegationManager)); emit OperatorSharesIncreased(defaultOperator, staker, strategy, shares); - ssf.updateDepositScalingFactor(0, shares, magnitude); + uint256 slashingFactor = _getSlashingFactor(staker, strategyMock, magnitude); + dsf.update(0, shares, slashingFactor); cheats.expectEmit(true, true, true, true, address(delegationManager)); - emit DepositScalingFactorUpdated(staker, strategy, ssf.depositScalingFactor); + emit DepositScalingFactorUpdated(staker, strategy, dsf.scalingFactor()); cheats.prank(address(strategyManagerMock)); delegationManager.increaseDelegatedShares(staker, strategy, 0, shares); @@ -2675,7 +2693,7 @@ contract DelegationManagerUnitTests_Undelegate is DelegationManagerUnitTests { // delegate from the `staker` to them _delegateToOperatorWhoAcceptsAllStakers(defaultStaker, defaultOperator); - (uint256 depositScalingFactor,,) = delegationManager.stakerScalingFactor(defaultStaker, strategyMock); + uint256 depositScalingFactor = delegationManager.depositScalingFactor(defaultStaker, strategyMock); assertTrue(depositScalingFactor > WAD, "bad test setup"); // Format queued withdrawal @@ -2714,7 +2732,7 @@ contract DelegationManagerUnitTests_Undelegate is DelegationManagerUnitTests { assertEq(delegationManager.operatorShares(defaultOperator, strategyMock), 0, "operator shares not decreased correctly"); (uint256[] memory stakerWithdrawableShares, ) = delegationManager.getWithdrawableShares(defaultStaker, strategies); assertEq(stakerWithdrawableShares[0], 0, "staker withdrawable shares not calculated correctly"); - (uint256 newDepositScalingFactor,,) = delegationManager.stakerScalingFactor(defaultStaker, strategyMock); + uint256 newDepositScalingFactor = delegationManager.depositScalingFactor(defaultStaker, strategyMock); assertEq(newDepositScalingFactor, WAD, "staker scaling factor not reset correctly"); } @@ -2754,7 +2772,7 @@ contract DelegationManagerUnitTests_Undelegate is DelegationManagerUnitTests { assertEq(operatorSharesAfterSlash, operatorSharesBefore / 2, "operator shares not properly updated"); } - (uint256 depositScalingFactor,,) = delegationManager.stakerScalingFactor(defaultStaker, strategyMock); + uint256 depositScalingFactor = delegationManager.depositScalingFactor(defaultStaker, strategyMock); assertEq(depositScalingFactor, WAD, "bad test setup"); // Get withdrawable shares @@ -2796,7 +2814,7 @@ contract DelegationManagerUnitTests_Undelegate is DelegationManagerUnitTests { assertEq(delegationManager.operatorShares(defaultOperator, strategyMock), 0, "operator shares not decreased correctly"); (uint256[] memory stakerWithdrawableShares, ) = delegationManager.getWithdrawableShares(defaultStaker, strategies); assertEq(stakerWithdrawableShares[0], 0, "staker withdrawable shares not calculated correctly"); - (uint256 newDepositScalingFactor,,) = delegationManager.stakerScalingFactor(defaultStaker, strategyMock); + uint256 newDepositScalingFactor = delegationManager.depositScalingFactor(defaultStaker, strategyMock); assertEq(newDepositScalingFactor, WAD, "staker scaling factor not reset correctly"); } @@ -2824,7 +2842,6 @@ contract DelegationManagerUnitTests_Undelegate is DelegationManagerUnitTests { uint64 operatorMagnitude = 0; uint256 operatorSharesAfterSlash; { - uint256 operatorSharesBefore = delegationManager.operatorShares(defaultOperator, strategyMock); _setOperatorMagnitude(defaultOperator, strategyMock, operatorMagnitude); cheats.prank(address(allocationManagerMock)); delegationManager.decreaseOperatorShares(defaultOperator, strategyMock, WAD); @@ -2832,7 +2849,7 @@ contract DelegationManagerUnitTests_Undelegate is DelegationManagerUnitTests { assertEq(operatorSharesAfterSlash, 0, "operator shares not fully slashed"); } - (uint256 depositScalingFactor,,) = delegationManager.stakerScalingFactor(defaultStaker, strategyMock); + uint256 depositScalingFactor = delegationManager.depositScalingFactor(defaultStaker, strategyMock); assertEq(depositScalingFactor, WAD, "bad test setup"); // Get withdrawable shares @@ -2863,7 +2880,7 @@ contract DelegationManagerUnitTests_Undelegate is DelegationManagerUnitTests { assertEq(delegationManager.operatorShares(defaultOperator, strategyMock), 0, "operator shares not decreased correctly"); (uint256[] memory stakerWithdrawableShares, ) = delegationManager.getWithdrawableShares(defaultStaker, strategies); assertEq(stakerWithdrawableShares[0], 0, "staker withdrawable shares not calculated correctly"); - (uint256 newDepositScalingFactor,,) = delegationManager.stakerScalingFactor(defaultStaker, strategyMock); + uint256 newDepositScalingFactor = delegationManager.depositScalingFactor(defaultStaker, strategyMock); assertEq(newDepositScalingFactor, WAD, "staker scaling factor not reset correctly"); } @@ -2890,7 +2907,7 @@ contract DelegationManagerUnitTests_Undelegate is DelegationManagerUnitTests { // delegate from the `staker` to them _delegateToOperatorWhoAcceptsAllStakers(defaultStaker, defaultOperator); - (uint256 depositScalingFactor,,) = delegationManager.stakerScalingFactor(defaultStaker, strategyMock); + uint256 depositScalingFactor = delegationManager.depositScalingFactor(defaultStaker, strategyMock); assertEq(depositScalingFactor, uint256(WAD).divWad(uint256(operatorMagnitude)), "first deposit should result in k value of (1 / magnitude)"); uint256 operatorSharesBefore = delegationManager.operatorShares(defaultOperator, strategyMock); @@ -2906,13 +2923,9 @@ contract DelegationManagerUnitTests_Undelegate is DelegationManagerUnitTests { depositSharesToWithdraw: shares }); - StakerScalingFactors memory ssf = StakerScalingFactors({ - depositScalingFactor: uint184(depositScalingFactor), - isBeaconChainScalingFactorSet: false, - beaconChainScalingFactor: 0 - }); - - uint256 operatorSharesDecreased = uint256(shares).toShares(ssf, operatorMagnitude); + DepositScalingFactor memory _dsf = DepositScalingFactor(depositScalingFactor); + uint256 slashingFactor = _getSlashingFactor(defaultStaker, strategyMock, operatorMagnitude); + uint256 operatorSharesDecreased = _dsf.calcWithdrawable(shares, slashingFactor); // Undelegate the staker cheats.expectEmit(true, true, true, true, address(delegationManager)); @@ -2937,7 +2950,7 @@ contract DelegationManagerUnitTests_Undelegate is DelegationManagerUnitTests { // Checks - operator & staker shares (uint256[] memory stakerWithdrawableShares, ) = delegationManager.getWithdrawableShares(defaultStaker, strategies); assertEq(stakerWithdrawableShares[0], 0, "staker withdrawable shares not calculated correctly"); - (uint256 newDepositScalingFactor,,) = delegationManager.stakerScalingFactor(defaultStaker, strategyMock); + uint256 newDepositScalingFactor = delegationManager.depositScalingFactor(defaultStaker, strategyMock); assertEq(newDepositScalingFactor, WAD, "staker scaling factor not reset correctly"); // // Re-delegate the staker to the operator again. The shares should have increased but may be less than from before due to rounding @@ -3209,15 +3222,10 @@ contract DelegationManagerUnitTests_queueWithdrawals is DelegationManagerUnitTes uint256 nonceAfter = delegationManager.cumulativeWithdrawalsQueued(defaultStaker); uint256 delegatedSharesAfter = delegationManager.operatorShares(defaultOperator, strategies[0]); - { - (uint256 depositScalingFactor, uint64 beaconChainScalingFactor, bool isBeaconChainScalingFactorSet) = delegationManager.stakerScalingFactor(defaultStaker, strategyMock); - ssf = StakerScalingFactors({ - depositScalingFactor: uint184(depositScalingFactor), - beaconChainScalingFactor: beaconChainScalingFactor, - isBeaconChainScalingFactorSet: isBeaconChainScalingFactorSet - }); - } - uint256 sharesWithdrawn = withdrawalAmount.toShares(ssf, 5e17); + uint256 slashingFactor = _getSlashingFactor(defaultStaker, strategyMock, operatorMagnitude); + dsf = DepositScalingFactor(delegationManager.depositScalingFactor(defaultStaker, strategyMock)); + + uint256 sharesWithdrawn = dsf.calcWithdrawable(withdrawalAmount, slashingFactor); assertEq(nonceBefore + 1, nonceAfter, "staker nonce should have incremented"); assertApproxEqRel( delegatedSharesBefore - sharesWithdrawn, @@ -3228,16 +3236,18 @@ contract DelegationManagerUnitTests_queueWithdrawals is DelegationManagerUnitTes } /** - * @notice Verifies that `DelegationManager.queueWithdrawals` reverts when queuing a withdrawal for the `withdrawer` + * @notice Verifies that `DelegationManager.queueWithdrawals` queues an empty withdrawal for the `withdrawer` * from the `strategy` for the `sharesAmount` since the Operator is slashed 100% while the staker is deposited - * - Asserts that queuing a withdrawal reverts when the operator is slashed 100% + * - Asserts that queuing a withdrawal results in an empty withdrawal when the operator is slashed 100% * - Asserts that staker withdrawableShares after is 0 * - Checks that event was emitted with correct withdrawalRoot and withdrawal */ - function testFuzz_Revert_queueWithdrawal_SingleStrat_slashed100PercentWhileStaked( + function testFuzz_queueWithdrawal_SingleStrat_slashed100PercentWhileStaked( uint128 depositAmount, uint128 withdrawalAmount ) public { + withdrawalAmount = uint128(bound(withdrawalAmount, 0, depositAmount)); + // Register operator _registerOperatorWithBaseDetails(defaultOperator); @@ -3271,11 +3281,9 @@ contract DelegationManagerUnitTests_queueWithdrawals is DelegationManagerUnitTes } assertEq(delegationManager.delegatedTo(defaultStaker), defaultOperator, "staker should be delegated to operator"); - uint256 delegatedSharesBefore = delegationManager.operatorShares(defaultOperator, strategies[0]); - // queueWithdrawals should revert from the 100% slashing + // queueWithdrawals should result in an empty withdrawal cheats.prank(defaultStaker); - cheats.expectRevert(IDelegationManagerErrors.FullySlashed.selector); delegationManager.queueWithdrawals(queuedWithdrawalParams); (uint256[] memory withdrawableShares, ) = delegationManager.getWithdrawableShares(defaultStaker, strategies); @@ -3284,6 +3292,14 @@ contract DelegationManagerUnitTests_queueWithdrawals is DelegationManagerUnitTes 0, "withdrawable shares should be 0 after being slashed fully" ); + + (IDelegationManagerTypes.Withdrawal[] memory withdrawals, uint[][] memory shares) + = delegationManager.getQueuedWithdrawals(defaultStaker); + + assertEq(withdrawals.length, 1, "staker should have a single withdrawal"); + assertEq(shares.length, 1, "output arrays should have equal length"); + assertEq(shares[0].length, 1, "withdrawal should consider a single strategy"); + assertEq(shares[0][0], 0, "withdrawal should be for 0 shares"); } /** @@ -3418,7 +3434,6 @@ contract DelegationManagerUnitTests_completeQueuedWithdrawal is DelegationManage ( IDelegationManagerTypes.Withdrawal memory withdrawal, IERC20[] memory tokens, - bytes32 withdrawalRoot ) = _setUpCompleteQueuedWithdrawalSingleStrat({ staker: defaultStaker, withdrawer: defaultStaker, @@ -3831,4 +3846,4 @@ contract DelegationManagerUnitTests_completeQueuedWithdrawal is DelegationManage 7. RegisterOperator, Deposit/Delegate, Mock Slash 100% (set maxMagnitudes), Undelegate, Complete non 100% slashed strategies 8. RegisterOperator, Deposit/Delegate, Undelegate, Re delegate to another operator, Mock Slash 100% (set maxMagnitudes), Complete as shares (withdrawals should have been slashed even though delegated to a new operator) - */ \ No newline at end of file + */ diff --git a/src/test/unit/EigenPodManagerUnit.t.sol b/src/test/unit/EigenPodManagerUnit.t.sol index b10b9a565..5903ee285 100644 --- a/src/test/unit/EigenPodManagerUnit.t.sol +++ b/src/test/unit/EigenPodManagerUnit.t.sol @@ -75,7 +75,7 @@ contract EigenPodManagerUnitTests is EigenLayerUnitTestSetup, IEigenPodManagerEv function _initializePodWithShares(address podOwner, int256 shares) internal { // Deploy pod - IEigenPod deployedPod = _deployAndReturnEigenPodForStaker(podOwner); + _deployAndReturnEigenPodForStaker(podOwner); if (shares >= 0) { cheats.prank(address(delegationManagerMock)); @@ -98,7 +98,7 @@ contract EigenPodManagerUnitTests is EigenLayerUnitTestSetup, IEigenPodManagerEv return deployedPod; } - function _checkPodDeployed(address staker, address expectedPod, uint256 numPodsBefore) internal { + function _checkPodDeployed(address staker, address expectedPod, uint256 numPodsBefore) internal view { assertEq(address(eigenPodManager.ownerToPod(staker)), expectedPod, "Expected pod not deployed"); assertEq(eigenPodManager.numPods(), numPodsBefore + 1, "Num pods not incremented"); } @@ -110,7 +110,7 @@ contract EigenPodManagerUnitTests_Initialization_Setters is EigenPodManagerUnitT Initialization Tests *******************************************************************************/ - function test_initialization() public { + function test_initialization() public view { // Check max pods, beacon chain, owner, and pauser assertEq(eigenPodManager.owner(), initialOwner, "Initialization: owner incorrect"); assertEq(address(eigenPodManager.pauserRegistry()), address(pauserRegistry), "Initialization: pauser registry incorrect"); @@ -445,10 +445,10 @@ contract EigenPodManagerUnitTests_BeaconChainETHBalanceUpdateTests is EigenPodMa eigenPodManager.recordBeaconChainETHBalanceUpdate(defaultStaker, 0, 0); } - function testFuzz_recordBalanceUpdate(int224 sharesBefore, int224 sharesDelta) public { + function testFuzz_recordBalanceUpdate(int256 sharesBefore, int256 sharesDelta) public { // Constrain inputs - int256 sharesBefore = sharesBefore * int256(GWEI_TO_WEI); - int256 sharesDelta = sharesDelta * int256(GWEI_TO_WEI); + sharesBefore = int224(bound(sharesBefore, type(int224).min, type(int224).max)) * int256(GWEI_TO_WEI); + sharesDelta = int224(bound(sharesDelta, type(int224).min, type(int224).max)) * int256(GWEI_TO_WEI); // Initialize shares _initializePodWithShares(defaultStaker, sharesBefore); diff --git a/src/test/unit/EigenPodUnit.t.sol b/src/test/unit/EigenPodUnit.t.sol index bc278b9c8..09861a8e8 100644 --- a/src/test/unit/EigenPodUnit.t.sol +++ b/src/test/unit/EigenPodUnit.t.sol @@ -245,7 +245,7 @@ contract EigenPodUnitTests is EigenLayerUnitTestSetup, EigenPodPausingConstants, function assert_ProofsRemainingEqualsActive( EigenPodUser staker, string memory err - ) internal { + ) internal view { EigenPod pod = staker.pod(); assertEq(pod.currentCheckpoint().proofsRemaining, pod.activeValidatorCount(), err); } @@ -1732,7 +1732,7 @@ contract EigenPodUnitTests_proofParsingTests is EigenPodHarnessSetup, ProofParsi bytes validatorFieldsProof; bytes32[] validatorFields; - function _assertWithdrawalCredentialsSet(uint256 restakedBalanceGwei) internal { + function _assertWithdrawalCredentialsSet(uint256 restakedBalanceGwei) internal view { IEigenPodTypes.ValidatorInfo memory validatorInfo = eigenPodHarness.validatorPubkeyHashToInfo(validatorFields[0]); assertEq(uint8(validatorInfo.status), uint8(IEigenPodTypes.VALIDATOR_STATUS.ACTIVE), "Validator status should be active"); assertEq(validatorInfo.validatorIndex, validatorIndex, "Validator index incorrectly set"); diff --git a/src/test/unit/PausableUnit.t.sol b/src/test/unit/PausableUnit.t.sol index 4b939132c..54e4c8b8f 100644 --- a/src/test/unit/PausableUnit.t.sol +++ b/src/test/unit/PausableUnit.t.sol @@ -25,6 +25,9 @@ contract PausableUnitTests is Test { /// @notice Emitted when the pause is lifted by `account`, and changed to `newPausedStatus`. event Unpaused(address indexed account, uint256 newPausedStatus); + /// @notice Emitted when the `pauserRegistry` is set to `newPauserRegistry`. + event PauserRegistrySet(IPauserRegistry pauserRegistry, IPauserRegistry newPauserRegistry); + function setUp() virtual public { address[] memory pausers = new address[](1); pausers[0] = pauser; @@ -160,5 +163,4 @@ contract PausableUnitTests is Test { pausable.unpause(newPausedStatus); cheats.stopPrank(); } - } diff --git a/src/test/unit/RewardsCoordinatorUnit.t.sol b/src/test/unit/RewardsCoordinatorUnit.t.sol index 66b9080db..0e7b15b73 100644 --- a/src/test/unit/RewardsCoordinatorUnit.t.sol +++ b/src/test/unit/RewardsCoordinatorUnit.t.sol @@ -1653,7 +1653,7 @@ contract RewardsCoordinatorUnitTests_processClaim is RewardsCoordinatorUnitTests function testFuzz_processClaim_Revert_WhenRootDisabled( bool setClaimerFor, address claimerFor, - bytes32 merkleRoot + bytes32 root ) public filterFuzzedAddressInputs(claimerFor) { // if setClaimerFor is true, set the earners claimer to the fuzzed address address claimer; @@ -1667,7 +1667,7 @@ contract RewardsCoordinatorUnitTests_processClaim is RewardsCoordinatorUnitTests // Submit a root and disable it cheats.startPrank(rewardsUpdater); - rewardsCoordinator.submitRoot(merkleRoot, 1); + rewardsCoordinator.submitRoot(root, 1); uint32 rootIndex = 0; IRewardsCoordinator.DistributionRoot memory distributionRoot = rewardsCoordinator.getDistributionRootAtIndex(rootIndex); rewardsCoordinator.disableRoot(rootIndex); diff --git a/src/test/unit/StrategyBaseUnit.t.sol b/src/test/unit/StrategyBaseUnit.t.sol index c8543b509..13a80673b 100644 --- a/src/test/unit/StrategyBaseUnit.t.sol +++ b/src/test/unit/StrategyBaseUnit.t.sol @@ -317,7 +317,7 @@ contract StrategyBaseUnitTests is Test { } // uint240 input to prevent overflow - function testIntegrityOfSharesToUnderlyingWithZeroTotalShares(uint240 amountSharesToQuery) public { + function testIntegrityOfSharesToUnderlyingWithZeroTotalShares(uint240 amountSharesToQuery) public view { uint256 underlyingFromShares = strategy.sharesToUnderlying(amountSharesToQuery); require(underlyingFromShares == amountSharesToQuery, "underlyingFromShares != amountSharesToQuery"); diff --git a/src/test/unit/StrategyFactoryUnit.t.sol b/src/test/unit/StrategyFactoryUnit.t.sol index 8754267fc..577585116 100644 --- a/src/test/unit/StrategyFactoryUnit.t.sol +++ b/src/test/unit/StrategyFactoryUnit.t.sol @@ -68,7 +68,7 @@ contract StrategyFactoryUnitTests is EigenLayerUnitTestSetup { ); } - function test_initialization() public { + function test_initialization() public view { assertEq( address(strategyFactory.strategyManager()), address(strategyManagerMock), diff --git a/src/test/unit/StrategyManagerUnit.t.sol b/src/test/unit/StrategyManagerUnit.t.sol index 1719305e8..bfa67ff61 100644 --- a/src/test/unit/StrategyManagerUnit.t.sol +++ b/src/test/unit/StrategyManagerUnit.t.sol @@ -236,7 +236,7 @@ contract StrategyManagerUnitTests_initialize is StrategyManagerUnitTests { strategyManager.initialize(initialOwner, initialOwner, 0); } - function test_InitializedStorageProperly() public { + function test_InitializedStorageProperly() public view { assertEq(strategyManager.owner(), initialOwner, "strategyManager.owner() != initialOwner"); assertEq( strategyManager.strategyWhitelister(), @@ -364,7 +364,6 @@ contract StrategyManagerUnitTests_depositIntoStrategy is StrategyManagerUnitTest // pod = new EigenPod(ethPOSDeposit, eigenPodManager, GOERLI_GENESIS_TIME); // eigenPodBeacon = new UpgradeableBeacon(address(pod)); - // // Second, deploy the *implementation* contracts, using the *proxy contracts* as inputs // DelegationManager delegationImplementation = new DelegationManager(avsDirectory, strategyManager, eigenPodManager, allocationManager, MIN_WITHDRAWAL_DELAY); // StrategyManager strategyManagerImplementation = new StrategyManager(delegation); @@ -456,7 +455,6 @@ contract StrategyManagerUnitTests_depositIntoStrategy is StrategyManagerUnitTest // "_testDepositToStrategy: stakerStrategyList array updated incorrectly" // ); // } - // // check that the shares out match the expected amount out // // the actual transfer in will be lower by 1-2 wei than expected due to stETH's internal rounding // // to account for this we check approximate rather than strict equivalence here @@ -595,7 +593,6 @@ contract StrategyManagerUnitTests_depositIntoStrategy is StrategyManagerUnitTest // strategyManager.addStrategiesToDepositWhitelist(_strategy); // cheats.stopPrank(); // } - // uint256 operatorSharesBefore = strategyManager.stakerDepositShares(sender, oneWeiFeeOnTransferTokenStrategy); // // check the expected output // uint256 expectedSharesOut = oneWeiFeeOnTransferTokenStrategy.underlyingToShares(amountToDeposit); @@ -614,7 +611,6 @@ contract StrategyManagerUnitTests_depositIntoStrategy is StrategyManagerUnitTest // "_testDepositToStrategy: stakerStrategyList array updated incorrectly" // ); // } - // // check that the shares out match the expected amount out // // the actual transfer in will be lower by 1 wei than expected due to stETH's internal rounding // // to account for this we check approximate rather than strict equivalence here diff --git a/src/test/utils/EigenLayerUnitTestSetup.sol b/src/test/utils/EigenLayerUnitTestSetup.sol index 9bbde7568..9c1894a0b 100644 --- a/src/test/utils/EigenLayerUnitTestSetup.sol +++ b/src/test/utils/EigenLayerUnitTestSetup.sol @@ -17,7 +17,14 @@ import "src/test/mocks/DelegationManagerMock.sol"; import "src/test/mocks/EigenPodManagerMock.sol"; import "src/test/mocks/EmptyContract.sol"; +import "src/test/utils/SingleItemArrayLib.sol"; +import "src/test/utils/Random.sol"; + abstract contract EigenLayerUnitTestSetup is Test { + using SingleItemArrayLib for *; + + uint256 internal constant MAX_PRIVATE_KEY = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140; + Vm cheats = Vm(VM_ADDRESS); address constant pauser = address(555); @@ -41,6 +48,15 @@ abstract contract EigenLayerUnitTestSetup is Test { _; } + modifier rand(Randomness r) { + r.set(); + _; + } + + function random() internal returns (Randomness) { + return Randomness.wrap(Random.SEED).shuffle(); + } + function setUp() public virtual { address[] memory pausers = new address[](2); pausers[0] = pauser; diff --git a/src/test/utils/Operators.sol b/src/test/utils/Operators.sol index 7863b5ae4..1bc8acd50 100644 --- a/src/test/utils/Operators.sol +++ b/src/test/utils/Operators.sol @@ -16,19 +16,19 @@ contract Operators is Test { return string.concat(".operators[", string.concat(vm.toString(index), "].")); } - function getNumOperators() public returns(uint256) { + function getNumOperators() public view returns(uint256) { return stdJson.readUint(operatorConfigJson, ".numOperators"); } - function getOperatorAddress(uint256 index) public returns(address) { + function getOperatorAddress(uint256 index) public view returns (address) { return stdJson.readAddress(operatorConfigJson, string.concat(operatorPrefix(index), "Address")); } - function getOperatorSecretKey(uint256 index) public returns(uint256) { + function getOperatorSecretKey(uint256 index) public view returns (uint256) { return readUint(operatorConfigJson, index, "SecretKey"); } - function readUint(string memory json, uint256 index, string memory key) public returns (uint256) { + function readUint(string memory json, uint256 index, string memory key) public pure returns (uint256) { return stringToUint(stdJson.readString(json, string.concat(operatorPrefix(index), key))); } diff --git a/src/test/utils/Owners.sol b/src/test/utils/Owners.sol index f16c17c6d..6b55c70bf 100644 --- a/src/test/utils/Owners.sol +++ b/src/test/utils/Owners.sol @@ -17,11 +17,11 @@ contract Owners is Test { return string.concat(".owners[", string.concat(vm.toString(index), "].")); } - function getNumOperators() public returns(uint256) { + function getNumOperators() public view returns(uint256) { return stdJson.readUint(ownersConfigJson, ".numOwners"); } - function getOwnerAddress(uint256 index) public returns(address) { + function getOwnerAddress(uint256 index) public view returns(address) { return stdJson.readAddress(ownersConfigJson, string.concat(ownerPrefix(index), "Address")); } diff --git a/src/test/utils/ProofParsing.sol b/src/test/utils/ProofParsing.sol index ebc3e6b6b..a6a88545f 100644 --- a/src/test/utils/ProofParsing.sol +++ b/src/test/utils/ProofParsing.sol @@ -26,51 +26,51 @@ contract ProofParsing is Test { proofConfigJson = vm.readFile(path); } - function getSlot() public returns(uint256) { + function getSlot() public view returns (uint256) { return stdJson.readUint(proofConfigJson, ".slot"); } - function getValidatorIndex() public returns(uint256) { + function getValidatorIndex() public view returns (uint256) { return stdJson.readUint(proofConfigJson, ".validatorIndex"); } - function getValidatorPubkeyHash() public returns(bytes32) { + function getValidatorPubkeyHash() public view returns (bytes32) { return stdJson.readBytes32(proofConfigJson, ".ValidatorFields[0]"); } - function getWithdrawalIndex() public returns(uint256) { + function getWithdrawalIndex() public view returns (uint256) { return stdJson.readUint(proofConfigJson, ".withdrawalIndex"); } - function getBlockRootIndex() public returns(uint256) { + function getBlockRootIndex() public view returns (uint256) { return stdJson.readUint(proofConfigJson, ".blockHeaderRootIndex"); } - function getHistoricalSummaryIndex() public returns(uint256) { + function getHistoricalSummaryIndex() public view returns (uint256) { return stdJson.readUint(proofConfigJson, ".historicalSummaryIndex"); } - function getBeaconStateRoot() public returns(bytes32) { + function getBeaconStateRoot() public view returns (bytes32) { return stdJson.readBytes32(proofConfigJson, ".beaconStateRoot"); } - function getBlockRoot() public returns(bytes32) { + function getBlockRoot() public view returns (bytes32) { return stdJson.readBytes32(proofConfigJson, ".blockHeaderRoot"); } - function getSlotRoot() public returns(bytes32) { + function getSlotRoot() public view returns (bytes32) { return stdJson.readBytes32(proofConfigJson, ".slotRoot"); } - function getTimestampRoot() public returns(bytes32) { + function getTimestampRoot() public view returns (bytes32) { return stdJson.readBytes32(proofConfigJson, ".timestampRoot"); } - function getExecutionPayloadRoot() public returns(bytes32) { + function getExecutionPayloadRoot() public view returns (bytes32) { return stdJson.readBytes32(proofConfigJson, ".executionPayloadRoot"); } - function getLatestBlockRoot() public returns(bytes32) { + function getLatestBlockRoot() public view returns (bytes32) { return stdJson.readBytes32(proofConfigJson, ".latestBlockHeaderRoot"); } function getExecutionPayloadProof () public returns(bytes32[7] memory) { diff --git a/src/test/utils/Random.sol b/src/test/utils/Random.sol new file mode 100644 index 000000000..3d2e3e152 --- /dev/null +++ b/src/test/utils/Random.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "src/contracts/interfaces/IStrategy.sol"; +import "src/contracts/libraries/OperatorSetLib.sol"; + +type Randomness is uint256; + +using Random for Randomness global; + +library Random { + /// ----------------------------------------------------------------------- + /// Constants + /// ----------------------------------------------------------------------- + + // Equivalent to: `uint256(keccak256("RANDOMNESS.SEED"))`. + uint256 constant SEED = 0x93bfe7cafd9427243dc4fe8c6e706851eb6696ba8e48960dd74ecc96544938ce; + + /// Equivalent to: `uint256(keccak256("RANDOMNESS.SEED"))`. + uint256 constant SLOT = 0xd0660badbab446a974e6a19901c78a2ad88d7e4f1710b85e1cfc0878477344fd; + + /// ----------------------------------------------------------------------- + /// Helpers + /// ----------------------------------------------------------------------- + + function set( + Randomness r + ) internal returns (Randomness) { + /// @solidity memory-safe-assembly + assembly { + sstore(SLOT, r) + } + return r; + } + + function shuffle( + Randomness r + ) internal returns (Randomness) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, sload(SLOT)) + mstore(0x20, r) + r := keccak256(0x00, 0x20) + } + return r.set(); + } + + /// ----------------------------------------------------------------------- + /// Native Types + /// ----------------------------------------------------------------------- + + function Uint256(Randomness r, uint256 min, uint256 max) internal returns (uint256) { + return max <= min ? min : r.Uint256() % (max - min) + min; + } + + function Uint256( + Randomness r + ) internal returns (uint256) { + return r.shuffle().unwrap(); + } + + function Uint64(Randomness r, uint64 min, uint64 max) internal returns (uint64) { + return uint64(Uint256(r, min, max)); + } + + function Uint64( + Randomness r + ) internal returns (uint64) { + return uint64(Uint256(r)); + } + + function Uint32(Randomness r, uint32 min, uint32 max) internal returns (uint32) { + return uint32(Uint256(r, min, max)); + } + + function Uint32( + Randomness r + ) internal returns (uint32) { + return uint32(Uint256(r)); + } + + function Bytes32( + Randomness r + ) internal returns (bytes32) { + return bytes32(r.Uint256()); + } + + function Address( + Randomness r + ) internal returns (address) { + return address(uint160(r.Uint256(1, type(uint160).max))); + } + + /// ----------------------------------------------------------------------- + /// EigenLayer Types + /// ----------------------------------------------------------------------- + + function strategyArray(Randomness r, uint256 len) internal returns (IStrategy[] memory strategies) { + strategies = new IStrategy[](len); + for (uint256 i; i < len; ++i) { + strategies[i] = IStrategy(r.Address()); + } + } + + function operatorSetArray( + Randomness r, + address avs, + uint256 len + ) internal returns (OperatorSet[] memory operatorSets) { + operatorSets = new OperatorSet[](len); + for (uint256 i; i < len; ++i) { + operatorSets[i] = OperatorSet(avs, r.Uint32()); + } + } + + /// ----------------------------------------------------------------------- + /// Helpers + /// ----------------------------------------------------------------------- + + function wrap( + uint256 r + ) internal pure returns (Randomness) { + return Randomness.wrap(r); + } + + function unwrap( + Randomness r + ) internal pure returns (uint256) { + return Randomness.unwrap(r); + } +} diff --git a/src/test/utils/SingleItemArrayLib.sol b/src/test/utils/SingleItemArrayLib.sol new file mode 100644 index 000000000..f337fe8cb --- /dev/null +++ b/src/test/utils/SingleItemArrayLib.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "src/contracts/interfaces/IAllocationManager.sol"; + +/// @dev Helper library for simplifying the syntax for creating single item arrays for inputs. +library SingleItemArrayLib { + function toArrayU16( + uint16 x + ) internal pure returns (uint16[] memory array) { + array = new uint16[](1); + array[0] = x; + } + + function toArrayU32( + uint32 x + ) internal pure returns (uint32[] memory array) { + array = new uint32[](1); + array[0] = x; + } + + function toArrayU64( + uint64 x + ) internal pure returns (uint64[] memory array) { + array = new uint64[](1); + array[0] = x; + } + + function toArray( + IStrategy strategy + ) internal pure returns (IStrategy[] memory array) { + array = new IStrategy[](1); + array[0] = strategy; + } + + function toArray( + OperatorSet memory operatorSet + ) internal pure returns (OperatorSet[] memory array) { + array = new OperatorSet[](1); + array[0] = operatorSet; + } + + function toArray( + IAllocationManagerTypes.CreateSetParams memory createSetParams + ) internal pure returns (IAllocationManagerTypes.CreateSetParams[] memory array) { + array = new IAllocationManagerTypes.CreateSetParams[](1); + array[0] = createSetParams; + } + + function toArray( + IAllocationManagerTypes.AllocateParams memory allocateParams + ) internal pure returns (IAllocationManagerTypes.AllocateParams[] memory array) { + array = new IAllocationManagerTypes.AllocateParams[](1); + array[0] = allocateParams; + } +} \ No newline at end of file