From 8068700de74b4fb44e597dbd0ed06be6b4aa5af1 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Wed, 23 Oct 2024 11:18:27 +0200 Subject: [PATCH 01/23] feat(contracts): rm wrong eigenlayer submodule --- .gitmodules | 3 --- bolt-contracts/lib/eigenlayer-contracts | 1 - 2 files changed, 4 deletions(-) delete mode 160000 bolt-contracts/lib/eigenlayer-contracts diff --git a/.gitmodules b/.gitmodules index eda39c33e..117c69dbd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,9 +7,6 @@ [submodule "bolt-contracts/lib/core"] path = bolt-contracts/lib/core url = https://github.com/symbioticfi/core -[submodule "bolt-contracts/lib/eigenlayer-contracts"] - path = bolt-contracts/lib/eigenlayer-contracts - url = https://github.com/layr-labs/eigenlayer-contracts [submodule "bolt-contracts/lib/openzeppelin-contracts-upgradeable"] path = bolt-contracts/lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable diff --git a/bolt-contracts/lib/eigenlayer-contracts b/bolt-contracts/lib/eigenlayer-contracts deleted file mode 160000 index 00fc4b95e..000000000 --- a/bolt-contracts/lib/eigenlayer-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 00fc4b95e9c1a5c4f370e41f56d01052d186da07 From fc2e9f598595aa01dee7223cb79cb7d347e33879 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Wed, 23 Oct 2024 11:18:31 +0200 Subject: [PATCH 02/23] forge install: eigenlayer-contracts testnet-holesky --- .gitmodules | 3 +++ bolt-contracts/lib/eigenlayer-contracts | 1 + 2 files changed, 4 insertions(+) create mode 160000 bolt-contracts/lib/eigenlayer-contracts diff --git a/.gitmodules b/.gitmodules index 117c69dbd..fc82349f4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "bolt-contracts/lib/openzeppelin-foundry-upgrades"] path = bolt-contracts/lib/openzeppelin-foundry-upgrades url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades +[submodule "bolt-contracts/lib/eigenlayer-contracts"] + path = bolt-contracts/lib/eigenlayer-contracts + url = https://github.com/layr-labs/eigenlayer-contracts diff --git a/bolt-contracts/lib/eigenlayer-contracts b/bolt-contracts/lib/eigenlayer-contracts new file mode 160000 index 000000000..898c3e07e --- /dev/null +++ b/bolt-contracts/lib/eigenlayer-contracts @@ -0,0 +1 @@ +Subproject commit 898c3e07ed52440876cedb03da07c8578e1da166 From c98bdfce946358bf9ffa37352fc389a728f70558 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Wed, 23 Oct 2024 11:20:33 +0200 Subject: [PATCH 03/23] feat(contracts): rm wrong symbiotic submodule --- .gitmodules | 3 --- bolt-contracts/lib/core | 1 - 2 files changed, 4 deletions(-) delete mode 160000 bolt-contracts/lib/core diff --git a/.gitmodules b/.gitmodules index fc82349f4..079419ca4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,9 +4,6 @@ [submodule "bolt-contracts/lib/openzeppelin-contracts"] path = bolt-contracts/lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts -[submodule "bolt-contracts/lib/core"] - path = bolt-contracts/lib/core - url = https://github.com/symbioticfi/core [submodule "bolt-contracts/lib/openzeppelin-contracts-upgradeable"] path = bolt-contracts/lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable diff --git a/bolt-contracts/lib/core b/bolt-contracts/lib/core deleted file mode 160000 index 76bf70945..000000000 --- a/bolt-contracts/lib/core +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 76bf709458410b2682f7bc20c6e3a90845bf4b51 From d6ad4eed942b1164c38afa6574479cd58a9d2171 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Wed, 23 Oct 2024 11:20:41 +0200 Subject: [PATCH 04/23] forge install: core 1.0.0-devnet.8 --- .gitmodules | 3 +++ bolt-contracts/lib/core | 1 + 2 files changed, 4 insertions(+) create mode 160000 bolt-contracts/lib/core diff --git a/.gitmodules b/.gitmodules index 079419ca4..82a905b4f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "bolt-contracts/lib/eigenlayer-contracts"] path = bolt-contracts/lib/eigenlayer-contracts url = https://github.com/layr-labs/eigenlayer-contracts +[submodule "bolt-contracts/lib/core"] + path = bolt-contracts/lib/core + url = https://github.com/symbioticfi/core diff --git a/bolt-contracts/lib/core b/bolt-contracts/lib/core new file mode 160000 index 000000000..f38f1b16b --- /dev/null +++ b/bolt-contracts/lib/core @@ -0,0 +1 @@ +Subproject commit f38f1b16b8207dcff55d681a0d5ba28c66e785c8 From f72732a743caeca119b357c946bbf4a6d7cdbbf4 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Wed, 23 Oct 2024 11:39:16 +0200 Subject: [PATCH 05/23] test(contracts): fix Symbiotic tests with correct version --- .../test/BoltManager.Symbiotic.t.sol | 48 ++++++++++--------- .../test/fixtures/SymbioticSetup.f.sol | 11 ++--- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/bolt-contracts/test/BoltManager.Symbiotic.t.sol b/bolt-contracts/test/BoltManager.Symbiotic.t.sol index d64492646..75b7a16ad 100644 --- a/bolt-contracts/test/BoltManager.Symbiotic.t.sol +++ b/bolt-contracts/test/BoltManager.Symbiotic.t.sol @@ -10,6 +10,7 @@ import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; import {IOptInService} from "@symbiotic/interfaces/service/IOptInService.sol"; import {IVaultConfigurator} from "@symbiotic/interfaces/IVaultConfigurator.sol"; import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; +import {IBaseSlasher} from "@symbiotic/interfaces/slasher/IBaseSlasher.sol"; import {IMetadataService} from "@symbiotic/interfaces/service/IMetadataService.sol"; import {INetworkRestakeDelegator} from "@symbiotic/interfaces/delegator/INetworkRestakeDelegator.sol"; import {INetworkMiddlewareService} from "@symbiotic/interfaces/service/INetworkMiddlewareService.sol"; @@ -18,7 +19,6 @@ import {IVetoSlasher} from "@symbiotic/interfaces/slasher/IVetoSlasher.sol"; import {IDelegatorFactory} from "@symbiotic/interfaces/IDelegatorFactory.sol"; import {IMigratablesFactory} from "@symbiotic/interfaces/common/IMigratablesFactory.sol"; import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; -import {SimpleCollateral} from "@symbiotic/../test/mocks/SimpleCollateral.sol"; import {IBoltValidatorsV1} from "../src/interfaces/IBoltValidatorsV1.sol"; import {IBoltMiddlewareV1} from "../src/interfaces/IBoltMiddlewareV1.sol"; @@ -32,6 +32,7 @@ import {BoltConfig} from "../src/lib/Config.sol"; import {Utils} from "./Utils.sol"; import {SymbioticSetupFixture} from "./fixtures/SymbioticSetup.f.sol"; +import {Token} from "../test/mocks/Token.sol"; contract BoltManagerSymbioticTest is Test { using BLS12381 for BLS12381.G1Point; @@ -60,7 +61,7 @@ contract BoltManagerSymbioticTest is Test { IVault public vault; INetworkRestakeDelegator public networkRestakeDelegator; IVaultConfigurator public vaultConfigurator; - SimpleCollateral public collateral; + Token public collateral; address deployer = makeAddr("deployer"); address admin = makeAddr("admin"); @@ -102,21 +103,21 @@ contract BoltManagerSymbioticTest is Test { IVaultConfigurator.InitParams memory vaultConfiguratorInitParams = IVaultConfigurator.InitParams({ version: IMigratablesFactory(vaultConfigurator.VAULT_FACTORY()).lastVersion(), owner: vaultAdmin, - vaultParams: IVault.InitParams({ - collateral: address(collateral), - delegator: address(0), - slasher: address(0), - burner: address(0xdead), - epochDuration: EPOCH_DURATION, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: vaultAdmin, - depositWhitelistSetRoleHolder: vaultAdmin, - depositorWhitelistRoleHolder: vaultAdmin, - isDepositLimitSetRoleHolder: vaultAdmin, - depositLimitSetRoleHolder: vaultAdmin - }), + vaultParams: abi.encode( + IVault.InitParams({ + collateral: address(collateral), + burner: address(0xdead), + epochDuration: EPOCH_DURATION, + depositWhitelist: false, + isDepositLimit: false, + depositLimit: 0, + defaultAdminRoleHolder: vaultAdmin, + depositWhitelistSetRoleHolder: vaultAdmin, + depositorWhitelistRoleHolder: vaultAdmin, + isDepositLimitSetRoleHolder: vaultAdmin, + depositLimitSetRoleHolder: vaultAdmin + }) + ), delegatorIndex: 0, // Use NetworkRestakeDelegator delegatorParams: abi.encode( INetworkRestakeDelegator.InitParams({ @@ -133,6 +134,9 @@ contract BoltManagerSymbioticTest is Test { slasherIndex: 1, // Use VetoSlasher slasherParams: abi.encode( IVetoSlasher.InitParams({ + baseParams: IBaseSlasher.BaseParams({ + isBurnerHook: false // ? + }), // veto duration must be smaller than epoch duration vetoDuration: uint48(12 hours), resolverSetEpochsDelay: 3 @@ -245,12 +249,10 @@ contract BoltManagerSymbioticTest is Test { networkRestakeDelegator.setNetworkLimit(subnetwork, 2 ether); // --- Add stake to the Vault --- + deal(address(collateral), provider, 1 ether); vm.prank(provider); - SimpleCollateral(collateral).mint(1 ether); - - vm.prank(provider); - SimpleCollateral(collateral).approve(address(vault), 1 ether); + collateral.approve(address(vault), 1 ether); // deposit collateral from "provider" on behalf of "operator" vm.prank(provider); @@ -258,8 +260,8 @@ contract BoltManagerSymbioticTest is Test { assertEq(depositedAmount, 1 ether); assertEq(mintedShares, 1 ether); - assertEq(vault.balanceOf(operator), 1 ether); - assertEq(SimpleCollateral(collateral).balanceOf(address(vault)), 1 ether); + assertEq(vault.slashableBalanceOf(operator), 1 ether); + assertEq(collateral.balanceOf(address(vault)), 1 ether); } /// @notice Compute the hash of a BLS public key diff --git a/bolt-contracts/test/fixtures/SymbioticSetup.f.sol b/bolt-contracts/test/fixtures/SymbioticSetup.f.sol index e23fdafb7..c5afca2a4 100644 --- a/bolt-contracts/test/fixtures/SymbioticSetup.f.sol +++ b/bolt-contracts/test/fixtures/SymbioticSetup.f.sol @@ -19,7 +19,6 @@ import {Slasher} from "@symbiotic/contracts/slasher/Slasher.sol"; import {VetoSlasher} from "@symbiotic/contracts/slasher/VetoSlasher.sol"; import {VaultConfigurator} from "@symbiotic/contracts/VaultConfigurator.sol"; -import {SimpleCollateral} from "@symbiotic/../test/mocks/SimpleCollateral.sol"; import {Token} from "../mocks/Token.sol"; contract SymbioticSetupFixture is Test { @@ -40,7 +39,7 @@ contract SymbioticSetupFixture is Test { OptInService operatorVaultOptInService, OptInService operatorNetworkOptInService, VaultConfigurator vaultConfigurator, - SimpleCollateral collateral + Token collateral ) { vm.startPrank(deployer); @@ -53,9 +52,10 @@ contract SymbioticSetupFixture is Test { MetadataService operatorMetadataService_ = new MetadataService(address(operatorRegistry_)); MetadataService networkMetadataService_ = new MetadataService(address(networkRegistry_)); NetworkMiddlewareService networkMiddlewareService_ = new NetworkMiddlewareService(address(networkRegistry_)); - OptInService operatorVaultOptInService_ = new OptInService(address(operatorRegistry_), address(vaultFactory_)); + OptInService operatorVaultOptInService_ = + new OptInService(address(operatorRegistry_), address(vaultFactory_), "vaultOptIn"); OptInService operatorNetworkOptInService_ = - new OptInService(address(operatorRegistry_), address(networkRegistry_)); + new OptInService(address(operatorRegistry_), address(networkRegistry_), "networkOptIn"); Vault vault_ = new Vault(address(delegatorFactory_), address(slasherFactory_), address(vaultFactory_)); vaultFactory_.whitelist(address(vault_)); @@ -113,7 +113,6 @@ contract SymbioticSetupFixture is Test { slasherFactory_.transferOwnership(owner); Token token_ = new Token("Token"); - SimpleCollateral collateral_ = new SimpleCollateral(address(token_)); vm.stopPrank(); @@ -129,7 +128,7 @@ contract SymbioticSetupFixture is Test { operatorVaultOptInService_, operatorNetworkOptInService_, vaultConfigurator_, - collateral_ + token_ ); } } From 160a6c54a4cf76839298acf6293bad2e92d5d89d Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Wed, 23 Oct 2024 11:50:26 +0200 Subject: [PATCH 06/23] feat(contracts): create BoltEigenLayerMiddlewareV2 --- .../contracts/BoltEigenLayerMiddlewareV2.sol | 355 ++++++++++++++++++ 1 file changed, 355 insertions(+) create mode 100644 bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV2.sol diff --git a/bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV2.sol b/bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV2.sol new file mode 100644 index 000000000..828a3367a --- /dev/null +++ b/bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV2.sol @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; +import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; + +import {MapWithTimeData} from "../lib/MapWithTimeData.sol"; +import {IBoltParametersV1} from "../interfaces/IBoltParametersV1.sol"; +import {IBoltValidatorsV1} from "../interfaces/IBoltValidatorsV1.sol"; +import {IBoltMiddlewareV1} from "../interfaces/IBoltMiddlewareV1.sol"; +import {IBoltManagerV1} from "../interfaces/IBoltManagerV1.sol"; + +import {IStrategyManager} from "@eigenlayer/src/contracts/interfaces/IStrategyManager.sol"; +import {IServiceManager} from "@eigenlayer/src/contracts/interfaces/IServiceManager.sol"; +import {IAVSDirectory} from "@eigenlayer/src/contracts/interfaces/IAVSDirectory.sol"; +import {IDelegationManager} from "@eigenlayer/src/contracts/interfaces/IDelegationManager.sol"; +import {ISignatureUtils} from "@eigenlayer/src/contracts/interfaces/ISignatureUtils.sol"; +import {IStrategy} from "@eigenlayer/src/contracts/interfaces/IStrategy.sol"; +import {AVSDirectoryStorage} from "@eigenlayer/src/contracts/core/AVSDirectoryStorage.sol"; +import {DelegationManagerStorage} from "@eigenlayer/src/contracts/core/DelegationManagerStorage.sol"; +import {StrategyManagerStorage} from "@eigenlayer/src/contracts/core/StrategyManagerStorage.sol"; + +/// @title Bolt Manager +/// @notice This contract is responsible for interfacing with the EigenLayer restaking protocol. +/// @dev This contract is upgradeable using the UUPSProxy pattern. Storage layout remains fixed across upgrades +/// with the use of storage gaps. +/// See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps +/// To validate the storage layout, use the Openzeppelin Foundry Upgrades toolkit. +/// You can also validate manually with forge: forge inspect storage-layout --pretty +contract BoltEigenLayerMiddlewareV1 is IBoltMiddlewareV1, OwnableUpgradeable, UUPSUpgradeable { + using EnumerableSet for EnumerableSet.AddressSet; + using EnumerableMap for EnumerableMap.AddressToUintMap; + using MapWithTimeData for EnumerableMap.AddressToUintMap; + + // ========= STORAGE ========= + + /// @notice Start timestamp of the first epoch. + uint48 public START_TIMESTAMP; + + /// @notice Bolt Parameters contract. + IBoltParametersV1 public parameters; + + /// @notice Validators registry, where validators are registered via their + /// BLS pubkey and are assigned a sequence number. + IBoltManagerV1 public manager; + + /// @notice Set of EigenLayer protocol strategies that are used in Bolt Protocol. + EnumerableMap.AddressToUintMap private strategies; + + /// @notice Address of the EigenLayer AVS Directory contract. + IAVSDirectory public AVS_DIRECTORY; + + /// @notice Address of the EigenLayer Delegation Manager contract. + DelegationManagerStorage public DELEGATION_MANAGER; + + /// @notice Address of the EigenLayer Strategy Manager contract. + StrategyManagerStorage public STRATEGY_MANAGER; + + /// @notice Name hash of the restaking protocol for identifying the instance of `IBoltMiddleware`. + bytes32 public NAME_HASH; + + // --> Storage layout marker: 9 slots + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + * This can be validated with the Openzeppelin Foundry Upgrades toolkit. + * + * Total storage slots: 50 + */ + uint256[41] private __gap; + + // ========= ERRORS ========= + + error StrategyNotAllowed(); + error OperatorAlreadyRegisteredToAVS(); + + // ========= INITIALIZER & PROXY FUNCTIONALITY ========= // + + /// @notice Constructor for the BoltEigenLayerMiddleware contract. + /// @param _parameters The address of the Bolt Parameters contract. + /// @param _manager The address of the Bolt Manager contract. + /// @param _eigenlayerAVSDirectory The address of the EigenLayer AVS Directory contract. + /// @param _eigenlayerDelegationManager The address of the EigenLayer Delegation Manager contract. + /// @param _eigenlayerStrategyManager The address of the EigenLayer Strategy Manager. + function initialize( + address _owner, + address _parameters, + address _manager, + address _eigenlayerAVSDirectory, + address _eigenlayerDelegationManager, + address _eigenlayerStrategyManager + ) public initializer { + __Ownable_init(_owner); + parameters = IBoltParametersV1(_parameters); + manager = IBoltManagerV1(_manager); + START_TIMESTAMP = Time.timestamp(); + + AVS_DIRECTORY = IAVSDirectory(_eigenlayerAVSDirectory); + DELEGATION_MANAGER = DelegationManagerStorage(_eigenlayerDelegationManager); + STRATEGY_MANAGER = StrategyManagerStorage(_eigenlayerStrategyManager); + NAME_HASH = keccak256("EIGENLAYER"); + } + + function _authorizeUpgrade( + address newImplementation + ) internal override onlyOwner {} + + // ========= VIEW FUNCTIONS ========= + + /// @notice Get the start timestamp of an epoch. + function getEpochStartTs( + uint48 epoch + ) public view returns (uint48 timestamp) { + return START_TIMESTAMP + epoch * parameters.EPOCH_DURATION(); + } + + /// @notice Get the epoch at a given timestamp. + function getEpochAtTs( + uint48 timestamp + ) public view returns (uint48 epoch) { + return (timestamp - START_TIMESTAMP) / parameters.EPOCH_DURATION(); + } + + /// @notice Get the current epoch. + function getCurrentEpoch() public view returns (uint48 epoch) { + return getEpochAtTs(Time.timestamp()); + } + + function getWhitelistedStrategies() public view returns (address[] memory) { + return strategies.keys(); + } + + // ========= ADMIN FUNCTIONS ========= + /// @notice Register a strategy to work in Bolt Protocol. + /// @param strategy The EigenLayer strategy address + function registerStrategy( + address strategy + ) public onlyOwner { + if (strategies.contains(strategy)) { + revert AlreadyRegistered(); + } + + if (!STRATEGY_MANAGER.strategyIsWhitelistedForDeposit(IStrategy(strategy))) { + revert StrategyNotAllowed(); + } + + strategies.add(strategy); + strategies.enable(strategy); + } + + /// @notice Deregister a strategy from working in Bolt Protocol. + /// @param strategy The EigenLayer strategy address. + function deregisterStrategy( + address strategy + ) public onlyOwner { + if (!strategies.contains(strategy)) { + revert NotRegistered(); + } + + strategies.remove(strategy); + } + + // ========= EIGENLAYER MIDDLEWARE LOGIC ========= + + /// @notice Allow an operator to signal opt-in to Bolt Protocol. + /// @dev This requires calling the EigenLayer AVS Directory contract to register the operator. + /// EigenLayer internally contains a mapping from `msg.sender` (our AVS contract) to the operator. + /// The msg.sender of this call will be the operator address. + function registerOperator( + string calldata rpc, + ISignatureUtils.SignatureWithSaltAndExpiry calldata operatorSignature + ) public { + if (manager.isOperator(msg.sender)) { + revert AlreadyRegistered(); + } + + if (!DELEGATION_MANAGER.isOperator(msg.sender)) { + revert NotOperator(); + } + + // Register the operator to the AVS directory for this AVS + AVS_DIRECTORY.registerOperatorToAVS(msg.sender, operatorSignature); + + // Register the operator in the manager + manager.registerOperator(msg.sender, rpc); + } + + /// @notice Deregister an EigenLayer operator from working in Bolt Protocol. + /// @dev This requires calling the EigenLayer AVS Directory contract to deregister the operator. + /// EigenLayer internally contains a mapping from `msg.sender` (our AVS contract) to the operator. + function deregisterOperator() public { + if (!manager.isOperator(msg.sender)) { + revert NotRegistered(); + } + + AVS_DIRECTORY.deregisterOperatorFromAVS(msg.sender); + + manager.deregisterOperator(msg.sender); + } + + /// @notice Allow an operator to signal indefinite opt-out from Bolt Protocol. + /// @dev Pausing activity does not prevent the operator from being slashable for + /// the current network epoch until the end of the slashing window. + function pauseOperator() public { + manager.pauseOperator(msg.sender); + } + + /// @notice Allow a disabled operator to signal opt-in to Bolt Protocol. + function unpauseOperator() public { + manager.unpauseOperator(msg.sender); + } + + /// @notice Allow a strategy to signal indefinite opt-out from Bolt Protocol. + function pauseStrategy() public { + if (!strategies.contains(msg.sender)) { + revert NotRegistered(); + } + + strategies.disable(msg.sender); + } + + /// @notice Allow a disabled strategy to signal opt-in to Bolt Protocol. + function unpauseStrategy() public { + if (!strategies.contains(msg.sender)) { + revert NotRegistered(); + } + + strategies.enable(msg.sender); + } + + /// @notice Check if a strategy is currently enabled to work in Bolt Protocol. + /// @param strategy The strategy address to check the enabled status for. + /// @return True if the strategy is enabled, false otherwise. + function isStrategyEnabled( + address strategy + ) public view returns (bool) { + (uint48 enabledTime, uint48 disabledTime) = strategies.getTimes(strategy); + return enabledTime != 0 && disabledTime == 0; + } + + /// @notice Get the collaterals and amounts staked by an operator across the supported strategies. + /// + /// @param operator The operator address to get the collaterals and amounts staked for. + /// @return collaterals The collaterals staked by the operator. + /// @dev Assumes that the operator is registered and enabled. + function getOperatorCollaterals( + address operator + ) public view returns (address[] memory, uint256[] memory) { + address[] memory collateralTokens = new address[](strategies.length()); + uint256[] memory amounts = new uint256[](strategies.length()); + + uint48 epochStartTs = getEpochStartTs(getEpochAtTs(Time.timestamp())); + + IStrategy[] memory strategyImpls = new IStrategy[](strategies.length()); + + for (uint256 i = 0; i < strategies.length(); ++i) { + (address strategy, uint48 enabledTime, uint48 disabledTime) = strategies.atWithTimes(i); + + if (!_wasEnabledAt(enabledTime, disabledTime, epochStartTs)) { + continue; + } + + IStrategy strategyImpl = IStrategy(strategy); + + address collateral = address(strategyImpl.underlyingToken()); + collateralTokens[i] = collateral; + + strategyImpls[i] = strategyImpl; + } + + // NOTE: order is preserved, which is why we can use the same index for both arrays below + uint256[] memory shares = DELEGATION_MANAGER.getOperatorShares(operator, strategyImpls); + + for (uint256 i = 0; i < strategyImpls.length; ++i) { + amounts[i] = strategyImpls[i].sharesToUnderlyingView(shares[i]); + } + + return (collateralTokens, amounts); + } + + /// @notice Get the amount of tokens delegated to an operator across the allowed strategies. + /// @param operator The operator address to get the stake for. + /// @param collateral The collateral address to get the stake for. + /// @return amount The amount of tokens delegated to the operator of the specified collateral. + function getOperatorStake(address operator, address collateral) public view returns (uint256 amount) { + uint48 timestamp = Time.timestamp(); + return getOperatorStakeAt(operator, collateral, timestamp); + } + + /// @notice Get the stake of an operator in EigenLayer protocol at a given timestamp. + /// @param operator The operator address to check the stake for. + /// @param collateral The collateral address to check the stake for. + /// @param timestamp The timestamp to check the stake at. + /// @return amount The stake of the operator at the given timestamp, in collateral token. + function getOperatorStakeAt( + address operator, + address collateral, + uint48 timestamp + ) public view returns (uint256 amount) { + if (timestamp > Time.timestamp() || timestamp < START_TIMESTAMP) { + revert InvalidQuery(); + } + + uint48 epochStartTs = getEpochStartTs(getEpochAtTs(timestamp)); + + // NOTE: Can this be done more gas-efficiently? + IStrategy[] memory strategyMem = new IStrategy[](1); + + for (uint256 i = 0; i < strategies.length(); i++) { + (address strategy, uint48 enabledTime, uint48 disabledTime) = strategies.atWithTimes(i); + + if (collateral != address(IStrategy(strategy).underlyingToken())) { + continue; + } + + if (!_wasEnabledAt(enabledTime, disabledTime, epochStartTs)) { + continue; + } + + strategyMem[0] = IStrategy(strategy); + // NOTE: order is preserved i.e., shares[i] corresponds to strategies[i] + uint256[] memory shares = DELEGATION_MANAGER.getOperatorShares(operator, strategyMem); + amount += IStrategy(strategy).sharesToUnderlyingView(shares[0]); + } + + return amount; + } + + // ========= EIGENLAYER AVS FUNCTIONS ========= + + /// @notice emits an `AVSMetadataURIUpdated` event indicating the information has updated. + /// @param metadataURI The URI for metadata associated with an avs + function updateAVSMetadataURI( + string calldata metadataURI + ) public onlyOwner { + AVS_DIRECTORY.updateAVSMetadataURI(metadataURI); + } + + // ========= HELPER FUNCTIONS ========= + + /// @notice Check if a map entry was active at a given timestamp. + /// @param enabledTime The enabled time of the map entry. + /// @param disabledTime The disabled time of the map entry. + /// @param timestamp The timestamp to check the map entry status at. + /// @return True if the map entry was active at the given timestamp, false otherwise. + function _wasEnabledAt(uint48 enabledTime, uint48 disabledTime, uint48 timestamp) private pure returns (bool) { + return enabledTime != 0 && enabledTime <= timestamp && (disabledTime == 0 || disabledTime >= timestamp); + } +} From 71c787c1750025eeda959a7233c79ecf8166be3e Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Wed, 23 Oct 2024 11:50:54 +0200 Subject: [PATCH 07/23] forge install: eigenlayer-middleware testnet-holesky --- .gitmodules | 3 +++ bolt-contracts/lib/eigenlayer-middleware | 1 + 2 files changed, 4 insertions(+) create mode 160000 bolt-contracts/lib/eigenlayer-middleware diff --git a/.gitmodules b/.gitmodules index 82a905b4f..79ae70dae 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,6 @@ [submodule "bolt-contracts/lib/core"] path = bolt-contracts/lib/core url = https://github.com/symbioticfi/core +[submodule "bolt-contracts/lib/eigenlayer-middleware"] + path = bolt-contracts/lib/eigenlayer-middleware + url = https://github.com/layr-labs/eigenlayer-middleware diff --git a/bolt-contracts/lib/eigenlayer-middleware b/bolt-contracts/lib/eigenlayer-middleware new file mode 160000 index 000000000..48e0aecae --- /dev/null +++ b/bolt-contracts/lib/eigenlayer-middleware @@ -0,0 +1 @@ +Subproject commit 48e0aecae3f778356a5009f912ade946b285fe9b From 333d8dda0deceefd922f24b5de72226f6b99e1a7 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Wed, 23 Oct 2024 13:55:46 +0200 Subject: [PATCH 08/23] feat(contracts): implement IServiceManager --- bolt-contracts/foundry.toml | 1 + .../contracts/BoltEigenLayerMiddlewareV2.sol | 87 ++++++++++++++----- 2 files changed, 66 insertions(+), 22 deletions(-) diff --git a/bolt-contracts/foundry.toml b/bolt-contracts/foundry.toml index b19612e4a..8dd782afe 100644 --- a/bolt-contracts/foundry.toml +++ b/bolt-contracts/foundry.toml @@ -26,6 +26,7 @@ remappings = [ "@relic/=lib/relic-sdk/packages/contracts", "@symbiotic/=lib/core/src/", "@eigenlayer/=lib/eigenlayer-contracts/", + "@eigenlayer-middleware/=lib/eigenlayer-middleware/", "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/", "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/", "@openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/", diff --git a/bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV2.sol b/bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV2.sol index 828a3367a..9fe54b216 100644 --- a/bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV2.sol +++ b/bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV2.sol @@ -14,8 +14,8 @@ import {IBoltValidatorsV1} from "../interfaces/IBoltValidatorsV1.sol"; import {IBoltMiddlewareV1} from "../interfaces/IBoltMiddlewareV1.sol"; import {IBoltManagerV1} from "../interfaces/IBoltManagerV1.sol"; +import {IServiceManager} from "@eigenlayer-middleware/src/interfaces/IServiceManager.sol"; import {IStrategyManager} from "@eigenlayer/src/contracts/interfaces/IStrategyManager.sol"; -import {IServiceManager} from "@eigenlayer/src/contracts/interfaces/IServiceManager.sol"; import {IAVSDirectory} from "@eigenlayer/src/contracts/interfaces/IAVSDirectory.sol"; import {IDelegationManager} from "@eigenlayer/src/contracts/interfaces/IDelegationManager.sol"; import {ISignatureUtils} from "@eigenlayer/src/contracts/interfaces/ISignatureUtils.sol"; @@ -31,7 +31,7 @@ import {StrategyManagerStorage} from "@eigenlayer/src/contracts/core/StrategyMan /// See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps /// To validate the storage layout, use the Openzeppelin Foundry Upgrades toolkit. /// You can also validate manually with forge: forge inspect storage-layout --pretty -contract BoltEigenLayerMiddlewareV1 is IBoltMiddlewareV1, OwnableUpgradeable, UUPSUpgradeable { +contract BoltEigenLayerMiddlewareV1 is IBoltMiddlewareV1, IServiceManager, OwnableUpgradeable, UUPSUpgradeable { using EnumerableSet for EnumerableSet.AddressSet; using EnumerableMap for EnumerableMap.AddressToUintMap; using MapWithTimeData for EnumerableMap.AddressToUintMap; @@ -184,8 +184,7 @@ contract BoltEigenLayerMiddlewareV1 is IBoltMiddlewareV1, OwnableUpgradeable, UU revert NotOperator(); } - // Register the operator to the AVS directory for this AVS - AVS_DIRECTORY.registerOperatorToAVS(msg.sender, operatorSignature); + registerOperatorToAVS(msg.sender, operatorSignature); // Register the operator in the manager manager.registerOperator(msg.sender, rpc); @@ -199,7 +198,7 @@ contract BoltEigenLayerMiddlewareV1 is IBoltMiddlewareV1, OwnableUpgradeable, UU revert NotRegistered(); } - AVS_DIRECTORY.deregisterOperatorFromAVS(msg.sender); + deregisterOperatorFromAVS(msg.sender); manager.deregisterOperator(msg.sender); } @@ -257,8 +256,6 @@ contract BoltEigenLayerMiddlewareV1 is IBoltMiddlewareV1, OwnableUpgradeable, UU uint48 epochStartTs = getEpochStartTs(getEpochAtTs(Time.timestamp())); - IStrategy[] memory strategyImpls = new IStrategy[](strategies.length()); - for (uint256 i = 0; i < strategies.length(); ++i) { (address strategy, uint48 enabledTime, uint48 disabledTime) = strategies.atWithTimes(i); @@ -271,14 +268,8 @@ contract BoltEigenLayerMiddlewareV1 is IBoltMiddlewareV1, OwnableUpgradeable, UU address collateral = address(strategyImpl.underlyingToken()); collateralTokens[i] = collateral; - strategyImpls[i] = strategyImpl; - } - - // NOTE: order is preserved, which is why we can use the same index for both arrays below - uint256[] memory shares = DELEGATION_MANAGER.getOperatorShares(operator, strategyImpls); - - for (uint256 i = 0; i < strategyImpls.length; ++i) { - amounts[i] = strategyImpls[i].sharesToUnderlyingView(shares[i]); + uint256 shares = DELEGATION_MANAGER.operatorShares(operator, strategyImpl); + amounts[i] = strategyImpl.sharesToUnderlyingView(shares); } return (collateralTokens, amounts); @@ -309,9 +300,6 @@ contract BoltEigenLayerMiddlewareV1 is IBoltMiddlewareV1, OwnableUpgradeable, UU uint48 epochStartTs = getEpochStartTs(getEpochAtTs(timestamp)); - // NOTE: Can this be done more gas-efficiently? - IStrategy[] memory strategyMem = new IStrategy[](1); - for (uint256 i = 0; i < strategies.length(); i++) { (address strategy, uint48 enabledTime, uint48 disabledTime) = strategies.atWithTimes(i); @@ -323,10 +311,8 @@ contract BoltEigenLayerMiddlewareV1 is IBoltMiddlewareV1, OwnableUpgradeable, UU continue; } - strategyMem[0] = IStrategy(strategy); - // NOTE: order is preserved i.e., shares[i] corresponds to strategies[i] - uint256[] memory shares = DELEGATION_MANAGER.getOperatorShares(operator, strategyMem); - amount += IStrategy(strategy).sharesToUnderlyingView(shares[0]); + uint256 shares = DELEGATION_MANAGER.operatorShares(operator, IStrategy(strategy)); + amount += IStrategy(strategy).sharesToUnderlyingView(shares); } return amount; @@ -352,4 +338,61 @@ contract BoltEigenLayerMiddlewareV1 is IBoltMiddlewareV1, OwnableUpgradeable, UU function _wasEnabledAt(uint48 enabledTime, uint48 disabledTime, uint48 timestamp) private pure returns (bool) { return enabledTime != 0 && enabledTime <= timestamp && (disabledTime == 0 || disabledTime >= timestamp); } + + // ============== EIGENLAYER SERVICE MANAGER ================= // + // Cfr. https://docs.eigenlayer.xyz/developers/avs-dashboard-onboarding + // getOperatorRestakedStrategies and getRestakeableStrategies have reference implementations + // that read from RegistryCoordinator & StakeRegistry. These are middleware contracts that + // are not used in the EigenLayer operator CLI as of today (23 Oct 2024): https://github.com/Layr-Labs/eigensdk-go/blob/0042b1a0dd502bb03c6bf1da85fc096c5c8e8f1b/chainio/clients/elcontracts/writer.go#L158 + // + // So we'll just get that information from our own system for now. + + function registerOperatorToAVS( + address operator, + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature + ) public override { + // Register the operator to the AVS directory for this AVS + AVS_DIRECTORY.registerOperatorToAVS(operator, operatorSignature); + } + + function deregisterOperatorFromAVS( + address operator + ) public override { + // NOTE: need to do this check because these functions have to be public + if (msg.sender != operator) { + revert NotAllowed(); + } + + AVS_DIRECTORY.deregisterOperatorFromAVS(operator); + } + + function getOperatorRestakedStrategies( + address operator + ) external view override returns (address[] memory) { + address[] memory restakedStrategies = new address[](strategies.length()); + + uint48 epochStartTs = getEpochStartTs(getEpochAtTs(Time.timestamp())); + + for (uint256 i = 0; i < strategies.length(); ++i) { + (address strategy, uint48 enabledTime, uint48 disabledTime) = strategies.atWithTimes(i); + + if (!_wasEnabledAt(enabledTime, disabledTime, epochStartTs)) { + continue; + } + + if (DELEGATION_MANAGER.operatorShares(operator, IStrategy(strategy)) > 0) { + restakedStrategies[restakedStrategies.length] = strategy; + } + } + + return restakedStrategies; + } + + function getRestakeableStrategies() external view override returns (address[] memory) { + return strategies.keys(); + } + + function avsDirectory() external view override returns (address) { + return address(AVS_DIRECTORY); + } } From d70980c69964273a84a6523100f8c359f46c70d9 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Wed, 23 Oct 2024 14:00:00 +0200 Subject: [PATCH 09/23] feat(contracts): tests for BoltEigenLayerMiddlewareV2 --- bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV2.sol | 2 +- bolt-contracts/test/BoltManager.EigenLayer.t.sol | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV2.sol b/bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV2.sol index 9fe54b216..3b3c435fb 100644 --- a/bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV2.sol +++ b/bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV2.sol @@ -31,7 +31,7 @@ import {StrategyManagerStorage} from "@eigenlayer/src/contracts/core/StrategyMan /// See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps /// To validate the storage layout, use the Openzeppelin Foundry Upgrades toolkit. /// You can also validate manually with forge: forge inspect storage-layout --pretty -contract BoltEigenLayerMiddlewareV1 is IBoltMiddlewareV1, IServiceManager, OwnableUpgradeable, UUPSUpgradeable { +contract BoltEigenLayerMiddlewareV2 is IBoltMiddlewareV1, IServiceManager, OwnableUpgradeable, UUPSUpgradeable { using EnumerableSet for EnumerableSet.AddressSet; using EnumerableMap for EnumerableMap.AddressToUintMap; using MapWithTimeData for EnumerableMap.AddressToUintMap; diff --git a/bolt-contracts/test/BoltManager.EigenLayer.t.sol b/bolt-contracts/test/BoltManager.EigenLayer.t.sol index 4a815704f..0edfe0186 100644 --- a/bolt-contracts/test/BoltManager.EigenLayer.t.sol +++ b/bolt-contracts/test/BoltManager.EigenLayer.t.sol @@ -6,7 +6,7 @@ import {Test, console} from "forge-std/Test.sol"; import {BoltValidatorsV1} from "../src/contracts/BoltValidatorsV1.sol"; import {BoltManagerV1} from "../src/contracts/BoltManagerV1.sol"; import {BoltParametersV1} from "../src/contracts/BoltParametersV1.sol"; -import {BoltEigenLayerMiddlewareV1} from "../src/contracts/BoltEigenLayerMiddlewareV1.sol"; +import {BoltEigenLayerMiddlewareV2} from "../src/contracts/BoltEigenLayerMiddlewareV2.sol"; import {BoltConfig} from "../src/lib/Config.sol"; import {IBoltValidatorsV1} from "../src/interfaces/IBoltValidatorsV1.sol"; import {IBoltManagerV1} from "../src/interfaces/IBoltManagerV1.sol"; @@ -30,7 +30,7 @@ contract BoltManagerEigenLayerTest is Test { BoltValidatorsV1 public validators; BoltManagerV1 public manager; - BoltEigenLayerMiddlewareV1 public middleware; + BoltEigenLayerMiddlewareV2 public middleware; EigenLayerDeployer public eigenLayerDeployer; uint128 public constant PRECONF_MAX_GAS_LIMIT = 5_000_000; @@ -74,7 +74,7 @@ contract BoltManagerEigenLayerTest is Test { validators.initialize(admin, address(parameters)); manager = new BoltManagerV1(); manager.initialize(admin, address(parameters), address(validators)); - middleware = new BoltEigenLayerMiddlewareV1(); + middleware = new BoltEigenLayerMiddlewareV2(); middleware.initialize( address(admin), From 0edb796201c7dbac0599d3c8a423b71a72426a8a Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Wed, 23 Oct 2024 14:52:53 +0200 Subject: [PATCH 10/23] feat(contracts): vault deploy script (wip) --- bolt-contracts/config/holesky/deployments.json | 1 + 1 file changed, 1 insertion(+) diff --git a/bolt-contracts/config/holesky/deployments.json b/bolt-contracts/config/holesky/deployments.json index 18e40f615..4bdd38fc4 100644 --- a/bolt-contracts/config/holesky/deployments.json +++ b/bolt-contracts/config/holesky/deployments.json @@ -7,6 +7,7 @@ "operatorRegistry": "0xAdFC41729fF447974cE27DdFa358A0f2096c3F39", "networkOptInService": "0xF5AFc9FA3Ca63a07E529DDbB6eae55C665cCa83E", "vaultFactory": "0x18C659a269a7172eF78BBC19Fe47ad2237Be0590", + "vaultConfigurator": "0xD2191FE92987171691d552C219b8caEf186eb9cA", "networkRegistry": "0xac5acD8A105C8305fb980734a5AD920b5920106A", "networkMiddlewareService": "0x683F470440964E353b389391CdDDf8df381C282f", "middleware": "0x04f40d9CaE475E5BaA462acE53E5c58A0DD8D8e8", From 4e14a161293a47c91d4ccdb3c8c8244c20f0173c Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Wed, 23 Oct 2024 14:53:04 +0200 Subject: [PATCH 11/23] feat(contracts): vault deploy script (wip) --- bolt-contracts/config/holesky/vaults.json | 10 ++ .../holesky/admin/helpers/DeployVaults.s.sol | 125 ++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 bolt-contracts/config/holesky/vaults.json create mode 100644 bolt-contracts/script/holesky/admin/helpers/DeployVaults.s.sol diff --git a/bolt-contracts/config/holesky/vaults.json b/bolt-contracts/config/holesky/vaults.json new file mode 100644 index 000000000..3e56143b1 --- /dev/null +++ b/bolt-contracts/config/holesky/vaults.json @@ -0,0 +1,10 @@ +[ + { + "collateral": "0x8d09a4502Cc8Cf1547aD300E066060D043f6982D", + "admin": "0x20A1305Ec6c13FFE8e5289f98ba1A18Aa262c15E" + }, + { + "collateral": "0x7322c24752f79c05FFD1E2a6FCB97020C1C264F1", + "admin": "0x20A1305Ec6c13FFE8e5289f98ba1A18Aa262c15E" + } +] \ No newline at end of file diff --git a/bolt-contracts/script/holesky/admin/helpers/DeployVaults.s.sol b/bolt-contracts/script/holesky/admin/helpers/DeployVaults.s.sol new file mode 100644 index 000000000..820fb3c34 --- /dev/null +++ b/bolt-contracts/script/holesky/admin/helpers/DeployVaults.s.sol @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {Script, console} from "forge-std/Script.sol"; + +import {INetworkRegistry} from "@symbiotic/interfaces/INetworkRegistry.sol"; +import {IOperatorRegistry} from "@symbiotic/interfaces/IOperatorRegistry.sol"; +import {IVaultFactory} from "@symbiotic/interfaces/IVaultFactory.sol"; +import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; +import {IOptInService} from "@symbiotic/interfaces/service/IOptInService.sol"; +import {IVaultConfigurator} from "@symbiotic/interfaces/IVaultConfigurator.sol"; +import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; +import {IBaseSlasher} from "@symbiotic/interfaces/slasher/IBaseSlasher.sol"; +import {IMetadataService} from "@symbiotic/interfaces/service/IMetadataService.sol"; +import {INetworkRestakeDelegator} from "@symbiotic/interfaces/delegator/INetworkRestakeDelegator.sol"; +import {INetworkMiddlewareService} from "@symbiotic/interfaces/service/INetworkMiddlewareService.sol"; +import {ISlasherFactory} from "@symbiotic/interfaces/ISlasherFactory.sol"; +import {IVetoSlasher} from "@symbiotic/interfaces/slasher/IVetoSlasher.sol"; +import {IDelegatorFactory} from "@symbiotic/interfaces/IDelegatorFactory.sol"; +import {IMigratablesFactory} from "@symbiotic/interfaces/common/IMigratablesFactory.sol"; +import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; + +contract DeploySymbioticVaults is Script { + struct VaultConfig { + address admin; + address collateral; + } + + function run() public { + IVaultConfigurator vaultConfigurator = _readVaultConfigurator(); + VaultConfig[] memory configs = _readVaultConfigs(); + uint48 epochDuration = _readEpochDuration(); + + // TODO: Check if vaults for specific collateral are already deployed + + vm.startBroadcast(); + + for (uint256 i; i < configs.length; ++i) { + VaultConfig memory config = configs[i]; + + console.log("Deploying vault with collateral:", config.collateral); + + address[] memory adminRoleHolders = new address[](1); + adminRoleHolders[0] = config.admin; + + IVaultConfigurator.InitParams memory vaultConfiguratorInitParams = IVaultConfigurator.InitParams({ + version: IMigratablesFactory(vaultConfigurator.VAULT_FACTORY()).lastVersion(), + owner: config.admin, + vaultParams: abi.encode( + IVault.InitParams({ + collateral: config.collateral, + burner: address(0xdead), + epochDuration: epochDuration, + depositWhitelist: false, + isDepositLimit: false, + depositLimit: 0, + defaultAdminRoleHolder: config.admin, + depositWhitelistSetRoleHolder: config.admin, + depositorWhitelistRoleHolder: config.admin, + isDepositLimitSetRoleHolder: config.admin, + depositLimitSetRoleHolder: config.admin + }) + ), + delegatorIndex: 0, // Use NetworkRestakeDelegator + delegatorParams: abi.encode( + INetworkRestakeDelegator.InitParams({ + baseParams: IBaseDelegator.BaseParams({ + defaultAdminRoleHolder: config.admin, + hook: address(0), // we don't need a hook + hookSetRoleHolder: config.admin + }), + networkLimitSetRoleHolders: adminRoleHolders, + operatorNetworkSharesSetRoleHolders: adminRoleHolders + }) + ), + withSlasher: true, + slasherIndex: 1, // Use VetoSlasher + slasherParams: abi.encode( + IVetoSlasher.InitParams({ + baseParams: IBaseSlasher.BaseParams({ + isBurnerHook: false // ? + }), + // veto duration must be smaller than epoch duration + vetoDuration: uint48(12 hours), + resolverSetEpochsDelay: 3 + }) + ) + }); + + (address vault, address networkRestakeDelegator, address vetoSlasher) = + vaultConfigurator.create(vaultConfiguratorInitParams); + + console.log("Deployed vault with collateral:", config.collateral); + console.log("Vault address:", vault); + console.log("NetworkRestakeDelegator:", networkRestakeDelegator); + console.log("VetoSlasher:", vetoSlasher); + } + + vm.stopBroadcast(); + } + + function _readVaultConfigs() internal view returns (VaultConfig[] memory configs) { + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/config/holesky/vaults.json"); + string memory json = vm.readFile(path); + + configs = abi.decode(vm.parseJson(json), (VaultConfig[])); + } + + function _readEpochDuration() internal view returns (uint48) { + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/config/holesky/parameters.json"); + string memory json = vm.readFile(path); + + return uint48(vm.parseJsonUint(json, ".epochDuration")); + } + + function _readVaultConfigurator() internal view returns (IVaultConfigurator) { + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/config/holesky/deployments.json"); + string memory json = vm.readFile(path); + + return IVaultConfigurator(vm.parseJsonAddress(json, ".symbiotic.vaultConfigurator")); + } +} From 3c1bb7e912bf858d33edfa9d18322cf1256f2421 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Wed, 23 Oct 2024 15:19:13 +0200 Subject: [PATCH 12/23] feat(contracts): update Symbiotic deployments (https://docs.symbiotic.fi/deployments\#core) --- bolt-contracts/config/holesky/deployments.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bolt-contracts/config/holesky/deployments.json b/bolt-contracts/config/holesky/deployments.json index 4bdd38fc4..19698b4a2 100644 --- a/bolt-contracts/config/holesky/deployments.json +++ b/bolt-contracts/config/holesky/deployments.json @@ -4,12 +4,12 @@ }, "symbiotic": { "network": "0xb017002D8024d8c8870A5CECeFCc63887650D2a4", - "operatorRegistry": "0xAdFC41729fF447974cE27DdFa358A0f2096c3F39", - "networkOptInService": "0xF5AFc9FA3Ca63a07E529DDbB6eae55C665cCa83E", - "vaultFactory": "0x18C659a269a7172eF78BBC19Fe47ad2237Be0590", + "operatorRegistry": "0x6F75a4ffF97326A00e52662d82EA4FdE86a2C548", + "networkOptInService": "0x58973d16FFA900D11fC22e5e2B6840d9f7e13401", + "vaultFactory": "0x407A039D94948484D356eFB765b3c74382A050B4", "vaultConfigurator": "0xD2191FE92987171691d552C219b8caEf186eb9cA", - "networkRegistry": "0xac5acD8A105C8305fb980734a5AD920b5920106A", - "networkMiddlewareService": "0x683F470440964E353b389391CdDDf8df381C282f", + "networkRegistry": "0x7d03b7343BF8d5cEC7C0C27ecE084a20113D15C9", + "networkMiddlewareService": "0x62a1ddfD86b4c1636759d9286D3A0EC722D086e3", "middleware": "0x04f40d9CaE475E5BaA462acE53E5c58A0DD8D8e8", "supportedVaults": [ "0x1df2fbfcD600ADd561013f44B2D055E2e974f605", From 1c1ec6e2a465533066f5265549808400b7e6d6fe Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Wed, 23 Oct 2024 15:25:37 +0200 Subject: [PATCH 13/23] feat(contracts/script): update script --- bolt-contracts/config/holesky/vaults.json | 8 ++++---- .../script/holesky/admin/helpers/DeployVaults.s.sol | 10 +--------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/bolt-contracts/config/holesky/vaults.json b/bolt-contracts/config/holesky/vaults.json index 3e56143b1..e26adcf7e 100644 --- a/bolt-contracts/config/holesky/vaults.json +++ b/bolt-contracts/config/holesky/vaults.json @@ -1,10 +1,10 @@ [ { - "collateral": "0x8d09a4502Cc8Cf1547aD300E066060D043f6982D", - "admin": "0x20A1305Ec6c13FFE8e5289f98ba1A18Aa262c15E" + "admin": "0x20A1305Ec6c13FFE8e5289f98ba1A18Aa262c15E", + "collateral": "0x8d09a4502Cc8Cf1547aD300E066060D043f6982D" }, { - "collateral": "0x7322c24752f79c05FFD1E2a6FCB97020C1C264F1", - "admin": "0x20A1305Ec6c13FFE8e5289f98ba1A18Aa262c15E" + "admin": "0x20A1305Ec6c13FFE8e5289f98ba1A18Aa262c15E", + "collateral": "0x7322c24752f79c05FFD1E2a6FCB97020C1C264F1" } ] \ No newline at end of file diff --git a/bolt-contracts/script/holesky/admin/helpers/DeployVaults.s.sol b/bolt-contracts/script/holesky/admin/helpers/DeployVaults.s.sol index 820fb3c34..426d40c30 100644 --- a/bolt-contracts/script/holesky/admin/helpers/DeployVaults.s.sol +++ b/bolt-contracts/script/holesky/admin/helpers/DeployVaults.s.sol @@ -3,22 +3,14 @@ pragma solidity 0.8.25; import {Script, console} from "forge-std/Script.sol"; -import {INetworkRegistry} from "@symbiotic/interfaces/INetworkRegistry.sol"; -import {IOperatorRegistry} from "@symbiotic/interfaces/IOperatorRegistry.sol"; -import {IVaultFactory} from "@symbiotic/interfaces/IVaultFactory.sol"; import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; import {IOptInService} from "@symbiotic/interfaces/service/IOptInService.sol"; import {IVaultConfigurator} from "@symbiotic/interfaces/IVaultConfigurator.sol"; import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; import {IBaseSlasher} from "@symbiotic/interfaces/slasher/IBaseSlasher.sol"; -import {IMetadataService} from "@symbiotic/interfaces/service/IMetadataService.sol"; import {INetworkRestakeDelegator} from "@symbiotic/interfaces/delegator/INetworkRestakeDelegator.sol"; -import {INetworkMiddlewareService} from "@symbiotic/interfaces/service/INetworkMiddlewareService.sol"; -import {ISlasherFactory} from "@symbiotic/interfaces/ISlasherFactory.sol"; import {IVetoSlasher} from "@symbiotic/interfaces/slasher/IVetoSlasher.sol"; -import {IDelegatorFactory} from "@symbiotic/interfaces/IDelegatorFactory.sol"; import {IMigratablesFactory} from "@symbiotic/interfaces/common/IMigratablesFactory.sol"; -import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; contract DeploySymbioticVaults is Script { struct VaultConfig { @@ -31,7 +23,7 @@ contract DeploySymbioticVaults is Script { VaultConfig[] memory configs = _readVaultConfigs(); uint48 epochDuration = _readEpochDuration(); - // TODO: Check if vaults for specific collateral are already deployed + // TODO: Check if vaults for specific collateral are already deployed! vm.startBroadcast(); From ba501fba7a2cfd9ea70c6b45f25c50a78ebdcb1f Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Thu, 24 Oct 2024 10:29:21 +0200 Subject: [PATCH 14/23] feat(contracts/script): deploy vaults script --- .../holesky/admin/helpers/DeployVaults.s.sol | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/bolt-contracts/script/holesky/admin/helpers/DeployVaults.s.sol b/bolt-contracts/script/holesky/admin/helpers/DeployVaults.s.sol index 426d40c30..c0352a3a2 100644 --- a/bolt-contracts/script/holesky/admin/helpers/DeployVaults.s.sol +++ b/bolt-contracts/script/holesky/admin/helpers/DeployVaults.s.sol @@ -3,6 +3,8 @@ pragma solidity 0.8.25; import {Script, console} from "forge-std/Script.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; import {IOptInService} from "@symbiotic/interfaces/service/IOptInService.sol"; import {IVaultConfigurator} from "@symbiotic/interfaces/IVaultConfigurator.sol"; @@ -30,13 +32,39 @@ contract DeploySymbioticVaults is Script { for (uint256 i; i < configs.length; ++i) { VaultConfig memory config = configs[i]; - console.log("Deploying vault with collateral:", config.collateral); + IMigratablesFactory vaultFactory = IMigratablesFactory(vaultConfigurator.VAULT_FACTORY()); + + bool exists; + + // First check if the vault already exists. We do this by checking for the collateral, and the admin. + // If we need to check for more properties in the future (like version), we can add them here. + for (uint256 j; j < vaultFactory.totalEntities(); ++j) { + address existingVault = vaultFactory.entity(j); + + if ( + IVault(existingVault).collateral() == config.collateral + && OwnableUpgradeable(existingVault).owner() == config.admin + ) { + console.log( + "Vault for collateral %s already deployed with admin %s", config.collateral, config.admin + ); + console.log("Address:", existingVault); + exists = true; + + break; + } + } + + if (exists) { + continue; + } address[] memory adminRoleHolders = new address[](1); adminRoleHolders[0] = config.admin; IVaultConfigurator.InitParams memory vaultConfiguratorInitParams = IVaultConfigurator.InitParams({ - version: IMigratablesFactory(vaultConfigurator.VAULT_FACTORY()).lastVersion(), + // Use Version 1 for a standard vault (non-tokenized). + version: 1, owner: config.admin, vaultParams: abi.encode( IVault.InitParams({ From 2cecc7e0a047dbb74409e0c71f19323877f93617 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Thu, 24 Oct 2024 10:57:24 +0200 Subject: [PATCH 15/23] feat(contracts/script): update supported vaults script --- .../config/holesky/deployments.json | 8 +- bolt-contracts/config/holesky/vaults.json | 16 ++++ bolt-contracts/docs/admin/deploying.md | 23 +++++- .../admin/helpers/UpdateSupportedVaults.s.sol | 74 +++++++++++++++++++ 4 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 bolt-contracts/script/holesky/admin/helpers/UpdateSupportedVaults.s.sol diff --git a/bolt-contracts/config/holesky/deployments.json b/bolt-contracts/config/holesky/deployments.json index 19698b4a2..a5af7aec0 100644 --- a/bolt-contracts/config/holesky/deployments.json +++ b/bolt-contracts/config/holesky/deployments.json @@ -12,8 +12,12 @@ "networkMiddlewareService": "0x62a1ddfD86b4c1636759d9286D3A0EC722D086e3", "middleware": "0x04f40d9CaE475E5BaA462acE53E5c58A0DD8D8e8", "supportedVaults": [ - "0x1df2fbfcD600ADd561013f44B2D055E2e974f605", - "0xf427d00c34609053d97167352061DD2F0F27F853" + "0xc79c533a77691641d52ebD5e87E51dCbCaeb0D78", + "0xe5708788c90e971f73D928b7c5A8FD09137010e0", + "0x11c5b9A9cd8269580aDDbeE38857eE451c1CFacd", + "0xC56Ba584929c6f381744fA2d7a028fA927817f2b", + "0xcDdeFfcD2bA579B8801af1d603812fF64c301462", + "0x91e84e12Bb65576C0a6614c5E6EbbB2eA595E10f" ] }, "eigenLayer": { diff --git a/bolt-contracts/config/holesky/vaults.json b/bolt-contracts/config/holesky/vaults.json index e26adcf7e..9df2dc1f0 100644 --- a/bolt-contracts/config/holesky/vaults.json +++ b/bolt-contracts/config/holesky/vaults.json @@ -6,5 +6,21 @@ { "admin": "0x20A1305Ec6c13FFE8e5289f98ba1A18Aa262c15E", "collateral": "0x7322c24752f79c05FFD1E2a6FCB97020C1C264F1" + }, + { + "admin": "0x20A1305Ec6c13FFE8e5289f98ba1A18Aa262c15E", + "collateral": "0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034" + }, + { + "admin": "0x20A1305Ec6c13FFE8e5289f98ba1A18Aa262c15E", + "collateral": "0x94373a4919B3240D86eA41593D5eBa789FEF3848" + }, + { + "admin": "0x20A1305Ec6c13FFE8e5289f98ba1A18Aa262c15E", + "collateral": "0x8720095Fa5739Ab051799211B146a2EEE4Dd8B37" + }, + { + "admin": "0x20A1305Ec6c13FFE8e5289f98ba1A18Aa262c15E", + "collateral": "0xe3C063B1BEe9de02eb28352b55D49D85514C67FF" } ] \ No newline at end of file diff --git a/bolt-contracts/docs/admin/deploying.md b/bolt-contracts/docs/admin/deploying.md index 56de63f22..dd958666a 100644 --- a/bolt-contracts/docs/admin/deploying.md +++ b/bolt-contracts/docs/admin/deploying.md @@ -34,19 +34,28 @@ export ADMIN_PRIVATE_KEY=0x... ### Pre-deployment -Register a Symbiotic network for Bolt with the Symbiotic `NetworkRegistry`. The private key with which the script is run will determine the network address. This private key will also need to be used later. +- Register a Symbiotic network for Bolt with the Symbiotic `NetworkRegistry`. The private key with which the script is run will determine the network address. This private key will also need to be used later. ```bash forge script script/holesky/admin/helpers/Symbiotic.s.sol --rpc-url $HOLESKY_RPC --private-key $NETWORK_PRIVATE_KEY --broadcast -vvvv --sig "run(string memory arg)" registerNetwork ``` -Make sure `deployments.json` contains the correct address for the Symbiotic network. +Make sure [`deployments.json`](../../config/holesky/deployments.json) contains the correct address for the Symbiotic network. + +- Deploy Bolt-specific Symbiotic Vaults. Vaults will be deployed from the [`vaults.json`](../../config/holesky/vaults.json) configuration file. + +```bash +forge script script/holesky/admin/helpers/DeployVaults.s.sol --rpc-url $HOLESKY_RPC --private-key $ADMIN_PRIVATE_KEY --verify --broadcast -vvvv +``` + +If vaults with the `(collateral, admin)` combination already exist, they won't be recreated. After these vaults have been created, copy their +addresses into the [`deployments.json`](../../config/holesky/deployments.json) file under `symbiotic.supportedVaults`. ### Deployment Run the following script to deploy Bolt V1: ```bash -forge script script/holesky/admin/Deploy.s.sol --rpc-url $HOLESKY_RPC --private-key $ADMIN_PRIVATE_KEY --broadcast -vvvv +forge script script/holesky/admin/Deploy.s.sol --rpc-url $HOLESKY_RPC --private-key $ADMIN_PRIVATE_KEY --verify --broadcast -vvvv ``` This will deploy all the contracts. The address corresponding to the private key will be the system admin. @@ -71,3 +80,11 @@ forge script script/holesky/admin/helpers/RegisterAVS.s.sol --rpc-url $HOLESKY_R > [!IMPORTANT] > After the `deployments.json` file has been fully updated with the correct contract addresses, push it to Github. + +### Other Scripts + +#### Modifying supported Vaults +This script will update supported vaults according to `deployments.json`, and remove any vaults that have been whitelisted but are no longer in the `supportedVaults` list. +```bash +forge script script/holesky/admin/helpers/UpdateSupportedVaults.s.sol --rpc-url $HOLESKY_RPC --private-key $ADMIN_PRIVATE_KEY --broadcast -vvv +``` \ No newline at end of file diff --git a/bolt-contracts/script/holesky/admin/helpers/UpdateSupportedVaults.s.sol b/bolt-contracts/script/holesky/admin/helpers/UpdateSupportedVaults.s.sol new file mode 100644 index 000000000..14fdb19be --- /dev/null +++ b/bolt-contracts/script/holesky/admin/helpers/UpdateSupportedVaults.s.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {Script, console} from "forge-std/Script.sol"; + +import {INetworkRegistry} from "@symbiotic/interfaces/INetworkRegistry.sol"; +import {INetworkMiddlewareService} from "@symbiotic/interfaces/service/INetworkMiddlewareService.sol"; + +import {BoltSymbioticMiddlewareV1} from "../../../../src/contracts/BoltSymbioticMiddlewareV1.sol"; + +contract UpdateSupportedVaults is Script { + function run() public { + BoltSymbioticMiddlewareV1 middleware = _readSymbioticMiddleware(); + + address[] memory whitelisted = middleware.getWhitelistedVaults(); + address[] memory toWhitelist = _readVaultsToWhitelist(); + + vm.startBroadcast(); + + // Step 1: Whitelist new vaults + for (uint256 i = 0; i < toWhitelist.length; i++) { + address vault = toWhitelist[i]; + + bool isWhitelisted = false; + for (uint256 j = 0; j < whitelisted.length; j++) { + if (whitelisted[j] == vault) { + isWhitelisted = true; + break; + } + } + + if (!isWhitelisted) { + console.log("Whitelisting vault", vault); + middleware.registerVault(vault); + } + } + + // Step 2: Remove vaults from contract that are not in the supported vaults list + for (uint256 i = 0; i < whitelisted.length; i++) { + address vault = whitelisted[i]; + + bool isSupported = false; + for (uint256 j = 0; j < toWhitelist.length; j++) { + if (toWhitelist[j] == vault) { + isSupported = true; + break; + } + } + + if (!isSupported) { + console.log("Removing vault", vault); + middleware.deregisterVault(vault); + } + } + + vm.stopBroadcast(); + } + + function _readVaultsToWhitelist() public view returns (address[] memory) { + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/config/holesky/deployments.json"); + string memory json = vm.readFile(path); + + return vm.parseJsonAddressArray(json, ".symbiotic.supportedVaults"); + } + + function _readSymbioticMiddleware() public view returns (BoltSymbioticMiddlewareV1) { + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/config/holesky/deployments.json"); + string memory json = vm.readFile(path); + + return BoltSymbioticMiddlewareV1(vm.parseJsonAddress(json, ".symbiotic.middleware")); + } +} From 33131266327971b3b82c553653b2ac40cc72dcbf Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Thu, 24 Oct 2024 10:59:04 +0200 Subject: [PATCH 16/23] docs(contracts): nit --- bolt-contracts/docs/admin/deploying.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bolt-contracts/docs/admin/deploying.md b/bolt-contracts/docs/admin/deploying.md index dd958666a..215827083 100644 --- a/bolt-contracts/docs/admin/deploying.md +++ b/bolt-contracts/docs/admin/deploying.md @@ -83,8 +83,8 @@ forge script script/holesky/admin/helpers/RegisterAVS.s.sol --rpc-url $HOLESKY_R ### Other Scripts -#### Modifying supported Vaults -This script will update supported vaults according to `deployments.json`, and remove any vaults that have been whitelisted but are no longer in the `supportedVaults` list. +#### Modifying supported Symbiotic Vaults +This script will update supported vaults according to `deployments.json`, and remove any vaults that have been whitelisted but are no longer in the `symbiotic.supportedVaults` list. ```bash forge script script/holesky/admin/helpers/UpdateSupportedVaults.s.sol --rpc-url $HOLESKY_RPC --private-key $ADMIN_PRIVATE_KEY --broadcast -vvv ``` \ No newline at end of file From 35c53b09cd77d8d12b24d5809cfb94b77473f804 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Thu, 24 Oct 2024 12:01:04 +0200 Subject: [PATCH 17/23] feat(contracts): upgrade script --- .../config/holesky/deployments.json | 4 +- bolt-contracts/docs/admin/upgrading.md | 20 +- .../script/holesky/admin/Upgrade.s.sol | 70 ++- .../contracts/BoltSymbioticMiddlewareV2.sol | 436 ++++++++++++++++++ 4 files changed, 527 insertions(+), 3 deletions(-) create mode 100644 bolt-contracts/src/contracts/BoltSymbioticMiddlewareV2.sol diff --git a/bolt-contracts/config/holesky/deployments.json b/bolt-contracts/config/holesky/deployments.json index a5af7aec0..a5a41415d 100644 --- a/bolt-contracts/config/holesky/deployments.json +++ b/bolt-contracts/config/holesky/deployments.json @@ -1,6 +1,8 @@ { "bolt": { - "validators": "0x47D2DC1DE1eFEFA5e6944402f2eda3981D36a9c8" + "validators": "0x47D2DC1DE1eFEFA5e6944402f2eda3981D36a9c8", + "parameters": "0x20d1cf3A5BD5928dB3118b2CfEF54FDF9fda5c12", + "manager": "0x440202829b493F9FF43E730EB5e8379EEa3678CF" }, "symbiotic": { "network": "0xb017002D8024d8c8870A5CECeFCc63887650D2a4", diff --git a/bolt-contracts/docs/admin/upgrading.md b/bolt-contracts/docs/admin/upgrading.md index 65466d91b..74d5fd4a9 100644 --- a/bolt-contracts/docs/admin/upgrading.md +++ b/bolt-contracts/docs/admin/upgrading.md @@ -13,4 +13,22 @@ bytes memory initManager = abi.encodeCall(BoltManagerV2.initialize, (params)); Upgrades.upgradeProxy(proxy, "BoltManagerV2.sol", initManager, opts); ``` -Before an upgrade, update the [`Upgrade.s.sol`](../script/holesky/Upgrade.s.sol) script to include the correct contracts, references and configurations. \ No newline at end of file +Before an upgrade, update the [`Upgrade.s.sol`](../script/holesky/Upgrade.s.sol) script to include the correct contracts, references and configurations. + +## Unsafe +In order to run an unsafe upgrade, set `Options.unsafeSkipAllChecks` to `true`: +```solidity +Options memory opts; +opts.unsafeSkipAllChecks = true; +``` + +## Reinitializers +In case you need to reinitialize your contract, you'll need to create a reinitializer. + +Let `x` = your version number. Add the following new initializer to the contract to be upgraded: + +```solidity +function initializeVx() public reinitializer(x) { ... } +``` + +For more info, check out https://docs.openzeppelin.com/contracts/5.x/api/proxy#Initializable. \ No newline at end of file diff --git a/bolt-contracts/script/holesky/admin/Upgrade.s.sol b/bolt-contracts/script/holesky/admin/Upgrade.s.sol index 6ca5b0518..e549a53d4 100644 --- a/bolt-contracts/script/holesky/admin/Upgrade.s.sol +++ b/bolt-contracts/script/holesky/admin/Upgrade.s.sol @@ -4,19 +4,87 @@ pragma solidity 0.8.25; import {Script, console} from "forge-std/Script.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {Upgrades} from "@openzeppelin-foundry-upgrades/src/Upgrades.sol"; +import {Upgrades, Options} from "@openzeppelin-foundry-upgrades/src/Upgrades.sol"; import {BoltParametersV1} from "../../../src/contracts/BoltParametersV1.sol"; import {BoltValidatorsV1} from "../../../src/contracts/BoltValidatorsV1.sol"; import {BoltManagerV1} from "../../../src/contracts/BoltManagerV1.sol"; import {BoltEigenLayerMiddlewareV1} from "../../../src/contracts/BoltEigenLayerMiddlewareV1.sol"; import {BoltSymbioticMiddlewareV1} from "../../../src/contracts/BoltSymbioticMiddlewareV1.sol"; +import {BoltSymbioticMiddlewareV2} from "../../../src/contracts/BoltSymbioticMiddlewareV2.sol"; import {BoltConfig} from "../../../src/lib/Config.sol"; contract UpgradeBolt is Script { + struct Deployments { + address boltManager; + address boltParameters; + address symbioticNetwork; + address symbioticOperatorRegistry; + address symbioticOperatorNetOptIn; + address symbioticVaultFactory; + address symbioticMiddleware; + address[] supportedVaults; + address eigenLayerAVSDirectory; + address eigenLayerDelegationManager; + address eigenLayerStrategyManager; + address[] supportedStrategies; + } + function run() public { + address admin = msg.sender; + console.log("Upgrading with admin", admin); // TODO: Validate upgrades with Upgrades.validateUpgrade + Options memory opts; + opts.unsafeSkipAllChecks = true; + opts.referenceContract = "BoltSymbioticMiddlewareV1.sol"; + + string memory upgradeTo = "BoltSymbioticMiddlewareV2.sol"; + + Deployments memory deployments = _readDeployments(); + + bytes memory initSymbioticMiddleware = abi.encodeCall( + BoltSymbioticMiddlewareV2.initializeV2, + ( + admin, + deployments.boltParameters, + deployments.boltManager, + deployments.symbioticNetwork, + deployments.symbioticOperatorRegistry, + deployments.symbioticOperatorNetOptIn, + deployments.symbioticVaultFactory + ) + ); + + vm.startBroadcast(admin); + + Upgrades.upgradeProxy(deployments.symbioticMiddleware, upgradeTo, initSymbioticMiddleware, opts); + + vm.stopBroadcast(); + + console.log("BoltSymbioticMiddleware proxy upgraded from %s to %s", opts.referenceContract, upgradeTo); + // TODO: Upgrade contracts with Upgrades.upgradeProxy } + + function _readDeployments() public view returns (Deployments memory) { + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/config/holesky/deployments.json"); + string memory json = vm.readFile(path); + + return Deployments({ + boltParameters: vm.parseJsonAddress(json, ".bolt.parameters"), + boltManager: vm.parseJsonAddress(json, ".bolt.manager"), + symbioticNetwork: vm.parseJsonAddress(json, ".symbiotic.network"), + symbioticOperatorRegistry: vm.parseJsonAddress(json, ".symbiotic.operatorRegistry"), + symbioticOperatorNetOptIn: vm.parseJsonAddress(json, ".symbiotic.networkOptInService"), + symbioticVaultFactory: vm.parseJsonAddress(json, ".symbiotic.vaultFactory"), + supportedVaults: vm.parseJsonAddressArray(json, ".symbiotic.supportedVaults"), + symbioticMiddleware: vm.parseJsonAddress(json, ".symbiotic.middleware"), + eigenLayerAVSDirectory: vm.parseJsonAddress(json, ".eigenLayer.avsDirectory"), + eigenLayerDelegationManager: vm.parseJsonAddress(json, ".eigenLayer.delegationManager"), + eigenLayerStrategyManager: vm.parseJsonAddress(json, ".eigenLayer.strategyManager"), + supportedStrategies: vm.parseJsonAddressArray(json, ".eigenLayer.supportedStrategies") + }); + } } diff --git a/bolt-contracts/src/contracts/BoltSymbioticMiddlewareV2.sol b/bolt-contracts/src/contracts/BoltSymbioticMiddlewareV2.sol new file mode 100644 index 000000000..ae0530fa6 --- /dev/null +++ b/bolt-contracts/src/contracts/BoltSymbioticMiddlewareV2.sol @@ -0,0 +1,436 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; +import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; + +import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; +import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; +import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; +import {IRegistry} from "@symbiotic/interfaces/common/IRegistry.sol"; +import {IOptInService} from "@symbiotic/interfaces/service/IOptInService.sol"; +import {ISlasher} from "@symbiotic/interfaces/slasher/ISlasher.sol"; +import {IVetoSlasher} from "@symbiotic/interfaces/slasher/IVetoSlasher.sol"; +import {IEntity} from "@symbiotic/interfaces/common/IEntity.sol"; + +import {MapWithTimeData} from "../lib/MapWithTimeData.sol"; +import {IBoltValidatorsV1} from "../interfaces/IBoltValidatorsV1.sol"; +import {IBoltParametersV1} from "../interfaces/IBoltParametersV1.sol"; +import {IBoltMiddlewareV1} from "../interfaces/IBoltMiddlewareV1.sol"; +import {IBoltManagerV1} from "../interfaces/IBoltManagerV1.sol"; + +/// @title Bolt Symbiotic Middleware +/// @notice This contract is responsible for interfacing with the Symbiotic restaking protocol. +/// @dev This contract is upgradeable using the UUPSProxy pattern. Storage layout remains fixed across upgrades +/// with the use of storage gaps. +/// See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps +/// To validate the storage layout, use the Openzeppelin Foundry Upgrades toolkit. +/// You can also validate manually with forge: forge inspect storage-layout --pretty +contract BoltSymbioticMiddlewareV2 is IBoltMiddlewareV1, OwnableUpgradeable, UUPSUpgradeable { + using EnumerableSet for EnumerableSet.AddressSet; + using EnumerableMap for EnumerableMap.AddressToUintMap; + using MapWithTimeData for EnumerableMap.AddressToUintMap; + using Subnetwork for address; + + // ========== CONSTANTS ============ // + /// @notice Slasher that can instantly slash operators without veto. + uint256 public INSTANT_SLASHER_TYPE = 0; + + /// @notice Slasher that can request a veto before actually slashing operators. + uint256 public VETO_SLASHER_TYPE = 1; + + // ========= STORAGE ========= // + + /// @notice Start timestamp of the first epoch. + uint48 public START_TIMESTAMP; + + /// @notice Bolt Parameters contract. + IBoltParametersV1 public parameters; + + /// @notice Validators registry, where validators are registered via their + /// BLS pubkey and are assigned a sequence number. + IBoltManagerV1 public manager; + + /// @notice Set of Symbiotic protocol vaults that are used in Bolt Protocol. + EnumerableMap.AddressToUintMap private vaults; + + /// @notice Address of the Bolt network in Symbiotic Protocol. + address public BOLT_SYMBIOTIC_NETWORK; + + /// @notice Address of the Symbiotic Operator Registry contract. + address public OPERATOR_REGISTRY; + + /// @notice Address of the Symbiotic Vault Factory contract. + address public VAULT_FACTORY; + + /// @notice Address of the Symbiotic Operator Network Opt-In contract. + address public OPERATOR_NET_OPTIN; + + bytes32 public NAME_HASH; + + // --> Storage layout marker: 12 slots + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + * This can be validated with the Openzeppelin Foundry Upgrades toolkit. + * + * Total storage slots: 50 + */ + uint256[38] private __gap; + + // ========= ERRORS ========= + + error NotVault(); + error SlashAmountTooHigh(); + error UnknownSlasherType(); + + // ========= CONSTRUCTOR ========= + + /// @notice Constructor for the BoltSymbioticMiddleware contract. + /// @param _parameters The address of the Bolt Parameters contract. + /// @param _manager The address of the Bolt Manager contract. + /// @param _symbioticNetwork The address of the Symbiotic network. + /// @param _symbioticOperatorRegistry The address of the Symbiotic operator registry. + /// @param _symbioticOperatorNetOptIn The address of the Symbiotic operator network opt-in contract. + /// @param _symbioticVaultFactory The address of the Symbiotic vault registry. + function initialize( + address _owner, + address _parameters, + address _manager, + address _symbioticNetwork, + address _symbioticOperatorRegistry, + address _symbioticOperatorNetOptIn, + address _symbioticVaultFactory + ) public initializer { + __Ownable_init(_owner); + parameters = IBoltParametersV1(_parameters); + manager = IBoltManagerV1(_manager); + START_TIMESTAMP = Time.timestamp(); + + BOLT_SYMBIOTIC_NETWORK = _symbioticNetwork; + OPERATOR_REGISTRY = _symbioticOperatorRegistry; + OPERATOR_NET_OPTIN = _symbioticOperatorNetOptIn; + VAULT_FACTORY = _symbioticVaultFactory; + NAME_HASH = keccak256("SYMBIOTIC"); + } + + function initializeV2( + address _owner, + address _parameters, + address _manager, + address _symbioticNetwork, + address _symbioticOperatorRegistry, + address _symbioticOperatorNetOptIn, + address _symbioticVaultFactory + ) public reinitializer(2) { + __Ownable_init(_owner); + parameters = IBoltParametersV1(_parameters); + manager = IBoltManagerV1(_manager); + START_TIMESTAMP = Time.timestamp(); + + BOLT_SYMBIOTIC_NETWORK = _symbioticNetwork; + OPERATOR_REGISTRY = _symbioticOperatorRegistry; + OPERATOR_NET_OPTIN = _symbioticOperatorNetOptIn; + VAULT_FACTORY = _symbioticVaultFactory; + NAME_HASH = keccak256("SYMBIOTIC"); + } + + function _authorizeUpgrade( + address newImplementation + ) internal override onlyOwner {} + + // ========= VIEW FUNCTIONS ========= + + /// @notice Get the start timestamp of an epoch. + function getEpochStartTs( + uint48 epoch + ) public view returns (uint48 timestamp) { + return START_TIMESTAMP + epoch * parameters.EPOCH_DURATION(); + } + + /// @notice Get the epoch at a given timestamp. + function getEpochAtTs( + uint48 timestamp + ) public view returns (uint48 epoch) { + return (timestamp - START_TIMESTAMP) / parameters.EPOCH_DURATION(); + } + + /// @notice Get the current epoch. + function getCurrentEpoch() public view returns (uint48 epoch) { + return getEpochAtTs(Time.timestamp()); + } + + /// @notice Get the whitelisted vaults. + function getWhitelistedVaults() public view returns (address[] memory) { + return vaults.keys(); + } + + // =========== ADMIN FUNCTIONS ============ // + + /// @notice Allow a vault to signal opt-in to Bolt Protocol. + /// @param vault The vault address to signal opt-in for. + function registerVault( + address vault + ) public onlyOwner { + if (vaults.contains(vault)) { + revert AlreadyRegistered(); + } + + if (!IRegistry(VAULT_FACTORY).isEntity(vault)) { + revert NotVault(); + } + + // TODO: check slashing conditions and veto duration + + vaults.add(vault); + vaults.enable(vault); + } + + /// @notice Deregister a vault from working in Bolt Protocol. + /// @param vault The vault address to deregister. + function deregisterVault( + address vault + ) public onlyOwner { + if (!vaults.contains(vault)) { + revert NotRegistered(); + } + + vaults.remove(vault); + } + + // ========= SYMBIOTIC MIDDLEWARE LOGIC ========= + + /// @notice Allow an operator to signal opt-in to Bolt Protocol. + /// msg.sender must be an operator in the Symbiotic network. + function registerOperator( + string calldata rpc + ) public { + if (manager.isOperator(msg.sender)) { + revert AlreadyRegistered(); + } + + if (!IRegistry(OPERATOR_REGISTRY).isEntity(msg.sender)) { + revert NotOperator(); + } + + if (!IOptInService(OPERATOR_NET_OPTIN).isOptedIn(msg.sender, BOLT_SYMBIOTIC_NETWORK)) { + revert OperatorNotOptedIn(); + } + + manager.registerOperator(msg.sender, rpc); + } + + /// @notice Deregister a Symbiotic operator from working in Bolt Protocol. + /// @dev This does NOT deregister the operator from the Symbiotic network. + function deregisterOperator() public { + if (!manager.isOperator(msg.sender)) { + revert NotRegistered(); + } + + manager.deregisterOperator(msg.sender); + } + + /// @notice Allow an operator to signal indefinite opt-out from Bolt Protocol. + /// @dev Pausing activity does not prevent the operator from being slashable for + /// the current network epoch until the end of the slashing window. + function pauseOperator() public { + manager.pauseOperator(msg.sender); + } + + /// @notice Allow a disabled operator to signal opt-in to Bolt Protocol. + function unpauseOperator() public { + manager.unpauseOperator(msg.sender); + } + + /// @notice Allow a vault to signal indefinite opt-out from Bolt Protocol. + function pauseVault() public { + if (!vaults.contains(msg.sender)) { + revert NotRegistered(); + } + + vaults.disable(msg.sender); + } + + /// @notice Allow a disabled vault to signal opt-in to Bolt Protocol. + function unpauseVault() public { + if (!vaults.contains(msg.sender)) { + revert NotRegistered(); + } + + vaults.enable(msg.sender); + } + + /// @notice Check if a vault is currently enabled to work in Bolt Protocol. + /// @param vault The vault address to check the enabled status for. + /// @return True if the vault is enabled, false otherwise. + function isVaultEnabled( + address vault + ) public view returns (bool) { + (uint48 enabledTime, uint48 disabledTime) = vaults.getTimes(vault); + return enabledTime != 0 && disabledTime == 0; + } + + /// @notice Get the collaterals and amounts staked by an operator across the supported strategies. + /// + /// @param operator The operator address to get the collaterals and amounts staked for. + /// @return collaterals The collaterals staked by the operator. + /// @dev Assumes that the operator is registered and enabled. + function getOperatorCollaterals( + address operator + ) public view returns (address[] memory, uint256[] memory) { + address[] memory collateralTokens = new address[](vaults.length()); + uint256[] memory amounts = new uint256[](vaults.length()); + + uint48 epochStartTs = getEpochStartTs(getEpochAtTs(Time.timestamp())); + + for (uint256 i = 0; i < vaults.length(); ++i) { + (address vault, uint48 enabledTime, uint48 disabledTime) = vaults.atWithTimes(i); + + if (!_wasEnabledAt(enabledTime, disabledTime, epochStartTs)) { + continue; + } + + address collateral = IVault(vault).collateral(); + collateralTokens[i] = collateral; + + // in order to have stake in a network, the operator needs to be opted in to that vault. + // this authorization is fully handled in the Vault, we just need to read the stake. + amounts[i] = IBaseDelegator(IVault(vault).delegator()).stakeAt( + // The stake for each subnetwork is stored in the vault's delegator contract. + // stakeAt returns the stake of "operator" at "timestamp" for "network" (or subnetwork) + // bytes(0) is for hints, which we don't currently use. + BOLT_SYMBIOTIC_NETWORK.subnetwork(0), + operator, + epochStartTs, + new bytes(0) + ); + } + + return (collateralTokens, amounts); + } + + /// @notice Get the stake of an operator in Symbiotic protocol at the current timestamp. + /// @param operator The operator address to check the stake for. + /// @param collateral The collateral address to check the stake for. + /// @return amount The stake of the operator at the current timestamp, in collateral token. + function getOperatorStake(address operator, address collateral) public view returns (uint256 amount) { + uint48 timestamp = Time.timestamp(); + return getOperatorStakeAt(operator, collateral, timestamp); + } + + /// @notice Get the stake of an operator in Symbiotic protocol at a given timestamp. + /// @param operator The operator address to check the stake for. + /// @param collateral The collateral address to check the stake for. + /// @param timestamp The timestamp to check the stake at. + /// @return amount The stake of the operator at the given timestamp, in collateral token. + function getOperatorStakeAt( + address operator, + address collateral, + uint48 timestamp + ) public view returns (uint256 amount) { + if (timestamp > Time.timestamp() || timestamp < START_TIMESTAMP) { + revert InvalidQuery(); + } + + uint48 epochStartTs = getEpochStartTs(getEpochAtTs(timestamp)); + + for (uint256 i = 0; i < vaults.length(); ++i) { + (address vault, uint48 enabledTime, uint48 disabledTime) = vaults.atWithTimes(i); + + if (collateral != IVault(vault).collateral()) { + continue; + } + + if (!_wasEnabledAt(enabledTime, disabledTime, epochStartTs)) { + continue; + } + + // in order to have stake in a network, the operator needs to be opted in to that vault. + // this authorization is fully handled in the Vault, we just need to read the stake. + amount += IBaseDelegator(IVault(vault).delegator()).stakeAt( + // The stake for each subnetwork is stored in the vault's delegator contract. + // stakeAt returns the stake of "operator" at "timestamp" for "network" (or subnetwork) + // bytes(0) is for hints, which we don't currently use. + BOLT_SYMBIOTIC_NETWORK.subnetwork(0), + operator, + epochStartTs, + new bytes(0) + ); + } + + return amount; + } + + /// @notice Slash a given operator for a given amount of collateral. + /// @param timestamp The timestamp of the slash event. + /// @param operator The operator address to slash. + /// @param collateral The collateral address to slash. + /// @param amount The amount of collateral to slash. + function slash(uint48 timestamp, address operator, address collateral, uint256 amount) public onlyOwner { + // TODO: remove onlyOwner modifier and gate the slashing logic behind the BoltChallenger + // fault proof mechanism to allow for permissionless slashing. + + uint48 epochStartTs = getEpochStartTs(getEpochAtTs(timestamp)); + + for (uint256 i = 0; i < vaults.length(); ++i) { + (address vault, uint48 enabledTime, uint48 disabledTime) = vaults.atWithTimes(i); + + if (!_wasEnabledAt(enabledTime, disabledTime, epochStartTs)) { + continue; + } + + if (collateral != IVault(vault).collateral()) { + continue; + } + + uint256 operatorStake = getOperatorStakeAt(operator, collateral, epochStartTs); + + if (amount > operatorStake) { + revert SlashAmountTooHigh(); + } + + uint256 vaultStake = IBaseDelegator(IVault(vault).delegator()).stakeAt( + BOLT_SYMBIOTIC_NETWORK.subnetwork(0), operator, epochStartTs, new bytes(0) + ); + + // Slash the vault pro-rata. + _slashVault(epochStartTs, vault, operator, (amount * vaultStake) / operatorStake); + } + } + + // ========= HELPER FUNCTIONS ========= + + /// @notice Check if a map entry was active at a given timestamp. + /// @param enabledTime The enabled time of the map entry. + /// @param disabledTime The disabled time of the map entry. + /// @param timestamp The timestamp to check the map entry status at. + /// @return True if the map entry was active at the given timestamp, false otherwise. + function _wasEnabledAt(uint48 enabledTime, uint48 disabledTime, uint48 timestamp) private pure returns (bool) { + return enabledTime != 0 && enabledTime <= timestamp && (disabledTime == 0 || disabledTime >= timestamp); + } + + /// @notice Slash an operator for a given amount of collateral. + /// @param timestamp The timestamp of the slash event. + /// @param operator The operator address to slash. + /// @param amount The amount of collateral to slash. + function _slashVault(uint48 timestamp, address vault, address operator, uint256 amount) private { + address slasher = IVault(vault).slasher(); + uint256 slasherType = IEntity(slasher).TYPE(); + + if (slasherType == INSTANT_SLASHER_TYPE) { + ISlasher(slasher).slash(BOLT_SYMBIOTIC_NETWORK.subnetwork(0), operator, amount, timestamp, new bytes(0)); + } else if (slasherType == VETO_SLASHER_TYPE) { + IVetoSlasher(slasher).requestSlash( + BOLT_SYMBIOTIC_NETWORK.subnetwork(0), operator, amount, timestamp, new bytes(0) + ); + } else { + revert UnknownSlasherType(); + } + } +} From 0eb29b8f608f526262454f57680beb6b60de8374 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Thu, 24 Oct 2024 12:20:11 +0200 Subject: [PATCH 18/23] docs(contracts): verifying storage layout on upgrades --- bolt-contracts/docs/admin/upgrading.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/bolt-contracts/docs/admin/upgrading.md b/bolt-contracts/docs/admin/upgrading.md index 74d5fd4a9..08074fa39 100644 --- a/bolt-contracts/docs/admin/upgrading.md +++ b/bolt-contracts/docs/admin/upgrading.md @@ -22,6 +22,31 @@ Options memory opts; opts.unsafeSkipAllChecks = true; ``` +## Verifying Storage Layout +You can verify storage layouts using `forge inspect`. Example: + +```bash +forge inspect BoltSymbioticMiddlewareV2 storage-layout --pretty +``` + +This will output the following table: +| Name | Type | Slot | Offset | Bytes | Contract | +|------------------------|---------------------------------------|------|--------|-------|-----------------------------------------------------------------------| +| INSTANT_SLASHER_TYPE | uint256 | 0 | 0 | 32 | src/contracts/BoltSymbioticMiddlewareV2.sol:BoltSymbioticMiddlewareV2 | +| VETO_SLASHER_TYPE | uint256 | 1 | 0 | 32 | src/contracts/BoltSymbioticMiddlewareV2.sol:BoltSymbioticMiddlewareV2 | +| START_TIMESTAMP | uint48 | 2 | 0 | 6 | src/contracts/BoltSymbioticMiddlewareV2.sol:BoltSymbioticMiddlewareV2 | +| parameters | contract IBoltParametersV1 | 2 | 6 | 20 | src/contracts/BoltSymbioticMiddlewareV2.sol:BoltSymbioticMiddlewareV2 | +| manager | contract IBoltManagerV1 | 3 | 0 | 20 | src/contracts/BoltSymbioticMiddlewareV2.sol:BoltSymbioticMiddlewareV2 | +| vaults | struct EnumerableMap.AddressToUintMap | 4 | 0 | 96 | src/contracts/BoltSymbioticMiddlewareV2.sol:BoltSymbioticMiddlewareV2 | +| BOLT_SYMBIOTIC_NETWORK | address | 7 | 0 | 20 | src/contracts/BoltSymbioticMiddlewareV2.sol:BoltSymbioticMiddlewareV2 | +| OPERATOR_REGISTRY | address | 8 | 0 | 20 | src/contracts/BoltSymbioticMiddlewareV2.sol:BoltSymbioticMiddlewareV2 | +| VAULT_FACTORY | address | 9 | 0 | 20 | src/contracts/BoltSymbioticMiddlewareV2.sol:BoltSymbioticMiddlewareV2 | +| OPERATOR_NET_OPTIN | address | 10 | 0 | 20 | src/contracts/BoltSymbioticMiddlewareV2.sol:BoltSymbioticMiddlewareV2 | +| NAME_HASH | bytes32 | 11 | 0 | 32 | src/contracts/BoltSymbioticMiddlewareV2.sol:BoltSymbioticMiddlewareV2 | +| __gap | uint256[38] | 12 | 0 | 1216 | src/contracts/BoltSymbioticMiddlewareV2.sol:BoltSymbioticMiddlewareV2 | + +The last line indicating the `__gap` storage slot is what's most important. `__gap` has a total of 50 storage slots reserved. You **MUST** verify that the array length of __gap`, in this case `38`, is equal to `50 - __gap.Slot`. In this case, the `Slot` column for `__gap` shows 12, so the layout is correct. + ## Reinitializers In case you need to reinitialize your contract, you'll need to create a reinitializer. From 8a5831db9edee03cade7c252b1641a9966161249 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Thu, 24 Oct 2024 12:20:46 +0200 Subject: [PATCH 19/23] docs(contracts): verifying storage layout on upgrades --- bolt-contracts/docs/admin/upgrading.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bolt-contracts/docs/admin/upgrading.md b/bolt-contracts/docs/admin/upgrading.md index 08074fa39..28de9192f 100644 --- a/bolt-contracts/docs/admin/upgrading.md +++ b/bolt-contracts/docs/admin/upgrading.md @@ -45,7 +45,7 @@ This will output the following table: | NAME_HASH | bytes32 | 11 | 0 | 32 | src/contracts/BoltSymbioticMiddlewareV2.sol:BoltSymbioticMiddlewareV2 | | __gap | uint256[38] | 12 | 0 | 1216 | src/contracts/BoltSymbioticMiddlewareV2.sol:BoltSymbioticMiddlewareV2 | -The last line indicating the `__gap` storage slot is what's most important. `__gap` has a total of 50 storage slots reserved. You **MUST** verify that the array length of __gap`, in this case `38`, is equal to `50 - __gap.Slot`. In this case, the `Slot` column for `__gap` shows 12, so the layout is correct. +The last line indicating the `__gap` storage slot is what's most important. `__gap` has a total of 50 storage slots reserved. You **MUST** verify that the array length of `__gap`, in this case `38`, is equal to `50 - __gap.Slot`. In this case, the `Slot` column for `__gap` shows 12, so the layout is correct. ## Reinitializers In case you need to reinitialize your contract, you'll need to create a reinitializer. From e0790a5515c98aef5ff802cf2d3c38c25e5014c2 Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Thu, 24 Oct 2024 12:37:28 +0200 Subject: [PATCH 20/23] chore: deploy el middleware upgrade --- .../script/holesky/admin/Upgrade.s.sol | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/bolt-contracts/script/holesky/admin/Upgrade.s.sol b/bolt-contracts/script/holesky/admin/Upgrade.s.sol index e549a53d4..0781b56a9 100644 --- a/bolt-contracts/script/holesky/admin/Upgrade.s.sol +++ b/bolt-contracts/script/holesky/admin/Upgrade.s.sol @@ -10,6 +10,7 @@ import {BoltParametersV1} from "../../../src/contracts/BoltParametersV1.sol"; import {BoltValidatorsV1} from "../../../src/contracts/BoltValidatorsV1.sol"; import {BoltManagerV1} from "../../../src/contracts/BoltManagerV1.sol"; import {BoltEigenLayerMiddlewareV1} from "../../../src/contracts/BoltEigenLayerMiddlewareV1.sol"; +import {BoltEigenLayerMiddlewareV2} from "../../../src/contracts/BoltEigenLayerMiddlewareV2.sol"; import {BoltSymbioticMiddlewareV1} from "../../../src/contracts/BoltSymbioticMiddlewareV1.sol"; import {BoltSymbioticMiddlewareV2} from "../../../src/contracts/BoltSymbioticMiddlewareV2.sol"; import {BoltConfig} from "../../../src/lib/Config.sol"; @@ -30,9 +31,9 @@ contract UpgradeBolt is Script { address[] supportedStrategies; } - function run() public { + function upgradeSymbioticMiddleware() public { address admin = msg.sender; - console.log("Upgrading with admin", admin); + console.log("Upgrading Symbiotic middleware with admin", admin); // TODO: Validate upgrades with Upgrades.validateUpgrade Options memory opts; @@ -67,6 +68,46 @@ contract UpgradeBolt is Script { // TODO: Upgrade contracts with Upgrades.upgradeProxy } + function upgradeEigenLayerMiddleware() public { + address admin = msg.sender; + console.log("Upgrading EigenLayer middleware with admin", admin); + // TODO: Validate upgrades with Upgrades.validateUpgrade + + Options memory opts; + opts.unsafeSkipAllChecks = true; + opts.referenceContract = "BoltEigenLayerMiddlewareV1.sol"; + + string memory upgradeTo = "BoltEigenLayerMiddlewareV2.sol"; + + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/config/holesky/deployments.json"); + string memory json = vm.readFile(path); + + address middlewareV1 = vm.parseJsonAddress(json, ".eigenLayer.middleware"); + + Deployments memory deployments = _readDeployments(); + + bytes memory initEigenLayerMiddleware = abi.encodeCall( + BoltEigenLayerMiddlewareV2.initialize, + ( + admin, + deployments.boltParameters, + deployments.boltManager, + deployments.eigenLayerAVSDirectory, + deployments.eigenLayerDelegationManager, + deployments.eigenLayerStrategyManager + ) + ); + + vm.startBroadcast(admin); + + Upgrades.upgradeProxy(middlewareV1, upgradeTo, initEigenLayerMiddleware, opts); + + vm.stopBroadcast(); + + console.log("BoltSymbioticMiddleware proxy upgraded from %s to %s", opts.referenceContract, upgradeTo); + } + function _readDeployments() public view returns (Deployments memory) { string memory root = vm.projectRoot(); string memory path = string.concat(root, "/config/holesky/deployments.json"); From c66d80a73096e588e16dc7ac49bbc13c499204f4 Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Thu, 24 Oct 2024 12:40:28 +0200 Subject: [PATCH 21/23] fix: save el middleware address in deployments --- bolt-contracts/script/holesky/admin/Upgrade.s.sol | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/bolt-contracts/script/holesky/admin/Upgrade.s.sol b/bolt-contracts/script/holesky/admin/Upgrade.s.sol index 0781b56a9..3fe6152b0 100644 --- a/bolt-contracts/script/holesky/admin/Upgrade.s.sol +++ b/bolt-contracts/script/holesky/admin/Upgrade.s.sol @@ -28,6 +28,7 @@ contract UpgradeBolt is Script { address eigenLayerAVSDirectory; address eigenLayerDelegationManager; address eigenLayerStrategyManager; + address eigenLayerMiddleware; address[] supportedStrategies; } @@ -79,12 +80,6 @@ contract UpgradeBolt is Script { string memory upgradeTo = "BoltEigenLayerMiddlewareV2.sol"; - string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/config/holesky/deployments.json"); - string memory json = vm.readFile(path); - - address middlewareV1 = vm.parseJsonAddress(json, ".eigenLayer.middleware"); - Deployments memory deployments = _readDeployments(); bytes memory initEigenLayerMiddleware = abi.encodeCall( @@ -101,7 +96,7 @@ contract UpgradeBolt is Script { vm.startBroadcast(admin); - Upgrades.upgradeProxy(middlewareV1, upgradeTo, initEigenLayerMiddleware, opts); + Upgrades.upgradeProxy(deployments.eigenLayerMiddleware, upgradeTo, initEigenLayerMiddleware, opts); vm.stopBroadcast(); @@ -125,6 +120,7 @@ contract UpgradeBolt is Script { eigenLayerAVSDirectory: vm.parseJsonAddress(json, ".eigenLayer.avsDirectory"), eigenLayerDelegationManager: vm.parseJsonAddress(json, ".eigenLayer.delegationManager"), eigenLayerStrategyManager: vm.parseJsonAddress(json, ".eigenLayer.strategyManager"), + eigenLayerMiddleware: vm.parseJsonAddress(json, ".eigenLayer.middleware"), supportedStrategies: vm.parseJsonAddressArray(json, ".eigenLayer.supportedStrategies") }); } From f4b7a308ccd6a1810c0caa592584c4c7dc1d9fd9 Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Thu, 24 Oct 2024 12:48:34 +0200 Subject: [PATCH 22/23] fix: initialize v2 --- .../script/holesky/admin/Upgrade.s.sol | 2 +- .../contracts/BoltEigenLayerMiddlewareV2.sol | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/bolt-contracts/script/holesky/admin/Upgrade.s.sol b/bolt-contracts/script/holesky/admin/Upgrade.s.sol index 3fe6152b0..9f75f7e08 100644 --- a/bolt-contracts/script/holesky/admin/Upgrade.s.sol +++ b/bolt-contracts/script/holesky/admin/Upgrade.s.sol @@ -83,7 +83,7 @@ contract UpgradeBolt is Script { Deployments memory deployments = _readDeployments(); bytes memory initEigenLayerMiddleware = abi.encodeCall( - BoltEigenLayerMiddlewareV2.initialize, + BoltEigenLayerMiddlewareV2.initializeV2, ( admin, deployments.boltParameters, diff --git a/bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV2.sol b/bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV2.sol index 3b3c435fb..0533c9872 100644 --- a/bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV2.sol +++ b/bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV2.sol @@ -107,6 +107,25 @@ contract BoltEigenLayerMiddlewareV2 is IBoltMiddlewareV1, IServiceManager, Ownab NAME_HASH = keccak256("EIGENLAYER"); } + function initializeV2( + address _owner, + address _parameters, + address _manager, + address _eigenlayerAVSDirectory, + address _eigenlayerDelegationManager, + address _eigenlayerStrategyManager + ) public reinitializer(2) { + __Ownable_init(_owner); + parameters = IBoltParametersV1(_parameters); + manager = IBoltManagerV1(_manager); + START_TIMESTAMP = Time.timestamp(); + + AVS_DIRECTORY = IAVSDirectory(_eigenlayerAVSDirectory); + DELEGATION_MANAGER = DelegationManagerStorage(_eigenlayerDelegationManager); + STRATEGY_MANAGER = StrategyManagerStorage(_eigenlayerStrategyManager); + NAME_HASH = keccak256("EIGENLAYER"); + } + function _authorizeUpgrade( address newImplementation ) internal override onlyOwner {} From 8df4a6c1c8851d44fd5e4b12c981d84bc0f0e59b Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Thu, 24 Oct 2024 13:51:36 +0200 Subject: [PATCH 23/23] chore: update titles --- bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV1.sol | 2 +- bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV2.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV1.sol b/bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV1.sol index 750501777..34d4b1b9a 100644 --- a/bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV1.sol +++ b/bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV1.sol @@ -23,7 +23,7 @@ import {AVSDirectoryStorage} from "@eigenlayer/src/contracts/core/AVSDirectorySt import {DelegationManagerStorage} from "@eigenlayer/src/contracts/core/DelegationManagerStorage.sol"; import {StrategyManagerStorage} from "@eigenlayer/src/contracts/core/StrategyManagerStorage.sol"; -/// @title Bolt Manager +/// @title Bolt EigenLayer Middleware contract. /// @notice This contract is responsible for interfacing with the EigenLayer restaking protocol. /// @dev This contract is upgradeable using the UUPSProxy pattern. Storage layout remains fixed across upgrades /// with the use of storage gaps. diff --git a/bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV2.sol b/bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV2.sol index 0533c9872..a73921f37 100644 --- a/bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV2.sol +++ b/bolt-contracts/src/contracts/BoltEigenLayerMiddlewareV2.sol @@ -24,7 +24,7 @@ import {AVSDirectoryStorage} from "@eigenlayer/src/contracts/core/AVSDirectorySt import {DelegationManagerStorage} from "@eigenlayer/src/contracts/core/DelegationManagerStorage.sol"; import {StrategyManagerStorage} from "@eigenlayer/src/contracts/core/StrategyManagerStorage.sol"; -/// @title Bolt Manager +/// @title Bolt EigenLayer Middleware contract. /// @notice This contract is responsible for interfacing with the EigenLayer restaking protocol. /// @dev This contract is upgradeable using the UUPSProxy pattern. Storage layout remains fixed across upgrades /// with the use of storage gaps.