Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: mev-commit AVS for validator opt-in #175

Closed
wants to merge 52 commits into from
Closed
Changes from 1 commit
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
71236f8
refactor: val reg now uses explicit "exist" fields
shaspitz Jun 25, 2024
c5deb7e
fix: bindings + abi
shaspitz Jun 25, 2024
8536b4a
chore: add eigenlayer contracts as dep
shaspitz Jun 11, 2024
7e877ab
feat: commit wip code
shaspitz Jun 11, 2024
d8bb2e6
feat: more wip code
shaspitz Jun 11, 2024
e14737f
feat: wip updates
shaspitz Jun 12, 2024
1bdd96b
fix: require msg nit
shaspitz Jun 12, 2024
b908a2a
feat: more wip code + README
shaspitz Jun 12, 2024
3756963
feat: add interface, docs
shaspitz Jun 13, 2024
8e23e2c
feat: wip
shaspitz Jun 13, 2024
856c963
feat: wip
shaspitz Jun 13, 2024
3a08641
feat: commit eth_tmp
shaspitz Jun 13, 2024
4647f70
doc: populate metadata
shaspitz Jun 13, 2024
6e538ea
docs: todos
shaspitz Jun 13, 2024
81799e9
chores: todos
shaspitz Jun 13, 2024
b959ab2
chore: use correct primev logo
shaspitz Jun 16, 2024
c59c0bd
fix: metadata link
shaspitz Jun 16, 2024
dea3e5c
chore: logo as png
shaspitz Jun 17, 2024
a89de09
feat: still wip but added lst support
shaspitz Jun 18, 2024
a32f2ab
docs: cleanup + readme
shaspitz Jun 18, 2024
677008d
docs: nit comment
shaspitz Jun 18, 2024
159349c
docs: clean up readme
shaspitz Jun 18, 2024
c9103be
docs: nits
shaspitz Jun 18, 2024
990a10f
docs: update readme
shaspitz Jun 18, 2024
0e00276
docs: update
shaspitz Jun 18, 2024
12fd84b
docs: readme
shaspitz Jun 18, 2024
2ddfa74
docs: update
shaspitz Jun 18, 2024
d69007c
docs: update
shaspitz Jun 18, 2024
c856a2a
docs: update
shaspitz Jun 18, 2024
201a057
docs: update docs
shaspitz Jun 18, 2024
cb8e695
docs: update
shaspitz Jun 18, 2024
548ee50
docs: update
shaspitz Jun 18, 2024
1f773c0
docs: update
shaspitz Jun 18, 2024
6134431
feat: freeze in batches
shaspitz Jun 18, 2024
0e5f091
fix: interface matches freeze func
shaspitz Jun 18, 2024
1a4e76e
refactor: move to val reg dir
shaspitz Jun 21, 2024
47e56b7
refactor: function order etc
shaspitz Jun 25, 2024
325d47a
refactor: ordering nits
shaspitz Jun 25, 2024
66fddcc
feat: remove cached restakers list + todos
shaspitz Jun 25, 2024
603dada
feat: simplify state storage using util + modifiers + nameChanges
shaspitz Jun 26, 2024
fe70c5d
feat: allow lst restakers to choose multiple validators
shaspitz Jun 26, 2024
780bbb7
feat: pay unfreeze fee to receiver account
shaspitz Jun 26, 2024
682a06e
feat: organize funcs and handle edge cases
shaspitz Jun 26, 2024
29e9a18
chore: add funcs to interface
shaspitz Jun 26, 2024
4c70ff1
docs: comments on main funcs
shaspitz Jun 26, 2024
4580b5d
docs: comments for interface funcs
shaspitz Jun 26, 2024
b14fdd7
fix: make IValidatorRegistryV1 an interface
shaspitz Jun 26, 2024
63c7b51
feat: introduce validator opt in router + docs
shaspitz Jun 26, 2024
dca122b
fix: use main branch for metadata json
shaspitz Jun 26, 2024
3fdb106
test: init unit testing setup
shaspitz Jun 26, 2024
0a457db
test: operator registration
shaspitz Jun 26, 2024
56c9eef
test: validator registration + mocks
shaspitz Jun 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: more wip code
shaspitz committed Jun 26, 2024
commit d8bb2e6010be990a0d7062ba615ca966bfc3f89b
130 changes: 93 additions & 37 deletions contracts/contracts/avs/MevCommitAVS.sol
Original file line number Diff line number Diff line change
@@ -19,28 +19,42 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/U
import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol";
import {IEigenPodManager} from "eigenlayer-contracts/src/contracts/interfaces/IEigenPodManager.sol";
import {IEigenPod} from "eigenlayer-contracts/src/contracts/interfaces/IEigenPod.sol";
import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol";
import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol";


// Write about how v2 (or future version with more decentralization) will
// give operators the task of doing the pubkey relaying to the mev-commit chain.
// That is the off-chain process is replaced by operators, who all look for the
// valset lists posted to some DA layer (eigenDA?), and then race/attest to post
// this to the mev-commit chain. The operator accounts could be auto funded on our chain.
// Slashing operators in this scheme would require social intervention as it could
// be pretty clear off chain of malicous actions and/or malicious off-chain validation
// of eigenpod conditions, delegation conditions, etc.

// TODO: Allow owner account to opt out any hash in case keys are lost
contract MevCommitAVS is MevCommitAVSStorage, OwnableUpgradeable, UUPSUpgradeable {

IDelegationManager internal delegationManager;
IEigenPodManager internal eigenPodManager;
IAVSDirectory internal eigenAVSDirectory;

event OperatorAdded(address indexed operator);
event OperatorRemoved(address indexed operator);

function _authorizeUpgrade(address newImplementation) internal override onlyOwner { }

function initialize(
address _owner,
IDelegationManager _delegationManager,
IEigenPodManager _eigenPodManager
IEigenPodManager _eigenPodManager,
IAVSDirectory _avsDirectory
) external initializer {
__Ownable_init(_owner);
__UUPSUpgradeable_init();
delegationManager = _delegationManager;
eigenPodManager = _eigenPodManager;
_avsDirectory = _avsDirectory;
__Ownable_init(_owner);
__UUPSUpgradeable_init();
}

/// @dev See https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#initializing_the_implementation_contract
@@ -49,67 +63,109 @@ contract MevCommitAVS is MevCommitAVSStorage, OwnableUpgradeable, UUPSUpgradeabl
_disableInitializers();
}

function registerOperator(
bytes calldata pubkey,
ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
) external {
address operator = msg.sender;
require(delegationManager.isOperator(operator), "sender must be an operator");
require(!_isOperatorRegistered(operator), "operator must not already be registered with MevCommitAVS");
eigenAVSDirectory.registerOperatorToAVS(operator, operatorSignature);
emit OperatorAdded(operator);
}

function deregisterOperator(address operator) external {
require(msg.sender == operator || msg.sender == owner(), "sender must be operator or owner");
require(_isOperatorRegistered(operator), "operator must already be registered with MevCommitAVS");
eigenAVSDirectory.deregisterOperatorFromAVS(operator);
emit OperatorRemoved(operator);
}


// TODO: Incorporate the below enum into this contract.
// Note opt-in status will truly reside on mev-commit chain. However,
// if one of the calling EOAs tries to opt-in validators that are not a part
// of their eigenpod OR are not delegated to a registered operator, the off-chain
// process post a tx that REJECTS the val set.
// (will also need to consider duplicate opt-in. Likely freeze in that scenario too)
// (also got to decide if we freeze whitelisted account in duplicate scenario, if actual
// (will likely have to enforce that a whitelisted address has to opt in entire
// eigenpods, not certain vals. This way we could tell eigenpods with dup req,
// "sorry, a whitelisted account has already opted in the vals from your pod)
// eigenpod opted-in right before them.. Likely not. But this can be impl detail of oracle)
// For both REJECTED AND FROZEN valsets, the user has to pay a fee to the oracle
// to opt-in once again. Note can prob consolidate frozen and rejected.

// TODO: also note that it once again may make sense to freeze by group of validators!
// Make sure murat knows of this.
// Note freezing MUST exist on L1, so that users could pay back the oracle on that same chain.
// In this case. Mev-commit chain is just a place for cheap blockspace that we can post the full
// validator list, akin to DA.
// TODO: Does this enum go in storage file?
enum ValSetStatus{
EMPTY,
ATTESTED,
WITHDRAWING,
REJECTED,
FROZEN
}

// TODO: Oracle reject function

// TODO: Oracle Freeze function

function optInValSet(bytes32 valSetHash) external {
require(valSetHash != bytes32(0), "valSetHash must be non-zero");
require(eigenPodManager.hasPod(msg.sender), "Caller must have eigenpod");
require(addressToValSetHash[msg.sender] == bytes32(0), "Caller must not already have opted in");
addressToValSetHash[msg.sender] = valSetHash;
}


// TODO: Now we freeze by group of validators! Not vals themselves. This simplifies implementation.
// Note for v1 no freeze duration will need to exist. Any account can unfreeze itself immediately for fee.

// TODO: No requirement will exist that hash has to include all vals from pod. Adds to much complexity
// Instead let a user know on frontend (before sending tx) that they cannot opt-in a key that's already opted in.

// TODO: Whitelist is now just operators! Every large org seems to have its own operator.
// Note this can be what "operators do" for now. ie. they have the ability to opt-in their users.
// But we still allow home stakers to opt-in themselves too.
// Make it very clear that part 2 of opt-in is neccessary to explicitly communicate to
// the opter-inner that they must follow the relay connection requirement. Otherwise delegators may be
// blindly frozen. When opting in as a part of step 2, the sender should be running the validators
// its opting in (st. relay requirement is met).

// TODO: Determine if we have a function to let some accounts bypass pod requirement.
function optInValSetFromWhitelist(bytes32 valSetHash) external {
require(valSetHash != bytes32(0), "valSetHash must be non-zero");
require(whitelist[msg.sender], "Caller must be whitelisted");
require(addressToValSetHash[msg.sender] == bytes32(0), "Caller must not already have opted in");
addressToValSetHash[msg.sender] = valSetHash;
// Update yes operators can do this.

function storeValidatorsByPods(bytes[][] calldata valPubKeys, address[] calldata podAddrs) external {
for (uint256 i = 0; i < podAddrs.length; i++) {
_storeValidatorsByPod(valPubKeys[i], podAddrs[i]);
}
}

function storeValidatorsByPod(bytes[] calldata valPubKeys, address podAddr) external {
_storeValidatorsByPod(valPubKeys, podAddr);
}

function _storeValidatorsByPod(bytes[] calldata valPubKeys, address podAddr) internal {
IEigenPod pod = IEigenPod(podAddr);
address delegatedTo = delegationManager.delegatedTo(podAddr); // TODO: Confirm delegatedTo takes pod address
require(msg.sender == pod.podOwner() || msg.sender == delegatedTo, "sender must be pod owner or operator that a pod is delegated to");
for (uint256 i = 0; i < valPubKeys.length; i++) {
require(pod.validatorPubkeyToInfo(valPubKeys[i]).status == IEigenPod.VALIDATOR_STATUS.ACTIVE, "validator must be active under pod");
require(validatorRecords[valPubKeys[i]].status == ValidatorStatus.NULL, "validator record must not already exist");
validatorRecords[valPubKeys[i]] = ValidatorRecord({
status: ValidatorStatus.STORED,
podAddress: podAddr
});
}
}


// TODO: Implement "request withdraw" type deal with block enforcement.
// Use existing code and tests from other PR. Also likely will need to define a FSM struct.

function optOutValSet(bytes32 valSetHash) external {
require(valSetHash != bytes32(0), "valSetHash must be non-zero");
require(addressToValSetHash[msg.sender] == valSetHash, "Caller must have opted in with same hash");
addressToValSetHash[msg.sender] = bytes32(0);


// TODO: Pull whitelisting CRUD from other PR

function isValidatorOptedIn(bytes calldata valPubKey) external view returns (ValidatorStatus) {
// TODO: Check two things: validator is stored in this contract AND
// Validator is active with eigenlayer (still associated to same pod).
// Also check that validator still has delegation to registered operator.
// TODO: Anything else to check? Make sure no edge cases in eigenlayer state changes that happen async.
}

function optOutValSetFromWhitelist(bytes32 valSetHash) external {
require(valSetHash != bytes32(0), "valSetHash must be non-zero");
require(whitelist[msg.sender], "Caller must be whitelisted");
require(addressToValSetHash[msg.sender] == valSetHash, "Caller must have opted in with same hash");
addressToValSetHash[msg.sender] = bytes32(0);
function _isOperatorRegistered(address operator) internal view returns (bool) {
return operators[operator];
}

// TODO: Pull whitelisting CRUD from other PR
function avsDirectory() external view returns (address) {
return address(eigenAVSDirectory);
}

fallback() external payable {
revert("Invalid call");
19 changes: 14 additions & 5 deletions contracts/contracts/avs/MevCommitAVSStorage.sol
Original file line number Diff line number Diff line change
@@ -2,11 +2,20 @@
pragma solidity ^0.8.20;

abstract contract MevCommitAVSStorage {
// Mapping from address who opted in a set of validators to the Keccak-256 hash over that set.
mapping(address => bytes32) public addressToValSetHash;
mapping(address => bool) public operators;

mapping(address => bool) public whitelist;
// TODO: Put in interface
struct ValidatorRecord {
ValidatorStatus status;
address podAddress;
}

// TODO: Determine if stored on L1 or our chain.
mapping(bytes => bool) public frozenSet;
// TODO: Put in interface
enum ValidatorStatus{
NULL,
STORED,
FROZEN,
WITHDRAWING
}
mapping(bytes => ValidatorRecord) public validatorRecords;
}