diff --git a/.github/workflows/validate-deployment-scripts.yml b/.github/workflows/validate-deployment-scripts.yml index 625c6ee2ae..3789f54198 100644 --- a/.github/workflows/validate-deployment-scripts.yml +++ b/.github/workflows/validate-deployment-scripts.yml @@ -48,7 +48,7 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@82dee4ba654bd2146511f85f0d013af94670c4de with: - version: stable + version: v1.3.5 # Run Forge's formatting checker to ensure consistent code style. - name: "Forge Fmt" diff --git a/docs/core/AllocationManager.md b/docs/core/AllocationManager.md index 0003c9a78e..0bb2ee45c0 100644 --- a/docs/core/AllocationManager.md +++ b/docs/core/AllocationManager.md @@ -10,6 +10,7 @@ Libraries and Mixins: | File | Notes | | -------- | -------- | +| [`SplitContractMixin.sol`](../../src/contracts/mixins/SplitContractMixin.sol) | contract splitting for codesize optimization | | [`PermissionControllerMixin.sol`](../../src/contracts/mixins/PermissionControllerMixin.sol) | account delegation | | [`Deprecated_OwnableUpgradeable`](../../src/contracts/mixins/Deprecated_OwnableUpgradeable.sol) | deprecated ownable logic | | [`Pausable.sol`](../../src/contracts/permissions/Pausable.sol) | | @@ -29,11 +30,58 @@ The `AllocationManager` manages AVS metadata registration, registration and dere The `AllocationManager's` responsibilities are broken down into the following concepts: +* [Contract Architecture](#contract-architecture) * [AVS Metadata](#avs-metadata) * [Operator Sets](#operator-sets) * [Allocations and Slashing](#allocations-and-slashing) * [Config](#config) +## Contract Architecture + +The `AllocationManager` uses a **split contract pattern** implemented via the `SplitContractMixin` to address EVM contract size limitations while maintaining full backwards compatibility. + +```mermaid +graph TD +Alice --> |call| Proxy["AllocationManager Proxy"] +Proxy -->|delegatecall| Logic["AllocationManager Logic"] +Logic -->|_delegateView| View["AllocationManager View"] +``` + +### Split Contract Pattern + +**Main Contract (`AllocationManager`):** +- Contains all state-mutating functions (actions) +- Inherits from `SplitContractMixin` which provides delegation capabilities +- Delegates all view function calls to the separate view contract +- Maintains the same external interface as a monolithic contract + +**View Contract (`AllocationManagerView`):** +- Contains all read-only view functions +- Shares the same storage layout as the main contract using `layout at 151` directive +- Implements the same `IAllocationManagerView` interface + +### Rationale + +**Codesize Optimization:** +- The EVM has a contract size limit of 24KB (24,576 bytes) for deployed contracts +- Complex contracts like `AllocationManager` with extensive functionality can exceed this limit +- By splitting view functions into a separate contract, the main contract stays under the size limit +- This allows for more comprehensive functionality without compromising deployability + +**Backwards Compatibility:** +- The external interface remains identical to a monolithic contract +- All existing integrations continue to work without modification +- View functions are transparently delegated using `_delegateView()` +- No breaking changes to the ABI or function signatures + +**Implementation Details:** +- View functions in the main contract use `_delegateView(viewImplementation)` to delegate calls. +- The `viewImplementation` address is set during construction and stored as an immutable variable. +- The `_delegateView()` function conveniently indicates which calls are intended to be delegated as view functions, but it does not enforce this at the EVM level; rather, it signals intended usage and expected behavior to the user or integrator. +- Both contracts are aligned in storage layout, so all state variables are accessible as intended. + +This pattern is especially useful for complex contracts that require a comprehensive set of view functions while maintaining the ability to perform state mutations. It helps keep contracts deployable within EVM bytecode limits, while making clear which functions are for data retrieval. + ## Parameterization * `ALLOCATION_CONFIGURATION_DELAY`: The delay in blocks before allocations take effect. diff --git a/foundry.toml b/foundry.toml index 5f939766e9..4a63500fdf 100644 --- a/foundry.toml +++ b/foundry.toml @@ -22,7 +22,7 @@ "forge-std/=lib/forge-std/src/" ] # Specifies the exact version of Solidity to use, overriding auto-detection. - solc_version = '0.8.27' + solc_version = '0.8.30' # If enabled, treats Solidity compiler warnings as errors, preventing artifact generation if warnings are present. deny_warnings = true # If set to true, changes compilation pipeline to go through the new IR optimizer. diff --git a/script/deploy/devnet/deploy_from_scratch.s.sol b/script/deploy/devnet/deploy_from_scratch.s.sol index f196d06e9c..2f81bdb657 100644 --- a/script/deploy/devnet/deploy_from_scratch.s.sol +++ b/script/deploy/devnet/deploy_from_scratch.s.sol @@ -13,6 +13,7 @@ import "../../../src/contracts/core/DelegationManager.sol"; import "../../../src/contracts/core/AVSDirectory.sol"; import "../../../src/contracts/core/RewardsCoordinator.sol"; import "../../../src/contracts/core/AllocationManager.sol"; +import "../../../src/contracts/core/AllocationManagerView.sol"; import "../../../src/contracts/permissions/PermissionController.sol"; import "../../../src/contracts/strategies/StrategyBaseTVLLimits.sol"; import "../../../src/contracts/strategies/StrategyFactory.sol"; @@ -60,6 +61,7 @@ contract DeployFromScratch is Script, Test { StrategyBase public baseStrategyImplementation; AllocationManager public allocationManagerImplementation; AllocationManager public allocationManager; + AllocationManagerView public allocationManagerView; PermissionController public permissionController; PermissionController public permissionControllerImplementation; @@ -211,6 +213,9 @@ contract DeployFromScratch is Script, Test { strategyFactory = StrategyFactory( address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) ); + allocationManagerView = AllocationManagerView( + address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) + ); permissionController = PermissionController( address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) ); @@ -228,14 +233,15 @@ contract DeployFromScratch is Script, Test { delegationImplementation = new DelegationManager( strategyManager, eigenPodManager, - allocationManager, + IAllocationManager(address(allocationManager)), eigenLayerPauserReg, permissionController, MIN_WITHDRAWAL_DELAY, SEMVER ); - strategyManagerImplementation = new StrategyManager(allocationManager, delegation, eigenLayerPauserReg, SEMVER); + strategyManagerImplementation = + new StrategyManager(IAllocationManager(address(allocationManager)), delegation, eigenLayerPauserReg, SEMVER); avsDirectoryImplementation = new AVSDirectory(delegation, eigenLayerPauserReg, SEMVER); eigenPodManagerImplementation = new EigenPodManager(ethPOSDeposit, eigenPodBeacon, delegation, eigenLayerPauserReg, SEMVER); @@ -243,7 +249,7 @@ contract DeployFromScratch is Script, Test { IRewardsCoordinatorTypes.RewardsCoordinatorConstructorParams( delegation, strategyManager, - allocationManager, + IAllocationManager(address(allocationManager)), eigenLayerPauserReg, permissionController, REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS, @@ -255,6 +261,7 @@ contract DeployFromScratch is Script, Test { ) ); allocationManagerImplementation = new AllocationManager( + allocationManagerView, delegation, eigenStrategy, eigenLayerPauserReg, @@ -480,7 +487,7 @@ contract DeployFromScratch is Script, Test { "rewardsCoordinator: strategyManager address not set correctly" ); require( - delegationContract.allocationManager() == allocationManager, + delegationContract.allocationManager() == IAllocationManager(address(allocationManager)), "delegationManager: allocationManager address not set correctly" ); require( diff --git a/script/deploy/local/deploy_from_scratch.slashing.s.sol b/script/deploy/local/deploy_from_scratch.slashing.s.sol index 49022cdef1..3e62d14651 100644 --- a/script/deploy/local/deploy_from_scratch.slashing.s.sol +++ b/script/deploy/local/deploy_from_scratch.slashing.s.sol @@ -14,6 +14,7 @@ import "../../../src/contracts/core/DelegationManager.sol"; import "../../../src/contracts/core/AVSDirectory.sol"; import "../../../src/contracts/core/RewardsCoordinator.sol"; import "../../../src/contracts/core/AllocationManager.sol"; +import "../../../src/contracts/core/AllocationManagerView.sol"; import "../../../src/contracts/permissions/PermissionController.sol"; import "../../../src/contracts/strategies/StrategyBaseTVLLimits.sol"; @@ -65,6 +66,7 @@ contract DeployFromScratch is Script, Test { StrategyBase public baseStrategyImplementation; AllocationManager public allocationManagerImplementation; AllocationManager public allocationManager; + AllocationManagerView public allocationManagerView; PermissionController public permissionControllerImplementation; PermissionController public permissionController; @@ -219,6 +221,9 @@ contract DeployFromScratch is Script, Test { allocationManager = AllocationManager( address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) ); + allocationManagerView = AllocationManagerView( + address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) + ); permissionController = PermissionController( address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) ); @@ -238,13 +243,14 @@ contract DeployFromScratch is Script, Test { delegationImplementation = new DelegationManager( strategyManager, eigenPodManager, - allocationManager, + IAllocationManager(address(allocationManager)), eigenLayerPauserReg, permissionController, MIN_WITHDRAWAL_DELAY, SEMVER ); - strategyManagerImplementation = new StrategyManager(allocationManager, delegation, eigenLayerPauserReg, SEMVER); + strategyManagerImplementation = + new StrategyManager(IAllocationManager(address(allocationManager)), delegation, eigenLayerPauserReg, SEMVER); avsDirectoryImplementation = new AVSDirectory(delegation, eigenLayerPauserReg, SEMVER); eigenPodManagerImplementation = new EigenPodManager(ethPOSDeposit, eigenPodBeacon, delegation, eigenLayerPauserReg, SEMVER); @@ -252,7 +258,7 @@ contract DeployFromScratch is Script, Test { IRewardsCoordinatorTypes.RewardsCoordinatorConstructorParams( delegation, strategyManager, - allocationManager, + IAllocationManager(address(allocationManager)), eigenLayerPauserReg, permissionController, REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS, @@ -264,6 +270,7 @@ contract DeployFromScratch is Script, Test { ) ); allocationManagerImplementation = new AllocationManager( + allocationManagerView, delegation, eigenStrategy, eigenLayerPauserReg, diff --git a/script/releases/Env.sol b/script/releases/Env.sol index 7a5410c944..0c164a1efd 100644 --- a/script/releases/Env.sol +++ b/script/releases/Env.sol @@ -11,12 +11,14 @@ import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.so /// core/ import "src/contracts/core/AllocationManager.sol"; +import "src/contracts/core/AllocationManagerView.sol"; import "src/contracts/core/AVSDirectory.sol"; import "src/contracts/core/DelegationManager.sol"; import "src/contracts/core/RewardsCoordinator.sol"; import "src/contracts/interfaces/IRewardsCoordinator.sol"; import "src/contracts/core/StrategyManager.sol"; import "src/contracts/core/ReleaseManager.sol"; +import "src/contracts/core/ProtocolRegistry.sol"; /// permissions/ import "src/contracts/permissions/PauserRegistry.sol"; @@ -206,14 +208,20 @@ library Env { */ function allocationManager( DeployedProxy - ) internal view returns (AllocationManager) { - return AllocationManager(_deployedProxy(type(AllocationManager).name)); + ) internal view returns (IAllocationManager) { + return IAllocationManager(_deployedProxy(type(AllocationManager).name)); } function allocationManager( DeployedImpl - ) internal view returns (AllocationManager) { - return AllocationManager(_deployedImpl(type(AllocationManager).name)); + ) internal view returns (IAllocationManager) { + return IAllocationManager(_deployedImpl(type(AllocationManager).name)); + } + + function allocationManagerView( + DeployedImpl + ) internal view returns (IAllocationManagerView) { + return IAllocationManagerView(_deployedImpl(type(AllocationManagerView).name)); } function avsDirectory( @@ -276,6 +284,18 @@ library Env { return ReleaseManager(_deployedImpl(type(ReleaseManager).name)); } + function protocolRegistry( + DeployedProxy + ) internal view returns (ProtocolRegistry) { + return ProtocolRegistry(_deployedProxy(type(ProtocolRegistry).name)); + } + + function protocolRegistry( + DeployedImpl + ) internal view returns (ProtocolRegistry) { + return ProtocolRegistry(_deployedImpl(type(ProtocolRegistry).name)); + } + /** * permissions/ */ diff --git a/script/releases/v1.7.0-v1.8.0-multichain-hourglass-combined/1-deploySourceChain.s.sol b/script/releases/v1.7.0-v1.8.0-multichain-hourglass-combined/1-deploySourceChain.s.sol index 6587331d54..6dc0d3f12c 100644 --- a/script/releases/v1.7.0-v1.8.0-multichain-hourglass-combined/1-deploySourceChain.s.sol +++ b/script/releases/v1.7.0-v1.8.0-multichain-hourglass-combined/1-deploySourceChain.s.sol @@ -25,7 +25,7 @@ contract DeploySourceChain is EOADeployer { deployedTo: address( new KeyRegistrar({ _permissionController: Env.proxy.permissionController(), - _allocationManager: Env.proxy.allocationManager(), + _allocationManager: IAllocationManager(address(Env.proxy.allocationManager())), _version: Env.deployVersion() }) ) @@ -48,7 +48,7 @@ contract DeploySourceChain is EOADeployer { name: type(CrossChainRegistry).name, deployedTo: address( new CrossChainRegistry({ - _allocationManager: Env.proxy.allocationManager(), + _allocationManager: IAllocationManager(address(Env.proxy.allocationManager())), _keyRegistrar: Env.proxy.keyRegistrar(), _permissionController: Env.proxy.permissionController(), _pauserRegistry: Env.impl.pauserRegistry(), diff --git a/script/tasks/complete_withdrawal_from_strategy.s.sol b/script/tasks/complete_withdrawal_from_strategy.s.sol index 9194e22c19..22ef490cc2 100644 --- a/script/tasks/complete_withdrawal_from_strategy.s.sol +++ b/script/tasks/complete_withdrawal_from_strategy.s.sol @@ -75,7 +75,8 @@ contract CompleteWithdrawFromStrategy is Script, Test { DepositScalingFactor memory dsf = DepositScalingFactor(dm.depositScalingFactor(msg.sender, strategies[0])); // Get TM for Operator in strategies - uint64[] memory maxMagnitudes = am.getMaxMagnitudesAtBlock(msg.sender, strategies, startBlock); + uint64[] memory maxMagnitudes = + IAllocationManager(address(am)).getMaxMagnitudesAtBlock(msg.sender, strategies, startBlock); uint256 slashingFactor = _getSlashingFactor(em, msg.sender, strategies[0], maxMagnitudes[0]); uint256 sharesToWithdraw = dsf.calcWithdrawable(amount, slashingFactor); diff --git a/script/utils/ExistingDeploymentParser.sol b/script/utils/ExistingDeploymentParser.sol index cd04ddadf2..3f84dfbb06 100644 --- a/script/utils/ExistingDeploymentParser.sol +++ b/script/utils/ExistingDeploymentParser.sol @@ -10,6 +10,7 @@ import "../../src/contracts/core/DelegationManager.sol"; import "../../src/contracts/core/AVSDirectory.sol"; import "../../src/contracts/core/RewardsCoordinator.sol"; import "../../src/contracts/core/AllocationManager.sol"; +import "../../src/contracts/core/AllocationManagerView.sol"; import "../../src/contracts/permissions/PermissionController.sol"; import "../../src/contracts/strategies/StrategyFactory.sol"; @@ -104,8 +105,9 @@ contract ExistingDeploymentParser is Script, Logger { UpgradeableBeacon public strategyBeacon; /// @dev AllocationManager - AllocationManager public allocationManager; - AllocationManager public allocationManagerImplementation; + IAllocationManager public allocationManager; + IAllocationManager public allocationManagerImplementation; + IAllocationManagerView public allocationManagerView; /// @dev AVSDirectory AVSDirectory public avsDirectory; @@ -230,9 +232,16 @@ contract ExistingDeploymentParser is Script, Logger { ); // AllocationManager - allocationManager = AllocationManager(json.readAddress(".addresses.allocationManager")); + allocationManager = IAllocationManager(json.readAddress(".addresses.allocationManager")); allocationManagerImplementation = - AllocationManager(json.readAddress(".addresses.allocationManagerImplementation")); + IAllocationManager(json.readAddress(".addresses.allocationManagerImplementation")); + + // allocationManagerView = IAllocationManagerView(json.readAddress(".addresses.allocationManagerView")); + + // FIXME: hotfix - remove later... + allocationManagerView = new AllocationManagerView( + delegationManager, eigenStrategy, DEALLOCATION_DELAY, ALLOCATION_CONFIGURATION_DELAY + ); // AVSDirectory avsDirectory = AVSDirectory(json.readAddress(".addresses.avsDirectory")); diff --git a/src/contracts/core/AVSDirectory.sol b/src/contracts/core/AVSDirectory.sol index 6f76e421e3..6a30484cbb 100644 --- a/src/contracts/core/AVSDirectory.sol +++ b/src/contracts/core/AVSDirectory.sol @@ -7,7 +7,7 @@ import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol import "../mixins/SignatureUtilsMixin.sol"; import "../permissions/Pausable.sol"; -import "./AVSDirectoryStorage.sol"; +import "./storage/AVSDirectoryStorage.sol"; contract AVSDirectory is Initializable, diff --git a/src/contracts/core/AllocationManager.sol b/src/contracts/core/AllocationManager.sol index 4fedfab8f1..0845579c39 100644 --- a/src/contracts/core/AllocationManager.sol +++ b/src/contracts/core/AllocationManager.sol @@ -4,12 +4,13 @@ pragma solidity ^0.8.27; import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol"; import "../mixins/Deprecated_OwnableUpgradeable.sol"; +import "../mixins/SplitContractMixin.sol"; import "../mixins/PermissionControllerMixin.sol"; import "../mixins/SemVerMixin.sol"; import "../permissions/Pausable.sol"; import "../libraries/SlashingLib.sol"; import "../libraries/OperatorSetLib.sol"; -import "./AllocationManagerStorage.sol"; +import "./storage/AllocationManagerStorage.sol"; contract AllocationManager is Initializable, @@ -18,7 +19,9 @@ contract AllocationManager is AllocationManagerStorage, ReentrancyGuardUpgradeable, PermissionControllerMixin, - SemVerMixin + SemVerMixin, + SplitContractMixin, + IAllocationManager { using DoubleEndedQueue for DoubleEndedQueue.Bytes32Deque; using Snapshots for Snapshots.DefaultWadHistory; @@ -37,6 +40,7 @@ contract AllocationManager is * @dev Initializes the DelegationManager address, the deallocation delay, and the allocation configuration delay. */ constructor( + IAllocationManagerView _allocationManagerView, IDelegationManager _delegation, IStrategy _eigenStrategy, IPauserRegistry _pauserRegistry, @@ -47,20 +51,21 @@ contract AllocationManager is ) AllocationManagerStorage(_delegation, _eigenStrategy, _DEALLOCATION_DELAY, _ALLOCATION_CONFIGURATION_DELAY) Pausable(_pauserRegistry) + SplitContractMixin(address(_allocationManagerView)) PermissionControllerMixin(_permissionController) SemVerMixin(_version) { _disableInitializers(); } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerActions function initialize( uint256 initialPausedStatus ) external initializer { _setPausedStatus(initialPausedStatus); } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerActions function slashOperator( address avs, SlashingParams calldata params @@ -74,7 +79,7 @@ contract AllocationManager is return _slashOperator(params, operatorSet); } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerActions function modifyAllocations( address operator, AllocateParams[] memory params @@ -166,7 +171,7 @@ contract AllocationManager is } } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerActions function clearDeallocationQueue( address operator, IStrategy[] calldata strategies, @@ -178,7 +183,7 @@ contract AllocationManager is } } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerActions function registerForOperatorSets( address operator, RegisterParams calldata params @@ -205,7 +210,7 @@ contract AllocationManager is getAVSRegistrar(params.avs).registerOperator(operator, params.avs, params.operatorSetIds, params.data); } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerActions function deregisterFromOperatorSets( DeregisterParams calldata params ) external onlyWhenNotPaused(PAUSED_OPERATOR_SET_REGISTRATION_AND_DEREGISTRATION) { @@ -235,7 +240,7 @@ contract AllocationManager is getAVSRegistrar(params.avs).deregisterOperator(params.operator, params.avs, params.operatorSetIds); } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerActions function setAllocationDelay(address operator, uint32 delay) external { /// If the caller is the delegationManager, the operator is newly registered /// This results in *newly-registered* operators in the core protocol to have their allocation delay effective immediately @@ -250,7 +255,7 @@ contract AllocationManager is _setAllocationDelay(operator, delay, newlyRegistered); } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerActions function setAVSRegistrar(address avs, IAVSRegistrar registrar) external checkCanCall(avs) { // Check that the registrar is correctly configured to prevent an AVSRegistrar contract // from being used with the wrong AVS @@ -259,13 +264,13 @@ contract AllocationManager is emit AVSRegistrarSet(avs, getAVSRegistrar(avs)); } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerActions function updateAVSMetadataURI(address avs, string calldata metadataURI) external checkCanCall(avs) { if (!_avsRegisteredMetadata[avs]) _avsRegisteredMetadata[avs] = true; emit AVSMetadataURIUpdated(avs, metadataURI); } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerActions function createOperatorSets(address avs, CreateSetParams[] calldata params) external checkCanCall(avs) { require(_avsRegisteredMetadata[avs], NonexistentAVSMetadata()); for (uint256 i = 0; i < params.length; i++) { @@ -273,7 +278,7 @@ contract AllocationManager is } } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerActions function createRedistributingOperatorSets( address avs, CreateSetParams[] calldata params, @@ -289,7 +294,7 @@ contract AllocationManager is } } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerActions function addStrategiesToOperatorSet( address avs, uint32 operatorSetId, @@ -305,7 +310,7 @@ contract AllocationManager is } } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerActions function removeStrategiesFromOperatorSet( address avs, uint32 operatorSetId, @@ -629,53 +634,6 @@ contract AllocationManager is } } - /** - * @dev Returns the minimum allocated stake at the future block. - * @param operatorSet The operator set to get the minimum allocated stake for. - * @param operators The operators to get the minimum allocated stake for. - * @param strategies The strategies to get the minimum allocated stake for. - * @param futureBlock The future block to get the minimum allocated stake for. - */ - function _getMinimumAllocatedStake( - OperatorSet memory operatorSet, - address[] memory operators, - IStrategy[] memory strategies, - uint32 futureBlock - ) internal view returns (uint256[][] memory allocatedStake) { - allocatedStake = new uint256[][](operators.length); - uint256[][] memory delegatedStake = delegation.getOperatorsShares(operators, strategies); - - for (uint256 i = 0; i < operators.length; i++) { - address operator = operators[i]; - - allocatedStake[i] = new uint256[](strategies.length); - - for (uint256 j = 0; j < strategies.length; j++) { - IStrategy strategy = strategies[j]; - - // Fetch the max magnitude and allocation for the operator/strategy. - // Prevent division by 0 if needed. This mirrors the "FullySlashed" checks - // in the DelegationManager - uint64 maxMagnitude = _maxMagnitudeHistory[operator][strategy].latest(); - if (maxMagnitude == 0) { - continue; - } - - Allocation memory alloc = getAllocation(operator, operatorSet, strategy); - - // If the pending change takes effect before `futureBlock`, include it in `currentMagnitude` - // However, ONLY include the pending change if it is a deallocation, since this method - // is supposed to return the minimum slashable stake between now and `futureBlock` - if (alloc.effectBlock <= futureBlock && alloc.pendingDiff < 0) { - alloc.currentMagnitude = _addInt128(alloc.currentMagnitude, alloc.pendingDiff); - } - - uint256 slashableProportion = uint256(alloc.currentMagnitude).divWad(maxMagnitude); - allocatedStake[i][j] = delegatedStake[i][j].mulWad(slashableProportion); - } - } - } - function _updateMaxMagnitude(address operator, IStrategy strategy, uint64 newMaxMagnitude) internal { _maxMagnitudeHistory[operator][strategy].push({key: uint32(block.number), value: newMaxMagnitude}); emit MaxMagnitudeUpdated(operator, strategy, newMaxMagnitude); @@ -690,206 +648,108 @@ contract AllocationManager is return uint256(int256(int128(uint128(a)) + b)).toUint64(); } - /** - * @notice Helper function to check if an operator is redistributable from a list of operator sets - * @param operator The operator to check - * @param operatorSets The list of operator sets to check - * @return True if the operator is redistributable from any of the operator sets, false otherwise - */ - function _isOperatorRedistributable( - address operator, - OperatorSet[] memory operatorSets - ) internal view returns (bool) { - for (uint256 i = 0; i < operatorSets.length; ++i) { - if (isOperatorSlashable(operator, operatorSets[i]) && isRedistributingOperatorSet(operatorSets[i])) { - return true; - } - } - return false; - } - /** * * VIEW FUNCTIONS * */ - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerView function getOperatorSetCount( - address avs - ) external view returns (uint256) { - return _operatorSets[avs].length(); + address + ) external view returns (uint256 count) { + _delegateView(viewImplementation); + count; } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerView function getAllocatedSets( - address operator - ) public 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; + address + ) external view returns (OperatorSet[] memory operatorSets) { + _delegateView(viewImplementation); + operatorSets; } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerView function getAllocatedStrategies( - address operator, - OperatorSet memory operatorSet - ) external view returns (IStrategy[] memory) { - address[] memory values = allocatedStrategies[operator][operatorSet.key()].values(); - IStrategy[] memory strategies; - - assembly { - strategies := values - } - - return strategies; + address, + OperatorSet memory + ) external view returns (IStrategy[] memory strategies) { + _delegateView(viewImplementation); + strategies; } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerView function getAllocation( - address operator, - OperatorSet memory operatorSet, - IStrategy strategy - ) public view returns (Allocation memory) { - (, Allocation memory allocation) = _getUpdatedAllocation(operator, operatorSet.key(), strategy); - - return allocation; + address, + OperatorSet memory, + IStrategy + ) external view returns (Allocation memory allocation) { + _delegateView(viewImplementation); + allocation; } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerView 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 _allocations; + address[] memory, + OperatorSet memory, + IStrategy + ) external view returns (Allocation[] memory allocations) { + _delegateView(viewImplementation); + allocations; } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerView 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 (operatorSets, _allocations); + address, + IStrategy + ) external view returns (OperatorSet[] memory operatorSets, Allocation[] memory allocations) { + _delegateView(viewImplementation); + operatorSets; + allocations; } - /// @inheritdoc IAllocationManager - function getEncumberedMagnitude(address operator, IStrategy strategy) external view returns (uint64) { - (uint64 curEncumberedMagnitude,) = _getFreeAndUsedMagnitude(operator, strategy); - return curEncumberedMagnitude; + /// @inheritdoc IAllocationManagerView + function getEncumberedMagnitude(address, IStrategy) external view returns (uint64 encumberedMagnitude) { + _delegateView(viewImplementation); + encumberedMagnitude; } - /// @inheritdoc IAllocationManager - function getAllocatableMagnitude(address operator, IStrategy strategy) external view returns (uint64) { - (, uint64 curAllocatableMagnitude) = _getFreeAndUsedMagnitude(operator, strategy); - return curAllocatableMagnitude; + /// @inheritdoc IAllocationManagerView + function getAllocatableMagnitude(address, IStrategy) external view returns (uint64 allocatableMagnitude) { + _delegateView(viewImplementation); + allocatableMagnitude; } - /// @dev For an operator, returns up-to-date amounts for current encumbered and available - /// magnitude. Note that these two values will always add up to the operator's max magnitude - /// for the strategy - function _getFreeAndUsedMagnitude( - address operator, - IStrategy strategy - ) internal view returns (uint64 curEncumberedMagnitude, uint64 curAllocatableMagnitude) { - // This method needs to simulate clearing any pending deallocations. - // This roughly mimics the calculations done in `_clearDeallocationQueue` and - // `_getUpdatedAllocation`, while operating on a `curEncumberedMagnitude` - // rather than continually reading/updating state. - curEncumberedMagnitude = encumberedMagnitude[operator][strategy]; - - uint256 length = deallocationQueue[operator][strategy].length(); - for (uint256 i = 0; i < length; ++i) { - bytes32 operatorSetKey = deallocationQueue[operator][strategy].at(i); - Allocation memory allocation = allocations[operator][operatorSetKey][strategy]; - - // If we've reached a pending deallocation that isn't completable yet, - // we can stop. Any subsequent modifications will also be uncompletable. - 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, allocation.pendingDiff); - } - - // The difference between the operator's max magnitude and its encumbered magnitude - // is the magnitude that can be allocated. - curAllocatableMagnitude = _maxMagnitudeHistory[operator][strategy].latest() - curEncumberedMagnitude; - return (curEncumberedMagnitude, curAllocatableMagnitude); + /// @inheritdoc IAllocationManagerView + function getMaxMagnitude(address, IStrategy) external view returns (uint64 maxMagnitude) { + _delegateView(viewImplementation); + maxMagnitude; } - /// @inheritdoc IAllocationManager - function getMaxMagnitude(address operator, IStrategy strategy) public view returns (uint64) { - return _maxMagnitudeHistory[operator][strategy].latest(); + /// @inheritdoc IAllocationManagerView + function getMaxMagnitudes(address, IStrategy[] calldata) external view returns (uint64[] memory maxMagnitudes) { + _delegateView(viewImplementation); + maxMagnitudes; } - /// @inheritdoc IAllocationManager - function getMaxMagnitudes( - address operator, - IStrategy[] memory strategies - ) external view returns (uint64[] memory) { - uint64[] memory maxMagnitudes = new uint64[](strategies.length); - - for (uint256 i = 0; i < strategies.length; ++i) { - maxMagnitudes[i] = getMaxMagnitude(operator, strategies[i]); - } - - return maxMagnitudes; + /// @inheritdoc IAllocationManagerView + function getMaxMagnitudes(address[] calldata, IStrategy) external view returns (uint64[] memory maxMagnitudes) { + _delegateView(viewImplementation); + maxMagnitudes; } - /// @inheritdoc IAllocationManager - 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] = getMaxMagnitude(operators[i], strategy); - } - - return maxMagnitudes; - } - - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerView function getMaxMagnitudesAtBlock( - address operator, - 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({key: blockNumber}); - } - - return maxMagnitudes; + address, + IStrategy[] calldata, + uint32 + ) external view returns (uint64[] memory maxMagnitudes) { + _delegateView(viewImplementation); + maxMagnitudes; } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerView function getAllocationDelay( address operator ) public view returns (bool, uint32) { @@ -907,47 +767,45 @@ contract AllocationManager is return (isSet, delay); } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerView 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; + address + ) external view returns (OperatorSet[] memory operatorSets) { + _delegateView(viewImplementation); + operatorSets; } - /// @inheritdoc IAllocationManager - function isMemberOfOperatorSet(address operator, OperatorSet memory operatorSet) public view returns (bool) { - return _operatorSetMembers[operatorSet.key()].contains(operator); + /// @inheritdoc IAllocationManagerView + function isMemberOfOperatorSet(address, OperatorSet memory) external view returns (bool result) { + _delegateView(viewImplementation); + result; } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerView function isOperatorSet( - OperatorSet memory operatorSet - ) external view returns (bool) { - return _operatorSets[operatorSet.avs].contains(operatorSet.id); + OperatorSet memory + ) external view returns (bool result) { + _delegateView(viewImplementation); + result; } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerView function getMembers( - OperatorSet memory operatorSet - ) external view returns (address[] memory) { - return _operatorSetMembers[operatorSet.key()].values(); + OperatorSet memory + ) external view returns (address[] memory operators) { + _delegateView(viewImplementation); + operators; } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerView function getMemberCount( - OperatorSet memory operatorSet - ) external view returns (uint256) { - return _operatorSetMembers[operatorSet.key()].length(); + OperatorSet memory + ) external view returns (uint256 memberCount) { + _delegateView(viewImplementation); + memberCount; } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerView function getAVSRegistrar( address avs ) public view returns (IAVSRegistrar) { @@ -956,51 +814,36 @@ contract AllocationManager is return address(registrar) == address(0) ? IAVSRegistrar(avs) : registrar; } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerView function getStrategiesInOperatorSet( - OperatorSet memory operatorSet - ) external view returns (IStrategy[] memory) { - address[] memory values = _operatorSetStrategies[operatorSet.key()].values(); - IStrategy[] memory strategies; - - assembly { - strategies := values - } - - return strategies; + OperatorSet memory + ) external view returns (IStrategy[] memory strategies) { + _delegateView(viewImplementation); + strategies; } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerView function getMinimumSlashableStake( - OperatorSet memory operatorSet, - address[] memory operators, - IStrategy[] memory strategies, - uint32 futureBlock + OperatorSet memory, + address[] memory, + IStrategy[] memory, + uint32 ) external view returns (uint256[][] memory slashableStake) { - slashableStake = _getMinimumAllocatedStake(operatorSet, operators, strategies, futureBlock); - - for (uint256 i = 0; i < operators.length; i++) { - // If the operator is not slashable by the opSet, all strategies should have a slashable stake of 0 - if (!isOperatorSlashable(operators[i], operatorSet)) { - for (uint256 j = 0; j < strategies.length; j++) { - slashableStake[i][j] = 0; - } - } - } + _delegateView(viewImplementation); + slashableStake; } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerView function getAllocatedStake( - OperatorSet memory operatorSet, - address[] memory operators, - IStrategy[] memory strategies - ) external view returns (uint256[][] memory) { - /// This helper function returns the minimum allocated stake by taking into account deallocations at some `futureBlock`. - /// We use the block.number, as the `futureBlock`, meaning that no **future** deallocations are considered. - return _getMinimumAllocatedStake(operatorSet, operators, strategies, uint32(block.number)); + OperatorSet memory, + address[] memory, + IStrategy[] memory + ) external view returns (uint256[][] memory slashableStake) { + _delegateView(viewImplementation); + slashableStake; } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerView function isOperatorSlashable(address operator, OperatorSet memory operatorSet) public view returns (bool) { RegistrationStatus memory status = registrationStatus[operator][operatorSet.key()]; @@ -1009,7 +852,7 @@ contract AllocationManager is return status.registered || block.number <= status.slashableUntil; } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerView function getRedistributionRecipient( OperatorSet memory operatorSet ) public view returns (address) { @@ -1018,33 +861,26 @@ contract AllocationManager is return redistributionRecipient == address(0) ? DEFAULT_BURN_ADDRESS : redistributionRecipient; } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerView function isRedistributingOperatorSet( OperatorSet memory operatorSet ) public view returns (bool) { return getRedistributionRecipient(operatorSet) != DEFAULT_BURN_ADDRESS; } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerView function getSlashCount( - OperatorSet memory operatorSet - ) external view returns (uint256) { - return _slashIds[operatorSet.key()]; + OperatorSet memory + ) external view returns (uint256 slashCount) { + _delegateView(viewImplementation); + slashCount; } - /// @inheritdoc IAllocationManager + /// @inheritdoc IAllocationManagerView function isOperatorRedistributable( - address operator - ) external view returns (bool) { - // Get the registered and allocated sets for the operator. - // We get both sets, since: - // - Upon registration the operator allocation will be pending to a redistributing operator set, and as such not yet in RegisteredSets. - // - Upon deregistration the operator is removed from RegisteredSets, but is still allocated. - OperatorSet[] memory registeredSets = getRegisteredSets(operator); - OperatorSet[] memory allocatedSets = getAllocatedSets(operator); - - // Check if the operator is redistributable from any of the registered or allocated sets - return - _isOperatorRedistributable(operator, registeredSets) || _isOperatorRedistributable(operator, allocatedSets); + address + ) external view returns (bool result) { + _delegateView(viewImplementation); + result; } } diff --git a/src/contracts/core/AllocationManagerView.sol b/src/contracts/core/AllocationManagerView.sol new file mode 100644 index 0000000000..96cdb667dd --- /dev/null +++ b/src/contracts/core/AllocationManagerView.sol @@ -0,0 +1,497 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.29; // Minimum for `layout at` directive. + +import "./storage/AllocationManagerStorage.sol"; + +/// @notice Non-state mutating view functions, (static) called by the `AllocationManager`. +/// @dev The `layout at 151` directive specifies that `AllocationManagerStorage` should be placed +/// starting at storage slot 151. This slot number is calculated based on the storage layout +/// of the main `AllocationManager` contract, which inherits from multiple contracts in this order: +/// +/// 1. Initializable +/// 2. Deprecated_OwnableUpgradeable +/// 3. Pausable +/// 4. AllocationManagerStorage +/// +/// Since `AllocationManagerView` only needs access to the storage variables from +/// `AllocationManagerStorage` (without the other mixins), it uses `layout at 151` to +/// align its storage layout with the main `AllocationManager` contract. +contract AllocationManagerView is IAllocationManagerView, AllocationManagerStorage layout at 151 { + using DoubleEndedQueue for DoubleEndedQueue.Bytes32Deque; + using Snapshots for Snapshots.DefaultWadHistory; + using OperatorSetLib for OperatorSet; + using SlashingLib for uint256; + using EnumerableSet for *; + using SafeCast for *; + + /** + * + * INITIALIZING FUNCTIONS + * + */ + + /** + * @dev Initializes the DelegationManager address, the deallocation delay, and the allocation configuration delay. + */ + constructor( + IDelegationManager _delegation, + IStrategy _eigenStrategy, + uint32 _DEALLOCATION_DELAY, + uint32 _ALLOCATION_CONFIGURATION_DELAY + ) AllocationManagerStorage(_delegation, _eigenStrategy, _DEALLOCATION_DELAY, _ALLOCATION_CONFIGURATION_DELAY) {} + + /** + * + * INTERNAL FUNCTIONS + * + */ + + /** + * @dev For an operator set, get the operator's effective allocated magnitude. + * 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 _getUpdatedAllocation( + address operator, + 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); + } + + // Otherwise, complete the pending change and return updated info + allocation.currentMagnitude = _addInt128(allocation.currentMagnitude, allocation.pendingDiff); + + // If the completed change was a deallocation, update used magnitude + if (allocation.pendingDiff < 0) { + info.encumberedMagnitude = _addInt128(info.encumberedMagnitude, allocation.pendingDiff); + } + + allocation.effectBlock = 0; + allocation.pendingDiff = 0; + + return (info, allocation); + } + + /** + * @dev Returns the minimum allocated stake at the future block. + * @param operatorSet The operator set to get the minimum allocated stake for. + * @param operators The operators to get the minimum allocated stake for. + * @param strategies The strategies to get the minimum allocated stake for. + * @param futureBlock The future block to get the minimum allocated stake for. + */ + function _getMinimumAllocatedStake( + OperatorSet memory operatorSet, + address[] memory operators, + IStrategy[] memory strategies, + uint32 futureBlock + ) internal view returns (uint256[][] memory allocatedStake) { + allocatedStake = new uint256[][](operators.length); + uint256[][] memory delegatedStake = delegation.getOperatorsShares(operators, strategies); + + for (uint256 i = 0; i < operators.length; i++) { + address operator = operators[i]; + + allocatedStake[i] = new uint256[](strategies.length); + + for (uint256 j = 0; j < strategies.length; j++) { + IStrategy strategy = strategies[j]; + + // Fetch the max magnitude and allocation for the operator/strategy. + // Prevent division by 0 if needed. This mirrors the "FullySlashed" checks + // in the DelegationManager + uint64 maxMagnitude = _maxMagnitudeHistory[operator][strategy].latest(); + if (maxMagnitude == 0) { + continue; + } + + Allocation memory alloc = getAllocation(operator, operatorSet, strategy); + + // If the pending change takes effect before `futureBlock`, include it in `currentMagnitude` + // However, ONLY include the pending change if it is a deallocation, since this method + // is supposed to return the minimum slashable stake between now and `futureBlock` + if (alloc.effectBlock <= futureBlock && alloc.pendingDiff < 0) { + alloc.currentMagnitude = _addInt128(alloc.currentMagnitude, alloc.pendingDiff); + } + + uint256 slashableProportion = uint256(alloc.currentMagnitude).divWad(maxMagnitude); + allocatedStake[i][j] = delegatedStake[i][j].mulWad(slashableProportion); + } + } + } + + /// @dev Use safe casting when downcasting to uint64 + function _addInt128(uint64 a, int128 b) internal pure returns (uint64) { + return uint256(int256(int128(uint128(a)) + b)).toUint64(); + } + + /** + * @notice Helper function to check if an operator is redistributable from a list of operator sets + * @param operator The operator to check + * @param operatorSets The list of operator sets to check + * @return True if the operator is redistributable from any of the operator sets, false otherwise + */ + function _isOperatorRedistributable( + address operator, + OperatorSet[] memory operatorSets + ) internal view returns (bool) { + for (uint256 i = 0; i < operatorSets.length; ++i) { + if (isOperatorSlashable(operator, operatorSets[i]) && isRedistributingOperatorSet(operatorSets[i])) { + return true; + } + } + return false; + } + + /** + * + * VIEW FUNCTIONS + * + */ + + /// @inheritdoc IAllocationManagerView + function getOperatorSetCount( + address avs + ) external view returns (uint256) { + return _operatorSets[avs].length(); + } + + /// @inheritdoc IAllocationManagerView + function getAllocatedSets( + address operator + ) public 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 IAllocationManagerView + function getAllocatedStrategies( + address operator, + 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 IAllocationManagerView + function getAllocation( + address operator, + OperatorSet memory operatorSet, + IStrategy strategy + ) public view returns (Allocation memory) { + (, Allocation memory allocation) = _getUpdatedAllocation(operator, operatorSet.key(), strategy); + + return allocation; + } + + /// @inheritdoc IAllocationManagerView + 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 _allocations; + } + + /// @inheritdoc IAllocationManagerView + 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 (operatorSets, _allocations); + } + + /// @inheritdoc IAllocationManagerView + function getEncumberedMagnitude(address operator, IStrategy strategy) external view returns (uint64) { + (uint64 curEncumberedMagnitude,) = _getFreeAndUsedMagnitude(operator, strategy); + return curEncumberedMagnitude; + } + + /// @inheritdoc IAllocationManagerView + function getAllocatableMagnitude(address operator, IStrategy strategy) external view returns (uint64) { + (, uint64 curAllocatableMagnitude) = _getFreeAndUsedMagnitude(operator, strategy); + return curAllocatableMagnitude; + } + + /// @dev For an operator, returns up-to-date amounts for current encumbered and available + /// magnitude. Note that these two values will always add up to the operator's max magnitude + /// for the strategy + function _getFreeAndUsedMagnitude( + address operator, + IStrategy strategy + ) internal view returns (uint64 curEncumberedMagnitude, uint64 curAllocatableMagnitude) { + // This method needs to simulate clearing any pending deallocations. + // This roughly mimics the calculations done in `_clearDeallocationQueue` and + // `_getUpdatedAllocation`, while operating on a `curEncumberedMagnitude` + // rather than continually reading/updating state. + curEncumberedMagnitude = encumberedMagnitude[operator][strategy]; + + uint256 length = deallocationQueue[operator][strategy].length(); + for (uint256 i = 0; i < length; ++i) { + bytes32 operatorSetKey = deallocationQueue[operator][strategy].at(i); + Allocation memory allocation = allocations[operator][operatorSetKey][strategy]; + + // If we've reached a pending deallocation that isn't completable yet, + // we can stop. Any subsequent modifications will also be uncompletable. + 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, allocation.pendingDiff); + } + + // The difference between the operator's max magnitude and its encumbered magnitude + // is the magnitude that can be allocated. + curAllocatableMagnitude = _maxMagnitudeHistory[operator][strategy].latest() - curEncumberedMagnitude; + return (curEncumberedMagnitude, curAllocatableMagnitude); + } + + /// @inheritdoc IAllocationManagerView + function getMaxMagnitude(address operator, IStrategy strategy) public view returns (uint64) { + return _maxMagnitudeHistory[operator][strategy].latest(); + } + + /// @inheritdoc IAllocationManagerView + function getMaxMagnitudes( + address operator, + IStrategy[] memory strategies + ) external view returns (uint64[] memory) { + uint64[] memory maxMagnitudes = new uint64[](strategies.length); + + for (uint256 i = 0; i < strategies.length; ++i) { + maxMagnitudes[i] = getMaxMagnitude(operator, strategies[i]); + } + + return maxMagnitudes; + } + + /// @inheritdoc IAllocationManagerView + 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] = getMaxMagnitude(operators[i], strategy); + } + + return maxMagnitudes; + } + + /// @inheritdoc IAllocationManagerView + function getMaxMagnitudesAtBlock( + address operator, + 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({key: blockNumber}); + } + + return maxMagnitudes; + } + + /// @inheritdoc IAllocationManagerView + function getAllocationDelay( + address operator + ) public view returns (bool, uint32) { + AllocationDelayInfo memory info = _allocationDelayInfo[operator]; + + 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; + isSet = true; + } + + return (isSet, delay); + } + + /// @inheritdoc IAllocationManagerView + 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 IAllocationManagerView + function isMemberOfOperatorSet(address operator, OperatorSet memory operatorSet) public view returns (bool) { + return _operatorSetMembers[operatorSet.key()].contains(operator); + } + + /// @inheritdoc IAllocationManagerView + function isOperatorSet( + OperatorSet memory operatorSet + ) external view returns (bool) { + return _operatorSets[operatorSet.avs].contains(operatorSet.id); + } + + /// @inheritdoc IAllocationManagerView + function getMembers( + OperatorSet memory operatorSet + ) external view returns (address[] memory) { + return _operatorSetMembers[operatorSet.key()].values(); + } + + /// @inheritdoc IAllocationManagerView + function getMemberCount( + OperatorSet memory operatorSet + ) external view returns (uint256) { + return _operatorSetMembers[operatorSet.key()].length(); + } + + /// @inheritdoc IAllocationManagerView + function getAVSRegistrar( + address avs + ) public view returns (IAVSRegistrar) { + IAVSRegistrar registrar = _avsRegistrar[avs]; + + return address(registrar) == address(0) ? IAVSRegistrar(avs) : registrar; + } + + /// @inheritdoc IAllocationManagerView + function getStrategiesInOperatorSet( + OperatorSet memory operatorSet + ) external view returns (IStrategy[] memory) { + address[] memory values = _operatorSetStrategies[operatorSet.key()].values(); + IStrategy[] memory strategies; + + assembly { + strategies := values + } + + return strategies; + } + + /// @inheritdoc IAllocationManagerView + function getMinimumSlashableStake( + OperatorSet memory operatorSet, + address[] memory operators, + IStrategy[] memory strategies, + uint32 futureBlock + ) external view returns (uint256[][] memory slashableStake) { + slashableStake = _getMinimumAllocatedStake(operatorSet, operators, strategies, futureBlock); + + for (uint256 i = 0; i < operators.length; i++) { + // If the operator is not slashable by the opSet, all strategies should have a slashable stake of 0 + if (!isOperatorSlashable(operators[i], operatorSet)) { + for (uint256 j = 0; j < strategies.length; j++) { + slashableStake[i][j] = 0; + } + } + } + } + + /// @inheritdoc IAllocationManagerView + function getAllocatedStake( + OperatorSet memory operatorSet, + address[] memory operators, + IStrategy[] memory strategies + ) external view returns (uint256[][] memory) { + /// This helper function returns the minimum allocated stake by taking into account deallocations at some `futureBlock`. + /// We use the block.number, as the `futureBlock`, meaning that no **future** deallocations are considered. + return _getMinimumAllocatedStake(operatorSet, operators, strategies, uint32(block.number)); + } + + /// @inheritdoc IAllocationManagerView + function isOperatorSlashable(address operator, OperatorSet memory operatorSet) public view returns (bool) { + RegistrationStatus memory status = registrationStatus[operator][operatorSet.key()]; + + // slashableUntil returns the last block the operator is slashable in so we check for + // less than or equal to + return status.registered || block.number <= status.slashableUntil; + } + + /// @inheritdoc IAllocationManagerView + function getRedistributionRecipient( + OperatorSet memory operatorSet + ) public view returns (address) { + // Load the redistribution recipient and return it if set, otherwise return the default burn address. + address redistributionRecipient = _redistributionRecipients[operatorSet.key()]; + return redistributionRecipient == address(0) ? DEFAULT_BURN_ADDRESS : redistributionRecipient; + } + + /// @inheritdoc IAllocationManagerView + function isRedistributingOperatorSet( + OperatorSet memory operatorSet + ) public view returns (bool) { + return getRedistributionRecipient(operatorSet) != DEFAULT_BURN_ADDRESS; + } + + /// @inheritdoc IAllocationManagerView + function getSlashCount( + OperatorSet memory operatorSet + ) external view returns (uint256) { + return _slashIds[operatorSet.key()]; + } + + /// @inheritdoc IAllocationManagerView + function isOperatorRedistributable( + address operator + ) external view returns (bool) { + // Get the registered and allocated sets for the operator. + // We get both sets, since: + // - Upon registration the operator allocation will be pending to a redistributing operator set, and as such not yet in RegisteredSets. + // - Upon deregistration the operator is removed from RegisteredSets, but is still allocated. + OperatorSet[] memory registeredSets = getRegisteredSets(operator); + OperatorSet[] memory allocatedSets = getAllocatedSets(operator); + + // Check if the operator is redistributable from any of the registered or allocated sets + return + _isOperatorRedistributable(operator, registeredSets) || _isOperatorRedistributable(operator, allocatedSets); + } +} diff --git a/src/contracts/core/DelegationManager.sol b/src/contracts/core/DelegationManager.sol index 4cf592555a..01b61951a4 100644 --- a/src/contracts/core/DelegationManager.sol +++ b/src/contracts/core/DelegationManager.sol @@ -10,7 +10,7 @@ import "../mixins/Deprecated_OwnableUpgradeable.sol"; import "../permissions/Pausable.sol"; import "../libraries/SlashingLib.sol"; import "../libraries/Snapshots.sol"; -import "./DelegationManagerStorage.sol"; +import "./storage/DelegationManagerStorage.sol"; /** * @title DelegationManager diff --git a/src/contracts/core/ProtocolRegistry.sol b/src/contracts/core/ProtocolRegistry.sol index 67a80ce5c5..ff98b5b567 100644 --- a/src/contracts/core/ProtocolRegistry.sol +++ b/src/contracts/core/ProtocolRegistry.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.27; import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin-upgrades/contracts/access/AccessControlEnumerableUpgradeable.sol"; import "../interfaces/IPausable.sol"; -import "./ProtocolRegistryStorage.sol"; +import "./storage/ProtocolRegistryStorage.sol"; contract ProtocolRegistry is Initializable, AccessControlEnumerableUpgradeable, ProtocolRegistryStorage { using ShortStringsUpgradeable for *; diff --git a/src/contracts/core/ReleaseManager.sol b/src/contracts/core/ReleaseManager.sol index 35fa604f88..a5f08a555a 100644 --- a/src/contracts/core/ReleaseManager.sol +++ b/src/contracts/core/ReleaseManager.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.27; import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; import "../mixins/PermissionControllerMixin.sol"; import "../mixins/SemVerMixin.sol"; -import "./ReleaseManagerStorage.sol"; +import "./storage/ReleaseManagerStorage.sol"; contract ReleaseManager is Initializable, ReleaseManagerStorage, PermissionControllerMixin, SemVerMixin { using OperatorSetLib for OperatorSet; diff --git a/src/contracts/core/RewardsCoordinator.sol b/src/contracts/core/RewardsCoordinator.sol index ac9a8b47fe..54a6d82b87 100644 --- a/src/contracts/core/RewardsCoordinator.sol +++ b/src/contracts/core/RewardsCoordinator.sol @@ -8,7 +8,7 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "../libraries/Merkle.sol"; import "../permissions/Pausable.sol"; -import "./RewardsCoordinatorStorage.sol"; +import "./storage/RewardsCoordinatorStorage.sol"; import "../mixins/PermissionControllerMixin.sol"; import "../mixins/SemVerMixin.sol"; diff --git a/src/contracts/core/StrategyManager.sol b/src/contracts/core/StrategyManager.sol index 02f6717ea7..117e6906c1 100644 --- a/src/contracts/core/StrategyManager.sol +++ b/src/contracts/core/StrategyManager.sol @@ -9,7 +9,7 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "../mixins/SignatureUtilsMixin.sol"; import "../interfaces/IEigenPodManager.sol"; import "../permissions/Pausable.sol"; -import "./StrategyManagerStorage.sol"; +import "./storage/StrategyManagerStorage.sol"; /** * @title The primary entry- and exit-point for funds into and out of EigenLayer. diff --git a/src/contracts/core/AVSDirectoryStorage.sol b/src/contracts/core/storage/AVSDirectoryStorage.sol similarity index 96% rename from src/contracts/core/AVSDirectoryStorage.sol rename to src/contracts/core/storage/AVSDirectoryStorage.sol index 0b9237a606..84ee2f8756 100644 --- a/src/contracts/core/AVSDirectoryStorage.sol +++ b/src/contracts/core/storage/AVSDirectoryStorage.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.27; -import "../interfaces/IAVSDirectory.sol"; -import "../interfaces/IDelegationManager.sol"; +import "../../interfaces/IAVSDirectory.sol"; +import "../../interfaces/IDelegationManager.sol"; abstract contract AVSDirectoryStorage is IAVSDirectory { // Constants diff --git a/src/contracts/core/AllocationManagerStorage.sol b/src/contracts/core/storage/AllocationManagerStorage.sol similarity index 97% rename from src/contracts/core/AllocationManagerStorage.sol rename to src/contracts/core/storage/AllocationManagerStorage.sol index c87c6f1bb5..8fc464bd92 100644 --- a/src/contracts/core/AllocationManagerStorage.sol +++ b/src/contracts/core/storage/AllocationManagerStorage.sol @@ -4,11 +4,10 @@ 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/IDelegationManager.sol"; -import {Snapshots} from "../libraries/Snapshots.sol"; +import "../../interfaces/IAllocationManager.sol"; +import {Snapshots} from "../../libraries/Snapshots.sol"; -abstract contract AllocationManagerStorage is IAllocationManager { +abstract contract AllocationManagerStorage is IAllocationManagerStorage { using Snapshots for Snapshots.DefaultWadHistory; // Constants diff --git a/src/contracts/core/DelegationManagerStorage.sol b/src/contracts/core/storage/DelegationManagerStorage.sol similarity index 96% rename from src/contracts/core/DelegationManagerStorage.sol rename to src/contracts/core/storage/DelegationManagerStorage.sol index 8e38b4c8d1..05f56a05a6 100644 --- a/src/contracts/core/DelegationManagerStorage.sol +++ b/src/contracts/core/storage/DelegationManagerStorage.sol @@ -3,12 +3,12 @@ pragma solidity ^0.8.27; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import "../libraries/SlashingLib.sol"; -import "../interfaces/IDelegationManager.sol"; -import "../interfaces/IEigenPodManager.sol"; -import "../interfaces/IAllocationManager.sol"; +import "../../libraries/SlashingLib.sol"; +import "../../interfaces/IDelegationManager.sol"; +import "../../interfaces/IEigenPodManager.sol"; +import "../../interfaces/IAllocationManager.sol"; -import {Snapshots} from "../libraries/Snapshots.sol"; +import {Snapshots} from "../../libraries/Snapshots.sol"; /** * @title Storage variables for the `DelegationManager` contract. diff --git a/src/contracts/core/ProtocolRegistryStorage.sol b/src/contracts/core/storage/ProtocolRegistryStorage.sol similarity index 96% rename from src/contracts/core/ProtocolRegistryStorage.sol rename to src/contracts/core/storage/ProtocolRegistryStorage.sol index 312be739d1..d1cfcc0a37 100644 --- a/src/contracts/core/ProtocolRegistryStorage.sol +++ b/src/contracts/core/storage/ProtocolRegistryStorage.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.27; import "@openzeppelin-upgrades/contracts/utils/ShortStringsUpgradeable.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; -import "../interfaces/IProtocolRegistry.sol"; +import "../../interfaces/IProtocolRegistry.sol"; abstract contract ProtocolRegistryStorage is IProtocolRegistry { /** diff --git a/src/contracts/core/ReleaseManagerStorage.sol b/src/contracts/core/storage/ReleaseManagerStorage.sol similarity index 94% rename from src/contracts/core/ReleaseManagerStorage.sol rename to src/contracts/core/storage/ReleaseManagerStorage.sol index 149355b5ab..6decfa9b95 100644 --- a/src/contracts/core/ReleaseManagerStorage.sol +++ b/src/contracts/core/storage/ReleaseManagerStorage.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.27; -import "../interfaces/IReleaseManager.sol"; +import "../../interfaces/IReleaseManager.sol"; abstract contract ReleaseManagerStorage is IReleaseManager { // Mutables diff --git a/src/contracts/core/RewardsCoordinatorStorage.sol b/src/contracts/core/storage/RewardsCoordinatorStorage.sol similarity index 99% rename from src/contracts/core/RewardsCoordinatorStorage.sol rename to src/contracts/core/storage/RewardsCoordinatorStorage.sol index a743959809..5cf50fd318 100644 --- a/src/contracts/core/RewardsCoordinatorStorage.sol +++ b/src/contracts/core/storage/RewardsCoordinatorStorage.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.27; -import "../interfaces/IRewardsCoordinator.sol"; +import "../../interfaces/IRewardsCoordinator.sol"; /** * @title Storage variables for the `RewardsCoordinator` contract. diff --git a/src/contracts/core/StrategyManagerStorage.sol b/src/contracts/core/storage/StrategyManagerStorage.sol similarity index 94% rename from src/contracts/core/StrategyManagerStorage.sol rename to src/contracts/core/storage/StrategyManagerStorage.sol index bbedfc2bce..f63c4c78c3 100644 --- a/src/contracts/core/StrategyManagerStorage.sol +++ b/src/contracts/core/storage/StrategyManagerStorage.sol @@ -4,12 +4,12 @@ pragma solidity ^0.8.27; import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import "../interfaces/IAllocationManager.sol"; -import "../interfaces/IAVSDirectory.sol"; -import "../interfaces/IDelegationManager.sol"; -import "../interfaces/IEigenPodManager.sol"; -import "../interfaces/IStrategy.sol"; -import "../interfaces/IStrategyManager.sol"; +import "../../interfaces/IAllocationManager.sol"; +import "../../interfaces/IAVSDirectory.sol"; +import "../../interfaces/IDelegationManager.sol"; +import "../../interfaces/IEigenPodManager.sol"; +import "../../interfaces/IStrategy.sol"; +import "../../interfaces/IStrategyManager.sol"; /** * @title Storage variables for the `StrategyManager` contract. diff --git a/src/contracts/interfaces/IAllocationManager.sol b/src/contracts/interfaces/IAllocationManager.sol index e277963364..f02aa98a22 100644 --- a/src/contracts/interfaces/IAllocationManager.sol +++ b/src/contracts/interfaces/IAllocationManager.sol @@ -2,7 +2,9 @@ pragma solidity >=0.5.0; import {OperatorSet} from "../libraries/OperatorSetLib.sol"; +import "./IDelegationManager.sol"; import "./IPauserRegistry.sol"; +import "./IPausable.sol"; import "./IStrategy.sol"; import "./IAVSRegistrar.sol"; import "./ISemVerMixin.sol"; @@ -223,7 +225,37 @@ interface IAllocationManagerEvents is IAllocationManagerTypes { event StrategyRemovedFromOperatorSet(OperatorSet operatorSet, IStrategy strategy); } -interface IAllocationManager is IAllocationManagerErrors, IAllocationManagerEvents, ISemVerMixin { +interface IAllocationManagerStorage is IAllocationManagerEvents { + /** + * @notice The DelegationManager contract for EigenLayer + */ + function delegation() external view returns (IDelegationManager); + + /** + * @notice The Eigen strategy contract + * @dev Cannot be added to redistributing operator sets + */ + function eigenStrategy() external view returns (IStrategy); + + /** + * @notice Returns the number of blocks between an operator deallocating magnitude and the magnitude becoming + * unslashable and then being able to be reallocated to another operator set. Note that unlike the allocation delay + * which is configurable by the operator, the DEALLOCATION_DELAY is globally fixed and cannot be changed. + */ + function DEALLOCATION_DELAY() external view returns (uint32 delay); + + /** + * @notice Delay before alloaction delay modifications take effect. + */ + function ALLOCATION_CONFIGURATION_DELAY() external view returns (uint32); +} + +interface IAllocationManagerActions is + IAllocationManagerErrors, + IAllocationManagerEvents, + IAllocationManagerStorage, + ISemVerMixin +{ /** * @dev Initializes the initial owner and paused status. */ @@ -379,7 +411,9 @@ interface IAllocationManager is IAllocationManagerErrors, IAllocationManagerEven uint32 operatorSetId, IStrategy[] calldata strategies ) external; +} +interface IAllocationManagerView is IAllocationManagerErrors, IAllocationManagerEvents, IAllocationManagerStorage { /** * * VIEW FUNCTIONS @@ -535,13 +569,6 @@ interface IAllocationManager is IAllocationManagerErrors, IAllocationManagerEven address operator ) external view returns (bool isSet, uint32 delay); - /** - * @notice Returns the number of blocks between an operator deallocating magnitude and the magnitude becoming - * unslashable and then being able to be reallocated to another operator set. Note that unlike the allocation delay - * which is configurable by the operator, the DEALLOCATION_DELAY is globally fixed and cannot be changed. - */ - function DEALLOCATION_DELAY() external view returns (uint32 delay); - /** * @notice Returns a list of all operator sets the operator is registered for * @param operator The operator address to query. @@ -680,3 +707,5 @@ interface IAllocationManager is IAllocationManagerErrors, IAllocationManagerEven address operator ) external view returns (bool); } + +interface IAllocationManager is IAllocationManagerActions, IAllocationManagerView, IPausable {} diff --git a/src/contracts/mixins/SplitContractMixin.sol b/src/contracts/mixins/SplitContractMixin.sol new file mode 100644 index 0000000000..b04f15ca3d --- /dev/null +++ b/src/contracts/mixins/SplitContractMixin.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +abstract contract SplitContractMixin { + /// @notice The address of the second half of the contract. + address internal immutable viewImplementation; + + constructor( + address _viewImplementation + ) { + viewImplementation = _viewImplementation; + } + + /** + * @dev Delegates the current call to `implementation`. + * + * This function does not return to its internal call site, it will return directly to the external caller. + * + * Copied from OpenZeppelin Contracts v4.9.0 (proxy/Proxy.sol). + */ + function _delegate( + address implementation + ) internal virtual { + assembly { + // Copy msg.data. We take full control of memory in this inline assembly + // block because it will not return to Solidity code. We overwrite the + // Solidity scratch pad at memory position 0. + calldatacopy(0, 0, calldatasize()) + + // Call the implementation. + // out and outsize are 0 because we don't know the size yet. + let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) + + // Copy the returned data. + returndatacopy(0, 0, returndatasize()) + + switch result + // delegatecall returns 0 on error. + case 0 { revert(0, returndatasize()) } + default { return(0, returndatasize()) } + } + } + + /** + * @dev Performs a delegate call to `implementation` in the context of a view function. + * + * This function typecasts the non-view `_delegate` function to a view function in order to + * allow its invocation from a view context. This is required because the EVM itself does not + * enforce view/pure mutability, and using inline assembly, it is possible to cast a function + * pointer to a view (read-only) signature. This pattern is sometimes used for readonly proxies, + * but it should be used cautiously since any state-modifying logic in the underlying delegate + * violates the spirit of a view call. + * + * @param implementation The address to which the call should be delegated. + */ + function _delegateView( + address implementation + ) internal view virtual { + function(address) fn = _delegate; + function(address) view fnView; + /// @solidity memory-safe-assembly + assembly { + fnView := fn + } + fnView(implementation); + } +} diff --git a/src/test/DevnetLifecycle.t.sol b/src/test/DevnetLifecycle.t.sol index e60d847d29..be4612d678 100644 --- a/src/test/DevnetLifecycle.t.sol +++ b/src/test/DevnetLifecycle.t.sol @@ -22,7 +22,7 @@ contract Devnet_Lifecycle_Test is Test, IAllocationManagerTypes { DelegationManager public delegationManager; StrategyManager public strategyManager; AVSDirectory public avsDirectory; - AllocationManager public allocationManager; + IAllocationManager public allocationManager; StrategyBase public wethStrategy; IERC20 public weth; @@ -46,7 +46,7 @@ contract Devnet_Lifecycle_Test is Test, IAllocationManagerTypes { delegationManager = DelegationManager(0x3391eBafDD4b2e84Eeecf1711Ff9FC06EF9Ed182); strategyManager = StrategyManager(0x70f8bC2Da145b434de66114ac539c9756eF64fb3); avsDirectory = AVSDirectory(0xCa839541648D3e23137457b1Fd4A06bccEADD33a); - allocationManager = AllocationManager(0xAbD5Dd30CaEF8598d4EadFE7D45Fd582EDEade15); + allocationManager = IAllocationManager(0xAbD5Dd30CaEF8598d4EadFE7D45Fd582EDEade15); wethStrategy = StrategyBase(0x4f812633943022fA97cb0881683aAf9f318D5Caa); weth = IERC20(0x94373a4919B3240D86eA41593D5eBa789FEF3848); diff --git a/src/test/harnesses/AllocationManagerHarness.sol b/src/test/harnesses/AllocationManagerHarness.sol index dd8ec83525..d983fe870e 100644 --- a/src/test/harnesses/AllocationManagerHarness.sol +++ b/src/test/harnesses/AllocationManagerHarness.sol @@ -9,6 +9,7 @@ contract AllocationManagerHarness is AllocationManager { using DoubleEndedQueue for DoubleEndedQueue.Bytes32Deque; constructor( + IAllocationManagerView _allocationManagerView, IDelegationManager _delegation, IStrategy _eigenStrategy, IPauserRegistry _pauserRegistry, @@ -17,6 +18,7 @@ contract AllocationManagerHarness is AllocationManager { uint32 _ALLOCATION_CONFIGURATION_DELAY ) AllocationManager( + _allocationManagerView, _delegation, _eigenStrategy, _pauserRegistry, diff --git a/src/test/integration/IntegrationDeployer.t.sol b/src/test/integration/IntegrationDeployer.t.sol index 5c978a93c4..67709fba0f 100644 --- a/src/test/integration/IntegrationDeployer.t.sol +++ b/src/test/integration/IntegrationDeployer.t.sol @@ -11,6 +11,7 @@ import "forge-std/Test.sol"; import "src/contracts/core/DelegationManager.sol"; import "src/contracts/core/AllocationManager.sol"; +import "src/contracts/core/AllocationManagerView.sol"; import "src/contracts/core/StrategyManager.sol"; import "src/contracts/strategies/StrategyFactory.sol"; import "src/contracts/strategies/StrategyBase.sol"; @@ -37,7 +38,7 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser { // Fork ids for specific fork tests bool isUpgraded; - uint mainnetForkBlock = 22_514_370; // Post Pectra Compatibility Upgrade + uint mainnetForkBlock = 23_634_615; // Post Pectra Compatibility Upgrade string version = "9.9.9"; @@ -313,8 +314,9 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser { } if (address(allocationManager) == address(0)) { allocationManager = - AllocationManager(address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))); + IAllocationManager(address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))); } + // allocationManagerView is deployed as a standalone implementation (not a proxy) in _deployImplementations() if (address(permissionController) == address(0)) { permissionController = PermissionController(address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))); @@ -329,14 +331,23 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser { /// Deploy an implementation contract for each contract in the system function _deployImplementations() public { - allocationManagerImplementation = new AllocationManager( - delegationManager, - eigenStrategy, - eigenLayerPauserReg, - permissionController, - DEALLOCATION_DELAY, - ALLOCATION_CONFIGURATION_DELAY, - version + // Deploy AllocationManagerView as a standalone implementation (not a proxy) + allocationManagerView = + new AllocationManagerView(delegationManager, eigenStrategy, DEALLOCATION_DELAY, ALLOCATION_CONFIGURATION_DELAY); + + allocationManagerImplementation = IAllocationManager( + address( + new AllocationManager( + allocationManagerView, + delegationManager, + eigenStrategy, + eigenLayerPauserReg, + permissionController, + DEALLOCATION_DELAY, + ALLOCATION_CONFIGURATION_DELAY, + version + ) + ) ); permissionControllerImplementation = new PermissionController(version); delegationManagerImplementation = new DelegationManager( @@ -409,6 +420,8 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser { ITransparentUpgradeableProxy(payable(address(allocationManager))), address(allocationManagerImplementation) ); + // AllocationManagerView is not a proxy, so no upgrade needed + // PermissionController eigenLayerProxyAdmin.upgrade( ITransparentUpgradeableProxy(payable(address(permissionController))), address(permissionControllerImplementation) diff --git a/src/test/integration/tests/ALM_Multi.t.sol b/src/test/integration/tests/ALM_Multi.t.sol index 53e0b8d81a..f241b44ff5 100644 --- a/src/test/integration/tests/ALM_Multi.t.sol +++ b/src/test/integration/tests/ALM_Multi.t.sol @@ -67,11 +67,13 @@ contract Integration_ALM_Multi is IntegrationCheckUtils { } } + // TODO: FIX THIS OR REPLACE (SUPER FLAKEY) /// Reduce fuzz runs because this test is thiccc: /// /// forge-config: default.fuzz.runs = 3 /// forge-config: forktest.fuzz.runs = 3 function test_Multi(uint24 _r) public rand(_r) { + cheats.pauseGasMetering(); // Do 20 iterations for (uint i = 1; i <= NUM_ITERATIONS; i++) { console.log("%s: %d", "iter".green().italic(), i - 1); @@ -86,6 +88,7 @@ contract Integration_ALM_Multi is IntegrationCheckUtils { // Ensure all pending actions are completed for the next iteration _rollForward_DeallocationDelay(); } + cheats.resumeGasMetering(); } /// @dev NONE operators can: diff --git a/src/test/integration/tests/upgrade/AllocationManagerUpgrade.t.sol b/src/test/integration/tests/upgrade/AllocationManagerUpgrade.t.sol new file mode 100644 index 0000000000..a89975bd55 --- /dev/null +++ b/src/test/integration/tests/upgrade/AllocationManagerUpgrade.t.sol @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "src/test/integration/UpgradeTest.t.sol"; +import {OperatorSet} from "src/contracts/libraries/OperatorSetLib.sol"; + +contract Integration_Upgrade_AllocationManager is UpgradeTest { + using ArrayLib for *; + using StdStyle for *; + // Storage state snapshots + + struct AllocationManagerState { + // Storage variables that should persist across upgrades + bool avsRegisteredMetadata; + uint slashCount; + address redistributionRecipient; + bool isRedistributingOperatorSet; + bool isOperatorSet; + uint operatorSetCount; + bool isMemberOfOperatorSet; + uint memberCount; + IStrategy[] strategiesInOperatorSet; + bool isOperatorSlashable; + bool isOperatorRedistributable; + uint64 encumberedMagnitude; + uint64 allocatableMagnitude; + uint64 maxMagnitude; + bool allocationDelayIsSet; + uint32 allocationDelay; + OperatorSet[] allocatedSets; + OperatorSet[] registeredSets; + IStrategy[] allocatedStrategies; + Allocation allocation; + OperatorSet[] strategyAllocationsOperatorSets; + Allocation[] strategyAllocations; + } + + User staker; + User operator; + AllocateParams allocateParams; + + AVS avs; + IStrategy[] strategies; + uint[] initTokenBalances; + uint[] initDepositShares; + OperatorSet operatorSet; + + /// Shared setup: + /// + /// 1. Generate staker with assets, operator, and AVS + /// 2. Staker deposits assets and delegates to operator + /// 3. AVS creates an operator set containing the strategies held by the staker + /// 4. Operator registers for the operator set + /// 5. Operator allocates to the operator set + /// 6. Roll blocks to complete allocation + function _init() internal override { + // 1. Create entities + (staker, strategies, initTokenBalances) = _newRandomStaker(); + operator = _newRandomOperator_NoAssets(); + (avs,) = _newRandomAVS(); + + operator.setAllocationDelay(ALLOCATION_CONFIGURATION_DELAY); + rollForward({blocks: ALLOCATION_CONFIGURATION_DELAY + 1}); + + // 2. Staker deposits into EigenLayer + staker.depositIntoEigenlayer(strategies, initTokenBalances); + initDepositShares = _calculateExpectedShares(strategies, initTokenBalances); + check_Deposit_State(staker, strategies, initDepositShares); + + // 3. Staker delegates to operator + staker.delegateTo(operator); + check_Delegation_State(staker, operator, strategies, initDepositShares); + + // 4. AVS creates an operator set containing the strategies held by the staker + operatorSet = avs.createOperatorSet(strategies); + + // 5. Operator registers for the operator set + operator.registerForOperatorSet(operatorSet); + check_Registration_State_NoAllocation(operator, operatorSet, allStrats); + + // 6. Operator allocates to the operator set + allocateParams = _genAllocation_AllAvailable(operator, operatorSet); + operator.modifyAllocations(allocateParams); + check_IncrAlloc_State_Slashable(operator, allocateParams); + + // 7. Roll blocks to complete allocation + _rollBlocksForCompleteAllocation(operator, operatorSet, strategies); + } + + function testFuzz_query_upgrade_compare(uint24 r) public rand(r) { + AllocationManagerState memory preUpgrade = _captureAllocationManagerState(); + _upgradeEigenLayerContracts(); + AllocationManagerState memory postUpgrade = _captureAllocationManagerState(); + _verifyStorageStateConsistency(preUpgrade, postUpgrade); + } + + function _captureAllocationManagerState() internal view returns (AllocationManagerState memory state) { + // Capture storage variables that should persist across upgrades + state.isOperatorSet = allocationManager.isOperatorSet(operatorSet); + state.operatorSetCount = allocationManager.getOperatorSetCount(address(avs)); + state.isMemberOfOperatorSet = allocationManager.isMemberOfOperatorSet(address(operator), operatorSet); + state.memberCount = allocationManager.getMemberCount(operatorSet); + state.strategiesInOperatorSet = allocationManager.getStrategiesInOperatorSet(operatorSet); + state.isOperatorSlashable = allocationManager.isOperatorSlashable(address(operator), operatorSet); + state.isOperatorRedistributable = allocationManager.isOperatorRedistributable(address(operator)); + + // Only query strategy-related data if we have at least one strategy + if (strategies.length > 0 && address(strategies[0]) != address(0)) { + state.encumberedMagnitude = allocationManager.getEncumberedMagnitude(address(operator), strategies[0]); + state.allocatableMagnitude = allocationManager.getAllocatableMagnitude(address(operator), strategies[0]); + state.maxMagnitude = allocationManager.getMaxMagnitude(address(operator), strategies[0]); + } else { + state.encumberedMagnitude = 0; + state.allocatableMagnitude = 0; + state.maxMagnitude = 0; + } + (state.allocationDelayIsSet, state.allocationDelay) = allocationManager.getAllocationDelay(address(operator)); + state.allocatedSets = allocationManager.getAllocatedSets(address(operator)); + state.registeredSets = allocationManager.getRegisteredSets(address(operator)); + state.allocatedStrategies = allocationManager.getAllocatedStrategies(address(operator), operatorSet); + // Only query allocation data if we have at least one strategy + if (strategies.length > 0 && address(strategies[0]) != address(0)) { + state.allocation = allocationManager.getAllocation(address(operator), operatorSet, strategies[0]); + (state.strategyAllocationsOperatorSets, state.strategyAllocations) = + allocationManager.getStrategyAllocations(address(operator), strategies[0]); + } else { + state.allocation = Allocation(0, 0, 0); + state.strategyAllocationsOperatorSets = new OperatorSet[](0); + state.strategyAllocations = new Allocation[](0); + } + state.redistributionRecipient = allocationManager.getRedistributionRecipient(operatorSet); + state.isRedistributingOperatorSet = allocationManager.isRedistributingOperatorSet(operatorSet); + state.slashCount = allocationManager.getSlashCount(operatorSet); + } + + function _verifyStorageStateConsistency(AllocationManagerState memory preUpgrade, AllocationManagerState memory postUpgrade) internal { + // Verify storage variables that should persist across upgrades + assertEq(preUpgrade.isOperatorSet, postUpgrade.isOperatorSet, "isOperatorSet should be the same"); + assertEq(preUpgrade.operatorSetCount, postUpgrade.operatorSetCount, "operatorSetCount should be the same"); + assertEq(preUpgrade.isMemberOfOperatorSet, postUpgrade.isMemberOfOperatorSet, "isMemberOfOperatorSet should be the same"); + assertEq(preUpgrade.memberCount, postUpgrade.memberCount, "memberCount should be the same"); + assertEq(preUpgrade.isOperatorSlashable, postUpgrade.isOperatorSlashable, "isOperatorSlashable should be the same"); + assertEq( + preUpgrade.isOperatorRedistributable, postUpgrade.isOperatorRedistributable, "isOperatorRedistributable should be the same" + ); + assertEq(preUpgrade.encumberedMagnitude, postUpgrade.encumberedMagnitude, "encumberedMagnitude should be the same"); + assertEq(preUpgrade.allocatableMagnitude, postUpgrade.allocatableMagnitude, "allocatableMagnitude should be the same"); + assertEq(preUpgrade.maxMagnitude, postUpgrade.maxMagnitude, "maxMagnitude should be the same"); + assertEq(preUpgrade.allocationDelayIsSet, postUpgrade.allocationDelayIsSet, "allocationDelayIsSet should be the same"); + assertEq(preUpgrade.allocationDelay, postUpgrade.allocationDelay, "allocationDelay should be the same"); + assertEq(preUpgrade.redistributionRecipient, postUpgrade.redistributionRecipient, "redistributionRecipient should be the same"); + assertEq( + preUpgrade.isRedistributingOperatorSet, + postUpgrade.isRedistributingOperatorSet, + "isRedistributingOperatorSet should be the same" + ); + assertEq(preUpgrade.slashCount, postUpgrade.slashCount, "slashCount should be the same"); + + // Verify array lengths + assertEq( + preUpgrade.strategiesInOperatorSet.length, + postUpgrade.strategiesInOperatorSet.length, + "strategiesInOperatorSet length should be the same" + ); + assertEq(preUpgrade.allocatedSets.length, postUpgrade.allocatedSets.length, "allocatedSets length should be the same"); + assertEq(preUpgrade.registeredSets.length, postUpgrade.registeredSets.length, "registeredSets length should be the same"); + assertEq( + preUpgrade.allocatedStrategies.length, postUpgrade.allocatedStrategies.length, "allocatedStrategies length should be the same" + ); + assertEq( + preUpgrade.strategyAllocationsOperatorSets.length, + postUpgrade.strategyAllocationsOperatorSets.length, + "strategyAllocationsOperatorSets length should be the same" + ); + assertEq( + preUpgrade.strategyAllocations.length, postUpgrade.strategyAllocations.length, "strategyAllocations length should be the same" + ); + + // Verify allocation struct + assertEq( + preUpgrade.allocation.currentMagnitude, + postUpgrade.allocation.currentMagnitude, + "allocation.currentMagnitude should be the same" + ); + assertEq(preUpgrade.allocation.pendingDiff, postUpgrade.allocation.pendingDiff, "allocation.pendingDiff should be the same"); + assertEq(preUpgrade.allocation.effectBlock, postUpgrade.allocation.effectBlock, "allocation.effectBlock should be the same"); + + // Verify array contents (if arrays have elements) + if (preUpgrade.strategiesInOperatorSet.length > 0) { + for (uint i = 0; i < preUpgrade.strategiesInOperatorSet.length; i++) { + assertEq( + address(preUpgrade.strategiesInOperatorSet[i]), + address(postUpgrade.strategiesInOperatorSet[i]), + "strategiesInOperatorSet element should be the same" + ); + } + } + + if (preUpgrade.allocatedSets.length > 0) { + for (uint i = 0; i < preUpgrade.allocatedSets.length; i++) { + assertEq(preUpgrade.allocatedSets[i].avs, postUpgrade.allocatedSets[i].avs, "allocatedSets avs should be the same"); + assertEq(preUpgrade.allocatedSets[i].id, postUpgrade.allocatedSets[i].id, "allocatedSets id should be the same"); + } + } + + if (preUpgrade.registeredSets.length > 0) { + for (uint i = 0; i < preUpgrade.registeredSets.length; i++) { + assertEq(preUpgrade.registeredSets[i].avs, postUpgrade.registeredSets[i].avs, "registeredSets avs should be the same"); + assertEq(preUpgrade.registeredSets[i].id, postUpgrade.registeredSets[i].id, "registeredSets id should be the same"); + } + } + + if (preUpgrade.allocatedStrategies.length > 0) { + for (uint i = 0; i < preUpgrade.allocatedStrategies.length; i++) { + assertEq( + address(preUpgrade.allocatedStrategies[i]), + address(postUpgrade.allocatedStrategies[i]), + "allocatedStrategies element should be the same" + ); + } + } + + if (preUpgrade.strategyAllocations.length > 0) { + for (uint i = 0; i < preUpgrade.strategyAllocations.length; i++) { + assertEq( + preUpgrade.strategyAllocations[i].currentMagnitude, + postUpgrade.strategyAllocations[i].currentMagnitude, + "strategyAllocations currentMagnitude should be the same" + ); + assertEq( + preUpgrade.strategyAllocations[i].pendingDiff, + postUpgrade.strategyAllocations[i].pendingDiff, + "strategyAllocations pendingDiff should be the same" + ); + assertEq( + preUpgrade.strategyAllocations[i].effectBlock, + postUpgrade.strategyAllocations[i].effectBlock, + "strategyAllocations effectBlock should be the same" + ); + } + } + + console.log("All storage state consistency checks passed!".green()); + } +} diff --git a/src/test/integration/users/AVS.t.sol b/src/test/integration/users/AVS.t.sol index 1d78207fdd..e5a5a56cbc 100644 --- a/src/test/integration/users/AVS.t.sol +++ b/src/test/integration/users/AVS.t.sol @@ -80,7 +80,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { print.method("updateAVSMetadataURI"); console.log("Setting AVS metadata URI to: %s", uri); - _tryPrankAppointee_AllocationManager(IAllocationManager.updateAVSMetadataURI.selector); + _tryPrankAppointee_AllocationManager(IAllocationManagerActions.updateAVSMetadataURI.selector); allocationManager.updateAVSMetadataURI(address(this), uri); print.gasUsed(); @@ -185,7 +185,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { ) ); } - _tryPrankAppointee_AllocationManager(IAllocationManager.slashOperator.selector); + _tryPrankAppointee_AllocationManager(IAllocationManagerActions.slashOperator.selector); (slashId, shares) = allocationManager.slashOperator(address(this), params); print.gasUsed(); } @@ -222,7 +222,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { ); } - _tryPrankAppointee_AllocationManager(IAllocationManager.slashOperator.selector); + _tryPrankAppointee_AllocationManager(IAllocationManagerActions.slashOperator.selector); IAllocationManager_PreRedistribution(address(allocationManager)).slashOperator(address(this), slashParams); print.gasUsed(); @@ -261,7 +261,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { ); } - _tryPrankAppointee_AllocationManager(IAllocationManager.slashOperator.selector); + _tryPrankAppointee_AllocationManager(IAllocationManagerActions.slashOperator.selector); (slashId, shares) = allocationManager.slashOperator(address(this), p); print.gasUsed(); } @@ -287,7 +287,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { DeregisterParams memory p = DeregisterParams({operator: address(operator), avs: address(this), operatorSetIds: operatorSetIds}); print.deregisterFromOperatorSets(p); - _tryPrankAppointee_AllocationManager(IAllocationManager.deregisterFromOperatorSets.selector); + _tryPrankAppointee_AllocationManager(IAllocationManagerActions.deregisterFromOperatorSets.selector); allocationManager.deregisterFromOperatorSets(p); print.gasUsed(); } @@ -295,7 +295,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { function setAVSRegistrar(IAVSRegistrar registrar) public createSnapshot { print.method("setAVSRegistrar"); console.log("Setting AVS registrar to: %s", address(registrar)); - _tryPrankAppointee_AllocationManager(IAllocationManager.setAVSRegistrar.selector); + _tryPrankAppointee_AllocationManager(IAllocationManagerActions.setAVSRegistrar.selector); allocationManager.setAVSRegistrar(address(this), registrar); print.gasUsed(); } @@ -308,7 +308,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { for (uint i; i < strategies.length; ++i) { console.log(" strategy: %s", address(strategies[i])); } - _tryPrankAppointee_AllocationManager(IAllocationManager.addStrategiesToOperatorSet.selector); + _tryPrankAppointee_AllocationManager(IAllocationManagerActions.addStrategiesToOperatorSet.selector); allocationManager.addStrategiesToOperatorSet(address(this), operatorSetId, strategies); print.gasUsed(); } @@ -321,7 +321,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { for (uint i; i < strategies.length; ++i) { console.log(" strategy: %s", address(strategies[i])); } - _tryPrankAppointee_AllocationManager(IAllocationManager.removeStrategiesFromOperatorSet.selector); + _tryPrankAppointee_AllocationManager(IAllocationManagerActions.removeStrategiesFromOperatorSet.selector); allocationManager.removeStrategiesFromOperatorSet(address(this), operatorSetId, strategies); print.gasUsed(); } diff --git a/src/test/integration/users/User.t.sol b/src/test/integration/users/User.t.sol index 579c39fe72..0136b9c845 100644 --- a/src/test/integration/users/User.t.sol +++ b/src/test/integration/users/User.t.sol @@ -21,7 +21,7 @@ import "src/test/utils/Logger.t.sol"; import "src/test/utils/ArrayLib.sol"; interface IUserDeployer { - function allocationManager() external view returns (AllocationManager); + function allocationManager() external view returns (IAllocationManager); function delegationManager() external view returns (DelegationManager); function permissionController() external view returns (PermissionController); function strategyManager() external view returns (StrategyManager); @@ -104,7 +104,7 @@ contract User is Logger, TypeImporter { ) ); - _tryPrankAppointee_AllocationManager(IAllocationManager.modifyAllocations.selector); + _tryPrankAppointee_AllocationManager(IAllocationManagerActions.modifyAllocations.selector); allocationManager().modifyAllocations(address(this), params.toArray()); print.gasUsed(); } @@ -132,7 +132,7 @@ contract User is Logger, TypeImporter { string.concat("{avs: ", Logger(operatorSet.avs).NAME_COLORED(), ", operatorSetId: ", cheats.toString(operatorSet.id), "}") ); - _tryPrankAppointee_AllocationManager(IAllocationManager.registerForOperatorSets.selector); + _tryPrankAppointee_AllocationManager(IAllocationManagerActions.registerForOperatorSets.selector); allocationManager().registerForOperatorSets( address(this), RegisterParams({avs: operatorSet.avs, operatorSetIds: operatorSet.id.toArrayU32(), data: ""}) ); @@ -145,7 +145,7 @@ contract User is Logger, TypeImporter { string.concat("{avs: ", Logger(operatorSet.avs).NAME_COLORED(), ", operatorSetId: ", cheats.toString(operatorSet.id), "}") ); - _tryPrankAppointee_AllocationManager(IAllocationManager.deregisterFromOperatorSets.selector); + _tryPrankAppointee_AllocationManager(IAllocationManagerActions.deregisterFromOperatorSets.selector); allocationManager().deregisterFromOperatorSets( DeregisterParams({operator: address(this), avs: operatorSet.avs, operatorSetIds: operatorSet.id.toArrayU32()}) ); @@ -193,7 +193,7 @@ contract User is Logger, TypeImporter { function setAllocationDelay(uint32 delay) public virtual createSnapshot { print.method("setAllocationDelay"); - _tryPrankAppointee_AllocationManager(IAllocationManager.setAllocationDelay.selector); + _tryPrankAppointee_AllocationManager(IAllocationManagerActions.setAllocationDelay.selector); allocationManager().setAllocationDelay(address(this), delay); print.gasUsed(); @@ -613,8 +613,8 @@ contract User is Logger, TypeImporter { /// View Methods /// ----------------------------------------------------------------------- - function allocationManager() public view returns (AllocationManager) { - return AllocationManager(address(delegationManager.allocationManager())); + function allocationManager() public view returns (IAllocationManager) { + return IAllocationManager(address(delegationManager.allocationManager())); } function permissionController() public view returns (PermissionController) { diff --git a/src/test/unit/AllocationManagerUnit.t.sol b/src/test/unit/AllocationManagerUnit.t.sol index 5398010d2b..89b141d9d2 100644 --- a/src/test/unit/AllocationManagerUnit.t.sol +++ b/src/test/unit/AllocationManagerUnit.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.27; import "src/test/harnesses/AllocationManagerHarness.sol"; +import "src/contracts/core/AllocationManagerView.sol"; import "src/test/utils/EigenLayerUnitTestSetup.sol"; import "src/test/mocks/MockAVSRegistrar.sol"; @@ -35,7 +36,7 @@ contract AllocationManagerUnitTests is EigenLayerUnitTestSetup, IAllocationManag /// Mocks /// ----------------------------------------------------------------------- - AllocationManagerHarness allocationManager; + IAllocationManager allocationManager; ERC20PresetFixedSupply tokenMock; StrategyBase strategyMock; @@ -91,13 +92,17 @@ contract AllocationManagerUnitTests is EigenLayerUnitTestSetup, IAllocationManag function _initializeAllocationManager(IPauserRegistry _pauserRegistry, uint _initialPausedStatus) internal - returns (AllocationManagerHarness) + returns (IAllocationManager) { - return allocationManager = AllocationManagerHarness( + IAllocationManagerView allocationManagerView = new AllocationManagerView( + IDelegationManager(address(delegationManagerMock)), eigenStrategy, DEALLOCATION_DELAY, ALLOCATION_CONFIGURATION_DELAY + ); + return allocationManager = IAllocationManager( address( new TransparentUpgradeableProxy( address( new AllocationManagerHarness( + allocationManagerView, IDelegationManager(address(delegationManagerMock)), eigenStrategy, _pauserRegistry, @@ -285,7 +290,7 @@ contract AllocationManagerUnitTests is EigenLayerUnitTestSetup, IAllocationManag uint32 lastEffectBlock = 0; for (uint i = 0; i < numDeallocations; ++i) { - bytes32 operatorSetKey = allocationManager.deallocationQueueAtIndex(operator, strategy, i); + bytes32 operatorSetKey = AllocationManagerHarness(address(allocationManager)).deallocationQueueAtIndex(operator, strategy, i); Allocation memory allocation = allocationManager.getAllocation(operator, OperatorSetLib.decode(operatorSetKey), strategy); assertTrue(lastEffectBlock <= allocation.effectBlock, "Deallocation queue is not in ascending order of effectBlocks"); @@ -553,7 +558,7 @@ contract AllocationManagerUnitTests_Initialization_Setters is AllocationManagerU // Deploy the contract with the expected initial state. uint initialPausedStatus = r.Uint256(); - AllocationManager alm = _initializeAllocationManager(expectedPauserRegistry, initialPausedStatus); + IAllocationManager alm = _initializeAllocationManager(expectedPauserRegistry, initialPausedStatus); // Assert that the contract can only be initialized once. vm.expectRevert("Initializable: contract is already initialized"); diff --git a/src/test/unit/PermissionControllerUnit.t.sol b/src/test/unit/PermissionControllerUnit.t.sol index 4119f1a700..f9af5c564e 100644 --- a/src/test/unit/PermissionControllerUnit.t.sol +++ b/src/test/unit/PermissionControllerUnit.t.sol @@ -18,7 +18,7 @@ contract PermissionControllerUnitTests is EigenLayerUnitTestSetup, IPermissionCo address target1; address target2; bytes4 selector1 = IDelegationManager.updateOperatorMetadataURI.selector; - bytes4 selector2 = IAllocationManager.modifyAllocations.selector; + bytes4 selector2 = IAllocationManagerActions.modifyAllocations.selector; function setUp() public virtual override { // Setup - already deploys permissionController diff --git a/src/test/unit/SplitContractMixin.t.sol b/src/test/unit/SplitContractMixin.t.sol new file mode 100644 index 0000000000..d6c365fce4 --- /dev/null +++ b/src/test/unit/SplitContractMixin.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "forge-std/Test.sol"; +import "src/contracts/mixins/SplitContractMixin.sol"; + +contract SplitContractMixinTest is Test, SplitContractMixin { + uint value; + Delegate public delegate; + + constructor() SplitContractMixin(address(0x123)) { + value = vm.randomUint(); + delegate = new Delegate(); + } + + function getValue() public view returns (uint result) { + _delegateView(address(delegate)); + result; + } + + function test_getValue() public { + (bool success, bytes memory data) = address(this).call(abi.encodeWithSelector(this.getValue.selector)); + assertTrue(success); + uint result = abi.decode(data, (uint)); + assertEq(result, value); + } +} + +// Mock contract to test delegation +contract Delegate is Test { + uint value; + + function getValue() public view returns (uint result) { + return value; + } +} diff --git a/src/test/utils/Logger.t.sol b/src/test/utils/Logger.t.sol index 9d86400f21..e1432fb801 100644 --- a/src/test/utils/Logger.t.sol +++ b/src/test/utils/Logger.t.sol @@ -52,25 +52,25 @@ abstract contract Logger is Test { bytes32 constant LOG_STATE_SLOT = bytes32(0); modifier noTracing() { - uint traceCounter = _getTraceCounter(); - if (traceCounter == 0) cheats.pauseTracing(); + // uint traceCounter = _getTraceCounter(); + // if (traceCounter == 0) cheats.pauseTracing(); - traceCounter++; - _setTraceCounter(traceCounter); + // traceCounter++; + // _setTraceCounter(traceCounter); _; - traceCounter = _getTraceCounter(); - traceCounter--; - _setTraceCounter(traceCounter); + // traceCounter = _getTraceCounter(); + // traceCounter--; + // _setTraceCounter(traceCounter); - if (traceCounter == 0) cheats.resumeTracing(); + // if (traceCounter == 0) cheats.resumeTracing(); } modifier noLogging() { - logging = false; + // logging = false; _; - logging = true; + // logging = true; } /// -----------------------------------------------------------------------