Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,9 @@
path = contracts/lib/universal-router
url = https://github.com/Uniswap/universal-router

[submodule "contracts/lib/eigenlayer-middleware"]
path = contracts/lib/eigenlayer-middleware
url = https://github.com/Layr-Labs/eigenlayer-middleware
[submodule "contracts/lib/eigenlayer-contracts"]
path = contracts/lib/eigenlayer-contracts
url = https://github.com/Layr-Labs/eigenlayer-contracts
35 changes: 27 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,13 @@

<div align="center">

<img src="https://img.shields.io/badge/Solidity-363636?style=for-the-badge&logo=solidity&logoColor=white" alt="Solidity"/>
<img src="https://img.shields.io/badge/Ethereum-3C3C3D?style=for-the-badge&logo=ethereum&logoColor=white" alt="Ethereum"/>
<img src="https://img.shields.io/badge/Uniswap-FF007A?style=for-the-badge&logo=uniswap&logoColor=white" alt="Uniswap"/>
<img src="https://img.shields.io/badge/Solidity-363636?style=for-the-badge&logo=solidity&logoColor=white" alt="Solidity-"/>
<img src="https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript"/>
<img src="https://img.shields.io/badge/React-61DAFB?style=for-the-badge&logo=react&logoColor=black" alt="React"/>
<img src="https://img.shields.io/badge/Vite-646CFF?style=for-the-badge&logo=vite&logoColor=white" alt="Vite"/>
<img src="https://img.shields.io/badge/Tailwind_CSS-38B2AC?style=for-the-badge&logo=tailwind-css&logoColor=white" alt="Tailwind CSS"/>
<img src="https://img.shields.io/badge/Foundry-000000?style=for-the-badge&logo=foundry&logoColor=white" alt="Foundry"/>
<img src="https://img.shields.io/badge/Node.js-339933?style=for-the-badge&logo=node.js&logoColor=white" alt="Node.js"/>
<img src="https://img.shields.io/badge/PostgreSQL-4169E1?style=for-the-badge&logo=postgresql&logoColor=white" alt="PostgreSQL"/>
<img src="https://img.shields.io/badge/Subsquid-000000?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2Zz48L3N2Zz4=&logoColor=white" alt="Subsquid"/>

</div>

<p align="center">
Expand Down Expand Up @@ -43,7 +38,6 @@ Minimal setup to run the frontend and indexer locally.
## Prerequisites

- Node.js v18+
- Docker (for indexer PostgreSQL)
- Foundry

## Quick Start
Expand All @@ -66,7 +60,32 @@ npm run dev

Frontend runs on: **http://localhost:3000**

### 3. Indexer
### 3. HookAttestationAVS Operator

The operator verifies hook implementations match their specifications without accessing source code.

```bash
cd operator
npm install
```

**Run tests:**
```bash
npm test
```

**Run operator in dry-run mode:**
```bash
cp .env.example .env
npm start
```

**Constraints:**
- Dry-run mode only (on-chain contracts not yet deployed)
- Uses mock state sampler (real IHookStateView pending)
- BLS signatures and multi-operator consensus pending EigenLayer integration

See `operator/INTEGRATION_ROADMAP.md` for full integration requirements.

### 4. Contracts
- `forge build` - Compile contracts
Expand Down
1 change: 1 addition & 0 deletions contracts/lib/eigenlayer-contracts
Submodule eigenlayer-contracts added at 31aade
1 change: 1 addition & 0 deletions contracts/lib/eigenlayer-middleware
Submodule eigenlayer-middleware added at a7a549
45 changes: 45 additions & 0 deletions docs/avs-integration/ECDSAStakeRegistry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# ECDSAStakeRegistry

The ECDSAStakeRegistry is a contract that manages operator registration and quorum updates for an AVS using ECDSA signatures. It serves a similar purpose to the RegistryCoordinator and StakeRegistry contracts but with some key differences:

* Signature Scheme: The ECDSAStakeRegistry uses ECDSA signatures for operator registration and verification, while the RegistryCoordinator and StakeRegistry use BLS signatures.
* Quorum Management: Unlike the RegistryCoordinator, which supports multiple quorums, the ECDSAStakeRegistry manages a single quorum. This simplifies the contract and reduces the need for quorum-specific functions.
* Stake Management: The ECDSAStakeRegistry tracks operator stakes and total stake weight using checkpoints, allowing for efficient retrieval of historical stake data. It also defines a threshold stake that must meet the cumulative stake of signed messages.

The core functionalities of the ECDSAStakeRegistry include:

**Operator Registration**:

```solidity
function registerOperatorWithSignature(
ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature,
address _signingKey
) external;
```

**Stake and Weight Management**:&#x20;

The ECDSAStakeRegistry uses checkpoints to track operator stakes and total stake weight. It provides functions to retrieve operator weights and total weight at specific block numbers:

```solidity
function _getOperatorWeight(address _signer, uint32 _referenceBlock) internal view returns (uint256);
function _getTotalWeight(uint32 _referenceBlock) internal view returns (uint256);
```

**Threshold Stake Validation**:&#x20;

The contract defines a threshold stake that must meet the cumulative stake of signed messages. It provides a function to validate the threshold stake:

```solidity
function _validateThresholdStake(uint256 _signedWeight, uint32 _referenceBlock) internal view;
```

**Signature Verification**:&#x20;

The ECDSAStakeRegistry implements the IERC1271 interface, allowing it to verify ECDSA signatures using the isValidSignature function:

```solidity
function isValidSignature(bytes32 _hash, bytes memory _signature) public view override returns (bytes4);
```

In summary, the ECDSAStakeRegistry is a simplified version of the RegistryCoordinator and StakeRegistry contracts, tailored for AVS using ECDSA signatures and managing a single quorum. It provides functions for operator registration, stake management, and signature verification, ensuring the security and integrity of the AVS.
51 changes: 51 additions & 0 deletions docs/avs-integration/Quorums.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Quorums

We care about strategies for an AVS because they are the interface we use to handle assets (and hence security) in an AVS. So operators are delegated some assets and with these funds they **register** with AVSs to secure the operations of these AVSs.

But how does this **registration with stake** actually happen at the AVS level?

Answer: **Quorums**.

A quorum is a grouping and configuration of specific kinds of stake that an AVS considers when interacting with operators.

When operators register for an AVS, they select one or more quorums within the AVS to register for.&#x20;

This looks something like

```solidity
function registerOperator(
bytes calldata quorumNumbers,
string calldata socket,
IBLSApkRegistry.PubkeyRegistrationParams calldata params,
SignatureWithSaltAndExpiry memory operatorSignature
)
```

Ignore the 2nd and 3rd parameter for now. The first parameter specifies which quorums defined by the AVS the operator wants to register with, and the fourth parameter is the signature of the operator used by the AVS to register the operator with the `DelegationManager`.

Note: the way that quorums are handled throughout the AVS contracts are via byte arrays and bitmaps.

### Quorum Definition

When we say "a quorum is a grouping and configuration of specific kinds of stake" concretely we mean that each quorum is defined as a list of `StrategyParams`

```solidity
/**
* @notice In weighing a particular strategy, the amount of underlying asset for that strategy is
* multiplied by its multiplier, then divided by WEIGHTING_DIVISOR
*/
struct StrategyParams {
IStrategy strategy;
uint96 multiplier;
}
```

So you can imagine if EigenLayer knows about 3 strategies by having strategies A, B, C in its `StrategyManager`, an AVS can define its quorum as using the first 2 strategies with its own preference for how the AVS values each strategy by indicating the multiplier.

So you can end up with a quorum that has 2 strategies: `[{strategy: A, multiplier: 2}, {strategy: C, multiplier: 5}]`

The purpose of having a quorum is that an AVS can customize the makeup of its security offering by choosing which kinds of stake/security it would like to utilize.

*There now exists a relationship between operators, their stake, and how AVSs define the security they want via quorums. As a result, we've created a few registry contracts that help in handling the accounting involved in this relationship. These are the `StakeRegistry` and the `IndexRegistry`. We will cover these in more depth later in this section.*

*Since we have a few registries that help our service manage state (both operator and stake state) we need a way to consistently interact with these registries and that's the role of the `RegistryCoordinator`.*
95 changes: 95 additions & 0 deletions docs/avs-integration/RegistryCoordinator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# RegistryCoordinator

The `RegistryCoordinator` coordinates among a few registries:

* `StakeRegistry`
* `IndexRegistry`
* `BLSApkRegistry`

Since the operations of an AVS revolve around quorums as they define the security of an AVS, the `RegistryCoordinator` becomes the primary entry point for handling quorum updates. This means pushing these updates to all the registries it's tracking.

**Given that the `RegistryCoordinator` is the entry point, it's the contract that keeps track of which quorums exist and have been initialized. It is also the primary entry point for operators as they register for and deregister from an AVS' quorums.**

The below code blocks are from the `RegistyCoordinator` contract.

#### Quorum Creation

```solidity
/**
* Config for initial quorums (see `createQuorum`):
* @param _operatorSetParams max operator count and operator churn parameters
* @param _minimumStakes minimum stake weight to allow an operator to register
* @param _strategyParams which Strategies/multipliers a quorum considers when calculating stake weight
*/
function initialize(
...
OperatorSetParam[] memory _operatorSetParams,
uint96[] memory _minimumStakes,
IStakeRegistry.StrategyParams[][] memory _strategyParams
) external initializer {
...
// Create quorums
for (uint256 i = 0; i < _operatorSetParams.length; i++) {
_createQuorum(_operatorSetParams[i], _minimumStakes[i], _strategyParams[i]);
}
}

/**
* @notice Creates a quorum and initializes it in each registry contract
* @param operatorSetParams configures the quorum's max operator count and churn parameters
* @param minimumStake sets the minimum stake required for an operator to register or remain
* registered
* @param strategyParams a list of strategies and multipliers used by the StakeRegistry to
* calculate an operator's stake weight for the quorum
*/
function _createQuorum(
OperatorSetParam memory operatorSetParams,
uint96 minimumStake,
IStakeRegistry.StrategyParams[] memory strategyParams
) {...}
```

#### Operator Registration into Quorums

```solidity
/**
* @notice Registers msg.sender as an operator for one or more quorums. If any quorum exceeds its maximum
* operator capacity after the operator is registered, this method will fail.
* @param quorumNumbers is an ordered byte array containing the quorum numbers being registered for
* @param socket is the socket of the operator (typically an IP address)
* @param params contains the G1 & G2 public keys of the operator, and a signature proving their ownership
* @param operatorSignature is the signature of the operator used by the AVS to register the operator in the delegation manager
* @dev `params` is ignored if the caller has previously registered a public key
* @dev `operatorSignature` is ignored if the operator's status is already REGISTERED
*/
function registerOperator(
bytes calldata quorumNumbers,
string calldata socket,
IBLSApkRegistry.PubkeyRegistrationParams calldata params,
SignatureWithSaltAndExpiry memory operatorSignature
) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) {
/**
* If the operator has NEVER registered a pubkey before, use `params` to register
* their pubkey in blsApkRegistry
*
* If the operator HAS registered a pubkey, `params` is ignored and the pubkey hash
* (operatorId) is fetched instead
*/
bytes32 operatorId = _getOrCreateOperatorId(msg.sender, params);

// Register the operator in each of the registry contracts and update the operator's
// quorum bitmap and registration status
uint32[] memory numOperatorsPerQuorum = _registerOperator({
operator: msg.sender,
operatorId: operatorId,
quorumNumbers: quorumNumbers,
socket: socket,
operatorSignature: operatorSignature
}).numOperatorsPerQuorum;
...
}
```

| Contract | Interface |
| ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| <https://github.com/Layr-Labs/eigenlayer-middleware/blob/dev/src/RegistryCoordinator.sol> | <https://github.com/Layr-Labs/eigenlayer-middleware/blob/dev/src/interfaces/IRegistryCoordinator.sol> |
Loading
Loading