diff --git a/contracts/contracts/validator-registry/avs/MevCommitAVS.sol b/contracts/contracts/validator-registry/avs/MevCommitAVS.sol index f5dc176d0..f2ed8716b 100644 --- a/contracts/contracts/validator-registry/avs/MevCommitAVS.sol +++ b/contracts/contracts/validator-registry/avs/MevCommitAVS.sol @@ -394,14 +394,8 @@ contract MevCommitAVS is IMevCommitAVS, MevCommitAVSStorage, } /// @dev Internal function to register an LST restaker. - /// @notice For UX purposes each chosen validator must be "opted-in" when an LST restaker first registers. - /// However a chosen validator may later become not "opted-in" as defined in _isValidatorOptedIn(), - /// which will affect rewards/points earned by the LST restaker. function _registerLSTRestaker(bytes[] calldata chosenValidators) internal { require(chosenValidators.length > 0, "LST restaker must choose at least one validator"); - for (uint256 i = 0; i < chosenValidators.length; i++) { - require(_isValidatorOptedIn(chosenValidators[i]), "chosen validator must be opted in"); - } uint256 stratLen = _strategyManager.stakerStrategyListLength(msg.sender); require(stratLen > 0, "LST restaker must have deposited into at least one strategy"); lstRestakerRegistrations[msg.sender] = LSTRestakerRegistrationInfo({ diff --git a/contracts/contracts/validator-registry/avs/README.md b/contracts/contracts/validator-registry/avs/README.md index 761b87ec3..2d3c411ce 100644 --- a/contracts/contracts/validator-registry/avs/README.md +++ b/contracts/contracts/validator-registry/avs/README.md @@ -40,25 +40,39 @@ Deregistration requires calling `requestValidatorsDeregistration`, waiting a con LST restakers are also able to register with our avs by: -1. Delegating to an Operator who's registered with the mev-commit AVS. -2. Calling `registerLSTRestaker` with one or more chosen validator pubkey(s). These chosen validators must be opted-in to the mev-commit protocol as described above. +1. Depositing into at least one strategy with eigenlayer core. +2. Delegating to an Operator who's registered with the mev-commit AVS. +3. Calling `registerLSTRestaker` with one or more chosen validator pubkey(s). ```solidity function registerLSTRestaker(bytes[] calldata chosenValidators) external onlyNonRegisteredLstRestaker() onlySenderWithRegisteredOperator() ``` -LST restakers who register in this way will receive points or rewards commensurate with their chosen validator(s) correctly following the protocol. Further, any one of these chosen validators being frozen will result in a corresponding freeze of points accrual for the LST restaker. When an LST restaker chooses multiple validators, attribution is split evenly between the validators. +LST restakers will receive points/rewards commensurate with their chosen validator(s) being opted-in over time. Nothing enforces that validators chosen by LST restakers must be "opted-in" as described above. That is, responsibility is left up to the LST restaker as to choosing validators that are, and will stay, opted-in. When an LST restaker chooses multiple validators, attribution is split evenly between the validators. + +Validator opt-in state can be queried with `isValidatorOptedIn()`. This query offers concrete criteria that must be true for an LST restaker to accrue points/rewards over time from a chosen validator. + +```solidity +function isValidatorOptedIn(bytes calldata valPubKey) returns (bool) { + bool isValRegistered = validatorRegistrations[valPubKey].exists; + bool isFrozen = validatorRegistrations[valPubKey].freezeHeight.exists; + bool isDeregRequested = validatorRegistrations[valPubKey].deregRequestHeight.exists; + IEigenPod pod = _eigenPodManager.getPod(validatorRegistrations[valPubKey].podOwner); + bool isValActive = pod.validatorPubkeyToInfo(valPubKey).status == IEigenPod.VALIDATOR_STATUS.ACTIVE; + address delegatedOperator = _delegationManager.delegatedTo(validatorRegistrations[valPubKey].podOwner); + bool isOperatorRegistered = operatorRegistrations[delegatedOperator].exists; + return isValRegistered && !isFrozen && !isDeregRequested && isValActive && isOperatorRegistered; +} +``` Since validators are chosen in sets, an LST restaker can only choose a new set of validators by deregistering, and registering again with the new set. This simplifies contract implementation and enforces an LST restaker is responsible for the actions of its chosen validator(s). Points/rewards for LST restakers would be computed off-chain, with heavy use of indexed events. As there is not an efficient on-chain mapping from each validator to the set of LST restakers who've chosen that validator. When a rewards/points system is introduced, it may consider the following information (and possibly more): +* Opt-in state over time of the LST restaker's chosen validator(s), as defined above. * The block height when the LST restaker registered with the AVS, requested deregistration, and/or deregistered. -* The amount and denomination of LST that the restaker has delegated to a mev-commit registered Operator. Changes in LST delegation via the eigenlayer core contracts will affect point/reward accrual. +* The amount and denomination of LST that the restaker delegated to a mev-commit registered Operator over time. Changes in LST delegation via the eigenlayer core contracts will affect point/reward accrual. * Operator deregistration events, if for example an LST restaker's delegated Operator is deregistered with the mev-commit AVS. -* Validator deregistration events, if for example an LST restaker's chosen validator is deregistered with the mev-commit AVS. -* Validator eigenPod events, if for example an LST restaker's chosen validator's eigenPod status changes from `ACTIVE` to `WITHDRAWN`. -* Freeze/Unfreeze events relevant to an LST restaker's chosen validator(s). * Correctly proposed blocks by the LST restaker's chosen validator(s). Deregistration requires the restaker calling `requestLSTRestakerDeregistration`, waiting a configurable amount of blocks, then calling `deregisterLSTRestaker`. diff --git a/contracts/test/validator-registry/avs/MevCommitAVSTest.sol b/contracts/test/validator-registry/avs/MevCommitAVSTest.sol index da4194d0c..b52fc8b2c 100644 --- a/contracts/test/validator-registry/avs/MevCommitAVSTest.sol +++ b/contracts/test/validator-registry/avs/MevCommitAVSTest.sol @@ -131,7 +131,7 @@ contract MevCommitAVSTest is Test { testRegisterOperator(); address otherAcct = address(0x777); - vm.expectRevert("sender must be operator or MevCommitAVS owner"); + vm.expectRevert("sender must be operator"); vm.prank(otherAcct); mevCommitAVS.requestOperatorDeregistration(operator); @@ -150,7 +150,7 @@ contract MevCommitAVSTest is Test { assertEq(reg.deregRequestHeight.blockHeight, 108); vm.expectRevert("operator must not have already requested deregistration"); - vm.prank(owner); + vm.prank(operator); mevCommitAVS.requestOperatorDeregistration(operator); } @@ -165,12 +165,12 @@ contract MevCommitAVSTest is Test { testRegisterOperator(); address otherAcct = address(0x777); - vm.expectRevert("sender must be operator or MevCommitAVS owner"); + vm.expectRevert("sender must be operator"); vm.prank(otherAcct); mevCommitAVS.deregisterOperator(operator); vm.expectRevert("operator must have requested deregistration"); - vm.prank(owner); + vm.prank(operator); mevCommitAVS.deregisterOperator(operator); vm.prank(operator); @@ -393,6 +393,38 @@ contract MevCommitAVSTest is Test { // TODO: test that LST restakers cannot choose validators who're not registered / valid with eigen core function testRegisterLSTRestaker() public { + + address operator = address(0x888); + address lstRestaker = address(0x34443); + address otherAcct = address(0x777); + bytes[] memory chosenVals = new bytes[](0); + vm.expectRevert("sender must be delegated to an operator that is registered with MevCommitAVS"); + vm.prank(otherAcct); + mevCommitAVS.registerLSTRestaker(chosenVals); + + testRegisterValidatorsByPodOwners(); + + ISignatureUtils.SignatureWithExpiry memory sig = ISignatureUtils.SignatureWithExpiry({ + signature: bytes("signature"), + expiry: 10 + }); + vm.prank(lstRestaker); + delegationManagerMock.delegateTo(operator, sig, bytes32("salt")); + + vm.expectRevert("LST restaker must choose at least one validator"); + vm.prank(lstRestaker); + mevCommitAVS.registerLSTRestaker(chosenVals); + + + + + // ""LST restaker must choose at least one validator"" + + // isOpted in stuff + + // "LST restaker must have deposited into at least one strategy" + + // TODO: "sender must not be registered LST restaker" } function testRequestLSTRestakerDeregistration() public {