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/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/templates/README.md b/script/templates/README.md new file mode 100644 index 000000000..dfca3e0ce --- /dev/null +++ b/script/templates/README.md @@ -0,0 +1,108 @@ +# 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) + + + +## Customization + +For custom table calculator logic, extend the base contracts: + +```solidity +contract MyCustomTableCalculator is BN254TableCalculatorBase { + function _getOperatorWeights( + OperatorSet calldata operatorSet + ) internal view override returns (address[] memory, uint256[][] memory) { + // Custom weight calculation logic + } +} +``` + +Deploy separately, then reference the address in your configuration. + +## Advanced Configuration + +### Configuration Parameters + +**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 + +### Result + +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 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 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/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 index bde231059..bed72585f 100644 --- a/test/unit/libraries/WeightCapUtilsUnit.t.sol +++ b/test/unit/libraries/WeightCapUtilsUnit.t.sol @@ -53,7 +53,7 @@ contract WeightCapUtilsUnitTests is Test { 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);