Skip to content

Commit

Permalink
docs: clarify MevCommitAVS behavior w/ associated tests (#235)
Browse files Browse the repository at this point in the history
* docs: clarify isValidatorOptedIn behavior w/ associated test

* docs: clarify requestValidatorsDeregistration + deregisterValidators behavior w/ associated test
  • Loading branch information
shaspitz authored Jul 18, 2024
1 parent 4d60ace commit 4777e2b
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 28 deletions.
3 changes: 3 additions & 0 deletions contracts/contracts/validator-registry/avs/MevCommitAVS.sol
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,9 @@ contract MevCommitAVS is IMevCommitAVS, MevCommitAVSStorage,

/// @dev Internal function to register validators by their pod owner.
/// @notice Invalid pubkeys should not correspond to VALIDATOR_STATUS.ACTIVE due to validations in EigenPod.sol
/// @dev A successful call to this function gauruntees isValidatorOptedIn() returns true for each pubkey immediately after
/// this function returns. However, sucessive state-changes (ex: delegated operator deregisters) may result in changes
/// to validator opt-in state.
function _registerValidatorsByPodOwner(
bytes[] calldata valPubKeys,
address podOwner
Expand Down
53 changes: 25 additions & 28 deletions contracts/contracts/validator-registry/avs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,42 @@ Operators mainly serve the purpose of (optionally) being able to register valida

## Validator Opt-in

Recall that a native-restaking enabled validator opting-in to mev-commit requires two steps:
Recall that a native-restaking enabled validator opting-in to mev-commit requires two high level steps:

1. The validator must delegate their native stake to an Operator who's registered with the mev-commit AVS.
2. The validator must separately *register* with the mev-commit AVS, confirming their attestation to follow the rules of the protocol.
1. The validator must delegate (via eigenlayer core) their native stake to an Operator who's registered with the mev-commit AVS.
2. The validator must separately be *registered* with the mev-commit AVS, confirming their attestation to follow the rules of the protocol.

Multiple validator public keys can be registered at once, alongside their associated eigenpod owner `podOwner` address. Note each eigenpod owner account can represent one or many restaked validators:
Multiple validator public keys can be registered at once, alongside their associated eigenpod owner `podOwner` address. Note each eigenpod owner account may represent one or many restaked validators:

```solidity
function registerValidatorsByPodOwner(bytes[] calldata valPubKeys, address podOwner);
```

This function stores relevant state and ensures that the provided pubkeys are indeed actively restaked with `podOwner`'s eigenPod. Note two entities are able to register validator pub keys in this way:
This function verifies and updates state such that directly after the call, `isValidatorOptedIn(valPubKey)` will return true for each `valPubKey`.

Note two entities are able to register validator pub keys in this way:

1. The eigenpod owner account itself.
2. An Operator account, so long as the relevant eigenpod is delegated to that Operator.
2. The (delegated and fully registered) Operator account.

If an Operator is registering pubkeys on behalf of validators, it's expected that the Operator manages those validators itself, or represents the validators to an extent that the Operator can realistically attest to the validator following the rules of mev-commit (staking-as-a-service providers for example). This trustful relationship between validators and their delegated Operator piggybacks off already agreed upon trust assumptions with eigenlayer delegation.

Validator deregistration requires calling `requestValidatorsDeregistration`, waiting a configurable amount of blocks, then calling `deregisterValidators`. These functions are similarly callable by the eigenpod owner OR delegated operator. A delegated operator calling either `requestValidatorsDeregistration` or `deregisterValidators` does not require that operator to be registered with the MevCommitAVS (this is allowed due to aforementioned trust assumptions between validators and their delegated Operator).

### What defines a validator staying "opted-in"

A validator staying opted-in following registration is explicitly defined by the following criteria:

Note if an Operator is registering pubkeys on behalf of validators, it's expected that the Operator manages those validators itself, or represents the validators to an extent that the Operator can realistically attest to the validator following the rules of mev-commit (staking-as-a-service providers for example). This trustful relationship between validators and their delegated Operator piggybacks off already agreed upon trust assumptions with eigenlayer delegation.
1. The validator's registration entry must still exists with the MevCommitAVS (ie. validator has not been deregistered).
2. The validator must not be frozen.
3. The validator must not have requested deregistration with the MevCommitAVS.
4. The validator must be `VALIDATOR_STATUS.ACTIVE` with respect to its eigenpod.
5. The validator's delegated operator must be registered with the MevCommitAVS.
6. The validator's delegated operator must not have requested deregistration with the MevCommitAVS.

Deregistration requires calling `requestValidatorsDeregistration`, waiting a configurable amount of blocks, then calling `deregisterValidators`. These functions are similarly callable by the eigenpod owner OR delegated operator.
Directly following a successful call to `registerValidatorsByPodOwner`, all of these criteria will be true as enforced by the function. However, anyone of these criteria becoming false will result in the validator no longer being "opted-in" from the mev-commit protocol's perspective.

For example if an opted-in validator's delegated operator requests deregistration with the MevCommitAVS, the eigenpod owner representing this validator needs to [redelegate to a new operator](https://docs.eigenlayer.xyz/eigenlayer/restaking-guides/restaking-user-guide/restaker-delegation/redelegation-process) who's registered with the MevCommitAVS, to reclaim opted-in status.

## LST Restaker Registration

Expand All @@ -52,26 +69,6 @@ LST restakers will receive points/rewards commensurate with their chosen validat

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) {
IMevCommitAVS.ValidatorRegistrationInfo memory valRegistration = validatorRegistrations[valPubKey];
bool isValRegistered = valRegistration.exists;
bool isFrozen = valRegistration.freezeHeight.exists;
bool isValDeregRequested = valRegistration.deregRequestHeight.exists;
IEigenPod pod = _eigenPodManager.getPod(valRegistration.podOwner);
bool isValActive = pod.validatorPubkeyToInfo(valPubKey).status == IEigenPod.VALIDATOR_STATUS.ACTIVE;
address delegatedOperator = _delegationManager.delegatedTo(valRegistration.podOwner);
IMevCommitAVS.OperatorRegistrationInfo memory operatorRegistration = operatorRegistrations[delegatedOperator];
bool isOperatorRegistered = operatorRegistration.exists;
bool isOperatorDeregRequested = operatorRegistration.deregRequestHeight.exists;
return isValRegistered && !isFrozen && !isValDeregRequested && isValActive
&& isOperatorRegistered && !isOperatorDeregRequested;
}
```

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):
Expand Down
76 changes: 76 additions & 0 deletions contracts/test/validator-registry/avs/MevCommitAVSTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1001,4 +1001,80 @@ contract MevCommitAVSTest is Test {
assertTrue(mevCommitAVS.getValidatorRegInfo(valPubkeys[1]).exists);
assertFalse(mevCommitAVS.getValidatorRegInfo(valPubkeys[1]).freezeHeight.exists);
}

function testValidatorIsOptedIn() public {
testRegisterValidatorsByPodOwners();

bytes[] memory valPubkeys = new bytes[](2);
valPubkeys[0] = bytes("valPubkey1");
valPubkeys[1] = bytes("valPubkey2");

assertTrue(mevCommitAVS.isValidatorOptedIn(valPubkeys[0]));
assertTrue(mevCommitAVS.isValidatorOptedIn(valPubkeys[1]));

address operator = address(0x888);
vm.prank(operator);
mevCommitAVS.requestOperatorDeregistration(operator);

assertFalse(mevCommitAVS.isValidatorOptedIn(valPubkeys[0]));
assertFalse(mevCommitAVS.isValidatorOptedIn(valPubkeys[1]));

address newOperator = address(0x999);
delegationManagerMock.setIsOperator(newOperator, true);

vm.prank(newOperator);
ISignatureUtils.SignatureWithSaltAndExpiry memory newOperatorSigWithSalt = ISignatureUtils.SignatureWithSaltAndExpiry({
signature: bytes("signature"),
salt: bytes32("salt"),
expiry: block.timestamp + 1 days
});
mevCommitAVS.registerOperator(newOperatorSigWithSalt);
assertTrue(mevCommitAVS.getOperatorRegInfo(newOperator).exists);

address podOwner = address(0x420);
vm.prank(podOwner);
ISignatureUtils.SignatureWithExpiry memory newOperatorSig = ISignatureUtils.SignatureWithExpiry({
signature: bytes("signature"),
expiry: block.timestamp + 1 days
});
delegationManagerMock.delegateTo(newOperator, newOperatorSig, bytes32("salt"));

assertTrue(mevCommitAVS.isValidatorOptedIn(valPubkeys[0]));
assertTrue(mevCommitAVS.isValidatorOptedIn(valPubkeys[1]));
}

function testDeregisteredOperatorCanStillDeregisterValidators() public {
testRegisterValidatorsByPodOwners();

address operator = address(0x888);
vm.prank(operator);
mevCommitAVS.requestOperatorDeregistration(operator);
assertTrue(mevCommitAVS.getOperatorRegInfo(operator).exists);
assertTrue(mevCommitAVS.getOperatorRegInfo(operator).deregRequestHeight.exists);

bytes[] memory valPubkeys = new bytes[](2);
valPubkeys[0] = bytes("valPubkey1");
valPubkeys[1] = bytes("valPubkey2");

address podOwner = address(0x420);
vm.expectEmit(true, true, true, true);
emit ValidatorDeregistrationRequested(valPubkeys[0], podOwner);
vm.expectEmit(true, true, true, true);
emit ValidatorDeregistrationRequested(valPubkeys[1], podOwner);
vm.prank(operator);
mevCommitAVS.requestValidatorsDeregistration(valPubkeys);

vm.roll(2000);

vm.prank(operator);
mevCommitAVS.deregisterOperator(operator);
assertFalse(mevCommitAVS.getOperatorRegInfo(operator).exists);

vm.expectEmit(true, true, true, true);
emit ValidatorDeregistered(valPubkeys[0], podOwner);
vm.expectEmit(true, true, true, true);
emit ValidatorDeregistered(valPubkeys[1], podOwner);
vm.prank(operator);
mevCommitAVS.deregisterValidators(valPubkeys);
}
}

0 comments on commit 4777e2b

Please sign in to comment.