diff --git a/script/DeployEigenlayerSlashing.s.sol b/script/DeployEigenlayerSlashing.s.sol index 3af10d08..4bcb352f 100644 --- a/script/DeployEigenlayerSlashing.s.sol +++ b/script/DeployEigenlayerSlashing.s.sol @@ -26,6 +26,7 @@ contract DeployEigenlayerSlashingScript is Script { AddressProvider public addressProvider; address rewardsCoordinator = 0x7750d328b314EfFa365A0402CcfD489B80B0adda; + address avsOperatorManager = 0x2093Bbb221f1d8C7c932c32ee28Be6dEe4a37A6a; function run() external { @@ -33,7 +34,7 @@ contract DeployEigenlayerSlashingScript is Script { etherFiNodeImplementation = new EtherFiNode(); etherFiNodesManagerImplementation = new EtherFiNodesManager(); - etherFiRestakerImplementation = new EtherFiRestaker(rewardsCoordinator); + etherFiRestakerImplementation = new EtherFiRestaker(rewardsCoordinator, avsOperatorManager); console2.log("etherFiNode Impl:", address(etherFiNodeImplementation)); console2.log("etherFiNodesManager Impl:", address(etherFiNodesManagerImplementation)); diff --git a/script/deploys/DeployEtherFiRestaker.s.sol b/script/deploys/DeployEtherFiRestaker.s.sol index eda1ba4f..33582528 100644 --- a/script/deploys/DeployEtherFiRestaker.s.sol +++ b/script/deploys/DeployEtherFiRestaker.s.sol @@ -18,6 +18,7 @@ contract Deploy is Script { AddressProvider public addressProvider; address eigenlayerRewardsCoordinator; + address avsOperatorManager = 0x2093Bbb221f1d8C7c932c32ee28Be6dEe4a37A6a; address admin; @@ -29,7 +30,7 @@ contract Deploy is Script { vm.startBroadcast(deployerPrivateKey); - EtherFiRestaker restaker = EtherFiRestaker(payable(new UUPSProxy(payable(new EtherFiRestaker(eigenlayerRewardsCoordinator)), ""))); + EtherFiRestaker restaker = EtherFiRestaker(payable(new UUPSProxy(payable(new EtherFiRestaker(eigenlayerRewardsCoordinator, avsOperatorManager)), ""))); restaker.initialize( addressProvider.getContractAddress("LiquidityPool"), addressProvider.getContractAddress("Liquifier") diff --git a/src/EtherFiRestaker.sol b/src/EtherFiRestaker.sol index 71cb06eb..96fa7597 100644 --- a/src/EtherFiRestaker.sol +++ b/src/EtherFiRestaker.sol @@ -12,6 +12,7 @@ import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import "./Liquifier.sol"; import "./LiquidityPool.sol"; +import "./interfaces/IAvsOperatorManager.sol"; import "./eigenlayer-interfaces/IStrategyManager.sol"; import "./eigenlayer-interfaces/IDelegationManager.sol"; @@ -44,6 +45,8 @@ contract EtherFiRestaker is Initializable, UUPSUpgradeable, OwnableUpgradeable, EnumerableSet.Bytes32Set private withdrawalRootsSet; mapping(bytes32 => IDelegationManager.Withdrawal) public DEPRECATED_withdrawalRootToWithdrawal; + IAvsOperatorManager public immutable avsOperatorManager; + event QueuedStEthWithdrawals(uint256[] _reqIds); event CompletedStEthQueuedWithdrawals(uint256[] _reqIds); @@ -57,10 +60,12 @@ contract EtherFiRestaker is Initializable, UUPSUpgradeable, OwnableUpgradeable, error NotRegistered(); error WrongOutput(); error IncorrectCaller(); + error InvalidOperatorContract(); /// @custom:oz-upgrades-unsafe-allow constructor - constructor(address _rewardsCoordinator) { + constructor(address _rewardsCoordinator, address _avsOperatorManager) { rewardsCoordinator = IRewardsCoordinator(_rewardsCoordinator); + avsOperatorManager = IAvsOperatorManager(_avsOperatorManager); _disableInitializers(); } @@ -173,6 +178,15 @@ contract EtherFiRestaker is Initializable, UUPSUpgradeable, OwnableUpgradeable, return shares; } + // transfer a token to one of our dedicated AvsOperator contracts + function transferTokenToOperator(uint256 operatorId, address token, uint256 amount) external onlyAdmin { + + address operator = avsOperatorManager.avsOperators(operatorId); + if (operator == address(0)) revert InvalidOperatorContract(); + + IERC20(token).transfer(operator, amount); + } + /// queue withdrawals for un-restaking the token /// Made easy for operators /// @param token the token to withdraw diff --git a/src/interfaces/IAvsOperatorManager.sol b/src/interfaces/IAvsOperatorManager.sol new file mode 100644 index 00000000..bd2b191e --- /dev/null +++ b/src/interfaces/IAvsOperatorManager.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "../eigenlayer-interfaces/IDelegationManager.sol"; +import "../eigenlayer-interfaces/IAVSDirectory.sol"; + +/** + * @title IAvsOperatorManager + * @author ether.fi + */ +interface IAvsOperatorManager { + + function avsOperators(uint256 _id) external returns (address); + function registerAsOperator(uint256 _id, address _delegationApprover, uint32 _allocationDelay, string calldata _metaDataURI) external; + function modifyOperatorDetails(uint256 _id, address _delegationApprover) external; + function updateOperatorMetadataURI(uint256 _id, string calldata _metadataURI) external; + function forwardOperatorCall(uint256 _id, address _target, bytes4 _selector, bytes calldata _args) external; + function forwardOperatorCall(uint256 _id, address _target, bytes calldata _input) external; + function adminForwardCall(uint256 _id, address _target, bytes4 _selector, bytes calldata _args) external; + function isValidOperatorCall(uint256 _id, address _target, bytes4 _selector, bytes calldata) external returns (bool); + function isValidAdminCall(address _target, bytes4 _selector, bytes calldata) external view returns (bool); + function updateAllowedOperatorCalls(uint256 _operatorId, address _target, bytes4 _selector, bool _allowed) external; + function updateAllowedAdminCalls(address _target, bytes4 _selector, bool _allowed) external; + function updateAvsNodeRunner(uint256 _id, address _avsNodeRunner) external; + function updateEcdsaSigner(uint256 _id, address _ecdsaSigner) external; + function upgradeEtherFiAvsOperator(address _newImplementation) external; + function instantiateEtherFiAvsOperator(uint256 _nums) external returns (uint256[] memory _ids); + function avsNodeRunner(uint256 _id) external view returns (address); + function ecdsaSigner(uint256 _id) external view returns (address); + function calculateOperatorAVSRegistrationDigestHash(uint256 _id, address _avsServiceManager, bytes32 _salt, uint256 _expiry) external view returns (bytes32); + + //--------------------------------------------------------------------------- + //----------------------------- Events ----------------------------------- + //--------------------------------------------------------------------------- + + event ForwardedOperatorCall(uint256 indexed id, address indexed target, bytes4 indexed selector, bytes data, address sender); + event CreatedEtherFiAvsOperator(uint256 indexed id, address etherFiAvsOperator); + event ModifiedOperatorDetails(uint256 indexed id, IDelegationManager.OperatorDetails newOperatorDetails); + event UpdatedOperatorMetadataURI(uint256 indexed id, string metadataURI); + event UpdatedAvsNodeRunner(uint256 indexed id, address avsNodeRunner); + event UpdatedEcdsaSigner(uint256 indexed id, address ecdsaSigner); + event AllowedOperatorCallsUpdated(uint256 indexed id, address indexed target, bytes4 indexed selector, bool allowed); + event AllowedAdminCallsUpdated(address indexed target, bytes4 indexed selector, bool allowed); + event AdminUpdated(address indexed admin, bool isAdmin); + + //-------------------------------------------------------------------------- + //----------------------------- Errors ----------------------------------- + //-------------------------------------------------------------------------- + + error IncorrectRole(); + error InvalidOperatorCall(); + error InvalidAdminCall(); + +} diff --git a/test/ContractCodeChecker.t.sol b/test/ContractCodeChecker.t.sol index db57effc..39e526e0 100644 --- a/test/ContractCodeChecker.t.sol +++ b/test/ContractCodeChecker.t.sol @@ -24,7 +24,7 @@ contract ContractCodeCheckerTest is TestSetup { EtherFiNode etherFiNodeImplementation = new EtherFiNode(); address etherFiNodeImplAddress = address(0xc5F2764383f93259Fba1D820b894B1DE0d47937e); - EtherFiRestaker etherFiRestakerImplementation = new EtherFiRestaker(address(0x7750d328b314EfFa365A0402CcfD489B80B0adda)); + EtherFiRestaker etherFiRestakerImplementation = new EtherFiRestaker(address(0x7750d328b314EfFa365A0402CcfD489B80B0adda), address(0x2093Bbb221f1d8C7c932c32ee28Be6dEe4a37A6a)); address etherFiRestakerImplAddress = address(0x0052F731a6BEA541843385ffBA408F52B74Cb624); // Verify bytecode matches between deployed contracts and their implementations diff --git a/test/EigenLayerIntegration.t.sol b/test/EigenLayerIntegration.t.sol index a1c95808..e6a119fb 100644 --- a/test/EigenLayerIntegration.t.sol +++ b/test/EigenLayerIntegration.t.sol @@ -504,7 +504,7 @@ contract EigenLayerIntegraitonTest is TestSetup, ProofParsing { EtherFiNode etherFiNodeImplementation = new EtherFiNode(); address etherFiNodeImplAddress = address(0xc5F2764383f93259Fba1D820b894B1DE0d47937e); - EtherFiRestaker etherFiRestakerImplementation = new EtherFiRestaker(address(0x7750d328b314EfFa365A0402CcfD489B80B0adda)); + EtherFiRestaker etherFiRestakerImplementation = new EtherFiRestaker(address(0x7750d328b314EfFa365A0402CcfD489B80B0adda), address(0x2093Bbb221f1d8C7c932c32ee28Be6dEe4a37A6a)); address etherFiRestakerImplAddress = address(0x0052F731a6BEA541843385ffBA408F52B74Cb624); verifyContractByteCodeMatch(etherFiNodesManagerImplAddress, address(etherFiNodesManagerImplementation)); diff --git a/test/EtherFiRestaker.t.sol b/test/EtherFiRestaker.t.sol index 718b7e4c..b34c23eb 100644 --- a/test/EtherFiRestaker.t.sol +++ b/test/EtherFiRestaker.t.sol @@ -232,7 +232,7 @@ contract EtherFiRestakerTest is TestSetup { EtherFiRestaker restaker = EtherFiRestaker(payable(0x1B7a4C3797236A1C37f8741c0Be35c2c72736fFf)); address _claimer = vm.addr(433); - address newRestakerImpl = address(new EtherFiRestaker(address(eigenLayerRewardsCoordinator))); + address newRestakerImpl = address(new EtherFiRestaker(address(eigenLayerRewardsCoordinator), address(avsOperatorManager))); vm.startPrank(restaker.owner()); restaker.upgradeTo(newRestakerImpl); @@ -241,4 +241,21 @@ contract EtherFiRestakerTest is TestSetup { assertEq(eigenLayerRewardsCoordinator.claimerFor(address(restaker)), _claimer); } + function test_transferTokenToOperator() public { + EtherFiRestaker restaker = EtherFiRestaker(payable(0x1B7a4C3797236A1C37f8741c0Be35c2c72736fFf)); + address wstETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; + address avsOperatorManager = 0x2093Bbb221f1d8C7c932c32ee28Be6dEe4a37A6a; + + // upgrade to new impl + address newRestakerImpl = address(new EtherFiRestaker(address(eigenLayerRewardsCoordinator), address(avsOperatorManager))); + vm.startPrank(restaker.owner()); + restaker.upgradeTo(newRestakerImpl); + + uint256 startBalance = stEth.balanceOf(address(restaker)); + restaker.transferTokenToOperator(13, wstETH, 10 ether); + uint256 endBalance = stEth.balanceOf(address(restaker)); + + console2.log("balances:", startBalance, endBalance); + } + } diff --git a/test/TestSetup.sol b/test/TestSetup.sol index 8c76304a..8d357209 100644 --- a/test/TestSetup.sol +++ b/test/TestSetup.sol @@ -206,6 +206,8 @@ contract TestSetup is Test, ContractCodeChecker, DepositDataGeneration { EtherFiTimelock public etherFiTimelockInstance; BucketRateLimiter public bucketRateLimiter; + IAvsOperatorManager public avsOperatorManager; + bool public shouldSetupRoleRegistry = true; bytes32 root; @@ -448,7 +450,7 @@ contract TestSetup is Test, ContractCodeChecker, DepositDataGeneration { } function deployEtherFiRestaker() internal { - etherFiRestakerImplementation = new EtherFiRestaker(address(0x1B7a4C3797236A1C37f8741c0Be35c2c72736fFf)); + etherFiRestakerImplementation = new EtherFiRestaker(address(0x1B7a4C3797236A1C37f8741c0Be35c2c72736fFf), address(0x2093Bbb221f1d8C7c932c32ee28Be6dEe4a37A6a)); etherFiRestakerProxy = new UUPSProxy(address(etherFiRestakerImplementation), ""); etherFiRestakerInstance = EtherFiRestaker(payable(etherFiRestakerProxy)); @@ -628,7 +630,7 @@ contract TestSetup is Test, ContractCodeChecker, DepositDataGeneration { etherFiOracleProxy = new UUPSProxy(address(etherFiOracleImplementation), ""); etherFiOracleInstance = EtherFiOracle(payable(etherFiOracleProxy)); - etherFiRestakerImplementation = new EtherFiRestaker(address(0x0)); + etherFiRestakerImplementation = new EtherFiRestaker(address(0x0), address(0x0)); etherFiRestakerProxy = new UUPSProxy(address(etherFiRestakerImplementation), ""); etherFiRestakerInstance = EtherFiRestaker(payable(etherFiRestakerProxy)); @@ -764,6 +766,8 @@ contract TestSetup is Test, ContractCodeChecker, DepositDataGeneration { auctionInstance.initializeOnUpgrade(address(membershipManagerInstance), 1 ether, address(etherFiAdminInstance), address(nodeOperatorManagerInstance)); membershipNftInstance.initializeOnUpgrade(address(liquidityPoolInstance)); + avsOperatorManager = IAvsOperatorManager(0x2093Bbb221f1d8C7c932c32ee28Be6dEe4a37A6a); + // configure eigenlayer dependency differently for mainnet vs testnet because we rely // on the contracts already deployed by eigenlayer on those chains