diff --git a/src/interfaces/IRegistryCoordinator.sol b/src/interfaces/IRegistryCoordinator.sol index 9bd3221e..7dae857b 100644 --- a/src/interfaces/IRegistryCoordinator.sol +++ b/src/interfaces/IRegistryCoordinator.sol @@ -147,6 +147,8 @@ interface IRegistryCoordinator is bytes memory quorumNumbers ) external; + function operatorSetsEnabled() external view returns (bool); + /** * @notice Checks if a quorum is an M2 quorum. * @param quorumNumber The quorum identifier. @@ -156,6 +158,12 @@ interface IRegistryCoordinator is uint8 quorumNumber ) external view returns (bool); + /** + * @notice Returns whether M2 quorum registration is disabled. + * @return True if M2 quorum registration is disabled, false otherwise. + */ + function isM2QuorumRegistrationDisabled() external view returns (bool); + /** * @notice Disables M2 quorum registration for the AVS. Once disabled, this cannot be enabled. * @dev When disabled, all registrations to M2 quorums will revert. Deregistrations are still possible. diff --git a/src/unaudited/BLSSigCheckOperatorStateRetriever.sol b/src/unaudited/BLSSigCheckOperatorStateRetriever.sol new file mode 100644 index 00000000..d4fcc063 --- /dev/null +++ b/src/unaudited/BLSSigCheckOperatorStateRetriever.sol @@ -0,0 +1,423 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import {IBLSApkRegistry} from "../interfaces/IBLSApkRegistry.sol"; +import {IBLSSignatureCheckerTypes} from "../interfaces/IBLSSignatureChecker.sol"; +import {IStakeRegistry} from "../interfaces/IStakeRegistry.sol"; +import {IIndexRegistry} from "../interfaces/IIndexRegistry.sol"; +import {ISlashingRegistryCoordinator} from "../interfaces/ISlashingRegistryCoordinator.sol"; +import {BitmapUtils} from "../libraries/BitmapUtils.sol"; +import {BN254} from "../libraries/BN254.sol"; +import {BN256G2} from "./BN256G2.sol"; + +/** + * @title BLSSigCheckOperatorStateRetriever with view functions that allow to retrieve the state of an AVSs registry system. + * @author Bread coop + */ +contract BLSSigCheckOperatorStateRetriever { + struct Operator { + address operator; + bytes32 operatorId; + uint96 stake; + } + + struct CheckSignaturesIndices { + uint32[] nonSignerQuorumBitmapIndices; + uint32[] quorumApkIndices; + uint32[] totalStakeIndices; + uint32[][] nonSignerStakeIndices; // nonSignerStakeIndices[quorumNumberIndex][nonSignerIndex] + } + + error OperatorNotRegistered(); + /// @dev Thrown when the signature is not on the curve. + error InvalidSigma(); + + /** + * @notice This function is intended to to be called by AVS operators every time a new task is created (i.e.) + * the AVS coordinator makes a request to AVS operators. Since all of the crucial information is kept onchain, + * operators don't need to run indexers to fetch the data. + * @param registryCoordinator is the registry coordinator to fetch the AVS registry information from + * @param operatorId the id of the operator to fetch the quorums lists + * @param blockNumber is the block number to get the operator state for + * @return 1) the quorumBitmap of the operator at the given blockNumber + * 2) 2d array of Operator structs. For each quorum the provided operator + * was a part of at `blockNumber`, an ordered list of operators. + */ + function getOperatorState( + ISlashingRegistryCoordinator registryCoordinator, + bytes32 operatorId, + uint32 blockNumber + ) external view returns (uint256, Operator[][] memory) { + bytes32[] memory operatorIds = new bytes32[](1); + operatorIds[0] = operatorId; + uint256 index = + registryCoordinator.getQuorumBitmapIndicesAtBlockNumber(blockNumber, operatorIds)[0]; + + uint256 quorumBitmap = + registryCoordinator.getQuorumBitmapAtBlockNumberByIndex(operatorId, blockNumber, index); + + bytes memory quorumNumbers = BitmapUtils.bitmapToBytesArray(quorumBitmap); + + return (quorumBitmap, getOperatorState(registryCoordinator, quorumNumbers, blockNumber)); + } + + /** + * @notice returns the ordered list of operators (id and stake) for each quorum. The AVS coordinator + * may call this function directly to get the operator state for a given block number + * @param registryCoordinator is the registry coordinator to fetch the AVS registry information from + * @param quorumNumbers are the ids of the quorums to get the operator state for + * @param blockNumber is the block number to get the operator state for + * @return 2d array of Operators. For each quorum, an ordered list of Operators + */ + function getOperatorState( + ISlashingRegistryCoordinator registryCoordinator, + bytes memory quorumNumbers, + uint32 blockNumber + ) public view returns (Operator[][] memory) { + IStakeRegistry stakeRegistry = registryCoordinator.stakeRegistry(); + IIndexRegistry indexRegistry = registryCoordinator.indexRegistry(); + IBLSApkRegistry blsApkRegistry = registryCoordinator.blsApkRegistry(); + + Operator[][] memory operators = new Operator[][](quorumNumbers.length); + for (uint256 i = 0; i < quorumNumbers.length; i++) { + uint8 quorumNumber = uint8(quorumNumbers[i]); + bytes32[] memory operatorIds = + indexRegistry.getOperatorListAtBlockNumber(quorumNumber, blockNumber); + operators[i] = new Operator[](operatorIds.length); + for (uint256 j = 0; j < operatorIds.length; j++) { + operators[i][j] = Operator({ + operator: blsApkRegistry.getOperatorFromPubkeyHash(operatorIds[j]), + operatorId: bytes32(operatorIds[j]), + stake: stakeRegistry.getStakeAtBlockNumber( + bytes32(operatorIds[j]), quorumNumber, blockNumber + ) + }); + } + } + + return operators; + } + + /** + * @notice this is called by the AVS operator to get the relevant indices for the checkSignatures function + * if they are not running an indexer + * @param registryCoordinator is the registry coordinator to fetch the AVS registry information from + * @param referenceBlockNumber is the block number to get the indices for + * @param quorumNumbers are the ids of the quorums to get the operator state for + * @param nonSignerOperatorIds are the ids of the nonsigning operators + * @return 1) the indices of the quorumBitmaps for each of the operators in the @param nonSignerOperatorIds array at the given blocknumber + * 2) the indices of the total stakes entries for the given quorums at the given blocknumber + * 3) the indices of the stakes of each of the nonsigners in each of the quorums they were a + * part of (for each nonsigner, an array of length the number of quorums they were a part of + * that are also part of the provided quorumNumbers) at the given blocknumber + * 4) the indices of the quorum apks for each of the provided quorums at the given blocknumber + */ + function getCheckSignaturesIndices( + ISlashingRegistryCoordinator registryCoordinator, + uint32 referenceBlockNumber, + bytes memory quorumNumbers, + bytes32[] memory nonSignerOperatorIds + ) public view returns (CheckSignaturesIndices memory) { + IStakeRegistry stakeRegistry = registryCoordinator.stakeRegistry(); + CheckSignaturesIndices memory checkSignaturesIndices; + + // get the indices of the quorumBitmap updates for each of the operators in the nonSignerOperatorIds array + checkSignaturesIndices.nonSignerQuorumBitmapIndices = registryCoordinator + .getQuorumBitmapIndicesAtBlockNumber(referenceBlockNumber, nonSignerOperatorIds); + + // get the indices of the totalStake updates for each of the quorums in the quorumNumbers array + checkSignaturesIndices.totalStakeIndices = + stakeRegistry.getTotalStakeIndicesAtBlockNumber(referenceBlockNumber, quorumNumbers); + + checkSignaturesIndices.nonSignerStakeIndices = new uint32[][](quorumNumbers.length); + for ( + uint8 quorumNumberIndex = 0; + quorumNumberIndex < quorumNumbers.length; + quorumNumberIndex++ + ) { + uint256 numNonSignersForQuorum = 0; + // this array's length will be at most the number of nonSignerOperatorIds, this will be trimmed after it is filled + checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex] = + new uint32[](nonSignerOperatorIds.length); + + for (uint256 i = 0; i < nonSignerOperatorIds.length; i++) { + // get the quorumBitmap for the operator at the given blocknumber and index + uint192 nonSignerQuorumBitmap = registryCoordinator + .getQuorumBitmapAtBlockNumberByIndex( + nonSignerOperatorIds[i], + referenceBlockNumber, + checkSignaturesIndices.nonSignerQuorumBitmapIndices[i] + ); + + require(nonSignerQuorumBitmap != 0, OperatorNotRegistered()); + + // if the operator was a part of the quorum and the quorum is a part of the provided quorumNumbers + if ((nonSignerQuorumBitmap >> uint8(quorumNumbers[quorumNumberIndex])) & 1 == 1) { + // get the index of the stake update for the operator at the given blocknumber and quorum number + checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex][numNonSignersForQuorum] + = stakeRegistry.getStakeUpdateIndexAtBlockNumber( + nonSignerOperatorIds[i], + uint8(quorumNumbers[quorumNumberIndex]), + referenceBlockNumber + ); + numNonSignersForQuorum++; + } + } + + // resize the array to the number of nonSigners for this quorum + uint32[] memory nonSignerStakeIndicesForQuorum = new uint32[](numNonSignersForQuorum); + for (uint256 i = 0; i < numNonSignersForQuorum; i++) { + nonSignerStakeIndicesForQuorum[i] = + checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex][i]; + } + checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex] = + nonSignerStakeIndicesForQuorum; + } + + IBLSApkRegistry blsApkRegistry = registryCoordinator.blsApkRegistry(); + // get the indices of the quorum apks for each of the provided quorums at the given blocknumber + checkSignaturesIndices.quorumApkIndices = + blsApkRegistry.getApkIndicesAtBlockNumber(quorumNumbers, referenceBlockNumber); + + return checkSignaturesIndices; + } + + /** + * @notice this function returns the quorumBitmaps for each of the operators in the operatorIds array at the given blocknumber + * @param registryCoordinator is the AVS registry coordinator to fetch the operator information from + * @param operatorIds are the ids of the operators to get the quorumBitmaps for + * @param blockNumber is the block number to get the quorumBitmaps for + */ + function getQuorumBitmapsAtBlockNumber( + ISlashingRegistryCoordinator registryCoordinator, + bytes32[] memory operatorIds, + uint32 blockNumber + ) external view returns (uint256[] memory) { + uint32[] memory quorumBitmapIndices = + registryCoordinator.getQuorumBitmapIndicesAtBlockNumber(blockNumber, operatorIds); + uint256[] memory quorumBitmaps = new uint256[](operatorIds.length); + for (uint256 i = 0; i < operatorIds.length; i++) { + quorumBitmaps[i] = registryCoordinator.getQuorumBitmapAtBlockNumberByIndex( + operatorIds[i], blockNumber, quorumBitmapIndices[i] + ); + } + return quorumBitmaps; + } + + /** + * @notice This function returns the operatorIds for each of the operators in the operators array + * @param registryCoordinator is the AVS registry coordinator to fetch the operator information from + * @param operators is the array of operator address to get corresponding operatorIds for + * @dev if an operator is not registered, the operatorId will be 0 + */ + function getBatchOperatorId( + ISlashingRegistryCoordinator registryCoordinator, + address[] memory operators + ) external view returns (bytes32[] memory operatorIds) { + operatorIds = new bytes32[](operators.length); + for (uint256 i = 0; i < operators.length; ++i) { + operatorIds[i] = registryCoordinator.getOperatorId(operators[i]); + } + } + + /** + * @notice This function returns the operator addresses for each of the operators in the operatorIds array + * @param registryCoordinator is the AVS registry coordinator to fetch the operator information from + * @param operators is the array of operatorIds to get corresponding operator addresses for + * @dev if an operator is not registered, the operator address will be 0 + */ + function getBatchOperatorFromId( + ISlashingRegistryCoordinator registryCoordinator, + bytes32[] memory operatorIds + ) external view returns (address[] memory operators) { + operators = new address[](operatorIds.length); + for (uint256 i = 0; i < operatorIds.length; ++i) { + operators[i] = registryCoordinator.getOperatorFromId(operatorIds[i]); + } + } + + // avoid stack too deep + struct GetNonSignerStakesAndSignatureMemory { + BN254.G1Point[] quorumApks; + BN254.G2Point apkG2; + IIndexRegistry indexRegistry; + IBLSApkRegistry blsApkRegistry; + bytes32[] signingOperatorIds; + } + + /** + * @notice Returns the stakes and signature information for non-signing operators in specified quorums + * @param registryCoordinator The registry coordinator contract to fetch operator information from + * @param quorumNumbers Array of quorum numbers to check for non-signers + * @param sigma The aggregate BLS signature to verify + * @param operators Array of operator addresses that signed the message + * @param blockNumber Is the block number to get the indices for + * @return NonSignerStakesAndSignature Struct containing: + * - nonSignerQuorumBitmapIndices: Indices for retrieving quorum bitmaps of non-signers + * - nonSignerPubkeys: BLS public keys of operators that did not sign + * - quorumApks: Aggregate public keys for each quorum + * - apkG2: Aggregate public key of all signing operators in G2 + * - sigma: The provided signature + * - quorumApkIndices: Indices for retrieving quorum APKs + * - totalStakeIndices: Indices for retrieving total stake info + * - nonSignerStakeIndices: Indices for retrieving non-signer stake info + * @dev Computes the indices of operators that did not sign across all specified quorums + * @dev This function does not validate the signature matches the provided parameters, only that it's in a valid format + */ + function getNonSignerStakesAndSignature( + ISlashingRegistryCoordinator registryCoordinator, + bytes calldata quorumNumbers, + BN254.G1Point calldata sigma, + address[] calldata operators, + uint32 blockNumber + ) external view returns (IBLSSignatureCheckerTypes.NonSignerStakesAndSignature memory) { + GetNonSignerStakesAndSignatureMemory memory m; + m.quorumApks = new BN254.G1Point[](quorumNumbers.length); + m.indexRegistry = registryCoordinator.indexRegistry(); + m.blsApkRegistry = registryCoordinator.blsApkRegistry(); + + // Safe guard AVSs from generating NonSignerStakesAndSignature with invalid sigma + require(_isOnCurve(sigma), InvalidSigma()); + + // Compute the g2 APK of the signing operator set + m.signingOperatorIds = new bytes32[](operators.length); + for (uint256 i = 0; i < operators.length; i++) { + m.signingOperatorIds[i] = registryCoordinator.getOperatorId(operators[i]); + BN254.G2Point memory operatorG2Pk = m.blsApkRegistry.getOperatorPubkeyG2(operators[i]); + (m.apkG2.X[1], m.apkG2.X[0], m.apkG2.Y[1], m.apkG2.Y[0]) = BN256G2.ECTwistAdd( + m.apkG2.X[1], + m.apkG2.X[0], + m.apkG2.Y[1], + m.apkG2.Y[0], + operatorG2Pk.X[1], + operatorG2Pk.X[0], + operatorG2Pk.Y[1], + operatorG2Pk.Y[0] + ); + } + + // Extra scope for stack limit + { + uint32[] memory signingOperatorQuorumBitmapIndices = registryCoordinator + .getQuorumBitmapIndicesAtBlockNumber(blockNumber, m.signingOperatorIds); + // Check that all operators are registered (this is like the check in getCheckSignaturesIndices, but we check against _signing_ operators) + for (uint256 i = 0; i < operators.length; i++) { + uint192 signingOperatorQuorumBitmap = registryCoordinator + .getQuorumBitmapAtBlockNumberByIndex( + m.signingOperatorIds[i], blockNumber, signingOperatorQuorumBitmapIndices[i] + ); + require(signingOperatorQuorumBitmap != 0, OperatorNotRegistered()); + } + } + + // We use this as a dynamic array + uint256 nonSignerOperatorsCount = 0; + bytes32[] memory nonSignerOperatorIds = new bytes32[](16); + // For every quorum + for (uint256 i = 0; i < quorumNumbers.length; i++) { + bytes32[] memory operatorIdsInQuorum = + m.indexRegistry.getOperatorListAtBlockNumber(uint8(quorumNumbers[i]), blockNumber); + // Operator IDs are computed from the hash of the BLS public keys, so an operatorId's public key can't change over time + // This lets us compute the APK at the given block number + m.quorumApks[i] = _computeG1Apk(registryCoordinator, operatorIdsInQuorum); + // We check for every operator in the quorum + for (uint256 j = 0; j < operatorIdsInQuorum.length; j++) { + bool isNewNonSigner = true; + // If it is in the signing operators array + for (uint256 k = 0; k < m.signingOperatorIds.length; k++) { + if (operatorIdsInQuorum[j] == m.signingOperatorIds[k]) { + isNewNonSigner = false; + break; + } + } + // Or already in the non-signing operators array + for (uint256 l = 0; l < nonSignerOperatorsCount; l++) { + if (nonSignerOperatorIds[l] == operatorIdsInQuorum[j]) { + isNewNonSigner = false; + break; + } + } + // And if not, we add it to the non-signing operators array + if (isNewNonSigner) { + // If we are at the end of the array, we need to resize it + if (nonSignerOperatorsCount == nonSignerOperatorIds.length) { + uint256 newCapacity = nonSignerOperatorIds.length * 2; + bytes32[] memory newNonSignerOperatorIds = new bytes32[](newCapacity); + for (uint256 l = 0; l < nonSignerOperatorIds.length; l++) { + newNonSignerOperatorIds[l] = nonSignerOperatorIds[l]; + } + nonSignerOperatorIds = newNonSignerOperatorIds; + } + + nonSignerOperatorIds[nonSignerOperatorsCount] = operatorIdsInQuorum[j]; + nonSignerOperatorsCount++; + } + } + } + + // Trim the nonSignerOperatorIds array to the actual count + bytes32[] memory trimmedNonSignerOperatorIds = new bytes32[](nonSignerOperatorsCount); + for (uint256 i = 0; i < nonSignerOperatorsCount; i++) { + trimmedNonSignerOperatorIds[i] = nonSignerOperatorIds[i]; + } + + BN254.G1Point[] memory nonSignerPubkeys = new BN254.G1Point[](nonSignerOperatorsCount); + for (uint256 i = 0; i < nonSignerOperatorsCount; i++) { + address nonSignerOperator = + registryCoordinator.getOperatorFromId(trimmedNonSignerOperatorIds[i]); + (nonSignerPubkeys[i],) = m.blsApkRegistry.getRegisteredPubkey(nonSignerOperator); + } + + CheckSignaturesIndices memory checkSignaturesIndices = getCheckSignaturesIndices( + registryCoordinator, blockNumber, quorumNumbers, trimmedNonSignerOperatorIds + ); + return IBLSSignatureCheckerTypes.NonSignerStakesAndSignature({ + nonSignerQuorumBitmapIndices: checkSignaturesIndices.nonSignerQuorumBitmapIndices, + nonSignerPubkeys: nonSignerPubkeys, + quorumApks: m.quorumApks, + apkG2: m.apkG2, + sigma: sigma, + quorumApkIndices: checkSignaturesIndices.quorumApkIndices, + totalStakeIndices: checkSignaturesIndices.totalStakeIndices, + nonSignerStakeIndices: checkSignaturesIndices.nonSignerStakeIndices + }); + } + + /** + * @notice Computes the aggregate public key (APK) in G1 for a list of operators + * @dev Aggregates the individual G1 public keys of operators by adding them together + * @param registryCoordinator The registry coordinator contract to fetch operator info from + * @param operatorIds Array of operator IDs to compute the aggregate key for + * @return The aggregate public key as a G1 point, computed by summing individual operator pubkeys + */ + function _computeG1Apk( + ISlashingRegistryCoordinator registryCoordinator, + bytes32[] memory operatorIds + ) internal view returns (BN254.G1Point memory) { + BN254.G1Point memory apk = BN254.G1Point(0, 0); + IBLSApkRegistry blsApkRegistry = registryCoordinator.blsApkRegistry(); + for (uint256 i = 0; i < operatorIds.length; i++) { + address operator = registryCoordinator.getOperatorFromId(operatorIds[i]); + BN254.G1Point memory operatorPk; + (operatorPk.X, operatorPk.Y) = blsApkRegistry.operatorToPubkey(operator); + apk = BN254.plus(apk, operatorPk); + } + return apk; + } + + /** + * @notice Checks if a point lies on the BN254 elliptic curve + * @dev The curve equation is y^2 = x^3 + 3 (mod p) + * @param p The point to check, in G1 + * @return true if the point lies on the curve, false otherwise + */ + function _isOnCurve( + BN254.G1Point memory p + ) internal pure returns (bool) { + uint256 y2 = mulmod(p.Y, p.Y, BN254.FP_MODULUS); + uint256 x2 = mulmod(p.X, p.X, BN254.FP_MODULUS); + uint256 x3 = mulmod(p.X, x2, BN254.FP_MODULUS); + uint256 rhs = addmod(x3, 3, BN254.FP_MODULUS); + return y2 == rhs; + } +} diff --git a/src/unaudited/BN256G2.sol b/src/unaudited/BN256G2.sol new file mode 100644 index 00000000..a1721df3 --- /dev/null +++ b/src/unaudited/BN256G2.sol @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +/** + * @title Elliptic curve operations on twist points for alt_bn128 + * @author Mustafa Al-Bassam (mus@musalbas.com) + * @dev Homepage: https://github.com/musalbas/solidity-BN256G2 + * @dev This is a modified version of the original BN256G2 library to work with solidity 0.8.27 + */ +library BN256G2 { + uint256 internal constant FIELD_MODULUS = + 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47; + uint256 internal constant TWISTBX = + 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; + uint256 internal constant TWISTBY = + 0x9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d2; + uint256 internal constant PTXX = 0; + uint256 internal constant PTXY = 1; + uint256 internal constant PTYX = 2; + uint256 internal constant PTYY = 3; + uint256 internal constant PTZX = 4; + uint256 internal constant PTZY = 5; + + /** + * @notice Add two twist points + * @param pt1xx Coefficient 1 of x on point 1 + * @param pt1xy Coefficient 2 of x on point 1 + * @param pt1yx Coefficient 1 of y on point 1 + * @param pt1yy Coefficient 2 of y on point 1 + * @param pt2xx Coefficient 1 of x on point 2 + * @param pt2xy Coefficient 2 of x on point 2 + * @param pt2yx Coefficient 1 of y on point 2 + * @param pt2yy Coefficient 2 of y on point 2 + * @return (pt3xx, pt3xy, pt3yx, pt3yy) + */ + function ECTwistAdd( + uint256 pt1xx, + uint256 pt1xy, + uint256 pt1yx, + uint256 pt1yy, + uint256 pt2xx, + uint256 pt2xy, + uint256 pt2yx, + uint256 pt2yy + ) public view returns (uint256, uint256, uint256, uint256) { + if (pt1xx == 0 && pt1xy == 0 && pt1yx == 0 && pt1yy == 0) { + if (!(pt2xx == 0 && pt2xy == 0 && pt2yx == 0 && pt2yy == 0)) { + assert(_isOnCurve(pt2xx, pt2xy, pt2yx, pt2yy)); + } + return (pt2xx, pt2xy, pt2yx, pt2yy); + } else if (pt2xx == 0 && pt2xy == 0 && pt2yx == 0 && pt2yy == 0) { + assert(_isOnCurve(pt1xx, pt1xy, pt1yx, pt1yy)); + return (pt1xx, pt1xy, pt1yx, pt1yy); + } + + assert(_isOnCurve(pt1xx, pt1xy, pt1yx, pt1yy)); + assert(_isOnCurve(pt2xx, pt2xy, pt2yx, pt2yy)); + + uint256[6] memory pt3 = + _ECTwistAddJacobian(pt1xx, pt1xy, pt1yx, pt1yy, 1, 0, pt2xx, pt2xy, pt2yx, pt2yy, 1, 0); + + return _fromJacobian(pt3[PTXX], pt3[PTXY], pt3[PTYX], pt3[PTYY], pt3[PTZX], pt3[PTZY]); + } + + /** + * @notice Multiply a twist point by a scalar + * @param s Scalar to multiply by + * @param pt1xx Coefficient 1 of x + * @param pt1xy Coefficient 2 of x + * @param pt1yx Coefficient 1 of y + * @param pt1yy Coefficient 2 of y + * @return (pt2xx, pt2xy, pt2yx, pt2yy) + */ + function ECTwistMul( + uint256 s, + uint256 pt1xx, + uint256 pt1xy, + uint256 pt1yx, + uint256 pt1yy + ) public view returns (uint256, uint256, uint256, uint256) { + uint256 pt1zx = 1; + if (pt1xx == 0 && pt1xy == 0 && pt1yx == 0 && pt1yy == 0) { + pt1xx = 1; + pt1yx = 1; + pt1zx = 0; + } else { + assert(_isOnCurve(pt1xx, pt1xy, pt1yx, pt1yy)); + } + + uint256[6] memory pt2 = _ECTwistMulJacobian(s, pt1xx, pt1xy, pt1yx, pt1yy, pt1zx, 0); + + return _fromJacobian(pt2[PTXX], pt2[PTXY], pt2[PTYX], pt2[PTYY], pt2[PTZX], pt2[PTZY]); + } + + /** + * @notice Get the field modulus + * @return The field modulus + */ + function GetFieldModulus() public pure returns (uint256) { + return FIELD_MODULUS; + } + + function submod(uint256 a, uint256 b, uint256 n) internal pure returns (uint256) { + return addmod(a, n - b, n); + } + + function _FQ2Mul( + uint256 xx, + uint256 xy, + uint256 yx, + uint256 yy + ) internal pure returns (uint256, uint256) { + return ( + submod(mulmod(xx, yx, FIELD_MODULUS), mulmod(xy, yy, FIELD_MODULUS), FIELD_MODULUS), + addmod(mulmod(xx, yy, FIELD_MODULUS), mulmod(xy, yx, FIELD_MODULUS), FIELD_MODULUS) + ); + } + + function _FQ2Muc(uint256 xx, uint256 xy, uint256 c) internal pure returns (uint256, uint256) { + return (mulmod(xx, c, FIELD_MODULUS), mulmod(xy, c, FIELD_MODULUS)); + } + + function _FQ2Add( + uint256 xx, + uint256 xy, + uint256 yx, + uint256 yy + ) internal pure returns (uint256, uint256) { + return (addmod(xx, yx, FIELD_MODULUS), addmod(xy, yy, FIELD_MODULUS)); + } + + function _FQ2Sub( + uint256 xx, + uint256 xy, + uint256 yx, + uint256 yy + ) internal pure returns (uint256 rx, uint256 ry) { + return (submod(xx, yx, FIELD_MODULUS), submod(xy, yy, FIELD_MODULUS)); + } + + function _FQ2Div( + uint256 xx, + uint256 xy, + uint256 yx, + uint256 yy + ) internal view returns (uint256, uint256) { + (yx, yy) = _FQ2Inv(yx, yy); + return _FQ2Mul(xx, xy, yx, yy); + } + + function _FQ2Inv(uint256 x, uint256 y) internal view returns (uint256, uint256) { + uint256 inv = _modInv( + addmod(mulmod(y, y, FIELD_MODULUS), mulmod(x, x, FIELD_MODULUS), FIELD_MODULUS), + FIELD_MODULUS + ); + return (mulmod(x, inv, FIELD_MODULUS), FIELD_MODULUS - mulmod(y, inv, FIELD_MODULUS)); + } + + function _isOnCurve( + uint256 xx, + uint256 xy, + uint256 yx, + uint256 yy + ) internal pure returns (bool) { + uint256 yyx; + uint256 yyy; + uint256 xxxx; + uint256 xxxy; + (yyx, yyy) = _FQ2Mul(yx, yy, yx, yy); + (xxxx, xxxy) = _FQ2Mul(xx, xy, xx, xy); + (xxxx, xxxy) = _FQ2Mul(xxxx, xxxy, xx, xy); + (yyx, yyy) = _FQ2Sub(yyx, yyy, xxxx, xxxy); + (yyx, yyy) = _FQ2Sub(yyx, yyy, TWISTBX, TWISTBY); + return yyx == 0 && yyy == 0; + } + + function _modInv(uint256 a, uint256 n) internal view returns (uint256 result) { + bool success; + assembly { + let freemem := mload(0x40) + mstore(freemem, 0x20) + mstore(add(freemem, 0x20), 0x20) + mstore(add(freemem, 0x40), 0x20) + mstore(add(freemem, 0x60), a) + mstore(add(freemem, 0x80), sub(n, 2)) + mstore(add(freemem, 0xA0), n) + success := staticcall(sub(gas(), 2000), 5, freemem, 0xC0, freemem, 0x20) + result := mload(freemem) + } + require(success); + } + + function _fromJacobian( + uint256 pt1xx, + uint256 pt1xy, + uint256 pt1yx, + uint256 pt1yy, + uint256 pt1zx, + uint256 pt1zy + ) internal view returns (uint256 pt2xx, uint256 pt2xy, uint256 pt2yx, uint256 pt2yy) { + uint256 invzx; + uint256 invzy; + (invzx, invzy) = _FQ2Inv(pt1zx, pt1zy); + (pt2xx, pt2xy) = _FQ2Mul(pt1xx, pt1xy, invzx, invzy); + (pt2yx, pt2yy) = _FQ2Mul(pt1yx, pt1yy, invzx, invzy); + } + + function _ECTwistAddJacobian( + uint256 pt1xx, + uint256 pt1xy, + uint256 pt1yx, + uint256 pt1yy, + uint256 pt1zx, + uint256 pt1zy, + uint256 pt2xx, + uint256 pt2xy, + uint256 pt2yx, + uint256 pt2yy, + uint256 pt2zx, + uint256 pt2zy + ) internal pure returns (uint256[6] memory pt3) { + if (pt1zx == 0 && pt1zy == 0) { + (pt3[PTXX], pt3[PTXY], pt3[PTYX], pt3[PTYY], pt3[PTZX], pt3[PTZY]) = + (pt2xx, pt2xy, pt2yx, pt2yy, pt2zx, pt2zy); + return pt3; + } else if (pt2zx == 0 && pt2zy == 0) { + (pt3[PTXX], pt3[PTXY], pt3[PTYX], pt3[PTYY], pt3[PTZX], pt3[PTZY]) = + (pt1xx, pt1xy, pt1yx, pt1yy, pt1zx, pt1zy); + return pt3; + } + + (pt2yx, pt2yy) = _FQ2Mul(pt2yx, pt2yy, pt1zx, pt1zy); // U1 = y2 * z1 + (pt3[PTYX], pt3[PTYY]) = _FQ2Mul(pt1yx, pt1yy, pt2zx, pt2zy); // U2 = y1 * z2 + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt1zx, pt1zy); // V1 = x2 * z1 + (pt3[PTZX], pt3[PTZY]) = _FQ2Mul(pt1xx, pt1xy, pt2zx, pt2zy); // V2 = x1 * z2 + + if (pt2xx == pt3[PTZX] && pt2xy == pt3[PTZY]) { + if (pt2yx == pt3[PTYX] && pt2yy == pt3[PTYY]) { + (pt3[PTXX], pt3[PTXY], pt3[PTYX], pt3[PTYY], pt3[PTZX], pt3[PTZY]) = + _ECTwistDoubleJacobian(pt1xx, pt1xy, pt1yx, pt1yy, pt1zx, pt1zy); + return pt3; + } + (pt3[PTXX], pt3[PTXY], pt3[PTYX], pt3[PTYY], pt3[PTZX], pt3[PTZY]) = (1, 0, 1, 0, 0, 0); + return pt3; + } + + (pt2zx, pt2zy) = _FQ2Mul(pt1zx, pt1zy, pt2zx, pt2zy); // W = z1 * z2 + (pt1xx, pt1xy) = _FQ2Sub(pt2yx, pt2yy, pt3[PTYX], pt3[PTYY]); // U = U1 - U2 + (pt1yx, pt1yy) = _FQ2Sub(pt2xx, pt2xy, pt3[PTZX], pt3[PTZY]); // V = V1 - V2 + (pt1zx, pt1zy) = _FQ2Mul(pt1yx, pt1yy, pt1yx, pt1yy); // V_squared = V * V + (pt2yx, pt2yy) = _FQ2Mul(pt1zx, pt1zy, pt3[PTZX], pt3[PTZY]); // V_squared_times_V2 = V_squared * V2 + (pt1zx, pt1zy) = _FQ2Mul(pt1zx, pt1zy, pt1yx, pt1yy); // V_cubed = V * V_squared + (pt3[PTZX], pt3[PTZY]) = _FQ2Mul(pt1zx, pt1zy, pt2zx, pt2zy); // newz = V_cubed * W + (pt2xx, pt2xy) = _FQ2Mul(pt1xx, pt1xy, pt1xx, pt1xy); // U * U + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt2zx, pt2zy); // U * U * W + (pt2xx, pt2xy) = _FQ2Sub(pt2xx, pt2xy, pt1zx, pt1zy); // U * U * W - V_cubed + (pt2zx, pt2zy) = _FQ2Muc(pt2yx, pt2yy, 2); // 2 * V_squared_times_V2 + (pt2xx, pt2xy) = _FQ2Sub(pt2xx, pt2xy, pt2zx, pt2zy); // A = U * U * W - V_cubed - 2 * V_squared_times_V2 + (pt3[PTXX], pt3[PTXY]) = _FQ2Mul(pt1yx, pt1yy, pt2xx, pt2xy); // newx = V * A + (pt1yx, pt1yy) = _FQ2Sub(pt2yx, pt2yy, pt2xx, pt2xy); // V_squared_times_V2 - A + (pt1yx, pt1yy) = _FQ2Mul(pt1xx, pt1xy, pt1yx, pt1yy); // U * (V_squared_times_V2 - A) + (pt1xx, pt1xy) = _FQ2Mul(pt1zx, pt1zy, pt3[PTYX], pt3[PTYY]); // V_cubed * U2 + (pt3[PTYX], pt3[PTYY]) = _FQ2Sub(pt1yx, pt1yy, pt1xx, pt1xy); // newy = U * (V_squared_times_V2 - A) - V_cubed * U2 + } + + function _ECTwistDoubleJacobian( + uint256 pt1xx, + uint256 pt1xy, + uint256 pt1yx, + uint256 pt1yy, + uint256 pt1zx, + uint256 pt1zy + ) + internal + pure + returns ( + uint256 pt2xx, + uint256 pt2xy, + uint256 pt2yx, + uint256 pt2yy, + uint256 pt2zx, + uint256 pt2zy + ) + { + (pt2xx, pt2xy) = _FQ2Muc(pt1xx, pt1xy, 3); // 3 * x + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt1xx, pt1xy); // W = 3 * x * x + (pt1zx, pt1zy) = _FQ2Mul(pt1yx, pt1yy, pt1zx, pt1zy); // S = y * z + (pt2yx, pt2yy) = _FQ2Mul(pt1xx, pt1xy, pt1yx, pt1yy); // x * y + (pt2yx, pt2yy) = _FQ2Mul(pt2yx, pt2yy, pt1zx, pt1zy); // B = x * y * S + (pt1xx, pt1xy) = _FQ2Mul(pt2xx, pt2xy, pt2xx, pt2xy); // W * W + (pt2zx, pt2zy) = _FQ2Muc(pt2yx, pt2yy, 8); // 8 * B + (pt1xx, pt1xy) = _FQ2Sub(pt1xx, pt1xy, pt2zx, pt2zy); // H = W * W - 8 * B + (pt2zx, pt2zy) = _FQ2Mul(pt1zx, pt1zy, pt1zx, pt1zy); // S_squared = S * S + (pt2yx, pt2yy) = _FQ2Muc(pt2yx, pt2yy, 4); // 4 * B + (pt2yx, pt2yy) = _FQ2Sub(pt2yx, pt2yy, pt1xx, pt1xy); // 4 * B - H + (pt2yx, pt2yy) = _FQ2Mul(pt2yx, pt2yy, pt2xx, pt2xy); // W * (4 * B - H) + (pt2xx, pt2xy) = _FQ2Muc(pt1yx, pt1yy, 8); // 8 * y + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt1yx, pt1yy); // 8 * y * y + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt2zx, pt2zy); // 8 * y * y * S_squared + (pt2yx, pt2yy) = _FQ2Sub(pt2yx, pt2yy, pt2xx, pt2xy); // newy = W * (4 * B - H) - 8 * y * y * S_squared + (pt2xx, pt2xy) = _FQ2Muc(pt1xx, pt1xy, 2); // 2 * H + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt1zx, pt1zy); // newx = 2 * H * S + (pt2zx, pt2zy) = _FQ2Mul(pt1zx, pt1zy, pt2zx, pt2zy); // S * S_squared + (pt2zx, pt2zy) = _FQ2Muc(pt2zx, pt2zy, 8); // newz = 8 * S * S_squared + } + + function _ECTwistMulJacobian( + uint256 d, + uint256 pt1xx, + uint256 pt1xy, + uint256 pt1yx, + uint256 pt1yy, + uint256 pt1zx, + uint256 pt1zy + ) internal pure returns (uint256[6] memory pt2) { + while (d != 0) { + if ((d & 1) != 0) { + pt2 = _ECTwistAddJacobian( + pt2[PTXX], + pt2[PTXY], + pt2[PTYX], + pt2[PTYY], + pt2[PTZX], + pt2[PTZY], + pt1xx, + pt1xy, + pt1yx, + pt1yy, + pt1zx, + pt1zy + ); + } + (pt1xx, pt1xy, pt1yx, pt1yy, pt1zx, pt1zy) = + _ECTwistDoubleJacobian(pt1xx, pt1xy, pt1yx, pt1yy, pt1zx, pt1zy); + + d = d / 2; + } + } +} diff --git a/test/fork/EigenDA.t.sol b/test/fork/EigenDA.t.sol new file mode 100644 index 00000000..419c1492 --- /dev/null +++ b/test/fork/EigenDA.t.sol @@ -0,0 +1,1130 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import {Vm} from "forge-std/Vm.sol"; +import {stdJson} from "forge-std/StdJson.sol"; +import {Test, console2 as console} from "forge-std/Test.sol"; +import {UpgradeableProxyLib} from "../unit/UpgradeableProxyLib.sol"; +import {BN254} from "../../src/libraries/BN254.sol"; +import {Pausable} from "eigenlayer-contracts/src/contracts/permissions/Pausable.sol"; +import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; +import {OperatorLib} from "../utils/OperatorLib.sol"; +import {BitmapUtils} from "../../src/libraries/BitmapUtils.sol"; + +import {IRegistryCoordinator} from "../../src/interfaces/IRegistryCoordinator.sol"; +import {IServiceManager} from "../../src/interfaces/IServiceManager.sol"; +import {IStakeRegistry} from "../../src/interfaces/IStakeRegistry.sol"; +import {IBLSApkRegistry, IBLSApkRegistryTypes} from "../../src/interfaces/IBLSApkRegistry.sol"; +import {IIndexRegistry} from "../../src/interfaces/IIndexRegistry.sol"; +import {ISlashingRegistryCoordinator} from "../../src/interfaces/ISlashingRegistryCoordinator.sol"; +import {ISocketRegistry} from "../../src/interfaces/ISocketRegistry.sol"; +import {IPauserRegistry} from "eigenlayer-contracts/src/contracts/interfaces/IPauserRegistry.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IDelegationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; +import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; +import {IAVSRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IAVSRegistrar.sol"; +import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; +import {IRewardsCoordinator} from + "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol"; +import {IPermissionController} from + "eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol"; +import { + IAllocationManager, + OperatorSet, + IAllocationManagerTypes +} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { + ISignatureUtilsMixin, + ISignatureUtilsMixinTypes +} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtilsMixin.sol"; + +// Import concrete implementation for deployment +import {RegistryCoordinator, IRegistryCoordinatorTypes} from "../../src/RegistryCoordinator.sol"; +import {ISlashingRegistryCoordinatorTypes} from + "../../src/interfaces/ISlashingRegistryCoordinator.sol"; +import {ServiceManagerBase} from "../../src/ServiceManagerBase.sol"; +import {BLSApkRegistry} from "../../src/BLSApkRegistry.sol"; +import {IndexRegistry} from "../../src/IndexRegistry.sol"; +import {StakeRegistry, IStakeRegistryTypes} from "../../src/StakeRegistry.sol"; +import {SocketRegistry} from "../../src/SocketRegistry.sol"; + +// Import ERC20Mock contract +import {ERC20Mock} from "../mocks/ERC20Mock.sol"; +import {IStrategyFactory} from "eigenlayer-contracts/src/contracts/interfaces/IStrategyFactory.sol"; +import {IStrategyManager} from "eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol"; + +// Extended interface to get addresses of other contracts +interface IServiceManagerExtended { + function avsDirectory() external view returns (IAVSDirectory); +} + +interface StakeRegistryExtended { + function delegation() external view returns (IDelegationManager); +} + +interface IDelegationManagerExtended { + function allocationManager() external view returns (IAllocationManager); + function strategyManager() external view returns (IStrategyManager); +} + +interface IAllocationManagerExtended { + function permissionController() external view returns (IPermissionController); +} + +contract EigenDA_SM_Gap { + uint256[50] private __EigenDASM_GAP; +} + +contract BLSSignatureChecker_Pausable_GAP { + uint256[100] private __GAP; +} + +// EigenDAServiceManagerStorage, ServiceManagerBase, BLSSignatureChecker, Pausable +contract TestServiceManager is + EigenDA_SM_Gap, + ServiceManagerBase, + BLSSignatureChecker_Pausable_GAP +{ + constructor( + IAVSDirectory __avsDirectory, + IRewardsCoordinator __rewardsCoordinator, + ISlashingRegistryCoordinator __registryCoordinator, + IStakeRegistry __stakeRegistry, + IPermissionController __permissionController, + IAllocationManager __allocationManager + ) + ServiceManagerBase( + __avsDirectory, + __rewardsCoordinator, + __registryCoordinator, + __stakeRegistry, + __permissionController, + __allocationManager + ) + {} +} + +contract EigenDATest is Test { + using stdJson for string; + using OperatorLib for *; + + struct EigenDADeploymentData { + address blsApkRegistry; + address eigenDAProxyAdmin; + address eigenDAServiceManager; + address indexRegistry; + address mockDispatcher; + address operatorStateRetriever; + address registryCoordinator; + address serviceManagerRouter; + address stakeRegistry; + } + + struct EigenDAChainInfo { + uint256 chainId; + uint256 deploymentBlock; + } + + struct EigenDAPermissionsData { + address eigenDABatchConfirmer; + address eigenDAChurner; + address eigenDAEjector; + address eigenDAOwner; + address eigenDAUpgrader; + address pauserRegistry; + } + + struct EigenDAData { + EigenDADeploymentData addresses; + EigenDAChainInfo chainInfo; + EigenDAPermissionsData permissions; + } + + struct RegistryCoordinatorState { + uint32 operatorSetUpdateNonce; + uint8 numQuorums; + } + + struct ServiceManagerState { + uint256 paused; + address owner; + } + + struct BlsApkRegistryState { + bytes32[] currentApkHashes; + } + + struct IndexRegistryState { + uint32[] operatorCounts; + } + + struct StakeRegistryState { + uint32[] numStrategies; + } + + struct M2QuorumOperators { + uint8[] quorumNumbers; + address[][] operatorIds; + string placeholder; // Add a dummy field to avoid getter compiler error + } + + struct ContractStates { + RegistryCoordinatorState registryCoordinator; + ServiceManagerState serviceManager; + BlsApkRegistryState blsApkRegistry; + IndexRegistryState indexRegistry; + StakeRegistryState stakeRegistry; + } + + // Variables to hold our data + EigenDAData public eigenDAData; + ContractStates public preUpgradeStates; + M2QuorumOperators public m2QuorumOperators; + + // Core contract addresses from StakeRegistry + address public delegationManagerAddr; + address public avsDirectoryAddr; + address public allocationManagerAddr; + address public permissionControllerAddr; + address public rewardsCoordinatorAddr; + + address public registryCoordinatorOwner; + address public serviceManagerOwner; + + uint256 constant OPERATOR_COUNT = 5; + OperatorLib.Operator[OPERATOR_COUNT] public operators; + + address public newRegistryCoordinatorImpl; + address public newServiceManagerImpl; + address public newBlsApkRegistryImpl; + address public newIndexRegistryImpl; + address public newStakeRegistryImpl; + address public socketRegistry; + + ISlashingRegistryCoordinator public registryCoordinator; + IBLSApkRegistry public apkRegistry; + IIndexRegistry public indexRegistry; + IStakeRegistry public stakeRegistry; + IServiceManager public serviceManager; + IAllocationManager public allocationManager; + IAVSDirectory public avsDirectory; + IDelegationManagerExtended public delegationManager; + IPermissionController public permissionController; + + address public token; + IStrategy public strategy; + IStrategyFactory public strategyFactory; + IStrategyManager public strategyManager; + + function setUp() public virtual { + // Setup the Holesky fork and load EigenDA deployment data + eigenDAData = _setupEigenDAFork("test/utils"); + + delegationManagerAddr = + address(StakeRegistryExtended(eigenDAData.addresses.stakeRegistry).delegation()); + avsDirectoryAddr = address( + IServiceManagerExtended(eigenDAData.addresses.eigenDAServiceManager).avsDirectory() + ); + allocationManagerAddr = + address(IDelegationManagerExtended(delegationManagerAddr).allocationManager()); + permissionControllerAddr = + address(IAllocationManagerExtended(allocationManagerAddr).permissionController()); + /// TODO: + rewardsCoordinatorAddr = address(0); + + registryCoordinator = + ISlashingRegistryCoordinator(eigenDAData.addresses.registryCoordinator); + apkRegistry = IBLSApkRegistry(eigenDAData.addresses.blsApkRegistry); + indexRegistry = IIndexRegistry(eigenDAData.addresses.indexRegistry); + stakeRegistry = IStakeRegistry(eigenDAData.addresses.stakeRegistry); + serviceManager = IServiceManager(eigenDAData.addresses.eigenDAServiceManager); + allocationManager = IAllocationManager(allocationManagerAddr); + avsDirectory = IAVSDirectory(avsDirectoryAddr); + delegationManager = IDelegationManagerExtended(delegationManagerAddr); + permissionController = IPermissionController(permissionControllerAddr); + // Initialize strategy manager and factory + strategyManager = delegationManager.strategyManager(); + strategyFactory = IStrategyFactory(strategyManager.strategyWhitelister()); + serviceManagerOwner = + OwnableUpgradeable(eigenDAData.addresses.eigenDAServiceManager).owner(); + registryCoordinatorOwner = + OwnableUpgradeable(eigenDAData.addresses.registryCoordinator).owner(); + + _verifyInitialSetup(); + + _captureAndStorePreUpgradeState(); + + _deployNewImplementations(); + + _createOperators(); + + uint256 operatorTokenAmount = 10 ether; + (token, strategy) = _setupTokensForOperators(operatorTokenAmount); + _setUpTokensForExistingQuorums(1000 ether); + + console.log("Registering operators in EigenLayer..."); + _registerOperatorsAsEigenLayerOperators(); + } + + function test_Upgrade() public { + _upgradeContracts(); + } + + function test_ValidatePostUpgradeState() public { + _upgradeContracts(); + console.log("Validating post-upgrade contract states"); + require( + serviceManagerOwner == OwnableUpgradeable(address(serviceManager)).owner(), + "Service Manager owner mismatch post-upgrade" + ); + + // Verify quorum count is maintained + uint8 quorumCount = registryCoordinator.quorumCount(); + console.log("quorum count:", quorumCount); + require( + quorumCount == preUpgradeStates.registryCoordinator.numQuorums, + "Quorum count changed after upgrade" + ); + + // Verify each quorum's data is maintained across all registries + for (uint8 i = 0; i < quorumCount; i++) { + // 1. Verify BLSApkRegistry state + bytes32 currentApkHash = BN254.hashG1Point(apkRegistry.getApk(i)); + require( + currentApkHash == preUpgradeStates.blsApkRegistry.currentApkHashes[i], + "BLSApkRegistry: APK hash changed after upgrade" + ); + + // 2. Verify IndexRegistry state + uint32 operatorCount = indexRegistry.totalOperatorsForQuorum(i); + require( + operatorCount == preUpgradeStates.indexRegistry.operatorCounts[i], + "IndexRegistry: Operator count changed after upgrade" + ); + + // 3. Verify StakeRegistry state - only if quorum exists in StakeRegistry + if (stakeRegistry.getTotalStakeHistoryLength(i) > 0) { + uint256 strategyCount = stakeRegistry.strategyParamsLength(i); + require( + uint32(strategyCount) == preUpgradeStates.stakeRegistry.numStrategies[i], + "StakeRegistry: Strategy count changed after upgrade" + ); + } + } + + console.log("Post-upgrade validation successful"); + } + + function test_PostUpgrade_CreateOperatorSet() public { + _upgradeContracts(); + + _configureUAMAppointees(); + + _createTotalDelegatedStakeOpSet(); + + // Register operators for the new quorum + uint32[] memory operatorSetIds = new uint32[](1); + operatorSetIds[0] = 3; // Quorum 3 (totalDelegatedStake) + + console.log("Registering operators for quorum 1..."); + for (uint256 i = 0; i < OPERATOR_COUNT; i++) { + vm.startPrank(operators[i].key.addr); + OperatorLib.registerOperatorFromAVS_OpSet( + operators[i], + allocationManagerAddr, + address(registryCoordinator), + address(serviceManager), + operatorSetIds + ); + vm.stopPrank(); + } + + // Verify that operator sets are enabled in the Registry Coordinator + console.log("Verifying operator sets are enabled..."); + bool operatorSetsEnabled = + IRegistryCoordinator(address(registryCoordinator)).operatorSetsEnabled(); + assertTrue( + operatorSetsEnabled, + "Operator sets should be enabled after creating a slashable stake quorum" + ); + + // Verify operators are registered + uint32 operatorCount = indexRegistry.totalOperatorsForQuorum(3); + assertEq(operatorCount, OPERATOR_COUNT, "All operators should be registered"); + + console.log("Successfully created new operator set quorum with %d operators", operatorCount); + } + + function test_PostUpgrade_DeregisterM2Operators() public { + // Upgrade the contracts first + _upgradeContracts(); + _configureUAMAppointees(); + + _createTotalDelegatedStakeOpSet(); + + uint256 totalDeregisteredOperators = 0; + + console.log("Deregistering M2 quorum operators..."); + + // Iterate through quorums and deregister each operator + for (uint8 i = 0; i < m2QuorumOperators.quorumNumbers.length; i++) { + uint8 quorumNumber = m2QuorumOperators.quorumNumbers[i]; + address[] memory operatorAddresses = m2QuorumOperators.operatorIds[i]; + + console.log( + "Deregistering %d operators from quorum %d", operatorAddresses.length, quorumNumber + ); + + // Prepare quorum number array for deregistration + uint8[] memory quorumNumbersArray = new uint8[](1); + quorumNumbersArray[0] = quorumNumber; + + // Deregister each operator from the quorum + for (uint256 j = 0; j < operatorAddresses.length; j++) { + address operatorAddr = operatorAddresses[j]; + + OperatorLib.Wallet memory wallet; + wallet.addr = operatorAddr; + + OperatorLib.Operator memory operator; + operator.key = wallet; + + vm.startPrank(operatorAddr); + + OperatorLib.deregisterOperatorFromAVS_M2( + operator, address(registryCoordinator), quorumNumbersArray + ); + + vm.stopPrank(); + totalDeregisteredOperators++; + } + } + + console.log( + "Successfully deregistered %d operators from M2 quorums", totalDeregisteredOperators + ); + + // Verify operators are deregistered by checking the updated operator counts + for (uint8 i = 0; i < m2QuorumOperators.quorumNumbers.length; i++) { + uint8 quorumNumber = m2QuorumOperators.quorumNumbers[i]; + if (m2QuorumOperators.operatorIds[i].length > 0) { + uint32 operatorCountAfter = indexRegistry.totalOperatorsForQuorum(quorumNumber); + assertEq(operatorCountAfter, 0, "Operators should be deregistered from quorum"); + console.log("Verified quorum %d now has 0 operators", quorumNumber); + } + } + } + + function test_PostUpgrade_RegisterToM2Quorums() public { + _upgradeContracts(); + _configureUAMAppointees(); + _createTotalDelegatedStakeOpSet(); + + // Use existing operators that were created in setUp + console.log("Using %d existing operators", OPERATOR_COUNT); + + console.log("Registering operators to M2 quorums..."); + + uint256 quorumCount = 1; + uint8[] memory quorumsToRegister = new uint8[](quorumCount); + for (uint8 i = 0; i < quorumCount; i++) { + quorumsToRegister[i] = 0; + } + + // Register each operator to the existing M2 quorums + for (uint256 i = 0; i < OPERATOR_COUNT; i++) { + vm.startPrank(operators[i].key.addr); + + OperatorLib.registerOperatorToAVS_M2( + operators[i], + address(avsDirectory), + address(serviceManager), + address(registryCoordinator), + quorumsToRegister + ); + + vm.stopPrank(); + console.log("Registered operator %d to M2 quorums", i + 1); + } + + console.log("Successfully registered %d operators to M2 quorums", OPERATOR_COUNT); + } + + function test_PostUpgrade_DisableM2() public { + _upgradeContracts(); + + _configureUAMAppointees(); + + // Create a slashable stake quorum with lookAheadPeriod + _createSlashableStakeOpSet(10); + + // Verify that operator sets are enabled in the Registry Coordinator + console.log("Verifying operator sets are enabled..."); + bool operatorSetsEnabled = + IRegistryCoordinator(address(registryCoordinator)).operatorSetsEnabled(); + assertTrue( + operatorSetsEnabled, + "Operator sets should be enabled after creating a slashable stake quorum" + ); + + // Disable M2 quorum registration in the Registry Coordinator + console.log("Disabling M2 quorum registration..."); + vm.startPrank(registryCoordinatorOwner); + IRegistryCoordinator(address(registryCoordinator)).disableM2QuorumRegistration(); + vm.stopPrank(); + + // Verify M2 quorum registration is disabled + bool isM2QuorumRegistrationDisabled = + IRegistryCoordinator(address(registryCoordinator)).isM2QuorumRegistrationDisabled(); + assertTrue(isM2QuorumRegistrationDisabled, "M2 quorum registration should be disabled"); + + console.log("Successfully disabled M2 quorum registration."); + } + + function test_PostUpgrade_DisableM2_Registration() public { + _upgradeContracts(); + _configureUAMAppointees(); + _createTotalDelegatedStakeOpSet(); + + console.log("Disabling M2 quorum registration..."); + vm.startPrank(registryCoordinatorOwner); + IRegistryCoordinator(address(registryCoordinator)).disableM2QuorumRegistration(); + vm.stopPrank(); + + bool isM2QuorumRegistrationDisabled = + IRegistryCoordinator(address(registryCoordinator)).isM2QuorumRegistrationDisabled(); + assertTrue(isM2QuorumRegistrationDisabled, "M2 quorum registration should be disabled"); + + uint8[] memory quorumsToRegister = new uint8[](1); + quorumsToRegister[0] = 0; // Quorum 0 is an M2 quorum + + console.log("Attempting to register to M2 quorums after disabling M2 registration..."); + vm.startPrank(operators[0].key.addr); + + bytes32 salt = keccak256(abi.encodePacked(block.timestamp, operators[0].key.addr)); + uint256 expiry = block.timestamp + 1 hours; + + bytes32 operatorRegistrationDigestHash = avsDirectory + .calculateOperatorAVSRegistrationDigestHash( + operators[0].key.addr, address(serviceManager), salt, expiry + ); + + bytes memory signature = + OperatorLib.signWithOperatorKey(operators[0], operatorRegistrationDigestHash); + + bytes32 pubkeyRegistrationMessageHash = + registryCoordinator.calculatePubkeyRegistrationMessageHash(operators[0].key.addr); + + BN254.G1Point memory blsSig = + OperatorLib.signMessage(operators[0].signingKey, pubkeyRegistrationMessageHash); + + IBLSApkRegistryTypes.PubkeyRegistrationParams memory params = IBLSApkRegistryTypes + .PubkeyRegistrationParams({ + pubkeyG1: operators[0].signingKey.publicKeyG1, + pubkeyG2: operators[0].signingKey.publicKeyG2, + pubkeyRegistrationSignature: blsSig + }); + + ISignatureUtilsMixinTypes.SignatureWithSaltAndExpiry memory operatorSignature = + ISignatureUtilsMixinTypes.SignatureWithSaltAndExpiry({ + signature: signature, + salt: salt, + expiry: expiry + }); + + uint256 quorumBitmap = 0; + for (uint256 i = 0; i < quorumsToRegister.length; i++) { + quorumBitmap = BitmapUtils.setBit(quorumBitmap, quorumsToRegister[i]); + } + bytes memory quorumNumbersBytes = BitmapUtils.bitmapToBytesArray(quorumBitmap); + + vm.expectRevert(bytes4(keccak256("M2QuorumRegistrationIsDisabled()"))); + IRegistryCoordinator(address(registryCoordinator)).registerOperator( + quorumNumbersBytes, "socket", params, operatorSignature + ); + + vm.stopPrank(); + console.log("Successfully verified M2 registration is disabled"); + } + + function test_PostUpgrade_DisableM2_Deregistration() public { + _upgradeContracts(); + _configureUAMAppointees(); + + // Register operators to M2 quorums before disabling M2 registration + console.log("Registering operators to M2 quorums before disabling registration..."); + uint8[] memory quorumsToRegister = new uint8[](1); + quorumsToRegister[0] = 0; // Quorum 0 is an M2 quorum + + // Now disable M2 quorum registration + console.log("Disabling M2 quorum registration..."); + vm.startPrank(registryCoordinatorOwner); + IRegistryCoordinator(address(registryCoordinator)).disableM2QuorumRegistration(); + vm.stopPrank(); + + // Verify M2 quorum registration is disabled + bool isM2QuorumRegistrationDisabled = + IRegistryCoordinator(address(registryCoordinator)).isM2QuorumRegistrationDisabled(); + assertTrue(isM2QuorumRegistrationDisabled, "M2 quorum registration should be disabled"); + + // Attempt to deregister operator from M2 quorums - this should succeed + console.log("Attempting to deregister from M2 quorums after disabling M2 registration..."); + address operatorAddr = m2QuorumOperators.operatorIds[0][0]; + + OperatorLib.Wallet memory wallet; + wallet.addr = operatorAddr; + + OperatorLib.Operator memory operator; + operator.key = wallet; + + vm.startPrank(operatorAddr); + OperatorLib.deregisterOperatorFromAVS_M2( + operators[0], address(registryCoordinator), quorumsToRegister + ); + vm.stopPrank(); + + console.log( + "Successfully verified deregistration from M2 quorums is still possible after disabling registration" + ); + } + + function test_TotalDelegatedStakeQuorumRegistration() public { + _upgradeContracts(); + _configureUAMAppointees(); + _createTotalDelegatedStakeOpSet(); + vm.startPrank(registryCoordinatorOwner); + IRegistryCoordinator(address(registryCoordinator)).disableM2QuorumRegistration(); + vm.stopPrank(); + + uint8 quorumCount = registryCoordinator.quorumCount(); + uint8 totalDelegatedStakeQuorumId = quorumCount - 1; + + uint32[] memory operatorSetIds = new uint32[](1); + operatorSetIds[0] = totalDelegatedStakeQuorumId; + + for (uint256 i = 0; i < OPERATOR_COUNT; i++) { + vm.startPrank(operators[i].key.addr); + + console.log("Registering operator %d: %s", i, operators[i].key.addr); + + OperatorLib.registerOperatorFromAVS_OpSet( + operators[i], + allocationManagerAddr, + address(registryCoordinator), + address(serviceManager), + operatorSetIds + ); + + vm.stopPrank(); + } + + uint32 registeredOperatorCount = + indexRegistry.totalOperatorsForQuorum(totalDelegatedStakeQuorumId); + assertEq( + registeredOperatorCount, + OPERATOR_COUNT, + "All operators should be registered to operatorset" + ); + + for (uint256 i = 0; i < OPERATOR_COUNT; i++) { + // Check operator registration status in RegistryCoordinator + ISlashingRegistryCoordinatorTypes.OperatorStatus status = + registryCoordinator.getOperatorStatus(operators[i].key.addr); + + assertTrue( + status == ISlashingRegistryCoordinatorTypes.OperatorStatus.REGISTERED, + "Operator should be registered" + ); + } + } + + function _createTotalDelegatedStakeOpSet() internal { + console.log("Creating a new slashable stake quorum (quorum 1)..."); + + ISlashingRegistryCoordinatorTypes.OperatorSetParam memory operatorSetParam = + ISlashingRegistryCoordinatorTypes.OperatorSetParam({ + maxOperatorCount: 100, + kickBIPsOfOperatorStake: 10500, // 105% + kickBIPsOfTotalStake: 100 // 1% + }); + + IStakeRegistryTypes.StrategyParams[] memory strategyParams = + new IStakeRegistryTypes.StrategyParams[](1); + strategyParams[0] = + IStakeRegistryTypes.StrategyParams({strategy: strategy, multiplier: 1 * 1e18}); + + uint96 minimumStake = uint96(1 ether); + + vm.startPrank(serviceManagerOwner); + registryCoordinator.createTotalDelegatedStakeQuorum( + operatorSetParam, minimumStake, strategyParams + ); + vm.stopPrank(); + } + + function _createSlashableStakeOpSet( + uint32 lookAheadPeriod + ) internal { + console.log("Creating a new slashable stake quorum with look ahead period..."); + + // Define parameters for the new quorum + ISlashingRegistryCoordinatorTypes.OperatorSetParam memory operatorSetParam = + ISlashingRegistryCoordinatorTypes.OperatorSetParam({ + maxOperatorCount: 100, + kickBIPsOfOperatorStake: 10500, // 105% + kickBIPsOfTotalStake: 100 // 1% + }); + + IStakeRegistryTypes.StrategyParams[] memory strategyParams = + new IStakeRegistryTypes.StrategyParams[](1); + strategyParams[0] = + IStakeRegistryTypes.StrategyParams({strategy: strategy, multiplier: 1 * 1e18}); + + uint96 minimumStake = uint96(1 ether); + + vm.startPrank(serviceManagerOwner); + registryCoordinator.createSlashableStakeQuorum( + operatorSetParam, minimumStake, strategyParams, lookAheadPeriod + ); + vm.stopPrank(); + } + + function _captureAndStorePreUpgradeState() internal { + preUpgradeStates.registryCoordinator.numQuorums = registryCoordinator.quorumCount(); + + Pausable serviceManagerPausable = Pausable(address(serviceManager)); + preUpgradeStates.serviceManager.paused = serviceManagerPausable.paused(); + + uint8 quorumCount = registryCoordinator.quorumCount(); + preUpgradeStates.blsApkRegistry.currentApkHashes = new bytes32[](quorumCount); + preUpgradeStates.indexRegistry.operatorCounts = new uint32[](quorumCount); + preUpgradeStates.stakeRegistry.numStrategies = new uint32[](quorumCount); + + // For each quorum, gather data from all registries + for (uint8 quorumIndex = 0; quorumIndex < quorumCount; quorumIndex++) { + // Get operator count for each quorum from IndexRegistry + uint32 operatorCount = indexRegistry.totalOperatorsForQuorum(quorumIndex); + preUpgradeStates.indexRegistry.operatorCounts[quorumIndex] = operatorCount; + + // Get APK hash for each quorum from BLSApkRegistry + // Store the hash of the APK as bytes32 + preUpgradeStates.blsApkRegistry.currentApkHashes[quorumIndex] = + BN254.hashG1Point(apkRegistry.getApk(quorumIndex)); + + // Get strategy count for each quorum from StakeRegistry + uint256 strategyCount = 0; + // Check if quorum exists in StakeRegistry before querying + if (stakeRegistry.getTotalStakeHistoryLength(quorumIndex) > 0) { + strategyCount = stakeRegistry.strategyParamsLength(quorumIndex); + } + preUpgradeStates.stakeRegistry.numStrategies[quorumIndex] = uint32(strategyCount); + } + + // Record operators for M2 quorums + _recordM2QuorumOperators(); + } + + function _recordM2QuorumOperators() internal { + // Use the getM2QuorumOperators function to get M2 quorum operators + (uint8[] memory quorumNumbers, address[][] memory operatorLists) = _getM2QuorumOperators(); + + // Set the values in the m2QuorumOperators struct + m2QuorumOperators.quorumNumbers = quorumNumbers; + m2QuorumOperators.operatorIds = operatorLists; + + for (uint8 i = 0; i < quorumNumbers.length; i++) { + console.log( + "Recorded %d operators for quorum %d", operatorLists[i].length, quorumNumbers[i] + ); + } + } + + function _getM2QuorumOperators() + public + view + returns (uint8[] memory m2QuorumNumbers, address[][] memory m2QuorumOperatorLists) + { + uint256 quorumCount = registryCoordinator.quorumCount(); + m2QuorumNumbers = new uint8[](quorumCount); + m2QuorumOperatorLists = new address[][](quorumCount); + + for (uint8 i = 0; i < quorumCount; i++) { + uint32 operatorCount = indexRegistry.totalOperatorsForQuorum(i); + + if (operatorCount > 0) { + // Get the current list of operators for this quorum using external call + bytes32[] memory operatorIds = + indexRegistry.getOperatorListAtBlockNumber(i, uint32(block.number)); + + // Convert bytes32 operatorIds to addresses + address[] memory operatorAddresses = new address[](operatorIds.length); + for (uint256 j = 0; j < operatorIds.length; j++) { + // Use the BLSApkRegistry to get the operator address from the ID + operatorAddresses[j] = apkRegistry.getOperatorFromPubkeyHash(operatorIds[j]); + } + + m2QuorumOperatorLists[i] = operatorAddresses; + m2QuorumNumbers[i] = i; + } + } + + return (m2QuorumNumbers, m2QuorumOperatorLists); + } + + function _configureUAMAppointees() internal { + console.log("Configuring permissions for operator set creation..."); + + console.log("Setting AVS address in Registry Coordinator..."); + vm.startPrank(registryCoordinatorOwner); + registryCoordinator.setAVS(address(serviceManager)); + vm.stopPrank(); + + console.log("Appointee set for createOperatorSets"); + vm.startPrank(serviceManagerOwner); + serviceManager.setAppointee( + address(registryCoordinator), + allocationManagerAddr, + IAllocationManager.createOperatorSets.selector + ); + serviceManager.setAppointee( + serviceManagerOwner, + allocationManagerAddr, + IAllocationManager.updateAVSMetadataURI.selector + ); + + serviceManager.setAppointee( + serviceManagerOwner, allocationManagerAddr, IAllocationManager.setAVSRegistrar.selector + ); + + console.log("Appointees set for required permissions"); + + string memory metadataURI = "https://eigenda.xyz/metadata"; + console.log("Updating AVS metadata URI to:", metadataURI); + allocationManager.updateAVSMetadataURI(address(serviceManager), metadataURI); + + allocationManager.setAVSRegistrar( + address(serviceManager), IAVSRegistrar(address(registryCoordinator)) + ); + vm.stopPrank(); + console.log("AVS Registrar set"); + } + + function _deployNewImplementations() internal { + socketRegistry = address( + new SocketRegistry( + ISlashingRegistryCoordinator(eigenDAData.addresses.registryCoordinator) + ) + ); + + IRegistryCoordinatorTypes.SlashingRegistryParams memory slashingParams = + IRegistryCoordinatorTypes.SlashingRegistryParams({ + stakeRegistry: stakeRegistry, + blsApkRegistry: apkRegistry, + indexRegistry: indexRegistry, + socketRegistry: ISocketRegistry(socketRegistry), + allocationManager: allocationManager, + pauserRegistry: IPauserRegistry(eigenDAData.permissions.pauserRegistry) + }); + + IRegistryCoordinatorTypes.RegistryCoordinatorParams memory params = + IRegistryCoordinatorTypes.RegistryCoordinatorParams({ + serviceManager: serviceManager, + slashingParams: slashingParams + }); + + newRegistryCoordinatorImpl = address(new RegistryCoordinator(params)); + + IRewardsCoordinator rewardsCoordinator = IRewardsCoordinator(rewardsCoordinatorAddr); + + // Assert all addresses are not zero before deployment + assertTrue(permissionControllerAddr != address(0), "PermissionController address not found"); + assertTrue(address(avsDirectory) != address(0), "AVSDirectory address is zero"); + // assertTrue(address(rewardsCoordinator) != address(0), "RewardsCoordinator address is zero"); //TODO: + assertTrue( + address(registryCoordinator) != address(0), "RegistryCoordinator address is zero" + ); + assertTrue(address(stakeRegistry) != address(0), "StakeRegistry address is zero"); + assertTrue( + address(permissionController) != address(0), "PermissionController address is zero" + ); + assertTrue(address(allocationManager) != address(0), "AllocationManager address is zero"); + assertTrue(delegationManagerAddr != address(0), "DelegationManager address is zero"); + assertTrue(address(avsDirectory) != address(0), "AVSDirectory address is zero"); + assertTrue(address(allocationManager) != address(0), "AllocationManager address is zero"); + + newServiceManagerImpl = address( + new TestServiceManager( + avsDirectory, + rewardsCoordinator, + registryCoordinator, + stakeRegistry, + permissionController, + allocationManager + ) + ); + newBlsApkRegistryImpl = address(new BLSApkRegistry(registryCoordinator)); + newIndexRegistryImpl = address(new IndexRegistry(registryCoordinator)); + + newStakeRegistryImpl = address( + new StakeRegistry( + registryCoordinator, + IDelegationManager(address(delegationManager)), + avsDirectory, + allocationManager + ) + ); + } + + function _setupEigenDAFork( + string memory jsonPath + ) internal returns (EigenDAData memory) { + string memory rpcUrl = vm.envString("HOLESKY_RPC_URL"); + + vm.createSelectFork(rpcUrl); + + EigenDAData memory data = _readEigenDADeploymentJson(jsonPath, 17000); + + /// Recent block post ALM upgrade + vm.rollFork(3592349); + + return data; + } + + function _upgradeContracts() internal { + vm.startPrank(eigenDAData.permissions.eigenDAUpgrader); + + UpgradeableProxyLib.upgrade( + eigenDAData.addresses.registryCoordinator, newRegistryCoordinatorImpl + ); + UpgradeableProxyLib.upgrade( + eigenDAData.addresses.eigenDAServiceManager, newServiceManagerImpl + ); + UpgradeableProxyLib.upgrade(eigenDAData.addresses.blsApkRegistry, newBlsApkRegistryImpl); + UpgradeableProxyLib.upgrade(eigenDAData.addresses.indexRegistry, newIndexRegistryImpl); + UpgradeableProxyLib.upgrade(eigenDAData.addresses.stakeRegistry, newStakeRegistryImpl); + + vm.stopPrank(); + } + + function _readEigenDADeploymentJson( + string memory path, + uint256 chainId + ) internal returns (EigenDAData memory) { + string memory filePath = string(abi.encodePacked(path, "/EigenDA_Holesky.json")); + return _loadEigenDAJson(filePath); + } + + function _loadEigenDAJson( + string memory filePath + ) internal returns (EigenDAData memory) { + string memory json = vm.readFile(filePath); + require(vm.exists(filePath), "EigenDA deployment file does not exist"); + + EigenDAData memory data; + + // Parse addresses section + data.addresses.blsApkRegistry = json.readAddress(".addresses.blsApkRegistry"); + data.addresses.eigenDAProxyAdmin = json.readAddress(".addresses.eigenDAProxyAdmin"); + data.addresses.eigenDAServiceManager = json.readAddress(".addresses.eigenDAServiceManager"); + data.addresses.indexRegistry = json.readAddress(".addresses.indexRegistry"); + data.addresses.mockDispatcher = json.readAddress(".addresses.mockRollup"); + data.addresses.operatorStateRetriever = + json.readAddress(".addresses.operatorStateRetriever"); + data.addresses.registryCoordinator = json.readAddress(".addresses.registryCoordinator"); + data.addresses.serviceManagerRouter = json.readAddress(".addresses.serviceManagerRouter"); + data.addresses.stakeRegistry = json.readAddress(".addresses.stakeRegistry"); + + // Parse chainInfo section + data.chainInfo.chainId = json.readUint(".chainInfo.chainId"); + data.chainInfo.deploymentBlock = json.readUint(".chainInfo.deploymentBlock"); + + // Parse permissions section + data.permissions.eigenDABatchConfirmer = + json.readAddress(".permissions.eigenDABatchConfirmer"); + data.permissions.eigenDAChurner = json.readAddress(".permissions.eigenDAChurner"); + data.permissions.eigenDAEjector = json.readAddress(".permissions.eigenDAEjector"); + data.permissions.eigenDAOwner = json.readAddress(".permissions.eigenDAOwner"); + data.permissions.eigenDAUpgrader = json.readAddress(".permissions.eigenDAUpgrader"); + data.permissions.pauserRegistry = json.readAddress(".permissions.pauserRegistry"); + + // Label all addresses for better debugging and tracing + vm.label(data.addresses.blsApkRegistry, "BLSApkRegistry"); + vm.label(data.addresses.eigenDAProxyAdmin, "EigenDAProxyAdmin"); + vm.label(data.addresses.eigenDAServiceManager, "EigenDAServiceManager"); + vm.label(data.addresses.indexRegistry, "IndexRegistry"); + vm.label(data.addresses.mockDispatcher, "MockDispatcher"); + vm.label(data.addresses.operatorStateRetriever, "OperatorStateRetriever"); + vm.label(data.addresses.registryCoordinator, "RegistryCoordinator"); + vm.label(data.addresses.serviceManagerRouter, "ServiceManagerRouter"); + vm.label(data.addresses.stakeRegistry, "StakeRegistry"); + + // Label permissioned addresses + vm.label(data.permissions.eigenDABatchConfirmer, "EigenDABatchConfirmer"); + vm.label(data.permissions.eigenDAChurner, "EigenDAChurner"); + vm.label(data.permissions.eigenDAEjector, "EigenDAEjector"); + vm.label(data.permissions.eigenDAOwner, "EigenDAOwner"); + vm.label(data.permissions.eigenDAUpgrader, "EigenDAUpgrader"); + vm.label(data.permissions.pauserRegistry, "PauserRegistry"); + + return data; + } + + function _verifyInitialSetup() internal view { + // Verify that contracts are deployed and at least not null + require( + eigenDAData.addresses.registryCoordinator != address(0), + "Registry Coordinator should be deployed" + ); + require( + eigenDAData.addresses.eigenDAServiceManager != address(0), + "Service Manager should be deployed" + ); + require( + eigenDAData.addresses.blsApkRegistry != address(0), + "BLS APK Registry should be deployed" + ); + require( + eigenDAData.addresses.indexRegistry != address(0), "Index Registry should be deployed" + ); + require( + eigenDAData.addresses.stakeRegistry != address(0), "Stake Registry should be deployed" + ); + + require( + eigenDAData.permissions.eigenDAUpgrader != address(0), + "EigenDA Upgrader should be defined" + ); + require( + eigenDAData.permissions.pauserRegistry != address(0), + "Pauser Registry should be defined" + ); + } + + function _createTokenAndStrategy() internal returns (address token, IStrategy strategy) { + ERC20Mock tokenContract = new ERC20Mock(); + token = address(tokenContract); + strategy = IStrategyFactory(strategyFactory).deployNewStrategy(IERC20(token)); + } + + function _setUpTokensForExistingQuorums( + uint256 amount + ) internal { + uint8 quorumNumber = 0; + uint8 strategyIndex = 2; + /// Strategy with a token we can deal with foundry + IStakeRegistry.StrategyParams memory stratParams = + stakeRegistry.strategyParamsByIndex(quorumNumber, strategyIndex); + + address strategyAddress = address(stratParams.strategy); + + console.log("Using strategy %s for quorum %d", strategyAddress, quorumNumber); + + // For each operator, deposit tokens into each strategy + for (uint256 opIndex = 0; opIndex < OPERATOR_COUNT; opIndex++) { + // Get the underlying token for this strategy + IERC20 underlyingTokenIERC20 = IStrategy(strategyAddress).underlyingToken(); + address tokenAddress = address(underlyingTokenIERC20); + + deal(tokenAddress, operators[opIndex].key.addr, amount, true); + + vm.startPrank(operators[opIndex].key.addr); + // Deposit tokens into the strategy + OperatorLib.depositTokenIntoStrategy( + operators[opIndex], address(strategyManager), strategyAddress, tokenAddress, amount + ); + + console.log( + "Deposited %d tokens into strategy %s for operator %s", + amount, + strategyAddress, + operators[opIndex].key.addr + ); + + vm.stopPrank(); + } + } + + function _setupTokensForOperators( + uint256 amount + ) internal returns (address token, IStrategy strategy) { + (token, strategy) = _createTokenAndStrategy(); + + for (uint256 i = 0; i < OPERATOR_COUNT; i++) { + OperatorLib.mintMockTokens(operators[i], token, amount); + vm.startPrank(operators[i].key.addr); + OperatorLib.depositTokenIntoStrategy( + operators[i], address(strategyManager), address(strategy), token, amount + ); + vm.stopPrank(); + } + } + + function _createOperators() internal { + for (uint256 i = 0; i < OPERATOR_COUNT; i++) { + operators[i] = OperatorLib.createOperator(string(abi.encodePacked("operator-", i + 1))); + } + } + + function _createOperators( + uint256 numOperators + ) internal returns (OperatorLib.Operator[] memory) { + OperatorLib.Operator[] memory ops = new OperatorLib.Operator[](numOperators); + for (uint256 i = 0; i < numOperators; i++) { + ops[i] = OperatorLib.createOperator(string(abi.encodePacked("operator-", i + 1))); + } + return ops; + } + + /** + * @dev Registers operators as EigenLayer operators + */ + function _registerOperatorsAsEigenLayerOperators() internal { + for (uint256 i = 0; i < OPERATOR_COUNT; i++) { + vm.startPrank(operators[i].key.addr); + OperatorLib.registerAsOperator(operators[i], delegationManagerAddr); + vm.stopPrank(); + } + } + + /** + * @dev Registers operators as EigenLayer operators + * @param operatorsToRegister Array of operators to register + */ + function _registerOperatorsAsEigenLayerOperators( + OperatorLib.Operator[] memory operatorsToRegister + ) internal { + for (uint256 i = 0; i < operatorsToRegister.length; i++) { + vm.startPrank(operatorsToRegister[i].key.addr); + OperatorLib.registerAsOperator(operatorsToRegister[i], delegationManagerAddr); + vm.stopPrank(); + } + } + + /** + * @dev Gets and sorts operator addresses for use in quorum updates + * @return Sorted two-dimensional array of operator addresses + */ + function _getAndSortOperators() internal view returns (address[][] memory) { + address[][] memory registeredOperators = new address[][](1); + registeredOperators[0] = new address[](OPERATOR_COUNT); + for (uint256 i = 0; i < OPERATOR_COUNT; i++) { + registeredOperators[0][i] = operators[i].key.addr; + } + + // Sort operator addresses + for (uint256 i = 0; i < registeredOperators[0].length - 1; i++) { + for (uint256 j = 0; j < registeredOperators[0].length - i - 1; j++) { + if (registeredOperators[0][j] > registeredOperators[0][j + 1]) { + address temp = registeredOperators[0][j]; + registeredOperators[0][j] = registeredOperators[0][j + 1]; + registeredOperators[0][j + 1] = temp; + } + } + } + + return registeredOperators; + } +} diff --git a/test/unit/BLSSigCheckOperatorStateRetriever.t.sol b/test/unit/BLSSigCheckOperatorStateRetriever.t.sol new file mode 100644 index 00000000..16b30413 --- /dev/null +++ b/test/unit/BLSSigCheckOperatorStateRetriever.t.sol @@ -0,0 +1,1341 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "../utils/MockAVSDeployer.sol"; +import {IStakeRegistryErrors} from "../../src/interfaces/IStakeRegistry.sol"; +import {ISlashingRegistryCoordinatorTypes} from "../../src/interfaces/IRegistryCoordinator.sol"; +import {IBLSSignatureCheckerTypes} from "../../src/interfaces/IBLSSignatureChecker.sol"; +import {BN256G2} from "../../src/unaudited/BN256G2.sol"; +import {BLSSigCheckOperatorStateRetriever} from + "../../src/unaudited/BLSSigCheckOperatorStateRetriever.sol"; + +contract BLSSigCheckOperatorStateRetrieverUnitTests is MockAVSDeployer { + using BN254 for BN254.G1Point; + + BLSSigCheckOperatorStateRetriever sigCheckOperatorStateRetriever; + + function setUp() public virtual { + numQuorums = 8; + _deployMockEigenLayerAndAVS(numQuorums); + sigCheckOperatorStateRetriever = new BLSSigCheckOperatorStateRetriever(); + } + + function test_getOperatorState_revert_neverRegistered() public { + cheats.expectRevert( + "RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId" + ); + sigCheckOperatorStateRetriever.getOperatorState( + registryCoordinator, defaultOperatorId, uint32(block.number) + ); + } + + function test_getOperatorState_revert_registeredFirstAfterReferenceBlockNumber() public { + cheats.roll(registrationBlockNumber); + _registerOperatorWithCoordinator(defaultOperator, 1, defaultPubKey); + + // should revert because the operator was registered for the first time after the reference block number + cheats.expectRevert( + "RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId" + ); + sigCheckOperatorStateRetriever.getOperatorState( + registryCoordinator, defaultOperatorId, registrationBlockNumber - 1 + ); + } + + function test_getOperatorState_deregisteredBeforeReferenceBlockNumber() public { + uint256 quorumBitmap = 1; + cheats.roll(registrationBlockNumber); + _registerOperatorWithCoordinator(defaultOperator, quorumBitmap, defaultPubKey); + + cheats.roll(registrationBlockNumber + 10); + cheats.prank(defaultOperator); + registryCoordinator.deregisterOperator(BitmapUtils.bitmapToBytesArray(quorumBitmap)); + + ( + uint256 fetchedQuorumBitmap, + BLSSigCheckOperatorStateRetriever.Operator[][] memory operators + ) = sigCheckOperatorStateRetriever.getOperatorState( + registryCoordinator, defaultOperatorId, uint32(block.number) + ); + assertEq(fetchedQuorumBitmap, 0); + assertEq(operators.length, 0); + } + + function test_getOperatorState_registeredAtReferenceBlockNumber() public { + uint256 quorumBitmap = 1; + cheats.roll(registrationBlockNumber); + _registerOperatorWithCoordinator(defaultOperator, quorumBitmap, defaultPubKey); + + ( + uint256 fetchedQuorumBitmap, + BLSSigCheckOperatorStateRetriever.Operator[][] memory operators + ) = sigCheckOperatorStateRetriever.getOperatorState( + registryCoordinator, defaultOperatorId, uint32(block.number) + ); + assertEq(fetchedQuorumBitmap, 1); + assertEq(operators.length, 1); + assertEq(operators[0].length, 1); + assertEq(operators[0][0].operator, defaultOperator); + assertEq(operators[0][0].operatorId, defaultOperatorId); + assertEq(operators[0][0].stake, defaultStake); + } + + function test_getOperatorState_revert_quorumNotCreatedAtCallTime() public { + cheats.expectRevert( + "IndexRegistry._operatorCountAtBlockNumber: quorum did not exist at given block number" + ); + sigCheckOperatorStateRetriever.getOperatorState( + registryCoordinator, + BitmapUtils.bitmapToBytesArray(1 << numQuorums), + uint32(block.number) + ); + } + + function test_getOperatorState_revert_quorumNotCreatedAtReferenceBlockNumber() public { + cheats.roll(registrationBlockNumber); + ISlashingRegistryCoordinatorTypes.OperatorSetParam memory operatorSetParams = + ISlashingRegistryCoordinatorTypes.OperatorSetParam({ + maxOperatorCount: defaultMaxOperatorCount, + kickBIPsOfOperatorStake: defaultKickBIPsOfOperatorStake, + kickBIPsOfTotalStake: defaultKickBIPsOfTotalStake + }); + uint96 minimumStake = 1; + IStakeRegistryTypes.StrategyParams[] memory strategyParams = + new IStakeRegistryTypes.StrategyParams[](1); + strategyParams[0] = IStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(1000)), + multiplier: 1e16 + }); + + cheats.prank(registryCoordinator.owner()); + registryCoordinator.createTotalDelegatedStakeQuorum( + operatorSetParams, minimumStake, strategyParams + ); + + cheats.expectRevert( + "IndexRegistry._operatorCountAtBlockNumber: quorum did not exist at given block number" + ); + sigCheckOperatorStateRetriever.getOperatorState( + registryCoordinator, + BitmapUtils.bitmapToBytesArray(1 << numQuorums), + uint32(registrationBlockNumber - 1) + ); + } + + function test_getOperatorState_returnsCorrect() public { + uint256 quorumBitmapOne = 1; + uint256 quorumBitmapThree = 3; + cheats.roll(registrationBlockNumber); + _registerOperatorWithCoordinator(defaultOperator, quorumBitmapOne, defaultPubKey); + + address otherOperator = _incrementAddress(defaultOperator, 1); + BN254.G1Point memory otherPubKey = BN254.G1Point(1, 2); + bytes32 otherOperatorId = BN254.hashG1Point(otherPubKey); + _registerOperatorWithCoordinator( + otherOperator, quorumBitmapThree, otherPubKey, defaultStake - 1 + ); + + BLSSigCheckOperatorStateRetriever.Operator[][] memory operators = + sigCheckOperatorStateRetriever.getOperatorState( + registryCoordinator, + BitmapUtils.bitmapToBytesArray(quorumBitmapThree), + uint32(block.number) + ); + assertEq(operators.length, 2); + assertEq(operators[0].length, 2); + assertEq(operators[1].length, 1); + assertEq(operators[0][0].operator, defaultOperator); + assertEq(operators[0][0].operatorId, defaultOperatorId); + assertEq(operators[0][0].stake, defaultStake); + assertEq(operators[0][1].operator, otherOperator); + assertEq(operators[0][1].operatorId, otherOperatorId); + assertEq(operators[0][1].stake, defaultStake - 1); + assertEq(operators[1][0].operator, otherOperator); + assertEq(operators[1][0].operatorId, otherOperatorId); + assertEq(operators[1][0].stake, defaultStake - 1); + } + + function test_getCheckSignaturesIndices_revert_neverRegistered() public { + bytes32[] memory nonSignerOperatorIds = new bytes32[](1); + nonSignerOperatorIds[0] = defaultOperatorId; + + cheats.expectRevert( + "RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId" + ); + sigCheckOperatorStateRetriever.getCheckSignaturesIndices( + registryCoordinator, + uint32(block.number), + BitmapUtils.bitmapToBytesArray(1), + nonSignerOperatorIds + ); + } + + function test_getCheckSignaturesIndices_revert_registeredFirstAfterReferenceBlockNumber() + public + { + bytes32[] memory nonSignerOperatorIds = new bytes32[](1); + nonSignerOperatorIds[0] = defaultOperatorId; + + cheats.roll(registrationBlockNumber); + _registerOperatorWithCoordinator(defaultOperator, 1, defaultPubKey); + + // should revert because the operator was registered for the first time after the reference block number + cheats.expectRevert( + "RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId" + ); + sigCheckOperatorStateRetriever.getCheckSignaturesIndices( + registryCoordinator, + registrationBlockNumber - 1, + BitmapUtils.bitmapToBytesArray(1), + nonSignerOperatorIds + ); + } + + function test_getCheckSignaturesIndices_revert_deregisteredAtReferenceBlockNumber() public { + bytes32[] memory nonSignerOperatorIds = new bytes32[](1); + nonSignerOperatorIds[0] = defaultOperatorId; + + cheats.roll(registrationBlockNumber); + _registerOperatorWithCoordinator(defaultOperator, 1, defaultPubKey); + + cheats.roll(registrationBlockNumber + 10); + cheats.prank(defaultOperator); + registryCoordinator.deregisterOperator(BitmapUtils.bitmapToBytesArray(1)); + + // should revert because the operator was registered for the first time after the reference block number + cheats.expectRevert(BLSSigCheckOperatorStateRetriever.OperatorNotRegistered.selector); + sigCheckOperatorStateRetriever.getCheckSignaturesIndices( + registryCoordinator, + uint32(block.number), + BitmapUtils.bitmapToBytesArray(1), + nonSignerOperatorIds + ); + } + + function test_getCheckSignaturesIndices_revert_quorumNotCreatedAtCallTime() public { + bytes32[] memory nonSignerOperatorIds = new bytes32[](1); + nonSignerOperatorIds[0] = defaultOperatorId; + + _registerOperatorWithCoordinator(defaultOperator, 1, defaultPubKey); + + cheats.expectRevert(IStakeRegistryErrors.QuorumDoesNotExist.selector); + sigCheckOperatorStateRetriever.getCheckSignaturesIndices( + registryCoordinator, + uint32(block.number), + BitmapUtils.bitmapToBytesArray(1 << numQuorums), + nonSignerOperatorIds + ); + } + + function test_getCheckSignaturesIndices_revert_quorumNotCreatedAtReferenceBlockNumber() + public + { + cheats.roll(registrationBlockNumber); + _registerOperatorWithCoordinator(defaultOperator, 1, defaultPubKey); + + cheats.roll(registrationBlockNumber + 10); + bytes32[] memory nonSignerOperatorIds = new bytes32[](1); + nonSignerOperatorIds[0] = defaultOperatorId; + + ISlashingRegistryCoordinatorTypes.OperatorSetParam memory operatorSetParams = + ISlashingRegistryCoordinatorTypes.OperatorSetParam({ + maxOperatorCount: defaultMaxOperatorCount, + kickBIPsOfOperatorStake: defaultKickBIPsOfOperatorStake, + kickBIPsOfTotalStake: defaultKickBIPsOfTotalStake + }); + uint96 minimumStake = 1; + IStakeRegistryTypes.StrategyParams[] memory strategyParams = + new IStakeRegistryTypes.StrategyParams[](1); + strategyParams[0] = IStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(1000)), + multiplier: 1e16 + }); + + cheats.prank(registryCoordinator.owner()); + registryCoordinator.createTotalDelegatedStakeQuorum( + operatorSetParams, minimumStake, strategyParams + ); + + cheats.expectRevert(IStakeRegistryErrors.EmptyStakeHistory.selector); + sigCheckOperatorStateRetriever.getCheckSignaturesIndices( + registryCoordinator, + registrationBlockNumber + 5, + BitmapUtils.bitmapToBytesArray(1 << numQuorums), + nonSignerOperatorIds + ); + } + + function test_getCheckSignaturesIndices_returnsCorrect() public { + uint256 quorumBitmapOne = 1; + uint256 quorumBitmapTwo = 2; + uint256 quorumBitmapThree = 3; + + assertFalse( + registryCoordinator.operatorSetsEnabled(), "operatorSetsEnabled should be false" + ); + + cheats.roll(registrationBlockNumber); + _registerOperatorWithCoordinator(defaultOperator, quorumBitmapOne, defaultPubKey); + + cheats.roll(registrationBlockNumber + 10); + address otherOperator = _incrementAddress(defaultOperator, 1); + BN254.G1Point memory otherPubKey = BN254.G1Point(1, 2); + bytes32 otherOperatorId = BN254.hashG1Point(otherPubKey); + _registerOperatorWithCoordinator( + otherOperator, quorumBitmapThree, otherPubKey, defaultStake - 1 + ); + + cheats.roll(registrationBlockNumber + 15); + cheats.prank(defaultOperator); + registryCoordinator.deregisterOperator(BitmapUtils.bitmapToBytesArray(quorumBitmapOne)); + + cheats.roll(registrationBlockNumber + 20); + _registerOperatorWithCoordinator(defaultOperator, quorumBitmapTwo, defaultPubKey); + + cheats.roll(registrationBlockNumber + 25); + cheats.prank(otherOperator); + registryCoordinator.deregisterOperator(BitmapUtils.bitmapToBytesArray(quorumBitmapTwo)); + + cheats.roll(registrationBlockNumber + 30); + _registerOperatorWithCoordinator( + otherOperator, quorumBitmapTwo, otherPubKey, defaultStake - 2 + ); + + bytes32[] memory nonSignerOperatorIds = new bytes32[](2); + nonSignerOperatorIds[0] = defaultOperatorId; + nonSignerOperatorIds[1] = otherOperatorId; + + BLSSigCheckOperatorStateRetriever.CheckSignaturesIndices memory checkSignaturesIndices = + sigCheckOperatorStateRetriever.getCheckSignaturesIndices( + registryCoordinator, + uint32(block.number), + BitmapUtils.bitmapToBytesArray(quorumBitmapThree), + nonSignerOperatorIds + ); + // we're querying for 2 operators, so there should be 2 nonSignerQuorumBitmapIndices + assertEq(checkSignaturesIndices.nonSignerQuorumBitmapIndices.length, 2); + // the first operator (0) registered for quorum 1, (1) deregistered from quorum 1, and (2) registered for quorum 2 + assertEq(checkSignaturesIndices.nonSignerQuorumBitmapIndices[0], 2); + // the second operator (0) registered for quorum 1 and 2 (1) deregistered from quorum 2, and (2) registered for quorum 2 + assertEq(checkSignaturesIndices.nonSignerQuorumBitmapIndices[1], 2); + // the operators, together, serve 2 quorums so there should be 2 quorumApkIndices + assertEq(checkSignaturesIndices.quorumApkIndices.length, 2); + // quorum 1 (0) was initialized, (1) the first operator registered, (2) the second operator registered, and (3) the first operator deregistered + assertEq(checkSignaturesIndices.quorumApkIndices[0], 3); + // quorum 2 (0) was initialized, (1) the second operator registered, (2) the first operator registered, (3) the second operator deregistered, and (4) the second operator registered + assertEq(checkSignaturesIndices.quorumApkIndices[1], 4); + // the operators, together, serve 2 quorums so there should be 2 totalStakeIndices + assertEq(checkSignaturesIndices.totalStakeIndices.length, 2); + // quorum 1 (0) was initialized, (1) the first operator registered, (2) the second operator registered, and (3) the first operator deregistered + assertEq(checkSignaturesIndices.totalStakeIndices[0], 3); + // quorum 2 (0) was initialized, (1) the second operator registered, (2) the first operator registered, (3) the second operator deregistered, and (4) the second operator registered + assertEq(checkSignaturesIndices.totalStakeIndices[1], 4); + // the operators, together, serve 2 quorums so there should be 2 nonSignerStakeIndices + assertEq(checkSignaturesIndices.nonSignerStakeIndices.length, 2); + // quorum 1 only has the second operator registered, so there should be 1 nonSignerStakeIndices + assertEq(checkSignaturesIndices.nonSignerStakeIndices[0].length, 1); + // the second operator has (0) registered for quorum 1 + assertEq(checkSignaturesIndices.nonSignerStakeIndices[0][0], 0); + // quorum 2 has both operators registered, so there should be 2 nonSignerStakeIndices + assertEq(checkSignaturesIndices.nonSignerStakeIndices[1].length, 2); + // the first operator has (0) registered for quorum 1 + assertEq(checkSignaturesIndices.nonSignerStakeIndices[1][0], 0); + // the second operator has (0) registered for quorum 2, (1) deregistered from quorum 2, and (2) registered for quorum 2 + assertEq(checkSignaturesIndices.nonSignerStakeIndices[1][1], 2); + + nonSignerOperatorIds = new bytes32[](1); + nonSignerOperatorIds[0] = otherOperatorId; + // taking only the deregistration into account + checkSignaturesIndices = sigCheckOperatorStateRetriever.getCheckSignaturesIndices( + registryCoordinator, + registrationBlockNumber + 15, + BitmapUtils.bitmapToBytesArray(quorumBitmapThree), + nonSignerOperatorIds + ); + // we're querying for 1 operator, so there should be 1 nonSignerQuorumBitmapIndices + assertEq(checkSignaturesIndices.nonSignerQuorumBitmapIndices.length, 1); + // the second operator (0) registered for quorum 1 and 2 + assertEq(checkSignaturesIndices.nonSignerQuorumBitmapIndices[0], 0); + // at the time, the operator served 2 quorums so there should be 2 quorumApkIndices + assertEq(checkSignaturesIndices.quorumApkIndices.length, 2); + // at the time, quorum 1 (0) was initialized, (1) the first operator registered, (2) the second operator registered, and (3) the first operator deregistered + assertEq(checkSignaturesIndices.quorumApkIndices[0], 3); + // at the time, quorum 2 (0) was initialized, (1) the second operator registered + assertEq(checkSignaturesIndices.quorumApkIndices[1], 1); + // at the time, the operator served 2 quorums so there should be 2 totalStakeIndices + assertEq(checkSignaturesIndices.totalStakeIndices.length, 2); + // at the time, quorum 1 (0) was initialized, (1) the first operator registered, (2) the second operator registered, and (3) the first operator deregistered + assertEq(checkSignaturesIndices.totalStakeIndices[0], 3); + // at the time, quorum 2 (0) was initialized, (1) the second operator registered + assertEq(checkSignaturesIndices.totalStakeIndices[1], 1); + // at the time, the operator served 2 quorums so there should be 2 nonSignerStakeIndices + assertEq(checkSignaturesIndices.nonSignerStakeIndices.length, 2); + // quorum 1 only has the second operator registered, so there should be 1 nonSignerStakeIndices + assertEq(checkSignaturesIndices.nonSignerStakeIndices[0].length, 1); + // the second operator has (0) registered for quorum 1 + assertEq(checkSignaturesIndices.nonSignerStakeIndices[0][0], 0); + // quorum 2 only has the second operator registered, so there should be 1 nonSignerStakeIndices + assertEq(checkSignaturesIndices.nonSignerStakeIndices[1].length, 1); + // the second operator has (0) registered for quorum 2 + assertEq(checkSignaturesIndices.nonSignerStakeIndices[1][0], 0); + } + + function testGetOperatorState_Valid( + uint256 pseudoRandomNumber + ) public { + // register random operators and get the expected indices within the quorums and the metadata for the operators + ( + OperatorMetadata[] memory operatorMetadatas, + uint256[][] memory expectedOperatorOverallIndices + ) = _registerRandomOperators(pseudoRandomNumber); + + for (uint256 i = 0; i < operatorMetadatas.length; i++) { + uint32 blockNumber = uint32(registrationBlockNumber + blocksBetweenRegistrations * i); + + uint256 gasBefore = gasleft(); + // retrieve the ordered list of operators for each quorum along with their id and stake + (uint256 quorumBitmap, BLSSigCheckOperatorStateRetriever.Operator[][] memory operators) + = sigCheckOperatorStateRetriever.getOperatorState( + registryCoordinator, operatorMetadatas[i].operatorId, blockNumber + ); + uint256 gasAfter = gasleft(); + emit log_named_uint("gasUsed", gasBefore - gasAfter); + + assertEq(operatorMetadatas[i].quorumBitmap, quorumBitmap); + bytes memory quorumNumbers = BitmapUtils.bitmapToBytesArray(quorumBitmap); + + // assert that the operators returned are the expected ones + _assertExpectedOperators( + quorumNumbers, operators, expectedOperatorOverallIndices, operatorMetadatas + ); + } + + // choose a random operator to deregister + uint256 operatorIndexToDeregister = pseudoRandomNumber % maxOperatorsToRegister; + bytes memory quorumNumbersToDeregister = BitmapUtils.bitmapToBytesArray( + operatorMetadatas[operatorIndexToDeregister].quorumBitmap + ); + + uint32 deregistrationBlockNumber = registrationBlockNumber + + blocksBetweenRegistrations * (uint32(operatorMetadatas.length) + 1); + cheats.roll(deregistrationBlockNumber); + + cheats.prank(_incrementAddress(defaultOperator, operatorIndexToDeregister)); + registryCoordinator.deregisterOperator(quorumNumbersToDeregister); + // modify expectedOperatorOverallIndices by moving th operatorIdsToSwap to the index where the operatorIndexToDeregister was + for (uint256 i = 0; i < quorumNumbersToDeregister.length; i++) { + uint8 quorumNumber = uint8(quorumNumbersToDeregister[i]); + // loop through indices till we find operatorIndexToDeregister, then move that last operator into that index + for (uint256 j = 0; j < expectedOperatorOverallIndices[quorumNumber].length; j++) { + if (expectedOperatorOverallIndices[quorumNumber][j] == operatorIndexToDeregister) { + expectedOperatorOverallIndices[quorumNumber][j] = expectedOperatorOverallIndices[quorumNumber][expectedOperatorOverallIndices[quorumNumber] + .length - 1]; + break; + } + } + } + + // make sure the state retriever returns the expected state after deregistration + bytes memory allQuorumNumbers = new bytes(maxQuorumsToRegisterFor); + for (uint8 i = 0; i < allQuorumNumbers.length; i++) { + allQuorumNumbers[i] = bytes1(i); + } + + _assertExpectedOperators( + allQuorumNumbers, + sigCheckOperatorStateRetriever.getOperatorState( + registryCoordinator, allQuorumNumbers, deregistrationBlockNumber + ), + expectedOperatorOverallIndices, + operatorMetadatas + ); + } + + function testCheckSignaturesIndices_NoNonSigners_Valid( + uint256 pseudoRandomNumber + ) public { + ( + OperatorMetadata[] memory operatorMetadatas, + uint256[][] memory expectedOperatorOverallIndices + ) = _registerRandomOperators(pseudoRandomNumber); + + uint32 cumulativeBlockNumber = + registrationBlockNumber + blocksBetweenRegistrations * uint32(operatorMetadatas.length); + + // get the quorum bitmap for which there is at least 1 operator + uint256 allInclusiveQuorumBitmap = 0; + for (uint8 i = 0; i < operatorMetadatas.length; i++) { + allInclusiveQuorumBitmap |= operatorMetadatas[i].quorumBitmap; + } + + bytes memory allInclusiveQuorumNumbers = + BitmapUtils.bitmapToBytesArray(allInclusiveQuorumBitmap); + + bytes32[] memory nonSignerOperatorIds = new bytes32[](0); + + BLSSigCheckOperatorStateRetriever.CheckSignaturesIndices memory checkSignaturesIndices = + sigCheckOperatorStateRetriever.getCheckSignaturesIndices( + registryCoordinator, + cumulativeBlockNumber, + allInclusiveQuorumNumbers, + nonSignerOperatorIds + ); + + assertEq( + checkSignaturesIndices.nonSignerQuorumBitmapIndices.length, + 0, + "nonSignerQuorumBitmapIndices should be empty if no nonsigners" + ); + assertEq( + checkSignaturesIndices.quorumApkIndices.length, + allInclusiveQuorumNumbers.length, + "quorumApkIndices should be the number of quorums queried for" + ); + assertEq( + checkSignaturesIndices.totalStakeIndices.length, + allInclusiveQuorumNumbers.length, + "totalStakeIndices should be the number of quorums queried for" + ); + assertEq( + checkSignaturesIndices.nonSignerStakeIndices.length, + allInclusiveQuorumNumbers.length, + "nonSignerStakeIndices should be the number of quorums queried for" + ); + + // assert the indices are the number of registered operators for the quorum minus 1 + for (uint8 i = 0; i < allInclusiveQuorumNumbers.length; i++) { + uint8 quorumNumber = uint8(allInclusiveQuorumNumbers[i]); + assertEq( + checkSignaturesIndices.quorumApkIndices[i], + expectedOperatorOverallIndices[quorumNumber].length, + "quorumApkIndex should be the number of registered operators for the quorum" + ); + assertEq( + checkSignaturesIndices.totalStakeIndices[i], + expectedOperatorOverallIndices[quorumNumber].length, + "totalStakeIndex should be the number of registered operators for the quorum" + ); + } + } + + function testCheckSignaturesIndices_FewNonSigners_Valid( + uint256 pseudoRandomNumber + ) public { + ( + OperatorMetadata[] memory operatorMetadatas, + uint256[][] memory expectedOperatorOverallIndices + ) = _registerRandomOperators(pseudoRandomNumber); + + uint32 cumulativeBlockNumber = + registrationBlockNumber + blocksBetweenRegistrations * uint32(operatorMetadatas.length); + + // get the quorum bitmap for which there is at least 1 operator + uint256 allInclusiveQuorumBitmap = 0; + for (uint8 i = 0; i < operatorMetadatas.length; i++) { + allInclusiveQuorumBitmap |= operatorMetadatas[i].quorumBitmap; + } + + bytes memory allInclusiveQuorumNumbers = + BitmapUtils.bitmapToBytesArray(allInclusiveQuorumBitmap); + + bytes32[] memory nonSignerOperatorIds = + new bytes32[](pseudoRandomNumber % (operatorMetadatas.length - 1) + 1); + uint256 randomIndex = uint256( + keccak256(abi.encodePacked("nonSignerOperatorIds", pseudoRandomNumber)) + ) % operatorMetadatas.length; + for (uint256 i = 0; i < nonSignerOperatorIds.length; i++) { + nonSignerOperatorIds[i] = + operatorMetadatas[(randomIndex + i) % operatorMetadatas.length].operatorId; + } + + BLSSigCheckOperatorStateRetriever.CheckSignaturesIndices memory checkSignaturesIndices = + sigCheckOperatorStateRetriever.getCheckSignaturesIndices( + registryCoordinator, + cumulativeBlockNumber, + allInclusiveQuorumNumbers, + nonSignerOperatorIds + ); + + assertEq( + checkSignaturesIndices.nonSignerQuorumBitmapIndices.length, + nonSignerOperatorIds.length, + "nonSignerQuorumBitmapIndices should be the number of nonsigners" + ); + assertEq( + checkSignaturesIndices.quorumApkIndices.length, + allInclusiveQuorumNumbers.length, + "quorumApkIndices should be the number of quorums queried for" + ); + assertEq( + checkSignaturesIndices.totalStakeIndices.length, + allInclusiveQuorumNumbers.length, + "totalStakeIndices should be the number of quorums queried for" + ); + assertEq( + checkSignaturesIndices.nonSignerStakeIndices.length, + allInclusiveQuorumNumbers.length, + "nonSignerStakeIndices should be the number of quorums queried for" + ); + + // assert the indices are the number of registered operators for the quorum minus 1 + for (uint8 i = 0; i < allInclusiveQuorumNumbers.length; i++) { + uint8 quorumNumber = uint8(allInclusiveQuorumNumbers[i]); + assertEq( + checkSignaturesIndices.quorumApkIndices[i], + expectedOperatorOverallIndices[quorumNumber].length, + "quorumApkIndex should be the number of registered operators for the quorum" + ); + assertEq( + checkSignaturesIndices.totalStakeIndices[i], + expectedOperatorOverallIndices[quorumNumber].length, + "totalStakeIndex should be the number of registered operators for the quorum" + ); + } + + // assert the quorum bitmap and stake indices are zero because there have been no kicks or stake updates + for (uint256 i = 0; i < nonSignerOperatorIds.length; i++) { + assertEq( + checkSignaturesIndices.nonSignerQuorumBitmapIndices[i], + 0, + "nonSignerQuorumBitmapIndices should be zero because there have been no kicks" + ); + } + for (uint256 i = 0; i < checkSignaturesIndices.nonSignerStakeIndices.length; i++) { + for (uint256 j = 0; j < checkSignaturesIndices.nonSignerStakeIndices[i].length; j++) { + assertEq( + checkSignaturesIndices.nonSignerStakeIndices[i][j], + 0, + "nonSignerStakeIndices should be zero because there have been no stake updates past the first one" + ); + } + } + } + + function test_getQuorumBitmapsAtBlockNumber_returnsCorrect() public { + uint256 quorumBitmapOne = 1; + uint256 quorumBitmapThree = 3; + cheats.roll(registrationBlockNumber); + _registerOperatorWithCoordinator(defaultOperator, quorumBitmapOne, defaultPubKey); + + address otherOperator = _incrementAddress(defaultOperator, 1); + BN254.G1Point memory otherPubKey = BN254.G1Point(1, 2); + bytes32 otherOperatorId = BN254.hashG1Point(otherPubKey); + _registerOperatorWithCoordinator( + otherOperator, quorumBitmapThree, otherPubKey, defaultStake - 1 + ); + + bytes32[] memory operatorIds = new bytes32[](2); + operatorIds[0] = defaultOperatorId; + operatorIds[1] = otherOperatorId; + uint256[] memory quorumBitmaps = sigCheckOperatorStateRetriever + .getQuorumBitmapsAtBlockNumber(registryCoordinator, operatorIds, uint32(block.number)); + + assertEq(quorumBitmaps.length, 2); + assertEq(quorumBitmaps[0], quorumBitmapOne); + assertEq(quorumBitmaps[1], quorumBitmapThree); + } + + function _assertExpectedOperators( + bytes memory quorumNumbers, + BLSSigCheckOperatorStateRetriever.Operator[][] memory operators, + uint256[][] memory expectedOperatorOverallIndices, + OperatorMetadata[] memory operatorMetadatas + ) internal { + // for each quorum + for (uint256 j = 0; j < quorumNumbers.length; j++) { + // make sure the each operator id and stake is correct + for (uint256 k = 0; k < operators[j].length; k++) { + uint8 quorumNumber = uint8(quorumNumbers[j]); + assertEq( + operators[j][k].operatorId, + operatorMetadatas[expectedOperatorOverallIndices[quorumNumber][k]].operatorId + ); + // using assertApprox to account for rounding errors + assertApproxEqAbs( + operators[j][k].stake, + operatorMetadatas[expectedOperatorOverallIndices[quorumNumber][k]].stakes[quorumNumber], + 1 + ); + } + } + } + + function test_getBatchOperatorId_emptyArray() public { + address[] memory operators = new address[](0); + bytes32[] memory operatorIds = + sigCheckOperatorStateRetriever.getBatchOperatorId(registryCoordinator, operators); + assertEq(operatorIds.length, 0, "Should return empty array for empty input"); + } + + function test_getBatchOperatorId_unregisteredOperators() public { + address[] memory operators = new address[](2); + operators[0] = address(1); + operators[1] = address(2); + + bytes32[] memory operatorIds = + sigCheckOperatorStateRetriever.getBatchOperatorId(registryCoordinator, operators); + + assertEq(operatorIds.length, 2, "Should return array of same length as input"); + assertEq(operatorIds[0], bytes32(0), "Unregistered operator should return 0"); + assertEq(operatorIds[1], bytes32(0), "Unregistered operator should return 0"); + } + + function test_getBatchOperatorId_mixedRegistration() public { + // Register one operator + cheats.roll(registrationBlockNumber); + _registerOperatorWithCoordinator(defaultOperator, 1, defaultPubKey); + + // Create test array with one registered and one unregistered operator + address[] memory operators = new address[](2); + operators[0] = defaultOperator; + operators[1] = address(2); // unregistered + + bytes32[] memory operatorIds = + sigCheckOperatorStateRetriever.getBatchOperatorId(registryCoordinator, operators); + + assertEq(operatorIds.length, 2, "Should return array of same length as input"); + assertEq( + operatorIds[0], defaultOperatorId, "Should return correct ID for registered operator" + ); + assertEq(operatorIds[1], bytes32(0), "Should return 0 for unregistered operator"); + } + + function test_getBatchOperatorFromId_emptyArray() public { + bytes32[] memory operatorIds = new bytes32[](0); + address[] memory operators = + sigCheckOperatorStateRetriever.getBatchOperatorFromId(registryCoordinator, operatorIds); + assertEq(operators.length, 0, "Should return empty array for empty input"); + } + + function test_getBatchOperatorFromId_unregisteredIds() public { + bytes32[] memory operatorIds = new bytes32[](2); + operatorIds[0] = bytes32(uint256(1)); + operatorIds[1] = bytes32(uint256(2)); + + address[] memory operators = + sigCheckOperatorStateRetriever.getBatchOperatorFromId(registryCoordinator, operatorIds); + + assertEq(operators.length, 2, "Should return array of same length as input"); + assertEq(operators[0], address(0), "Unregistered ID should return address(0)"); + assertEq(operators[1], address(0), "Unregistered ID should return address(0)"); + } + + function test_getBatchOperatorFromId_mixedRegistration() public { + // Register one operator + cheats.roll(registrationBlockNumber); + _registerOperatorWithCoordinator(defaultOperator, 1, defaultPubKey); + + // Create test array with one registered and one unregistered operator ID + bytes32[] memory operatorIds = new bytes32[](2); + operatorIds[0] = defaultOperatorId; + operatorIds[1] = bytes32(uint256(2)); // unregistered + + address[] memory operators = + sigCheckOperatorStateRetriever.getBatchOperatorFromId(registryCoordinator, operatorIds); + + assertEq(operators.length, 2, "Should return array of same length as input"); + assertEq(operators[0], defaultOperator, "Should return correct address for registered ID"); + assertEq(operators[1], address(0), "Should return address(0) for unregistered ID"); + } + + // helper function to generate a G2 point from a scalar + function _makeG2Point( + uint256 scalar + ) internal returns (BN254.G2Point memory) { + // BN256G2.ECTwistMul returns (X0, X1, Y0, Y1) in that order + (uint256 reX, uint256 imX, uint256 reY, uint256 imY) = + BN256G2.ECTwistMul(scalar, BN254.G2x0, BN254.G2x1, BN254.G2y0, BN254.G2y1); + + // BN254.G2Point uses [im, re] ordering + return BN254.G2Point([imX, reX], [imY, reY]); + } + + // helper function to add two G2 points + function _addG2Points( + BN254.G2Point memory a, + BN254.G2Point memory b + ) internal returns (BN254.G2Point memory) { + BN254.G2Point memory sum; + // sum starts as (0,0), so we add a first: + (sum.X[1], sum.X[0], sum.Y[1], sum.Y[0]) = BN256G2.ECTwistAdd( + // sum so far + sum.X[1], + sum.X[0], + sum.Y[1], + sum.Y[0], + // a (flip to [im, re] for BN256G2) + a.X[1], + a.X[0], + a.Y[1], + a.Y[0] + ); + // then add b: + (sum.X[1], sum.X[0], sum.Y[1], sum.Y[0]) = BN256G2.ECTwistAdd( + sum.X[1], sum.X[0], sum.Y[1], sum.Y[0], b.X[1], b.X[0], b.Y[1], b.Y[0] + ); + return sum; + } + + function test_getNonSignerStakesAndSignature_returnsCorrect() public { + // setup + uint256 quorumBitmapOne = 1; + uint256 quorumBitmapThree = 3; + cheats.roll(registrationBlockNumber); + + _registerOperatorWithCoordinator(defaultOperator, quorumBitmapOne, defaultPubKey); + + address otherOperator = _incrementAddress(defaultOperator, 1); + BN254.G1Point memory otherPubKey = BN254.G1Point(1, 2); + _registerOperatorWithCoordinator( + otherOperator, quorumBitmapThree, otherPubKey, defaultStake - 1 + ); + + // Generate actual G2 pubkeys + BN254.G2Point memory op1G2 = _makeG2Point(2); + BN254.G2Point memory op2G2 = _makeG2Point(3); + + // Mock the registry calls so the contract sees those G2 points + vm.mockCall( + address(blsApkRegistry), + abi.encodeWithSelector(IBLSApkRegistry.getOperatorPubkeyG2.selector, defaultOperator), + abi.encode(op1G2) + ); + vm.mockCall( + address(blsApkRegistry), + abi.encodeWithSelector(IBLSApkRegistry.getOperatorPubkeyG2.selector, otherOperator), + abi.encode(op2G2) + ); + + // Prepare inputs + BN254.G1Point memory dummySigma = BN254.scalar_mul_tiny(BN254.generatorG1(), 123); + address[] memory signingOperators = new address[](2); + signingOperators[0] = defaultOperator; + signingOperators[1] = otherOperator; + + bytes memory quorumNumbers = new bytes(2); + quorumNumbers[0] = bytes1(uint8(0)); + quorumNumbers[1] = bytes1(uint8(1)); + + // Call the function under test + IBLSSignatureCheckerTypes.NonSignerStakesAndSignature memory result = + sigCheckOperatorStateRetriever.getNonSignerStakesAndSignature( + registryCoordinator, quorumNumbers, dummySigma, signingOperators, uint32(block.number) + ); + + // Non-signers + assertEq(result.nonSignerQuorumBitmapIndices.length, 0, "Should have no non-signer"); + assertEq(result.nonSignerPubkeys.length, 0, "Should have no non-signer pubkeys"); + + // Quorum APKs + assertEq(result.quorumApks.length, 2, "Should have 2 quorum APKs"); + (BN254.G1Point memory expectedApk0) = + _getApkAtBlocknumber(registryCoordinator, 0, uint32(block.number)); + (BN254.G1Point memory expectedApk1) = + _getApkAtBlocknumber(registryCoordinator, 1, uint32(block.number)); + assertEq(result.quorumApks[0].X, expectedApk0.X, "First quorum APK X mismatch"); + assertEq(result.quorumApks[0].Y, expectedApk0.Y, "First quorum APK Y mismatch"); + assertEq(result.quorumApks[1].X, expectedApk1.X, "Second quorum APK X mismatch"); + assertEq(result.quorumApks[1].Y, expectedApk1.Y, "Second quorum APK Y mismatch"); + + // Aggregated G2 = op1G2 + op2G2 + BN254.G2Point memory expectedSum = _addG2Points(op1G2, op2G2); + assertEq(result.apkG2.X[0], expectedSum.X[0], "aggregated X[0] mismatch"); + assertEq(result.apkG2.X[1], expectedSum.X[1], "aggregated X[1] mismatch"); + assertEq(result.apkG2.Y[0], expectedSum.Y[0], "aggregated Y[0] mismatch"); + assertEq(result.apkG2.Y[1], expectedSum.Y[1], "aggregated Y[1] mismatch"); + + // Sigma + assertEq(result.sigma.X, dummySigma.X, "Sigma X mismatch"); + assertEq(result.sigma.Y, dummySigma.Y, "Sigma Y mismatch"); + + // Indices + assertEq(result.quorumApkIndices.length, 2, "Should have 2 quorum APK indices"); + assertEq(result.quorumApkIndices[0], 1, "First quorum APK index mismatch"); + assertEq(result.quorumApkIndices[1], 1, "Second quorum APK index mismatch"); + assertEq(result.totalStakeIndices.length, 2, "Should have 2 total stake indices"); + assertEq(result.totalStakeIndices[0], 1, "First total stake index mismatch"); + assertEq(result.totalStakeIndices[1], 1, "Second total stake index mismatch"); + + // Non-signer stake indices + assertEq( + result.nonSignerStakeIndices.length, + 2, + "Should have 2 arrays of non-signer stake indices" + ); + assertEq(result.nonSignerStakeIndices[0].length, 0, "First quorum non-signer mismatch"); + assertEq(result.nonSignerStakeIndices[1].length, 0, "Second quorum non-signer mismatch"); + } + + function test_getNonSignerStakesAndSignature_returnsCorrect_oneSigner() public { + // setup + uint256 quorumBitmapOne = 1; + uint256 quorumBitmapThree = 3; + cheats.roll(registrationBlockNumber); + + _registerOperatorWithCoordinator(defaultOperator, quorumBitmapOne, defaultPubKey); + + address otherOperator = _incrementAddress(defaultOperator, 1); + BN254.G1Point memory otherPubKey = BN254.G1Point(1, 2); + _registerOperatorWithCoordinator( + otherOperator, quorumBitmapThree, otherPubKey, defaultStake - 1 + ); + + // Generate actual G2 pubkeys + BN254.G2Point memory op1G2 = _makeG2Point(2); + BN254.G2Point memory op2G2 = _makeG2Point(3); + + // Mock them + vm.mockCall( + address(blsApkRegistry), + abi.encodeWithSelector(IBLSApkRegistry.getOperatorPubkeyG2.selector, defaultOperator), + abi.encode(op1G2) + ); + vm.mockCall( + address(blsApkRegistry), + abi.encodeWithSelector(IBLSApkRegistry.getOperatorPubkeyG2.selector, otherOperator), + abi.encode(op2G2) + ); + + // Prepare input + BN254.G1Point memory dummySigma = BN254.scalar_mul_tiny(BN254.generatorG1(), 123); + + address[] memory signingOperators = new address[](1); + signingOperators[0] = defaultOperator; // only op1 + + bytes memory quorumNumbers = new bytes(2); + quorumNumbers[0] = bytes1(uint8(0)); + quorumNumbers[1] = bytes1(uint8(1)); + + // Call under test + IBLSSignatureCheckerTypes.NonSignerStakesAndSignature memory result = + sigCheckOperatorStateRetriever.getNonSignerStakesAndSignature( + registryCoordinator, quorumNumbers, dummySigma, signingOperators, uint32(block.number) + ); + + // Validate + // One non-signer => otherOperator + assertEq(result.nonSignerQuorumBitmapIndices.length, 1, "Should have 1 non-signer"); + assertEq( + result.nonSignerQuorumBitmapIndices[0], 0, "Unexpected non-signer quorum bitmap index" + ); + assertEq(result.nonSignerPubkeys.length, 1, "Should have 1 non-signer pubkey"); + assertEq(result.nonSignerPubkeys[0].X, otherPubKey.X, "Unexpected non-signer pubkey X"); + assertEq(result.nonSignerPubkeys[0].Y, otherPubKey.Y, "Unexpected non-signer pubkey Y"); + + // Quorum APKs + assertEq(result.quorumApks.length, 2, "Should have 2 quorum APKs"); + (BN254.G1Point memory expectedApk0) = + _getApkAtBlocknumber(registryCoordinator, 0, uint32(block.number)); + (BN254.G1Point memory expectedApk1) = + _getApkAtBlocknumber(registryCoordinator, 1, uint32(block.number)); + assertEq(result.quorumApks[0].X, expectedApk0.X, "First quorum APK X mismatch"); + assertEq(result.quorumApks[0].Y, expectedApk0.Y, "First quorum APK Y mismatch"); + assertEq(result.quorumApks[1].X, expectedApk1.X, "Second quorum APK X mismatch"); + assertEq(result.quorumApks[1].Y, expectedApk1.Y, "Second quorum APK Y mismatch"); + + // Since only defaultOperator signed, aggregator's G2 should match op1G2 + assertEq(result.apkG2.X[0], op1G2.X[0], "aggregated X[0] mismatch"); + assertEq(result.apkG2.X[1], op1G2.X[1], "aggregated X[1] mismatch"); + assertEq(result.apkG2.Y[0], op1G2.Y[0], "aggregated Y[0] mismatch"); + assertEq(result.apkG2.Y[1], op1G2.Y[1], "aggregated Y[1] mismatch"); + + // Sigma + assertEq(result.sigma.X, dummySigma.X, "Sigma X mismatch"); + assertEq(result.sigma.Y, dummySigma.Y, "Sigma Y mismatch"); + + // Indices + assertEq(result.quorumApkIndices.length, 2, "Should have 2 quorum APK indices"); + assertEq(result.quorumApkIndices[0], 1, "First quorum index mismatch"); + assertEq(result.quorumApkIndices[1], 1, "Second quorum index mismatch"); + assertEq(result.totalStakeIndices.length, 2, "Should have 2 total stake indices"); + assertEq(result.totalStakeIndices[0], 1, "First total stake index mismatch"); + assertEq(result.totalStakeIndices[1], 1, "Second total stake index mismatch"); + + // Non-signer stake indices + // Each quorum has exactly 1 non-signer (the otherOperator) + assertEq( + result.nonSignerStakeIndices.length, + 2, + "Should have 2 arrays of non-signer stake indices" + ); + assertEq( + result.nonSignerStakeIndices[0].length, + 1, + "First quorum should have 1 non-signer stake index" + ); + assertEq( + result.nonSignerStakeIndices[1].length, + 1, + "Second quorum should have 1 non-signer stake index" + ); + } + + function test_getNonSignerStakesAndSignature_changingQuorumOperatorSet() public { + // setup + uint256 quorumBitmapOne = 1; + uint256 quorumBitmapThree = 3; + cheats.roll(registrationBlockNumber); + + _registerOperatorWithCoordinator(defaultOperator, quorumBitmapOne, defaultPubKey); + + address otherOperator = _incrementAddress(defaultOperator, 1); + BN254.G1Point memory otherPubKey = BN254.G1Point(1, 2); + _registerOperatorWithCoordinator( + otherOperator, quorumBitmapThree, otherPubKey, defaultStake - 1 + ); + + // Generate actual G2 pubkeys + BN254.G2Point memory op1G2 = _makeG2Point(2); + BN254.G2Point memory op2G2 = _makeG2Point(3); + + // Mock the registry calls so the contract sees those G2 points + vm.mockCall( + address(blsApkRegistry), + abi.encodeWithSelector(IBLSApkRegistry.getOperatorPubkeyG2.selector, defaultOperator), + abi.encode(op1G2) + ); + vm.mockCall( + address(blsApkRegistry), + abi.encodeWithSelector(IBLSApkRegistry.getOperatorPubkeyG2.selector, otherOperator), + abi.encode(op2G2) + ); + + // Prepare inputs + BN254.G1Point memory dummySigma = BN254.scalar_mul_tiny(BN254.generatorG1(), 123); + address[] memory signingOperators = new address[](2); + signingOperators[0] = defaultOperator; + signingOperators[1] = otherOperator; + + bytes memory quorumNumbers = new bytes(2); + quorumNumbers[0] = bytes1(uint8(0)); + quorumNumbers[1] = bytes1(uint8(1)); + + // Deregister the otherOperator + cheats.roll(registrationBlockNumber + 10); + cheats.prank(otherOperator); + registryCoordinator.deregisterOperator(BitmapUtils.bitmapToBytesArray(quorumBitmapThree)); + + // Call the function under test + IBLSSignatureCheckerTypes.NonSignerStakesAndSignature memory result = + sigCheckOperatorStateRetriever.getNonSignerStakesAndSignature( + registryCoordinator, + quorumNumbers, + dummySigma, + signingOperators, + registrationBlockNumber + ); + + // Non-signers + assertEq(result.nonSignerQuorumBitmapIndices.length, 0, "Should have no non-signer"); + assertEq(result.nonSignerPubkeys.length, 0, "Should have no non-signer pubkeys"); + + // Quorum APKs + assertEq(result.quorumApks.length, 2, "Should have 2 quorum APKs"); + (BN254.G1Point memory expectedApk0) = + _getApkAtBlocknumber(registryCoordinator, 0, uint32(registrationBlockNumber)); + (BN254.G1Point memory expectedApk1) = + _getApkAtBlocknumber(registryCoordinator, 1, uint32(registrationBlockNumber)); + assertEq(result.quorumApks[0].X, expectedApk0.X, "First quorum APK X mismatch"); + assertEq(result.quorumApks[0].Y, expectedApk0.Y, "First quorum APK Y mismatch"); + assertEq(result.quorumApks[1].X, expectedApk1.X, "Second quorum APK X mismatch"); + assertEq(result.quorumApks[1].Y, expectedApk1.Y, "Second quorum APK Y mismatch"); + + // Aggregated G2 = op1G2 + op2G2 + BN254.G2Point memory expectedSum = _addG2Points(op1G2, op2G2); + assertEq(result.apkG2.X[0], expectedSum.X[0], "aggregated X[0] mismatch"); + assertEq(result.apkG2.X[1], expectedSum.X[1], "aggregated X[1] mismatch"); + assertEq(result.apkG2.Y[0], expectedSum.Y[0], "aggregated Y[0] mismatch"); + assertEq(result.apkG2.Y[1], expectedSum.Y[1], "aggregated Y[1] mismatch"); + + // Sigma + assertEq(result.sigma.X, dummySigma.X, "Sigma X mismatch"); + assertEq(result.sigma.Y, dummySigma.Y, "Sigma Y mismatch"); + + // Indices + assertEq(result.quorumApkIndices.length, 2, "Should have 2 quorum APK indices"); + assertEq(result.quorumApkIndices[0], 1, "First quorum APK index mismatch"); + assertEq(result.quorumApkIndices[1], 1, "Second quorum APK index mismatch"); + assertEq(result.totalStakeIndices.length, 2, "Should have 2 total stake indices"); + assertEq(result.totalStakeIndices[0], 1, "First total stake index mismatch"); + assertEq(result.totalStakeIndices[1], 1, "Second total stake index mismatch"); + + // Non-signer stake indices + assertEq( + result.nonSignerStakeIndices.length, + 2, + "Should have 2 arrays of non-signer stake indices" + ); + assertEq(result.nonSignerStakeIndices[0].length, 0, "First quorum non-signer mismatch"); + assertEq(result.nonSignerStakeIndices[1].length, 0, "Second quorum non-signer mismatch"); + } + + function test_getNonSignerStakesAndSignature_revert_signerNeverRegistered() public { + // Setup - register only one operator + uint256 quorumBitmap = 1; // Quorum 0 only + + cheats.roll(registrationBlockNumber); + _registerOperatorWithCoordinator(defaultOperator, quorumBitmap, defaultPubKey); + + // Create G2 points for the registered operator + BN254.G2Point memory op1G2 = _makeG2Point(2); + vm.mockCall( + address(blsApkRegistry), + abi.encodeWithSelector(IBLSApkRegistry.getOperatorPubkeyG2.selector, defaultOperator), + abi.encode(op1G2) + ); + + // Create a dummy signature + BN254.G1Point memory dummySigma = BN254.scalar_mul_tiny(BN254.generatorG1(), 123); + + // Try to include an unregistered operator as a signer + address unregisteredOperator = _incrementAddress(defaultOperator, 1); + address[] memory signingOperators = new address[](2); + signingOperators[0] = defaultOperator; + signingOperators[1] = unregisteredOperator; // This operator was never registered + + bytes memory quorumNumbers = new bytes(1); + quorumNumbers[0] = bytes1(uint8(0)); // Quorum 0 + + // Should revert because one of the signers was never registered + cheats.expectRevert( + bytes( + "RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId" + ) + ); + sigCheckOperatorStateRetriever.getNonSignerStakesAndSignature( + registryCoordinator, quorumNumbers, dummySigma, signingOperators, uint32(block.number) + ); + } + + function test_getNonSignerStakesAndSignature_revert_signerRegisteredAfterReferenceBlock() + public + { + // Setup - register one operator + uint256 quorumBitmap = 1; // Quorum 0 only + + // Save initial block number + uint32 initialBlock = registrationBlockNumber; + + cheats.roll(initialBlock); + _registerOperatorWithCoordinator(defaultOperator, quorumBitmap, defaultPubKey); + + // Register second operator later + cheats.roll(initialBlock + 10); + address secondOperator = _incrementAddress(defaultOperator, 1); + BN254.G1Point memory secondPubKey = BN254.G1Point(1, 2); + _registerOperatorWithCoordinator( + secondOperator, quorumBitmap, secondPubKey, defaultStake - 1 + ); + + // Create G2 points for both operators + BN254.G2Point memory op1G2 = _makeG2Point(2); + BN254.G2Point memory op2G2 = _makeG2Point(3); + + vm.mockCall( + address(blsApkRegistry), + abi.encodeWithSelector(IBLSApkRegistry.getOperatorPubkeyG2.selector, defaultOperator), + abi.encode(op1G2) + ); + vm.mockCall( + address(blsApkRegistry), + abi.encodeWithSelector(IBLSApkRegistry.getOperatorPubkeyG2.selector, secondOperator), + abi.encode(op2G2) + ); + + // Create a dummy signature + BN254.G1Point memory dummySigma = BN254.scalar_mul_tiny(BN254.generatorG1(), 123); + + // Include both operators as signers + address[] memory signingOperators = new address[](2); + signingOperators[0] = defaultOperator; + signingOperators[1] = secondOperator; + + bytes memory quorumNumbers = new bytes(1); + quorumNumbers[0] = bytes1(uint8(0)); // Quorum 0 + + // Should revert when querying at a block before the second operator was registered + cheats.expectRevert( + bytes( + "RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId" + ) + ); + sigCheckOperatorStateRetriever.getNonSignerStakesAndSignature( + registryCoordinator, quorumNumbers, dummySigma, signingOperators, initialBlock + 5 + ); + } + + function test_getNonSignerStakesAndSignature_revert_signerDeregisteredAtReferenceBlock() + public + { + // Setup - register two operators + uint256 quorumBitmap = 1; // Quorum 0 only + + cheats.roll(registrationBlockNumber); + _registerOperatorWithCoordinator(defaultOperator, quorumBitmap, defaultPubKey); + + address secondOperator = _incrementAddress(defaultOperator, 1); + BN254.G1Point memory secondPubKey = BN254.G1Point(1, 2); + _registerOperatorWithCoordinator( + secondOperator, quorumBitmap, secondPubKey, defaultStake - 1 + ); + + // Create G2 points for the operators + BN254.G2Point memory op1G2 = _makeG2Point(2); + BN254.G2Point memory op2G2 = _makeG2Point(3); + + vm.mockCall( + address(blsApkRegistry), + abi.encodeWithSelector(IBLSApkRegistry.getOperatorPubkeyG2.selector, defaultOperator), + abi.encode(op1G2) + ); + vm.mockCall( + address(blsApkRegistry), + abi.encodeWithSelector(IBLSApkRegistry.getOperatorPubkeyG2.selector, secondOperator), + abi.encode(op2G2) + ); + + // Deregister the second operator + cheats.roll(registrationBlockNumber + 10); + cheats.prank(secondOperator); + registryCoordinator.deregisterOperator(BitmapUtils.bitmapToBytesArray(quorumBitmap)); + + // Create a dummy signature + BN254.G1Point memory dummySigma = BN254.scalar_mul_tiny(BN254.generatorG1(), 123); + + // Include both operators as signers + address[] memory signingOperators = new address[](2); + signingOperators[0] = defaultOperator; + signingOperators[1] = secondOperator; // This operator is deregistered + + bytes memory quorumNumbers = new bytes(1); + quorumNumbers[0] = bytes1(uint8(0)); // Quorum 0 + + // Should revert because secondOperator was deregistered + cheats.expectRevert(BLSSigCheckOperatorStateRetriever.OperatorNotRegistered.selector); + sigCheckOperatorStateRetriever.getNonSignerStakesAndSignature( + registryCoordinator, quorumNumbers, dummySigma, signingOperators, uint32(block.number) + ); + } + + function test_getNonSignerStakesAndSignature_revert_quorumNotCreatedAtCallTime() public { + // Setup - register one operator + uint256 quorumBitmap = 1; // Quorum 0 only + + cheats.roll(registrationBlockNumber); + _registerOperatorWithCoordinator(defaultOperator, quorumBitmap, defaultPubKey); + + // Create G2 points for the operator + BN254.G2Point memory op1G2 = _makeG2Point(2); + vm.mockCall( + address(blsApkRegistry), + abi.encodeWithSelector(IBLSApkRegistry.getOperatorPubkeyG2.selector, defaultOperator), + abi.encode(op1G2) + ); + + // Create a dummy signature + BN254.G1Point memory dummySigma = BN254.scalar_mul_tiny(BN254.generatorG1(), 123); + + // Include the operator as a signer + address[] memory signingOperators = new address[](1); + signingOperators[0] = defaultOperator; + + // Try to query for a non-existent quorum (quorum 9) + bytes memory invalidQuorumNumbers = new bytes(1); + invalidQuorumNumbers[0] = bytes1(uint8(9)); // Invalid quorum number + + // Should revert because quorum 9 doesn't exist, but with a different error message + cheats.expectRevert( + bytes( + "IndexRegistry._operatorCountAtBlockNumber: quorum did not exist at given block number" + ) + ); + sigCheckOperatorStateRetriever.getNonSignerStakesAndSignature( + registryCoordinator, + invalidQuorumNumbers, + dummySigma, + signingOperators, + uint32(block.number) + ); + } + + function test_getNonSignerStakesAndSignature_revert_quorumNotCreatedAtReferenceBlock() public { + // Setup - register one operator in quorum 0 + uint256 quorumBitmap = 1; + + cheats.roll(registrationBlockNumber); + _registerOperatorWithCoordinator(defaultOperator, quorumBitmap, defaultPubKey); + + // Save this block number + uint32 initialBlock = uint32(block.number); + + // Create a new quorum later + cheats.roll(initialBlock + 10); + + ISlashingRegistryCoordinatorTypes.OperatorSetParam memory operatorSetParams = + ISlashingRegistryCoordinatorTypes.OperatorSetParam({ + maxOperatorCount: defaultMaxOperatorCount, + kickBIPsOfOperatorStake: defaultKickBIPsOfOperatorStake, + kickBIPsOfTotalStake: defaultKickBIPsOfTotalStake + }); + uint96 minimumStake = 1; + IStakeRegistryTypes.StrategyParams[] memory strategyParams = + new IStakeRegistryTypes.StrategyParams[](1); + strategyParams[0] = IStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(1000)), + multiplier: 1e16 + }); + + // Create quorum 8 + cheats.prank(registryCoordinator.owner()); + registryCoordinator.createTotalDelegatedStakeQuorum( + operatorSetParams, minimumStake, strategyParams + ); + + // Create G2 points for the operator + BN254.G2Point memory op1G2 = _makeG2Point(2); + vm.mockCall( + address(blsApkRegistry), + abi.encodeWithSelector(IBLSApkRegistry.getOperatorPubkeyG2.selector, defaultOperator), + abi.encode(op1G2) + ); + + // Create a dummy signature + BN254.G1Point memory dummySigma = BN254.scalar_mul_tiny(BN254.generatorG1(), 123); + + // Include the operator as a signer + address[] memory signingOperators = new address[](1); + signingOperators[0] = defaultOperator; + + // Try to query for the newly created quorum but at a historical block + bytes memory newQuorumNumbers = new bytes(1); + newQuorumNumbers[0] = bytes1(uint8(numQuorums)); + + // Should revert when querying for the newly created quorum at a block before it was created + cheats.expectRevert( + bytes( + "IndexRegistry._operatorCountAtBlockNumber: quorum did not exist at given block number" + ) + ); + sigCheckOperatorStateRetriever.getNonSignerStakesAndSignature( + registryCoordinator, newQuorumNumbers, dummySigma, signingOperators, initialBlock + ); + } + + function _getApkAtBlocknumber( + ISlashingRegistryCoordinator registryCoordinator, + uint8 quorumNumber, + uint32 blockNumber + ) internal view returns (BN254.G1Point memory) { + bytes32[] memory operatorIds = registryCoordinator.indexRegistry() + .getOperatorListAtBlockNumber(quorumNumber, blockNumber); + BN254.G1Point memory apk = BN254.G1Point(0, 0); + IBLSApkRegistry blsApkRegistry = registryCoordinator.blsApkRegistry(); + for (uint256 i = 0; i < operatorIds.length; i++) { + address operator = registryCoordinator.getOperatorFromId(operatorIds[i]); + BN254.G1Point memory operatorPk; + (operatorPk.X, operatorPk.Y) = blsApkRegistry.operatorToPubkey(operator); + apk = BN254.plus(apk, operatorPk); + } + return apk; + } +} diff --git a/test/utils/EigenDA_Holesky.json b/test/utils/EigenDA_Holesky.json new file mode 100644 index 00000000..16539651 --- /dev/null +++ b/test/utils/EigenDA_Holesky.json @@ -0,0 +1,30 @@ +{ + "addresses": { + "blsApkRegistry": "0x066cF95c1bf0927124DFB8B02B401bc23A79730D", + "blsApkRegistryImplementation": "0x885C0CC8118E428a2C04de58A93eB15Ed4F0e064", + "eigenDAProxyAdmin": "0xB043055dd967A382577c2f5261fA6428f2905c15", + "eigenDAServiceManager": "0xD4A7E1Bd8015057293f0D0A557088c286942e84b", + "eigenDAServiceManagerImplementation": "0x0A987C508b0f56154CA534b7Fa5b84863cbcc49d", + "indexRegistry": "0x2E3D6c0744b10eb0A4e6F679F71554a39Ec47a5D", + "indexRegistryImplementation": "0x889B040116f453D89e9d6d692Ad70Edd7357420d", + "mockRollup": "0x4B1481fFF061A0099408e71702b40Fe0932080Bc", + "operatorStateRetriever": "0xB4baAfee917fb4449f5ec64804217bccE9f46C67", + "registryCoordinator": "0x53012C69A189cfA2D9d29eb6F19B32e0A2EA3490", + "registryCoordinatorImplementation": "0xC908fAFAE29B5C9F0b5E0Da1d3025b8d6D42bfa0", + "serviceManagerRouter": "0x44632dfBdCb6D3E21EF613B0ca8A6A0c618F5a37", + "stakeRegistry": "0xBDACD5998989Eec814ac7A0f0f6596088AA2a270", + "stakeRegistryImplementation": "0xa8d25410c3e3347d93647f10FB6961069BEc98E5" + }, + "chainInfo": { + "chainId": 17000, + "deploymentBlock": 1168409 + }, + "permissions": { + "eigenDABatchConfirmer": "0xC0996A3Cc9ECF2A96115C117f6Da99FA80F525eB", + "eigenDAChurner": "0x400c0D378F0E6881efb11b8E82480A3140B69258", + "eigenDAEjector": "0x28Ade60640fdBDb2609D8d8734D1b5cBeFc0C348", + "eigenDAOwner": "0x28Ade60640fdBDb2609D8d8734D1b5cBeFc0C348", + "eigenDAUpgrader": "0x28Ade60640fdBDb2609D8d8734D1b5cBeFc0C348", + "pauserRegistry": "0x85Ef7299F8311B25642679edBF02B62FA2212F06" + } + } \ No newline at end of file diff --git a/test/utils/OperatorLib.sol b/test/utils/OperatorLib.sol index 828cba05..1b7ebb57 100644 --- a/test/utils/OperatorLib.sol +++ b/test/utils/OperatorLib.sol @@ -35,10 +35,12 @@ import {CoreDeployLib} from "./CoreDeployLib.sol"; import {ERC20Mock} from "../mocks/ERC20Mock.sol"; import {BN254} from "../../src/libraries/BN254.sol"; import {BN256G2} from "./BN256G2.sol"; +import {BitmapUtils} from "../../src/libraries/BitmapUtils.sol"; library OperatorLib { using BN254 for *; using Strings for uint256; + using BitmapUtils for *; Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); @@ -172,8 +174,7 @@ library OperatorLib { address avsDirectory, address serviceManager, address registryCoordinator, - bytes memory quorumNumbers, - string memory socket + uint8[] memory quorumNumbers ) internal { IAVSDirectory avsDirectoryInstance = IAVSDirectory(avsDirectory); RegistryCoordinator registryCoordinatorInstance = RegistryCoordinator(registryCoordinator); @@ -207,18 +208,32 @@ library OperatorLib { expiry: expiry }); + // Convert quorumNumbers to bytes using BitmapUtils + uint256 quorumBitmap = 0; + for (uint256 i = 0; i < quorumNumbers.length; i++) { + quorumBitmap = BitmapUtils.setBit(quorumBitmap, quorumNumbers[i]); + } + bytes memory quorumNumbersBytes = BitmapUtils.bitmapToBytesArray(quorumBitmap); + // Call the registerOperator function on the registry registryCoordinatorInstance.registerOperator( - quorumNumbers, socket, params, operatorSignature + quorumNumbersBytes, "socket", params, operatorSignature ); } function deregisterOperatorFromAVS_M2( Operator memory operator, - address registryCoordinator + address registryCoordinator, + uint8[] memory quorumNumbers ) internal { - vm.prank(operator.key.addr); - RegistryCoordinator(registryCoordinator).deregisterOperator(""); + // Convert quorumNumbers to bytes using BitmapUtils + uint256 quorumBitmap = 0; + for (uint256 i = 0; i < quorumNumbers.length; i++) { + quorumBitmap = BitmapUtils.setBit(quorumBitmap, quorumNumbers[i]); + } + bytes memory quorumNumbersBytes = BitmapUtils.bitmapToBytesArray(quorumBitmap); + + RegistryCoordinator(registryCoordinator).deregisterOperator(quorumNumbersBytes); } function registerOperatorFromAVS_OpSet(