diff --git a/README.md b/README.md index 38519cc3..2569a01d 100644 --- a/README.md +++ b/README.md @@ -260,6 +260,34 @@ docker run --rm --network host -v ./.nodes:/root/.nodes \ | `REGISTRY_ADDRESS` | if not mounted | From `.nodes/avs_deploy.json` | Volume | AVS registrar address | | `FUNDED_KEY` | if not mounted | From `.nodes/deployer` | Volume | Deployer private key | +### Transfer Ownership + +Transfers ownership of the service contracts to new owners. + +```bash +PROXY_OWNER=$(cast wallet new --json | jq -r '.[0].private_key') +PROXY_OWNER_ADDRESS=$(cast wallet addr --private-key "$PROXY_OWNER") +echo "Proxy owner address: $PROXY_OWNER_ADDRESS" +AVS_OWNER=$(cast wallet new --json | jq -r '.[0].private_key') +AVS_OWNER_ADDRESS=$(cast wallet addr --private-key "$AVS_OWNER") +echo "Avs owner address: $AVS_OWNER_ADDRESS" + +# WAVS_SERVICE_MANAGER_ADDRESS=$(jq -r '.addresses.WavsServiceManager' .nodes/avs_deploy.json) + +docker run --rm --network host -v ./.nodes:/root/.nodes \ + --env-file .env \ + wavs-middleware transfer_ownership ${PROXY_OWNER} ${AVS_OWNER} +``` + +| Environment Variable | Required | Default | Source | Description | +| ------------------------------ | --------------------- | ----------------------------- | ------ | ---------------------------------------------- | +| `DEPLOY_ENV` | for non-default value | `LOCAL` | `.env` | Deployment environment (`LOCAL` or `TESTNET`) | +| `RPC_URL` | for non-default value | `http://localhost:8545` | `.env` | RPC URL | +| `WAVS_SERVICE_MANAGER_ADDRESS` | if not mounted | From `.nodes/avs_deploy.json` | volume | Service manager contract address | +| `FUNDED_KEY` | if not mounted | From `.nodes/deployer` | Volume | Deployer private key | +| `PROXY_OWNER` | Yes | - | Params | New owner for proxy admin | +| `AVS_OWNER` | Yes | - | Params | New owner for AVS registrar and stake registry | + ### Delegate to Operator Delegates tokens to an operator. @@ -433,6 +461,32 @@ docker run --rm --network host -v ./.nodes:/root/.nodes \ | `DEPLOY_FILE_MOCK` | for non-default value | `mock` | Command line | File name to store mock deployment | | `CONFIGURE_FILE` | for non-default value | `wavs-mock-config` | Command line | File name to read configuration data | +### 5. Mock Transfer Ownership + +Transfers ownership of the mock service contracts to new owners. + +```bash +PROXY_OWNER=$(cast wallet new --json | jq -r '.[0].private_key') +PROXY_OWNER_ADDRESS=$(cast wallet addr --private-key "$PROXY_OWNER") +echo "Proxy owner address: $PROXY_OWNER_ADDRESS" +AVS_OWNER=$(cast wallet new --json | jq -r '.[0].private_key') +AVS_OWNER_ADDRESS=$(cast wallet addr --private-key "$AVS_OWNER") +echo "Avs owner address: $AVS_OWNER_ADDRESS" + +docker run --rm --network host -v ./.nodes:/root/.nodes \ + --env-file .env \ + wavs-middleware -m mock transfer_ownership ${PROXY_OWNER} ${AVS_OWNER} +``` + +| Environment Variable | Required | Default | Source | Description | +| ------------------------------ | --------------------- | --------------------------- | ------------ | ---------------------------------------------- | +| `DEPLOY_ENV` | for non-default value | `LOCAL` | `.env` | Deployment environment (`LOCAL` or `TESTNET`) | +| `MOCK_RPC_URL` | for non-default value | `http://localhost:8546` | Command line | RPC URL for mock blockchain | +| `MOCK_DEPLOYER_KEY` | if not mounted | From `.nodes/mock-deployer` | Volume | Deployer private key | +| `WAVS_SERVICE_MANAGER_ADDRESS` | if not mounted | From `.nodes/mock.json` | Volume | Service manager contract address | +| `PROXY_OWNER` | Yes | - | Params | New owner for proxy admin | +| `AVS_OWNER` | Yes | - | Params | New owner for AVS registrar and stake registry | + ## Deploy Testnet Same as the local deploy, change `DEPLOY_ENV` to `"TESTNET"` and make sure the `FUNDED_KEY` is actually funded on testnet diff --git a/contracts/script/eigenlayer/bls/WavsListOperators.s.sol b/contracts/script/eigenlayer/bls/WavsListOperators.s.sol index 19cbf3d8..3b3adec6 100644 --- a/contracts/script/eigenlayer/bls/WavsListOperators.s.sol +++ b/contracts/script/eigenlayer/bls/WavsListOperators.s.sol @@ -2,13 +2,8 @@ pragma solidity ^0.8.27; import {Script} from "forge-std/Script.sol"; -import {console} from "forge-std/console.sol"; -import {IStakeRegistry} from "@eigenlayer-middleware/src/interfaces/IStakeRegistry.sol"; -import {IAllocationManager} from "@eigenlayer/contracts/interfaces/IAllocationManager.sol"; -import {OperatorSet} from "@eigenlayer/contracts/libraries/OperatorSetLib.sol"; -import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -import {WavsServiceManager} from "src/eigenlayer/bls/WavsServiceManager.sol"; +import {WavsListOperatorsLib} from "script/eigenlayer/bls/utils/WavsListOperatorsLib.sol"; /** * @title WavsListOperators @@ -17,182 +12,21 @@ import {WavsServiceManager} from "src/eigenlayer/bls/WavsServiceManager.sol"; * @dev This script is used to list the operators for the WAVS service manager. */ contract WavsListOperators is Script { - /** - * @notice The operator info struct. - * @param stakeRegistry The stake registry address. - * @param totalWeight The total weight of the operators. - * @param minimumStake The minimum stake of the operators. - * @param operators The operators. - * @param weights The weights of the operators. - * @param strategies The strategies of the operators. - */ - struct OperatorInfo { - address stakeRegistry; - uint96 totalWeight; - uint96 minimumStake; - address[] operators; - uint96[] weights; - IStakeRegistry.StrategyParams[] strategies; - } - /// @notice The environment variable for the WAVS service manager address. string public constant ENV_SERVICE_MANAGER = "WAVS_SERVICE_MANAGER_ADDRESS"; - WavsServiceManager private serviceManager; - uint256 private _quorumNumerator; - uint256 private _quorumDenominator; + address private _serviceManager; /// @notice The setup function for the script. function setUp() public virtual { - serviceManager = WavsServiceManager(vm.envAddress(ENV_SERVICE_MANAGER)); + _serviceManager = vm.envAddress(ENV_SERVICE_MANAGER); } /// @notice The run function for the script. function run() external { - vm.startBroadcast(); - OperatorInfo memory opInfo = _listOperators(serviceManager.getStakeRegistry()); - _quorumNumerator = serviceManager.quorumNumerator(); - _quorumDenominator = serviceManager.quorumDenominator(); - _writeOperatorListJson(opInfo); - vm.stopBroadcast(); - - console.log("=== List Operators ==="); - console.log("Service Manager Address:", address(serviceManager)); - console.log("Stake Registry Address:", serviceManager.getStakeRegistry()); - console.log("Strategies:"); - for (uint256 i = 0; i < opInfo.strategies.length; ++i) { - console.log( - string.concat( - "Strategy ", - Strings.toString(i), - ": ", - Strings.toHexString(uint160(address(opInfo.strategies[i].strategy)), 20), - " (", - Strings.toString(opInfo.strategies[i].multiplier), - ")" - ) - ); - } - - console.log(" "); // Blank line for separation - console.log("=== Quorum Information ==="); - console.log(string.concat("Total Weight: ", Strings.toString(uint256(opInfo.totalWeight)))); - console.log( - string.concat("Minimum Stake: ", Strings.toString(uint256(opInfo.minimumStake))) - ); - - console.log(" "); // Blank line for separation - console.log("=== Registered Operators ==="); - for (uint256 i = 0; i < opInfo.operators.length; ++i) { - string memory op = string.concat( - "Operator ", - Strings.toString(i + 1), - ": ", - Strings.toHexString(uint160(opInfo.operators[i]), 20) - ); - string memory weight = string.concat("= ", Strings.toString(uint256(opInfo.weights[i]))); - console.log(op, weight); - } - - console.log(" "); // Blank line for separation - console.log("=== Service Manager Quorum Information ==="); - console.log(string.concat("Quorum Numerator: ", Strings.toString(_quorumNumerator))); - console.log(string.concat("Quorum Denominator: ", Strings.toString(_quorumDenominator))); - } - - /** - * @notice The list operators function. - * @param _stakeRegistry The stake registry address. - * @return opInfo The operator info. - */ - function _listOperators( - address _stakeRegistry - ) private view returns (OperatorInfo memory) { - IStakeRegistry stakeRegistry = IStakeRegistry(_stakeRegistry); - - uint96 totalWeight = stakeRegistry.getCurrentTotalStake(0); - - IAllocationManager allocationManager = - IAllocationManager(serviceManager.getAllocationManager()); - OperatorSet memory opSetQuery = OperatorSet({avs: address(serviceManager), id: 0}); - address[] memory operators = allocationManager.getMembers(opSetQuery); - - uint96[] memory weights = new uint96[](operators.length); - for (uint256 i = 0; i < operators.length; ++i) { - weights[i] = stakeRegistry.weightOfOperatorForQuorum(0, operators[i]); - } - - uint256 strategyParamsLength = stakeRegistry.strategyParamsLength(0); - IStakeRegistry.StrategyParams[] memory strategies = - new IStakeRegistry.StrategyParams[](strategyParamsLength); - for (uint256 i = 0; i < strategyParamsLength; ++i) { - strategies[i] = stakeRegistry.strategyParamsByIndex(uint8(0), i); - } - - return OperatorInfo({ - stakeRegistry: address(stakeRegistry), - totalWeight: totalWeight, - minimumStake: stakeRegistry.minimumStakeForQuorum(0), - operators: operators, - weights: weights, - strategies: strategies - }); - } - - /** - * @notice The write operator list JSON function. - * @param opInfo The operator info. - */ - function _writeOperatorListJson( - OperatorInfo memory opInfo - ) internal { - if (!vm.exists("deployments/wavs-bls")) { - vm.createDir("deployments/wavs-bls", true); - } - - string memory json = "{\"serviceManager\":\""; - json = string.concat(json, Strings.toHexString(uint160(address(serviceManager)), 20)); - json = string.concat(json, "\",\"stakeRegistry\":\""); - json = string.concat(json, Strings.toHexString(uint160(opInfo.stakeRegistry), 20)); - json = string.concat(json, "\",\"totalWeight\":\""); - json = string.concat(json, Strings.toString(opInfo.totalWeight)); - json = string.concat(json, "\",\"minimumStake\":\""); - json = string.concat(json, Strings.toString(opInfo.minimumStake)); - json = string.concat(json, "\",\"strategies\":["); - - for (uint256 i = 0; i < opInfo.strategies.length; ++i) { - if (i > 0) { - json = string.concat(json, ","); - } - json = string.concat(json, "{\"strategy\":\""); - json = string.concat( - json, Strings.toHexString(uint160(address(opInfo.strategies[i].strategy)), 20) - ); - json = string.concat(json, "\",\"multiplier\":\""); - json = string.concat(json, Strings.toString(opInfo.strategies[i].multiplier)); - json = string.concat(json, "\"}"); - } - - json = string.concat(json, "],\"operators\":["); - - for (uint256 i = 0; i < opInfo.operators.length; ++i) { - if (i > 0) { - json = string.concat(json, ","); - } - json = string.concat(json, "{\"operator\":\""); - json = string.concat(json, Strings.toHexString(uint160(opInfo.operators[i]), 20)); - json = string.concat(json, "\",\"weight\":\""); - json = string.concat(json, Strings.toString(opInfo.weights[i])); - json = string.concat(json, "\"}"); - } - - json = string.concat(json, "],\"quorumNumerator\":\""); - json = string.concat(json, Strings.toString(_quorumNumerator)); - json = string.concat(json, "\",\"quorumDenominator\":\""); - json = string.concat(json, Strings.toString(_quorumDenominator)); - json = string.concat(json, "\""); - json = string.concat(json, "}"); - - vm.writeFile("deployments/wavs-bls/list_operators.json", json); + address[] memory operators = WavsListOperatorsLib.getOperators(_serviceManager, uint8(0)); + WavsListOperatorsLib.ConfigData memory configData = + WavsListOperatorsLib.getConfigData(_serviceManager, uint8(0), operators); + WavsListOperatorsLib.writeOperatorListJson(configData); } } diff --git a/contracts/script/eigenlayer/bls/WavsMiddlewareDeployer.s.sol b/contracts/script/eigenlayer/bls/WavsMiddlewareDeployer.s.sol index 306af39c..5ee09b44 100644 --- a/contracts/script/eigenlayer/bls/WavsMiddlewareDeployer.s.sol +++ b/contracts/script/eigenlayer/bls/WavsMiddlewareDeployer.s.sol @@ -3,13 +3,14 @@ pragma solidity ^0.8.27; import {Script} from "forge-std/Script.sol"; -import {RegistryCoordinator} from "@eigenlayer-middleware/src/RegistryCoordinator.sol"; import {StakeRegistry} from "@eigenlayer-middleware/src/StakeRegistry.sol"; import {BLSApkRegistry} from "@eigenlayer-middleware/src/BLSApkRegistry.sol"; import {IndexRegistry} from "@eigenlayer-middleware/src/IndexRegistry.sol"; import {SocketRegistry} from "@eigenlayer-middleware/src/SocketRegistry.sol"; import {PauserRegistry} from "@eigenlayer/contracts/permissions/PauserRegistry.sol"; import {IStakeRegistryTypes} from "@eigenlayer-middleware/src/interfaces/IStakeRegistry.sol"; +import {SlashingRegistryCoordinator} from + "@eigenlayer-middleware/src/SlashingRegistryCoordinator.sol"; import {WavsMiddlewareDeploymentLib} from "./utils/WavsMiddlewareDeploymentLib.sol"; import {WavsServiceManager} from "src/eigenlayer/bls/WavsServiceManager.sol"; @@ -100,8 +101,8 @@ contract WavsMiddlewareDeployer is Script { WavsServiceManager wavsServiceManager = WavsServiceManager(wavsMiddlewareDeployment.wavsServiceManager); StakeRegistry stakeRegistry = StakeRegistry(wavsMiddlewareDeployment.stakeRegistry); - RegistryCoordinator registryCoordinator = - RegistryCoordinator(wavsMiddlewareDeployment.registryCoordinator); + SlashingRegistryCoordinator registryCoordinator = + SlashingRegistryCoordinator(wavsMiddlewareDeployment.registryCoordinator); BLSApkRegistry blsApkRegistry = BLSApkRegistry(wavsMiddlewareDeployment.blsApkRegistry); IndexRegistry indexRegistry = IndexRegistry(wavsMiddlewareDeployment.indexRegistry); SocketRegistry socketRegistry = SocketRegistry(wavsMiddlewareDeployment.socketRegistry); @@ -120,10 +121,7 @@ contract WavsMiddlewareDeployer is Script { revert WavsMiddlewareDeployer__StakeRegistryMismatch(); } if ( - address(registryCoordinator.serviceManager()) - != wavsMiddlewareDeployment.wavsServiceManager - || address(registryCoordinator.stakeRegistry()) - != wavsMiddlewareDeployment.stakeRegistry + address(registryCoordinator.stakeRegistry()) != wavsMiddlewareDeployment.stakeRegistry || address(registryCoordinator.blsApkRegistry()) != wavsMiddlewareDeployment.blsApkRegistry || address(registryCoordinator.indexRegistry()) diff --git a/contracts/script/eigenlayer/bls/WavsMirrorDeployer.s.sol b/contracts/script/eigenlayer/bls/WavsMirrorDeployer.s.sol new file mode 100644 index 00000000..45bbb0d5 --- /dev/null +++ b/contracts/script/eigenlayer/bls/WavsMirrorDeployer.s.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.27; + +import {Script} from "forge-std/Script.sol"; + +import {WavsMirrorDeploymentLib} from "./utils/WavsMirrorDeploymentLib.sol"; +import {UpgradeableProxyLib} from "./utils/UpgradeableProxyLib.sol"; +import {WavsListOperatorsLib} from "./utils/WavsListOperatorsLib.sol"; + +/** + * @title WavsMirrorDeployer + * @author Lay3rLabs + * @notice This script deploys the WAVS mirror contracts. + * @dev This script is used to deploy the WAVS mirror contracts. + */ +contract WavsMirrorDeployer is Script { + using UpgradeableProxyLib for address; + + string private constant ENV_SERVICE_MANAGER = "WAVS_SERVICE_MANAGER_ADDRESS"; + string private constant ENV_SOURCE_RPC_URL = "SOURCE_RPC_URL"; + string private constant ENV_MIRROR_RPC_URL = "MIRROR_RPC_URL"; + + WavsListOperatorsLib.ConfigData private configData; + string private sourceRpcUrl; + string private mirrorRpcUrl; + address private _serviceManager; + + /// @notice The setup function for the script. + function setUp() public virtual { + _serviceManager = vm.envAddress(ENV_SERVICE_MANAGER); + sourceRpcUrl = vm.envString(ENV_SOURCE_RPC_URL); + mirrorRpcUrl = vm.envString(ENV_MIRROR_RPC_URL); + } + + /// @notice The run function for the script. + function run() external { + vm.createSelectFork(sourceRpcUrl); + address[] memory operators = WavsListOperatorsLib.getOperators(_serviceManager, uint8(0)); + configData = WavsListOperatorsLib.getConfigData(_serviceManager, uint8(0), operators); + vm.createSelectFork(mirrorRpcUrl); + + vm.startBroadcast(); + address proxyAdmin = UpgradeableProxyLib.deployProxyAdmin(); + + // first deploy (from eigenlayer) + WavsMirrorDeploymentLib.DeploymentData memory wavsMirrorDeployment = + WavsMirrorDeploymentLib.deployContracts(proxyAdmin); + + // WAVS configuration + WavsMirrorDeploymentLib.configureContracts(wavsMirrorDeployment, configData); + vm.stopBroadcast(); + + WavsMirrorDeploymentLib.writeDeploymentJson(wavsMirrorDeployment); + } +} diff --git a/contracts/script/eigenlayer/bls/WavsMirrorListOperators.s.sol b/contracts/script/eigenlayer/bls/WavsMirrorListOperators.s.sol new file mode 100644 index 00000000..1c1ffdeb --- /dev/null +++ b/contracts/script/eigenlayer/bls/WavsMirrorListOperators.s.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.27; + +import {Script} from "forge-std/Script.sol"; + +import {WavsListOperatorsLib} from "script/eigenlayer/bls/utils/WavsListOperatorsLib.sol"; + +/** + * @title WavsMirrorListOperators + * @author Lay3rLabs + * @notice This script lists the operators for the WAVS mirror service manager. + * @dev This script is used to list the operators for the WAVS mirror service manager. + */ +contract WavsMirrorListOperators is Script { + /// @notice The environment variable for the WAVS service manager address. + string public constant ENV_MIRROR_SERVICE_MANAGER = "MIRROR_SERVICE_MANAGER_ADDRESS"; + /// @notice The environment variable for the WAVS source service manager address. + string public constant ENV_SOURCE_SERVICE_MANAGER = "SOURCE_SERVICE_MANAGER_ADDRESS"; + /// @notice The environment variable for the WAVS source RPC URL. + string public constant ENV_SOURCE_RPC_URL = "SOURCE_RPC_URL"; + /// @notice The environment variable for the WAVS mirror RPC URL. + string public constant ENV_MIRROR_RPC_URL = "MIRROR_RPC_URL"; + + address private _mirrorServiceManager; + address private _sourceServiceManager; + string private _sourceRpcUrl; + string private _mirrorRpcUrl; + + /// @notice The setup function for the script. + function setUp() public virtual { + _mirrorServiceManager = vm.envAddress(ENV_MIRROR_SERVICE_MANAGER); + _sourceServiceManager = vm.envAddress(ENV_SOURCE_SERVICE_MANAGER); + _sourceRpcUrl = vm.envString(ENV_SOURCE_RPC_URL); + _mirrorRpcUrl = vm.envString(ENV_MIRROR_RPC_URL); + } + + /// @notice The run function for the script. + function run() external { + vm.createSelectFork(_sourceRpcUrl); + address[] memory operators = + WavsListOperatorsLib.getOperators(_sourceServiceManager, uint8(0)); + + vm.createSelectFork(_mirrorRpcUrl); + WavsListOperatorsLib.ConfigData memory configData = + WavsListOperatorsLib.getConfigData(_mirrorServiceManager, uint8(0), operators); + WavsListOperatorsLib.writeOperatorListJson(configData); + } +} diff --git a/contracts/script/eigenlayer/bls/utils/WavsListOperatorsLib.sol b/contracts/script/eigenlayer/bls/utils/WavsListOperatorsLib.sol new file mode 100644 index 00000000..685bb5e3 --- /dev/null +++ b/contracts/script/eigenlayer/bls/utils/WavsListOperatorsLib.sol @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {Vm} from "forge-std/Vm.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {BN254} from "@eigenlayer-middleware/src/libraries/BN254.sol"; +import {ISlashingRegistryCoordinator} from + "@eigenlayer-middleware/src/interfaces/ISlashingRegistryCoordinator.sol"; +import { + IStakeRegistry, + IStakeRegistryTypes +} from "@eigenlayer-middleware/src/interfaces/IStakeRegistry.sol"; +import {IBLSApkRegistry} from "@eigenlayer-middleware/src/interfaces/IBLSApkRegistry.sol"; +import {ISocketRegistry} from "@eigenlayer-middleware/src/interfaces/ISocketRegistry.sol"; +import {IAllocationManager} from "@eigenlayer/contracts/interfaces/IAllocationManager.sol"; +import {OperatorSet} from "@eigenlayer/contracts/libraries/OperatorSetLib.sol"; +import {IStrategy} from "@eigenlayer/contracts/interfaces/IStrategy.sol"; +import {WavsServiceManager} from "src/eigenlayer/bls/WavsServiceManager.sol"; + +/** + * @title WavsListOperatorsLib + * @author Lay3rLabs + * @notice This library contains the functions for listing the operators for the WAVS service manager. + * @dev This library is used to list the operators for the WAVS service manager. + */ +library WavsListOperatorsLib { + /* solhint-disable gas-struct-packing */ + /** + * @notice The operator data struct. + * @param operator The operator address. + * @param operatorId The operator ID. + * @param pubkey The public key. + * @param pubkeyG2 The G2 public key. + * @param socket The socket information. + * @param stake The stake amount. + */ + struct OperatorData { + address operator; + bytes32 operatorId; + BN254.G1Point pubkey; + BN254.G2Point pubkeyG2; + string socket; + uint96 stake; + } + /* solhint-enable gas-struct-packing */ + + /** + * @notice The config data struct. + * @param totalWeight The total weight. + * @param minimumStake The minimum stake. + * @param strategies The strategy parameters. + * @param operators The operator data. + * @param quorumNumerator The quorum numerator. + * @param quorumDenominator The quorum denominator. + */ + struct ConfigData { + uint96 totalWeight; + uint96 minimumStake; + IStakeRegistry.StrategyParams[] strategies; + OperatorData[] operators; + uint256 quorumNumerator; + uint256 quorumDenominator; + } + + Vm internal constant VM = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + /// @notice The error for the config file not found. + error WavsListOperatorsLib__ConfigFileNotFound(); + + /** + * @notice The get config data function. + * @param _serviceManager The service manager address. + * @param _quorumNumber The quorum number. + * @param _operators The operators. + * @return configData The config data. + */ + function getConfigData( + address _serviceManager, + uint8 _quorumNumber, + address[] memory _operators + ) internal view returns (ConfigData memory configData) { + WavsServiceManager serviceManager = WavsServiceManager(_serviceManager); + configData.quorumNumerator = serviceManager.quorumNumerator(); + configData.quorumDenominator = serviceManager.quorumDenominator(); + + ISlashingRegistryCoordinator slashingRegistryCoordinator = + ISlashingRegistryCoordinator(serviceManager.getRegistryCoordinator()); + IStakeRegistry stakeRegistry = slashingRegistryCoordinator.stakeRegistry(); + ISocketRegistry socketRegistry = slashingRegistryCoordinator.socketRegistry(); + IBLSApkRegistry blsApkRegistry = slashingRegistryCoordinator.blsApkRegistry(); + + configData.totalWeight = stakeRegistry.getCurrentTotalStake(_quorumNumber); + configData.minimumStake = stakeRegistry.minimumStakeForQuorum(_quorumNumber); + + uint256 operatorCount = _operators.length; + + OperatorData[] memory operatorData = new OperatorData[](operatorCount); + + for (uint256 i = 0; i < operatorCount; ++i) { + (BN254.G1Point memory pubkey, bytes32 operatorId) = + blsApkRegistry.getRegisteredPubkey(_operators[i]); + operatorData[i] = OperatorData({ + operator: _operators[i], + operatorId: operatorId, + pubkey: pubkey, + pubkeyG2: blsApkRegistry.getOperatorPubkeyG2(_operators[i]), + socket: socketRegistry.getOperatorSocket(operatorId), + stake: stakeRegistry.getCurrentStake(operatorId, _quorumNumber) + }); + } + + uint256 strategyParamsLength = stakeRegistry.strategyParamsLength(_quorumNumber); + IStakeRegistry.StrategyParams[] memory strategies = + new IStakeRegistry.StrategyParams[](strategyParamsLength); + for (uint256 i = 0; i < strategyParamsLength; ++i) { + strategies[i] = stakeRegistry.strategyParamsByIndex(_quorumNumber, i); + } + + configData.strategies = strategies; + configData.operators = operatorData; + + return configData; + } + + /** + * @notice The get operators function. + * @param _serviceManager The service manager address. + * @param _quorumNumber The quorum number. + * @return operators The operators. + */ + function getOperators( + address _serviceManager, + uint8 _quorumNumber + ) internal view returns (address[] memory) { + WavsServiceManager serviceManager = WavsServiceManager(_serviceManager); + + IAllocationManager allocationManager = + IAllocationManager(serviceManager.getAllocationManager()); + OperatorSet memory opSetQuery = + OperatorSet({avs: address(serviceManager), id: _quorumNumber}); + address[] memory operators = allocationManager.getMembers(opSetQuery); + + return operators; + } + + /** + * @notice The read config function. + * @param fileName The file name. + * @return configData The config data. + */ + function readConfig( + string memory fileName + ) internal returns (WavsListOperatorsLib.ConfigData memory) { + if (!VM.exists(fileName)) { + revert WavsListOperatorsLib__ConfigFileNotFound(); + } + + // load the complete config + string memory json = VM.readFile(fileName); + + // Parse basic config data + uint96 minimumStake = abi.decode(VM.parseJson(json, ".minimumStake"), (uint96)); + uint96 totalWeight = abi.decode(VM.parseJson(json, ".totalWeight"), (uint96)); + uint256 quorumNumerator = abi.decode(VM.parseJson(json, ".quorumNumerator"), (uint256)); + uint256 quorumDenominator = abi.decode(VM.parseJson(json, ".quorumDenominator"), (uint256)); + + // Parse the strategies array from the JSON + address[] memory strategies = + abi.decode(VM.parseJson(json, ".strategies[*].strategy"), (address[])); + uint256 strategyCount = strategies.length; + uint96[] memory multipliers = + abi.decode(VM.parseJson(json, ".strategies[*].multiplier"), (uint96[])); + + // Convert to strategy params + IStakeRegistryTypes.StrategyParams[] memory strategyParams = + new IStakeRegistryTypes.StrategyParams[](strategyCount); + for (uint256 i; i < strategyCount; ++i) { + strategyParams[i] = IStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(strategies[i]), + multiplier: multipliers[i] + }); + } + + // Parse operators data + address[] memory operatorAddresses = + abi.decode(VM.parseJson(json, ".operators[*].operator"), (address[])); + bytes32[] memory operatorIds = + abi.decode(VM.parseJson(json, ".operators[*].operatorId"), (bytes32[])); + uint256[] memory pubkeyXs = + abi.decode(VM.parseJson(json, ".operators[*].pubkey.x"), (uint256[])); + uint256[] memory pubkeyYs = + abi.decode(VM.parseJson(json, ".operators[*].pubkey.y"), (uint256[])); + string[] memory sockets = abi.decode(VM.parseJson(json, ".operators[*].socket"), (string[])); + uint96[] memory stakes = abi.decode(VM.parseJson(json, ".operators[*].stake"), (uint96[])); + + // Parse G2 public keys (arrays of 2 uint256s) + uint256[2][] memory pubkeyG2Xs = + abi.decode(VM.parseJson(json, ".operators[*].pubkeyG2.x"), (uint256[2][])); + uint256[2][] memory pubkeyG2Ys = + abi.decode(VM.parseJson(json, ".operators[*].pubkeyG2.y"), (uint256[2][])); + + uint256 operatorCount = operatorAddresses.length; + + // Convert to operator data + OperatorData[] memory operators = new OperatorData[](operatorCount); + for (uint256 i; i < operatorCount; ++i) { + operators[i] = OperatorData({ + operator: operatorAddresses[i], + operatorId: operatorIds[i], + pubkey: BN254.G1Point({X: pubkeyXs[i], Y: pubkeyYs[i]}), + pubkeyG2: BN254.G2Point({X: pubkeyG2Xs[i], Y: pubkeyG2Ys[i]}), + socket: sockets[i], + stake: stakes[i] + }); + } + + return ConfigData({ + totalWeight: totalWeight, + minimumStake: minimumStake, + strategies: strategyParams, + operators: operators, + quorumNumerator: quorumNumerator, + quorumDenominator: quorumDenominator + }); + } + + /** + * @notice The write operator list JSON function. + * @param configData The config data. + */ + function writeOperatorListJson( + ConfigData memory configData + ) internal { + if (!VM.exists("deployments/wavs-bls")) { + VM.createDir("deployments/wavs-bls", true); + } + + string memory json = "{\"totalWeight\":\""; + json = string.concat(json, Strings.toString(configData.totalWeight)); + json = string.concat(json, "\",\"minimumStake\":\""); + json = string.concat(json, Strings.toString(configData.minimumStake)); + json = string.concat(json, "\",\"strategies\":["); + + for (uint256 i = 0; i < configData.strategies.length; ++i) { + if (i > 0) { + json = string.concat(json, ","); + } + json = string.concat(json, "{\"strategy\":\""); + json = string.concat( + json, Strings.toHexString(uint160(address(configData.strategies[i].strategy)), 20) + ); + json = string.concat(json, "\",\"multiplier\":\""); + json = string.concat(json, Strings.toString(configData.strategies[i].multiplier)); + json = string.concat(json, "\"}"); + } + + json = string.concat(json, "],\"operators\":["); + + for (uint256 i = 0; i < configData.operators.length; ++i) { + if (i > 0) { + json = string.concat(json, ","); + } + json = string.concat(json, "{\"operator\":\""); + json = string.concat( + json, Strings.toHexString(uint160(configData.operators[i].operator), 20) + ); + json = string.concat(json, "\",\"operatorId\":\""); + json = string.concat( + json, Strings.toHexString(uint256(configData.operators[i].operatorId), 32) + ); + json = string.concat(json, "\",\"pubkey\":{"); + json = string.concat(json, "\"x\":\""); + json = string.concat( + json, Strings.toHexString(uint256(configData.operators[i].pubkey.X), 32) + ); + json = string.concat(json, "\",\"y\":\""); + json = string.concat( + json, Strings.toHexString(uint256(configData.operators[i].pubkey.Y), 32) + ); + json = string.concat(json, "\"},"); + json = string.concat(json, "\"pubkeyG2\":{"); + json = string.concat(json, "\"x\":[\""); + json = string.concat( + json, Strings.toHexString(uint256(configData.operators[i].pubkeyG2.X[0]), 32) + ); + json = string.concat( + json, + "\",\"", + Strings.toHexString(uint256(configData.operators[i].pubkeyG2.X[1]), 32) + ); + json = string.concat(json, "\"],"); + json = string.concat(json, "\"y\":[\""); + json = string.concat( + json, Strings.toHexString(uint256(configData.operators[i].pubkeyG2.Y[0]), 32) + ); + json = string.concat( + json, + "\",\"", + Strings.toHexString(uint256(configData.operators[i].pubkeyG2.Y[1]), 32) + ); + json = string.concat(json, "\"]},"); + json = string.concat(json, "\"socket\":\""); + json = string.concat(json, configData.operators[i].socket); + json = string.concat(json, "\",\"stake\":\""); + json = string.concat(json, Strings.toString(configData.operators[i].stake)); + json = string.concat(json, "\"}"); + } + + json = string.concat(json, "],\"quorumNumerator\":\""); + json = string.concat(json, Strings.toString(configData.quorumNumerator)); + json = string.concat(json, "\",\"quorumDenominator\":\""); + json = string.concat(json, Strings.toString(configData.quorumDenominator)); + json = string.concat(json, "\""); + json = string.concat(json, "}"); + + VM.writeFile("deployments/wavs-bls/list_operators.json", json); + } +} diff --git a/contracts/script/eigenlayer/bls/utils/WavsMiddlewareDeploymentLib.sol b/contracts/script/eigenlayer/bls/utils/WavsMiddlewareDeploymentLib.sol index c9137b8c..902b8778 100644 --- a/contracts/script/eigenlayer/bls/utils/WavsMiddlewareDeploymentLib.sol +++ b/contracts/script/eigenlayer/bls/utils/WavsMiddlewareDeploymentLib.sol @@ -9,7 +9,6 @@ import {StakeRegistry} from "@eigenlayer-middleware/src/StakeRegistry.sol"; import {BLSApkRegistry} from "@eigenlayer-middleware/src/BLSApkRegistry.sol"; import {IndexRegistry} from "@eigenlayer-middleware/src/IndexRegistry.sol"; import {SocketRegistry} from "@eigenlayer-middleware/src/SocketRegistry.sol"; -import {RegistryCoordinator} from "@eigenlayer-middleware/src/RegistryCoordinator.sol"; import {SlashingRegistryCoordinator} from "@eigenlayer-middleware/src/SlashingRegistryCoordinator.sol"; import {InstantSlasher} from "@eigenlayer-middleware/src/slashers/InstantSlasher.sol"; @@ -22,9 +21,6 @@ import { import {IBLSApkRegistry} from "@eigenlayer-middleware/src/interfaces/IBLSApkRegistry.sol"; import {IIndexRegistry} from "@eigenlayer-middleware/src/interfaces/IIndexRegistry.sol"; import {ISocketRegistry} from "@eigenlayer-middleware/src/interfaces/ISocketRegistry.sol"; -import {IServiceManager} from "@eigenlayer-middleware/src/interfaces/IServiceManager.sol"; -import {IRegistryCoordinatorTypes} from - "@eigenlayer-middleware/src/interfaces/IRegistryCoordinator.sol"; import { ISlashingRegistryCoordinator, ISlashingRegistryCoordinatorTypes @@ -76,24 +72,10 @@ library WavsMiddlewareDeploymentLib { address operatorStateRetriever; } - /** - * @notice The strategy config struct. - * @param strategy The strategy address. - * @param multiplier The multiplier. - */ - struct StrategyConfig { - address strategy; - uint96 multiplier; - } - Vm internal constant VM = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); /// @notice The error for the strategies file not found. error WavsMiddlewareDeploymentLib__StrategiesFileNotFound(); - /// @notice The error for the deployment file not found. - error WavsMiddlewareDeploymentLib__DeploymentFileNotFound(); - /// @notice The error for the AVS directory mismatch. - error WavsMiddlewareDeploymentLib__AVSDirectoryMismatch(); /** * @notice The deploy contracts function. @@ -137,19 +119,15 @@ library WavsMiddlewareDeploymentLib { IAllocationManager(core.allocationManager) ) ); - address registryCoordinatorImpl = address( - new RegistryCoordinator( - IRegistryCoordinatorTypes.RegistryCoordinatorParams({ - serviceManager: IServiceManager(wavsServiceManager), - slashingParams: IRegistryCoordinatorTypes.SlashingRegistryParams({ - stakeRegistry: IStakeRegistry(stakeRegistry), - blsApkRegistry: IBLSApkRegistry(blsApkRegistry), - indexRegistry: IIndexRegistry(indexRegistry), - socketRegistry: ISocketRegistry(socketRegistry), - allocationManager: IAllocationManager(core.allocationManager), - pauserRegistry: IPauserRegistry(pauserRegistry) - }) - }) + address slashingRegistryCoordinatorImpl = address( + new SlashingRegistryCoordinator( + IStakeRegistry(stakeRegistry), + IBLSApkRegistry(blsApkRegistry), + IIndexRegistry(indexRegistry), + ISocketRegistry(socketRegistry), + IAllocationManager(core.allocationManager), + IPauserRegistry(pauserRegistry), + "1.0.0" ) ); @@ -175,7 +153,7 @@ library WavsMiddlewareDeploymentLib { UpgradeableProxyLib.upgrade(stakeRegistry, stakeRegistryImpl); UpgradeableProxyLib.upgradeAndCall( registryCoordinator, - registryCoordinatorImpl, + slashingRegistryCoordinatorImpl, abi.encodeCall( SlashingRegistryCoordinator.initialize, (msg.sender, msg.sender, msg.sender, 0, wavsServiceManager) diff --git a/contracts/script/eigenlayer/bls/utils/WavsMirrorDeploymentLib.sol b/contracts/script/eigenlayer/bls/utils/WavsMirrorDeploymentLib.sol new file mode 100644 index 00000000..45f65bd9 --- /dev/null +++ b/contracts/script/eigenlayer/bls/utils/WavsMirrorDeploymentLib.sol @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.27; + +import {console2} from "forge-std/Test.sol"; +import {Vm} from "forge-std/Vm.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +import {IndexRegistry} from "@eigenlayer-middleware/src/IndexRegistry.sol"; +import {SocketRegistry} from "@eigenlayer-middleware/src/SocketRegistry.sol"; +import {SlashingRegistryCoordinator} from + "@eigenlayer-middleware/src/SlashingRegistryCoordinator.sol"; + +import {IStakeRegistry} from "@eigenlayer-middleware/src/interfaces/IStakeRegistry.sol"; +import { + IBLSApkRegistry, + IBLSApkRegistryTypes +} from "@eigenlayer-middleware/src/interfaces/IBLSApkRegistry.sol"; +import {IIndexRegistry} from "@eigenlayer-middleware/src/interfaces/IIndexRegistry.sol"; +import {ISocketRegistry} from "@eigenlayer-middleware/src/interfaces/ISocketRegistry.sol"; +import { + ISlashingRegistryCoordinator, + ISlashingRegistryCoordinatorTypes +} from "@eigenlayer-middleware/src/interfaces/ISlashingRegistryCoordinator.sol"; + +import {PauserRegistry} from "@eigenlayer/contracts/permissions/PauserRegistry.sol"; +import {IPauserRegistry} from "@eigenlayer/contracts/interfaces/IPauserRegistry.sol"; +import {IAllocationManager} from "@eigenlayer/contracts/interfaces/IAllocationManager.sol"; +import {IAVSDirectory} from "@eigenlayer/contracts/interfaces/IAVSDirectory.sol"; +import {IDelegationManager} from "@eigenlayer/contracts/interfaces/IDelegationManager.sol"; + +import {UpgradeableProxyLib} from "./UpgradeableProxyLib.sol"; +import {WavsServiceManager} from "src/eigenlayer/bls/WavsServiceManager.sol"; +import {MirrorStakeRegistry} from "src/eigenlayer/bls/mirror/MirrorStakeRegistry.sol"; +import {MirrorSlashingRegistryCoordinator} from + "src/eigenlayer/bls/mirror/MirrorSlashingRegistryCoordinator.sol"; +import {MirrorBLSApkRegistry} from "src/eigenlayer/bls/mirror/MirrorBLSApkRegistry.sol"; +import {WavsListOperatorsLib} from "./WavsListOperatorsLib.sol"; +import {IMirrorSlashingRegistryCoordinator} from + "src/eigenlayer/bls/interfaces/IMirrorSlashingRegistryCoordinator.sol"; +import {IWavsServiceManager} from "src/eigenlayer/bls/interfaces/IWavsServiceManager.sol"; + +/** + * @title WavsMiddlewareDeploymentLib + * @author Lay3rLabs + * @notice This library contains functions for deploying the WAVS middleware contracts. + * @dev This library is used to deploy the WAVS middleware contracts. + */ +library WavsMirrorDeploymentLib { + // using stdJson for *; + using Strings for *; + using UpgradeableProxyLib for address; + + /** + * @notice The deployment data struct. + * @param wavsServiceManager The WAVS service manager address. + * @param stakeRegistry The stake registry address. + * @param slashingRegistryCoordinator The mirror slashing registry coordinator address. + * @param blsApkRegistry The BLS APK registry address. + * @param indexRegistry The index registry address. + * @param socketRegistry The socket registry address. + * @param pauserRegistry The pauser registry address. + */ + struct DeploymentData { + address wavsServiceManager; + address stakeRegistry; + address slashingRegistryCoordinator; + address blsApkRegistry; + address indexRegistry; + address socketRegistry; + address pauserRegistry; + } + + Vm internal constant VM = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + /** + * @notice The deploy contracts function. + * @param proxyAdmin The proxy admin address. + * @return deployment The deployment data. + */ + function deployContracts( + address proxyAdmin + ) internal returns (DeploymentData memory) { + // First, deploy upgradeable proxy contracts that will point to the implementations. + address wavsServiceManager = UpgradeableProxyLib.setUpEmptyProxy(proxyAdmin); + address stakeRegistry = UpgradeableProxyLib.setUpEmptyProxy(proxyAdmin); + address slashingRegistryCoordinator = UpgradeableProxyLib.setUpEmptyProxy(proxyAdmin); + address blsApkRegistry = UpgradeableProxyLib.setUpEmptyProxy(proxyAdmin); + address indexRegistry = UpgradeableProxyLib.setUpEmptyProxy(proxyAdmin); + address socketRegistry = UpgradeableProxyLib.setUpEmptyProxy(proxyAdmin); + + address[] memory pausers = new address[](1); + pausers[0] = msg.sender; + address pauserRegistry = address(new PauserRegistry(pausers, msg.sender)); + + address wavsServiceManagerImpl = address( + new WavsServiceManager( + address(0), + address(0), + slashingRegistryCoordinator, + stakeRegistry, + address(0), + address(0) + ) + ); + address stakeRegistryImpl = address( + new MirrorStakeRegistry( + ISlashingRegistryCoordinator(slashingRegistryCoordinator), + IDelegationManager(address(0)), + IAVSDirectory(address(0)), + IAllocationManager(address(0)) + ) + ); + address slashingRegistryCoordinatorImpl = address( + new MirrorSlashingRegistryCoordinator( + IStakeRegistry(stakeRegistry), + IBLSApkRegistry(blsApkRegistry), + IIndexRegistry(indexRegistry), + ISocketRegistry(socketRegistry), + IAllocationManager(msg.sender), + IPauserRegistry(pauserRegistry), + "1.0.0" + ) + ); + + address blsApkRegistryImpl = address( + new MirrorBLSApkRegistry(ISlashingRegistryCoordinator(slashingRegistryCoordinator)) + ); + address indexRegistryImpl = + address(new IndexRegistry(ISlashingRegistryCoordinator(slashingRegistryCoordinator))); + address socketRegistryImpl = + address(new SocketRegistry(ISlashingRegistryCoordinator(slashingRegistryCoordinator))); + + UpgradeableProxyLib.upgradeAndCall( + wavsServiceManager, + wavsServiceManagerImpl, + abi.encodeCall(WavsServiceManager.initialize, (msg.sender, msg.sender)) + ); + UpgradeableProxyLib.upgrade(stakeRegistry, stakeRegistryImpl); + UpgradeableProxyLib.upgradeAndCall( + slashingRegistryCoordinator, + slashingRegistryCoordinatorImpl, + abi.encodeCall( + SlashingRegistryCoordinator.initialize, + (msg.sender, msg.sender, msg.sender, 0, wavsServiceManager) + ) + ); + UpgradeableProxyLib.upgrade(blsApkRegistry, blsApkRegistryImpl); + UpgradeableProxyLib.upgrade(indexRegistry, indexRegistryImpl); + UpgradeableProxyLib.upgrade(socketRegistry, socketRegistryImpl); + + return DeploymentData({ + wavsServiceManager: wavsServiceManager, + stakeRegistry: stakeRegistry, + slashingRegistryCoordinator: slashingRegistryCoordinator, + blsApkRegistry: blsApkRegistry, + indexRegistry: indexRegistry, + socketRegistry: socketRegistry, + pauserRegistry: pauserRegistry + }); + } + + /** + * @notice The configure contracts function. + * @param deployment The deployment data. + * @param configData The config data. + */ + function configureContracts( + DeploymentData memory deployment, + WavsListOperatorsLib.ConfigData memory configData + ) internal { + uint32 lookAheadPeriod = 0; + ISlashingRegistryCoordinator slashingRegistryCoordinator = + ISlashingRegistryCoordinator(deployment.slashingRegistryCoordinator); + slashingRegistryCoordinator.createSlashableStakeQuorum( + ISlashingRegistryCoordinatorTypes.OperatorSetParam({ + maxOperatorCount: 10_000, + kickBIPsOfOperatorStake: 10_500, + kickBIPsOfTotalStake: 100 + }), + configData.minimumStake, + configData.strategies, + lookAheadPeriod + ); + + uint32[] memory opSetIds = new uint32[](1); + opSetIds[0] = 0; + + uint256 operatorCount = configData.operators.length; + for (uint256 i = 0; i < operatorCount; ++i) { + bytes memory data = abi.encode( + ISlashingRegistryCoordinatorTypes.RegistrationType.NORMAL, + configData.operators[i].socket, + IBLSApkRegistryTypes.PubkeyRegistrationParams({ + pubkeyRegistrationSignature: configData.operators[i].pubkey, + pubkeyG1: configData.operators[i].pubkey, + pubkeyG2: configData.operators[i].pubkeyG2 + }) + ); + IMirrorSlashingRegistryCoordinator(deployment.slashingRegistryCoordinator) + .registerOperatorForMirror( + configData.operators[i].operator, + deployment.wavsServiceManager, + opSetIds, + configData.operators[i].stake, + data + ); + } + + IWavsServiceManager(deployment.wavsServiceManager).setQuorumThreshold( + configData.quorumNumerator, configData.quorumDenominator + ); + } + + /** + * @notice The write deployment JSON function. + * @param data The deployment data. + */ + function writeDeploymentJson( + DeploymentData memory data + ) internal { + address proxyAdmin = address(UpgradeableProxyLib.getProxyAdmin(data.wavsServiceManager)); + + string memory deploymentData = _generateDeploymentJson(data, proxyAdmin); + + if (!VM.exists("deployments/wavs-bls")) { + VM.createDir("deployments/wavs-bls", true); + } + + VM.writeFile("deployments/wavs-bls/mirror_deploy.json", deploymentData); + console2.log("Deployment artifacts written to: deployments/wavs-bls/mirror_deploy.json"); + } + + /** + * @notice The generate deployment JSON function. + * @param data The deployment data. + * @param proxyAdmin The proxy admin address. + * @return deploymentData The deployment JSON. + */ + function _generateDeploymentJson( + DeploymentData memory data, + address proxyAdmin + ) private view returns (string memory) { + return string.concat( + "{", + "\"lastUpdate\":{", + "\"timestamp\":\"", + VM.toString(block.timestamp), + "\",", + "\"block_number\":\"", + VM.toString(block.number), + "\"", + "},", + "\"addresses\":", + _generateContractsJson(data, proxyAdmin), + "}" + ); + } + + /** + * @notice The generate contracts JSON function. + * @param data The deployment data. + * @param proxyAdmin The proxy admin address. + * @return contractsJson The contracts JSON. + */ + function _generateContractsJson( + DeploymentData memory data, + address proxyAdmin + ) private view returns (string memory) { + return string.concat( + "{\"proxyAdmin\":\"", + proxyAdmin.toHexString(), + "\",\"WavsServiceManager\":\"", + data.wavsServiceManager.toHexString(), + "\",\"WavsServiceManagerImpl\":\"", + data.wavsServiceManager.getImplementation().toHexString(), + "\",\"stakeRegistry\":\"", + data.stakeRegistry.toHexString(), + "\",\"stakeRegistryImpl\":\"", + data.stakeRegistry.getImplementation().toHexString(), + "\",\"slashingRegistryCoordinator\":\"", + data.slashingRegistryCoordinator.toHexString(), + "\",\"slashingRegistryCoordinatorImpl\":\"", + data.slashingRegistryCoordinator.getImplementation().toHexString(), + "\",\"blsApkRegistry\":\"", + data.blsApkRegistry.toHexString(), + "\",\"blsApkRegistryImpl\":\"", + data.blsApkRegistry.getImplementation().toHexString(), + "\",\"indexRegistry\":\"", + data.indexRegistry.toHexString(), + "\",\"indexRegistryImpl\":\"", + data.indexRegistry.getImplementation().toHexString(), + "\",\"socketRegistry\":\"", + data.socketRegistry.toHexString(), + "\",\"socketRegistryImpl\":\"", + data.socketRegistry.getImplementation().toHexString(), + "\",\"pauserRegistry\":\"", + data.pauserRegistry.toHexString(), + "\"}" + ); + } +} diff --git a/contracts/script/eigenlayer/ecdsa/WavsListOperators.s.sol b/contracts/script/eigenlayer/ecdsa/WavsListOperators.s.sol index f7b366e5..82599af5 100644 --- a/contracts/script/eigenlayer/ecdsa/WavsListOperators.s.sol +++ b/contracts/script/eigenlayer/ecdsa/WavsListOperators.s.sol @@ -47,11 +47,10 @@ contract WavsListOperators is Script { /// @notice The run function for the script. function run() external { - vm.startBroadcast(); OperatorInfo memory opInfo = listOperators(); uint256 quorumNumerator = serviceManager.quorumNumerator(); uint256 quorumDenominator = serviceManager.quorumDenominator(); - vm.stopBroadcast(); + _writeOperatorListJson(opInfo); console.log("=== List Operators ==="); console.log("Service Manager Address:", address(serviceManager)); @@ -124,4 +123,42 @@ contract WavsListOperators is Script { weights: weights }); } + + /** + * @notice The write operator list JSON function. + * @param opInfo The operator info. + */ + function _writeOperatorListJson( + OperatorInfo memory opInfo + ) private { + if (!vm.exists("deployments/wavs-ecdsa")) { + vm.createDir("deployments/wavs-ecdsa", true); + } + + string memory json = string.concat("{"); + json = string.concat(json, "\"stakeRegistry\":\""); + json = string.concat(json, Strings.toHexString(uint160(address(opInfo.stakeRegistry)), 20)); + json = string.concat(json, "\",\"totalWeight\":\""); + json = string.concat(json, Strings.toString(opInfo.totalWeight)); + json = string.concat(json, "\",\"thresholdWeight\":\""); + json = string.concat(json, Strings.toString(opInfo.thresholdWeight)); + json = string.concat(json, "\",\"operators\":["); + for (uint256 i = 0; i < opInfo.operators.length; ++i) { + json = string.concat(json, "{\"operator\":\""); + json = string.concat(json, Strings.toHexString(uint160(opInfo.operators[i]), 20)); + json = string.concat(json, "\",\"signingKeyAddress\":\""); + json = + string.concat(json, Strings.toHexString(uint160(opInfo.signingKeyAddresses[i]), 20)); + json = string.concat(json, "\",\"weight\":\""); + json = string.concat(json, Strings.toString(opInfo.weights[i])); + json = string.concat(json, "\"}"); + if (i < opInfo.operators.length - 1) { + json = string.concat(json, ","); + } + } + json = string.concat(json, "]"); + json = string.concat(json, "}"); + + vm.writeFile("deployments/wavs-ecdsa/list_operators.json", json); + } } diff --git a/contracts/script/eigenlayer/ecdsa/WavsMirrorPrepareDeploy.s.sol b/contracts/script/eigenlayer/ecdsa/WavsMirrorPrepareDeploy.s.sol index 36ca0bac..b6ab5734 100644 --- a/contracts/script/eigenlayer/ecdsa/WavsMirrorPrepareDeploy.s.sol +++ b/contracts/script/eigenlayer/ecdsa/WavsMirrorPrepareDeploy.s.sol @@ -32,15 +32,11 @@ contract WavsMirrorPrepareDeploy is Script, IECDSAStakeRegistryTypes { /// @notice The run function for the script. function run() external { - vm.startBroadcast(); - // Pass in the configuration as a file, load it WavsMirrorDeploymentLib.InitialConfiguration memory configuration = WavsMirrorDeploymentLib.loadConfigurationFromChain(serviceManagerAddress); // write the configuration to a file WavsMirrorDeploymentLib.writeConfiguration(configFile, configuration); - - vm.stopBroadcast(); } } diff --git a/contracts/script/eigenlayer/ecdsa/utils/WavsMiddlewareDeploymentLib.sol b/contracts/script/eigenlayer/ecdsa/utils/WavsMiddlewareDeploymentLib.sol index f58ceb49..4a2ac23d 100644 --- a/contracts/script/eigenlayer/ecdsa/utils/WavsMiddlewareDeploymentLib.sol +++ b/contracts/script/eigenlayer/ecdsa/utils/WavsMiddlewareDeploymentLib.sol @@ -48,16 +48,6 @@ library WavsMiddlewareDeploymentLib { address operatorUpdateHandler; } - /** - * @notice The strategy config struct. - * @param strategy The strategy address. - * @param multiplier The multiplier. - */ - struct StrategyConfig { - address strategy; - uint96 multiplier; - } - Vm internal constant VM = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); /// @notice The error for the strategies file not found. diff --git a/contracts/src/eigenlayer/bls/interfaces/IMirrorBLSApkRegistry.sol b/contracts/src/eigenlayer/bls/interfaces/IMirrorBLSApkRegistry.sol new file mode 100644 index 00000000..73dee153 --- /dev/null +++ b/contracts/src/eigenlayer/bls/interfaces/IMirrorBLSApkRegistry.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IBLSApkRegistryTypes} from "@eigenlayer-middleware/src/interfaces/IBLSApkRegistry.sol"; + +/** + * @title IMirrorBLSApkRegistry + * @author Lay3rLabs + * @notice This interface defines the MirrorBLSApkRegistry contract. + * @dev This interface is used to interact with the MirrorBLSApkRegistry contract. + */ +interface IMirrorBLSApkRegistry { + /** + * @notice The function to register a BLS public key for a mirror. + * @param operator The operator. + * @param params The parameters for the registration. + * @return operatorId The ID of the operator. + */ + function registerBLSPublicKeyForMirror( + address operator, + IBLSApkRegistryTypes.PubkeyRegistrationParams calldata params + ) external returns (bytes32 operatorId); + + /** + * @notice The function to get or register an operator ID for a mirror. + * @param operator The operator. + * @param params The parameters for the registration. + * @return operatorId The ID of the operator. + */ + function getOrRegisterOperatorIdForMirror( + address operator, + IBLSApkRegistryTypes.PubkeyRegistrationParams calldata params + ) external returns (bytes32 operatorId); +} diff --git a/contracts/src/eigenlayer/bls/interfaces/IMirrorSlashingRegistryCoordinator.sol b/contracts/src/eigenlayer/bls/interfaces/IMirrorSlashingRegistryCoordinator.sol new file mode 100644 index 00000000..aff5be82 --- /dev/null +++ b/contracts/src/eigenlayer/bls/interfaces/IMirrorSlashingRegistryCoordinator.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +/** + * @title IMirrorSlashingRegistryCoordinator + * @author Lay3rLabs + * @notice This interface defines the MirrorSlashingRegistryCoordinator contract. + * @dev This interface is used to interact with the MirrorSlashingRegistryCoordinator contract. + */ +interface IMirrorSlashingRegistryCoordinator { + /** + * @notice The function to register an operator for a mirror. + * @param operator The operator. + * @param avs The AVS. + * @param operatorSetIds The operator set IDs. + * @param currentStake The current stake. + * @param data The data. + */ + function registerOperatorForMirror( + address operator, + address avs, + uint32[] memory operatorSetIds, + uint96 currentStake, + bytes calldata data + ) external; + + /** + * @notice The function to update the stake of operators for a mirror. + * @param stakeWeights The stake weights. + * @param operators The operators. + */ + function updateOperatorsForMirror( + uint96[] memory stakeWeights, + address[] memory operators + ) external; + + /** + * @notice The function to update the stake of operators for a quorum for a mirror. + * @param operatorsPerQuorum The operators per quorum. + * @param stakeWeights The stake weights. + * @param quorumNumbers The quorum numbers. + */ + function updateOperatorsForQuorumForMirror( + address[][] memory operatorsPerQuorum, + uint96[][] memory stakeWeights, + bytes calldata quorumNumbers + ) external; +} diff --git a/contracts/src/eigenlayer/bls/interfaces/IMirrorStakeRegistry.sol b/contracts/src/eigenlayer/bls/interfaces/IMirrorStakeRegistry.sol new file mode 100644 index 00000000..9741d148 --- /dev/null +++ b/contracts/src/eigenlayer/bls/interfaces/IMirrorStakeRegistry.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +/** + * @title IMirrorStakeRegistry + * @author Lay3rLabs + * @notice This interface defines the MirrorStakeRegistry contract. + * @dev This interface is used to interact with the MirrorStakeRegistry contract. + */ +interface IMirrorStakeRegistry { + /// @notice The error thrown when the function is not implemented. + error MirrorStakeRegistry_NotImplemented(); + + /** + * @notice The function to register an operator for a mirror. + * @param operatorId The ID of the operator. + * @param quorumNumbers The quorum numbers. + * @param currentStake The current stake. + * @return currentStakes The current stakes. + * @return totalStakes The total stakes. + */ + function registerOperatorForMirror( + bytes32 operatorId, + bytes calldata quorumNumbers, + uint96 currentStake + ) external returns (uint96[] memory, uint96[] memory); + + /** + * @notice The function to update the stake of operators for a mirror. + * @param stakeWeights The stake weights. + * @param operators The operators. + * @param operatorIds The IDs of the operators. + * @param quorumNumber The quorum number. + * @return results The results. + */ + function updateOperatorsStakeForMirror( + uint96[] memory stakeWeights, + address[] memory operators, + bytes32[] memory operatorIds, + uint8 quorumNumber + ) external returns (bool[] memory); +} diff --git a/contracts/src/eigenlayer/bls/mirror/MirrorBLSApkRegistry.sol b/contracts/src/eigenlayer/bls/mirror/MirrorBLSApkRegistry.sol new file mode 100644 index 00000000..74cebd10 --- /dev/null +++ b/contracts/src/eigenlayer/bls/mirror/MirrorBLSApkRegistry.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {BLSApkRegistry} from "@eigenlayer-middleware/src/BLSApkRegistry.sol"; +import {BN254} from "@eigenlayer-middleware/src/libraries/BN254.sol"; + +import {ISlashingRegistryCoordinator} from + "@eigenlayer-middleware/src/interfaces/ISlashingRegistryCoordinator.sol"; +import {IMirrorBLSApkRegistry} from "../interfaces/IMirrorBLSApkRegistry.sol"; + +/** + * @title MirrorBLSApkRegistry + * @author Lay3rLabs + * @notice This contract implements the MirrorBLSApkRegistry contract. + * @dev This contract is used to register and get BLS public keys for a mirror. + */ +contract MirrorBLSApkRegistry is BLSApkRegistry, IMirrorBLSApkRegistry { + /** + * @notice The constructor for the MirrorBLSApkRegistry contract. + * @param _registryCoordinator The registry coordinator. + */ + constructor( + ISlashingRegistryCoordinator _registryCoordinator + ) BLSApkRegistry(_registryCoordinator) {} + + /// @inheritdoc IMirrorBLSApkRegistry + function registerBLSPublicKeyForMirror( + address operator, + PubkeyRegistrationParams calldata params + ) public onlyRegistryCoordinator returns (bytes32) { + bytes32 pubkeyHash = BN254.hashG1Point(params.pubkeyG1); + require(pubkeyHash != ZERO_PK_HASH, ZeroPubKey()); + require(getOperatorId(operator) == bytes32(0), OperatorAlreadyRegistered()); + require(pubkeyHashToOperator[pubkeyHash] == address(0), BLSPubkeyAlreadyRegistered()); + + operatorToPubkey[operator] = params.pubkeyG1; + operatorToPubkeyG2[operator] = params.pubkeyG2; + operatorToPubkeyHash[operator] = pubkeyHash; + pubkeyHashToOperator[pubkeyHash] = operator; + + emit NewPubkeyRegistration(operator, params.pubkeyG1, params.pubkeyG2); + return pubkeyHash; + } + + /// @inheritdoc IMirrorBLSApkRegistry + function getOrRegisterOperatorIdForMirror( + address operator, + PubkeyRegistrationParams calldata params + ) external onlyRegistryCoordinator returns (bytes32 operatorId) { + operatorId = getOperatorId(operator); + if (operatorId == 0) { + operatorId = registerBLSPublicKeyForMirror(operator, params); + } + return operatorId; + } +} diff --git a/contracts/src/eigenlayer/bls/mirror/MirrorSlashingRegistryCoordinator.sol b/contracts/src/eigenlayer/bls/mirror/MirrorSlashingRegistryCoordinator.sol new file mode 100644 index 00000000..e7b40b8c --- /dev/null +++ b/contracts/src/eigenlayer/bls/mirror/MirrorSlashingRegistryCoordinator.sol @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import { + IBLSApkRegistry, + IBLSApkRegistryTypes +} from "@eigenlayer-middleware/src/interfaces/IBLSApkRegistry.sol"; +import {IIndexRegistry} from "@eigenlayer-middleware/src/interfaces/IIndexRegistry.sol"; +import {ISocketRegistry} from "@eigenlayer-middleware/src/interfaces/ISocketRegistry.sol"; +import {IPauserRegistry} from "eigenlayer-contracts/src/contracts/interfaces/IPauserRegistry.sol"; +import { + IStakeRegistry, + IStakeRegistryTypes +} from "@eigenlayer-middleware/src/interfaces/IStakeRegistry.sol"; +import {BitmapUtils} from "@eigenlayer-middleware/src/libraries/BitmapUtils.sol"; + +import {SlashingRegistryCoordinator} from + "@eigenlayer-middleware/src/SlashingRegistryCoordinator.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; + +import {IMirrorStakeRegistry} from "../interfaces/IMirrorStakeRegistry.sol"; +import {IMirrorBLSApkRegistry} from "../interfaces/IMirrorBLSApkRegistry.sol"; +import {IMirrorSlashingRegistryCoordinator} from + "../interfaces/IMirrorSlashingRegistryCoordinator.sol"; + +/** + * @title MirrorSlashingRegistryCoordinator + * @author Lay3rLabs + * @notice This contract implements the MirrorSlashingRegistryCoordinator contract. + * @dev This contract is used to coordinate the slashing registry for a mirror. + */ +contract MirrorSlashingRegistryCoordinator is + SlashingRegistryCoordinator, + IMirrorSlashingRegistryCoordinator +{ + using BitmapUtils for *; + + /** + * @notice The constructor for the MirrorSlashingRegistryCoordinator contract. + * @param _stakeRegistry The stake registry. + * @param _blsApkRegistry The BLS APK registry. + * @param _indexRegistry The index registry. + * @param _socketRegistry The socket registry. + * @param _allocationManager The allocation manager. + * @param _pauserRegistry The pauser registry. + * @param _version The version. + */ + constructor( + IStakeRegistry _stakeRegistry, + IBLSApkRegistry _blsApkRegistry, + IIndexRegistry _indexRegistry, + ISocketRegistry _socketRegistry, + IAllocationManager _allocationManager, + IPauserRegistry _pauserRegistry, + string memory _version + ) + SlashingRegistryCoordinator( + _stakeRegistry, + _blsApkRegistry, + _indexRegistry, + _socketRegistry, + _allocationManager, + _pauserRegistry, + _version + ) + {} + + /* solhint-disable gas-calldata-parameters */ + /// @inheritdoc SlashingRegistryCoordinator + function createTotalDelegatedStakeQuorum( + OperatorSetParam memory operatorSetParams, + uint96 minimumStake, + IStakeRegistryTypes.StrategyParams[] memory strategyParams + ) external override onlyOwner { + _createQuorumForMirror( + operatorSetParams, + minimumStake, + strategyParams, + IStakeRegistryTypes.StakeType.TOTAL_DELEGATED, + 0 + ); + } + + /// @inheritdoc SlashingRegistryCoordinator + function createSlashableStakeQuorum( + OperatorSetParam memory operatorSetParams, + uint96 minimumStake, + IStakeRegistryTypes.StrategyParams[] memory strategyParams, + uint32 lookAheadPeriod + ) external override onlyOwner { + _createQuorumForMirror( + operatorSetParams, + minimumStake, + strategyParams, + IStakeRegistryTypes.StakeType.TOTAL_SLASHABLE, + lookAheadPeriod + ); + } + + /// @inheritdoc IMirrorSlashingRegistryCoordinator + function registerOperatorForMirror( + address operator, + address avs, + uint32[] memory operatorSetIds, + uint96 currentStake, + bytes calldata data + ) external onlyOwner onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) { + require(supportsAVS(avs), InvalidAVS()); + bytes memory quorumNumbers = _getQuorumNumbers(operatorSetIds); + + ( + RegistrationType registrationType, + string memory socket, + IBLSApkRegistryTypes.PubkeyRegistrationParams memory params + ) = abi.decode( + data, (RegistrationType, string, IBLSApkRegistryTypes.PubkeyRegistrationParams) + ); + + /** + * If the operator has NEVER registered a pubkey before, use `params` to register + * their pubkey in blsApkRegistry + * + * If the operator HAS registered a pubkey, `params` is ignored and the pubkey hash + * (operatorId) is fetched instead + */ + bytes32 operatorId = IMirrorBLSApkRegistry(address(blsApkRegistry)) + .getOrRegisterOperatorIdForMirror(operator, params); + + if (registrationType == RegistrationType.NORMAL) { + uint32[] memory numOperatorsPerQuorum = _registerOperatorForMirror({ + operator: operator, + operatorId: operatorId, + quorumNumbers: quorumNumbers, + socket: socket, + checkMaxOperatorCount: true, + currentStake: currentStake + }).numOperatorsPerQuorum; + + // For each quorum, validate that the new operator count does not exceed the maximum + // (If it does, an operator needs to be replaced -- see `registerOperatorWithChurn`) + for (uint256 i = 0; i < quorumNumbers.length; ++i) { + uint8 quorumNumber = uint8(quorumNumbers[i]); + + require( + !(numOperatorsPerQuorum[i] > _quorumParams[quorumNumber].maxOperatorCount), + MaxOperatorCountReached() + ); + } + } else if (registrationType == RegistrationType.CHURN) { + // Decode registration data from bytes + ( + , + , + , + OperatorKickParam[] memory operatorKickParams, + SignatureWithSaltAndExpiry memory churnApproverSignature + ) = abi.decode( + data, + ( + RegistrationType, + string, + IBLSApkRegistryTypes.PubkeyRegistrationParams, + OperatorKickParam[], + SignatureWithSaltAndExpiry + ) + ); + _registerOperatorWithChurnForMirror({ + operator: operator, + operatorId: operatorId, + quorumNumbers: quorumNumbers, + socket: socket, + operatorKickParams: operatorKickParams, + churnApproverSignature: churnApproverSignature, + currentStake: currentStake + }); + } else { + revert InvalidRegistrationType(); + } + } + + /// @inheritdoc IMirrorSlashingRegistryCoordinator + function updateOperatorsForMirror( + uint96[] memory stakeWeights, + address[] memory operators + ) external onlyWhenNotPaused(PAUSED_UPDATE_OPERATOR) { + for (uint256 i = 0; i < operators.length; ++i) { + // create single-element arrays for the operator and operatorId + address[] memory singleOperator = new address[](1); + singleOperator[0] = operators[i]; + bytes32[] memory singleOperatorId = new bytes32[](1); + singleOperatorId[0] = _operatorInfo[operators[i]].operatorId; + + uint192 currentBitmap = _currentOperatorBitmap(singleOperatorId[0]); + bytes memory quorumNumbers = currentBitmap.bitmapToBytesArray(); + for (uint256 j = 0; j < quorumNumbers.length; ++j) { + // update the operator's stake for each quorum + _updateOperatorsStakesForMirror( + stakeWeights, singleOperator, singleOperatorId, uint8(quorumNumbers[j]) + ); + } + } + } + + /// @inheritdoc IMirrorSlashingRegistryCoordinator + function updateOperatorsForQuorumForMirror( + address[][] memory operatorsPerQuorum, + uint96[][] memory stakeWeights, + bytes calldata quorumNumbers + ) external onlyWhenNotPaused(PAUSED_UPDATE_OPERATOR) { + // Input validation + // - all quorums should exist (checked against `quorumCount` in orderedBytesArrayToBitmap) + // - there should be no duplicates in `quorumNumbers` + // - there should be one list of operators per quorum + BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount); + require(operatorsPerQuorum.length == quorumNumbers.length, InputLengthMismatch()); + + // For each quorum, update ALL registered operators + for (uint256 i = 0; i < quorumNumbers.length; ++i) { + uint8 quorumNumber = uint8(quorumNumbers[i]); + + // Ensure we've passed in the correct number of operators for this quorum + address[] memory currQuorumOperators = operatorsPerQuorum[i]; + require( + currQuorumOperators.length == indexRegistry.totalOperatorsForQuorum(quorumNumber), + QuorumOperatorCountMismatch() + ); + + bytes32[] memory operatorIds = new bytes32[](currQuorumOperators.length); + address prevOperatorAddress = address(0); + // For each operator: + // - check that they are registered for this quorum + // - check that their address is strictly greater than the last operator + // ... then, update their stakes + for (uint256 j = 0; j < currQuorumOperators.length; ++j) { + address operator = currQuorumOperators[j]; + + operatorIds[j] = _operatorInfo[operator].operatorId; + { + uint192 currentBitmap = _currentOperatorBitmap(operatorIds[j]); + // Check that the operator is registered + require( + BitmapUtils.isSet(currentBitmap, quorumNumber), NotRegisteredForQuorum() + ); + // Prevent duplicate operators + require(operator > prevOperatorAddress, NotSorted()); + } + + prevOperatorAddress = operator; + } + + _updateOperatorsStakesForMirror( + stakeWeights[i], currQuorumOperators, operatorIds, quorumNumber + ); + + // Update timestamp that all operators in quorum have been updated all at once + quorumUpdateBlockNumber[quorumNumber] = block.number; + emit QuorumBlockNumberUpdated(quorumNumber, block.number); + } + } + + /** + * @notice The function to register an operator for a mirror. + * @param operator The operator. + * @param operatorId The ID of the operator. + * @param quorumNumbers The quorum numbers. + * @param socket The socket. + * @param checkMaxOperatorCount The flag to check the maximum operator count. + * @param currentStake The current stake. + * @return results The register results. + */ + function _registerOperatorForMirror( + address operator, + bytes32 operatorId, + bytes memory quorumNumbers, + string memory socket, + bool checkMaxOperatorCount, + uint96 currentStake + ) internal returns (RegisterResults memory results) { + /** + * Get bitmap of quorums to register for and operator's current bitmap. Validate that: + * - we're trying to register for at least 1 quorum + * - the quorums we're registering for exist (checked against `quorumCount` in orderedBytesArrayToBitmap) + * - the operator is not currently registered for any quorums we're registering for + * Then, calculate the operator's new bitmap after registration + */ + uint192 quorumsToAdd = + uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount)); + uint192 currentBitmap = _currentOperatorBitmap(operatorId); + + // call hook to allow for any pre-register logic + _beforeRegisterOperator(operator, operatorId, quorumNumbers, currentBitmap); + + require(!quorumsToAdd.isEmpty(), BitmapEmpty()); + require(quorumsToAdd.noBitsInCommon(currentBitmap), AlreadyRegisteredForQuorums()); + uint192 newBitmap = uint192(currentBitmap.plus(quorumsToAdd)); + + // Check that the operator can reregister if ejected + require( + lastEjectionTimestamp[operator] + ejectionCooldown < block.timestamp, + CannotReregisterYet() + ); + + /** + * Update operator's bitmap, socket, and status. Only update operatorInfo if needed: + * if we're `REGISTERED`, the operatorId and status are already correct. + */ + _updateOperatorBitmap({operatorId: operatorId, newBitmap: newBitmap}); + + _setOperatorSocket(operatorId, socket); + + // If the operator wasn't registered for any quorums, update their status + // and register them with this AVS in EigenLayer core (DelegationManager) + if (_operatorInfo[operator].status != OperatorStatus.REGISTERED) { + _operatorInfo[operator] = OperatorInfo(operatorId, OperatorStatus.REGISTERED); + emit OperatorRegistered(operator, operatorId); + } + + // Register the operator with the BLSApkRegistry, StakeRegistry, and IndexRegistry + blsApkRegistry.registerOperator(operator, quorumNumbers); + (results.operatorStakes, results.totalStakes) = IMirrorStakeRegistry(address(stakeRegistry)) + .registerOperatorForMirror(operatorId, quorumNumbers, currentStake); + results.numOperatorsPerQuorum = indexRegistry.registerOperator(operatorId, quorumNumbers); + + if (checkMaxOperatorCount) { + for (uint256 i = 0; i < quorumNumbers.length; ++i) { + OperatorSetParam memory operatorSetParams = _quorumParams[uint8(quorumNumbers[i])]; + require( + !(results.numOperatorsPerQuorum[i] > operatorSetParams.maxOperatorCount), + MaxOperatorCountReached() + ); + } + } + + // call hook to allow for any post-register logic + _afterRegisterOperator(operator, operatorId, quorumNumbers, newBitmap); + + return results; + } + + /** + * @notice The function to register an operator with churn for a mirror. + * @param operator The operator. + * @param operatorId The ID of the operator. + * @param quorumNumbers The quorum numbers. + * @param socket The socket. + * @param operatorKickParams The operator kick parameters. + * @param churnApproverSignature The churn approver signature. + * @param currentStake The current stake. + */ + function _registerOperatorWithChurnForMirror( + address operator, + bytes32 operatorId, + bytes memory quorumNumbers, + string memory socket, + OperatorKickParam[] memory operatorKickParams, + SignatureWithSaltAndExpiry memory churnApproverSignature, + uint96 currentStake + ) internal { + require(operatorKickParams.length == quorumNumbers.length, InputLengthMismatch()); + + // Verify the churn approver's signature for the registering operator and kick params + _verifyChurnApproverSignature({ + registeringOperator: operator, + registeringOperatorId: operatorId, + operatorKickParams: operatorKickParams, + churnApproverSignature: churnApproverSignature + }); + + // Register the operator in each of the registry contracts and update the operator's + // quorum bitmap and registration status + RegisterResults memory results = _registerOperatorForMirror({ + operator: operator, + operatorId: operatorId, + quorumNumbers: quorumNumbers, + socket: socket, + checkMaxOperatorCount: false, + currentStake: currentStake + }); + + // Check that each quorum's operator count is below the configured maximum. If the max + // is exceeded, use `operatorKickParams` to deregister an existing operator to make space + for (uint256 i = 0; i < quorumNumbers.length; ++i) { + OperatorSetParam memory operatorSetParams = _quorumParams[uint8(quorumNumbers[i])]; + + /** + * If the new operator count for any quorum exceeds the maximum, validate + * that churn can be performed, then deregister the specified operator + */ + if (results.numOperatorsPerQuorum[i] > operatorSetParams.maxOperatorCount) { + _validateChurn({ + quorumNumber: uint8(quorumNumbers[i]), + totalQuorumStake: results.totalStakes[i], + newOperator: operator, + newOperatorStake: results.operatorStakes[i], + kickParams: operatorKickParams[i], + setParams: operatorSetParams + }); + + bytes memory singleQuorumNumber = new bytes(1); + singleQuorumNumber[0] = quorumNumbers[i]; + _kickOperator(operatorKickParams[i].operator, singleQuorumNumber); + } + } + } + + /** + * @notice The function to create a quorum for a mirror. + * @param operatorSetParams The operator set parameters. + * @param minimumStake The minimum stake. + * @param strategyParams The strategy parameters. + * @param stakeType The stake type. + * @param lookAheadPeriod The look ahead period. + */ + function _createQuorumForMirror( + OperatorSetParam memory operatorSetParams, + uint96 minimumStake, + IStakeRegistryTypes.StrategyParams[] memory strategyParams, + IStakeRegistryTypes.StakeType stakeType, + uint32 lookAheadPeriod + ) internal { + // The previous quorum count is the new quorum's number, + // this is because quorum numbers begin from index 0. + uint8 quorumNumber = quorumCount; + + // Hook to allow for any pre-create quorum logic + _beforeCreateQuorum(quorumNumber); + + // Increment the total quorum count. Fails if we're already at the max + require(quorumNumber < MAX_QUORUM_COUNT, MaxQuorumsReached()); + ++quorumCount; + + // Initialize the quorum here and in each registry + _setOperatorSetParams(quorumNumber, operatorSetParams); + + // Initialize stake registry based on stake type + if (stakeType == IStakeRegistryTypes.StakeType.TOTAL_DELEGATED) { + stakeRegistry.initializeDelegatedStakeQuorum(quorumNumber, minimumStake, strategyParams); + } else if (stakeType == IStakeRegistryTypes.StakeType.TOTAL_SLASHABLE) { + stakeRegistry.initializeSlashableStakeQuorum( + quorumNumber, minimumStake, lookAheadPeriod, strategyParams + ); + } + + indexRegistry.initializeQuorum(quorumNumber); + blsApkRegistry.initializeQuorum(quorumNumber); + + emit QuorumCreated({ + quorumNumber: quorumNumber, + operatorSetParams: operatorSetParams, + minimumStake: minimumStake, + strategyParams: strategyParams, + stakeType: stakeType, + lookAheadPeriod: lookAheadPeriod + }); + + // Hook to allow for any post-create quorum logic + _afterCreateQuorum(quorumNumber); + } + + /** + * @notice The function to update the stakes of operators for a mirror. + * @param stakeWeights The stake weights. + * @param operators The operators. + * @param operatorIds The IDs of the operators. + * @param quorumNumber The quorum number. + */ + function _updateOperatorsStakesForMirror( + uint96[] memory stakeWeights, + address[] memory operators, + bytes32[] memory operatorIds, + uint8 quorumNumber + ) internal virtual { + bytes memory singleQuorumNumber = new bytes(1); + singleQuorumNumber[0] = bytes1(quorumNumber); + bool[] memory doesNotMeetStakeThreshold = IMirrorStakeRegistry(address(stakeRegistry)) + .updateOperatorsStakeForMirror(stakeWeights, operators, operatorIds, quorumNumber); + for (uint256 j = 0; j < operators.length; ++j) { + // If the operator does not have the minimum stake, they need to be force deregistered. + if (doesNotMeetStakeThreshold[j]) { + _kickOperator(operators[j], singleQuorumNumber); + } + } + } +} diff --git a/contracts/src/eigenlayer/bls/mirror/MirrorStakeRegistry.sol b/contracts/src/eigenlayer/bls/mirror/MirrorStakeRegistry.sol new file mode 100644 index 00000000..00be83bf --- /dev/null +++ b/contracts/src/eigenlayer/bls/mirror/MirrorStakeRegistry.sol @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {ISlashingRegistryCoordinator} from + "@eigenlayer-middleware/src/interfaces/ISlashingRegistryCoordinator.sol"; +import {IDelegationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; +import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; +import {StakeRegistry} from "@eigenlayer-middleware/src/StakeRegistry.sol"; + +import {IMirrorStakeRegistry} from "../interfaces/IMirrorStakeRegistry.sol"; + +/** + * @title MirrorStakeRegistry + * @author Lay3rLabs + * @notice This contract implements the MirrorStakeRegistry contract. + * @dev This contract is used to register and update operators for a mirror. + */ +contract MirrorStakeRegistry is StakeRegistry, IMirrorStakeRegistry { + /** + * @notice The constructor for the MirrorStakeRegistry contract. + * @param _registryCoordinator The slashing registry coordinator. + * @param _delegationManager The delegation manager. + * @param _avsDirectory The AVS directory. + * @param _allocationManager The allocation manager. + */ + constructor( + ISlashingRegistryCoordinator _registryCoordinator, + IDelegationManager _delegationManager, + IAVSDirectory _avsDirectory, + IAllocationManager _allocationManager + ) StakeRegistry(_registryCoordinator, _delegationManager, _avsDirectory, _allocationManager) {} + + /* solhint-disable gas-calldata-parameters */ + /// @inheritdoc IMirrorStakeRegistry + function registerOperatorForMirror( + bytes32 operatorId, + bytes calldata quorumNumbers, + uint96 currentStake + ) public onlySlashingRegistryCoordinator returns (uint96[] memory, uint96[] memory) { + uint96[] memory currentStakes = new uint96[](quorumNumbers.length); + uint96[] memory totalStakes = new uint96[](quorumNumbers.length); + for (uint256 i = 0; i < quorumNumbers.length; ++i) { + uint8 quorumNumber = uint8(quorumNumbers[i]); + _checkQuorumExists(quorumNumber); + + // Update the operator's stake + int256 stakeDelta = _recordOperatorStakeUpdate({ + operatorId: operatorId, + quorumNumber: quorumNumber, + newStake: currentStake + }); + + // Update this quorum's total stake by applying the operator's delta + currentStakes[i] = currentStake; + totalStakes[i] = _recordTotalStakeUpdate(quorumNumber, stakeDelta); + } + + return (currentStakes, totalStakes); + } + + /// @inheritdoc IMirrorStakeRegistry + function updateOperatorsStakeForMirror( + uint96[] memory stakeWeights, + address[] memory operators, + bytes32[] memory operatorIds, + uint8 quorumNumber + ) external onlySlashingRegistryCoordinator returns (bool[] memory) { + bool[] memory shouldBeDeregistered = new bool[](operators.length); + + /** + * For each quorum, update the operator's stake and record the delta + * in the quorum's total stake. + * + * If the operator no longer has the minimum stake required to be registered + * in the quorum, the quorum number is added to `quorumsToRemove`, which + * is returned to the registry coordinator. + */ + _checkQuorumExists(quorumNumber); + + int256 totalStakeDelta = 0; + // If the operator no longer meets the minimum stake, set their stake to zero and mark them for removal + /// also handle setting the operator's stake to 0 and remove them from the quorum + for (uint256 i = 0; i < operators.length; ++i) { + if (stakeWeights[i] < minimumStakeForQuorum[quorumNumber]) { + stakeWeights[i] = 0; + shouldBeDeregistered[i] = true; + } + + // Update the operator's stake and retrieve the delta + // If we're deregistering them, their weight is set to 0 + int256 stakeDelta = _recordOperatorStakeUpdate({ + operatorId: operatorIds[i], + quorumNumber: quorumNumber, + newStake: stakeWeights[i] + }); + + totalStakeDelta += stakeDelta; + } + + // Apply the delta to the quorum's total stake + _recordTotalStakeUpdate(quorumNumber, totalStakeDelta); + + return shouldBeDeregistered; + } + + /// @inheritdoc StakeRegistry + function addStrategies( + uint8 quorumNumber, + StrategyParams[] memory _strategyParams + ) public override onlyCoordinatorOwner quorumExists(quorumNumber) { + _addStrategyParams(quorumNumber, _strategyParams); + } + + /// @inheritdoc StakeRegistry + function removeStrategies( + uint8 quorumNumber, + uint256[] memory indicesToRemove + ) public override onlyCoordinatorOwner quorumExists(quorumNumber) { + uint256 toRemoveLength = indicesToRemove.length; + require(toRemoveLength > 0, InputArrayLengthZero()); + + StrategyParams[] storage _strategyParams = strategyParams[quorumNumber]; + IStrategy[] storage _strategiesPerQuorum = strategiesPerQuorum[quorumNumber]; + IStrategy[] memory _strategiesToRemove = new IStrategy[](toRemoveLength); + + for (uint256 i = 0; i < toRemoveLength; ++i) { + _strategiesToRemove[i] = _strategyParams[indicesToRemove[i]].strategy; + emit StrategyRemovedFromQuorum( + quorumNumber, _strategyParams[indicesToRemove[i]].strategy + ); + emit StrategyMultiplierUpdated( + quorumNumber, _strategyParams[indicesToRemove[i]].strategy, 0 + ); + + // Replace index to remove with the last item in the list, then pop the last item + _strategyParams[indicesToRemove[i]] = _strategyParams[_strategyParams.length - 1]; + _strategyParams.pop(); + _strategiesPerQuorum[indicesToRemove[i]] = + _strategiesPerQuorum[_strategiesPerQuorum.length - 1]; + _strategiesPerQuorum.pop(); + } + } + + /* solhint-disable use-natspec */ + /// @inheritdoc StakeRegistry + function _weightOfOperatorsForQuorum( + uint8, /* quorumNumber */ + address[] memory /* operators */ + ) internal pure override returns (uint96[] memory, bool[] memory) { + revert MirrorStakeRegistry_NotImplemented(); + } + + /// @inheritdoc StakeRegistry + function registerOperator( + address, /* operator */ + bytes32, /* operatorId */ + bytes calldata /* quorumNumbers */ + ) public pure override returns (uint96[] memory, uint96[] memory) { + revert MirrorStakeRegistry_NotImplemented(); + } +} diff --git a/contracts/test/eigenlayer/bls/WavsServiceManager.t.sol b/contracts/test/eigenlayer/bls/WavsServiceManager.t.sol index fc4f3054..1ac3bff9 100644 --- a/contracts/test/eigenlayer/bls/WavsServiceManager.t.sol +++ b/contracts/test/eigenlayer/bls/WavsServiceManager.t.sol @@ -66,17 +66,14 @@ contract WavsServiceManagerTest is Test { /* solhint-disable func-name-mixedcase */ /// @notice The test_initial_state function. function test_initial_state() public view { - /* solhint-enable func-name-mixedcase */ // Test initial state assertEq(serviceManager.quorumNumerator(), 2, "Initial quorum numerator should be 2"); assertEq(serviceManager.quorumDenominator(), 3, "Initial quorum denominator should be 3"); assertEq(serviceManager.avsDirectory(), avsDirectory, "AVS directory should be set"); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_setQuorumThreshold function. function test_setQuorumThreshold() public { - /* solhint-enable func-name-mixedcase */ // Change quorum to 51% vm.startPrank(owner); serviceManager.setQuorumThreshold(51, 100); @@ -86,20 +83,16 @@ contract WavsServiceManagerTest is Test { assertEq(serviceManager.quorumDenominator(), 100, "Quorum denominator should be updated"); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_setQuorumThreshold_only_owner function. function test_setQuorumThreshold_only_owner() public { - /* solhint-enable func-name-mixedcase */ // Non-owner should not be able to set quorum threshold vm.prank(makeAddr("non-owner")); vm.expectRevert("Ownable: caller is not the owner"); serviceManager.setQuorumThreshold(1, 2); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_setQuorumThreshold_invalid_params function. function test_setQuorumThreshold_invalid_params() public { - /* solhint-enable func-name-mixedcase */ // numerator = 0 vm.prank(owner); vm.expectRevert( @@ -122,10 +115,8 @@ contract WavsServiceManagerTest is Test { serviceManager.setQuorumThreshold(3, 2); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_setServiceURI function. function test_setServiceURI() public { - /* solhint-enable func-name-mixedcase */ vm.startPrank(owner); serviceManager.setServiceURI("https://wavs.io"); vm.stopPrank(); diff --git a/contracts/test/eigenlayer/ecdsa/MirrorOperatorSyncHandler.t.sol b/contracts/test/eigenlayer/ecdsa/MirrorOperatorSyncHandler.t.sol index e286ff03..c64a4969 100644 --- a/contracts/test/eigenlayer/ecdsa/MirrorOperatorSyncHandler.t.sol +++ b/contracts/test/eigenlayer/ecdsa/MirrorOperatorSyncHandler.t.sol @@ -99,7 +99,6 @@ contract MirrorOperatorSyncHandlerTest is Test { /* solhint-disable func-name-mixedcase */ /// @notice The test_initial_state function. function test_initial_state() public view { - /* solhint-enable func-name-mixedcase */ // Verify deployment addresses are set correctly assertNotEq(address(serviceHandler), address(0), "ServiceHandler address cannot be zero"); @@ -127,10 +126,8 @@ contract MirrorOperatorSyncHandlerTest is Test { ); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_invalid_payload function. function test_invalid_payload() public { - /* solhint-enable func-name-mixedcase */ // Create an envelope with invalid payload IWavsServiceHandler.Envelope memory envelope = IWavsServiceHandler.Envelope({ eventId: bytes20(uint160(1)), @@ -146,10 +143,8 @@ contract MirrorOperatorSyncHandlerTest is Test { serviceHandler.handleSignedEnvelope(envelope, signatureData); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_invalid_trigger_id function. function test_invalid_trigger_id() public { - /* solhint-enable func-name-mixedcase */ // Keep the same operators address[] memory newOperators = operators; address[] memory newSigningKeyAddresses = signingKeyAddresses; @@ -209,10 +204,8 @@ contract MirrorOperatorSyncHandlerTest is Test { serviceHandler.handleSignedEnvelope(envelope, signatureData); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_insufficient_quorum function. function test_insufficient_quorum() public { - /* solhint-enable func-name-mixedcase */ // Create a valid UpdateWithId payload with triggerId = 1 address[] memory newOperators = new address[](1); address[] memory newSigningKeyAddresses = new address[](1); @@ -258,10 +251,8 @@ contract MirrorOperatorSyncHandlerTest is Test { serviceHandler.handleSignedEnvelope(envelope, signatureData); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_successful_update_weight function. function test_successful_update_weight() public { - /* solhint-enable func-name-mixedcase */ // let's change the weights and a public key // now op1 and op2 have 2/3 and can pass a future round address[] memory newOperators = new address[](2); diff --git a/contracts/test/eigenlayer/ecdsa/MirrorQuorumSyncHandler.t.sol b/contracts/test/eigenlayer/ecdsa/MirrorQuorumSyncHandler.t.sol index 99324ae7..3faae930 100644 --- a/contracts/test/eigenlayer/ecdsa/MirrorQuorumSyncHandler.t.sol +++ b/contracts/test/eigenlayer/ecdsa/MirrorQuorumSyncHandler.t.sol @@ -102,7 +102,6 @@ contract MirrorQuorumSyncHandlerTest is Test { /* solhint-disable func-name-mixedcase */ /// @notice The test_initial_state function. function test_initial_state() public view { - /* solhint-enable func-name-mixedcase */ // Test initial state of the service handler assertEq(serviceHandler.lastTriggerId(), 0, "Initial trigger ID should be 0"); assertEq( @@ -114,10 +113,8 @@ contract MirrorQuorumSyncHandlerTest is Test { assertEq(serviceManager.quorumDenominator(), 3, "Initial quorum denominator should be 3"); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_invalid_trigger_id function. function test_invalid_trigger_id() public { - /* solhint-enable func-name-mixedcase */ // update trigger to 5 IMirrorQuorumSyncHandler.UpdateWithId memory updateData = IMirrorQuorumSyncHandler.UpdateWithId({triggerId: 5, numerator: 2, denominator: 3}); @@ -158,10 +155,8 @@ contract MirrorQuorumSyncHandlerTest is Test { serviceHandler.handleSignedEnvelope(envelope, signatureData); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_insufficient_quorum function. function test_insufficient_quorum() public { - /* solhint-enable func-name-mixedcase */ // Create a valid UpdateWithId payload with triggerId = 1 IMirrorQuorumSyncHandler.UpdateWithId memory updateData = IMirrorQuorumSyncHandler.UpdateWithId({triggerId: 1, numerator: 2, denominator: 3}); @@ -211,10 +206,8 @@ contract MirrorQuorumSyncHandlerTest is Test { } */ - /* solhint-disable func-name-mixedcase */ /// @notice The test_successful_update_quorum function. function test_successful_update_quorum() public { - /* solhint-enable func-name-mixedcase */ // let's change quorum so 2/5 (4/10)can pass, not 2/3 // Create the UpdateWithId struct with triggerId = 1 IMirrorQuorumSyncHandler.UpdateWithId memory updateData = diff --git a/contracts/test/eigenlayer/ecdsa/MirrorStakeRegistry.t.sol b/contracts/test/eigenlayer/ecdsa/MirrorStakeRegistry.t.sol index 1c6e2227..029ce29d 100644 --- a/contracts/test/eigenlayer/ecdsa/MirrorStakeRegistry.t.sol +++ b/contracts/test/eigenlayer/ecdsa/MirrorStakeRegistry.t.sol @@ -102,7 +102,6 @@ contract MirrorStakeRegistryTest is Test { /* solhint-disable func-name-mixedcase */ /// @notice The test_initialization function. function test_initialization() public view { - /* solhint-enable func-name-mixedcase */ assertEq(registry.owner(), owner, "Owner should be set correctly"); assertEq( address(registry.serviceManager()), @@ -111,10 +110,8 @@ contract MirrorStakeRegistryTest is Test { ); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_registrationMethodsRevert function. function test_registrationMethodsRevert() public { - /* solhint-enable func-name-mixedcase */ // Test registerOperatorWithSignature reverts ISignatureUtilsMixinTypes.SignatureWithSaltAndExpiry memory sig; vm.expectRevert(MirrorStakeRegistry.RegistrationNotSupported.selector); @@ -142,10 +139,8 @@ contract MirrorStakeRegistryTest is Test { registry.updateOperatorsForQuorum(operatorsArray, extraData); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_ownerConfigMethodsRevert function. function test_ownerConfigMethodsRevert() public { - /* solhint-enable func-name-mixedcase */ vm.startPrank(owner); // Test updateQuorumConfig reverts @@ -165,10 +160,8 @@ contract MirrorStakeRegistryTest is Test { vm.stopPrank(); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_onlyOwnerRestriction function. function test_onlyOwnerRestriction() public { - /* solhint-enable func-name-mixedcase */ vm.startPrank(address(0x999)); // Not the owner // Test setOperatorDetails reverts for non-owners @@ -189,10 +182,8 @@ contract MirrorStakeRegistryTest is Test { vm.stopPrank(); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_setOperatorDetails function. function test_setOperatorDetails() public { - /* solhint-enable func-name-mixedcase */ vm.startPrank(owner); // Set operator details @@ -222,10 +213,8 @@ contract MirrorStakeRegistryTest is Test { vm.stopPrank(); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_batchSetOperatorDetails function. function test_batchSetOperatorDetails() public { - /* solhint-enable func-name-mixedcase */ vm.startPrank(owner); // Set up batch data @@ -302,10 +291,8 @@ contract MirrorStakeRegistryTest is Test { vm.stopPrank(); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_updateExistingOperator function. function test_updateExistingOperator() public { - /* solhint-enable func-name-mixedcase */ vm.startPrank(owner); // Set initial operator details @@ -345,10 +332,8 @@ contract MirrorStakeRegistryTest is Test { vm.stopPrank(); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_batchSetOperatorDetails_mismatchedArrays function. function test_batchSetOperatorDetails_mismatchedArrays() public { - /* solhint-enable func-name-mixedcase */ vm.startPrank(owner); // Set up batch data with mismatched array lengths @@ -389,10 +374,8 @@ contract MirrorStakeRegistryTest is Test { vm.stopPrank(); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_getOperatorWeightAtBlock function. function test_getOperatorWeightAtBlock() public { - /* solhint-enable func-name-mixedcase */ vm.startPrank(owner); // Set operator details @@ -414,10 +397,8 @@ contract MirrorStakeRegistryTest is Test { vm.stopPrank(); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_getTotalWeight function. function test_getTotalWeight() public { - /* solhint-enable func-name-mixedcase */ vm.startPrank(owner); // Set up batch data @@ -532,10 +513,8 @@ contract MirrorStakeRegistryTest is Test { } } - /* solhint-disable func-name-mixedcase */ /// @notice The test_isValidSignature function. function test_isValidSignature() public { - /* solhint-enable func-name-mixedcase */ vm.startPrank(owner); // Set up operators with weights diff --git a/contracts/test/eigenlayer/ecdsa/WavsAVSRegistrar.t.sol b/contracts/test/eigenlayer/ecdsa/WavsAVSRegistrar.t.sol index 3ecbb48f..4d8da7db 100644 --- a/contracts/test/eigenlayer/ecdsa/WavsAVSRegistrar.t.sol +++ b/contracts/test/eigenlayer/ecdsa/WavsAVSRegistrar.t.sol @@ -29,26 +29,21 @@ contract WavsAVSRegistrarTest is Test { /* solhint-disable func-name-mixedcase */ /// @notice The test_initial_state function. function test_initial_state() public view { - /* solhint-enable func-name-mixedcase */ // Test initial state assertEq(registrar.isPaused(), false, "Initial state should be unpaused"); assertEq(registrar.owner(), owner, "Owner should be set correctly"); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_pause function. function test_pause() public { - /* solhint-enable func-name-mixedcase */ // Test pause functionality vm.prank(owner); registrar.pause(); assertTrue(registrar.isPaused(), "Contract should be paused"); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_unpause function. function test_unpause() public { - /* solhint-enable func-name-mixedcase */ // First pause the contract vm.prank(owner); registrar.pause(); @@ -59,20 +54,16 @@ contract WavsAVSRegistrarTest is Test { assertFalse(registrar.isPaused(), "Contract should be unpaused"); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_only_owner_can_pause function. function test_only_owner_can_pause() public { - /* solhint-enable func-name-mixedcase */ // Non-owner should not be able to pause vm.prank(nonOwner); vm.expectRevert("Ownable: caller is not the owner"); registrar.pause(); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_only_owner_can_unpause function. function test_only_owner_can_unpause() public { - /* solhint-enable func-name-mixedcase */ // First pause the contract vm.prank(owner); registrar.pause(); @@ -83,10 +74,8 @@ contract WavsAVSRegistrarTest is Test { registrar.unpause(); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_registerOperator_works function. function test_registerOperator_works() public { - /* solhint-enable func-name-mixedcase */ address operator = address(0x123); address avs = address(0x456); uint32[] memory operatorSetIds = new uint32[](1); @@ -105,10 +94,8 @@ contract WavsAVSRegistrarTest is Test { registrar.registerOperator(operator, avs, operatorSetIds, data); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_deregisterOperator_works function. function test_deregisterOperator_works() public { - /* solhint-enable func-name-mixedcase */ address operator = address(0x123); address avs = address(0x456); uint32[] memory operatorSetIds = new uint32[](1); diff --git a/contracts/test/eigenlayer/ecdsa/WavsMiddlewareDeploymentLib.t.sol b/contracts/test/eigenlayer/ecdsa/WavsMiddlewareDeploymentLib.t.sol index f0c6fd7e..aae5ec0e 100644 --- a/contracts/test/eigenlayer/ecdsa/WavsMiddlewareDeploymentLib.t.sol +++ b/contracts/test/eigenlayer/ecdsa/WavsMiddlewareDeploymentLib.t.sol @@ -26,7 +26,6 @@ contract WavsMiddlewareDeploymentLibTest is Test { /* solhint-disable func-name-mixedcase */ /// @notice The test_parseStrategies function. function test_parseStrategies() public { - /* solhint-enable func-name-mixedcase */ IECDSAStakeRegistryTypes.Quorum memory quorum = WavsMiddlewareDeploymentLib.readQuorumConfig("deployments/strategies/", 17_000); console2.log(quorum.strategies.length); diff --git a/contracts/test/eigenlayer/ecdsa/WavsMirrorDeploymentLib.t.sol b/contracts/test/eigenlayer/ecdsa/WavsMirrorDeploymentLib.t.sol index 4a758eef..b2c0eb48 100644 --- a/contracts/test/eigenlayer/ecdsa/WavsMirrorDeploymentLib.t.sol +++ b/contracts/test/eigenlayer/ecdsa/WavsMirrorDeploymentLib.t.sol @@ -97,7 +97,6 @@ contract WavsMirrorDeploymentLibTest is Test { /* solhint-disable func-name-mixedcase */ /// @notice The test_initial_state function. function test_initial_state() public view { - /* solhint-enable func-name-mixedcase */ // Verify deployment addresses are set correctly assertNotEq(deployment.stakeRegistry, address(0), "StakeRegistry address cannot be zero"); assertNotEq( @@ -144,10 +143,8 @@ contract WavsMirrorDeploymentLibTest is Test { ); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_validateQuorumSigned_success function. function test_validateQuorumSigned_success() public view { - /* solhint-enable func-name-mixedcase */ // Create the envelope IWavsServiceHandler.Envelope memory envelope = IWavsServiceHandler.Envelope({ eventId: bytes20(uint160(1)), @@ -162,10 +159,8 @@ contract WavsMirrorDeploymentLibTest is Test { serviceManager.validate(envelope, signatureData); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_validateQuorumSigned_insufficient function. function test_validateQuorumSigned_insufficient() public { - /* solhint-enable func-name-mixedcase */ // Create the envelope IWavsServiceHandler.Envelope memory envelope = IWavsServiceHandler.Envelope({ eventId: bytes20(uint160(2)), @@ -185,10 +180,8 @@ contract WavsMirrorDeploymentLibTest is Test { serviceManager.validate(envelope, signatureData); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_validateQuorumSigned_exact function. function test_validateQuorumSigned_exact() public { - /* solhint-enable func-name-mixedcase */ // Change quorum to 3 of 5 address actualOwner = serviceManager.owner(); vm.startPrank(actualOwner); @@ -209,10 +202,8 @@ contract WavsMirrorDeploymentLibTest is Test { serviceManager.validate(envelope, signatureData); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_validateQuorumSigned_explicitSigningKeys function. function test_validateQuorumSigned_explicitSigningKeys() public { - /* solhint-enable func-name-mixedcase */ // Get the actual owner of the contracts address actualOwner = serviceManager.owner(); @@ -253,10 +244,8 @@ contract WavsMirrorDeploymentLibTest is Test { serviceManager.validate(envelope, signatureData); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_setQuorumThreshold function. function test_setQuorumThreshold() public { - /* solhint-enable func-name-mixedcase */ // Change quorum to 3 of 5 address actualOwner = serviceManager.owner(); vm.startPrank(actualOwner); @@ -289,20 +278,16 @@ contract WavsMirrorDeploymentLibTest is Test { serviceManager.validate(envelope, signatureData); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_setQuorumThreshold_only_owner function. function test_setQuorumThreshold_only_owner() public { - /* solhint-enable func-name-mixedcase */ // Non-owner should not be able to set quorum threshold vm.prank(address(0x999)); vm.expectRevert("Ownable: caller is not the owner"); serviceManager.setQuorumThreshold(1, 2); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_setQuorumThreshold_invalid_params function. function test_setQuorumThreshold_invalid_params() public { - /* solhint-enable func-name-mixedcase */ // Get the actual owner of the contracts address actualOwner = serviceManager.owner(); @@ -330,10 +315,8 @@ contract WavsMirrorDeploymentLibTest is Test { vm.stopPrank(); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_validate_invalid_signature_length function. function test_validate_invalid_signature_length() public { - /* solhint-enable func-name-mixedcase */ // Empty operators array address[] memory emptySigners = new address[](0); bytes[] memory emptySignatures = new bytes[](0); @@ -432,10 +415,8 @@ contract WavsMirrorDeploymentLibTest is Test { } } - /* solhint-disable func-name-mixedcase */ /// @notice The test_writeAndLoadConfiguration_roundtrip function. function test_writeAndLoadConfiguration_roundtrip() public { - /* solhint-enable func-name-mixedcase */ // 1. Define a sample InitialConfiguration WavsMirrorDeploymentLib.InitialConfiguration memory originalConfig; originalConfig.operators = operators; // Use operators from setUp diff --git a/contracts/test/eigenlayer/ecdsa/WavsOperatorUpdateHandler.t.sol b/contracts/test/eigenlayer/ecdsa/WavsOperatorUpdateHandler.t.sol index 6cc20fb3..f01c5219 100644 --- a/contracts/test/eigenlayer/ecdsa/WavsOperatorUpdateHandler.t.sol +++ b/contracts/test/eigenlayer/ecdsa/WavsOperatorUpdateHandler.t.sol @@ -120,7 +120,6 @@ contract WavsOperatorUpdateHandlerTest is Test { /* solhint-disable func-name-mixedcase */ /// @notice The test_initial_state function. function test_initial_state() public view { - /* solhint-enable func-name-mixedcase */ // Test initial state of the service handler assertEq( address(serviceHandler.getServiceManager()), @@ -134,10 +133,8 @@ contract WavsOperatorUpdateHandlerTest is Test { ); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_invalid_payload function. function test_invalid_payload() public { - /* solhint-enable func-name-mixedcase */ // Create an envelope with invalid payload IWavsServiceHandler.Envelope memory envelope = IWavsServiceHandler.Envelope({ eventId: bytes20(uint160(1)), @@ -153,10 +150,8 @@ contract WavsOperatorUpdateHandlerTest is Test { serviceHandler.handleSignedEnvelope(envelope, signatureData); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_insufficient_quorum function. function test_insufficient_quorum() public { - /* solhint-enable func-name-mixedcase */ // Create a valid UpdateWithId payload with triggerId = 1 IWavsOperatorUpdateHandler.OperatorUpdatePayload memory updateData = IWavsOperatorUpdateHandler.OperatorUpdatePayload({ @@ -187,10 +182,8 @@ contract WavsOperatorUpdateHandlerTest is Test { serviceHandler.handleSignedEnvelope(envelope, signatureData); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_failed_update_operators function. function test_failed_update_operators() public { - /* solhint-enable func-name-mixedcase */ // Create the update data with some operators IWavsOperatorUpdateHandler.OperatorUpdatePayload memory updateData = IWavsOperatorUpdateHandler.OperatorUpdatePayload({ @@ -213,10 +206,8 @@ contract WavsOperatorUpdateHandlerTest is Test { serviceHandler.handleSignedEnvelope(envelope, signatureData); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_successful_update_operators function. function test_successful_update_operators() public { - /* solhint-enable func-name-mixedcase */ // Create the update data address[][] memory operatorsPerQuorum = new address[][](1); operatorsPerQuorum[0] = operators; diff --git a/contracts/test/eigenlayer/ecdsa/WavsServiceManager.t.sol b/contracts/test/eigenlayer/ecdsa/WavsServiceManager.t.sol index ad19ac88..34c66754 100644 --- a/contracts/test/eigenlayer/ecdsa/WavsServiceManager.t.sol +++ b/contracts/test/eigenlayer/ecdsa/WavsServiceManager.t.sol @@ -89,7 +89,6 @@ contract WavsServiceManagerTest is Test { /* solhint-disable func-name-mixedcase */ /// @notice The test_initial_state function. function test_initial_state() public view { - /* solhint-enable func-name-mixedcase */ // Test initial state assertEq(serviceManager.quorumNumerator(), 2, "Initial quorum numerator should be 2"); assertEq(serviceManager.quorumDenominator(), 3, "Initial quorum denominator should be 3"); @@ -100,10 +99,8 @@ contract WavsServiceManagerTest is Test { assertEq(signer, operator1, "At block query should match operator"); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_validateQuorumSigned_success function. function test_validateQuorumSigned_success() public view { - /* solhint-enable func-name-mixedcase */ // 2/3 of 500 is 333, so 400 should pass serviceManager.validate( IWavsServiceHandler.Envelope({eventId: bytes20(0), ordering: bytes12(0), payload: ""}), @@ -114,10 +111,8 @@ contract WavsServiceManagerTest is Test { assertTrue(true, "Validation should pass with sufficient quorum"); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_validateQuorumSigned_insufficient function. function test_validateQuorumSigned_insufficient() public { - /* solhint-enable func-name-mixedcase */ // 2/3 of 500 is 333, so 300 should fail vm.expectRevert( abi.encodeWithSelector(IWavsServiceManager.InsufficientQuorum.selector, 300, 333, 500) @@ -128,10 +123,8 @@ contract WavsServiceManagerTest is Test { ); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_validateQuorumSigned_exact function. function test_validateQuorumSigned_exact() public { - /* solhint-enable func-name-mixedcase */ // Change quorum to 3 of 5 vm.startPrank(owner); serviceManager.setQuorumThreshold(3, 5); @@ -147,10 +140,8 @@ contract WavsServiceManagerTest is Test { assertTrue(true, "Validation should pass with exact quorum"); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_validateQuorumSigned_explicitSigningKeys function. function test_validateQuorumSigned_explicitSigningKeys() public { - /* solhint-enable func-name-mixedcase */ address signer1 = address(0x13579); vm.startPrank(owner); @@ -180,10 +171,8 @@ contract WavsServiceManagerTest is Test { assertTrue(true, "Validation should pass when signer is set"); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_validateQuorumSigned_zero_total_weight function. function test_validateQuorumSigned_zero_total_weight() public { - /* solhint-enable func-name-mixedcase */ // Set total weight to 0, which should always fail mockStakeRegistry.setTotalWeight(0); @@ -194,10 +183,8 @@ contract WavsServiceManagerTest is Test { ); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_setQuorumThreshold function. function test_setQuorumThreshold() public { - /* solhint-enable func-name-mixedcase */ // Change quorum to 51% vm.startPrank(owner); serviceManager.setQuorumThreshold(51, 100); @@ -222,20 +209,16 @@ contract WavsServiceManagerTest is Test { ); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_setQuorumThreshold_only_owner function. function test_setQuorumThreshold_only_owner() public { - /* solhint-enable func-name-mixedcase */ // Non-owner should not be able to set quorum threshold vm.prank(address(0x999)); vm.expectRevert("Ownable: caller is not the owner"); serviceManager.setQuorumThreshold(1, 2); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_setQuorumThreshold_invalid_params function. function test_setQuorumThreshold_invalid_params() public { - /* solhint-enable func-name-mixedcase */ // numerator = 0 vm.prank(owner); vm.expectRevert( @@ -258,10 +241,8 @@ contract WavsServiceManagerTest is Test { serviceManager.setQuorumThreshold(3, 2); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_validate_invalid_signature_length function. function test_validate_invalid_signature_length() public { - /* solhint-enable func-name-mixedcase */ // Empty operators array address[] memory emptySigners = new address[](0); bytes[] memory emptySignatures = new bytes[](0); diff --git a/contracts/test/eigenlayer/ecdsa/mocks/MockStakeRegistry.sol b/contracts/test/eigenlayer/ecdsa/mocks/MockStakeRegistry.sol index b2ec68d2..516121c6 100644 --- a/contracts/test/eigenlayer/ecdsa/mocks/MockStakeRegistry.sol +++ b/contracts/test/eigenlayer/ecdsa/mocks/MockStakeRegistry.sol @@ -68,13 +68,14 @@ contract MockStakeRegistry is IECDSAStakeRegistryErrors { totalOperators = _totalOperators; } + /* solhint-disable use-natspec */ /** * @notice The updateOperatorsForQuorum function. * @param operatorsPerQuorum The operators per quorum. * @param {_signature} The signature. * @dev This function doubles the weights of even operators and halves the weights of odd operators, for testing. */ - function updateOperatorsForQuorum( // solhint-disable-line use-natspec + function updateOperatorsForQuorum( address[][] calldata operatorsPerQuorum, bytes calldata /* _signature */ ) external virtual { @@ -103,7 +104,7 @@ contract MockStakeRegistry is IECDSAStakeRegistryErrors { * @param {signature} The signature. * @return The selector. */ - function isValidSignature( // solhint-disable-line use-natspec + function isValidSignature( bytes32, /* digest */ bytes calldata /* signature */ ) external pure returns (bytes4) { @@ -116,7 +117,7 @@ contract MockStakeRegistry is IECDSAStakeRegistryErrors { * @param {blockNumber} The block number. * @return The operator weight. */ - function getOperatorWeightAtBlock( // solhint-disable-line use-natspec + function getOperatorWeightAtBlock( address operator, uint32 /* blockNumber */ ) external view returns (uint256) { @@ -128,7 +129,7 @@ contract MockStakeRegistry is IECDSAStakeRegistryErrors { * @param {blockNumber} The block number. * @return The total weight. */ - function getLastCheckpointTotalWeightAtBlock( // solhint-disable-line use-natspec + function getLastCheckpointTotalWeightAtBlock( uint32 /* blockNumber */ ) external view returns (uint256) { return totalWeight; @@ -151,7 +152,7 @@ contract MockStakeRegistry is IECDSAStakeRegistryErrors { * @param {blockNumber} The block number. * @return The signing key. */ - function getOperatorSigningKeyAtBlock( // solhint-disable-line use-natspec + function getOperatorSigningKeyAtBlock( address operator, uint256 /* blockNumber */ ) external view returns (address) { @@ -175,7 +176,7 @@ contract MockStakeRegistry is IECDSAStakeRegistryErrors { * @param {blockNumber} The block number. * @return The operator. */ - function getOperatorForSigningKeyAtBlock( // solhint-disable-line use-natspec + function getOperatorForSigningKeyAtBlock( address signing, uint256 /* blockNumber */ ) external view returns (address) { diff --git a/contracts/test/eigenlayer/ecdsa/mocks/SimpleServiceManager.t.sol b/contracts/test/eigenlayer/ecdsa/mocks/SimpleServiceManager.t.sol index 8c90a123..9e7cc72a 100644 --- a/contracts/test/eigenlayer/ecdsa/mocks/SimpleServiceManager.t.sol +++ b/contracts/test/eigenlayer/ecdsa/mocks/SimpleServiceManager.t.sol @@ -44,7 +44,6 @@ contract SimpleServiceManagerTest is Test { /* solhint-disable func-name-mixedcase */ /// @notice The test_constructor function. function test_constructor() public view { - /* solhint-enable func-name-mixedcase */ assertEq(simpleServiceManager.getServiceURI(), ""); assertEq(simpleServiceManager.getLastCheckpointThresholdWeight(), 0); assertEq(simpleServiceManager.getLastCheckpointTotalWeight(), 0); @@ -54,10 +53,8 @@ contract SimpleServiceManagerTest is Test { // Service URI Tests // ============================================================================ - /* solhint-disable func-name-mixedcase */ /// @notice The test_setServiceURI function. function test_setServiceURI() public { - /* solhint-enable func-name-mixedcase */ string memory newURI = "https://example.com/service"; vm.expectEmit(true, true, true, true); emit IWavsServiceManager.ServiceURIUpdated(newURI); @@ -69,10 +66,8 @@ contract SimpleServiceManagerTest is Test { // Operator Weight Tests // ============================================================================ - /* solhint-disable func-name-mixedcase */ /// @notice The test_setOperatorWeight function. function test_setOperatorWeight() public { - /* solhint-enable func-name-mixedcase */ simpleServiceManager.setOperatorWeight(operator1, 100); assertEq(simpleServiceManager.getOperatorWeight(operator1), 100); } @@ -81,19 +76,15 @@ contract SimpleServiceManagerTest is Test { // Checkpoint Weight Tests // ============================================================================ - /* solhint-disable func-name-mixedcase */ /// @notice The test_setLastCheckpointThresholdWeight function. function test_setLastCheckpointThresholdWeight() public { - /* solhint-enable func-name-mixedcase */ uint256 weight = 1000; simpleServiceManager.setLastCheckpointThresholdWeight(weight); assertEq(simpleServiceManager.getLastCheckpointThresholdWeight(), weight); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_setLastCheckpointTotalWeight function. function test_setLastCheckpointTotalWeight() public { - /* solhint-enable func-name-mixedcase */ uint256 weight = 2000; simpleServiceManager.setLastCheckpointTotalWeight(weight); assertEq(simpleServiceManager.getLastCheckpointTotalWeight(), weight); @@ -103,10 +94,8 @@ contract SimpleServiceManagerTest is Test { // Validate Function Tests // ============================================================================ - /* solhint-disable func-name-mixedcase */ /// @notice The test_validate_success function. function test_validate_success() public { - /* solhint-enable func-name-mixedcase */ // Setup operators with weights simpleServiceManager.setOperatorWeight(operator1, 100); simpleServiceManager.setOperatorWeight(operator2, 200); @@ -131,10 +120,8 @@ contract SimpleServiceManagerTest is Test { simpleServiceManager.validate(testEnvelope, signatureData); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_validate_emptySigners function. function test_validate_emptySigners() public { - /* solhint-enable func-name-mixedcase */ address[] memory signers = new address[](0); bytes[] memory signatures = new bytes[](0); @@ -148,10 +135,8 @@ contract SimpleServiceManagerTest is Test { simpleServiceManager.validate(testEnvelope, signatureData); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_validate_mismatchedLengths function. function test_validate_mismatchedLengths() public { - /* solhint-enable func-name-mixedcase */ address[] memory signers = new address[](2); signers[0] = operator1; signers[1] = operator2; @@ -169,10 +154,8 @@ contract SimpleServiceManagerTest is Test { simpleServiceManager.validate(testEnvelope, signatureData); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_validate_invalidBlock function. function test_validate_invalidBlock() public { - /* solhint-enable func-name-mixedcase */ address[] memory signers = new address[](1); signers[0] = operator1; @@ -189,10 +172,8 @@ contract SimpleServiceManagerTest is Test { simpleServiceManager.validate(testEnvelope, signatureData); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_validate_unsortedOperators function. function test_validate_unsortedOperators() public { - /* solhint-enable func-name-mixedcase */ // Setup operators with weights simpleServiceManager.setOperatorWeight(operator2, 200); simpleServiceManager.setOperatorWeight(operator1, 100); @@ -217,10 +198,8 @@ contract SimpleServiceManagerTest is Test { simpleServiceManager.validate(testEnvelope, signatureData); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_validate_zeroWeight function. function test_validate_zeroWeight() public { - /* solhint-enable func-name-mixedcase */ // Setup threshold but no operator weights simpleServiceManager.setLastCheckpointThresholdWeight(100); @@ -240,10 +219,8 @@ contract SimpleServiceManagerTest is Test { simpleServiceManager.validate(testEnvelope, signatureData); } - /* solhint-disable func-name-mixedcase */ /// @notice The test_validate_insufficientQuorum function. function test_validate_insufficientQuorum() public { - /* solhint-enable func-name-mixedcase */ // Setup operators with weights simpleServiceManager.setOperatorWeight(operator1, 50); simpleServiceManager.setOperatorWeight(operator2, 100); diff --git a/docker/BLS_CLI.md b/docker/BLS_CLI.md index d264e8c9..556875f3 100644 --- a/docker/BLS_CLI.md +++ b/docker/BLS_CLI.md @@ -11,14 +11,26 @@ CHAIN=holesky cp docker/env.example.$CHAIN docker/.env ``` +## Test + +Terminal 1 + ```bash docci-background source docker/.env anvil --fork-url $FORK_RPC_URL --host 0.0.0.0 --port 8545 ``` +Terminal 2 + +```bash docci-background +anvil --host 0.0.0.0 --port 8546 +``` + +Terminal 3 + -```bash docci-output-contains="Quorum Numerator: 3" +```bash cd docker/ docker run --rm --network host -v ./.nodes:/root/.nodes \ @@ -54,3 +66,11 @@ docker run --rm --network host -v ./.nodes:/root/.nodes \ --env-file .env \ wavs-middleware -s bls list_operators ``` + +```bash +docker run --rm --network host -v ./.nodes:/root/.nodes \ + wavs-middleware -s bls -m mirror deploy + +docker run --rm --network host -v ./.nodes:/root/.nodes \ + wavs-middleware -s bls -m mirror list_operators +``` diff --git a/docker/ECDSA_CLI.md b/docker/ECDSA_CLI.md index fbc8016d..08354330 100644 --- a/docker/ECDSA_CLI.md +++ b/docker/ECDSA_CLI.md @@ -121,6 +121,17 @@ docker run --rm --network host -v ./.nodes:/root/.nodes \ docker run --rm --network host -v ./.nodes:/root/.nodes \ --env-file .env \ wavs-middleware unpause + +PROXY_OWNER=$(cast wallet new --json | jq -r '.[0].private_key') +PROXY_OWNER_ADDRESS=$(cast wallet addr --private-key "$PROXY_OWNER") +echo "Proxy owner address: $PROXY_OWNER_ADDRESS" +AVS_OWNER=$(cast wallet new --json | jq -r '.[0].private_key') +AVS_OWNER_ADDRESS=$(cast wallet addr --private-key "$AVS_OWNER") +echo "Avs owner address: $AVS_OWNER_ADDRESS" + +docker run --rm --network host -v ./.nodes:/root/.nodes \ + --env-file .env \ + wavs-middleware transfer_ownership ${PROXY_OWNER} ${AVS_OWNER} ``` ```bash @@ -128,7 +139,6 @@ MOCK_DEPLOYER_KEY=$(cast wallet new --json | jq -r '.[0].private_key') MOCK_DEPLOYER_ADDRESS=$(cast wallet addr --private-key "$MOCK_DEPLOYER_KEY") docker run --rm --network host -v ./.nodes:/root/.nodes \ - --env-file .env \ -e MOCK_DEPLOYER_KEY=${MOCK_DEPLOYER_KEY} \ wavs-middleware -m mock deploy @@ -137,4 +147,14 @@ docker run --rm --network host -v ./.nodes:/root/.nodes \ -v $LOCAL_CONFIG_PATH:/wavs/contracts/deployments/wavs-mock-config.json \ --env-file .env \ wavs-middleware -m mock configure + +PROXY_OWNER=$(cast wallet new --json | jq -r '.[0].private_key') +PROXY_OWNER_ADDRESS=$(cast wallet addr --private-key "$PROXY_OWNER") +echo "Proxy owner address: $PROXY_OWNER_ADDRESS" +AVS_OWNER=$(cast wallet new --json | jq -r '.[0].private_key') +AVS_OWNER_ADDRESS=$(cast wallet addr --private-key "$AVS_OWNER") +echo "Avs owner address: $AVS_OWNER_ADDRESS" + +docker run --rm --network host -v ./.nodes:/root/.nodes \ + wavs-middleware -m mock transfer_ownership ${PROXY_OWNER} ${AVS_OWNER} ``` diff --git a/justfile b/justfile index e817f2ef..9c91bdc3 100644 --- a/justfile +++ b/justfile @@ -5,4 +5,10 @@ help: # builds middleware docker-build TAG="local": - {{SUDO}} docker build . -t ghcr.io/lay3rlabs/wavs-middleware:{{TAG}} + {{SUDO}} docker build . -t ghcr.io/lay3rlabs/wavs-middleware:{{TAG}} + +docker-push TAG="local": + {{SUDO}} docker push ghcr.io/lay3rlabs/wavs-middleware:{{TAG}} + +dos2unix: + find ./scripts -type f -exec dos2unix {} + \ No newline at end of file diff --git a/scripts/bls/eigen/list_operators.sh b/scripts/bls/eigen/list_operators.sh index 8e96a605..874fe314 100755 --- a/scripts/bls/eigen/list_operators.sh +++ b/scripts/bls/eigen/list_operators.sh @@ -30,7 +30,7 @@ echo "Listing operators for service manager: $WAVS_SERVICE_MANAGER_ADDRESS" # List operators cd contracts || handle_error "Failed to change to contracts directory" -forge script script/eigenlayer/bls/WavsListOperators.s.sol -vvv --rpc-url "$RPC_URL" --broadcast --skip-simulation || handle_error "Failed to list operators" +forge script script/eigenlayer/bls/WavsListOperators.s.sol -vvv --rpc-url "$RPC_URL" --skip-simulation || handle_error "Failed to list operators" # Save operator list data -save_deployment_data "$HOME/.nodes/wavs-bls-list-operators.json" "$(cat "deployments/wavs-bls/list_operators.json")" +save_deployment_data "$HOME/.nodes/bls-list-operators.json" "$(cat "deployments/wavs-bls/list_operators.json")" diff --git a/scripts/bls/mirror/deploy.sh b/scripts/bls/mirror/deploy.sh new file mode 100755 index 00000000..498fc502 --- /dev/null +++ b/scripts/bls/mirror/deploy.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# -x echos all lines for debug +# set -x + +set -o errexit -o nounset -o pipefail +command -v shellcheck >/dev/null && shellcheck "$0" + +SCRIPT_DIR="$(realpath "$(dirname "$0")")" +# shellcheck source=../../helper.sh +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../helper.sh" + +# shellcheck source=../foundry_profile.sh +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../foundry_profile.sh" + +# Parse command line arguments in key=value format +parse_args "$@" + +# Check required parameters with defaults +check_param "DEPLOY_ENV" "${DEPLOY_ENV:-LOCAL}" + +# Set up environment based on DEPLOY_ENV +if [ "$DEPLOY_ENV" = "TESTNET" ]; then + check_param "SOURCE_RPC_URL" "${SOURCE_RPC_URL:-}" + check_param "MIRROR_RPC_URL" "${MIRROR_RPC_URL:-}" +else + check_param "SOURCE_RPC_URL" "${SOURCE_RPC_URL:-http://localhost:8545}" + check_param "MIRROR_RPC_URL" "${MIRROR_RPC_URL:-http://localhost:8546}" +fi + +# Read the deployer private key +deployer_private_key=$(load_deployment_data "$HOME/.nodes/deployer") +check_param "FUNDED_KEY" "${FUNDED_KEY:-$deployer_private_key}" +deployer_address=$(cast wallet address "$FUNDED_KEY") +echo "Deployer address: $deployer_address" + +# Ensure deployer has sufficient balance on mirror chain +ensure_balance "$deployer_address" "$MIRROR_RPC_URL" + +# Read service manager address from file +DEFAULT_SERVICE_MANAGER=$(jq -r '.addresses.WavsServiceManager' "$HOME/.nodes/avs_deploy.json" 2>/dev/null || true) +check_param "WAVS_SERVICE_MANAGER_ADDRESS" "${WAVS_SERVICE_MANAGER_ADDRESS:-$DEFAULT_SERVICE_MANAGER}" + +echo "Reading source chain config:" + +# Prepare deployment +cd contracts || handle_error "Failed to change to contracts directory" +forge script script/eigenlayer/bls/WavsMirrorDeployer.s.sol --private-key "$FUNDED_KEY" --rpc-url "$MIRROR_RPC_URL" -vvv --broadcast --skip-simulation || handle_error "Failed to run WavsMirrorDeployer script" + +echo "Mirror contracts deployed with addresses:" +cat "deployments/wavs-bls/mirror_deploy.json" | jq .addresses + +# Save deployment data +save_deployment_data "$HOME/.nodes/mirror.json" "$(cat "deployments/wavs-bls/mirror_deploy.json")" diff --git a/scripts/bls/mirror/list_operators.sh b/scripts/bls/mirror/list_operators.sh new file mode 100755 index 00000000..cbb248b8 --- /dev/null +++ b/scripts/bls/mirror/list_operators.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +# Script to list operators from both source and mirror chains +# This script reads operator information from the source chain and their corresponding weights from the mirror chain + +set -o errexit -o nounset -o pipefail +command -v shellcheck >/dev/null && shellcheck "$0" + +SCRIPT_DIR="$(realpath "$(dirname "$0")")" +# shellcheck source=../../helper.sh +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../helper.sh" + +# shellcheck source=../foundry_profile.sh +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../foundry_profile.sh" + +# Parse command line arguments in key=value format +parse_args "$@" + +# Check required parameters with defaults +check_param "DEPLOY_ENV" "${DEPLOY_ENV:-LOCAL}" + +# Set up RPC URLs based on environment +if [ "$DEPLOY_ENV" = "TESTNET" ]; then + check_param "SOURCE_RPC_URL" "${SOURCE_RPC_URL:-}" + check_param "MIRROR_RPC_URL" "${MIRROR_RPC_URL:-}" +else + check_param "SOURCE_RPC_URL" "${SOURCE_RPC_URL:-http://localhost:8545}" + check_param "MIRROR_RPC_URL" "${MIRROR_RPC_URL:-http://localhost:8546}" +fi + +# Get service manager addresses from environment variables or files +DEFAULT_SOURCE_SERVICE_MANAGER=$(jq -r '.addresses.WavsServiceManager' "$HOME/.nodes/avs_deploy.json" 2>/dev/null || true) +check_param "SOURCE_SERVICE_MANAGER_ADDRESS" "${SOURCE_SERVICE_MANAGER_ADDRESS:-$DEFAULT_SOURCE_SERVICE_MANAGER}" +DEFAULT_MIRROR_SERVICE_MANAGER=$(jq -r '.addresses.WavsServiceManager' "$HOME/.nodes/mirror.json" 2>/dev/null || true) +check_param "MIRROR_SERVICE_MANAGER_ADDRESS" "${MIRROR_SERVICE_MANAGER_ADDRESS:-$DEFAULT_MIRROR_SERVICE_MANAGER}" + +# Change to contracts directory and run the script +cd contracts || handle_error "Failed to change to contracts directory" +forge script script/eigenlayer/bls/WavsMirrorListOperators.s.sol -vvv --skip-simulation || handle_error "Failed to list operators" + +echo "Operator list:" +cat "deployments/wavs-bls/list_operators.json" | jq . + +# Save operator list data +save_deployment_data "$HOME/.nodes/mirror-list-operators.json" "$(cat "deployments/wavs-bls/list_operators.json")" diff --git a/scripts/ecdsa/eigen/list_operators.sh b/scripts/ecdsa/eigen/list_operators.sh index e6f1bf35..e06f5f16 100755 --- a/scripts/ecdsa/eigen/list_operators.sh +++ b/scripts/ecdsa/eigen/list_operators.sh @@ -30,4 +30,7 @@ echo "Listing operators for service manager: $WAVS_SERVICE_MANAGER_ADDRESS" # List operators cd contracts || handle_error "Failed to change to contracts directory" -forge script script/eigenlayer/ecdsa/WavsListOperators.s.sol -vvv --rpc-url "$RPC_URL" --broadcast --skip-simulation || handle_error "Failed to list operators" +forge script script/eigenlayer/ecdsa/WavsListOperators.s.sol -vvv --rpc-url "$RPC_URL" --skip-simulation || handle_error "Failed to list operators" + +# Save operator list data +save_deployment_data "$HOME/.nodes/ecdsa-list-operators.json" "$(cat "deployments/wavs-ecdsa/list_operators.json")" diff --git a/scripts/ecdsa/eigen/transfer_ownership.sh b/scripts/ecdsa/eigen/transfer_ownership.sh new file mode 100644 index 00000000..5b670b37 --- /dev/null +++ b/scripts/ecdsa/eigen/transfer_ownership.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# -x echos all lines for debug +# set -x + +set -o errexit -o nounset -o pipefail +command -v shellcheck >/dev/null && shellcheck "$0" + +SCRIPT_DIR="$(realpath "$(dirname "$0")")" +# shellcheck source=../../helper.sh +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../helper.sh" + +# shellcheck source=../foundry_profile.sh +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../foundry_profile.sh" + +# Parse command line arguments in key=value format +parse_args "$@" + +# Check required parameters with defaults +check_param "DEPLOY_ENV" "${DEPLOY_ENV:-LOCAL}" +DEFAULT_SERVICE_MANAGER=$(jq -r '.addresses.WavsServiceManager' "$HOME/.nodes/avs_deploy.json" 2>/dev/null || true) +check_param "WAVS_SERVICE_MANAGER_ADDRESS" "${WAVS_SERVICE_MANAGER_ADDRESS:-$DEFAULT_SERVICE_MANAGER}" +check_param "PROXY_OWNER" "${PROXY_OWNER:-$1}" +check_param "AVS_OWNER" "${AVS_OWNER:-$2}" + +# Set up environment based on DEPLOY_ENV +setup_environment + +# Read the deployer private key +deployer_private_key=$(load_deployment_data "$HOME/.nodes/deployer") +check_param "FUNDED_KEY" "${FUNDED_KEY:-$deployer_private_key}" +deployer_address=$(cast wallet address "$FUNDED_KEY") +echo "Deployer address: $deployer_address" + +ensure_balance "$deployer_address" + +transfer_ecdsa_ownership "$WAVS_SERVICE_MANAGER_ADDRESS" "$PROXY_OWNER" "$AVS_OWNER" "$FUNDED_KEY" "eigen" diff --git a/scripts/ecdsa/mirror/deploy.sh b/scripts/ecdsa/mirror/deploy.sh index 4ab08c62..c4d7a17f 100755 --- a/scripts/ecdsa/mirror/deploy.sh +++ b/scripts/ecdsa/mirror/deploy.sh @@ -47,7 +47,7 @@ echo "Reading source chain config:" # Prepare deployment cd contracts || handle_error "Failed to change to contracts directory" -forge script script/eigenlayer/ecdsa/WavsMirrorPrepareDeploy.s.sol --rpc-url "$SOURCE_RPC_URL" -vvv --broadcast --skip-simulation || handle_error "Failed to run WavsMirrorPrepareDeploy script" +forge script script/eigenlayer/ecdsa/WavsMirrorPrepareDeploy.s.sol --rpc-url "$SOURCE_RPC_URL" -vvv --skip-simulation || handle_error "Failed to run WavsMirrorPrepareDeploy script" echo "Got config:" cat "deployments/wavs-mirror-config.json" diff --git a/scripts/ecdsa/mirror/list_operators.sh b/scripts/ecdsa/mirror/list_operators.sh index 81b67b37..d087d6e1 100755 --- a/scripts/ecdsa/mirror/list_operators.sh +++ b/scripts/ecdsa/mirror/list_operators.sh @@ -38,7 +38,7 @@ check_param "MIRROR_SERVICE_MANAGER_ADDRESS" "${MIRROR_SERVICE_MANAGER_ADDRESS:- # Change to contracts directory and run the script cd contracts || handle_error "Failed to change to contracts directory" -forge script script/eigenlayer/ecdsa/WavsMirrorListOperators.s.sol -vvv --broadcast --skip-simulation || handle_error "Failed to list operators" +forge script script/eigenlayer/ecdsa/WavsMirrorListOperators.s.sol -vvv --skip-simulation || handle_error "Failed to list operators" echo "Operator list:" cat "deployments/wavs-ecdsa/mirror_list_operators.json" | jq . diff --git a/scripts/ecdsa/mock/transfer_ownership.sh b/scripts/ecdsa/mock/transfer_ownership.sh new file mode 100644 index 00000000..6aa3c6d5 --- /dev/null +++ b/scripts/ecdsa/mock/transfer_ownership.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# -x echos all lines for debug +# set -x + +set -o errexit -o nounset -o pipefail +command -v shellcheck >/dev/null && shellcheck "$0" + +SCRIPT_DIR="$(realpath "$(dirname "$0")")" +# shellcheck source=../../helper.sh +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../helper.sh" + +# shellcheck source=../foundry_profile.sh +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../foundry_profile.sh" + +# Parse command line arguments in key=value format +parse_args "$@" + +# Check required parameters with defaults +check_param "DEPLOY_ENV" "${DEPLOY_ENV:-LOCAL}" +DEFAULT_SERVICE_MANAGER=$(jq -r '.addresses.WavsServiceManager' "$HOME/.nodes/mock.json" 2>/dev/null || true) +check_param "WAVS_SERVICE_MANAGER_ADDRESS" "${WAVS_SERVICE_MANAGER_ADDRESS:-$DEFAULT_SERVICE_MANAGER}" +check_param "PROXY_OWNER" "${PROXY_OWNER:-$1}" +check_param "AVS_OWNER" "${AVS_OWNER:-$2}" + +# Set up environment based on DEPLOY_ENV +if [ "$DEPLOY_ENV" = "TESTNET" ]; then + check_param "RPC_URL" "${MOCK_RPC_URL:-}" +else + check_param "RPC_URL" "${MOCK_RPC_URL:-http://localhost:8546}" +fi + +# Read the deployer private key +deployer_private_key=$(load_deployment_data "$HOME/.nodes/mock-deployer") +check_param "MOCK_DEPLOYER_KEY" "${MOCK_DEPLOYER_KEY:-$deployer_private_key}" +mock_deployer_address=$(cast wallet address "$MOCK_DEPLOYER_KEY") +echo "Deployer address: $mock_deployer_address" + +ensure_balance "$mock_deployer_address" + +transfer_ecdsa_ownership "$WAVS_SERVICE_MANAGER_ADDRESS" "$PROXY_OWNER" "$AVS_OWNER" "$MOCK_DEPLOYER_KEY" "mock" diff --git a/scripts/helper.sh b/scripts/helper.sh index 5954da82..f9eb20a3 100644 --- a/scripts/helper.sh +++ b/scripts/helper.sh @@ -144,3 +144,54 @@ load_deployment_data() { fi fi } + +transfer_ecdsa_ownership() { + # Arguments (all optional, will fallback to env/params if not provided) + # $1 - WAVS_SERVICE_MANAGER_ADDRESS + # $2 - PROXY_OWNER + # $3 - AVS_OWNER + # $4 - FUNDED_KEY + + local wsm_address="${1}" + local proxy_owner="${2}" + local avs_owner="${3}" + local funded_key="${4}" + local mode="${5}" + + local avs_registrar_address + if [ "$mode" == "eigen" ]; then + # Get the AllocationManager address from the WAVS Service Manager + local allocation_manager_address + allocation_manager_address=$(cast call "$wsm_address" "getAllocationManager()(address)" --rpc-url "$RPC_URL") + echo "Allocation manager address: $allocation_manager_address" + # Get the AVS Registrar address from the AllocationManager for the WAVS Service Manager + avs_registrar_address=$(cast call "$allocation_manager_address" "getAVSRegistrar(address)(address)" "$wsm_address" --rpc-url "$RPC_URL") + echo "AVS registrar address: $avs_registrar_address" + fi + # Get the StakeRegistry address from the WAVS Service Manager + local stake_registry_address + stake_registry_address=$(cast call "$wsm_address" "getStakeRegistry()(address)" --rpc-url "$RPC_URL") + echo "Stake registry address: $stake_registry_address" + + # Get the ProxyAdmin of the WAVS Service Manager + local proxy_admin_address + proxy_admin_address=$(cast storage "$wsm_address" "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103" --rpc-url "$RPC_URL" | tail -n1 | sed 's/^0x000000000000000000000000//') + proxy_admin_address="0x$proxy_admin_address" + echo "Proxy admin address: $proxy_admin_address" + + echo "Transferring proxy ownership to $proxy_owner" + cast send "$proxy_admin_address" "transferOwnership(address)" "$proxy_owner" --private-key "$funded_key" --rpc-url "$RPC_URL" + + if [ "$mode" == "eigen" ]; then + echo "Transferring avs registrar ownership to $avs_owner" + cast send "$avs_registrar_address" "transferOwnership(address)" "$avs_owner" --private-key "$funded_key" --rpc-url "$RPC_URL" + fi + + echo "Transferring stake registry ownership to $avs_owner" + cast send "$stake_registry_address" "transferOwnership(address)" "$avs_owner" --private-key "$funded_key" --rpc-url "$RPC_URL" + + echo "Transferring allocation manager ownership to $avs_owner" + cast send "$wsm_address" "transferOwnership(address)" "$avs_owner" --private-key "$funded_key" --rpc-url "$RPC_URL" + + echo "Ownership transferred successfully" +}