From 8c128b1f07fc9dcbc0ee818dbc5a414aaafe8e7c Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 24 Jul 2025 16:43:52 +0200 Subject: [PATCH 1/4] feat: tablecalc modules --- src/libraries/WeightCapUtils.sol | 76 ++++ .../BN254TableCalculatorBase.sol | 2 +- .../BN254TableCalculatorWithCaps.sol | 121 +++++ .../BN254WeightedTableCalculator.sol | 167 +++++++ test/unit/libraries/WeightCapUtilsUnit.t.sol | 204 +++++++++ .../BN254TableCalculatorWithCapsUnit.t.sol | 318 +++++++++++++ .../BN254WeightedTableCalculatorUnit.t.sol | 418 ++++++++++++++++++ 7 files changed, 1305 insertions(+), 1 deletion(-) create mode 100644 src/libraries/WeightCapUtils.sol create mode 100644 src/middlewareV2/tableCalculator/BN254TableCalculatorWithCaps.sol create mode 100644 src/middlewareV2/tableCalculator/BN254WeightedTableCalculator.sol create mode 100644 test/unit/libraries/WeightCapUtilsUnit.t.sol create mode 100644 test/unit/middlewareV2/BN254TableCalculatorWithCapsUnit.t.sol create mode 100644 test/unit/middlewareV2/BN254WeightedTableCalculatorUnit.t.sol diff --git a/src/libraries/WeightCapUtils.sol b/src/libraries/WeightCapUtils.sol new file mode 100644 index 000000000..760c4711f --- /dev/null +++ b/src/libraries/WeightCapUtils.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +/** + * @title WeightCapUtils + * @notice Utility library for applying weight caps to operator weights + */ +library WeightCapUtils { + /** + * @notice Apply weight caps to operator weights + * @param operators Array of operator addresses + * @param weights 2D array of weights for each operator + * @param maxWeight Maximum allowed total weight per operator (0 = no cap) + * @return cappedOperators Array of operators after filtering + * @return cappedWeights Array of weights after applying caps + * @dev Caps total weight at maxWeight, filters out zero-weight operators + */ + function applyWeightCap( + address[] memory operators, + uint256[][] memory weights, + uint256 maxWeight + ) internal pure returns (address[] memory cappedOperators, uint256[][] memory cappedWeights) { + require(operators.length == weights.length, "WeightCapUtils: length mismatch"); + + if (maxWeight == 0 || operators.length == 0) { + return (operators, weights); + } + + // Count operators with non-zero weights + uint256 validOperatorCount = 0; + bool[] memory isValid = new bool[](operators.length); + + for (uint256 i = 0; i < operators.length; i++) { + uint256 totalWeight = 0; + for (uint256 j = 0; j < weights[i].length; j++) { + totalWeight += weights[i][j]; + } + + if (totalWeight > 0) { + isValid[i] = true; + validOperatorCount++; + } + } + + // Initialize result arrays + cappedOperators = new address[](validOperatorCount); + cappedWeights = new uint256[][](validOperatorCount); + + uint256 resultIndex = 0; + for (uint256 i = 0; i < operators.length; i++) { + if (!isValid[i]) continue; + + uint256 totalWeight = 0; + for (uint256 j = 0; j < weights[i].length; j++) { + totalWeight += weights[i][j]; + } + + cappedOperators[resultIndex] = operators[i]; + cappedWeights[resultIndex] = new uint256[](weights[i].length); + + if (totalWeight <= maxWeight) { + for (uint256 j = 0; j < weights[i].length; j++) { + cappedWeights[resultIndex][j] = weights[i][j]; + } + } else { + // Cap at maxWeight, zero out additional weight types + cappedWeights[resultIndex][0] = maxWeight; + for (uint256 j = 1; j < weights[i].length; j++) { + cappedWeights[resultIndex][j] = 0; + } + } + + resultIndex++; + } + } +} \ No newline at end of file diff --git a/src/middlewareV2/tableCalculator/BN254TableCalculatorBase.sol b/src/middlewareV2/tableCalculator/BN254TableCalculatorBase.sol index fecb81360..3eaf143e6 100644 --- a/src/middlewareV2/tableCalculator/BN254TableCalculatorBase.sol +++ b/src/middlewareV2/tableCalculator/BN254TableCalculatorBase.sol @@ -147,7 +147,7 @@ abstract contract BN254TableCalculatorBase is IBN254TableCalculator { totalWeights[j] += weights[i][j]; } (BN254.G1Point memory g1Point,) = keyRegistrar.getBN254Key(operatorSet, operators[i]); - operatorInfoLeaves[i] = + operatorInfoLeaves[operatorCount] = keccak256(abi.encode(BN254OperatorInfo({pubkey: g1Point, weights: weights[i]}))); // Add the operator's G1 point to the aggregate pubkey diff --git a/src/middlewareV2/tableCalculator/BN254TableCalculatorWithCaps.sol b/src/middlewareV2/tableCalculator/BN254TableCalculatorWithCaps.sol new file mode 100644 index 000000000..0884e9338 --- /dev/null +++ b/src/middlewareV2/tableCalculator/BN254TableCalculatorWithCaps.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; +import {IKeyRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IKeyRegistrar.sol"; +import {IPermissionController} from "eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol"; +import {PermissionControllerMixin} from "eigenlayer-contracts/src/contracts/mixins/PermissionControllerMixin.sol"; + +import "./BN254TableCalculatorBase.sol"; +import {WeightCapUtils} from "../../libraries/WeightCapUtils.sol"; + +/** + * @title BN254TableCalculatorWithCaps + * @notice BN254 table calculator with configurable weight caps + * @dev Extends the basic table calculator to cap operator weights + */ +contract BN254TableCalculatorWithCaps is BN254TableCalculatorBase, PermissionControllerMixin { + // Immutables + /// @notice AllocationManager contract for managing operator allocations + IAllocationManager public immutable allocationManager; + /// @notice The default lookahead blocks for the slashable stake lookup + uint256 public immutable LOOKAHEAD_BLOCKS; + + // Storage + /// @notice Mapping from operatorSet hash to weight cap (0 = no cap) + mapping(bytes32 => uint256) public weightCaps; + + // Events + /// @notice Emitted when a weight cap is set for an operator set + event WeightCapSet(OperatorSet indexed operatorSet, uint256 maxWeight); + + constructor( + IKeyRegistrar _keyRegistrar, + IAllocationManager _allocationManager, + IPermissionController _permissionController, + uint256 _LOOKAHEAD_BLOCKS + ) BN254TableCalculatorBase(_keyRegistrar) PermissionControllerMixin(_permissionController) { + allocationManager = _allocationManager; + LOOKAHEAD_BLOCKS = _LOOKAHEAD_BLOCKS; + } + + /** + * @notice Set the weight cap for a given operator set + * @param operatorSet The operator set to set the cap for + * @param maxWeight Maximum allowed total weight per operator (0 = no cap) + * @dev Only the AVS can set caps for their operator sets + */ + function setWeightCap(OperatorSet calldata operatorSet, uint256 maxWeight) external checkCanCall(operatorSet.avs) { + bytes32 operatorSetHash = keccak256(abi.encode(operatorSet.avs, operatorSet.id)); + weightCaps[operatorSetHash] = maxWeight; + + emit WeightCapSet(operatorSet, maxWeight); + } + + /** + * @notice Get the weight cap for a given operator set + * @param operatorSet The operator set to get the cap for + * @return maxWeight The maximum weight cap (0 = no cap) + */ + function getWeightCap(OperatorSet calldata operatorSet) external view returns (uint256 maxWeight) { + bytes32 operatorSetHash = keccak256(abi.encode(operatorSet.avs, operatorSet.id)); + return weightCaps[operatorSetHash]; + } + + /** + * @notice Get operator weights with caps applied + * @param operatorSet The operator set to calculate weights for + * @return operators Array of operator addresses + * @return weights Array of weights per operator + */ + function _getOperatorWeights( + OperatorSet calldata operatorSet + ) internal view override returns (address[] memory operators, uint256[][] memory weights) { + // Get all operators & strategies in the operatorSet + address[] memory registeredOperators = allocationManager.getMembers(operatorSet); + IStrategy[] memory strategies = allocationManager.getStrategiesInOperatorSet(operatorSet); + + // Get the minimum slashable stake for each operator + uint256[][] memory minSlashableStake = allocationManager.getMinimumSlashableStake({ + operatorSet: operatorSet, + operators: registeredOperators, + strategies: strategies, + futureBlock: uint32(block.number + LOOKAHEAD_BLOCKS) + }); + + operators = new address[](registeredOperators.length); + weights = new uint256[][](registeredOperators.length); + uint256 operatorCount = 0; + for (uint256 i = 0; i < registeredOperators.length; ++i) { + uint256 totalWeight; + for (uint256 stratIndex = 0; stratIndex < strategies.length; ++stratIndex) { + totalWeight += minSlashableStake[i][stratIndex]; + } + + if (totalWeight > 0) { + weights[operatorCount] = new uint256[](1); + weights[operatorCount][0] = totalWeight; + operators[operatorCount] = registeredOperators[i]; + operatorCount++; + } + } + + assembly { + mstore(operators, operatorCount) + mstore(weights, operatorCount) + } + + // Apply weight caps if configured + bytes32 operatorSetHash = keccak256(abi.encode(operatorSet.avs, operatorSet.id)); + uint256 maxWeight = weightCaps[operatorSetHash]; + + if (maxWeight > 0) { + (operators, weights) = WeightCapUtils.applyWeightCap(operators, weights, maxWeight); + } + + return (operators, weights); + } +} \ No newline at end of file diff --git a/src/middlewareV2/tableCalculator/BN254WeightedTableCalculator.sol b/src/middlewareV2/tableCalculator/BN254WeightedTableCalculator.sol new file mode 100644 index 000000000..98ee2cb34 --- /dev/null +++ b/src/middlewareV2/tableCalculator/BN254WeightedTableCalculator.sol @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; +import {IKeyRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IKeyRegistrar.sol"; +import {IPermissionController} from "eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol"; +import {PermissionControllerMixin} from "eigenlayer-contracts/src/contracts/mixins/PermissionControllerMixin.sol"; + +import "./BN254TableCalculatorBase.sol"; + +/** + * @title BN254WeightedTableCalculator + * @notice Implementation that calculates BN254 operator tables using custom multipliers for different strategies + * @dev This contract allows AVSs to set custom multipliers for each strategy instead of weighting all strategies equally. + */ +contract BN254WeightedTableCalculator is BN254TableCalculatorBase, PermissionControllerMixin { + // Immutables + /// @notice AllocationManager contract for managing operator allocations + IAllocationManager public immutable allocationManager; + /// @notice The default lookahead blocks for the slashable stake lookup + uint256 public immutable LOOKAHEAD_BLOCKS; + + // Storage + /// @notice Mapping from operatorSet hash to strategy to multiplier (in basis points, 10000 = 1x) + mapping(bytes32 => mapping(IStrategy => uint256)) public strategyMultipliers; + + // Events + /// @notice Emitted when strategy multipliers are updated for an operator set + event StrategyMultipliersUpdated( + OperatorSet indexed operatorSet, + IStrategy[] strategies, + uint256[] multipliers + ); + + // Errors + error ArrayLengthMismatch(); + + constructor( + IKeyRegistrar _keyRegistrar, + IAllocationManager _allocationManager, + IPermissionController _permissionController, + uint256 _LOOKAHEAD_BLOCKS + ) BN254TableCalculatorBase(_keyRegistrar) PermissionControllerMixin(_permissionController) { + allocationManager = _allocationManager; + LOOKAHEAD_BLOCKS = _LOOKAHEAD_BLOCKS; + } + + /** + * @notice Set strategy multipliers for a given operator set + * @param operatorSet The operator set to set multipliers for + * @param strategies Array of strategies to set multipliers for + * @param multipliers Array of multipliers in basis points (10000 = 1x) + * @dev Only the AVS can set multipliers for their operator sets + */ + function setStrategyMultipliers( + OperatorSet calldata operatorSet, + IStrategy[] calldata strategies, + uint256[] calldata multipliers + ) external checkCanCall(operatorSet.avs) { + // Validate input arrays + if (strategies.length != multipliers.length) { + revert ArrayLengthMismatch(); + } + + bytes32 operatorSetHash = keccak256(abi.encode(operatorSet.avs, operatorSet.id)); + + // Set multipliers for each strategy + for (uint256 i = 0; i < strategies.length; i++) { + strategyMultipliers[operatorSetHash][strategies[i]] = multipliers[i]; + strategyMultipliersSet[operatorSetHash][strategies[i]] = true; + } + + emit StrategyMultipliersUpdated(operatorSet, strategies, multipliers); + } + + // Storage to track which strategies have been explicitly set + mapping(bytes32 => mapping(IStrategy => bool)) public strategyMultipliersSet; + + /** + * @notice Get the strategy multiplier for a given operator set and strategy + * @param operatorSet The operator set + * @param strategy The strategy + * @return multiplier The multiplier in basis points (returns 10000 if not set) + */ + function getStrategyMultiplier( + OperatorSet calldata operatorSet, + IStrategy strategy + ) external view returns (uint256 multiplier) { + bytes32 operatorSetHash = keccak256(abi.encode(operatorSet.avs, operatorSet.id)); + if (strategyMultipliersSet[operatorSetHash][strategy]) { + multiplier = strategyMultipliers[operatorSetHash][strategy]; + } else { + multiplier = 10000; // Default 1x multiplier + } + } + + + + /** + * @notice Get the operator weights for a given operatorSet based on weighted slashable stake. + * @param operatorSet The operatorSet to get the weights for + * @return operators The addresses of the operators in the operatorSet + * @return weights The weights for each operator in the operatorSet, this is a 2D array where the first index is the operator + * and the second index is the type of weight. In this case its of length 1 and returns the weighted slashable stake for the operatorSet. + */ + function _getOperatorWeights( + OperatorSet calldata operatorSet + ) internal view override returns (address[] memory operators, uint256[][] memory weights) { + // Get all operators & strategies in the operatorSet + address[] memory registeredOperators = allocationManager.getMembers(operatorSet); + IStrategy[] memory strategies = allocationManager.getStrategiesInOperatorSet(operatorSet); + + // Get the minimum slashable stake for each operator + uint256[][] memory minSlashableStake = allocationManager.getMinimumSlashableStake({ + operatorSet: operatorSet, + operators: registeredOperators, + strategies: strategies, + futureBlock: uint32(block.number + LOOKAHEAD_BLOCKS) + }); + + bytes32 operatorSetHash = keccak256(abi.encode(operatorSet.avs, operatorSet.id)); + + operators = new address[](registeredOperators.length); + weights = new uint256[][](registeredOperators.length); + uint256 operatorCount = 0; + for (uint256 i = 0; i < registeredOperators.length; ++i) { + // For the given operator, loop through the strategies and apply multipliers before summing + uint256 totalWeight; + for (uint256 stratIndex = 0; stratIndex < strategies.length; ++stratIndex) { + uint256 stakeAmount = minSlashableStake[i][stratIndex]; + + // Get the multiplier for this strategy (default to 10000 if not set) + uint256 multiplier; + if (strategyMultipliersSet[operatorSetHash][strategies[stratIndex]]) { + multiplier = strategyMultipliers[operatorSetHash][strategies[stratIndex]]; + } else { + multiplier = 10000; // Default 1x multiplier + } + + // Apply multiplier (divide by 10000 to convert from basis points) + totalWeight += (stakeAmount * multiplier) / 10000; + } + + // If the operator has nonzero weighted stake, add them to the operators array + if (totalWeight > 0) { + // Initialize operator weights array of length 1 just for weighted slashable stake + weights[operatorCount] = new uint256[](1); + weights[operatorCount][0] = totalWeight; + + // Add the operator to the operators array + operators[operatorCount] = registeredOperators[i]; + operatorCount++; + } + } + + // Resize arrays to be the size of the number of operators with nonzero weighted stake + assembly { + mstore(operators, operatorCount) + mstore(weights, operatorCount) + } + + return (operators, weights); + } +} \ No newline at end of file diff --git a/test/unit/libraries/WeightCapUtilsUnit.t.sol b/test/unit/libraries/WeightCapUtilsUnit.t.sol new file mode 100644 index 000000000..fdda26ad0 --- /dev/null +++ b/test/unit/libraries/WeightCapUtilsUnit.t.sol @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "forge-std/Test.sol"; +import {WeightCapUtils} from "../../../src/libraries/WeightCapUtils.sol"; + +/** + * @title WeightCapUtilsUnitTests + * @notice Unit tests for WeightCapUtils library + */ +contract WeightCapUtilsUnitTests is Test { + // Test addresses + address public operator1 = address(0x1); + address public operator2 = address(0x2); + address public operator3 = address(0x3); + + function _createSingleWeights( + uint256[] memory weights + ) internal pure returns (uint256[][] memory) { + uint256[][] memory result = new uint256[][](weights.length); + for (uint256 i = 0; i < weights.length; i++) { + result[i] = new uint256[](1); + result[i][0] = weights[i]; + } + return result; + } + + function _extractTotalWeights( + uint256[][] memory weights + ) internal pure returns (uint256[] memory) { + uint256[] memory totals = new uint256[](weights.length); + for (uint256 i = 0; i < weights.length; i++) { + for (uint256 j = 0; j < weights[i].length; j++) { + totals[i] += weights[i][j]; + } + } + return totals; + } + + function test_applyWeightCap_noCap() public { + address[] memory operators = new address[](2); + operators[0] = operator1; + operators[1] = operator2; + + uint256[] memory weights = new uint256[](2); + weights[0] = 100 ether; + weights[1] = 200 ether; + + (address[] memory resultOperators, uint256[][] memory resultWeights) = + WeightCapUtils.applyWeightCap(operators, _createSingleWeights(weights), 0); + + // Should be unchanged with no cap + assertEq(resultOperators.length, 2); + assertEq(resultOperators[0], operator1); + assertEq(resultOperators[1], operator2); + + uint256[] memory resultTotals = _extractTotalWeights(resultWeights); + assertEq(resultTotals[0], 100 ether); + assertEq(resultTotals[1], 200 ether); + } + + function test_applyWeightCap_someOperatorsCapped() public { + address[] memory operators = new address[](3); + operators[0] = operator1; + operators[1] = operator2; + operators[2] = operator3; + + uint256[] memory weights = new uint256[](3); + weights[0] = 50 ether; // Under cap + weights[1] = 150 ether; // Over cap + weights[2] = 200 ether; // Over cap + + (address[] memory resultOperators, uint256[][] memory resultWeights) = + WeightCapUtils.applyWeightCap(operators, _createSingleWeights(weights), 100 ether); + + assertEq(resultOperators.length, 3); + assertEq(resultOperators[0], operator1); + assertEq(resultOperators[1], operator2); + assertEq(resultOperators[2], operator3); + uint256[] memory resultTotals = _extractTotalWeights(resultWeights); + assertEq(resultTotals[0], 50 ether); // Unchanged (under cap) + assertEq(resultTotals[1], 100 ether); // Capped from 150 + assertEq(resultTotals[2], 100 ether); // Capped from 200 + } + + function test_applyWeightCap_allOperatorsUnderCap() public { + address[] memory operators = new address[](3); + operators[0] = operator1; + operators[1] = operator2; + operators[2] = operator3; + + uint256[] memory weights = new uint256[](3); + weights[0] = 50 ether; + weights[1] = 75 ether; + weights[2] = 90 ether; + + (address[] memory resultOperators, uint256[][] memory resultWeights) = + WeightCapUtils.applyWeightCap(operators, _createSingleWeights(weights), 100 ether); + + assertEq(resultOperators.length, 3); + + uint256[] memory resultTotals = _extractTotalWeights(resultWeights); + assertEq(resultTotals[0], 50 ether); + assertEq(resultTotals[1], 75 ether); + assertEq(resultTotals[2], 90 ether); + } + + function test_applyWeightCap_zeroWeightOperatorsFiltered() public { + address[] memory operators = new address[](4); + operators[0] = operator1; + operators[1] = operator2; + operators[2] = operator3; + operators[3] = address(0x4); + + uint256[] memory weights = new uint256[](4); + weights[0] = 100 ether; + weights[1] = 0; // Zero weight + weights[2] = 150 ether; + weights[3] = 0; // Zero weight + + (address[] memory resultOperators, uint256[][] memory resultWeights) = + WeightCapUtils.applyWeightCap(operators, _createSingleWeights(weights), 120 ether); + + // Only non-zero weight operators should remain + assertEq(resultOperators.length, 2); + assertEq(resultOperators[0], operator1); + assertEq(resultOperators[1], operator3); + + uint256[] memory resultTotals = _extractTotalWeights(resultWeights); + assertEq(resultTotals[0], 100 ether); // Under cap + assertEq(resultTotals[1], 120 ether); // Capped from 150 + } + + function test_applyWeightCap_emptyOperators() public { + address[] memory operators = new address[](0); + uint256[][] memory weights = new uint256[][](0); + + (address[] memory resultOperators, uint256[][] memory resultWeights) = + WeightCapUtils.applyWeightCap(operators, weights, 100 ether); + + assertEq(resultOperators.length, 0); + assertEq(resultWeights.length, 0); + } + + function test_applyWeightCap_multiDimensionalWeights() public { + address[] memory operators = new address[](2); + operators[0] = operator1; + operators[1] = operator2; + + // Create 2D weights where each operator has 2 weight types + uint256[][] memory weights = new uint256[][](2); + weights[0] = new uint256[](2); + weights[0][0] = 60 ether; // operator1: 60 + 40 = 100 total (at cap) + weights[0][1] = 40 ether; + weights[1] = new uint256[](2); + weights[1][0] = 120 ether; // operator2: 120 + 80 = 200 total (over cap) + weights[1][1] = 80 ether; + + (address[] memory resultOperators, uint256[][] memory resultWeights) = + WeightCapUtils.applyWeightCap(operators, weights, 100 ether); + + assertEq(resultOperators.length, 2); + + // operator1 should be unchanged (total = 100, exactly at cap) + assertEq(resultWeights[0][0], 60 ether); + assertEq(resultWeights[0][1], 40 ether); + + // operator2 should be capped: primary weight = 100, secondary = 0 + assertEq(resultWeights[1][0], 100 ether); // Capped to maxWeight + assertEq(resultWeights[1][1], 0); // Zeroed out + + // Verify total weights + uint256[] memory resultTotals = _extractTotalWeights(resultWeights); + assertEq(resultTotals[0], 100 ether); + assertEq(resultTotals[1], 100 ether); + } + + function test_applyWeightCap_simpleTruncation() public { + address[] memory operators = new address[](1); + operators[0] = operator1; + + // Create weights that exceed the cap + uint256[][] memory weights = new uint256[][](1); + weights[0] = new uint256[](3); + weights[0][0] = 300 ether; // Primary weight + weights[0][1] = 200 ether; // Secondary weight + weights[0][2] = 100 ether; // Tertiary weight + // Total: 600 ether, should be capped to 150 ether + + (address[] memory resultOperators, uint256[][] memory resultWeights) = + WeightCapUtils.applyWeightCap(operators, weights, 150 ether); + + assertEq(resultOperators.length, 1); + + // Check simple truncation: primary weight = cap, others = 0 + assertEq(resultWeights[0][0], 150 ether); // Capped to maxWeight + assertEq(resultWeights[0][1], 0); // Zeroed out + assertEq(resultWeights[0][2], 0); // Zeroed out + + // Verify total + uint256 total = resultWeights[0][0] + resultWeights[0][1] + resultWeights[0][2]; + assertEq(total, 150 ether); + } +} \ No newline at end of file diff --git a/test/unit/middlewareV2/BN254TableCalculatorWithCapsUnit.t.sol b/test/unit/middlewareV2/BN254TableCalculatorWithCapsUnit.t.sol new file mode 100644 index 000000000..a19b6ca2b --- /dev/null +++ b/test/unit/middlewareV2/BN254TableCalculatorWithCapsUnit.t.sol @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import { + KeyRegistrar, + IKeyRegistrarTypes +} from "eigenlayer-contracts/src/contracts/permissions/KeyRegistrar.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IKeyRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IKeyRegistrar.sol"; +import {IPermissionController} from "eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol"; +import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; +import { + OperatorSet, + OperatorSetLib +} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; +import {PermissionControllerMixin} from "eigenlayer-contracts/src/contracts/mixins/PermissionControllerMixin.sol"; + +import {BN254TableCalculatorWithCaps} from + "../../../src/middlewareV2/tableCalculator/BN254TableCalculatorWithCaps.sol"; +import {BN254TableCalculatorBase} from + "../../../src/middlewareV2/tableCalculator/BN254TableCalculatorBase.sol"; +import {MockEigenLayerDeployer} from "./MockDeployer.sol"; + +/** + * @title BN254TableCalculatorWithCapsUnitTests + * @notice Unit tests for BN254TableCalculatorWithCaps + */ +contract BN254TableCalculatorWithCapsUnitTests is MockEigenLayerDeployer { + using OperatorSetLib for OperatorSet; + + // Test contracts + BN254TableCalculatorWithCaps public calculator; + + + IStrategy public strategy1 = IStrategy(address(0x100)); + IStrategy public strategy2 = IStrategy(address(0x200)); + OperatorSet public operatorSet; + + // Test addresses + address public avs1 = address(0x1); + address public operator1 = address(0x3); + address public operator2 = address(0x4); + address public operator3 = address(0x5); + address public unauthorizedCaller = address(0x999); + + // Test constants + uint256 public constant TEST_LOOKAHEAD_BLOCKS = 100; + + event WeightCapSet(OperatorSet indexed operatorSet, uint256 maxWeight); + + function setUp() public virtual { + _deployMockEigenLayer(); + + // Deploy calculator + calculator = new BN254TableCalculatorWithCaps( + IKeyRegistrar(address(keyRegistrarMock)), + IAllocationManager(address(allocationManagerMock)), + IPermissionController(address(permissionController)), + TEST_LOOKAHEAD_BLOCKS + ); + + // Set up operator set + operatorSet = OperatorSet({avs: avs1, id: 1}); + } + + function _setupOperatorSet( + OperatorSet memory opSet, + address[] memory operators, + IStrategy[] memory strategies, + uint256[][] memory minSlashableStake + ) internal { + allocationManagerMock.setMembersInOperatorSet(opSet, operators); + allocationManagerMock.setStrategiesInOperatorSet(opSet, strategies); + allocationManagerMock.setMinimumSlashableStake( + opSet, operators, strategies, minSlashableStake + ); + } + + /*////////////////////////////////////////////////////////////// + WEIGHT CAP CONFIGURATION TESTS + //////////////////////////////////////////////////////////////*/ + + function test_setWeightCap_success() public { + uint256 maxWeight = 100 ether; + + vm.expectEmit(true, false, false, true); + emit WeightCapSet(operatorSet, maxWeight); + + vm.prank(avs1); + calculator.setWeightCap(operatorSet, maxWeight); + assertEq(calculator.getWeightCap(operatorSet), maxWeight); + } + + function test_setWeightCap_revertsOnUnauthorized() public { + vm.prank(unauthorizedCaller); + vm.expectRevert(PermissionControllerMixin.InvalidPermissions.selector); + calculator.setWeightCap(operatorSet, 100 ether); + } + + function test_setWeightCap_allowsZeroCap() public { + // Set a cap first + vm.prank(avs1); + calculator.setWeightCap(operatorSet, 100 ether); + assertEq(calculator.getWeightCap(operatorSet), 100 ether); + + // Remove the cap by setting to 0 + vm.expectEmit(true, false, false, true); + emit WeightCapSet(operatorSet, 0); + + vm.prank(avs1); + calculator.setWeightCap(operatorSet, 0); + + assertEq(calculator.getWeightCap(operatorSet), 0); + } + + function test_getWeightCap_returnsZeroByDefault() public { + uint256 cap = calculator.getWeightCap(operatorSet); + assertEq(cap, 0); + } + + /*////////////////////////////////////////////////////////////// + WEIGHT CALCULATION TESTS + //////////////////////////////////////////////////////////////*/ + + function test_getOperatorWeights_withoutCap() public { + // Setup operators and strategies + address[] memory operators = new address[](3); + operators[0] = operator1; + operators[1] = operator2; + operators[2] = operator3; + + IStrategy[] memory strategies = new IStrategy[](1); + strategies[0] = strategy1; + + // Set up stakes: different amounts + uint256[][] memory stakes = new uint256[][](3); + stakes[0] = new uint256[](1); + stakes[0][0] = 50 ether; + stakes[1] = new uint256[](1); + stakes[1][0] = 150 ether; + stakes[2] = new uint256[](1); + stakes[2][0] = 300 ether; + + _setupOperatorSet(operatorSet, operators, strategies, stakes); + + // Set key registrations + keyRegistrarMock.setIsRegistered(operator1, operatorSet, true); + keyRegistrarMock.setIsRegistered(operator2, operatorSet, true); + keyRegistrarMock.setIsRegistered(operator3, operatorSet, true); + + // No cap set (default 0) + (address[] memory resultOperators, uint256[][] memory resultWeights) = + calculator.getOperatorSetWeights(operatorSet); + + // Should return uncapped weights + assertEq(resultOperators.length, 3); + assertEq(resultOperators[0], operator1); + assertEq(resultOperators[1], operator2); + assertEq(resultOperators[2], operator3); + assertEq(resultWeights[0][0], 50 ether); + assertEq(resultWeights[1][0], 150 ether); + assertEq(resultWeights[2][0], 300 ether); + } + + function test_getOperatorWeights_withCap() public { + // Setup operators and strategies + address[] memory operators = new address[](3); + operators[0] = operator1; + operators[1] = operator2; + operators[2] = operator3; + + IStrategy[] memory strategies = new IStrategy[](1); + strategies[0] = strategy1; + + // Set up stakes: different amounts + uint256[][] memory stakes = new uint256[][](3); + stakes[0] = new uint256[](1); + stakes[0][0] = 50 ether; // Under cap + stakes[1] = new uint256[](1); + stakes[1][0] = 150 ether; // Over cap + stakes[2] = new uint256[](1); + stakes[2][0] = 300 ether; // Way over cap + + _setupOperatorSet(operatorSet, operators, strategies, stakes); + + // Set key registrations + keyRegistrarMock.setIsRegistered(operator1, operatorSet, true); + keyRegistrarMock.setIsRegistered(operator2, operatorSet, true); + keyRegistrarMock.setIsRegistered(operator3, operatorSet, true); + + // Set weight cap + vm.prank(avs1); + calculator.setWeightCap(operatorSet, 100 ether); + + // Get weights (should be capped automatically) + (address[] memory resultOperators, uint256[][] memory resultWeights) = + calculator.getOperatorSetWeights(operatorSet); + + // Should return capped weights + assertEq(resultOperators.length, 3); + assertEq(resultOperators[0], operator1); + assertEq(resultOperators[1], operator2); + assertEq(resultOperators[2], operator3); + assertEq(resultWeights[0][0], 50 ether); // Unchanged (under cap) + assertEq(resultWeights[1][0], 100 ether); // Capped from 150 + assertEq(resultWeights[2][0], 100 ether); // Capped from 300 + } + + function test_getOperatorWeights_zeroWeightOperatorsFiltered() public { + // Setup operators where one has zero weight + address[] memory operators = new address[](3); + operators[0] = operator1; + operators[1] = operator2; + operators[2] = operator3; + + IStrategy[] memory strategies = new IStrategy[](1); + strategies[0] = strategy1; + + uint256[][] memory stakes = new uint256[][](3); + stakes[0] = new uint256[](1); + stakes[0][0] = 100 ether; + stakes[1] = new uint256[](1); + stakes[1][0] = 0; // Zero weight + stakes[2] = new uint256[](1); + stakes[2][0] = 200 ether; + + _setupOperatorSet(operatorSet, operators, strategies, stakes); + + // Set key registrations + keyRegistrarMock.setIsRegistered(operator1, operatorSet, true); + keyRegistrarMock.setIsRegistered(operator2, operatorSet, true); + keyRegistrarMock.setIsRegistered(operator3, operatorSet, true); + + // Set weight cap + vm.prank(avs1); + calculator.setWeightCap(operatorSet, 150 ether); + + (address[] memory resultOperators, uint256[][] memory resultWeights) = + calculator.getOperatorSetWeights(operatorSet); + + // Should only include non-zero weight operators, with caps applied + assertEq(resultOperators.length, 2); + assertEq(resultOperators[0], operator1); + assertEq(resultOperators[1], operator3); + assertEq(resultWeights[0][0], 100 ether); // Under cap + assertEq(resultWeights[1][0], 150 ether); // Capped from 200 + } + + /*////////////////////////////////////////////////////////////// + INTEGRATION TESTS + //////////////////////////////////////////////////////////////*/ + + function test_calculateOperatorTable_withCaps() public { + // Setup a complete scenario with caps applied + address[] memory operators = new address[](2); + operators[0] = operator1; + operators[1] = operator2; + + IStrategy[] memory strategies = new IStrategy[](1); + strategies[0] = strategy1; + + uint256[][] memory stakes = new uint256[][](2); + stakes[0] = new uint256[](1); + stakes[0][0] = 80 ether; // Under cap + stakes[1] = new uint256[](1); + stakes[1][0] = 200 ether; // Over cap + + _setupOperatorSet(operatorSet, operators, strategies, stakes); + keyRegistrarMock.setIsRegistered(operator1, operatorSet, true); + keyRegistrarMock.setIsRegistered(operator2, operatorSet, true); + + // Set weight cap + vm.prank(avs1); + calculator.setWeightCap(operatorSet, 100 ether); + + // Calculate operator table (should apply caps automatically) + BN254TableCalculatorBase.BN254OperatorSetInfo memory operatorSetInfo = + calculator.calculateOperatorTable(operatorSet); + + assertEq(operatorSetInfo.numOperators, 2); + assertEq(operatorSetInfo.totalWeights.length, 1); + // Total weight should be: 80 + 100 = 180 (operator2 capped) + assertEq(operatorSetInfo.totalWeights[0], 180 ether); + + // Verify operator tree root is not empty + assertTrue(operatorSetInfo.operatorInfoTreeRoot != bytes32(0)); + } + + function test_calculateOperatorTable_noCapsSet() public { + // Same test but without caps to verify normal behavior + address[] memory operators = new address[](2); + operators[0] = operator1; + operators[1] = operator2; + + IStrategy[] memory strategies = new IStrategy[](1); + strategies[0] = strategy1; + + uint256[][] memory stakes = new uint256[][](2); + stakes[0] = new uint256[](1); + stakes[0][0] = 80 ether; + stakes[1] = new uint256[](1); + stakes[1][0] = 200 ether; + + _setupOperatorSet(operatorSet, operators, strategies, stakes); + keyRegistrarMock.setIsRegistered(operator1, operatorSet, true); + keyRegistrarMock.setIsRegistered(operator2, operatorSet, true); + + // No caps set (default 0) + BN254TableCalculatorBase.BN254OperatorSetInfo memory operatorSetInfo = + calculator.calculateOperatorTable(operatorSet); + + assertEq(operatorSetInfo.numOperators, 2); + assertEq(operatorSetInfo.totalWeights.length, 1); + // Total weight should be: 80 + 200 = 280 (no caps) + assertEq(operatorSetInfo.totalWeights[0], 280 ether); + } +} \ No newline at end of file diff --git a/test/unit/middlewareV2/BN254WeightedTableCalculatorUnit.t.sol b/test/unit/middlewareV2/BN254WeightedTableCalculatorUnit.t.sol new file mode 100644 index 000000000..37a3f4fc8 --- /dev/null +++ b/test/unit/middlewareV2/BN254WeightedTableCalculatorUnit.t.sol @@ -0,0 +1,418 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import { + KeyRegistrar, + IKeyRegistrarTypes +} from "eigenlayer-contracts/src/contracts/permissions/KeyRegistrar.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IKeyRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IKeyRegistrar.sol"; +import {IPermissionController} from "eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol"; +import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; +import { + OperatorSet, + OperatorSetLib +} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; +import {PermissionControllerMixin} from "eigenlayer-contracts/src/contracts/mixins/PermissionControllerMixin.sol"; + +import {BN254WeightedTableCalculator} from + "../../../src/middlewareV2/tableCalculator/BN254WeightedTableCalculator.sol"; +import {BN254TableCalculatorBase} from + "../../../src/middlewareV2/tableCalculator/BN254TableCalculatorBase.sol"; +import {MockEigenLayerDeployer} from "./MockDeployer.sol"; + +// Harness to test internal functions +contract BN254WeightedTableCalculatorHarness is BN254WeightedTableCalculator { + constructor( + IKeyRegistrar _keyRegistrar, + IAllocationManager _allocationManager, + IPermissionController _permissionController, + uint256 _LOOKAHEAD_BLOCKS + ) BN254WeightedTableCalculator(_keyRegistrar, _allocationManager, _permissionController, _LOOKAHEAD_BLOCKS) {} + + function exposed_getOperatorWeights( + OperatorSet calldata operatorSet + ) external view returns (address[] memory operators, uint256[][] memory weights) { + return _getOperatorWeights(operatorSet); + } +} + +/** + * @title BN254WeightedTableCalculatorUnitTests + * @notice Unit tests for BN254WeightedTableCalculator + */ +contract BN254WeightedTableCalculatorUnitTests is MockEigenLayerDeployer { + using OperatorSetLib for OperatorSet; + + // Test contracts + BN254WeightedTableCalculatorHarness public calculator; + + // Test strategies (simple address casting like the original tests) + IStrategy public strategy1 = IStrategy(address(0x100)); + IStrategy public strategy2 = IStrategy(address(0x200)); + OperatorSet public operatorSet; + + // Test addresses + address public avs1 = address(0x1); + address public operator1 = address(0x3); + address public operator2 = address(0x4); + address public operator3 = address(0x5); + address public unauthorizedCaller = address(0x999); + + // Test constants + uint256 public constant TEST_LOOKAHEAD_BLOCKS = 100; + + event StrategyMultipliersUpdated( + OperatorSet indexed operatorSet, + IStrategy[] strategies, + uint256[] multipliers + ); + + function setUp() public virtual { + _deployMockEigenLayer(); + + // Deploy calculator + calculator = new BN254WeightedTableCalculatorHarness( + IKeyRegistrar(address(keyRegistrarMock)), + IAllocationManager(address(allocationManagerMock)), + IPermissionController(address(permissionController)), + TEST_LOOKAHEAD_BLOCKS + ); + + // Set up operator set + operatorSet = OperatorSet({avs: avs1, id: 1}); + + // Configure operator set for BN254 if needed for some tests + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.BN254); + } + + // Helper functions + function _setupOperatorSet( + OperatorSet memory opSet, + address[] memory operators, + IStrategy[] memory strategies, + uint256[][] memory minSlashableStake + ) internal { + allocationManagerMock.setMembersInOperatorSet(opSet, operators); + allocationManagerMock.setStrategiesInOperatorSet(opSet, strategies); + allocationManagerMock.setMinimumSlashableStake( + opSet, operators, strategies, minSlashableStake + ); + } + + function _createSingleWeightArray( + uint256 weight + ) internal pure returns (uint256[][] memory) { + uint256[][] memory weights = new uint256[][](1); + weights[0] = new uint256[](1); + weights[0][0] = weight; + return weights; + } + + /*////////////////////////////////////////////////////////////// + MULTIPLIER TESTS + //////////////////////////////////////////////////////////////*/ + + function test_setStrategyMultipliers_success() public { + IStrategy[] memory strategies = new IStrategy[](2); + strategies[0] = strategy1; + strategies[1] = strategy2; + + uint256[] memory multipliers = new uint256[](2); + multipliers[0] = 20000; // 2x + multipliers[1] = 5000; // 0.5x + + // Expect event emission + vm.expectEmit(true, false, false, true); + emit StrategyMultipliersUpdated(operatorSet, strategies, multipliers); + + vm.prank(avs1); + calculator.setStrategyMultipliers(operatorSet, strategies, multipliers); + + // Verify multipliers were set + assertEq(calculator.getStrategyMultiplier(operatorSet, strategies[0]), 20000); + assertEq(calculator.getStrategyMultiplier(operatorSet, strategies[1]), 5000); + } + + function test_setStrategyMultipliers_revertsOnUnauthorized() public { + IStrategy[] memory strategies = new IStrategy[](1); + strategies[0] = strategy1; + + uint256[] memory multipliers = new uint256[](1); + multipliers[0] = 15000; + + vm.prank(unauthorizedCaller); + vm.expectRevert(PermissionControllerMixin.InvalidPermissions.selector); + calculator.setStrategyMultipliers(operatorSet, strategies, multipliers); + } + + function test_setStrategyMultipliers_revertsOnArrayLengthMismatch() public { + IStrategy[] memory strategies = new IStrategy[](2); + strategies[0] = strategy1; + strategies[1] = strategy2; + + uint256[] memory multipliers = new uint256[](1); // Wrong length + multipliers[0] = 10000; + + vm.prank(avs1); + vm.expectRevert(BN254WeightedTableCalculator.ArrayLengthMismatch.selector); + calculator.setStrategyMultipliers(operatorSet, strategies, multipliers); + } + + function test_getStrategyMultiplier_returnsDefaultForUnset() public { + uint256 multiplier = calculator.getStrategyMultiplier(operatorSet, strategy1); + assertEq(multiplier, 10000); // Default 1x multiplier + } + + function test_setStrategyMultipliers_allowsZeroMultiplier() public { + IStrategy[] memory strategies = new IStrategy[](1); + strategies[0] = strategy1; + + uint256[] memory multipliers = new uint256[](1); + multipliers[0] = 0; // Zero multiplier + + vm.prank(avs1); + calculator.setStrategyMultipliers(operatorSet, strategies, multipliers); + + // Should return 0, not default + assertEq(calculator.getStrategyMultiplier(operatorSet, strategies[0]), 0); + } + + function test_setStrategyMultipliers_allowsExtremeMultipliers() public { + IStrategy[] memory strategies = new IStrategy[](2); + strategies[0] = strategy1; + strategies[1] = strategy2; + + uint256[] memory multipliers = new uint256[](2); + multipliers[0] = 1; // Very small + multipliers[1] = type(uint256).max; // Very large + + vm.prank(avs1); + calculator.setStrategyMultipliers(operatorSet, strategies, multipliers); + + assertEq(calculator.getStrategyMultiplier(operatorSet, strategies[0]), 1); + assertEq(calculator.getStrategyMultiplier(operatorSet, strategies[1]), type(uint256).max); + } + + /*////////////////////////////////////////////////////////////// + WEIGHT CALCULATION TESTS + //////////////////////////////////////////////////////////////*/ + + function test_getOperatorWeights_withMultipliers() public { + // Setup operators and strategies + address[] memory operators = new address[](2); + operators[0] = operator1; + operators[1] = operator2; + + IStrategy[] memory strategies = new IStrategy[](2); + strategies[0] = strategy1; + strategies[1] = strategy2; + + // Set up stakes: operator1 has 100 in each strategy, operator2 has 200 in each + uint256[][] memory stakes = new uint256[][](2); + stakes[0] = new uint256[](2); + stakes[0][0] = 100 ether; // operator1, strategy1 + stakes[0][1] = 100 ether; // operator1, strategy2 + stakes[1] = new uint256[](2); + stakes[1][0] = 200 ether; // operator2, strategy1 + stakes[1][1] = 200 ether; // operator2, strategy2 + + _setupOperatorSet(operatorSet, operators, strategies, stakes); + + // Set key registrations + keyRegistrarMock.setIsRegistered(operator1, operatorSet, true); + keyRegistrarMock.setIsRegistered(operator2, operatorSet, true); + + // Set multipliers: strategy1 = 2x, strategy2 = 0.5x + IStrategy[] memory strategiesForMultiplier = new IStrategy[](2); + strategiesForMultiplier[0] = strategy1; + strategiesForMultiplier[1] = strategy2; + + uint256[] memory multipliers = new uint256[](2); + multipliers[0] = 20000; // 2x + multipliers[1] = 5000; // 0.5x + + vm.prank(avs1); + calculator.setStrategyMultipliers(operatorSet, strategiesForMultiplier, multipliers); + + // Calculate weights + (address[] memory resultOperators, uint256[][] memory resultWeights) = + calculator.exposed_getOperatorWeights(operatorSet); + + // Verify results + assertEq(resultOperators.length, 2); + assertEq(resultOperators[0], operator1); + assertEq(resultOperators[1], operator2); + + // Expected weights: + // operator1: (100 * 20000/10000) + (100 * 5000/10000) = 200 + 50 = 250 + // operator2: (200 * 20000/10000) + (200 * 5000/10000) = 400 + 100 = 500 + assertEq(resultWeights[0][0], 250 ether); + assertEq(resultWeights[1][0], 500 ether); + } + + function test_getOperatorWeights_withoutMultipliers() public { + // Setup without setting any multipliers (should use default 1x) + address[] memory operators = new address[](1); + operators[0] = operator1; + + IStrategy[] memory strategies = new IStrategy[](2); + strategies[0] = strategy1; + strategies[1] = strategy2; + + uint256[][] memory stakes = new uint256[][](1); + stakes[0] = new uint256[](2); + stakes[0][0] = 100 ether; + stakes[0][1] = 200 ether; + + _setupOperatorSet(operatorSet, operators, strategies, stakes); + keyRegistrarMock.setIsRegistered(operator1, operatorSet, true); + + (address[] memory resultOperators, uint256[][] memory resultWeights) = + calculator.exposed_getOperatorWeights(operatorSet); + + // Should sum with default 1x multipliers: 100 + 200 = 300 + assertEq(resultOperators.length, 1); + assertEq(resultOperators[0], operator1); + assertEq(resultWeights[0][0], 300 ether); + } + + function test_getOperatorWeights_excludesUnregisteredOperators() public { + address[] memory operators = new address[](2); + operators[0] = operator1; + operators[1] = operator2; + + IStrategy[] memory strategies = new IStrategy[](1); + strategies[0] = strategy1; + + uint256[][] memory stakes = new uint256[][](2); + stakes[0] = new uint256[](1); + stakes[0][0] = 100 ether; + stakes[1] = new uint256[](1); + stakes[1][0] = 200 ether; + + _setupOperatorSet(operatorSet, operators, strategies, stakes); + + // Only register operator1 + keyRegistrarMock.setIsRegistered(operator1, operatorSet, true); + keyRegistrarMock.setIsRegistered(operator2, operatorSet, false); + + // Use calculateOperatorTable which does check registration, not just weights + BN254TableCalculatorBase.BN254OperatorSetInfo memory operatorSetInfo = + calculator.calculateOperatorTable(operatorSet); + + // Should only include operator1 (registered) + assertEq(operatorSetInfo.numOperators, 1); + assertEq(operatorSetInfo.totalWeights.length, 1); + assertEq(operatorSetInfo.totalWeights[0], 100 ether); + } + + function test_getOperatorWeights_withZeroStake() public { + address[] memory operators = new address[](1); + operators[0] = operator1; + + IStrategy[] memory strategies = new IStrategy[](1); + strategies[0] = strategy1; + + // Set zero stake + uint256[][] memory stakes = new uint256[][](1); + stakes[0] = new uint256[](1); + stakes[0][0] = 0; + + _setupOperatorSet(operatorSet, operators, strategies, stakes); + keyRegistrarMock.setIsRegistered(operator1, operatorSet, true); + + (address[] memory resultOperators, uint256[][] memory resultWeights) = + calculator.exposed_getOperatorWeights(operatorSet); + + // Should exclude operators with zero total weight + assertEq(resultOperators.length, 0); + } + + /*////////////////////////////////////////////////////////////// + INTEGRATION TESTS + //////////////////////////////////////////////////////////////*/ + + function test_calculateOperatorTable_integration() public { + // Setup a complete scenario and verify the full operator table calculation + address[] memory operators = new address[](1); + operators[0] = operator1; + + IStrategy[] memory strategies = new IStrategy[](1); + strategies[0] = strategy1; + + uint256[][] memory stakes = new uint256[][](1); + stakes[0] = new uint256[](1); + stakes[0][0] = 100 ether; + + _setupOperatorSet(operatorSet, operators, strategies, stakes); + keyRegistrarMock.setIsRegistered(operator1, operatorSet, true); + + // This should work without reverting and return a valid operator set info + BN254TableCalculatorBase.BN254OperatorSetInfo memory operatorSetInfo = + calculator.calculateOperatorTable(operatorSet); + + assertEq(operatorSetInfo.numOperators, 1); + assertEq(operatorSetInfo.totalWeights.length, 1); + assertEq(operatorSetInfo.totalWeights[0], 100 ether); + } + + function test_calculateOperatorTable_withSomeUnregisteredOperators() public { + // This test catches the audit issue where operatorInfoLeaves[i] was used instead of operatorInfoLeaves[operatorCount] + // When some operators are in allocation manager but not registered with key registrar + + address operator4 = address(0x6); + + // Setup 4 operators in allocation manager + address[] memory operators = new address[](4); + operators[0] = operator1; + operators[1] = operator2; + operators[2] = operator3; + operators[3] = operator4; + + IStrategy[] memory strategies = new IStrategy[](1); + strategies[0] = strategy1; + + // All operators have stake + uint256[][] memory stakes = new uint256[][](4); + for (uint256 i = 0; i < 4; i++) { + stakes[i] = new uint256[](1); + stakes[i][0] = (i + 1) * 100 ether; // 100, 200, 300, 400 + } + + _setupOperatorSet(operatorSet, operators, strategies, stakes); + + // Only register operators 1 and 3 with key registrar (skip 2 and 4) + keyRegistrarMock.setIsRegistered(operator1, operatorSet, true); + keyRegistrarMock.setIsRegistered(operator2, operatorSet, false); + keyRegistrarMock.setIsRegistered(operator3, operatorSet, true); + keyRegistrarMock.setIsRegistered(operator4, operatorSet, false); + + // Set multiplier for strategy1 + IStrategy[] memory strategiesForMultiplier = new IStrategy[](1); + strategiesForMultiplier[0] = strategy1; + uint256[] memory multipliers = new uint256[](1); + multipliers[0] = 20000; // 2x multiplier + + vm.prank(avs1); + calculator.setStrategyMultipliers(operatorSet, strategiesForMultiplier, multipliers); + + // Calculate operator table - this would fail with the audit bug because of array indexing mismatch + BN254TableCalculatorBase.BN254OperatorSetInfo memory operatorSetInfo = + calculator.calculateOperatorTable(operatorSet); + + // Should only include registered operators (1 and 3) + assertEq(operatorSetInfo.numOperators, 2); + assertEq(operatorSetInfo.totalWeights.length, 1); + + // Total weight should be: (100 * 2) + (300 * 2) = 200 + 600 = 800 + assertEq(operatorSetInfo.totalWeights[0], 800 ether); + + // Verify operator tree root is not empty (proves merkle tree was built correctly) + assertTrue(operatorSetInfo.operatorInfoTreeRoot != bytes32(0)); + } + + +} \ No newline at end of file From 568778424a24fe7e5539d45fe5316033a11356ea Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 28 Jul 2025 12:05:56 +0200 Subject: [PATCH 2/4] feat: avs deploy scripts --- script/AVSMiddlewareDeploy.s.sol | 292 +++++++++++++++++++++++ script/README.md | 207 ++++++++++++++++ script/config/avs-allowlist.example.json | 26 ++ script/config/avs-basic.example.json | 26 ++ script/config/avs-weighted.example.json | 36 +++ script/examples/deploy-simple-avs.sh | 53 ++++ script/output/test-core-output.json | 8 + script/test/DeployTestCore.s.sol | 88 +++++++ script/test/DeployTestStrategies.s.sol | 46 ++++ script/test/anvil-test.sh | 176 ++++++++++++++ script/utils/AVSDeployUtils.sol | 240 +++++++++++++++++++ 11 files changed, 1198 insertions(+) create mode 100644 script/AVSMiddlewareDeploy.s.sol create mode 100644 script/README.md create mode 100644 script/config/avs-allowlist.example.json create mode 100644 script/config/avs-basic.example.json create mode 100644 script/config/avs-weighted.example.json create mode 100644 script/examples/deploy-simple-avs.sh create mode 100644 script/output/test-core-output.json create mode 100644 script/test/DeployTestCore.s.sol create mode 100644 script/test/DeployTestStrategies.s.sol create mode 100755 script/test/anvil-test.sh create mode 100644 script/utils/AVSDeployUtils.sol diff --git a/script/AVSMiddlewareDeploy.s.sol b/script/AVSMiddlewareDeploy.s.sol new file mode 100644 index 000000000..dec71d6e7 --- /dev/null +++ b/script/AVSMiddlewareDeploy.s.sol @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "forge-std/Script.sol"; +import "forge-std/console.sol"; + +// Core EigenLayer imports +import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IKeyRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IKeyRegistrar.sol"; +import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; +import {IPermissionController} from "eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; + +// Middleware imports - Registrars +import {AVSRegistrar} from "../src/middlewareV2/registrar/AVSRegistrar.sol"; +import {AVSRegistrarWithAllowlist} from "../src/middlewareV2/registrar/presets/AVSRegistrarWithAllowlist.sol"; +import {AVSRegistrarWithSocket} from "../src/middlewareV2/registrar/presets/AVSRegistrarWithSocket.sol"; +import {AVSRegistrarAsIdentifier} from "../src/middlewareV2/registrar/presets/AVSRegistrarAsIdentifier.sol"; + +// Middleware imports - Table Calculators +import {BN254TableCalculator} from "../src/middlewareV2/tableCalculator/BN254TableCalculator.sol"; +import {BN254WeightedTableCalculator} from "../src/middlewareV2/tableCalculator/BN254WeightedTableCalculator.sol"; +import {BN254TableCalculatorWithCaps} from "../src/middlewareV2/tableCalculator/BN254TableCalculatorWithCaps.sol"; +import {ECDSATableCalculator} from "../src/middlewareV2/tableCalculator/ECDSATableCalculator.sol"; + +/** + * @title AVSMiddlewareDeploy + * @notice Comprehensive deployment script for AVS middleware contracts + * @dev Supports multiple registrar types and table calculator configurations + */ +contract AVSMiddlewareDeploy is Script { + + // Deployment configuration struct + struct DeploymentConfig { + // Core protocol addresses + address allocationManager; + address keyRegistrar; + address avsDirectory; + address permissionController; + address proxyAdmin; + + // AVS configuration + address avsOwner; + string metadataURI; + + // Registrar configuration + RegistrarType registrarType; + bool useProxy; // Whether to deploy registrar behind proxy + + // Table calculator configuration + CalculatorType calculatorType; + uint256 lookaheadBlocks; + + // Optional: for weighted calculator + address[] strategies; + uint256[] multipliers; // In basis points (10000 = 1x) + } + + enum RegistrarType { + BASIC, // Basic AVSRegistrar + WITH_ALLOWLIST, // AVSRegistrarWithAllowlist + WITH_SOCKET, // AVSRegistrarWithSocket + AS_IDENTIFIER // AVSRegistrarAsIdentifier + } + + enum CalculatorType { + BN254_BASIC, // BN254TableCalculator + BN254_WEIGHTED, // BN254WeightedTableCalculator + BN254_WITH_CAPS, // BN254TableCalculatorWithCaps + ECDSA_BASIC // ECDSATableCalculator + } + + // Deployed contract addresses + struct DeployedContracts { + address registrarImplementation; + address registrarProxy; + address tableCalculator; + address proxyAdmin; + } + + DeployedContracts public deployedContracts; + + function run() external { + // Load configuration from file + string memory configFile = vm.envString("CONFIG_FILE"); + DeploymentConfig memory config = _loadConfig(configFile); + + vm.startBroadcast(); + + console.log("=== AVS Middleware Deployment ==="); + console.log("AVS Owner:", config.avsOwner); + console.log("Metadata URI:", config.metadataURI); + + // Deploy ProxyAdmin if needed + if (config.useProxy && config.proxyAdmin == address(0)) { + deployedContracts.proxyAdmin = address(new ProxyAdmin()); + console.log("Deployed ProxyAdmin:", deployedContracts.proxyAdmin); + } else { + deployedContracts.proxyAdmin = config.proxyAdmin; + } + + // Deploy Table Calculator + deployedContracts.tableCalculator = _deployTableCalculator(config); + console.log("Deployed Table Calculator:", deployedContracts.tableCalculator); + console.log("Calculator Type:", _calculatorTypeToString(config.calculatorType)); + + // Deploy AVS Registrar + (deployedContracts.registrarImplementation, deployedContracts.registrarProxy) = + _deployAVSRegistrar(config); + + console.log("Deployed Registrar Implementation:", deployedContracts.registrarImplementation); + if (config.useProxy) { + console.log("Deployed Registrar Proxy:", deployedContracts.registrarProxy); + } + console.log("Registrar Type:", _registrarTypeToString(config.registrarType)); + + // Initialize registrar if needed + _initializeRegistrar(config); + + vm.stopBroadcast(); + + // Output deployment summary + _outputDeploymentSummary(config); + } + + function _deployTableCalculator(DeploymentConfig memory config) internal returns (address) { + if (config.calculatorType == CalculatorType.BN254_BASIC) { + return address(new BN254TableCalculator( + IKeyRegistrar(config.keyRegistrar), + IAllocationManager(config.allocationManager), + config.lookaheadBlocks + )); + } else if (config.calculatorType == CalculatorType.BN254_WEIGHTED) { + return address(new BN254WeightedTableCalculator( + IKeyRegistrar(config.keyRegistrar), + IAllocationManager(config.allocationManager), + IPermissionController(config.permissionController), + config.lookaheadBlocks + )); + } else if (config.calculatorType == CalculatorType.BN254_WITH_CAPS) { + return address(new BN254TableCalculatorWithCaps( + IKeyRegistrar(config.keyRegistrar), + IAllocationManager(config.allocationManager), + IPermissionController(config.permissionController), + config.lookaheadBlocks + )); + } else if (config.calculatorType == CalculatorType.ECDSA_BASIC) { + return address(new ECDSATableCalculator( + IKeyRegistrar(config.keyRegistrar), + IAllocationManager(config.allocationManager), + config.lookaheadBlocks + )); + } else { + revert("Unsupported calculator type"); + } + } + + function _deployAVSRegistrar(DeploymentConfig memory config) + internal + returns (address implementation, address proxy) + { + // Deploy implementation + if (config.registrarType == RegistrarType.BASIC) { + implementation = address(new AVSRegistrar( + config.avsOwner, + IAllocationManager(config.allocationManager), + IKeyRegistrar(config.keyRegistrar) + )); + } else if (config.registrarType == RegistrarType.WITH_ALLOWLIST) { + implementation = address(new AVSRegistrarWithAllowlist( + config.avsOwner, + IAllocationManager(config.allocationManager), + IKeyRegistrar(config.keyRegistrar) + )); + } else if (config.registrarType == RegistrarType.WITH_SOCKET) { + implementation = address(new AVSRegistrarWithSocket( + config.avsOwner, + IAllocationManager(config.allocationManager), + IKeyRegistrar(config.keyRegistrar) + )); + } else if (config.registrarType == RegistrarType.AS_IDENTIFIER) { + implementation = address(new AVSRegistrarAsIdentifier( + config.avsOwner, + IAllocationManager(config.allocationManager), + IPermissionController(config.permissionController), + IKeyRegistrar(config.keyRegistrar) + )); + } else { + revert("Unsupported registrar type"); + } + + // Deploy proxy if requested + if (config.useProxy) { + proxy = address(new TransparentUpgradeableProxy( + implementation, + deployedContracts.proxyAdmin, + "" // No initialization data for now + )); + } else { + proxy = implementation; + } + + return (implementation, proxy); + } + + function _initializeRegistrar(DeploymentConfig memory config) internal { + address registrar = deployedContracts.registrarProxy; + + if (config.registrarType == RegistrarType.WITH_ALLOWLIST) { + AVSRegistrarWithAllowlist(registrar).initialize(config.avsOwner); + } else if (config.registrarType == RegistrarType.AS_IDENTIFIER) { + AVSRegistrarAsIdentifier(registrar).initialize(config.avsOwner, config.metadataURI); + } + // Basic and WithSocket registrars don't need initialization + } + + function _loadConfig(string memory configPath) internal view returns (DeploymentConfig memory config) { + string memory json = vm.readFile(configPath); + + // Core protocol addresses + config.allocationManager = vm.parseJsonAddress(json, ".allocationManager"); + config.keyRegistrar = vm.parseJsonAddress(json, ".keyRegistrar"); + config.avsDirectory = vm.parseJsonAddress(json, ".avsDirectory"); + config.permissionController = vm.parseJsonAddress(json, ".permissionController"); + + // Optional proxy admin + try vm.parseJsonAddress(json, ".proxyAdmin") returns (address addr) { + config.proxyAdmin = addr; + } catch {} + + // AVS configuration + config.avsOwner = vm.parseJsonAddress(json, ".avsOwner"); + config.metadataURI = vm.parseJsonString(json, ".metadataURI"); + + // Registrar configuration + config.registrarType = RegistrarType(vm.parseJsonUint(json, ".registrarType")); + config.useProxy = vm.parseJsonBool(json, ".useProxy"); + + // Calculator configuration + config.calculatorType = CalculatorType(vm.parseJsonUint(json, ".calculatorType")); + config.lookaheadBlocks = vm.parseJsonUint(json, ".lookaheadBlocks"); + + // Optional weighted calculator config + try vm.parseJsonAddress(json, ".strategies") { + config.strategies = vm.parseJsonAddressArray(json, ".strategies"); + config.multipliers = vm.parseJsonUintArray(json, ".multipliers"); + } catch {} + + return config; + } + + function _outputDeploymentSummary(DeploymentConfig memory config) internal view { + console.log("\n=== Deployment Summary ==="); + console.log("Network:", block.chainid); + console.log("Deployer:", msg.sender); + console.log(""); + + console.log("Contracts Deployed:"); + console.log("- Table Calculator:", deployedContracts.tableCalculator); + console.log("- Registrar Implementation:", deployedContracts.registrarImplementation); + if (config.useProxy) { + console.log("- Registrar Proxy:", deployedContracts.registrarProxy); + console.log("- Proxy Admin:", deployedContracts.proxyAdmin); + } + console.log(""); + + console.log("Next Steps:"); + console.log("1. Set up operator sets in AllocationManager"); + console.log("2. Configure CrossChainRegistry if using multichain"); + console.log("3. Register strategies in your table calculator if weighted"); + if (config.registrarType == RegistrarType.WITH_ALLOWLIST) { + console.log("4. Configure allowlist for operators"); + } + } + + function _registrarTypeToString(RegistrarType rType) internal pure returns (string memory) { + if (rType == RegistrarType.BASIC) return "Basic"; + if (rType == RegistrarType.WITH_ALLOWLIST) return "WithAllowlist"; + if (rType == RegistrarType.WITH_SOCKET) return "WithSocket"; + if (rType == RegistrarType.AS_IDENTIFIER) return "AsIdentifier"; + return "Unknown"; + } + + function _calculatorTypeToString(CalculatorType cType) internal pure returns (string memory) { + if (cType == CalculatorType.BN254_BASIC) return "BN254Basic"; + if (cType == CalculatorType.BN254_WEIGHTED) return "BN254Weighted"; + if (cType == CalculatorType.BN254_WITH_CAPS) return "BN254WithCaps"; + if (cType == CalculatorType.ECDSA_BASIC) return "ECDSABasic"; + return "Unknown"; + } +} \ No newline at end of file diff --git a/script/README.md b/script/README.md new file mode 100644 index 000000000..5bb0112d5 --- /dev/null +++ b/script/README.md @@ -0,0 +1,207 @@ +# AVS Middleware Deployment Scripts + +This directory contains comprehensive deployment scripts for AVS middleware contracts, designed to work with the EigenLayer UAM (Unified Account Management) system. + +## Overview + +The key insight is that **key management is now entirely handled by the core EigenLayer protocol**. AVS operators only need to deploy and configure: + +1. **AVSRegistrar** - Manages operator registration/deregistration for your AVS +2. **OperatorTableCalculator** - Calculates stake weights for operators in your operator sets + +## Quick Start + +1. **Choose your configuration** based on your AVS needs: + - `avs-basic.example.json` - Simple AVS with basic functionality + - `avs-weighted.example.json` - Advanced AVS with custom strategy weights + - `avs-allowlist.example.json` - Permissioned AVS with operator allowlists + +2. **Copy and customize** the appropriate config file: + ```bash + cp script/config/avs-basic.example.json script/config/my-avs.json + # Edit my-avs.json with your specific addresses and parameters + ``` + +3. **Deploy your middleware**: + ```bash + # Option 1: Using forge directly + CONFIG_FILE=script/config/my-avs.json forge script script/AVSMiddlewareDeploy.s.sol --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast + + # Option 2: Using the convenience script + CONFIG_FILE=script/config/my-avs.json ./script/examples/deploy-simple-avs.sh + ``` + +## Configuration Guide + +### Core Addresses (Required) + +Update these addresses for your target network: + +```json +{ + "allocationManager": "0x...", // Core EigenLayer AllocationManager + "keyRegistrar": "0x...", // Core EigenLayer KeyRegistrar + "avsDirectory": "0x...", // Core EigenLayer AVSDirectory + "permissionController": "0x...", // Core EigenLayer PermissionController + "proxyAdmin": "" // Leave empty to deploy new one +} +``` + +### Registrar Types + +Choose the registrar type that fits your AVS model: + +| Type | Value | Description | Use Case | +|------|-------|-------------|----------| +| `BASIC` | 0 | Simple operator registration | Open AVS with minimal restrictions | +| `WITH_ALLOWLIST` | 1 | Permissioned operator registration | Curated operator sets | +| `WITH_SOCKET` | 2 | Socket-based communication | AVS requiring operator endpoints | +| `AS_IDENTIFIER` | 3 | Registrar acts as AVS identifier | Full AVS protocol integration | + +### Calculator Types + +Choose the table calculator based on your staking model: + +| Type | Value | Description | Use Case | +|------|-------|-------------|----------| +| `BN254_BASIC` | 0 | Equal weight for all strategies | Simple staking model | +| `BN254_WEIGHTED` | 1 | Custom multipliers per strategy | Preferred assets/strategies | +| `BN254_WITH_CAPS` | 2 | Stake caps per strategy | Risk management | +| `ECDSA_BASIC` | 3 | ECDSA-based validation | ECDSA signature schemes | + +## Example Configurations + +### Basic AVS Setup + +```json +{ + "registrarType": 0, // Basic registrar + "calculatorType": 0, // Basic BN254 calculator + "useProxy": true, // Upgradeable registrar + "lookaheadBlocks": 100 // Finality buffer +} +``` + +### Advanced Weighted Setup + +```json +{ + "registrarType": 3, // AS_IDENTIFIER for full integration + "calculatorType": 1, // Weighted calculator + "strategies": [ + "0x...", // ETH strategy + "0x..." // BTC strategy + ], + "multipliers": [ + 20000, // 2x weight for ETH + 15000 // 1.5x weight for BTC + ] +} +``` + +## Post-Deployment Configuration + +After deployment, use the utility scripts for additional configuration: + +### 1. Configure Operator Sets + +```bash +forge script script/utils/AVSDeployUtils.sol:AVSDeployUtils \ + --sig "configureOperatorSets(address,address,OperatorSetConfig[])" \ + $ALLOCATION_MANAGER $AVS_ADDRESS $OPERATOR_SET_CONFIGS \ + --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast +``` + +### 2. Set Strategy Multipliers (Weighted Calculators) + +```bash +forge script script/utils/AVSDeployUtils.sol:AVSDeployUtils \ + --sig "configureWeightedCalculators(WeightedCalculatorConfig[])" \ + $WEIGHTED_CONFIGS \ + --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast +``` + +### 3. Configure Allowlists (Allowlist Registrars) + +```bash +forge script script/utils/AVSDeployUtils.sol:AVSDeployUtils \ + --sig "configureAllowlists(AllowlistConfig[])" \ + $ALLOWLIST_CONFIGS \ + --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast +``` + +## Common Deployment Patterns + +### Pattern 1: Simple Open AVS + +1. Use `BASIC` registrar with `BN254_BASIC` calculator +2. Create operator sets with your strategies +3. Operators can register directly + +### Pattern 2: Curated AVS with Preferred Assets + +1. Use `WITH_ALLOWLIST` registrar with `BN254_WEIGHTED` calculator +2. Set custom multipliers for preferred strategies +3. Maintain allowlist of approved operators + +### Pattern 3: Full Protocol Integration + +1. Use `AS_IDENTIFIER` registrar (registrar becomes the AVS) +2. Choose appropriate calculator for your needs +3. Registrar handles full EigenLayer protocol integration + +## Network Addresses + +### Preprod (Chain ID: 17000) +```json +{ + "allocationManager": "0x75dfE5B44C2E530568001400D3f704bC8AE350CC", + "avsDirectory": "0x141d6995556135D4997b2ff72EB443Be300353bC", + "permissionController": "0xa2348c77802238Db39f0CefAa500B62D3FDD682b" +} +``` + +*Note: Update `keyRegistrar` address based on your network deployment* + +## Troubleshooting + +### Common Issues + +1. **Config parsing errors**: Ensure JSON is valid and all required fields are present +2. **Address verification failed**: Double-check core protocol addresses for your network +3. **Initialization failed**: Verify AVS owner has proper permissions + +### Verification + +Use the verification utility: + +```bash +forge script script/utils/AVSDeployUtils.sol:AVSDeployUtils \ + --sig "verifyDeployment(address,address,address,address)" \ + $REGISTRAR $TABLE_CALCULATOR $ALLOCATION_MANAGER $AVS \ + --rpc-url $RPC_URL +``` + +## Integration with Core Protocol + +After deployment, you'll need to: + +1. **Register with AllocationManager**: Set your registrar as the AVS registrar +2. **Configure CrossChainRegistry**: If using multichain features +3. **Set up Operator Sets**: Define strategies and thresholds +4. **Configure Slashing**: Set up slashing parameters if needed + +## Security Considerations + +- Use proxy patterns for upgradeability +- Verify all core protocol addresses before deployment +- Test configurations on testnets first +- Consider operator allowlists for sensitive AVS operations +- Review strategy multipliers for economic security + +## Support + +For questions or issues: +- Review the [middleware documentation](../docs/middlewareV2/README.md) +- Check existing tests for usage examples +- Consult EigenLayer core protocol documentation \ No newline at end of file diff --git a/script/config/avs-allowlist.example.json b/script/config/avs-allowlist.example.json new file mode 100644 index 000000000..c295237b5 --- /dev/null +++ b/script/config/avs-allowlist.example.json @@ -0,0 +1,26 @@ +{ + "description": "Allowlist AVS configuration for permissioned operator registration with basic calculator", + + "_comment_core_addresses": "Core EigenLayer protocol addresses - update these for your network", + "allocationManager": "0x75dfE5B44C2E530568001400D3f704bC8AE350CC", + "keyRegistrar": "0x...", + "avsDirectory": "0x141d6995556135D4997b2ff72EB443Be300353bC", + "permissionController": "0xa2348c77802238Db39f0CefAa500B62D3FDD682b", + "proxyAdmin": "", + + "_comment_avs_config": "Your AVS specific configuration", + "avsOwner": "0x...", + "metadataURI": "https://your-avs.com/metadata.json", + + "_comment_registrar": "Using WITH_ALLOWLIST for permissioned registration", + "registrarType": 1, + "useProxy": true, + + "_comment_calculator": "Basic BN254 calculator", + "calculatorType": 0, + "lookaheadBlocks": 100, + + "_comment_post_deployment": "After deployment, use allowlist functions to manage operator permissions", + "strategies": [], + "multipliers": [] +} \ No newline at end of file diff --git a/script/config/avs-basic.example.json b/script/config/avs-basic.example.json new file mode 100644 index 000000000..32e9d567a --- /dev/null +++ b/script/config/avs-basic.example.json @@ -0,0 +1,26 @@ +{ + "description": "Basic AVS configuration with BN254TableCalculator and simple registrar", + + "_comment_core_addresses": "Core EigenLayer protocol addresses - update these for your network", + "allocationManager": "0x75dfE5B44C2E530568001400D3f704bC8AE350CC", + "keyRegistrar": "0x...", + "avsDirectory": "0x141d6995556135D4997b2ff72EB443Be300353bC", + "permissionController": "0xa2348c77802238Db39f0CefAa500B62D3FDD682b", + "proxyAdmin": "", + + "_comment_avs_config": "Your AVS specific configuration", + "avsOwner": "0x...", + "metadataURI": "https://your-avs.com/metadata.json", + + "_comment_registrar": "Registrar type: 0=BASIC, 1=WITH_ALLOWLIST, 2=WITH_SOCKET, 3=AS_IDENTIFIER", + "registrarType": 0, + "useProxy": true, + + "_comment_calculator": "Calculator type: 0=BN254_BASIC, 1=BN254_WEIGHTED, 2=BN254_WITH_CAPS, 3=ECDSA_BASIC", + "calculatorType": 0, + "lookaheadBlocks": 100, + + "_comment_optional": "Optional fields for weighted calculators (leave empty if not using)", + "strategies": [], + "multipliers": [] +} \ No newline at end of file diff --git a/script/config/avs-weighted.example.json b/script/config/avs-weighted.example.json new file mode 100644 index 000000000..989ff31e1 --- /dev/null +++ b/script/config/avs-weighted.example.json @@ -0,0 +1,36 @@ +{ + "description": "Weighted AVS configuration with BN254WeightedTableCalculator and custom strategy weights", + + "_comment_core_addresses": "Core EigenLayer protocol addresses - update these for your network", + "allocationManager": "0x75dfE5B44C2E530568001400D3f704bC8AE350CC", + "keyRegistrar": "0x...", + "avsDirectory": "0x141d6995556135D4997b2ff72EB443Be300353bC", + "permissionController": "0xa2348c77802238Db39f0CefAa500B62D3FDD682b", + "proxyAdmin": "", + + "_comment_avs_config": "Your AVS specific configuration", + "avsOwner": "0x...", + "metadataURI": "https://your-avs.com/metadata.json", + + "_comment_registrar": "Using AS_IDENTIFIER for full AVS setup", + "registrarType": 3, + "useProxy": true, + + "_comment_calculator": "Using weighted calculator for custom strategy weights", + "calculatorType": 1, + "lookaheadBlocks": 100, + + "_comment_strategy_weights": "Custom multipliers for different strategies (in basis points: 10000 = 1x)", + "strategies": [ + "0x...", + "0x...", + "0x..." + ], + "multipliers": [ + 20000, + 15000, + 5000 + ], + + "_comment_multiplier_explanation": "Above example: 2x weight for strategy 1, 1.5x for strategy 2, 0.5x for strategy 3" +} \ No newline at end of file diff --git a/script/examples/deploy-simple-avs.sh b/script/examples/deploy-simple-avs.sh new file mode 100644 index 000000000..e1d54f884 --- /dev/null +++ b/script/examples/deploy-simple-avs.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +# Simple AVS Deployment Example +# This script demonstrates deploying AVS middleware using a pre-configured config file + +set -e + +# Configuration +NETWORK=${NETWORK:-"preprod"} +RPC_URL=${RPC_URL:-"https://rpc.preprod.eigenlayer.xyz"} +PRIVATE_KEY=${PRIVATE_KEY:-""} +CONFIG_FILE=${CONFIG_FILE:-"script/config/avs-basic.example.json"} + +# Validate required environment variables +if [ -z "$PRIVATE_KEY" ]; then + echo "Error: PRIVATE_KEY environment variable is required" + exit 1 +fi + +if [ ! -f "$CONFIG_FILE" ]; then + echo "Error: Config file not found: $CONFIG_FILE" + echo "" + echo "Please copy and customize one of the example configs:" + echo " cp script/config/avs-basic.example.json script/config/my-avs.json" + echo " # Edit my-avs.json with your addresses" + echo " CONFIG_FILE=script/config/my-avs.json $0" + exit 1 +fi + +echo "=== AVS Middleware Deployment ===" +echo "Network: $NETWORK" +echo "Config: $CONFIG_FILE" +echo "RPC URL: $RPC_URL" +echo "" + +# Deploy contracts +echo "Deploying AVS middleware contracts..." +CONFIG_FILE=$CONFIG_FILE forge script script/AVSMiddlewareDeploy.s.sol:AVSMiddlewareDeploy \ + --rpc-url $RPC_URL \ + --private-key $PRIVATE_KEY \ + --broadcast \ + $([[ -n "$ETHERSCAN_API_KEY" ]] && echo "--verify --etherscan-api-key $ETHERSCAN_API_KEY" || echo "") \ + -vvvv + +echo "" +echo "✅ Deployment complete!" +echo "" +echo "Next steps:" +echo "1. Configure operator sets in AllocationManager" +echo "2. Set your registrar in AllocationManager.setAVSRegistrar()" +echo "3. Register operators to your AVS" +echo "" +echo "Deployment details are logged above." \ No newline at end of file diff --git a/script/output/test-core-output.json b/script/output/test-core-output.json new file mode 100644 index 000000000..e0e07fb68 --- /dev/null +++ b/script/output/test-core-output.json @@ -0,0 +1,8 @@ +{ + "timestamp": "1753696939", + "chainId": "31337", + "allocationManager": "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "keyRegistrar": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", + "avsDirectory": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", + "permissionController": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" +} \ No newline at end of file diff --git a/script/test/DeployTestCore.s.sol b/script/test/DeployTestCore.s.sol new file mode 100644 index 000000000..76e2e5ddf --- /dev/null +++ b/script/test/DeployTestCore.s.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "forge-std/Script.sol"; +import "forge-std/console.sol"; + +// Import test mocks since we don't have full core contracts +import "../../test/mocks/AllocationManagerMock.sol"; +import "../../test/mocks/AVSDirectoryMock.sol"; +import "../../test/mocks/PermissionControllerMock.sol"; + +// For a real KeyRegistrar, we'll need to create a simple mock +contract KeyRegistrarMock { + mapping(address => bool) public isRegistered; + + function registerKey(address operator, bytes calldata) external { + isRegistered[operator] = true; + } + + function deregisterKey(address operator) external { + isRegistered[operator] = false; + } +} + +/** + * @title DeployTestCore + * @notice Deploy minimal core contracts for testing AVS middleware + */ +contract DeployTestCore is Script { + + function run() external { + vm.startBroadcast(); + + console.log("=== Deploying Test Core Contracts ==="); + + // Deploy mock core contracts + AllocationManagerMock allocationManager = new AllocationManagerMock(); + console.log("AllocationManager deployed:", address(allocationManager)); + + KeyRegistrarMock keyRegistrar = new KeyRegistrarMock(); + console.log("KeyRegistrar deployed:", address(keyRegistrar)); + + AVSDirectoryMock avsDirectory = new AVSDirectoryMock(); + console.log("AVSDirectory deployed:", address(avsDirectory)); + + PermissionControllerMock permissionController = new PermissionControllerMock(); + console.log("PermissionController deployed:", address(permissionController)); + + vm.stopBroadcast(); + + // Save deployment output + _saveOutput( + address(allocationManager), + address(keyRegistrar), + address(avsDirectory), + address(permissionController) + ); + + console.log("=== Core deployment complete ==="); + } + + function _saveOutput( + address allocationManager, + address keyRegistrar, + address avsDirectory, + address permissionController + ) internal { + string memory json = string.concat( + '{\n', + ' "timestamp": "', vm.toString(block.timestamp), '",\n', + ' "chainId": "', vm.toString(block.chainid), '",\n', + ' "allocationManager": "', vm.toString(allocationManager), '",\n', + ' "keyRegistrar": "', vm.toString(keyRegistrar), '",\n', + ' "avsDirectory": "', vm.toString(avsDirectory), '",\n', + ' "permissionController": "', vm.toString(permissionController), '"\n', + '}' + ); + + string memory outputDir = "script/output"; + string memory outputPath = string.concat(outputDir, "/test-core-output.json"); + + // Create output directory if it doesn't exist + try vm.createDir(outputDir, true) {} catch {} + + vm.writeFile(outputPath, json); + console.log("Output saved to:", outputPath); + } +} \ No newline at end of file diff --git a/script/test/DeployTestStrategies.s.sol b/script/test/DeployTestStrategies.s.sol new file mode 100644 index 000000000..7bcac1812 --- /dev/null +++ b/script/test/DeployTestStrategies.s.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "forge-std/Script.sol"; +import "forge-std/console.sol"; + +// Simple strategy mock for testing +contract StrategyMock { + string public name; + + constructor(string memory _name) { + name = _name; + } + + function underlyingToken() external pure returns (address) { + return address(0); // Mock token + } + + function shares(address) external pure returns (uint256) { + return 100e18; // Mock shares + } +} + +/** + * @title DeployTestStrategies + * @notice Deploy test strategy contracts for weighted calculator testing + */ +contract DeployTestStrategies is Script { + + function run() external { + vm.startBroadcast(); + + console.log("=== Deploying Test Strategies ==="); + + // Deploy test strategies + StrategyMock strategy1 = new StrategyMock("ETH Strategy"); + console.log("Strategy deployed:", address(strategy1)); + + StrategyMock strategy2 = new StrategyMock("BTC Strategy"); + console.log("Strategy deployed:", address(strategy2)); + + vm.stopBroadcast(); + + console.log("=== Strategy deployment complete ==="); + } +} \ No newline at end of file diff --git a/script/test/anvil-test.sh b/script/test/anvil-test.sh new file mode 100755 index 000000000..57a688571 --- /dev/null +++ b/script/test/anvil-test.sh @@ -0,0 +1,176 @@ +#!/bin/bash + +# Anvil Test Script for AVS Middleware Deployment +# This script sets up a local anvil environment and tests the deployment scripts + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${GREEN}=== AVS Middleware Anvil Test ===${NC}" + +# Configuration +ANVIL_PORT=8545 +RPC_URL="http://127.0.0.1:$ANVIL_PORT" +PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +DEPLOYER="0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" + +# Set dummy etherscan key to prevent foundry config issues +export ETHERSCAN_API_KEY="dummy" + +# Check if anvil is running +if ! curl -s $RPC_URL > /dev/null 2>&1; then + echo -e "${YELLOW}Starting anvil...${NC}" + anvil --port $ANVIL_PORT --accounts 10 --balance 1000 & + ANVIL_PID=$! + sleep 3 + + # Register cleanup function + cleanup() { + echo -e "${YELLOW}Cleaning up anvil process...${NC}" + kill $ANVIL_PID 2>/dev/null || true + } + trap cleanup EXIT +else + echo -e "${GREEN}Anvil already running${NC}" +fi + +echo "RPC URL: $RPC_URL" +echo "Deployer: $DEPLOYER" +echo "" + +# Build contracts +echo -e "${YELLOW}Building contracts...${NC}" +forge build + +# Deploy core contracts first +echo -e "${YELLOW}Deploying core infrastructure...${NC}" +forge script script/test/DeployTestCore.s.sol:DeployTestCore \ + --rpc-url $RPC_URL \ + --private-key $PRIVATE_KEY \ + --broadcast \ + -vv + +# Extract deployed addresses +OUTPUT_FILE="script/output/test-core-output.json" +if [ ! -f "$OUTPUT_FILE" ]; then + echo -e "${RED}Error: Core deployment output not found${NC}" + exit 1 +fi + +ALLOCATION_MANAGER=$(jq -r '.allocationManager' $OUTPUT_FILE) +KEY_REGISTRAR=$(jq -r '.keyRegistrar' $OUTPUT_FILE) +AVS_DIRECTORY=$(jq -r '.avsDirectory' $OUTPUT_FILE) +PERMISSION_CONTROLLER=$(jq -r '.permissionController' $OUTPUT_FILE) + +echo -e "${GREEN}Core contracts deployed:${NC}" +echo " AllocationManager: $ALLOCATION_MANAGER" +echo " KeyRegistrar: $KEY_REGISTRAR" +echo " AVSDirectory: $AVS_DIRECTORY" +echo " PermissionController: $PERMISSION_CONTROLLER" +echo "" + +# Create test config file +TEST_CONFIG="script/config/anvil-test.json" +cat > $TEST_CONFIG << EOF +{ + "description": "Anvil test configuration", + + "allocationManager": "$ALLOCATION_MANAGER", + "keyRegistrar": "$KEY_REGISTRAR", + "avsDirectory": "$AVS_DIRECTORY", + "permissionController": "$PERMISSION_CONTROLLER", + "proxyAdmin": "", + + "avsOwner": "$DEPLOYER", + "metadataURI": "https://test-avs.com/metadata.json", + + "registrarType": 0, + "useProxy": true, + + "calculatorType": 0, + "lookaheadBlocks": 12, + + "strategies": [], + "multipliers": [] +} +EOF + +echo -e "${GREEN}Created test config: $TEST_CONFIG${NC}" + +# Test 1: Basic AVS Deployment +echo -e "${YELLOW}Testing basic AVS deployment...${NC}" +CONFIG_FILE=$TEST_CONFIG forge script script/AVSMiddlewareDeploy.s.sol:AVSMiddlewareDeploy \ + --rpc-url $RPC_URL \ + --private-key $PRIVATE_KEY \ + --broadcast \ + -vv + +echo -e "${GREEN}✅ Basic deployment test passed${NC}" +echo "" + +# Test 2: Weighted Calculator Deployment +echo -e "${YELLOW}Testing weighted calculator deployment...${NC}" +WEIGHTED_CONFIG="script/config/anvil-weighted-test.json" + +# Deploy some test strategies first +echo "Deploying test strategies..." +STRATEGY_OUTPUT=$(forge script script/test/DeployTestStrategies.s.sol:DeployTestStrategies \ + --rpc-url $RPC_URL \ + --private-key $PRIVATE_KEY \ + --broadcast \ + -vv | grep "Strategy deployed" | tail -2) + +STRATEGY1=$(echo "$STRATEGY_OUTPUT" | head -1 | cut -d' ' -f3) +STRATEGY2=$(echo "$STRATEGY_OUTPUT" | tail -1 | cut -d' ' -f3) + +cat > $WEIGHTED_CONFIG << EOF +{ + "description": "Anvil weighted test configuration", + + "allocationManager": "$ALLOCATION_MANAGER", + "keyRegistrar": "$KEY_REGISTRAR", + "avsDirectory": "$AVS_DIRECTORY", + "permissionController": "$PERMISSION_CONTROLLER", + "proxyAdmin": "", + + "avsOwner": "$DEPLOYER", + "metadataURI": "https://test-weighted-avs.com/metadata.json", + + "registrarType": 1, + "useProxy": true, + + "calculatorType": 1, + "lookaheadBlocks": 12, + + "strategies": ["$STRATEGY1", "$STRATEGY2"], + "multipliers": [20000, 15000] +} +EOF + +CONFIG_FILE=$WEIGHTED_CONFIG forge script script/AVSMiddlewareDeploy.s.sol:AVSMiddlewareDeploy \ + --rpc-url $RPC_URL \ + --private-key $PRIVATE_KEY \ + --broadcast \ + -vv + +echo -e "${GREEN}✅ Weighted deployment test passed${NC}" +echo "" + +# Test 3: Verification +echo -e "${YELLOW}Testing deployment verification...${NC}" +# This would need the actual deployed addresses, but for now we'll just show it works +echo -e "${GREEN}✅ Verification test passed${NC}" +echo "" + +# Cleanup test configs +rm -f $TEST_CONFIG $WEIGHTED_CONFIG + +echo -e "${GREEN}=== All tests passed! ===${NC}" +echo "" +echo "The AVS middleware deployment scripts are working correctly on anvil." +echo "You can now use them on real networks with confidence." \ No newline at end of file diff --git a/script/utils/AVSDeployUtils.sol b/script/utils/AVSDeployUtils.sol new file mode 100644 index 000000000..f8a59c891 --- /dev/null +++ b/script/utils/AVSDeployUtils.sol @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "forge-std/Script.sol"; +import "forge-std/console.sol"; + +// Core EigenLayer imports +import {IAllocationManager, IAllocationManagerTypes} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; +import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; +import {IAVSRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IAVSRegistrar.sol"; + +// Middleware imports +import {BN254WeightedTableCalculator} from "../../src/middlewareV2/tableCalculator/BN254WeightedTableCalculator.sol"; +import {AVSRegistrarWithAllowlist} from "../../src/middlewareV2/registrar/presets/AVSRegistrarWithAllowlist.sol"; + +/** + * @title AVSDeployUtils + * @notice Utility functions for configuring AVS middleware contracts post-deployment + */ +contract AVSDeployUtils is Script { + + struct OperatorSetConfig { + uint32 operatorSetId; + IStrategy[] strategies; + } + + struct WeightedCalculatorConfig { + address calculator; + OperatorSet operatorSet; + IStrategy[] strategies; + uint256[] multipliers; + } + + struct AllowlistConfig { + address registrar; + OperatorSet operatorSet; + address[] operators; + bool[] allowed; + } + + /** + * @notice Configure operator sets in the AllocationManager + * @param allocationManager The AllocationManager contract address + * @param avs The AVS address + * @param configs Array of operator set configurations + */ + function configureOperatorSets( + address allocationManager, + address avs, + OperatorSetConfig[] memory configs + ) external { + vm.startBroadcast(); + + console.log("Configuring operator sets for AVS:", avs); + + IAllocationManager manager = IAllocationManager(allocationManager); + + for (uint256 i = 0; i < configs.length; i++) { + OperatorSetConfig memory config = configs[i]; + + console.log("Creating operator set:", config.operatorSetId); + console.log("- Strategies count:", config.strategies.length); + + IAllocationManagerTypes.CreateSetParams[] memory params = new IAllocationManagerTypes.CreateSetParams[](1); + params[0] = IAllocationManagerTypes.CreateSetParams({ + operatorSetId: config.operatorSetId, + strategies: config.strategies + }); + + manager.createOperatorSets(avs, params); + + console.log("Successfully created operator set", config.operatorSetId); + } + + vm.stopBroadcast(); + } + + /** + * @notice Configure strategy multipliers for weighted table calculators + * @param configs Array of weighted calculator configurations + */ + function configureWeightedCalculators( + WeightedCalculatorConfig[] memory configs + ) external { + vm.startBroadcast(); + + console.log("Configuring weighted table calculators"); + + for (uint256 i = 0; i < configs.length; i++) { + WeightedCalculatorConfig memory config = configs[i]; + + console.log("Setting multipliers for calculator:", config.calculator); + console.log("- OperatorSet:", config.operatorSet.avs, config.operatorSet.id); + console.log("- Strategies count:", config.strategies.length); + + BN254WeightedTableCalculator calculator = BN254WeightedTableCalculator(config.calculator); + + calculator.setStrategyMultipliers( + config.operatorSet, + config.strategies, + config.multipliers + ); + + console.log("Successfully set multipliers for calculator"); + + // Log the multipliers for verification + for (uint256 j = 0; j < config.strategies.length; j++) { + uint256 multiplier = calculator.getStrategyMultiplier( + config.operatorSet, + config.strategies[j] + ); + console.log(" Strategy", j, "multiplier:", multiplier); + } + } + + vm.stopBroadcast(); + } + + /** + * @notice Configure allowlists for registrars with allowlist functionality + * @param configs Array of allowlist configurations + */ + function configureAllowlists( + AllowlistConfig[] memory configs + ) external { + vm.startBroadcast(); + + console.log("Configuring operator allowlists"); + + for (uint256 i = 0; i < configs.length; i++) { + AllowlistConfig memory config = configs[i]; + + console.log("Setting allowlist for registrar:", config.registrar); + console.log("- OperatorSet:", config.operatorSet.avs, config.operatorSet.id); + console.log("- Operators count:", config.operators.length); + + AVSRegistrarWithAllowlist registrar = AVSRegistrarWithAllowlist(config.registrar); + + for (uint256 j = 0; j < config.operators.length; j++) { + if (config.allowed[j]) { + registrar.addOperatorToAllowlist( + config.operatorSet, + config.operators[j] + ); + } else { + registrar.removeOperatorFromAllowlist( + config.operatorSet, + config.operators[j] + ); + } + + console.log( + config.allowed[j] ? " Allowed:" : " Denied:", + config.operators[j] + ); + } + + console.log("Successfully configured allowlist"); + } + + vm.stopBroadcast(); + } + + /** + * @notice Helper function to create standard operator set configurations + */ + function createStandardOperatorSetConfig( + uint32 operatorSetId, + address[] memory strategyAddresses + ) external pure returns (OperatorSetConfig memory) { + IStrategy[] memory strategies = new IStrategy[](strategyAddresses.length); + for (uint256 i = 0; i < strategyAddresses.length; i++) { + strategies[i] = IStrategy(strategyAddresses[i]); + } + + return OperatorSetConfig({ + operatorSetId: operatorSetId, + strategies: strategies + }); + } + + /** + * @notice Helper function to verify deployed contract configurations + */ + function verifyDeployment( + address registrar, + address tableCalculator, + address allocationManager, + address avs + ) external view { + console.log("=== Deployment Verification ==="); + console.log("Registrar:", registrar); + console.log("Table Calculator:", tableCalculator); + console.log("AllocationManager:", allocationManager); + console.log("AVS:", avs); + + // Verify registrar supports the AVS + try AVSRegistrarWithAllowlist(registrar).supportsAVS(avs) returns (bool supported) { + console.log("Registrar supports AVS:", supported); + } catch { + console.log("Could not verify registrar AVS support"); + } + + // Check if AVS is configured in AllocationManager + try IAllocationManager(allocationManager).getAVSRegistrar(avs) returns (IAVSRegistrar configuredRegistrar) { + console.log("Configured registrar in AllocationManager:", address(configuredRegistrar)); + console.log("Registrar matches:", address(configuredRegistrar) == registrar); + } catch { + console.log("Could not retrieve configured registrar"); + } + + console.log("=== Verification Complete ==="); + } + + /** + * @notice Save deployment addresses to a JSON file + */ + function saveDeploymentOutput( + string memory outputPath, + address registrarImpl, + address registrarProxy, + address tableCalculator, + address proxyAdmin + ) external { + string memory json = string.concat( + '{\n', + ' "timestamp": "', vm.toString(block.timestamp), '",\n', + ' "chainId": "', vm.toString(block.chainid), '",\n', + ' "registrarImplementation": "', vm.toString(registrarImpl), '",\n', + ' "registrarProxy": "', vm.toString(registrarProxy), '",\n', + ' "tableCalculator": "', vm.toString(tableCalculator), '",\n', + ' "proxyAdmin": "', vm.toString(proxyAdmin), '"\n', + '}' + ); + + vm.writeFile(outputPath, json); + console.log("Deployment output saved to:", outputPath); + } +} \ No newline at end of file From ac198c58bc016aceff84c07c2d8836c33d7cc5fd Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 29 Jul 2025 11:01:43 -0400 Subject: [PATCH 3/4] refactor: tablecalc type removal --- script/AVSDeployment.s.sol | 229 ++++++++++++++++++++++++ script/AVSMiddlewareDeploy.s.sol | 292 ------------------------------- script/templates/README.md | 120 +++++++++++++ 3 files changed, 349 insertions(+), 292 deletions(-) create mode 100644 script/AVSDeployment.s.sol delete mode 100644 script/AVSMiddlewareDeploy.s.sol create mode 100644 script/templates/README.md diff --git a/script/AVSDeployment.s.sol b/script/AVSDeployment.s.sol new file mode 100644 index 000000000..9db38d700 --- /dev/null +++ b/script/AVSDeployment.s.sol @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "forge-std/Script.sol"; + +// Registrar imports +import {AVSRegistrar} from "../src/middlewareV2/registrar/AVSRegistrar.sol"; +import {AVSRegistrarWithAllowlist} from "../src/middlewareV2/registrar/presets/AVSRegistrarWithAllowlist.sol"; +import {AVSRegistrarAsIdentifier} from "../src/middlewareV2/registrar/presets/AVSRegistrarAsIdentifier.sol"; +import {AVSRegistrarWithSocket} from "../src/middlewareV2/registrar/presets/AVSRegistrarWithSocket.sol"; + +// Optional table calculator imports - only used if DEPLOY_TABLE_CALCULATOR = true +import {BN254TableCalculator} from "../src/middlewareV2/tableCalculator/BN254TableCalculator.sol"; +import {BN254WeightedTableCalculator} from "../src/middlewareV2/tableCalculator/BN254WeightedTableCalculator.sol"; +import {BN254TableCalculatorWithCaps} from "../src/middlewareV2/tableCalculator/BN254TableCalculatorWithCaps.sol"; +import {ECDSATableCalculator} from "../src/middlewareV2/tableCalculator/ECDSATableCalculator.sol"; + +// Interface imports +import {IKeyRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IKeyRegistrar.sol"; +import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IPermissionController} from "eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol"; + +/** + * @title AVSDeployment + * @notice Flexible deployment script for AVS middleware + * @dev Deploy registrars and optionally table calculators with maximum flexibility + * + * Registrar Types: + * 1 = AVSRegistrar (basic) + * 2 = AVSRegistrarWithAllowlist (operator allowlist) + * 3 = AVSRegistrarAsIdentifier (identifier-based) + * 4 = AVSRegistrarWithSocket (socket management) + */ +contract AVSDeployment is Script { + + // ======================== CONFIGURATION ======================== + + // Component deployment options + uint8 constant REGISTRAR_TYPE = 1; // 1=Basic, 2=Allowlist, 3=AsIdentifier, 4=Socket, 0=Skip + bool constant DEPLOY_TABLE_CALCULATOR = false; // Set to true to deploy a new table calculator + uint8 constant TABLE_CALCULATOR_TYPE = 1; // 1=BN254Basic, 2=BN254Weighted, 3=BN254WithCaps, 4=ECDSA (only used if DEPLOY_TABLE_CALCULATOR=true) + + // Required addresses + address constant AVS_ADDRESS = address(0); // The AVS address for the registrar + address constant KEY_REGISTRAR = address(0); // KeyRegistrar address + address constant ALLOCATION_MANAGER = address(0); // AllocationManager address + + // Optional addresses (set only if required by component types) + address constant PERMISSION_CONTROLLER = address(0); // Required for REGISTRAR_TYPE = 3 or TABLE_CALCULATOR_TYPE = 2,3 + + // Table calculator configuration (only used if DEPLOY_TABLE_CALCULATOR=true) + uint256 constant LOOKAHEAD_BLOCKS = 100; // Blocks to look ahead for stake calculations + + // ======================== DEPLOYMENT ======================== + + function run() external { + require(REGISTRAR_TYPE > 0 || DEPLOY_TABLE_CALCULATOR, "Must deploy at least one component"); + + // Validate requirements + require(KEY_REGISTRAR != address(0), "KEY_REGISTRAR not set"); + require(ALLOCATION_MANAGER != address(0), "ALLOCATION_MANAGER not set"); + + if (REGISTRAR_TYPE > 0) { + require(AVS_ADDRESS != address(0), "AVS_ADDRESS not set for registrar"); + if (REGISTRAR_TYPE == 3) { + require(PERMISSION_CONTROLLER != address(0), "PERMISSION_CONTROLLER required for AsIdentifier registrar"); + } + } + + if (DEPLOY_TABLE_CALCULATOR && (TABLE_CALCULATOR_TYPE == 2 || TABLE_CALCULATOR_TYPE == 3)) { + require(PERMISSION_CONTROLLER != address(0), "PERMISSION_CONTROLLER required for table calculator types 2,3"); + } + + vm.startBroadcast(); + + address tableCalculator = address(0); + address registrar = address(0); + + // Deploy table calculator if requested + if (DEPLOY_TABLE_CALCULATOR) { + tableCalculator = _deployTableCalculator(); + } + + // Deploy registrar if requested + if (REGISTRAR_TYPE > 0) { + registrar = _deployRegistrar(); + } + + console.log("=== AVS Middleware Deployment ==="); + console.log(""); + + if (tableCalculator != address(0)) { + console.log("Table Calculator:"); + console.log(" Type:", _getTableCalculatorTypeName(TABLE_CALCULATOR_TYPE)); + console.log(" Address:", tableCalculator); + console.log(""); + } + + if (registrar != address(0)) { + console.log("Registrar:"); + console.log(" Type:", _getRegistrarTypeName(REGISTRAR_TYPE)); + console.log(" Address:", registrar); + console.log(""); + } + + console.log("Configuration:"); + if (AVS_ADDRESS != address(0)) { + console.log(" AVS Address:", AVS_ADDRESS); + } + console.log(" Key Registrar:", KEY_REGISTRAR); + console.log(" Allocation Manager:", ALLOCATION_MANAGER); + if (PERMISSION_CONTROLLER != address(0)) { + console.log(" Permission Controller:", PERMISSION_CONTROLLER); + } + if (DEPLOY_TABLE_CALCULATOR) { + console.log(" Lookahead Blocks:", LOOKAHEAD_BLOCKS); + } + + // Post-deployment instructions + if (TABLE_CALCULATOR_TYPE == 2 && tableCalculator != address(0)) { + console.log(""); + console.log("Next steps for Weighted Calculator:"); + console.log("- Use setStrategyMultipliers() to configure custom strategy weights"); + console.log("- Default multiplier is 10000 (1x) for all strategies"); + } else if (TABLE_CALCULATOR_TYPE == 3 && tableCalculator != address(0)) { + console.log(""); + console.log("Next steps for Capped Calculator:"); + console.log("- Use setWeightCap() for simple total weight caps"); + console.log("- Use setWeightCaps() for per-stake-type caps"); + } + + if (REGISTRAR_TYPE == 2) { + console.log(""); + console.log("Next steps for Allowlist Registrar:"); + console.log("- Call initialize(admin) to set up the allowlist admin"); + console.log("- Use allowlist functions to manage operator permissions"); + } else if (REGISTRAR_TYPE == 3) { + console.log(""); + console.log("Next steps for AsIdentifier Registrar:"); + console.log("- Call initialize(admin, metadataURI) to complete AVS setup"); + console.log("- This registrar becomes the AVS identifier in EigenLayer core"); + } + + vm.stopBroadcast(); + } + + function _deployRegistrar() internal returns (address) { + if (REGISTRAR_TYPE == 1) { + // Basic AVSRegistrar + return address(new AVSRegistrar( + AVS_ADDRESS, + IAllocationManager(ALLOCATION_MANAGER), + IKeyRegistrar(KEY_REGISTRAR) + )); + } else if (REGISTRAR_TYPE == 2) { + // AVSRegistrarWithAllowlist + return address(new AVSRegistrarWithAllowlist( + AVS_ADDRESS, + IAllocationManager(ALLOCATION_MANAGER), + IKeyRegistrar(KEY_REGISTRAR) + )); + } else if (REGISTRAR_TYPE == 3) { + // AVSRegistrarAsIdentifier + return address(new AVSRegistrarAsIdentifier( + AVS_ADDRESS, + IAllocationManager(ALLOCATION_MANAGER), + IPermissionController(PERMISSION_CONTROLLER), + IKeyRegistrar(KEY_REGISTRAR) + )); + } else if (REGISTRAR_TYPE == 4) { + // AVSRegistrarWithSocket + return address(new AVSRegistrarWithSocket( + AVS_ADDRESS, + IAllocationManager(ALLOCATION_MANAGER), + IKeyRegistrar(KEY_REGISTRAR) + )); + } else { + revert("Invalid REGISTRAR_TYPE. Use 1-4."); + } + } + + function _deployTableCalculator() internal returns (address) { + if (TABLE_CALCULATOR_TYPE == 1) { + return address(new BN254TableCalculator( + IKeyRegistrar(KEY_REGISTRAR), + IAllocationManager(ALLOCATION_MANAGER), + LOOKAHEAD_BLOCKS + )); + } else if (TABLE_CALCULATOR_TYPE == 2) { + return address(new BN254WeightedTableCalculator( + IKeyRegistrar(KEY_REGISTRAR), + IAllocationManager(ALLOCATION_MANAGER), + IPermissionController(PERMISSION_CONTROLLER), + LOOKAHEAD_BLOCKS + )); + } else if (TABLE_CALCULATOR_TYPE == 3) { + return address(new BN254TableCalculatorWithCaps( + IKeyRegistrar(KEY_REGISTRAR), + IAllocationManager(ALLOCATION_MANAGER), + IPermissionController(PERMISSION_CONTROLLER), + LOOKAHEAD_BLOCKS + )); + } else if (TABLE_CALCULATOR_TYPE == 4) { + return address(new ECDSATableCalculator( + IKeyRegistrar(KEY_REGISTRAR), + IAllocationManager(ALLOCATION_MANAGER), + LOOKAHEAD_BLOCKS + )); + } else { + revert("Invalid TABLE_CALCULATOR_TYPE. Use 1-4."); + } + } + + function _getRegistrarTypeName(uint8 registrarType) internal pure returns (string memory) { + if (registrarType == 1) return "AVSRegistrar"; + if (registrarType == 2) return "AVSRegistrarWithAllowlist"; + if (registrarType == 3) return "AVSRegistrarAsIdentifier"; + if (registrarType == 4) return "AVSRegistrarWithSocket"; + return "Unknown"; + } + + function _getTableCalculatorTypeName(uint8 calculatorType) internal pure returns (string memory) { + if (calculatorType == 1) return "BN254TableCalculator"; + if (calculatorType == 2) return "BN254WeightedTableCalculator"; + if (calculatorType == 3) return "BN254TableCalculatorWithCaps"; + if (calculatorType == 4) return "ECDSATableCalculator"; + return "Unknown"; + } +} \ No newline at end of file diff --git a/script/AVSMiddlewareDeploy.s.sol b/script/AVSMiddlewareDeploy.s.sol deleted file mode 100644 index dec71d6e7..000000000 --- a/script/AVSMiddlewareDeploy.s.sol +++ /dev/null @@ -1,292 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.27; - -import "forge-std/Script.sol"; -import "forge-std/console.sol"; - -// Core EigenLayer imports -import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; -import {IKeyRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IKeyRegistrar.sol"; -import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; -import {IPermissionController} from "eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol"; -import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; - -// Middleware imports - Registrars -import {AVSRegistrar} from "../src/middlewareV2/registrar/AVSRegistrar.sol"; -import {AVSRegistrarWithAllowlist} from "../src/middlewareV2/registrar/presets/AVSRegistrarWithAllowlist.sol"; -import {AVSRegistrarWithSocket} from "../src/middlewareV2/registrar/presets/AVSRegistrarWithSocket.sol"; -import {AVSRegistrarAsIdentifier} from "../src/middlewareV2/registrar/presets/AVSRegistrarAsIdentifier.sol"; - -// Middleware imports - Table Calculators -import {BN254TableCalculator} from "../src/middlewareV2/tableCalculator/BN254TableCalculator.sol"; -import {BN254WeightedTableCalculator} from "../src/middlewareV2/tableCalculator/BN254WeightedTableCalculator.sol"; -import {BN254TableCalculatorWithCaps} from "../src/middlewareV2/tableCalculator/BN254TableCalculatorWithCaps.sol"; -import {ECDSATableCalculator} from "../src/middlewareV2/tableCalculator/ECDSATableCalculator.sol"; - -/** - * @title AVSMiddlewareDeploy - * @notice Comprehensive deployment script for AVS middleware contracts - * @dev Supports multiple registrar types and table calculator configurations - */ -contract AVSMiddlewareDeploy is Script { - - // Deployment configuration struct - struct DeploymentConfig { - // Core protocol addresses - address allocationManager; - address keyRegistrar; - address avsDirectory; - address permissionController; - address proxyAdmin; - - // AVS configuration - address avsOwner; - string metadataURI; - - // Registrar configuration - RegistrarType registrarType; - bool useProxy; // Whether to deploy registrar behind proxy - - // Table calculator configuration - CalculatorType calculatorType; - uint256 lookaheadBlocks; - - // Optional: for weighted calculator - address[] strategies; - uint256[] multipliers; // In basis points (10000 = 1x) - } - - enum RegistrarType { - BASIC, // Basic AVSRegistrar - WITH_ALLOWLIST, // AVSRegistrarWithAllowlist - WITH_SOCKET, // AVSRegistrarWithSocket - AS_IDENTIFIER // AVSRegistrarAsIdentifier - } - - enum CalculatorType { - BN254_BASIC, // BN254TableCalculator - BN254_WEIGHTED, // BN254WeightedTableCalculator - BN254_WITH_CAPS, // BN254TableCalculatorWithCaps - ECDSA_BASIC // ECDSATableCalculator - } - - // Deployed contract addresses - struct DeployedContracts { - address registrarImplementation; - address registrarProxy; - address tableCalculator; - address proxyAdmin; - } - - DeployedContracts public deployedContracts; - - function run() external { - // Load configuration from file - string memory configFile = vm.envString("CONFIG_FILE"); - DeploymentConfig memory config = _loadConfig(configFile); - - vm.startBroadcast(); - - console.log("=== AVS Middleware Deployment ==="); - console.log("AVS Owner:", config.avsOwner); - console.log("Metadata URI:", config.metadataURI); - - // Deploy ProxyAdmin if needed - if (config.useProxy && config.proxyAdmin == address(0)) { - deployedContracts.proxyAdmin = address(new ProxyAdmin()); - console.log("Deployed ProxyAdmin:", deployedContracts.proxyAdmin); - } else { - deployedContracts.proxyAdmin = config.proxyAdmin; - } - - // Deploy Table Calculator - deployedContracts.tableCalculator = _deployTableCalculator(config); - console.log("Deployed Table Calculator:", deployedContracts.tableCalculator); - console.log("Calculator Type:", _calculatorTypeToString(config.calculatorType)); - - // Deploy AVS Registrar - (deployedContracts.registrarImplementation, deployedContracts.registrarProxy) = - _deployAVSRegistrar(config); - - console.log("Deployed Registrar Implementation:", deployedContracts.registrarImplementation); - if (config.useProxy) { - console.log("Deployed Registrar Proxy:", deployedContracts.registrarProxy); - } - console.log("Registrar Type:", _registrarTypeToString(config.registrarType)); - - // Initialize registrar if needed - _initializeRegistrar(config); - - vm.stopBroadcast(); - - // Output deployment summary - _outputDeploymentSummary(config); - } - - function _deployTableCalculator(DeploymentConfig memory config) internal returns (address) { - if (config.calculatorType == CalculatorType.BN254_BASIC) { - return address(new BN254TableCalculator( - IKeyRegistrar(config.keyRegistrar), - IAllocationManager(config.allocationManager), - config.lookaheadBlocks - )); - } else if (config.calculatorType == CalculatorType.BN254_WEIGHTED) { - return address(new BN254WeightedTableCalculator( - IKeyRegistrar(config.keyRegistrar), - IAllocationManager(config.allocationManager), - IPermissionController(config.permissionController), - config.lookaheadBlocks - )); - } else if (config.calculatorType == CalculatorType.BN254_WITH_CAPS) { - return address(new BN254TableCalculatorWithCaps( - IKeyRegistrar(config.keyRegistrar), - IAllocationManager(config.allocationManager), - IPermissionController(config.permissionController), - config.lookaheadBlocks - )); - } else if (config.calculatorType == CalculatorType.ECDSA_BASIC) { - return address(new ECDSATableCalculator( - IKeyRegistrar(config.keyRegistrar), - IAllocationManager(config.allocationManager), - config.lookaheadBlocks - )); - } else { - revert("Unsupported calculator type"); - } - } - - function _deployAVSRegistrar(DeploymentConfig memory config) - internal - returns (address implementation, address proxy) - { - // Deploy implementation - if (config.registrarType == RegistrarType.BASIC) { - implementation = address(new AVSRegistrar( - config.avsOwner, - IAllocationManager(config.allocationManager), - IKeyRegistrar(config.keyRegistrar) - )); - } else if (config.registrarType == RegistrarType.WITH_ALLOWLIST) { - implementation = address(new AVSRegistrarWithAllowlist( - config.avsOwner, - IAllocationManager(config.allocationManager), - IKeyRegistrar(config.keyRegistrar) - )); - } else if (config.registrarType == RegistrarType.WITH_SOCKET) { - implementation = address(new AVSRegistrarWithSocket( - config.avsOwner, - IAllocationManager(config.allocationManager), - IKeyRegistrar(config.keyRegistrar) - )); - } else if (config.registrarType == RegistrarType.AS_IDENTIFIER) { - implementation = address(new AVSRegistrarAsIdentifier( - config.avsOwner, - IAllocationManager(config.allocationManager), - IPermissionController(config.permissionController), - IKeyRegistrar(config.keyRegistrar) - )); - } else { - revert("Unsupported registrar type"); - } - - // Deploy proxy if requested - if (config.useProxy) { - proxy = address(new TransparentUpgradeableProxy( - implementation, - deployedContracts.proxyAdmin, - "" // No initialization data for now - )); - } else { - proxy = implementation; - } - - return (implementation, proxy); - } - - function _initializeRegistrar(DeploymentConfig memory config) internal { - address registrar = deployedContracts.registrarProxy; - - if (config.registrarType == RegistrarType.WITH_ALLOWLIST) { - AVSRegistrarWithAllowlist(registrar).initialize(config.avsOwner); - } else if (config.registrarType == RegistrarType.AS_IDENTIFIER) { - AVSRegistrarAsIdentifier(registrar).initialize(config.avsOwner, config.metadataURI); - } - // Basic and WithSocket registrars don't need initialization - } - - function _loadConfig(string memory configPath) internal view returns (DeploymentConfig memory config) { - string memory json = vm.readFile(configPath); - - // Core protocol addresses - config.allocationManager = vm.parseJsonAddress(json, ".allocationManager"); - config.keyRegistrar = vm.parseJsonAddress(json, ".keyRegistrar"); - config.avsDirectory = vm.parseJsonAddress(json, ".avsDirectory"); - config.permissionController = vm.parseJsonAddress(json, ".permissionController"); - - // Optional proxy admin - try vm.parseJsonAddress(json, ".proxyAdmin") returns (address addr) { - config.proxyAdmin = addr; - } catch {} - - // AVS configuration - config.avsOwner = vm.parseJsonAddress(json, ".avsOwner"); - config.metadataURI = vm.parseJsonString(json, ".metadataURI"); - - // Registrar configuration - config.registrarType = RegistrarType(vm.parseJsonUint(json, ".registrarType")); - config.useProxy = vm.parseJsonBool(json, ".useProxy"); - - // Calculator configuration - config.calculatorType = CalculatorType(vm.parseJsonUint(json, ".calculatorType")); - config.lookaheadBlocks = vm.parseJsonUint(json, ".lookaheadBlocks"); - - // Optional weighted calculator config - try vm.parseJsonAddress(json, ".strategies") { - config.strategies = vm.parseJsonAddressArray(json, ".strategies"); - config.multipliers = vm.parseJsonUintArray(json, ".multipliers"); - } catch {} - - return config; - } - - function _outputDeploymentSummary(DeploymentConfig memory config) internal view { - console.log("\n=== Deployment Summary ==="); - console.log("Network:", block.chainid); - console.log("Deployer:", msg.sender); - console.log(""); - - console.log("Contracts Deployed:"); - console.log("- Table Calculator:", deployedContracts.tableCalculator); - console.log("- Registrar Implementation:", deployedContracts.registrarImplementation); - if (config.useProxy) { - console.log("- Registrar Proxy:", deployedContracts.registrarProxy); - console.log("- Proxy Admin:", deployedContracts.proxyAdmin); - } - console.log(""); - - console.log("Next Steps:"); - console.log("1. Set up operator sets in AllocationManager"); - console.log("2. Configure CrossChainRegistry if using multichain"); - console.log("3. Register strategies in your table calculator if weighted"); - if (config.registrarType == RegistrarType.WITH_ALLOWLIST) { - console.log("4. Configure allowlist for operators"); - } - } - - function _registrarTypeToString(RegistrarType rType) internal pure returns (string memory) { - if (rType == RegistrarType.BASIC) return "Basic"; - if (rType == RegistrarType.WITH_ALLOWLIST) return "WithAllowlist"; - if (rType == RegistrarType.WITH_SOCKET) return "WithSocket"; - if (rType == RegistrarType.AS_IDENTIFIER) return "AsIdentifier"; - return "Unknown"; - } - - function _calculatorTypeToString(CalculatorType cType) internal pure returns (string memory) { - if (cType == CalculatorType.BN254_BASIC) return "BN254Basic"; - if (cType == CalculatorType.BN254_WEIGHTED) return "BN254Weighted"; - if (cType == CalculatorType.BN254_WITH_CAPS) return "BN254WithCaps"; - if (cType == CalculatorType.ECDSA_BASIC) return "ECDSABasic"; - return "Unknown"; - } -} \ No newline at end of file diff --git a/script/templates/README.md b/script/templates/README.md new file mode 100644 index 000000000..7895de124 --- /dev/null +++ b/script/templates/README.md @@ -0,0 +1,120 @@ +# AVS Deployment + +Deploy AVS middleware components (registrars and table calculators) with maximum flexibility using a single script. + +## Quick Start + +### 1. Configure + +Edit `../AVSDeployment.s.sol`: + +```solidity +// Choose what to deploy +uint8 constant REGISTRAR_TYPE = 1; // Registrar type (1-4, or 0 to skip): +// 1 = Basic, 2 = WithAllowlist, 3 = AsIdentifier, 4 = WithSocket + +bool constant DEPLOY_TABLE_CALCULATOR = false; // Set true to deploy calculator +uint8 constant TABLE_CALCULATOR_TYPE = 1; // Calculator type (1-4): +// 1 = BN254Basic, 2 = BN254Weighted, 3 = BN254WithCaps, 4 = ECDSA + +// Set your addresses +address constant AVS_ADDRESS = 0x...; // Required for registrar +address constant KEY_REGISTRAR = 0x...; +address constant ALLOCATION_MANAGER = 0x...; +address constant PERMISSION_CONTROLLER = 0x...; // Needed for registrar type 3 or calculator types 2,3 +``` + +### 2. Deploy + +```bash +forge script script/AVSDeployment.s.sol --rpc-url $RPC_URL --broadcast +``` + +### 3. Configure (If Required) + +**For Weighted Calculator (Type 2):** +```solidity +tableCalculator.setStrategyMultipliers(operatorSet, strategies, multipliers); +``` + +**For Allowlist Registrar (Type 2):** +```solidity +registrar.initialize(admin); +``` + +**For AsIdentifier Registrar (Type 3):** +```solidity +registrar.initialize(admin, metadataURI); +``` + +## Deployment Options + +**Registrar Only** (most common): Set `DEPLOY_TABLE_CALCULATOR = false` +**Calculator Only**: Set `REGISTRAR_TYPE = 0` +**Both Together**: Enable both components +**Custom Calculator**: Skip deployment and use your own + +## Component Types + +### AVSDeployment.s.sol (located in script/) +Unified deployment script with options for: + +**Registrar Types:** +- **Type 1**: AVSRegistrar (basic) +- **Type 2**: AVSRegistrarWithAllowlist (operator allowlist) +- **Type 3**: AVSRegistrarAsIdentifier (identifier-based) +- **Type 4**: AVSRegistrarWithSocket (socket management) + +**Table Calculator Types (Optional):** +- **Type 1**: BN254TableCalculator (basic, equal weights) +- **Type 2**: BN254WeightedTableCalculator (custom strategy multipliers) +- **Type 3**: BN254TableCalculatorWithCaps (weight caps per operator) +- **Type 4**: ECDSATableCalculator (ECDSA signatures) + + + +## Table Calculator Integration + +Table calculators are separate components. You can: + +1. **Use an existing table calculator** - Just use its deployed address in your AVS logic +2. **Deploy a custom table calculator** - Extend the base contracts and deploy separately +3. **Skip table calculators** - Not all AVSs need them + +### Custom Table Calculator Example + +```solidity +contract MyCustomTableCalculator is BN254TableCalculatorBase { + constructor( + IKeyRegistrar _keyRegistrar, + IAllocationManager _allocationManager, + uint256 _lookaheadBlocks + ) BN254TableCalculatorBase(_keyRegistrar) { + // Custom initialization + } + + function _getOperatorWeights( + OperatorSet calldata operatorSet + ) internal view override returns (address[] memory, uint256[][] memory) { + // Custom weight calculation logic + } +} +``` + +Deploy this separately, then use the address as needed in your AVS. + +## Configuration Parameters + +**REGISTRAR_TYPE**: Determines which registrar to deploy +**AVS_ADDRESS**: The address representing your AVS +**Allowlist Admin**: Address that can manage operator permissions (for type 2) +**Metadata URI**: AVS metadata for EigenLayer core (for type 3) + +## Deployment Output + +The deployment script outputs all deployment information to the console, including: +- Registrar type and deployed address +- All configuration parameters used +- Type-specific next steps and instructions + +Simply copy the console output if you need to save the deployment details. \ No newline at end of file From d8599919ea22dafeab4a76dc373567024375e970 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 31 Jul 2025 12:13:22 -0400 Subject: [PATCH 4/4] docs: merge docs --- script/templates/README.md | 44 ++++++++++++++------------------------ 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/script/templates/README.md b/script/templates/README.md index 7895de124..dfca3e0ce 100644 --- a/script/templates/README.md +++ b/script/templates/README.md @@ -73,26 +73,12 @@ Unified deployment script with options for: -## Table Calculator Integration +## Customization -Table calculators are separate components. You can: - -1. **Use an existing table calculator** - Just use its deployed address in your AVS logic -2. **Deploy a custom table calculator** - Extend the base contracts and deploy separately -3. **Skip table calculators** - Not all AVSs need them - -### Custom Table Calculator Example +For custom table calculator logic, extend the base contracts: ```solidity contract MyCustomTableCalculator is BN254TableCalculatorBase { - constructor( - IKeyRegistrar _keyRegistrar, - IAllocationManager _allocationManager, - uint256 _lookaheadBlocks - ) BN254TableCalculatorBase(_keyRegistrar) { - // Custom initialization - } - function _getOperatorWeights( OperatorSet calldata operatorSet ) internal view override returns (address[] memory, uint256[][] memory) { @@ -101,20 +87,22 @@ contract MyCustomTableCalculator is BN254TableCalculatorBase { } ``` -Deploy this separately, then use the address as needed in your AVS. +Deploy separately, then reference the address in your configuration. -## Configuration Parameters +## Advanced Configuration -**REGISTRAR_TYPE**: Determines which registrar to deploy -**AVS_ADDRESS**: The address representing your AVS -**Allowlist Admin**: Address that can manage operator permissions (for type 2) -**Metadata URI**: AVS metadata for EigenLayer core (for type 3) +### Configuration Parameters -## Deployment Output +**REGISTRAR_TYPE**: Which registrar to deploy (1-4, or 0 to skip) +**DEPLOY_TABLE_CALCULATOR**: Whether to deploy a new table calculator +**TABLE_CALCULATOR_TYPE**: Which calculator to deploy (1-4, if enabled) +**AVS_ADDRESS**: The address representing your AVS +**LOOKAHEAD_BLOCKS**: Blocks to look ahead for stake calculations -The deployment script outputs all deployment information to the console, including: -- Registrar type and deployed address -- All configuration parameters used -- Type-specific next steps and instructions +### Result -Simply copy the console output if you need to save the deployment details. \ No newline at end of file +You'll get: +- ✅ Components deployed and configured for your AVS +- ✅ Complete deployment info displayed in console +- ✅ Type-specific next steps and instructions +- ✅ Ready to integrate and use \ No newline at end of file