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

Stake root #304

Open
wants to merge 7 commits into
base: feat/slashing-release-branch
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ node_modules/


*.DS_Store

test.sh
2 changes: 1 addition & 1 deletion lib/eigenlayer-contracts
Submodule eigenlayer-contracts updated 32 files
+4 −0 .gitignore
+3 −0 .gitmodules
+1 −0 lib/risc0-ethereum
+983 −91 pkg/bindings/AVSDirectory/binding.go
+1,016 −78 pkg/bindings/AVSDirectoryStorage/binding.go
+203 −0 pkg/bindings/Checkpoints/binding.go
+1 −1 pkg/bindings/DelayedWithdrawalRouter/binding.go
+946 −129 pkg/bindings/IAVSDirectory/binding.go
+203 −0 pkg/bindings/MagnitudeCheckpoints/binding.go
+1 −1 pkg/bindings/RewardsCoordinator/binding.go
+2 −1 remappings.txt
+55 −0 script/configs/devnet/deploy_from_scratch.anvil.config.json
+55 −0 script/configs/devnet/deploy_from_scratch.holesky.config.json
+19 −5 script/deploy/devnet/Deploy_From_Scratch.s.sol
+24 −0 script/deploy/devnet/Upgrade.s.sol
+80 −0 script/deploy/devnet/operatorSets/DeployStrategies.s.sol
+258 −0 script/deploy/devnet/operatorSets/PopulateSRC.sol
+48 −0 script/deploy/devnet/operatorSets/README.md
+36 −23 script/output/devnet/M2_from_scratch_deployment_data.json
+46 −0 script/output/holesky/pre_preprod_slashing.holesky.json
+9 −0 script/utils/ExistingDeploymentParser.sol
+655 −51 src/contracts/core/AVSDirectory.sol
+34 −2 src/contracts/core/AVSDirectoryStorage.sol
+546 −0 src/contracts/core/StakeRootCompendium.sol
+222 −2 src/contracts/interfaces/IAVSDirectory.sol
+0 −3 src/contracts/interfaces/IRewardsCoordinator.sol
+234 −0 src/contracts/interfaces/IStakeRootCompendium.sol
+287 −0 src/contracts/libraries/Checkpoints.sol
+41 −0 src/contracts/libraries/Merkle.sol
+73 −3 src/test/mocks/AVSDirectoryMock.sol
+460 −460 src/test/unit/AVSDirectoryUnit.t.sol
+1 −1 src/test/unit/RewardsCoordinatorUnit.t.sol
500 changes: 250 additions & 250 deletions script/OperatorSetUpgrade.s.sol

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion script/ServiceManagerRouterDeploy.s.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.12;

import {ServiceManagerRouter} from "../src/ServiceManagerRouter.sol";
import {ServiceManagerRouter} from "../src/periphery/ServiceManagerRouter.sol";
import "forge-std/Script.sol";

contract ServiceManagerRouterDeploy is Script {
Expand Down
69 changes: 31 additions & 38 deletions src/BLSApkRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,32 +28,28 @@ contract BLSApkRegistry is BLSApkRegistryStorage {
/**
* @notice Registers the `operator`'s pubkey for the specified `quorumNumbers`.
* @param operator The address of the operator to register.
* @param quorumNumbers The quorum numbers the operator is registering for, where each byte is an 8 bit integer quorumNumber.
* @param quorumNumber The quorum number the operator is registering for
* @dev access restricted to the RegistryCoordinator
* @dev Preconditions (these are assumed, not validated in this contract):
* 1) `quorumNumbers` has no duplicates
* 2) `quorumNumbers.length` != 0
* 3) `quorumNumbers` is ordered in ascending order
* 4) the operator is not already registered
* @dev Precondition: the operator is not already registered for the quorum
*/
function registerOperator(
address operator,
bytes memory quorumNumbers
uint8 quorumNumber
) public virtual onlyRegistryCoordinator {
// Get the operator's pubkey. Reverts if they have not registered a key
(BN254.G1Point memory pubkey, ) = getRegisteredPubkey(operator);

// Update each quorum's aggregate pubkey
_processQuorumApkUpdate(quorumNumbers, pubkey);
_processQuorumApkUpdate(quorumNumber, pubkey);

// Return pubkeyHash, which will become the operator's unique id
emit OperatorAddedToQuorums(operator, getOperatorId(operator), quorumNumbers);
emit OperatorAddedToQuorum(operator, getOperatorId(operator), quorumNumber);
}

/**
* @notice Deregisters the `operator`'s pubkey for the specified `quorumNumbers`.
* @param operator The address of the operator to deregister.
* @param quorumNumbers The quorum numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber.
* @param quorumNumber The quorum number the operator is deregistering from
* @dev access restricted to the RegistryCoordinator
* @dev Preconditions (these are assumed, not validated in this contract):
* 1) `quorumNumbers` has no duplicates
Expand All @@ -64,14 +60,14 @@ contract BLSApkRegistry is BLSApkRegistryStorage {
*/
function deregisterOperator(
address operator,
bytes memory quorumNumbers
uint8 quorumNumber
) public virtual onlyRegistryCoordinator {
// Get the operator's pubkey. Reverts if they have not registered a key
(BN254.G1Point memory pubkey, ) = getRegisteredPubkey(operator);

// Update each quorum's aggregate pubkey
_processQuorumApkUpdate(quorumNumbers, pubkey.negate());
emit OperatorRemovedFromQuorums(operator, getOperatorId(operator), quorumNumbers);
_processQuorumApkUpdate(quorumNumber, pubkey.negate());
emit OperatorRemovedFromQuorum(operator, getOperatorId(operator), quorumNumber);
}

/**
Expand Down Expand Up @@ -144,33 +140,30 @@ contract BLSApkRegistry is BLSApkRegistryStorage {
INTERNAL FUNCTIONS
*******************************************************************************/

function _processQuorumApkUpdate(bytes memory quorumNumbers, BN254.G1Point memory point) internal {
function _processQuorumApkUpdate(uint8 quorumNumber, BN254.G1Point memory point) internal {
BN254.G1Point memory newApk;

for (uint256 i = 0; i < quorumNumbers.length; i++) {
// Validate quorum exists and get history length
uint8 quorumNumber = uint8(quorumNumbers[i]);
uint256 historyLength = apkHistory[quorumNumber].length;
require(historyLength != 0, "BLSApkRegistry._processQuorumApkUpdate: quorum does not exist");

// Update aggregate public key for this quorum
newApk = currentApk[quorumNumber].plus(point);
currentApk[quorumNumber] = newApk;
bytes24 newApkHash = bytes24(BN254.hashG1Point(newApk));

// Update apk history. If the last update was made in this block, update the entry
// Otherwise, push a new historical entry and update the prev->next pointer
ApkUpdate storage lastUpdate = apkHistory[quorumNumber][historyLength - 1];
if (lastUpdate.updateBlockNumber == uint32(block.number)) {
lastUpdate.apkHash = newApkHash;
} else {
lastUpdate.nextUpdateBlockNumber = uint32(block.number);
apkHistory[quorumNumber].push(ApkUpdate({
apkHash: newApkHash,
updateBlockNumber: uint32(block.number),
nextUpdateBlockNumber: 0
}));
}
// Validate quorum exists and get history length
uint256 historyLength = apkHistory[quorumNumber].length;
require(historyLength != 0, "BLSApkRegistry._processQuorumApkUpdate: quorum does not exist");

// Update aggregate public key for this quorum
newApk = currentApk[quorumNumber].plus(point);
currentApk[quorumNumber] = newApk;
bytes24 newApkHash = bytes24(BN254.hashG1Point(newApk));

// Update apk history. If the last update was made in this block, update the entry
// Otherwise, push a new historical entry and update the prev->next pointer
ApkUpdate storage lastUpdate = apkHistory[quorumNumber][historyLength - 1];
if (lastUpdate.updateBlockNumber == uint32(block.number)) {
lastUpdate.apkHash = newApkHash;
} else {
lastUpdate.nextUpdateBlockNumber = uint32(block.number);
apkHistory[quorumNumber].push(ApkUpdate({
apkHash: newApkHash,
updateBlockNumber: uint32(block.number),
nextUpdateBlockNumber: 0
}));
}
}

Expand Down
18 changes: 3 additions & 15 deletions src/BLSSignatureChecker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ contract BLSSignatureChecker is IBLSSignatureChecker {

constructor(IRegistryCoordinator _registryCoordinator) {
registryCoordinator = _registryCoordinator;
stakeRegistry = _registryCoordinator.stakeRegistry();
blsApkRegistry = _registryCoordinator.blsApkRegistry();
delegation = stakeRegistry.delegation();
// delegation = stakeRegistry.delegation();
stakeRegistry = IStakeRegistry(address(0));
delegation = IDelegationManager(address(0));
}

/**
Expand Down Expand Up @@ -198,19 +199,6 @@ contract BLSSignatureChecker is IBLSSignatureChecker {
: 0;

for (uint256 i = 0; i < quorumNumbers.length; i++) {
// If we're disallowing stale stake updates, check that each quorum's last update block
// is within withdrawalDelayBlocks
if (_staleStakesForbidden) {
require(
registryCoordinator.quorumUpdateBlockNumber(
uint8(quorumNumbers[i])
) +
withdrawalDelayBlocks >
referenceBlockNumber,
"BLSSignatureChecker.checkSignatures: StakeRegistry updates must be within withdrawalDelayBlocks window"
);
}

// Validate params.quorumApks is correct for this quorum at the referenceBlockNumber,
// then add it to the total apk
require(
Expand Down
4 changes: 3 additions & 1 deletion src/EjectionManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,11 @@ contract EjectionManager is IEjectionManager, OwnableUpgradeable{
stakeForEjection += operatorStake;
++ejectedOperators;

uint8[] memory quorumNumbers = new uint8[](1);
quorumNumbers[0] = quorumNumber;
registryCoordinator.ejectOperator(
registryCoordinator.getOperatorFromId(_operatorIds[i][j]),
abi.encodePacked(quorumNumber)
quorumNumbers
);

emit OperatorEjected(_operatorIds[i][j], quorumNumber);
Expand Down
165 changes: 165 additions & 0 deletions src/OperatorCache.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.12;

import "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol";
import "eigenlayer-contracts/src/contracts/interfaces/IStakeRootCompendium.sol";
import "eigenlayer-contracts/src/contracts/libraries/Merkle.sol";

import {BN254} from "./libraries/BN254.sol";

contract OperatorCache {
uint32 public immutable CACHE_WINDOW;

/// @notice the stakeRoot compendium address
IStakeRootCompendium public immutable stakeRootCompendium;

struct Stakes {
uint96 delegatedStake;
uint96 slashableStake;
}

struct OperatorPartialLeaf {
uint256 index;
bytes proof;
address operator; // ew ideally we don't need this and the address is just apart of the extraData
Stakes stakes;
}

uint32 public latestCacheTimestamp;
bytes32 public operatorSetRoot;
Stakes public totalStakes;

mapping(bytes32 => Stakes) operatorStakeCache;

constructor(uint32 _CACHE_WINDOW, IStakeRootCompendium _stakeRootCompendium) {
CACHE_WINDOW = _CACHE_WINDOW;
stakeRootCompendium = _stakeRootCompendium;
}

/**
* @notice gets the operatorSetRoot and total stakes and caches them if the cache is stale
* @param stakeRootIndex the index of the stake root
* @param operatorSet the operator set to populate the cache with
* @param operatorSetRootProof the proof of the operator set root against the stake root
* @param operatorTreeRoot the root of the left subtree of the operator set tree
* @param totalStakesCalldata the total stakes of the operator set (the leaves of the left subtree)
* @dev skips if the cache is not stale
*/
function getOrCacheOperatorSet(
uint32 stakeRootIndex,
IAVSDirectory.OperatorSet calldata operatorSet,
bytes calldata operatorSetRootProof,
bytes32 operatorTreeRoot,
Stakes calldata totalStakesCalldata
) external returns (bytes32, Stakes memory) {
// if the latest cache is within the cache window, return
if(block.timestamp - latestCacheTimestamp < CACHE_WINDOW) {
return (operatorSetRoot, totalStakes);
}

// TODO: we probably need a ring buffer for latestCacheTimestamp and operatorSetRoots
// get the stake root submission
IStakeRootCompendium.StakeRootSubmission memory stakeRootSubmission = stakeRootCompendium.getStakeRootSubmission(stakeRootIndex);
require(block.timestamp - stakeRootSubmission.calculationTimestamp < CACHE_WINDOW, "OperatorCache.populateTotalCache: stale stake root");
require(stakeRootSubmission.blacklistableBefore < block.timestamp, "OperatorCache.populateTotalCache: stake root is blacklistable");
latestCacheTimestamp = stakeRootSubmission.calculationTimestamp;

// verify the operatorSetRoot and total stakes
// the operatorSetTree looks like
// operatorSetRoot
// / \
// operatorTreeRoot (uint256 totalSlashableStake_i, uint256 totalDelegatedStake_i)
//
bytes32 operatorSetRootMem = keccak256(
abi.encodePacked(
operatorTreeRoot,
keccak256(
abi.encodePacked(
uint256(totalStakesCalldata.delegatedStake),
uint256(totalStakesCalldata.slashableStake)
)
)
)
);

uint32 operatorSetIndex = stakeRootCompendium.getOperatorSetIndexAtTimestamp(operatorSet, stakeRootSubmission.calculationTimestamp);
require(
Merkle.verifyInclusionKeccak(
operatorSetRootProof,
stakeRootSubmission.stakeRoot,
operatorSetRootMem,
operatorSetIndex
),
"OperatorCache.populateTotalCache: invalid operator set proof"
);
operatorSetRoot = operatorSetRootMem;
totalStakes = totalStakesCalldata;

return (operatorSetRootMem, totalStakesCalldata);
}

/**
* @notice gets the sum of the stakes of certain operators and caches them if the cache is stale
* @param operatorSet the operator set to get stakes for
* @param isCached whether the operator is already cached
* @param publicKeys the public keys of the operators to get stakes for
* @param nonCachedOperators the operators that need caching
* @dev PRECONDITION: requires the operatorSetRoot and totalStakes to not be stale
* @dev skips for operators that are already cached
*/
function getOrCacheOperatorStakes(
IAVSDirectory.OperatorSet calldata operatorSet,
bool[] calldata isCached,
BN254.G1Point[] calldata publicKeys,
OperatorPartialLeaf[] calldata nonCachedOperators
) external returns (Stakes memory) {
bytes32 operatorSetRootMem = operatorSetRoot;
uint256 nonCachedOperatorsIndex = 0;
Stakes memory totalStakesOfOperators;

// TODO: should we add publicKeys in this loop?
// get the stakes of every operator
for(uint256 i = 0; i < publicKeys.length; i++) {
if (isCached[i]) {
// if the operator is already cached, load it
Stakes memory operatorStake = operatorStakeCache[_stakeCacheKey(operatorSet, publicKeys[i], operatorSetRoot)];
require(operatorStake.delegatedStake != 0, "OperatorCache.getOrCacheOperatorStakes: operator is not cached or registered");
totalStakesOfOperators.delegatedStake += operatorStake.delegatedStake;
totalStakesOfOperators.slashableStake += operatorStake.slashableStake;
} else {
require(
Merkle.verifyInclusionKeccak(
nonCachedOperators[nonCachedOperatorsIndex].proof,
operatorSetRootMem,
_hashOperatorLeaf(nonCachedOperators[nonCachedOperatorsIndex], publicKeys[i]),
nonCachedOperators[nonCachedOperatorsIndex].index
),
"OperatorCache.getOrCacheOperatorStakes: invalid operator proof"
);
// TODO: we probably need a ring buffer to save gas here and overwrite a dirty slot
operatorStakeCache[_stakeCacheKey(operatorSet, publicKeys[i], operatorSetRoot)] = nonCachedOperators[nonCachedOperatorsIndex].stakes;

totalStakesOfOperators.delegatedStake += nonCachedOperators[nonCachedOperatorsIndex].stakes.delegatedStake;
totalStakesOfOperators.slashableStake += nonCachedOperators[nonCachedOperatorsIndex].stakes.slashableStake;
nonCachedOperatorsIndex++;
}
}
return totalStakesOfOperators;
}

// the leaves of the stakeTree
function _hashOperatorLeaf(OperatorPartialLeaf calldata operator, BN254.G1Point calldata publicKey) internal pure returns(bytes32) {
return keccak256(
abi.encodePacked(
operator.operator,
operator.stakes.delegatedStake,
operator.stakes.slashableStake,
keccak256(abi.encodePacked(publicKey.X, publicKey.Y))
)
);
}

function _stakeCacheKey(IAVSDirectory.OperatorSet calldata operatorSet, BN254.G1Point calldata publicKey, bytes32 operatorSetRootMem) internal pure returns(bytes32) {
return keccak256(abi.encodePacked(operatorSet.avs, operatorSet.operatorSetId, publicKey.X, publicKey.Y, operatorSetRootMem));
}
}
Loading
Loading