From 5e39acb8680b7d1f1b244df37843dea1c6f471d2 Mon Sep 17 00:00:00 2001 From: noel Date: Thu, 18 Jul 2024 17:14:35 +0900 Subject: [PATCH] feat: AVS Creator --- contracts/script/AVSCreatorDeployer.s.sol | 106 +++++++++++++ contracts/src/core/AVSCreator.sol | 182 ++++++++++++++++++++++ contracts/test/AVSCreator.t.sol | 135 ++++++++++++++++ 3 files changed, 423 insertions(+) create mode 100644 contracts/script/AVSCreatorDeployer.s.sol create mode 100644 contracts/src/core/AVSCreator.sol create mode 100644 contracts/test/AVSCreator.t.sol diff --git a/contracts/script/AVSCreatorDeployer.s.sol b/contracts/script/AVSCreatorDeployer.s.sol new file mode 100644 index 0000000..8848e9d --- /dev/null +++ b/contracts/script/AVSCreatorDeployer.s.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.12; + +import "forge-std/Script.sol"; +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; + +import "../src/core/AVSCreator.sol"; +import {IndexRegistry} from "eigenlayer-middleware/IndexRegistry.sol"; +import {StakeRegistry} from "eigenlayer-middleware/StakeRegistry.sol"; +import {BLSApkRegistry} from "eigenlayer-middleware/BLSApkRegistry.sol"; +import {RegistryCoordinator} from "eigenlayer-middleware/RegistryCoordinator.sol"; +import {MachServiceManager} from "../src/core/MachServiceManager.sol"; +import {StakeRegistry, IStrategy} from "eigenlayer-middleware/StakeRegistry.sol"; + +// DELEGATION_MANAGER=$DELEGATION_MANAGER AVS_DIRECTORY=$AVS_DIRECTORY INITIAL_OWNER=$INITIAL_OWNER forge script ./script/AVSCreatorDeployer.s.sol \ +// --private-key $PK \ +// --rpc-url $URL \ +// --etherscan-api-key $API_KEY \ +// --broadcast -vvvv --slow --verify + +struct TokenAndWeight { + address token; + uint96 weight; +} + +contract AVSCreatorDeployer is Script { + function run() external { + vm.startBroadcast(); + + address initialOwner = vm.envAddress("INITIAL_OWNER"); + address delegationManager = vm.envAddress("DELEGATION_MANAGER"); + address avsDirectory = vm.envAddress("AVS_DIRECTORY"); + + AVSCreator creator = new AVSCreator(delegationManager, avsDirectory); + + // Set the bytecodes + creator.setIndexRegistryBytecode(type(IndexRegistry).creationCode); + creator.setStakeRegistryBytecode(type(StakeRegistry).creationCode); + creator.setApkRegistryBytecode(type(BLSApkRegistry).creationCode); + creator.setRegistryCoordinatorBytecode(type(RegistryCoordinator).creationCode); + creator.setServiceManagerBytecode(type(MachServiceManager).creationCode); + + uint256 numQuorums = 1; + uint256 numStrategies = 3; + uint96 minimumStake = 0; + uint32 maxOperatorCount = 50; + { + // strategies deployed + TokenAndWeight[] memory deployedStrategyArray = new TokenAndWeight[](numStrategies); + + { + // need manually step in + deployedStrategyArray[0].token = 0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0; + deployedStrategyArray[1].token = 0x7D704507b76571a51d9caE8AdDAbBFd0ba0e63d3; + deployedStrategyArray[2].token = 0x3A8fBdf9e77DFc25d09741f51d3E181b25d0c4E0; + } + + { + // need manually step in + deployedStrategyArray[0].weight = 1000000000000000000; + deployedStrategyArray[1].weight = 997992210000000000; + deployedStrategyArray[2].weight = 1104234999999999999; + } + + IRegistryCoordinator.OperatorSetParam[] memory operatorSetParams = + new IRegistryCoordinator.OperatorSetParam[](numQuorums); + + // prepare _operatorSetParams + for (uint256 i = 0; i < numQuorums; i++) { + // hard code these for now + operatorSetParams[i] = IRegistryCoordinator.OperatorSetParam({ + maxOperatorCount: maxOperatorCount, + kickBIPsOfOperatorStake: 11000, // an operator needs to have kickBIPsOfOperatorStake / 10000 times the stake of the operator with the least stake to kick them out + kickBIPsOfTotalStake: 1001 // an operator needs to have less than kickBIPsOfTotalStake / 10000 of the total stake to be kicked out + }); + } + + // prepare _minimumStakes + uint96[] memory minimumStakeForQuourm = new uint96[](numQuorums); + for (uint256 i = 0; i < numQuorums; i++) { + minimumStakeForQuourm[i] = minimumStake; + } + + // prepare _strategyParams + IStakeRegistry.StrategyParams[][] memory strategyParams = new IStakeRegistry.StrategyParams[][](numQuorums); + for (uint256 i = 0; i < numQuorums; i++) { + IStakeRegistry.StrategyParams[] memory params = new IStakeRegistry.StrategyParams[](numStrategies); + for (uint256 j = 0; j < numStrategies; j++) { + params[j] = IStakeRegistry.StrategyParams({ + strategy: IStrategy(deployedStrategyArray[j].token), + multiplier: deployedStrategyArray[j].weight + }); + } + strategyParams[i] = params; + } + + creator.createAVS( + initialOwner, initialOwner, initialOwner, operatorSetParams, minimumStakeForQuourm, strategyParams + ); + } + + // Stop broadcasting transactions + vm.stopBroadcast(); + } +} diff --git a/contracts/src/core/AVSCreator.sol b/contracts/src/core/AVSCreator.sol new file mode 100644 index 0000000..16fe8d7 --- /dev/null +++ b/contracts/src/core/AVSCreator.sol @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.12; + +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; + +import {IRegistryCoordinator} from "eigenlayer-middleware/interfaces/IRegistryCoordinator.sol"; +import {OperatorStateRetriever} from "eigenlayer-middleware/OperatorStateRetriever.sol"; +import {PauserRegistry, IPauserRegistry} from "eigenlayer-contracts/src/contracts/permissions/PauserRegistry.sol"; +import {IStakeRegistry} from "eigenlayer-middleware/interfaces/IStakeRegistry.sol"; +import {RegistryCoordinator} from "eigenlayer-middleware/RegistryCoordinator.sol"; + +contract EmptyContract {} + +contract AVSCreatorStorage { + address public immutable emptyContract; + address public immutable delegationManager; + address public immutable avsDirectory; + OperatorStateRetriever public immutable operatorStateRetriever; + + bytes public indexRegistryBytecode; + bytes public stakeRegistryBytecode; + bytes public apkRegistryBytecode; + bytes public registryCoordinatorBytecode; + bytes public serviceManagerBytecode; + + constructor(address delegationManager_, address avsDirectory_) { + delegationManager = delegationManager_; + avsDirectory = avsDirectory_; + emptyContract = address(new EmptyContract()); + operatorStateRetriever = new OperatorStateRetriever(); + } +} + +contract AVSCreator is AVSCreatorStorage { + error AlreadySet(); + + event Created( + ProxyAdmin ProxyAdmin, + PauserRegistry pauserRegistry, + address indexRegistryProxy, + address stakeRegistryProxy, + address apkRegistryProxy, + address registryCoordinatorProxy, + address serviceManagerProxy + ); + + modifier setOnce(bytes memory bytecode) { + if (bytecode.length != 0) { + revert AlreadySet(); + } + _; + } + + constructor(address delegationManager_, address avsDirectory_) + AVSCreatorStorage(delegationManager_, avsDirectory_) + {} + + function setIndexRegistryBytecode(bytes calldata bytecode) external setOnce(indexRegistryBytecode) { + indexRegistryBytecode = bytecode; + } + + function setStakeRegistryBytecode(bytes calldata bytecode) external setOnce(stakeRegistryBytecode) { + stakeRegistryBytecode = bytecode; + } + + function setApkRegistryBytecode(bytes calldata bytecode) external setOnce(apkRegistryBytecode) { + apkRegistryBytecode = bytecode; + } + + function setRegistryCoordinatorBytecode(bytes calldata bytecode) external setOnce(registryCoordinatorBytecode) { + registryCoordinatorBytecode = bytecode; + } + + function setServiceManagerBytecode(bytes calldata bytecode) external setOnce(serviceManagerBytecode) { + serviceManagerBytecode = bytecode; + } + + function createAVS( + address initialOwner_, + address churnApprover_, + address ejector_, + IRegistryCoordinator.OperatorSetParam[] memory operatorSetParams_, + uint96[] memory minimumStakes_, + IStakeRegistry.StrategyParams[][] memory strategyParams_ + ) external { + bytes memory registryCoordinatorInit; + PauserRegistry pauserRegistry; + { + { + address[] memory pausers = new address[](1); + pausers[0] = initialOwner_; + pauserRegistry = new PauserRegistry(pausers, initialOwner_); + } + + registryCoordinatorInit = abi.encodeWithSelector( + RegistryCoordinator.initialize.selector, + initialOwner_, + churnApprover_, + ejector_, + pauserRegistry, + 0, /*initialPausedStatus*/ + operatorSetParams_, + minimumStakes_, + strategyParams_ + ); + } + + ProxyAdmin proxyAdmin = new ProxyAdmin(); + + // Deploy proxies for each contract + address indexRegistryProxy = address(new TransparentUpgradeableProxy(emptyContract, address(proxyAdmin), "")); + address stakeRegistryProxy = address(new TransparentUpgradeableProxy(emptyContract, address(proxyAdmin), "")); + address apkRegistryProxy = address(new TransparentUpgradeableProxy(emptyContract, address(proxyAdmin), "")); + address registryCoordinatorProxy = + address(new TransparentUpgradeableProxy(emptyContract, address(proxyAdmin), "")); + address serviceManagerProxy = address(new TransparentUpgradeableProxy(emptyContract, address(proxyAdmin), "")); + + emit Created( + proxyAdmin, + pauserRegistry, + indexRegistryProxy, + stakeRegistryProxy, + apkRegistryProxy, + registryCoordinatorProxy, + serviceManagerProxy + ); + + // Deploy the actual implementation contracts + + { + address indexRegistryImpl = + _createImplementation(abi.encodePacked(indexRegistryBytecode, abi.encode(registryCoordinatorProxy))); + + address stakeRegistryImpl = _createImplementation( + abi.encodePacked(stakeRegistryBytecode, abi.encode(registryCoordinatorProxy, delegationManager)) + ); + + address apkRegistryImpl = + _createImplementation(abi.encodePacked(apkRegistryBytecode, abi.encode(registryCoordinatorProxy))); + + address registryCoordinatorImpl = _createImplementation( + abi.encodePacked( + registryCoordinatorBytecode, + abi.encode(serviceManagerProxy, stakeRegistryProxy, apkRegistryProxy, indexRegistryProxy) + ) + ); + + proxyAdmin.upgrade(TransparentUpgradeableProxy(payable(indexRegistryProxy)), indexRegistryImpl); + proxyAdmin.upgrade(TransparentUpgradeableProxy(payable(stakeRegistryProxy)), stakeRegistryImpl); + proxyAdmin.upgrade(TransparentUpgradeableProxy(payable(apkRegistryProxy)), apkRegistryImpl); + proxyAdmin.upgradeAndCall( + TransparentUpgradeableProxy(payable(address(registryCoordinatorProxy))), + address(registryCoordinatorImpl), + registryCoordinatorInit + ); + } + + proxyAdmin.transferOwnership(initialOwner_); + } + + function createServiceManagerImplementation(address registryCoordinatorProxy_, address stakeRegistryProxy_) + external + returns (address) + { + address impl = _createImplementation( + abi.encodePacked( + serviceManagerBytecode, abi.encode(avsDirectory, registryCoordinatorProxy_, stakeRegistryProxy_) + ) + ); + return impl; + } + + function _createImplementation(bytes memory bytecodeWithConstructor) internal returns (address) { + address addr; + assembly { + addr := create(0, add(bytecodeWithConstructor, 0x20), mload(bytecodeWithConstructor)) + if iszero(extcodesize(addr)) { revert(0, 0) } + } + return addr; + } +} diff --git a/contracts/test/AVSCreator.t.sol b/contracts/test/AVSCreator.t.sol new file mode 100644 index 0000000..1b1f88f --- /dev/null +++ b/contracts/test/AVSCreator.t.sol @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.12; + +import "forge-std/Test.sol"; +import "../src/core/AVSCreator.sol"; +import {IndexRegistry} from "eigenlayer-middleware/IndexRegistry.sol"; +import {BLSApkRegistry} from "eigenlayer-middleware/BLSApkRegistry.sol"; +import {RegistryCoordinator} from "eigenlayer-middleware/RegistryCoordinator.sol"; +import {MachServiceManager} from "../src/core/MachServiceManager.sol"; +import {StakeRegistry, IStrategy} from "eigenlayer-middleware/StakeRegistry.sol"; + +contract AVSCreatorTest is Test { + AVSCreator public avsCreator; + address delegationManager = address(0x1); + address avsDirectory = address(0x2); + + event Created( + ProxyAdmin ProxyAdmin, + PauserRegistry pauserRegistry, + address indexRegistryProxy, + address stakeRegistryProxy, + address apkRegistryProxy, + address registryCoordinatorProxy, + address serviceManagerProxy + ); + + function setUp() public { + avsCreator = new AVSCreator(delegationManager, avsDirectory); + } + + // Test initial contract setup + function testInitialSetup() public { + assertEq(avsCreator.delegationManager(), delegationManager); + assertEq(avsCreator.avsDirectory(), avsDirectory); + } + + // Test setting bytecode functions + function testSetIndexRegistryBytecode() public { + bytes memory bytecode = "dummyBytecode"; + avsCreator.setIndexRegistryBytecode(bytecode); + assertEq(avsCreator.indexRegistryBytecode(), bytecode); + + // Test to ensure it cannot be set again + vm.expectRevert(AVSCreator.AlreadySet.selector); + avsCreator.setIndexRegistryBytecode(bytecode); + } + + function testSetStakeRegistryBytecode() public { + bytes memory bytecode = "dummyBytecode"; + avsCreator.setStakeRegistryBytecode(bytecode); + assertEq(avsCreator.stakeRegistryBytecode(), bytecode); + + vm.expectRevert(AVSCreator.AlreadySet.selector); + avsCreator.setStakeRegistryBytecode(bytecode); + } + + function testSetApkRegistryBytecode() public { + bytes memory bytecode = "dummyBytecode"; + avsCreator.setApkRegistryBytecode(bytecode); + assertEq(avsCreator.apkRegistryBytecode(), bytecode); + + vm.expectRevert(AVSCreator.AlreadySet.selector); + avsCreator.setApkRegistryBytecode(bytecode); + } + + function testSetRegistryCoordinatorBytecode() public { + bytes memory bytecode = "dummyBytecode"; + avsCreator.setRegistryCoordinatorBytecode(bytecode); + assertEq(avsCreator.registryCoordinatorBytecode(), bytecode); + + vm.expectRevert(AVSCreator.AlreadySet.selector); + avsCreator.setRegistryCoordinatorBytecode(bytecode); + } + + function testSetServiceManagerBytecode() public { + bytes memory bytecode = "dummyBytecode"; + avsCreator.setServiceManagerBytecode(bytecode); + assertEq(avsCreator.serviceManagerBytecode(), bytecode); + + vm.expectRevert(AVSCreator.AlreadySet.selector); + avsCreator.setServiceManagerBytecode(bytecode); + } + + // Test the creation of the AVS + function testCreateAVS() public { + // Ensure bytecode is set before creating AVS + avsCreator.setIndexRegistryBytecode(type(IndexRegistry).creationCode); + avsCreator.setStakeRegistryBytecode(type(StakeRegistry).creationCode); + avsCreator.setApkRegistryBytecode(type(BLSApkRegistry).creationCode); + avsCreator.setRegistryCoordinatorBytecode(type(RegistryCoordinator).creationCode); + avsCreator.setServiceManagerBytecode(type(MachServiceManager).creationCode); + + IRegistryCoordinator.OperatorSetParam[] memory operatorSetParams = + new IRegistryCoordinator.OperatorSetParam[](1); + operatorSetParams[0] = IRegistryCoordinator.OperatorSetParam({ + maxOperatorCount: 50, + kickBIPsOfOperatorStake: 11000, + kickBIPsOfTotalStake: 1001 + }); + + uint96[] memory minimumStakes = new uint96[](1); + minimumStakes[0] = 0; + + IStakeRegistry.StrategyParams[][] memory strategyParams = new IStakeRegistry.StrategyParams[][](1); + strategyParams[0] = new IStakeRegistry.StrategyParams[](3); + strategyParams[0][0] = IStakeRegistry.StrategyParams({ + strategy: IStrategy(address(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0)), + multiplier: 1000000000000000000 + }); + strategyParams[0][1] = IStakeRegistry.StrategyParams({ + strategy: IStrategy(address(0x7D704507b76571a51d9caE8AdDAbBFd0ba0e63d3)), + multiplier: 997992210000000000 + }); + strategyParams[0][2] = IStakeRegistry.StrategyParams({ + strategy: IStrategy(address(0x3A8fBdf9e77DFc25d09741f51d3E181b25d0c4E0)), + multiplier: 1104234999999999999 + }); + // Expect the Created event + vm.expectEmit(true, true, true, false); + emit Created( + ProxyAdmin(address(0)), + PauserRegistry(address(0)), // Placeholder, will be replaced after the function call + address(0), // Placeholder, will be replaced after the function call + address(0), // Placeholder, will be replaced after the function call + address(0), // Placeholder, will be replaced after the function call + address(0), // Placeholder, will be replaced after the function call + address(0) // Placeholder, will be replaced after the function call + ); + avsCreator.createAVS( + address(this), address(this), address(this), operatorSetParams, minimumStakes, strategyParams + ); + + // You can add assertions here to verify the state after createAVS is called + } +}