diff --git a/contracts/config/anvil.json b/contracts/config/anvil.json index 599025c2..524c46c5 100644 --- a/contracts/config/anvil.json +++ b/contracts/config/anvil.json @@ -29,7 +29,10 @@ "avsOwner": "0x976EA74026E726554dB657fA54763abd0C3a0aa9", "rewardsInitiator": "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955", "vetoCommitteeMember": "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f", - "vetoWindowBlocks": 100 + "vetoWindowBlocks": 100, + "validatorsStrategies": [], + "bspsStrategies": [], + "mspsStrategies": [] }, "snowbridge": { diff --git a/contracts/config/example.jsonc b/contracts/config/example.jsonc index 4a0db9b7..79517734 100644 --- a/contracts/config/example.jsonc +++ b/contracts/config/example.jsonc @@ -73,7 +73,13 @@ /// The address of the account that is a member of the Veto Committee for vetoing slashing. "vetoCommitteeMember": "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f", /// The number of blocks that the Veto Committee will have to submit a veto. - "vetoWindowBlocks": 100 + "vetoWindowBlocks": 100, + /// The EigenLayer strategy addresses for the Validators to stake into. + "validatorsStrategies": [], + /// The EigenLayer strategy addresses for the Backup Storage Providers to stake into. + "bspsStrategies": [], + /// The EigenLayer strategy addresses for the Main Storage Providers to stake into. + "mspsStrategies": [] }, "snowbridge": { diff --git a/contracts/script/deploy/Config.sol b/contracts/script/deploy/Config.sol index 1cf5baab..509ef699 100644 --- a/contracts/script/deploy/Config.sol +++ b/contracts/script/deploy/Config.sol @@ -19,6 +19,9 @@ contract Config { address rewardsInitiator; address vetoCommitteeMember; uint32 vetoWindowBlocks; + address[] validatorsStrategies; + address[] bspsStrategies; + address[] mspsStrategies; } // EigenLayer parameters diff --git a/contracts/script/deploy/DeployLocal.s.sol b/contracts/script/deploy/DeployLocal.s.sol index b688c60b..c35646a1 100644 --- a/contracts/script/deploy/DeployLocal.s.sol +++ b/contracts/script/deploy/DeployLocal.s.sol @@ -58,6 +58,15 @@ import {MerkleUtils} from "../../src/libraries/MerkleUtils.sol"; import {VetoableSlasher} from "../../src/middleware/VetoableSlasher.sol"; import {RewardsRegistry} from "../../src/middleware/RewardsRegistry.sol"; +struct ServiceManagerInitParams { + address avsOwner; + address rewardsInitiator; + address[] validatorsStrategies; + address[] bspsStrategies; + address[] mspsStrategies; + address gateway; +} + contract Deploy is Script, DeployParams, Accounts { // Progress indicator uint16 public deploymentStep = 0; @@ -146,7 +155,7 @@ contract Deploy is Script, DeployParams, Accounts { _deployImplementations(eigenLayerConfig, pauserRegistry); Logging.logStep("Implementation contracts deployed successfully"); - // Upgrade proxies to point to implementations and initialize + // Upgrade proxies to point to implementations and initialise Logging.logSection("Initializing Contracts"); _upgradeAndInitializeProxies(eigenLayerConfig, proxyAdmin); Logging.logStep("Proxies upgraded and initialized successfully"); @@ -166,86 +175,6 @@ contract Deploy is Script, DeployParams, Accounts { Logging.logFooter(); _logProgress(); - // Deploy DataHaven custom contracts - Logging.logHeader("DATAHAVEN CUSTOM CONTRACTS DEPLOYMENT"); - - // Deploy the Service Manager - vm.broadcast(_deployerPrivateKey); - DataHavenServiceManager serviceManagerImplementation = - new DataHavenServiceManager(rewardsCoordinator, permissionController, allocationManager); - Logging.logContractDeployed( - "ServiceManager Implementation", address(serviceManagerImplementation) - ); - - vm.broadcast(_deployerPrivateKey); - DataHavenServiceManager serviceManager = DataHavenServiceManager( - address( - new TransparentUpgradeableProxy( - address(serviceManagerImplementation), - address(proxyAdmin), - abi.encodeWithSelector( - DataHavenServiceManager.initialize.selector, - avsConfig.avsOwner, - avsConfig.rewardsInitiator - ) - ) - ) - ); - Logging.logContractDeployed("ServiceManager Proxy", address(serviceManager)); - - // Deploy VetoableSlasher - vm.broadcast(_deployerPrivateKey); - VetoableSlasher vetoableSlasher = new VetoableSlasher( - allocationManager, - serviceManager, - avsConfig.vetoCommitteeMember, - avsConfig.vetoWindowBlocks - ); - Logging.logContractDeployed("VetoableSlasher", address(vetoableSlasher)); - - // Deploy RewardsRegistry - vm.broadcast(_deployerPrivateKey); - RewardsRegistry rewardsRegistry = new RewardsRegistry( - address(serviceManager), - address(0) // Will be set to the Agent address after creation - ); - Logging.logContractDeployed("RewardsRegistry", address(rewardsRegistry)); - - Logging.logSection("Configuring Service Manager"); - - // Register the DataHaven service in the AllocationManager - vm.broadcast(_avsOwnerPrivateKey); - serviceManager.updateAVSMetadataURI(""); - Logging.logStep("DataHaven service registered in AllocationManager"); - - // Set the slasher in the ServiceManager - vm.broadcast(_avsOwnerPrivateKey); - serviceManager.setSlasher(vetoableSlasher); - Logging.logStep("Slasher set in ServiceManager"); - - // Set the RewardsRegistry in the ServiceManager - vm.broadcast(_avsOwnerPrivateKey); - serviceManager.setRewardsRegistry(0, rewardsRegistry); - Logging.logStep("RewardsRegistry set in ServiceManager"); - - // Create an operator set in the DataHaven service - IAllocationManagerTypes.CreateSetParams[] memory operatorSetParams = - new IAllocationManagerTypes.CreateSetParams[](1); - IStrategy[] memory strategies = new IStrategy[](deployedStrategies.length); - for (uint256 i = 0; i < deployedStrategies.length; i++) { - strategies[i] = IStrategy(deployedStrategies[i]); - } - operatorSetParams[0] = - IAllocationManagerTypes.CreateSetParams({operatorSetId: 0, strategies: strategies}); - vm.broadcast(_avsOwnerPrivateKey); - serviceManager.createOperatorSets(operatorSetParams); - Logging.logStep( - "Operator set created in DataHaven service with all the deployed strategies" - ); - - Logging.logFooter(); - _logProgress(); - // Deploy Snowbridge and configure Agent Logging.logHeader("SNOWBRIDGE DEPLOYMENT"); @@ -259,6 +188,16 @@ contract Deploy is Script, DeployParams, Accounts { Logging.logFooter(); _logProgress(); + // Deploy DataHaven custom contracts + ( + DataHavenServiceManager serviceManager, + VetoableSlasher vetoableSlasher, + RewardsRegistry rewardsRegistry + ) = _deployDataHavenContracts(avsConfig, proxyAdmin, gateway); + + Logging.logFooter(); + _logProgress(); + // Set the Agent in the RewardsRegistry Logging.logHeader("FINAL CONFIGURATION"); // This needs to be executed by the AVS owner @@ -778,4 +717,114 @@ contract Deploy is Script, DeployParams, Accounts { vm.writeFile(deploymentPath, json); Logging.logInfo(string.concat("Deployment info saved to: ", deploymentPath)); } + + function _deployDataHavenContracts( + AVSConfig memory avsConfig, + ProxyAdmin proxyAdmin, + IGatewayV2 gateway + ) internal returns (DataHavenServiceManager, VetoableSlasher, RewardsRegistry) { + Logging.logHeader("DATAHAVEN CUSTOM CONTRACTS DEPLOYMENT"); + + // Deploy the Service Manager + vm.broadcast(_deployerPrivateKey); + DataHavenServiceManager serviceManagerImplementation = + new DataHavenServiceManager(rewardsCoordinator, permissionController, allocationManager); + Logging.logContractDeployed( + "ServiceManager Implementation", address(serviceManagerImplementation) + ); + + // Extract strategies logic to a helper function to reduce local variables + _prepareStrategiesForServiceManager(avsConfig, deployedStrategies); + + // Create service manager initialisation parameters struct to reduce stack variables + ServiceManagerInitParams memory initParams = ServiceManagerInitParams({ + avsOwner: avsConfig.avsOwner, + rewardsInitiator: avsConfig.rewardsInitiator, + validatorsStrategies: avsConfig.validatorsStrategies, + bspsStrategies: avsConfig.bspsStrategies, + mspsStrategies: avsConfig.mspsStrategies, + gateway: address(gateway) + }); + + // Create the service manager proxy + DataHavenServiceManager serviceManager = + _createServiceManagerProxy(serviceManagerImplementation, proxyAdmin, initParams); + Logging.logContractDeployed("ServiceManager Proxy", address(serviceManager)); + + // Deploy VetoableSlasher + vm.broadcast(_deployerPrivateKey); + VetoableSlasher vetoableSlasher = new VetoableSlasher( + allocationManager, + serviceManager, + avsConfig.vetoCommitteeMember, + avsConfig.vetoWindowBlocks + ); + Logging.logContractDeployed("VetoableSlasher", address(vetoableSlasher)); + + // Deploy RewardsRegistry + vm.broadcast(_deployerPrivateKey); + RewardsRegistry rewardsRegistry = new RewardsRegistry( + address(serviceManager), + address(0) // Will be set to the Agent address after creation + ); + Logging.logContractDeployed("RewardsRegistry", address(rewardsRegistry)); + + Logging.logSection("Configuring Service Manager"); + + // Register the DataHaven service in the AllocationManager + vm.broadcast(_avsOwnerPrivateKey); + serviceManager.updateAVSMetadataURI(""); + Logging.logStep("DataHaven service registered in AllocationManager"); + + // Set the slasher in the ServiceManager + vm.broadcast(_avsOwnerPrivateKey); + serviceManager.setSlasher(vetoableSlasher); + Logging.logStep("Slasher set in ServiceManager"); + + // Set the RewardsRegistry in the ServiceManager + uint32 validatorsSetId = serviceManager.VALIDATORS_SET_ID(); + vm.broadcast(_avsOwnerPrivateKey); + serviceManager.setRewardsRegistry(validatorsSetId, rewardsRegistry); + Logging.logStep("RewardsRegistry set in ServiceManager"); + + return (serviceManager, vetoableSlasher, rewardsRegistry); + } + + function _createServiceManagerProxy( + DataHavenServiceManager implementation, + ProxyAdmin proxyAdmin, + ServiceManagerInitParams memory params + ) internal returns (DataHavenServiceManager) { + vm.broadcast(_deployerPrivateKey); + bytes memory initData = abi.encodeWithSelector( + DataHavenServiceManager.initialise.selector, + params.avsOwner, + params.rewardsInitiator, + params.validatorsStrategies, + params.bspsStrategies, + params.mspsStrategies, + params.gateway + ); + + TransparentUpgradeableProxy proxy = + new TransparentUpgradeableProxy(address(implementation), address(proxyAdmin), initData); + + return DataHavenServiceManager(address(proxy)); + } + + function _prepareStrategiesForServiceManager( + AVSConfig memory config, + StrategyBaseTVLLimits[] memory strategies + ) internal pure { + if (config.validatorsStrategies.length == 0) { + config.validatorsStrategies = new address[](strategies.length); + config.bspsStrategies = new address[](strategies.length); + config.mspsStrategies = new address[](strategies.length); + for (uint256 i = 0; i < strategies.length; i++) { + config.validatorsStrategies[i] = address(strategies[i]); + config.bspsStrategies[i] = address(strategies[i]); + config.mspsStrategies[i] = address(strategies[i]); + } + } + } } diff --git a/contracts/script/deploy/DeployParams.s.sol b/contracts/script/deploy/DeployParams.s.sol index 46925e4e..a6388cc0 100644 --- a/contracts/script/deploy/DeployParams.s.sol +++ b/contracts/script/deploy/DeployParams.s.sol @@ -53,6 +53,10 @@ contract DeployParams is Script, Config { config.rewardsInitiator = vm.parseJsonAddress(configJson, ".avs.rewardsInitiator"); config.vetoCommitteeMember = vm.parseJsonAddress(configJson, ".avs.vetoCommitteeMember"); config.vetoWindowBlocks = uint32(vm.parseJsonUint(configJson, ".avs.vetoWindowBlocks")); + config.validatorsStrategies = + vm.parseJsonAddressArray(configJson, ".avs.validatorsStrategies"); + config.bspsStrategies = vm.parseJsonAddressArray(configJson, ".avs.bspsStrategies"); + config.mspsStrategies = vm.parseJsonAddressArray(configJson, ".avs.mspsStrategies"); return config; } diff --git a/contracts/script/transact/SignUpBsp.s.sol b/contracts/script/transact/SignUpBsp.s.sol new file mode 100644 index 00000000..782d2396 --- /dev/null +++ b/contracts/script/transact/SignUpBsp.s.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.27; + +import {SignUpOperatorBase} from "./SignUpOperatorBase.s.sol"; +import {DataHavenServiceManager} from "../../src/DataHavenServiceManager.sol"; + +/** + * @title SignUpBsp + * @notice Script to sign up a backup storage provider (BSP) for the DataHaven network + */ +contract SignUpBsp is SignUpOperatorBase { + /** + * @inheritdoc SignUpOperatorBase + */ + function _getOperatorSetId() internal view override returns (uint32) { + return serviceManager.BSPS_SET_ID(); + } + + /** + * @inheritdoc SignUpOperatorBase + */ + function _addToAllowlist() internal override { + vm.broadcast(_avsOwnerPrivateKey); + serviceManager.addBspToAllowlist(_operator); + } + + /** + * @inheritdoc SignUpOperatorBase + */ + function _getOperatorTypeName() internal pure override returns (string memory) { + return "BSP"; + } +} diff --git a/contracts/script/transact/SignUpMsp.s.sol b/contracts/script/transact/SignUpMsp.s.sol new file mode 100644 index 00000000..41c8190f --- /dev/null +++ b/contracts/script/transact/SignUpMsp.s.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.27; + +import {SignUpOperatorBase} from "./SignUpOperatorBase.s.sol"; +import {DataHavenServiceManager} from "../../src/DataHavenServiceManager.sol"; + +/** + * @title SignUpMsp + * @notice Script to sign up a main storage provider (MSP) for the DataHaven network + */ +contract SignUpMsp is SignUpOperatorBase { + /** + * @inheritdoc SignUpOperatorBase + */ + function _getOperatorSetId() internal view override returns (uint32) { + return serviceManager.MSPS_SET_ID(); + } + + /** + * @inheritdoc SignUpOperatorBase + */ + function _addToAllowlist() internal override { + vm.broadcast(_avsOwnerPrivateKey); + serviceManager.addMspToAllowlist(_operator); + } + + /** + * @inheritdoc SignUpOperatorBase + */ + function _getOperatorTypeName() internal pure override returns (string memory) { + return "MSP"; + } +} diff --git a/contracts/script/transact/SignUpOperatorBase.s.sol b/contracts/script/transact/SignUpOperatorBase.s.sol new file mode 100644 index 00000000..dec81f06 --- /dev/null +++ b/contracts/script/transact/SignUpOperatorBase.s.sol @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.27; + +// EigenLayer imports +import {IAllocationManagerTypes} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {StrategyBase} from "eigenlayer-contracts/src/contracts/strategies/StrategyBase.sol"; + +// OpenZeppelin imports +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +// Testing imports +import {Script} from "forge-std/Script.sol"; +import {console} from "forge-std/console.sol"; +import {Logging} from "../utils/Logging.sol"; +import {ELScriptStorage} from "../utils/ELScriptStorage.s.sol"; +import {DHScriptStorage} from "../utils/DHScriptStorage.s.sol"; +import {Accounts} from "../utils/Accounts.sol"; + +/** + * @title SignUpOperatorBase + * @notice Base contract for signing up different types of operators (Validators, BSPs, MSPs) + */ +abstract contract SignUpOperatorBase is Script, ELScriptStorage, DHScriptStorage, Accounts { + // Progress indicator + uint16 public deploymentStep = 0; + uint16 public totalSteps = 3; // Total major steps + + function _logProgress() internal { + deploymentStep++; + Logging.logProgress(deploymentStep, totalSteps); + } + + /** + * @notice Abstract method to be implemented by derived contracts to get the operator set ID + * @return The operator set ID for the specific type (Validator, BSP, MSP) + */ + function _getOperatorSetId() internal view virtual returns (uint32); + + /** + * @notice Abstract method to be implemented by derived contracts to add operator to allowlist + */ + function _addToAllowlist() internal virtual; + + /** + * @notice Abstract method to get the operator type name (for logging) + * @return The name of the operator type + */ + function _getOperatorTypeName() internal view virtual returns (string memory); + + function run() public { + string memory network = vm.envOr("NETWORK", string("anvil")); + Logging.logHeader(string.concat("SIGN UP DATAHAVEN ", _getOperatorTypeName())); + console.log("| Network: %s", network); + console.log("| Timestamp: %s", vm.toString(block.timestamp)); + Logging.logFooter(); + + // Read addresses of latest deployment of EigenLayer contracts, for the given network. + _loadELContracts(network); + Logging.logInfo(string.concat("Loaded EigenLayer contracts for network: ", network)); + + // Read addresses of latest deployment of DataHaven contracts, for the given network. + _loadDHContracts(network); + Logging.logInfo(string.concat("Loaded DataHaven contracts for network: ", network)); + + _logProgress(); + + // STEP 1: Stake tokens into strategies + Logging.logSection("Staking Tokens into Strategies"); + + // Get the deployed strategies and deposit some of the operator's balance into them. + for (uint256 i = 0; i < deployedStrategies.length; i++) { + IERC20 linkedToken = StrategyBase(deployedStrategies[i]).underlyingToken(); + + // Check that the operator has a balance of the linked token. + uint256 balance = linkedToken.balanceOf(_operator); + Logging.logInfo( + string.concat( + "Strategy ", + vm.toString(i), + " underlying token: ", + vm.toString(address(linkedToken)), + " - Operator balance: ", + vm.toString(balance) + ) + ); + + require(balance > 0, "Operator does not have a balance of the linked token"); + + // Stake some of the operator's balance as stake for the strategy. + vm.startBroadcast(_operatorPrivateKey); + uint256 balanceToStake = balance / 10; + linkedToken.approve(address(strategyManager), balanceToStake); + strategyManager.depositIntoStrategy(deployedStrategies[i], linkedToken, balanceToStake); + vm.stopBroadcast(); + + Logging.logStep( + string.concat( + "Staked ", vm.toString(balanceToStake), " tokens for strategy ", vm.toString(i) + ) + ); + } + _logProgress(); + + // STEP 2: Register as an operator in EigenLayer + Logging.logSection("Registering as EigenLayer Operator"); + + // Register the operator as an operator. + // We don't set a delegation approver, so that there is no need to sign any messages. + address initDelegationApprover = address(0); + uint32 allocationDelay = 0; + string memory metadataURI = ""; + vm.broadcast(_operatorPrivateKey); + delegation.registerAsOperator(initDelegationApprover, allocationDelay, metadataURI); + Logging.logStep( + string.concat("Registered operator in EigenLayer: ", vm.toString(_operator)) + ); + + // Check the staked balance of the operator. + Logging.logSection("Operator Shares Information"); + for (uint256 i = 0; i < deployedStrategies.length; i++) { + uint256 operatorShares = delegation.operatorShares(_operator, deployedStrategies[i]); + Logging.logInfo( + string.concat( + "Operator shares for strategy ", + vm.toString(i), + ": ", + vm.toString(operatorShares) + ) + ); + } + _logProgress(); + + // STEP 3: Register as a DataHaven operator of specific type + Logging.logSection(string.concat("Registering as DataHaven ", _getOperatorTypeName())); + + // Add the operator to the appropriate allowlist of the DataHaven service. + _addToAllowlist(); + Logging.logStep( + string.concat( + "Added operator to ", _getOperatorTypeName(), " allowlist of DataHaven service" + ) + ); + + // Register the operator as operator for the DataHaven service. + uint32[] memory operatorSetIds = new uint32[](1); + operatorSetIds[0] = _getOperatorSetId(); + IAllocationManagerTypes.RegisterParams memory registerParams = IAllocationManagerTypes + .RegisterParams({avs: address(serviceManager), operatorSetIds: operatorSetIds, data: ""}); + + vm.broadcast(_operatorPrivateKey); + allocationManager.registerForOperatorSets(_operator, registerParams); + Logging.logStep( + string.concat("Registered ", _getOperatorTypeName(), " in DataHaven service") + ); + + Logging.logHeader("OPERATOR SETUP COMPLETE"); + Logging.logInfo(string.concat(_getOperatorTypeName(), ": ", vm.toString(_operator))); + Logging.logInfo( + string.concat("Successfully configured ", _getOperatorTypeName(), " for DataHaven") + ); + Logging.logFooter(); + } +} diff --git a/contracts/script/transact/SignUpValidator.s.sol b/contracts/script/transact/SignUpValidator.s.sol new file mode 100644 index 00000000..44a241ba --- /dev/null +++ b/contracts/script/transact/SignUpValidator.s.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.27; + +import {SignUpOperatorBase} from "./SignUpOperatorBase.s.sol"; +import {DataHavenServiceManager} from "../../src/DataHavenServiceManager.sol"; + +/** + * @title SignUpValidator + * @notice Script to sign up a validator for the DataHaven network + */ +contract SignUpValidator is SignUpOperatorBase { + /** + * @inheritdoc SignUpOperatorBase + */ + function _getOperatorSetId() internal view override returns (uint32) { + return serviceManager.VALIDATORS_SET_ID(); + } + + /** + * @inheritdoc SignUpOperatorBase + */ + function _addToAllowlist() internal override { + vm.broadcast(_avsOwnerPrivateKey); + serviceManager.addValidatorToAllowlist(_operator); + } + + /** + * @inheritdoc SignUpOperatorBase + */ + function _getOperatorTypeName() internal pure override returns (string memory) { + return "VALIDATOR"; + } +} diff --git a/contracts/src/DataHavenServiceManager.sol b/contracts/src/DataHavenServiceManager.sol index d180786a..62a7e83f 100644 --- a/contracts/src/DataHavenServiceManager.sol +++ b/contracts/src/DataHavenServiceManager.sol @@ -1,22 +1,55 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.27; -import {IRewardsCoordinator} from - "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol"; +// EigenLayer imports +import { + IAllocationManager, + IAllocationManagerTypes +} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IAVSRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IAVSRegistrar.sol"; import {IPermissionController} from "eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol"; -import {IAllocationManager} from - "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IRewardsCoordinator} from + "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol"; +import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; +import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; +// Snowbridge imports +import {IGatewayV2} from "snowbridge/src/v2/IGateway.sol"; +import {ScaleCodec} from "snowbridge/src/utils/ScaleCodec.sol"; + +// DataHaven imports +import {DataHavenSnowbridgeMessages} from "./libraries/DataHavenSnowbridgeMessages.sol"; +import {IDataHavenServiceManager} from "./interfaces/IDataHavenServiceManager.sol"; import {ServiceManagerBase} from "./middleware/ServiceManagerBase.sol"; /** - * @title DataHaven ServiceManager contract. - * TODO: For now, it uses the ServiceManagerBase contract as is. - * TODO: We should add the DataHaven specific logic here. + * @title DataHaven ServiceManager contract + * @notice Manages validators, backup storage providers (BSPs), and main storage providers (MSPs) + * in the DataHaven network */ -contract DataHavenServiceManager is ServiceManagerBase { - uint256 public number; +contract DataHavenServiceManager is ServiceManagerBase, IDataHavenServiceManager { + /// @notice The metadata for the DataHaven AVS. + string public constant DATAHAVEN_AVS_METADATA = "https://datahaven.network/"; + + /// @notice The EigenLayer operator set ID for the Validators securing the DataHaven network. + uint32 public constant VALIDATORS_SET_ID = 0; + /// @notice The EigenLayer operator set ID for the Backup Storage Providers participating in the DataHaven network. + uint32 public constant BSPS_SET_ID = 1; + /// @notice The EigenLayer operator set ID for the Main Storage Providers participating in the DataHaven network. + uint32 public constant MSPS_SET_ID = 2; + + /// @inheritdoc IDataHavenServiceManager + mapping(address => bool) public validatorsAllowlist; + /// @inheritdoc IDataHavenServiceManager + mapping(address => bool) public bspsAllowlist; + /// @inheritdoc IDataHavenServiceManager + mapping(address => bool) public mspsAllowlist; + + IGatewayV2 private _snowbridgeGateway; + + /// @inheritdoc IDataHavenServiceManager + mapping(address => bytes32) public validatorEthAddressToSolochainAddress; /// @notice Sets the (immutable) `_registryCoordinator` address constructor( @@ -25,23 +58,306 @@ contract DataHavenServiceManager is ServiceManagerBase { IAllocationManager __allocationManager ) ServiceManagerBase(__rewardsCoordinator, __permissionController, __allocationManager) {} - function initialize( + /// @notice Modifier to ensure the caller is a registered Validator + modifier onlyValidator() { + OperatorSet memory operatorSet = OperatorSet({avs: address(this), id: VALIDATORS_SET_ID}); + require( + _allocationManager.isMemberOfOperatorSet(msg.sender, operatorSet), + CallerIsNotValidator() + ); + _; + } + + /// @inheritdoc IDataHavenServiceManager + function initialise( address initialOwner, - address rewardsInitiator + address rewardsInitiator, + IStrategy[] memory validatorsStrategies, + IStrategy[] memory bspsStrategies, + IStrategy[] memory mspsStrategies, + address _snowbridgeGatewayAddress ) public virtual initializer { __ServiceManagerBase_init(initialOwner, rewardsInitiator); + + // Register the DataHaven service in the AllocationManager. + _allocationManager.updateAVSMetadataURI(address(this), DATAHAVEN_AVS_METADATA); + + // Create the operator sets for the DataHaven service. + _createDataHavenOperatorSets(validatorsStrategies, bspsStrategies, mspsStrategies); + + // Set the Snowbridge Gateway address. + // This is the contract to which messages are sent, to be relayed to the Solochain network. + _snowbridgeGateway = IGatewayV2(_snowbridgeGatewayAddress); + } + + /// @inheritdoc IDataHavenServiceManager + function sendNewValidatorSet( + uint128 executionFee, + uint128 relayerFee + ) external payable onlyOwner { + // Send the new validator set message to the Snowbridge Gateway + bytes memory message = buildNewValidatorSetMessage(); + _snowbridgeGateway.v2_sendMessage{value: msg.value}( + message, + new bytes[](0), // No assets to send + bytes(""), // No claimer + executionFee, + relayerFee + ); + } + + /// @inheritdoc IDataHavenServiceManager + function buildNewValidatorSetMessage() public view returns (bytes memory) { + // Get the current validator set + OperatorSet memory operatorSet = OperatorSet({avs: address(this), id: VALIDATORS_SET_ID}); + address[] memory currentValidatorSet = _allocationManager.getMembers(operatorSet); + + // Build the new validator set message + bytes32[] memory newValidatorSet = new bytes32[](currentValidatorSet.length); + for (uint256 i = 0; i < currentValidatorSet.length; i++) { + newValidatorSet[i] = validatorEthAddressToSolochainAddress[currentValidatorSet[i]]; + } + DataHavenSnowbridgeMessages.NewValidatorSetPayload memory newValidatorSetPayload = + DataHavenSnowbridgeMessages.NewValidatorSetPayload({newValidatorSet: newValidatorSet}); + DataHavenSnowbridgeMessages.NewValidatorSet memory newValidatorSetMessage = + DataHavenSnowbridgeMessages.NewValidatorSet({ + nonce: 0, + topic: bytes32(0), + payload: newValidatorSetPayload + }); + + // Return the encoded message + return DataHavenSnowbridgeMessages.scaleEncodeNewValidatorSetMessage(newValidatorSetMessage); + } + + /// @inheritdoc IDataHavenServiceManager + function updateSolochainAddressForValidator( + bytes32 solochainAddress + ) external onlyValidator { + // Update the Solochain address for the Validator + validatorEthAddressToSolochainAddress[msg.sender] = solochainAddress; + } + + /// @inheritdoc IDataHavenServiceManager + function setSnowbridgeGateway( + address _newSnowbridgeGateway + ) external onlyOwner { + _snowbridgeGateway = IGatewayV2(_newSnowbridgeGateway); + emit SnowbridgeGatewaySet(_newSnowbridgeGateway); + } + + /// @inheritdoc IAVSRegistrar + function registerOperator( + address operator, + address avs, + uint32[] calldata operatorSetIds, + bytes calldata data + ) external override { + if (avs != address(this)) { + revert IncorrectAVSAddress(); + } + + if (operatorSetIds.length != 1) { + revert CantRegisterToMultipleOperatorSets(); + } + + // Case: Validator + if (operatorSetIds[0] == VALIDATORS_SET_ID) { + if (!validatorsAllowlist[operator]) { + revert OperatorNotInAllowlist(); + } + + // In the case of the Validators operator set, expect the data to have the Solochain address of the operator. + // TODO: We should have some sort of validation of this address that validators are setting for themselves. + validatorEthAddressToSolochainAddress[operator] = bytes32(data); + } + // Case: BSP + else if (operatorSetIds[0] == BSPS_SET_ID) { + if (!bspsAllowlist[operator]) { + revert OperatorNotInAllowlist(); + } + } + // Case: MSP + else if (operatorSetIds[0] == MSPS_SET_ID) { + if (!mspsAllowlist[operator]) { + revert OperatorNotInAllowlist(); + } + } + // Case: Invalid operator set ID + else { + revert InvalidOperatorSetId(); + } + + emit OperatorRegistered(operator, operatorSetIds[0]); + } + + /// @inheritdoc IAVSRegistrar + function deregisterOperator( + address operator, + address avs, + uint32[] calldata operatorSetIds + ) external override { + if (avs != address(this)) { + revert IncorrectAVSAddress(); + } + + if (operatorSetIds.length != 1) { + revert CantDeregisterFromMultipleOperatorSets(); + } + + if ( + operatorSetIds[0] != VALIDATORS_SET_ID && operatorSetIds[0] != BSPS_SET_ID + && operatorSetIds[0] != MSPS_SET_ID + ) { + revert InvalidOperatorSetId(); + } + + if (operatorSetIds[0] == VALIDATORS_SET_ID) { + // Remove validator from the addresses mapping + delete validatorEthAddressToSolochainAddress[operator]; + } + + emit OperatorDeregistered(operator, operatorSetIds[0]); + } + + /// @inheritdoc IDataHavenServiceManager + function addValidatorToAllowlist( + address validator + ) external onlyOwner { + validatorsAllowlist[validator] = true; + emit ValidatorAddedToAllowlist(validator); + } + + /// @inheritdoc IDataHavenServiceManager + function addBspToAllowlist( + address bsp + ) external onlyOwner { + bspsAllowlist[bsp] = true; + emit BspAddedToAllowlist(bsp); + } + + /// @inheritdoc IDataHavenServiceManager + function addMspToAllowlist( + address msp + ) external onlyOwner { + mspsAllowlist[msp] = true; + emit MspAddedToAllowlist(msp); + } + + /// @inheritdoc IDataHavenServiceManager + function removeValidatorFromAllowlist( + address validator + ) external onlyOwner { + validatorsAllowlist[validator] = false; + emit ValidatorRemovedFromAllowlist(validator); + } + + /// @inheritdoc IDataHavenServiceManager + function removeBspFromAllowlist( + address bsp + ) external onlyOwner { + bspsAllowlist[bsp] = false; + emit BspRemovedFromAllowlist(bsp); + } + + /// @inheritdoc IDataHavenServiceManager + function removeMspFromAllowlist( + address msp + ) external onlyOwner { + mspsAllowlist[msp] = false; + emit MspRemovedFromAllowlist(msp); + } + + /// @inheritdoc IDataHavenServiceManager + function validatorsSupportedStrategies() external view returns (IStrategy[] memory) { + OperatorSet memory operatorSet = OperatorSet({avs: address(this), id: VALIDATORS_SET_ID}); + return _allocationManager.getStrategiesInOperatorSet(operatorSet); + } + + /// @inheritdoc IDataHavenServiceManager + function removeStrategiesFromValidatorsSupportedStrategies( + IStrategy[] calldata _strategies + ) external onlyOwner { + _allocationManager.removeStrategiesFromOperatorSet( + address(this), VALIDATORS_SET_ID, _strategies + ); + } + + /// @inheritdoc IDataHavenServiceManager + function addStrategiesToValidatorsSupportedStrategies( + IStrategy[] calldata _strategies + ) external onlyOwner { + _allocationManager.addStrategiesToOperatorSet(address(this), VALIDATORS_SET_ID, _strategies); + } + + /// @inheritdoc IDataHavenServiceManager + function bspsSupportedStrategies() external view returns (IStrategy[] memory) { + OperatorSet memory operatorSet = OperatorSet({avs: address(this), id: BSPS_SET_ID}); + return _allocationManager.getStrategiesInOperatorSet(operatorSet); + } + + /// @inheritdoc IDataHavenServiceManager + function removeStrategiesFromBspsSupportedStrategies( + IStrategy[] calldata _strategies + ) external onlyOwner { + _allocationManager.removeStrategiesFromOperatorSet(address(this), BSPS_SET_ID, _strategies); + } + + /// @inheritdoc IDataHavenServiceManager + function addStrategiesToBspsSupportedStrategies( + IStrategy[] calldata _strategies + ) external onlyOwner { + _allocationManager.addStrategiesToOperatorSet(address(this), BSPS_SET_ID, _strategies); + } + + /// @inheritdoc IDataHavenServiceManager + function mspsSupportedStrategies() external view returns (IStrategy[] memory) { + OperatorSet memory operatorSet = OperatorSet({avs: address(this), id: MSPS_SET_ID}); + return _allocationManager.getStrategiesInOperatorSet(operatorSet); + } + + /// @inheritdoc IDataHavenServiceManager + function removeStrategiesFromMspsSupportedStrategies( + IStrategy[] calldata _strategies + ) external onlyOwner { + _allocationManager.removeStrategiesFromOperatorSet(address(this), MSPS_SET_ID, _strategies); + } + + /// @inheritdoc IDataHavenServiceManager + function addStrategiesToMspsSupportedStrategies( + IStrategy[] calldata _strategies + ) external onlyOwner { + _allocationManager.addStrategiesToOperatorSet(address(this), MSPS_SET_ID, _strategies); + } + + /// @inheritdoc IDataHavenServiceManager + function snowbridgeGateway() external view returns (address) { + return address(_snowbridgeGateway); } /** - * @notice Override the internal _ensureOperatorIsPartOfOperatorSet function to simplify testing - * @param operator The operator address - * @param operatorSetId The operator set ID - * @dev This should be removed once the AllocationManagerMock is updated to be able to handle operator sets + * @notice Creates the initial operator sets for DataHaven in the AllocationManager. + * @dev This function should be called during initialisation to set up the required operator sets. */ - function _ensureOperatorIsPartOfOperatorSet( - address operator, - uint32 operatorSetId - ) internal view override { - // No-op for testing + function _createDataHavenOperatorSets( + IStrategy[] memory validatorsStrategies, + IStrategy[] memory bspsStrategies, + IStrategy[] memory mspsStrategies + ) internal { + IAllocationManagerTypes.CreateSetParams[] memory operatorSets = + new IAllocationManagerTypes.CreateSetParams[](3); + operatorSets[0] = IAllocationManagerTypes.CreateSetParams({ + operatorSetId: VALIDATORS_SET_ID, + strategies: validatorsStrategies + }); + operatorSets[1] = IAllocationManagerTypes.CreateSetParams({ + operatorSetId: BSPS_SET_ID, + strategies: bspsStrategies + }); + operatorSets[2] = IAllocationManagerTypes.CreateSetParams({ + operatorSetId: MSPS_SET_ID, + strategies: mspsStrategies + }); + _allocationManager.createOperatorSets(address(this), operatorSets); } } diff --git a/contracts/src/interfaces/IDataHavenServiceManager.sol b/contracts/src/interfaces/IDataHavenServiceManager.sol new file mode 100644 index 00000000..08c9cd61 --- /dev/null +++ b/contracts/src/interfaces/IDataHavenServiceManager.sol @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.27; + +// EigenLayer imports +import {IAVSRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IAVSRegistrar.sol"; +import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; + +// Snowbridge imports +import {IGatewayV2} from "snowbridge/src/v2/IGateway.sol"; + +/** + * @title DataHaven Service Manager Errors Interface + * @notice Contains all error definitions used by the DataHaven Service Manager + */ +interface IDataHavenServiceManagerErrors { + /// @notice Thrown when an operator attempts to register with an incorrect AVS address + error IncorrectAVSAddress(); + /// @notice Thrown when an operator attempts to register to multiple operator sets at once + error CantRegisterToMultipleOperatorSets(); + /// @notice Thrown when an operator attempts to deregister from multiple operator sets at once + error CantDeregisterFromMultipleOperatorSets(); + /// @notice Thrown when an invalid operator set ID is provided + error InvalidOperatorSetId(); + /// @notice Thrown when an operator not in the appropriate allowlist attempts to register + error OperatorNotInAllowlist(); + /// @notice Thrown when the caller is not a Validator in the Validators operator set + error CallerIsNotValidator(); +} + +/** + * @title DataHaven Service Manager Events Interface + * @notice Contains all event definitions emitted by the DataHaven Service Manager + */ +interface IDataHavenServiceManagerEvents { + /// @notice Emitted when an operator successfully registers to an operator set + /// @param operator Address of the operator that registered + /// @param operatorSetId ID of the operator set the operator registered to + event OperatorRegistered(address indexed operator, uint32 indexed operatorSetId); + + /// @notice Emitted when an operator deregisters from an operator set + /// @param operator Address of the operator that deregistered + /// @param operatorSetId ID of the operator set the operator deregistered from + event OperatorDeregistered(address indexed operator, uint32 indexed operatorSetId); + + /// @notice Emitted when a validator is added to the allowlist + /// @param validator Address of the validator added to the allowlist + event ValidatorAddedToAllowlist(address indexed validator); + + /// @notice Emitted when a Backup Storage Provider is added to the allowlist + /// @param bsp Address of the BSP added to the allowlist + event BspAddedToAllowlist(address indexed bsp); + + /// @notice Emitted when a Main Storage Provider is added to the allowlist + /// @param msp Address of the MSP added to the allowlist + event MspAddedToAllowlist(address indexed msp); + + /// @notice Emitted when a validator is removed from the allowlist + /// @param validator Address of the validator removed from the allowlist + event ValidatorRemovedFromAllowlist(address indexed validator); + + /// @notice Emitted when a Backup Storage Provider is removed from the allowlist + /// @param bsp Address of the BSP removed from the allowlist + event BspRemovedFromAllowlist(address indexed bsp); + + /// @notice Emitted when a Main Storage Provider is removed from the allowlist + /// @param msp Address of the MSP removed from the allowlist + event MspRemovedFromAllowlist(address indexed msp); + + /// @notice Emitted when the Snowbridge Gateway address is set + /// @param snowbridgeGateway Address of the Snowbridge Gateway + event SnowbridgeGatewaySet(address indexed snowbridgeGateway); +} + +/** + * @title DataHaven Service Manager Interface + * @notice Defines the interface for the DataHaven Service Manager, which manages validators, + * backup storage providers (BSPs), and main storage providers (MSPs) in the DataHaven network + */ +interface IDataHavenServiceManager is + IDataHavenServiceManagerErrors, + IDataHavenServiceManagerEvents +{ + /// @notice Checks if a validator address is in the allowlist + /// @param validator Address to check + /// @return True if the validator is in the allowlist, false otherwise + function validatorsAllowlist( + address validator + ) external view returns (bool); + + /// @notice Checks if a BSP address is in the allowlist + /// @param bsp Address to check + /// @return True if the BSP is in the allowlist, false otherwise + function bspsAllowlist( + address bsp + ) external view returns (bool); + + /// @notice Checks if an MSP address is in the allowlist + /// @param msp Address to check + /// @return True if the MSP is in the allowlist, false otherwise + function mspsAllowlist( + address msp + ) external view returns (bool); + + /// @notice Returns the Snowbridge Gateway address + /// @return The Snowbridge gateway address + function snowbridgeGateway() external view returns (address); + + /** + * @notice Converts a validator address to the corresponding Solochain address + * @param validatorAddress The address of the validator to convert + * @return The corresponding Solochain address + */ + function validatorEthAddressToSolochainAddress( + address validatorAddress + ) external view returns (bytes32); + + /** + * @notice Initializes the DataHaven Service Manager + * @param initialOwner Address of the initial owner + * @param rewardsInitiator Address authorized to initiate rewards + * @param validatorsStrategies Array of strategies supported by validators + * @param bspsStrategies Array of strategies supported by BSPs + * @param mspsStrategies Array of strategies supported by MSPs + */ + function initialise( + address initialOwner, + address rewardsInitiator, + IStrategy[] memory validatorsStrategies, + IStrategy[] memory bspsStrategies, + IStrategy[] memory mspsStrategies, + address _snowbridgeGatewayAddress + ) external; + + /** + * @notice Sends a new validator set to the Snowbridge Gateway + * @dev The new validator set is made up of the Validators currently + * registered in the DataHaven Service Manager as operators of + * the Validators operator set (operatorSetId = VALIDATORS_SET_ID) + * @dev Only callable by the owner + * @param executionFee The execution fee for the Snowbridge message + * @param relayerFee The relayer fee for the Snowbridge message + */ + function sendNewValidatorSet(uint128 executionFee, uint128 relayerFee) external payable; + + /** + * @notice Builds a new validator set message to be sent to the Snowbridge Gateway + * @return The encoded message bytes to be sent to the Snowbridge Gateway + */ + function buildNewValidatorSetMessage() external view returns (bytes memory); + + /** + * @notice Updates the Solochain address for a Validator + * @param solochainAddress The new Solochain address for the Validator + * @dev The caller must be the registered operator address for the Validator, in EigenLayer, + * in the Validators operator set (operatorSetId = VALIDATORS_SET_ID) + */ + function updateSolochainAddressForValidator( + bytes32 solochainAddress + ) external; + + /** + * @notice Sets the Snowbridge Gateway address + * @param _snowbridgeGateway The address of the Snowbridge Gateway + */ + function setSnowbridgeGateway( + address _snowbridgeGateway + ) external; + + /** + * @notice Adds a validator to the allowlist + * @param validator Address of the validator to add + */ + function addValidatorToAllowlist( + address validator + ) external; + + /** + * @notice Adds a BSP to the allowlist + * @param bsp Address of the BSP to add + */ + function addBspToAllowlist( + address bsp + ) external; + + /** + * @notice Adds an MSP to the allowlist + * @param msp Address of the MSP to add + */ + function addMspToAllowlist( + address msp + ) external; + + /** + * @notice Removes a validator from the allowlist + * @param validator Address of the validator to remove + */ + function removeValidatorFromAllowlist( + address validator + ) external; + + /** + * @notice Removes a BSP from the allowlist + * @param bsp Address of the BSP to remove + */ + function removeBspFromAllowlist( + address bsp + ) external; + + /** + * @notice Removes an MSP from the allowlist + * @param msp Address of the MSP to remove + */ + function removeMspFromAllowlist( + address msp + ) external; + + /** + * @notice Returns all strategies supported by the DataHaven Validators operator set + * @return An array of strategy contracts that validators can delegate to + */ + function validatorsSupportedStrategies() external view returns (IStrategy[] memory); + + /** + * @notice Removes strategies from the list of supported strategies for DataHaven Validators + * @param _strategies Array of strategy contracts to remove from validators operator set + */ + function removeStrategiesFromValidatorsSupportedStrategies( + IStrategy[] calldata _strategies + ) external; + + /** + * @notice Adds strategies to the list of supported strategies for DataHaven Validators + * @param _strategies Array of strategy contracts to add to validators operator set + */ + function addStrategiesToValidatorsSupportedStrategies( + IStrategy[] calldata _strategies + ) external; + + /** + * @notice Returns all strategies supported by the Backup Storage Providers (BSPs) operator set + * @return An array of strategy contracts that BSPs can delegate to + */ + function bspsSupportedStrategies() external view returns (IStrategy[] memory); + + /** + * @notice Removes strategies from the list of supported strategies for Backup Storage Providers + * @param _strategies Array of strategy contracts to remove from BSPs operator set + */ + function removeStrategiesFromBspsSupportedStrategies( + IStrategy[] calldata _strategies + ) external; + + /** + * @notice Adds strategies to the list of supported strategies for Backup Storage Providers + * @param _strategies Array of strategy contracts to add to BSPs operator set + */ + function addStrategiesToBspsSupportedStrategies( + IStrategy[] calldata _strategies + ) external; + + /** + * @notice Returns all strategies supported by the Main Storage Providers (MSPs) operator set + * @return An array of strategy contracts that MSPs can delegate to + */ + function mspsSupportedStrategies() external view returns (IStrategy[] memory); + + /** + * @notice Removes strategies from the list of supported strategies for Main Storage Providers + * @param _strategies Array of strategy contracts to remove from MSPs operator set + */ + function removeStrategiesFromMspsSupportedStrategies( + IStrategy[] calldata _strategies + ) external; + + /** + * @notice Adds strategies to the list of supported strategies for Main Storage Providers + * @param _strategies Array of strategy contracts to add to MSPs operator set + */ + function addStrategiesToMspsSupportedStrategies( + IStrategy[] calldata _strategies + ) external; +} diff --git a/contracts/src/libraries/DataHavenSnowbridgeMessages.sol b/contracts/src/libraries/DataHavenSnowbridgeMessages.sol new file mode 100644 index 00000000..b3bfb8ad --- /dev/null +++ b/contracts/src/libraries/DataHavenSnowbridgeMessages.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.27; + +// Snowbridge imports +import {ScaleCodec} from "snowbridge/src/utils/ScaleCodec.sol"; + +library DataHavenSnowbridgeMessages { + /** + * @title New Validator Set Snowbridge Message + * @notice A struct representing a new validator set to be sent as a message through Snowbridge. + * This mimics the message format defined in the Snowbridge inbound pallet of the DataHaven + * solochain. + * !IMPORTANT: The fields in this struct are placeholder until we have the actual message format + * ! defined in the DataHaven solochain. + */ + struct NewValidatorSet { + /// @notice The nonce of the message + uint64 nonce; + /// @notice The topic of the message + bytes32 topic; + /// @notice The payload of the message + NewValidatorSetPayload payload; + } + + /** + * @title New Validator Set Snowbridge Message Payload + * @notice A struct representing the payload of a new validator set message. + * !IMPORTANT: The fields in this struct are placeholder until we have the actual message format + * ! defined in the DataHaven solochain. + */ + struct NewValidatorSetPayload { + /// @notice The new validator set. This should be interpreted as the list of + /// validator addresses in the DataHaven network. + bytes32[] newValidatorSet; + } + + /** + * @notice Encodes a new validator set message into a bytes array. + * @param message The new validator set message to encode. + * @return The encoded message. + */ + function scaleEncodeNewValidatorSetMessage( + NewValidatorSet memory message + ) public pure returns (bytes memory) { + return bytes.concat( + ScaleCodec.encodeU64(message.nonce), + message.topic, + scaleEncodeNewValidatorSetMessagePayload(message.payload) + ); + } + + /** + * @notice Encodes a new validator set message payload into a bytes array. + * @param payload The new validator set message payload to encode. + * @return The encoded payload. + */ + function scaleEncodeNewValidatorSetMessagePayload( + NewValidatorSetPayload memory payload + ) public pure returns (bytes memory) { + // Encode all fields into a buffer. + bytes memory accum = hex""; + for (uint256 i = 0; i < payload.newValidatorSet.length; i++) { + accum = bytes.concat(accum, payload.newValidatorSet[i]); + } + // Encode number of validator addresses, followed by encoded validator addresses. + return + bytes.concat(ScaleCodec.checkedEncodeCompactU32(payload.newValidatorSet.length), accum); + } +} diff --git a/contracts/src/middleware/ServiceManagerBase.sol b/contracts/src/middleware/ServiceManagerBase.sol index 0ee6ba58..d7a8e7cb 100644 --- a/contracts/src/middleware/ServiceManagerBase.sol +++ b/contracts/src/middleware/ServiceManagerBase.sol @@ -49,6 +49,7 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage, IAVSRegistrar _disableInitializers(); } + // solhint-disable-next-line func-name-mixedcase function __ServiceManagerBase_init( address initialOwner, address _rewardsInitiator @@ -218,7 +219,7 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage, IAVSRegistrar uint32[] calldata // operatorSetIds ) external virtual { // Always rejects Operator deregistration. - revert("ServiceManagerBase: deregistration not supported"); + revert("ServiceManagerBase: deregistration not supported, we are evil"); } /// @inheritdoc IServiceManager diff --git a/contracts/src/middleware/ServiceManagerBaseStorage.sol b/contracts/src/middleware/ServiceManagerBaseStorage.sol index a90662fb..948eb325 100644 --- a/contracts/src/middleware/ServiceManagerBaseStorage.sol +++ b/contracts/src/middleware/ServiceManagerBaseStorage.sol @@ -58,5 +58,6 @@ abstract contract ServiceManagerBaseStorage is IServiceManager, OwnableUpgradeab } // storage gap for upgradeability + // solhint-disable-next-line var-name-mixedcase uint256[49] private __GAP; } diff --git a/contracts/test/RewardsRegistry.t.sol b/contracts/test/RewardsRegistry.t.sol index 3210b583..744a6381 100644 --- a/contracts/test/RewardsRegistry.t.sol +++ b/contracts/test/RewardsRegistry.t.sol @@ -6,11 +6,11 @@ pragma solidity ^0.8.13; import {Test, console, stdError} from "forge-std/Test.sol"; import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; -import {MockAVSDeployer} from "./utils/MockAVSDeployer.sol"; +import {AVSDeployer} from "./utils/AVSDeployer.sol"; import {RewardsRegistry} from "../src/middleware/RewardsRegistry.sol"; import {IRewardsRegistry, IRewardsRegistryErrors} from "../src/interfaces/IRewardsRegistry.sol"; -contract RewardsRegistryTest is MockAVSDeployer { +contract RewardsRegistryTest is AVSDeployer { address public nonRewardsAgent; address public operatorAddress; diff --git a/contracts/test/ServiceManagerBase.t.sol b/contracts/test/ServiceManagerBase.t.sol index c4eed871..174604bc 100644 --- a/contracts/test/ServiceManagerBase.t.sol +++ b/contracts/test/ServiceManagerBase.t.sol @@ -15,15 +15,14 @@ import { } from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import {ServiceManagerMock} from "./mocks/ServiceManagerMock.sol"; -import {MockAVSDeployer} from "./utils/MockAVSDeployer.sol"; +import {AVSDeployer} from "./utils/AVSDeployer.sol"; import {IServiceManager} from "../src/interfaces/IServiceManager.sol"; import {IServiceManagerUI} from "../src/interfaces/IServiceManagerUI.sol"; import {ServiceManagerBase} from "../src/middleware/ServiceManagerBase.sol"; -contract ServiceManagerBaseTest is MockAVSDeployer { +contract ServiceManagerBaseTest is AVSDeployer { function setUp() public virtual { _deployMockEigenLayerAndAVS(); - _setUpDefaultStrategiesAndMultipliers(); } function beforeTestSetup( @@ -46,17 +45,6 @@ contract ServiceManagerBaseTest is MockAVSDeployer { IServiceManagerUI(address(serviceManager)).updateAVSMetadataURI("https://example.com"); } - function test_createOperatorSetsRevertsIfNoMetadataExists() public { - vm.prank(avsOwner); - vm.expectRevert( - abi.encodeWithSelector(IAllocationManagerErrors.NonexistentAVSMetadata.selector) - ); - - IAllocationManager.CreateSetParams[] memory emptyParams = - new IAllocationManager.CreateSetParams[](0); - ServiceManagerBase(address(serviceManager)).createOperatorSets(emptyParams); - } - function test_createOperatorSetsWithEmptyParams() public { vm.prank(avsOwner); IAllocationManager.CreateSetParams[] memory emptyParams = diff --git a/contracts/test/ServiceManagerRewardsRegistry.t.sol b/contracts/test/ServiceManagerRewardsRegistry.t.sol index 9c0205ed..b3c154ac 100644 --- a/contracts/test/ServiceManagerRewardsRegistry.t.sol +++ b/contracts/test/ServiceManagerRewardsRegistry.t.sol @@ -5,14 +5,16 @@ pragma solidity ^0.8.13; import {Test, console, stdError} from "forge-std/Test.sol"; import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; -import {MockAVSDeployer} from "./utils/MockAVSDeployer.sol"; +import {AVSDeployer} from "./utils/AVSDeployer.sol"; import {RewardsRegistry} from "../src/middleware/RewardsRegistry.sol"; import {IRewardsRegistry, IRewardsRegistryErrors} from "../src/interfaces/IRewardsRegistry.sol"; import {ServiceManagerMock} from "./mocks/ServiceManagerMock.sol"; import {IServiceManager, IServiceManagerErrors} from "../src/interfaces/IServiceManager.sol"; -contract ServiceManagerRewardsRegistryTest is MockAVSDeployer { +contract ServiceManagerRewardsRegistryTest is AVSDeployer { // Test addresses address public operatorAddress; address public nonOperatorAddress; @@ -73,7 +75,7 @@ contract ServiceManagerRewardsRegistryTest is MockAVSDeployer { ); assertEq( - address(serviceManager.getOperatorSetRewardsRegistry(newOperatorSetId)), + address(serviceManager.operatorSetToRewardsRegistry(newOperatorSetId)), address(newRewardsRegistry), "Rewards registry should be set correctly" ); @@ -95,6 +97,12 @@ contract ServiceManagerRewardsRegistryTest is MockAVSDeployer { function test_claimOperatorRewards() public { uint256 initialBalance = operatorAddress.balance; + vm.mockCall( + address(allocationManager), + abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector), + abi.encode(true) + ); + vm.prank(operatorAddress); vm.expectEmit(true, true, true, true); emit RewardsClaimed(operatorAddress, operatorPoints, operatorPoints); @@ -120,6 +128,12 @@ contract ServiceManagerRewardsRegistryTest is MockAVSDeployer { } function test_claimOperatorRewards_AlreadyClaimed() public { + vm.mockCall( + address(allocationManager), + abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector), + abi.encode(true) + ); + // First claim vm.prank(operatorAddress); serviceManager.claimOperatorRewards(operatorSetId, operatorPoints, validProof); @@ -166,6 +180,11 @@ contract ServiceManagerRewardsRegistryTest is MockAVSDeployer { // Claim from first registry uint256 initialBalance = operatorAddress.balance; + vm.mockCall( + address(allocationManager), + abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector), + abi.encode(true) + ); vm.prank(operatorAddress); serviceManager.claimOperatorRewards(operatorSetId, operatorPoints, validProof); diff --git a/contracts/test/SlasherBase.t.sol b/contracts/test/SlasherBase.t.sol index 2238a0f4..d4f7da50 100644 --- a/contracts/test/SlasherBase.t.sol +++ b/contracts/test/SlasherBase.t.sol @@ -14,19 +14,18 @@ import { IAllocationManagerTypes } from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; -import {MockAVSDeployer} from "./utils/MockAVSDeployer.sol"; +import {AVSDeployer} from "./utils/AVSDeployer.sol"; import {IServiceManager} from "../src/interfaces/IServiceManager.sol"; import {ISlasher, ISlasherErrors, ISlasherEvents} from "../src/interfaces/ISlasher.sol"; import {SlasherBase} from "../src/middleware/SlasherBase.sol"; import {SlasherMock} from "./mocks/SlasherBaseMock.sol"; -contract SlasherBaseTest is MockAVSDeployer { +contract SlasherBaseTest is AVSDeployer { SlasherMock public slasherContract; address public nonServiceManagerRole; function setUp() public virtual { _deployMockEigenLayerAndAVS(); - _setUpDefaultStrategiesAndMultipliers(); // Set up roles for testing nonServiceManagerRole = address(0x5678); diff --git a/contracts/test/SnowbridgeIntegration.t.sol b/contracts/test/SnowbridgeIntegration.t.sol index 2993d42a..161bf1d1 100644 --- a/contracts/test/SnowbridgeIntegration.t.sol +++ b/contracts/test/SnowbridgeIntegration.t.sol @@ -5,19 +5,28 @@ pragma solidity ^0.8.13; import {InboundMessageV2} from "snowbridge/src/Types.sol"; import {CommandV2, CommandKind, IGatewayV2} from "snowbridge/src/Types.sol"; -import {CallContractParams} from "snowbridge/src/v2/Types.sol"; +import { + CallContractParams, + Payload, + Message, + MessageKind, + Asset, + AssetKind +} from "snowbridge/src/v2/Types.sol"; import {BeefyVerification} from "snowbridge/src/BeefyVerification.sol"; import {BeefyClient} from "snowbridge/src/BeefyClient.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; import {MerkleUtils} from "../src/libraries/MerkleUtils.sol"; import { IRewardsRegistryEvents, IRewardsRegistryErrors } from "../src/interfaces/IRewardsRegistry.sol"; -import {MockSnowbridgeAndAVSDeployer} from "./utils/MockSnowbridgeAndAVSDeployer.sol"; - +import {SnowbridgeAndAVSDeployer} from "./utils/SnowbridgeAndAVSDeployer.sol"; import "forge-std/Test.sol"; -contract SnowbridgeIntegrationTest is MockSnowbridgeAndAVSDeployer { +contract SnowbridgeIntegrationTest is SnowbridgeAndAVSDeployer { // Storage variables to reduce stack depth uint128[] internal _validatorPoints; address[] internal _validatorAddresses; @@ -27,11 +36,15 @@ contract SnowbridgeIntegrationTest is MockSnowbridgeAndAVSDeployer { _deployMockAllContracts(); } - /** - * - * Constructor Tests * - * - */ + function beforeTestSetup( + bytes4 testSelector + ) public pure returns (bytes[] memory beforeTestCalldata) { + if (testSelector == this.test_sendNewValidatorsSetMessage.selector) { + beforeTestCalldata = new bytes[](1); + beforeTestCalldata[0] = abi.encodeWithSelector(this.setupValidatorsAsOperators.selector); + } + } + function test_constructor() public view { assertEq( rewardsRegistry.rewardsAgent(), @@ -81,6 +94,11 @@ contract SnowbridgeIntegrationTest is MockSnowbridgeAndAVSDeployer { _buildValidatorPointsProof(_validatorAddresses, _validatorPoints, 0); // Claim rewards for the first validator. + vm.mockCall( + address(allocationManager), + abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector), + abi.encode(true) + ); vm.startPrank(_validatorAddresses[0]); vm.expectEmit(address(rewardsRegistry)); emit IRewardsRegistryEvents.RewardsClaimed( @@ -148,6 +166,39 @@ contract SnowbridgeIntegrationTest is MockSnowbridgeAndAVSDeployer { gateway.v2_submit(badUpdateRewardsMessage, messagesProof, beefyProof, rewardAddress); } + function test_sendNewValidatorsSetMessage() public { + // Check that the current validators signed as operators have a registered address for the DataHaven solochain. + address[] memory currentValidators = allocationManager.getMembers( + OperatorSet({avs: address(serviceManager), id: serviceManager.VALIDATORS_SET_ID()}) + ); + for (uint256 i = 0; i < currentValidators.length; i++) { + assertEq( + serviceManager.validatorEthAddressToSolochainAddress(currentValidators[i]), + initialValidators[i], + "Validator should have a registered address for the DataHaven solochain" + ); + } + + // Mock balance for the AVS owner + vm.deal(avsOwner, 1000000 ether); + + // Send the new validator set message to the Snowbridge Gateway + bytes memory message = serviceManager.buildNewValidatorSetMessage(); + Payload memory payload = Payload({ + origin: address(serviceManager), + assets: new Asset[](0), + message: Message({kind: MessageKind.Raw, data: message}), + claimer: bytes(""), + value: 0, + executionFee: 1 ether, + relayerFee: 1 ether + }); + cheats.expectEmit(); + emit IGatewayV2.OutboundMessageAccepted(1, payload); + cheats.prank(avsOwner); + serviceManager.sendNewValidatorSet{value: 2 ether}(1 ether, 1 ether); + } + function _setupValidatorData() internal { // Build validator points and addresses. _validatorPoints = new uint128[](10); diff --git a/contracts/test/VetoableSlasher.t.sol b/contracts/test/VetoableSlasher.t.sol index 3b716bd6..e0325f11 100644 --- a/contracts/test/VetoableSlasher.t.sol +++ b/contracts/test/VetoableSlasher.t.sol @@ -14,7 +14,7 @@ import { IAllocationManagerTypes } from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; -import {MockAVSDeployer} from "./utils/MockAVSDeployer.sol"; +import {AVSDeployer} from "./utils/AVSDeployer.sol"; import {IServiceManager} from "../src/interfaces/IServiceManager.sol"; import {ISlasher, ISlasherErrors, ISlasherEvents} from "../src/interfaces/ISlasher.sol"; import { @@ -26,7 +26,7 @@ import { import {SlasherBase} from "../src/middleware/SlasherBase.sol"; import {VetoableSlasher} from "../src/middleware/VetoableSlasher.sol"; -contract VetoableSlasherTest is MockAVSDeployer { +contract VetoableSlasherTest is AVSDeployer { address public nonServiceManagerRole; address public nonVetoCommittee; @@ -49,7 +49,6 @@ contract VetoableSlasherTest is MockAVSDeployer { function setUp() public virtual { _deployMockEigenLayerAndAVS(); - _setUpDefaultStrategiesAndMultipliers(); // Set up roles for testing nonServiceManagerRole = address(0x5678); diff --git a/contracts/test/mocks/ServiceManagerMock.sol b/contracts/test/mocks/ServiceManagerMock.sol index 106d52a0..63b3d25f 100644 --- a/contracts/test/mocks/ServiceManagerMock.sol +++ b/contracts/test/mocks/ServiceManagerMock.sol @@ -25,7 +25,7 @@ contract ServiceManagerMock is ServiceManagerBase { IAllocationManager __allocationManager ) ServiceManagerBase(__rewardsCoordinator, __permissionController, __allocationManager) {} - function initialize( + function initialise( address initialOwner, address rewardsInitiator ) public virtual initializer { diff --git a/contracts/test/utils/MockAVSDeployer.sol b/contracts/test/utils/AVSDeployer.sol similarity index 70% rename from contracts/test/utils/MockAVSDeployer.sol rename to contracts/test/utils/AVSDeployer.sol index f957e487..df245a2e 100644 --- a/contracts/test/utils/MockAVSDeployer.sol +++ b/contracts/test/utils/AVSDeployer.sol @@ -28,18 +28,18 @@ import {IServiceManager} from "../../src/interfaces/IServiceManager.sol"; import {VetoableSlasher} from "../../src/middleware/VetoableSlasher.sol"; import {IVetoableSlasher} from "../../src/interfaces/IVetoableSlasher.sol"; import {RewardsRegistry} from "../../src/middleware/RewardsRegistry.sol"; +import {DataHavenServiceManager} from "../../src/DataHavenServiceManager.sol"; // Mocks -import {StrategyManagerMock} from "eigenlayer-contracts/src/test/mocks/StrategyManagerMock.sol"; +import {StrategyManager} from "eigenlayer-contracts/src/contracts/core/StrategyManager.sol"; import {RewardsCoordinatorMock} from "../mocks/RewardsCoordinatorMock.sol"; import {PermissionControllerMock} from "../mocks/PermissionControllerMock.sol"; import {EigenPodManagerMock} from "../mocks/EigenPodManagerMock.sol"; import {AllocationManagerMock} from "../mocks/AllocationManagerMock.sol"; -import {DelegationMock} from "../mocks/DelegationMock.sol"; -import {ServiceManagerMock} from "../mocks/ServiceManagerMock.sol"; +import {DelegationManager} from "eigenlayer-contracts/src/contracts/core/DelegationManager.sol"; import "forge-std/Test.sol"; -contract MockAVSDeployer is Test { +contract AVSDeployer is Test { Vm public cheats = Vm(VM_ADDRESS); ProxyAdmin public proxyAdmin; @@ -48,8 +48,8 @@ contract MockAVSDeployer is Test { EmptyContract public emptyContract; // AVS contracts - ServiceManagerMock public serviceManager; - ServiceManagerMock public serviceManagerImplementation; + DataHavenServiceManager public serviceManager; + DataHavenServiceManager public serviceManagerImplementation; VetoableSlasher public vetoableSlasher; RewardsRegistry public rewardsRegistry; @@ -61,12 +61,13 @@ contract MockAVSDeployer is Test { address public mockRewardsAgent = address(uint160(uint256(keccak256("rewardsAgent")))); // EigenLayer contracts - StrategyManagerMock public strategyManagerMock; - DelegationMock public delegationMock; + StrategyManager public strategyManager; + StrategyManager public strategyManagerImplementation; + DelegationManager public delegationManager; + DelegationManager public delegationManagerImplementation; EigenPodManagerMock public eigenPodManagerMock; AllocationManager public allocationManager; AllocationManager public allocationManagerImplementation; - AllocationManagerMock public allocationManagerMock; RewardsCoordinator public rewardsCoordinator; RewardsCoordinator public rewardsCoordinatorImplementation; RewardsCoordinatorMock public rewardsCoordinatorMock; @@ -115,19 +116,14 @@ contract MockAVSDeployer is Test { address[] memory pausers = new address[](1); pausers[0] = pauser; pauserRegistry = new PauserRegistry(pausers, unpauser); - delegationMock = new DelegationMock(); eigenPodManagerMock = new EigenPodManagerMock(pauserRegistry); - allocationManagerMock = new AllocationManagerMock(); permissionControllerMock = new PermissionControllerMock(); rewardsCoordinatorMock = new RewardsCoordinatorMock(); cheats.stopPrank(); - cheats.prank(strategyOwner); - strategyManagerMock = new StrategyManagerMock(delegationMock); + console.log("Mock EigenLayer contracts deployed"); - console.log("EigenLayer contracts deployed"); - - // Deploying proxy contracts for ServiceManager, and AllocationManager. + // Deploying proxy contracts for AllocationManager and StrategyManager. // The `proxyAdmin` contract is set as the admin of the proxy contracts, // which will be later upgraded to the actual implementation. cheats.prank(regularDeployer); @@ -136,13 +132,37 @@ contract MockAVSDeployer is Test { new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") ) ); + strategyManager = StrategyManager( + address( + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") + ) + ); - console.log("Proxy contracts deployed"); + console.log("AllocationManager and StrategyManager proxy contracts deployed"); + // Deploying DelegationManager implementation and its proxy. + cheats.prank(regularDeployer); + delegationManagerImplementation = new DelegationManager( + strategyManager, + eigenPodManagerMock, + allocationManager, + pauserRegistry, + permissionControllerMock, + uint32(10), // MIN_WITHDRAWAL_DELAY_BLOCKS + "v-mock" + ); + cheats.prank(regularDeployer); + delegationManager = DelegationManager( + address( + new TransparentUpgradeableProxy( + address(delegationManagerImplementation), address(proxyAdmin), "" + ) + ) + ); // Deploying AllocationManager implementation and upgrading the proxy. cheats.prank(regularDeployer); allocationManagerImplementation = new AllocationManager( - delegationMock, + delegationManager, pauserRegistry, permissionControllerMock, uint32(7 days), // DEALLOCATION_DELAY @@ -157,14 +177,30 @@ contract MockAVSDeployer is Test { console.log("AllocationManager implementation deployed"); + // Deploying StrategyManager implementation and its proxy. + cheats.prank(regularDeployer); + strategyManagerImplementation = + new StrategyManager(delegationManager, pauserRegistry, "v-mock"); + cheats.prank(proxyAdminOwner); + uint256 allUnpaused = 0; + proxyAdmin.upgradeAndCall( + ITransparentUpgradeableProxy(address(strategyManager)), + address(strategyManagerImplementation), + abi.encodeWithSelector( + StrategyManager.initialize.selector, strategyOwner, strategyOwner, allUnpaused + ) + ); + + console.log("StrategyManager implementation deployed"); + // Deploying RewardsCoordinator implementation and its proxy. // When the proxy is deployed, the `initialize` function is called. cheats.startPrank(regularDeployer); IRewardsCoordinatorTypes.RewardsCoordinatorConstructorParams memory params = IRewardsCoordinatorTypes.RewardsCoordinatorConstructorParams({ - delegationManager: delegationMock, - strategyManager: IStrategyManager(address(strategyManagerMock)), - allocationManager: allocationManagerMock, + delegationManager: delegationManager, + strategyManager: IStrategyManager(address(strategyManager)), + allocationManager: allocationManager, pauserRegistry: pauserRegistry, permissionController: permissionControllerMock, CALCULATION_INTERVAL_SECONDS: CALCULATION_INTERVAL_SECONDS, @@ -195,18 +231,41 @@ contract MockAVSDeployer is Test { console.log("RewardsCoordinator implementation deployed"); + // Set up strategies before deploying the ServiceManager + _setUpDefaultStrategiesAndMultipliers(); + // Deploying ServiceManager implementation and its proxy. // When the proxy is deployed, the `initialize` function is called. cheats.startPrank(regularDeployer); - serviceManagerImplementation = - new ServiceManagerMock(rewardsCoordinator, permissionControllerMock, allocationManager); - serviceManager = ServiceManagerMock( + serviceManagerImplementation = new DataHavenServiceManager( + rewardsCoordinator, permissionControllerMock, allocationManager + ); + + // Create arrays for the three sets of strategies required by DataHavenServiceManager + IStrategy[] memory validatorsStrategies = new IStrategy[](deployedStrategies.length); + IStrategy[] memory bspsStrategies = new IStrategy[](deployedStrategies.length); + IStrategy[] memory mspsStrategies = new IStrategy[](deployedStrategies.length); + + // For testing purposes, we'll use the same strategies for all three sets + for (uint256 i = 0; i < deployedStrategies.length; i++) { + validatorsStrategies[i] = deployedStrategies[i]; + bspsStrategies[i] = deployedStrategies[i]; + mspsStrategies[i] = deployedStrategies[i]; + } + + serviceManager = DataHavenServiceManager( address( new TransparentUpgradeableProxy( address(serviceManagerImplementation), address(proxyAdmin), abi.encodeWithSelector( - ServiceManagerMock.initialize.selector, avsOwner, rewardsInitiator + DataHavenServiceManager.initialise.selector, + avsOwner, + rewardsInitiator, + validatorsStrategies, + bspsStrategies, + mspsStrategies, + address(0) // This deployment does not use Snowbridge ) ) ) @@ -239,7 +298,7 @@ contract MockAVSDeployer is Test { function _setUpDefaultStrategiesAndMultipliers() internal virtual { // Deploy mock tokens to be used for strategies. - vm.startPrank(strategyOwner); + cheats.startPrank(strategyOwner); IERC20 token1 = new ERC20FixedSupply("dog wif hat", "MOCK1", mockTokenInitialSupply, address(this)); IERC20 token2 = @@ -248,9 +307,8 @@ contract MockAVSDeployer is Test { new ERC20FixedSupply("pepe wif avs", "MOCK3", mockTokenInitialSupply, address(this)); // Deploy mock strategies. - strategyImplementation = new StrategyBase( - IStrategyManager(address(strategyManagerMock)), pauserRegistry, "v-mock" - ); + strategyImplementation = + new StrategyBase(IStrategyManager(address(strategyManager)), pauserRegistry, "v-mock"); deployedStrategies.push( StrategyBase( address( @@ -290,15 +348,13 @@ contract MockAVSDeployer is Test { ) ) ); - vm.stopPrank(); + cheats.stopPrank(); deployedStrategies = _sortArrayAsc(deployedStrategies); - vm.startPrank(strategyOwner); - strategyManagerMock.setStrategyWhitelist(deployedStrategies[0], true); - strategyManagerMock.setStrategyWhitelist(deployedStrategies[1], true); - strategyManagerMock.setStrategyWhitelist(deployedStrategies[2], true); - vm.stopPrank(); + cheats.startPrank(strategyOwner); + strategyManager.addStrategiesToDepositWhitelist(deployedStrategies); + cheats.stopPrank(); defaultStrategyAndMultipliers.push( IRewardsCoordinatorTypes.StrategyAndMultiplier( @@ -318,19 +374,18 @@ contract MockAVSDeployer is Test { } function _labelContracts() internal { - vm.label(address(emptyContract), "EmptyContract"); - vm.label(address(proxyAdmin), "ProxyAdmin"); - vm.label(address(pauserRegistry), "PauserRegistry"); - vm.label(address(delegationMock), "DelegationMock"); - vm.label(address(eigenPodManagerMock), "EigenPodManagerMock"); - vm.label(address(strategyManagerMock), "StrategyManagerMock"); - vm.label(address(allocationManagerMock), "AllocationManagerMock"); - vm.label(address(rewardsCoordinatorMock), "RewardsCoordinatorMock"); - vm.label(address(allocationManager), "AllocationManager"); - vm.label(address(allocationManagerImplementation), "AllocationManagerImplementation"); - vm.label(address(serviceManager), "ServiceManager"); - vm.label(address(serviceManagerImplementation), "ServiceManagerImplementation"); - vm.label(address(vetoableSlasher), "VetoableSlasher"); + cheats.label(address(emptyContract), "EmptyContract"); + cheats.label(address(proxyAdmin), "ProxyAdmin"); + cheats.label(address(pauserRegistry), "PauserRegistry"); + cheats.label(address(delegationManager), "DelegationManager"); + cheats.label(address(eigenPodManagerMock), "EigenPodManagerMock"); + cheats.label(address(strategyManager), "StrategyManager"); + cheats.label(address(rewardsCoordinatorMock), "RewardsCoordinatorMock"); + cheats.label(address(allocationManager), "AllocationManager"); + cheats.label(address(allocationManagerImplementation), "AllocationManagerImplementation"); + cheats.label(address(serviceManager), "ServiceManager"); + cheats.label(address(serviceManagerImplementation), "ServiceManagerImplementation"); + cheats.label(address(vetoableSlasher), "VetoableSlasher"); } /// @dev Sort to ensure that the array is in ascending order for strategies @@ -357,4 +412,10 @@ contract MockAVSDeployer is Test { function _incrementBytes32(bytes32 start, uint256 inc) internal pure returns (bytes32) { return bytes32(uint256(start) + inc); } + + function _setERC20Balance(address token, address user, uint256 amount) internal { + // Assumes balanceOf is in slot 0 (standard in OpenZeppelin ERC20) + bytes32 slot = keccak256(abi.encode(user, uint256(0))); + cheats.store(token, slot, bytes32(amount)); + } } diff --git a/contracts/test/utils/MockSnowbridgeAndAVSDeployer.sol b/contracts/test/utils/SnowbridgeAndAVSDeployer.sol similarity index 64% rename from contracts/test/utils/MockSnowbridgeAndAVSDeployer.sol rename to contracts/test/utils/SnowbridgeAndAVSDeployer.sol index 260b5d3b..3d48fef4 100644 --- a/contracts/test/utils/MockSnowbridgeAndAVSDeployer.sol +++ b/contracts/test/utils/SnowbridgeAndAVSDeployer.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.27; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {Gateway} from "snowbridge/src/Gateway.sol"; import {IGatewayV2} from "snowbridge/src/v2/IGateway.sol"; import {GatewayProxy} from "snowbridge/src/GatewayProxy.sol"; @@ -10,7 +11,7 @@ import {Initializer} from "snowbridge/src/Initializer.sol"; import {OperatingMode} from "snowbridge/src/types/Common.sol"; import {ud60x18} from "snowbridge/lib/prb-math/src/UD60x18.sol"; import {BeefyClient} from "snowbridge/src/BeefyClient.sol"; -import {MockAVSDeployer} from "./MockAVSDeployer.sol"; +import {AVSDeployer} from "./AVSDeployer.sol"; import {MerkleUtils} from "../../src/libraries/MerkleUtils.sol"; import {IAllocationManagerTypes} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; @@ -18,7 +19,7 @@ import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy import "forge-std/Test.sol"; -contract MockSnowbridgeAndAVSDeployer is MockAVSDeployer { +contract SnowbridgeAndAVSDeployer is AVSDeployer { // Snowbridge contracts BeefyClient public beefyClient; IGatewayV2 public gateway; @@ -27,7 +28,26 @@ contract MockSnowbridgeAndAVSDeployer is MockAVSDeployer { Agent public rewardsAgent; Agent public wrongAgent; + // The addresses of the validators that are allowed to register to the DataHaven service. + address[] public validatorsAllowlist = [ + 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, // First pre-funded address in anvil + 0x70997970C51812dc3A010C7d01b50e0d17dc79C8, // Second pre-funded address in anvil + 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC, // Third pre-funded address in anvil + 0x90F79bf6EB2c4f870365E785982E1f101E93b906, // Fourth pre-funded address in anvil + 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65, // Fifth pre-funded address in anvil + 0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc, // Sixth pre-funded address in anvil + 0x976EA74026E726554dB657fA54763abd0C3a0aa9, // Seventh pre-funded address in anvil + 0x14dC79964da2C08b23698B3D3cc7Ca32193d9955, // Eighth pre-funded address in anvil + 0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f, // Ninth pre-funded address in anvil + 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720 // Tenth pre-funded address in anvil + ]; + // The addresses of the Backup Storage Providers that are allowed to register to the DataHaven service. + address[] public bspsAllowlist; + // The addresses of the Main Storage Providers that are allowed to register to the DataHaven service. + address[] public mspsAllowlist; + // Snowbridge contracts params + // The addresses of the initial (current) Validators in the DataHaven solochain. bytes32[] public initialValidators = [ keccak256(abi.encodePacked("validator1")), keccak256(abi.encodePacked("validator2")), @@ -40,6 +60,7 @@ contract MockSnowbridgeAndAVSDeployer is MockAVSDeployer { keccak256(abi.encodePacked("validator9")), keccak256(abi.encodePacked("validator10")) ]; + // The addresses of the next Validators in the DataHaven solochain. bytes32[] public nextValidators = [ keccak256(abi.encodePacked("validator11")), keccak256(abi.encodePacked("validator12")), @@ -141,7 +162,7 @@ contract MockSnowbridgeAndAVSDeployer is MockAVSDeployer { cheats.prank(regularDeployer); gateway.v2_createAgent(REWARDS_MESSAGE_ORIGIN); - // Get the agent address after creation + // Get the agent address after creation. address payable agentAddress = payable(gateway.agentOf(REWARDS_MESSAGE_ORIGIN)); rewardsAgent = Agent(agentAddress); @@ -155,11 +176,66 @@ contract MockSnowbridgeAndAVSDeployer is MockAVSDeployer { cheats.prank(regularDeployer); gateway.v2_createAgent(WRONG_MESSAGE_ORIGIN); - // Get the agent address after creation + // Get the agent address after creation. address payable wrongAgentAddress = payable(gateway.agentOf(WRONG_MESSAGE_ORIGIN)); wrongAgent = Agent(wrongAgentAddress); console.log("Wrong agent deployed at", address(wrongAgent)); + + // Set the Snowbridge Gateway address in the DataHaven service. + cheats.prank(avsOwner); + serviceManager.setSnowbridgeGateway(address(gateway)); + } + + function setupValidatorsAsOperators() public { + for (uint256 i = 0; i < validatorsAllowlist.length; i++) { + console.log("Setting up validator %s as operator", validatorsAllowlist[i]); + + // Whitelist the validator in the DataHaven service. + cheats.prank(avsOwner); + serviceManager.addValidatorToAllowlist(validatorsAllowlist[i]); + + cheats.startPrank(validatorsAllowlist[i]); + for (uint256 j = 0; j < deployedStrategies.length; j++) { + console.log( + "Depositing tokens from validator %s into strategy %s", + validatorsAllowlist[i], + address(deployedStrategies[j]) + ); + + // Give the validator some balance in the strategy's linked token. + IERC20 linkedToken = deployedStrategies[j].underlyingToken(); + _setERC20Balance(address(linkedToken), validatorsAllowlist[i], 1000 ether); + + // Stake some of the validator's balance as stake for the strategy. + linkedToken.approve(address(strategyManager), 1000 ether); + strategyManager.depositIntoStrategy(deployedStrategies[j], linkedToken, 1000 ether); + + console.log( + "Staked %s tokens from validator %s into strategy %s", + 1000 ether, + validatorsAllowlist[i], + address(deployedStrategies[j]) + ); + } + + // Register the validator as an operator in EigenLayer. + delegationManager.registerAsOperator(address(0), 0, ""); + + // Register the validator as an operator for the DataHaven service. + uint32[] memory operatorSetIds = new uint32[](1); + operatorSetIds[0] = serviceManager.VALIDATORS_SET_ID(); + IAllocationManagerTypes.RegisterParams memory registerParams = IAllocationManagerTypes + .RegisterParams({ + avs: address(serviceManager), + operatorSetIds: operatorSetIds, + data: abi.encodePacked(initialValidators[i]) + }); + allocationManager.registerForOperatorSets(validatorsAllowlist[i], registerParams); + cheats.stopPrank(); + + console.log("Validator %s setup as operator", validatorsAllowlist[i]); + } } function _buildValidatorSet(