From d4fff29355915478ff0386cd9611070dba8a4e3c Mon Sep 17 00:00:00 2001 From: rubydusa Date: Tue, 11 Mar 2025 16:05:20 +0200 Subject: [PATCH 01/32] feat: getNonSignerStakesAndSignature --- src/OperatorStateRetriever.sol | 113 ++++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 3 deletions(-) diff --git a/src/OperatorStateRetriever.sol b/src/OperatorStateRetriever.sol index d3e06490..f7c4136a 100644 --- a/src/OperatorStateRetriever.sol +++ b/src/OperatorStateRetriever.sol @@ -3,10 +3,12 @@ pragma solidity ^0.8.27; import {ISlashingRegistryCoordinator} from "./interfaces/ISlashingRegistryCoordinator.sol"; 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 {BitmapUtils} from "./libraries/BitmapUtils.sol"; +import {BN254} from "./libraries/BN254.sol"; /** * @title OperatorStateRetriever with view functions that allow to retrieve the state of an AVSs registry system. @@ -111,9 +113,9 @@ contract OperatorStateRetriever { function getCheckSignaturesIndices( ISlashingRegistryCoordinator registryCoordinator, uint32 referenceBlockNumber, - bytes calldata quorumNumbers, - bytes32[] calldata nonSignerOperatorIds - ) external view returns (CheckSignaturesIndices memory) { + bytes memory quorumNumbers, + bytes32[] memory nonSignerOperatorIds + ) public view returns (CheckSignaturesIndices memory) { IStakeRegistry stakeRegistry = registryCoordinator.stakeRegistry(); CheckSignaturesIndices memory checkSignaturesIndices; @@ -231,4 +233,109 @@ contract OperatorStateRetriever { operators[i] = registryCoordinator.getOperatorFromId(operatorIds[i]); } } + + // avoid stack too deep + struct GetNontSignerStakesAndSignatureMemory { + BN254.G1Point[] quorumApks; + BN254.G2Point apkG2; + IIndexRegistry indexRegistry; + IBLSApkRegistry blsApkRegistry; + bytes32[] operatorIds; + } + // TODO: Eigen's BN254 does not contain G2 addition implementation, need to copy from https://github.com/musalbas/solidity-BN256G2/ + function getNonSignerStakesAndSignature( + ISlashingRegistryCoordinator registryCoordinator, + bytes calldata quorumNumbers, + BN254.G1Point calldata sigma, + address[] calldata operators, + uint32 blockNumber + ) external view returns (IBLSSignatureCheckerTypes.NonSignerStakesAndSignature memory) { + GetNontSignerStakesAndSignatureMemory memory m; + m.quorumApks = new BN254.G1Point[](quorumNumbers.length); + m.indexRegistry = registryCoordinator.indexRegistry(); + m.blsApkRegistry = registryCoordinator.blsApkRegistry(); + + m.operatorIds = new bytes32[](operators.length); + for (uint256 i = 0; i < operators.length; i++) { + m.operatorIds[i] = registryCoordinator.getOperatorId(operators[i]); + } + + // extra scope for stack limit + { + uint32[] memory operatorQuorumBitmapIndices = registryCoordinator + .getQuorumBitmapIndicesAtBlockNumber(blockNumber, m.operatorIds); + // 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 operatorQuorumBitmap = registryCoordinator + .getQuorumBitmapAtBlockNumberByIndex( + m.operatorIds[i], + blockNumber, + operatorQuorumBitmapIndices[i] + ); + require(operatorQuorumBitmap != 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++) { + // TODO: This function is not timestamped. + // I didn't understand how to use history of quorum apks. + m.quorumApks[i] = m.blsApkRegistry.getApk(uint8(quorumNumbers[i])); + bytes32[] memory operatorIdsInQuorum = m.indexRegistry.getOperatorListAtBlockNumber(uint8(quorumNumbers[i]), blockNumber); + // 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.operatorIds.length; k++) { + if (operatorIdsInQuorum[j] == m.operatorIds[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++; + } + } + } + + BN254.G1Point[] memory nonSignerPubkeys = new BN254.G1Point[](nonSignerOperatorsCount); + for (uint256 i = 0; i < nonSignerOperatorsCount; i++) { + address nonSignerOperator = registryCoordinator.getOperatorFromId(nonSignerOperatorIds[i]); + (nonSignerPubkeys[i], ) = m.blsApkRegistry.getRegisteredPubkey(nonSignerOperator); + } + + CheckSignaturesIndices memory checkSignaturesIndices = getCheckSignaturesIndices(registryCoordinator, blockNumber, quorumNumbers, nonSignerOperatorIds); + 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 + }); + } } From f1186b684f6285ef077a683581e08394d72a4d07 Mon Sep 17 00:00:00 2001 From: rubydusa Date: Tue, 11 Mar 2025 17:38:15 +0200 Subject: [PATCH 02/32] chore: add BN256G2 --- src/libraries/BN256G2.sol | 394 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 394 insertions(+) create mode 100644 src/libraries/BN256G2.sol diff --git a/src/libraries/BN256G2.sol b/src/libraries/BN256G2.sol new file mode 100644 index 00000000..fc99aca1 --- /dev/null +++ b/src/libraries/BN256G2.sol @@ -0,0 +1,394 @@ +pragma solidity ^0.4.24; + +/** + * @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 + */ + +library BN256G2 { + uint256 internal constant FIELD_MODULUS = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47; + uint256 internal constant TWISTBX = 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; + uint256 internal constant TWISTBY = 0x9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d2; + uint internal constant PTXX = 0; + uint internal constant PTXY = 1; + uint internal constant PTYX = 2; + uint internal constant PTYY = 3; + uint internal constant PTZX = 4; + uint 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; + } + } +} \ No newline at end of file From 55ef0ed39e149c4ad80d60c06837c524457dfe4c Mon Sep 17 00:00:00 2001 From: rubydusa Date: Tue, 11 Mar 2025 18:14:11 +0200 Subject: [PATCH 03/32] refactor: BN256G2 ^0.4.24 -> ^0.8.27 --- src/libraries/BN256G2.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libraries/BN256G2.sol b/src/libraries/BN256G2.sol index fc99aca1..923bbe3f 100644 --- a/src/libraries/BN256G2.sol +++ b/src/libraries/BN256G2.sol @@ -1,9 +1,11 @@ -pragma solidity ^0.4.24; +// 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 { @@ -234,7 +236,7 @@ library BN256G2 { 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) + success := staticcall(sub(gas(), 2000), 5, freemem, 0xC0, freemem, 0x20) result := mload(freemem) } require(success); From 595b824b254f5a632586800ec378d8afbc8e4c06 Mon Sep 17 00:00:00 2001 From: rubydusa Date: Tue, 11 Mar 2025 18:16:21 +0200 Subject: [PATCH 04/32] fix: compute g2 apk --- src/OperatorStateRetriever.sol | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/OperatorStateRetriever.sol b/src/OperatorStateRetriever.sol index f7c4136a..3c1c1178 100644 --- a/src/OperatorStateRetriever.sol +++ b/src/OperatorStateRetriever.sol @@ -9,6 +9,7 @@ import {IIndexRegistry} from "./interfaces/IIndexRegistry.sol"; import {BitmapUtils} from "./libraries/BitmapUtils.sol"; import {BN254} from "./libraries/BN254.sol"; +import {BN256G2} from "./libraries/BN256G2.sol"; /** * @title OperatorStateRetriever with view functions that allow to retrieve the state of an AVSs registry system. @@ -242,7 +243,6 @@ contract OperatorStateRetriever { IBLSApkRegistry blsApkRegistry; bytes32[] operatorIds; } - // TODO: Eigen's BN254 does not contain G2 addition implementation, need to copy from https://github.com/musalbas/solidity-BN256G2/ function getNonSignerStakesAndSignature( ISlashingRegistryCoordinator registryCoordinator, bytes calldata quorumNumbers, @@ -258,6 +258,17 @@ contract OperatorStateRetriever { m.operatorIds = new bytes32[](operators.length); for (uint256 i = 0; i < operators.length; i++) { m.operatorIds[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 From b3223183a63b2d2895e1f0d9685d0fca046c5d8a Mon Sep 17 00:00:00 2001 From: hudsonhrh Date: Thu, 13 Mar 2025 22:09:39 -0500 Subject: [PATCH 05/32] test: inital test of getNonSignerStakesAndSignature --- src/OperatorStateRetriever.sol | 15 +- test/unit/OperatorStateRetrieverUnit.t.sol | 160 +++++++++++++++++++++ 2 files changed, 173 insertions(+), 2 deletions(-) diff --git a/src/OperatorStateRetriever.sol b/src/OperatorStateRetriever.sol index 3c1c1178..389095bc 100644 --- a/src/OperatorStateRetriever.sol +++ b/src/OperatorStateRetriever.sol @@ -331,13 +331,24 @@ contract OperatorStateRetriever { } } + // 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(nonSignerOperatorIds[i]); + address nonSignerOperator = registryCoordinator.getOperatorFromId(trimmedNonSignerOperatorIds[i]); (nonSignerPubkeys[i], ) = m.blsApkRegistry.getRegisteredPubkey(nonSignerOperator); } - CheckSignaturesIndices memory checkSignaturesIndices = getCheckSignaturesIndices(registryCoordinator, blockNumber, quorumNumbers, nonSignerOperatorIds); + CheckSignaturesIndices memory checkSignaturesIndices = getCheckSignaturesIndices( + registryCoordinator, + blockNumber, + quorumNumbers, + trimmedNonSignerOperatorIds + ); return IBLSSignatureCheckerTypes.NonSignerStakesAndSignature({ nonSignerQuorumBitmapIndices: checkSignaturesIndices.nonSignerQuorumBitmapIndices, nonSignerPubkeys: nonSignerPubkeys, diff --git a/test/unit/OperatorStateRetrieverUnit.t.sol b/test/unit/OperatorStateRetrieverUnit.t.sol index 4020604f..f085f209 100644 --- a/test/unit/OperatorStateRetrieverUnit.t.sol +++ b/test/unit/OperatorStateRetrieverUnit.t.sol @@ -4,6 +4,8 @@ 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"; + contract OperatorStateRetrieverUnitTests is MockAVSDeployer { using BN254 for BN254.G1Point; @@ -727,4 +729,162 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { assertEq(operators[0], defaultOperator, "Should return correct address for registered ID"); assertEq(operators[1], address(0), "Should return address(0) for unregistered ID"); } + + function test_getNonSignerStakesAndSignature_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 + ); + + // Create a dummy signature + BN254.G1Point memory dummySigma = BN254.G1Point(123, 456); + + // Assume operator1 and operator3 are signers + address[] memory signingOperators = new address[](2); + signingOperators[0] = defaultOperator; // operator1 + signingOperators[1] = otherOperator; // operator3 + + // Define the quorums we're checking (quorum 0 and 1) + bytes memory quorumNumbers = new bytes(2); + quorumNumbers[0] = bytes1(uint8(0)); // quorum 0 + quorumNumbers[1] = bytes1(uint8(1)); // quorum 1 + + // Call getNonSignerStakesAndSignature + IBLSSignatureCheckerTypes.NonSignerStakesAndSignature memory result = + operatorStateRetriever.getNonSignerStakesAndSignature( + registryCoordinator, + quorumNumbers, + dummySigma, + signingOperators, + uint32(block.number) + ); + + // Verify the results + // Since both operators are signers, there should be no non-signers + assertEq(result.nonSignerQuorumBitmapIndices.length, 0, "Should have no non-signer bitmap indices"); + assertEq(result.nonSignerPubkeys.length, 0, "Should have no non-signer pubkeys"); + + // Verify quorum APKs + assertEq(result.quorumApks.length, 2, "Should have 2 quorum APKs"); + // First quorum APK should match what's in the registry + (BN254.G1Point memory expectedApk0) = blsApkRegistry.getApk(0); + assertEq(result.quorumApks[0].X, expectedApk0.X, "First quorum APK X mismatch"); + assertEq(result.quorumApks[0].Y, expectedApk0.Y, "First quorum APK Y mismatch"); + // Second quorum APK should match what's in the registry + (BN254.G1Point memory expectedApk1) = blsApkRegistry.getApk(1); + assertEq(result.quorumApks[1].X, expectedApk1.X, "Second quorum APK X mismatch"); + assertEq(result.quorumApks[1].Y, expectedApk1.Y, "Second quorum APK Y mismatch"); + + // Verify aggregate pubkey in G2 + // Since both operators are signers, their pubkeys should have been aggregated + assertEq(result.apkG2.X[0], 0, "APK G2 X[0] should be 0"); + assertEq(result.apkG2.X[1], 0, "APK G2 X[1] should be 0"); + assertEq(result.apkG2.Y[0], 0, "APK G2 Y[0] should be 0"); + assertEq(result.apkG2.Y[1], 0, "APK G2 Y[1] should be 0"); + + // Verify sigma matches input + assertEq(result.sigma.X, dummySigma.X, "Sigma X mismatch"); + assertEq(result.sigma.Y, dummySigma.Y, "Sigma Y mismatch"); + + // Verify 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"); + + // Verify 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 should have no non-signer stake indices"); + assertEq(result.nonSignerStakeIndices[1].length, 0, "Second quorum should have no non-signer stake indices"); + } + + function test_getNonSignerStakesAndSignature_returnsCorrect_oneSigner() 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 + ); + + // Create a dummy signature + BN254.G1Point memory dummySigma = BN254.G1Point(123, 456); + + // Assume operator1 and operator3 are signers + address[] memory signingOperators = new address[](1); + signingOperators[0] = defaultOperator; // operator1 + + // Define the quorums we're checking (quorum 0 and 1) + bytes memory quorumNumbers = new bytes(2); + quorumNumbers[0] = bytes1(uint8(0)); // quorum 0 + quorumNumbers[1] = bytes1(uint8(1)); // quorum 1 + + // Call getNonSignerStakesAndSignature + IBLSSignatureCheckerTypes.NonSignerStakesAndSignature memory result = + operatorStateRetriever.getNonSignerStakesAndSignature( + registryCoordinator, + quorumNumbers, + dummySigma, + signingOperators, + uint32(block.number) + ); + + // Verify the results + // Since only one operator is a signer, there should be one non-signer + assertEq(result.nonSignerQuorumBitmapIndices.length, 1, "Should have 1 non-signer bitmap indices"); + assertEq(result.nonSignerPubkeys.length, 1, "Should have 1 non-signer pubkeys"); + + // Verify quorum APKs + assertEq(result.quorumApks.length, 2, "Should have 2 quorum APKs"); + // First quorum APK should match what's in the registry + (BN254.G1Point memory expectedApk0) = blsApkRegistry.getApk(0); + assertEq(result.quorumApks[0].X, expectedApk0.X, "First quorum APK X mismatch"); + assertEq(result.quorumApks[0].Y, expectedApk0.Y, "First quorum APK Y mismatch"); + // Second quorum APK should match what's in the registry + (BN254.G1Point memory expectedApk1) = blsApkRegistry.getApk(1); + assertEq(result.quorumApks[1].X, expectedApk1.X, "Second quorum APK X mismatch"); + assertEq(result.quorumApks[1].Y, expectedApk1.Y, "Second quorum APK Y mismatch"); + + // Verify aggregate pubkey in G2 + // Since only one operator is a signer, the aggregate pubkey should only contain that operator's pubkey + BN254.G2Point memory expectedApkG2 = blsApkRegistry.getOperatorPubkeyG2(defaultOperator); + + assertEq(result.apkG2.X[0], expectedApkG2.X[0], "APK G2 X[0] mismatch"); + assertEq(result.apkG2.X[1], expectedApkG2.X[1], "APK G2 X[1] mismatch"); + assertEq(result.apkG2.Y[0], expectedApkG2.Y[0], "APK G2 Y[0] mismatch"); + assertEq(result.apkG2.Y[1], expectedApkG2.Y[1], "APK G2 Y[1] mismatch"); + + // Verify sigma matches input + assertEq(result.sigma.X, dummySigma.X, "Sigma X mismatch"); + assertEq(result.sigma.Y, dummySigma.Y, "Sigma Y mismatch"); + + // Verify 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"); + + // Verify non-signer stake indices + 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 indices"); + assertEq(result.nonSignerStakeIndices[1].length, 1, "Second quorum should have 1 non-signer stake indices"); + } + } From d050b75d7eeb72f8c70b29559418fb6b4f0e2497 Mon Sep 17 00:00:00 2001 From: hudsonhrh Date: Fri, 14 Mar 2025 12:05:06 -0500 Subject: [PATCH 06/32] test: getNonSignerStakesAndSignature tests now use real G2 points --- test/unit/OperatorStateRetrieverUnit.t.sol | 210 +++++++++++++-------- 1 file changed, 133 insertions(+), 77 deletions(-) diff --git a/test/unit/OperatorStateRetrieverUnit.t.sol b/test/unit/OperatorStateRetrieverUnit.t.sol index f085f209..f1057624 100644 --- a/test/unit/OperatorStateRetrieverUnit.t.sol +++ b/test/unit/OperatorStateRetrieverUnit.t.sol @@ -5,6 +5,7 @@ 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/libraries/BN256G2.sol"; contract OperatorStateRetrieverUnitTests is MockAVSDeployer { @@ -730,34 +731,86 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { assertEq(operators[1], address(0), "Should return address(0) for unregistered ID"); } + // Some constants at the top level for BN256G2 usage: + uint256 constant G2_X0 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; // im + uint256 constant G2_X1 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; // re + uint256 constant G2_Y0 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; // im + uint256 constant G2_Y1 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; // re + + // 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 imX, uint256 reX, uint256 imY, uint256 reY) = + BN256G2.ECTwistMul(scalar, G2_X1, G2_X0, G2_Y1, G2_Y0); + + // BN254.G2Point uses [re, im] ordering + return BN254.G2Point( + [reX, imX], + [reY, imY] + ); + } + + // 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); - bytes32 otherOperatorId = BN254.hashG1Point(otherPubKey); - _registerOperatorWithCoordinator( - otherOperator, quorumBitmapThree, otherPubKey, defaultStake - 1 + _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) ); - - // Create a dummy signature + + // Prepare inputs BN254.G1Point memory dummySigma = BN254.G1Point(123, 456); - - // Assume operator1 and operator3 are signers address[] memory signingOperators = new address[](2); - signingOperators[0] = defaultOperator; // operator1 - signingOperators[1] = otherOperator; // operator3 - - // Define the quorums we're checking (quorum 0 and 1) + signingOperators[0] = defaultOperator; + signingOperators[1] = otherOperator; + bytes memory quorumNumbers = new bytes(2); - quorumNumbers[0] = bytes1(uint8(0)); // quorum 0 - quorumNumbers[1] = bytes1(uint8(1)); // quorum 1 - - // Call getNonSignerStakesAndSignature - IBLSSignatureCheckerTypes.NonSignerStakesAndSignature memory result = + quorumNumbers[0] = bytes1(uint8(0)); + quorumNumbers[1] = bytes1(uint8(1)); + + // Call the function under test + IBLSSignatureCheckerTypes.NonSignerStakesAndSignature memory result = operatorStateRetriever.getNonSignerStakesAndSignature( registryCoordinator, quorumNumbers, @@ -766,75 +819,84 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { uint32(block.number) ); - // Verify the results - // Since both operators are signers, there should be no non-signers - assertEq(result.nonSignerQuorumBitmapIndices.length, 0, "Should have no non-signer bitmap indices"); + // Non-signers + assertEq(result.nonSignerQuorumBitmapIndices.length, 0, "Should have no non-signer"); assertEq(result.nonSignerPubkeys.length, 0, "Should have no non-signer pubkeys"); - - // Verify quorum APKs + + // Quorum APKs assertEq(result.quorumApks.length, 2, "Should have 2 quorum APKs"); - // First quorum APK should match what's in the registry (BN254.G1Point memory expectedApk0) = blsApkRegistry.getApk(0); + (BN254.G1Point memory expectedApk1) = blsApkRegistry.getApk(1); assertEq(result.quorumApks[0].X, expectedApk0.X, "First quorum APK X mismatch"); assertEq(result.quorumApks[0].Y, expectedApk0.Y, "First quorum APK Y mismatch"); - // Second quorum APK should match what's in the registry - (BN254.G1Point memory expectedApk1) = blsApkRegistry.getApk(1); assertEq(result.quorumApks[1].X, expectedApk1.X, "Second quorum APK X mismatch"); assertEq(result.quorumApks[1].Y, expectedApk1.Y, "Second quorum APK Y mismatch"); - // Verify aggregate pubkey in G2 - // Since both operators are signers, their pubkeys should have been aggregated - assertEq(result.apkG2.X[0], 0, "APK G2 X[0] should be 0"); - assertEq(result.apkG2.X[1], 0, "APK G2 X[1] should be 0"); - assertEq(result.apkG2.Y[0], 0, "APK G2 Y[0] should be 0"); - assertEq(result.apkG2.Y[1], 0, "APK G2 Y[1] should be 0"); + // 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"); - // Verify sigma matches input + // Sigma assertEq(result.sigma.X, dummySigma.X, "Sigma X mismatch"); assertEq(result.sigma.Y, dummySigma.Y, "Sigma Y mismatch"); - // Verify indices + // 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"); - // Verify non-signer stake indices + // 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 should have no non-signer stake indices"); - assertEq(result.nonSignerStakeIndices[1].length, 0, "Second quorum should have no 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); - bytes32 otherOperatorId = BN254.hashG1Point(otherPubKey); - _registerOperatorWithCoordinator( - otherOperator, quorumBitmapThree, otherPubKey, defaultStake - 1 + _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) ); - - // Create a dummy signature + vm.mockCall( + address(blsApkRegistry), + abi.encodeWithSelector(IBLSApkRegistry.getOperatorPubkeyG2.selector, otherOperator), + abi.encode(op2G2) + ); + + // Prepare input BN254.G1Point memory dummySigma = BN254.G1Point(123, 456); - - // Assume operator1 and operator3 are signers + address[] memory signingOperators = new address[](1); - signingOperators[0] = defaultOperator; // operator1 - - // Define the quorums we're checking (quorum 0 and 1) + signingOperators[0] = defaultOperator; // only op1 + bytes memory quorumNumbers = new bytes(2); - quorumNumbers[0] = bytes1(uint8(0)); // quorum 0 - quorumNumbers[1] = bytes1(uint8(1)); // quorum 1 - - // Call getNonSignerStakesAndSignature - IBLSSignatureCheckerTypes.NonSignerStakesAndSignature memory result = + quorumNumbers[0] = bytes1(uint8(0)); + quorumNumbers[1] = bytes1(uint8(1)); + + // Call under test + IBLSSignatureCheckerTypes.NonSignerStakesAndSignature memory result = operatorStateRetriever.getNonSignerStakesAndSignature( registryCoordinator, quorumNumbers, @@ -843,48 +905,42 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { uint32(block.number) ); - // Verify the results - // Since only one operator is a signer, there should be one non-signer - assertEq(result.nonSignerQuorumBitmapIndices.length, 1, "Should have 1 non-signer bitmap indices"); - assertEq(result.nonSignerPubkeys.length, 1, "Should have 1 non-signer pubkeys"); - - // Verify quorum APKs + // Validate + // One non-signer => otherOperator + assertEq(result.nonSignerQuorumBitmapIndices.length, 1, "Should have 1 non-signer"); + assertEq(result.nonSignerPubkeys.length, 1, "Should have 1 non-signer pubkey"); + + // Quorum APKs assertEq(result.quorumApks.length, 2, "Should have 2 quorum APKs"); - // First quorum APK should match what's in the registry (BN254.G1Point memory expectedApk0) = blsApkRegistry.getApk(0); + (BN254.G1Point memory expectedApk1) = blsApkRegistry.getApk(1); assertEq(result.quorumApks[0].X, expectedApk0.X, "First quorum APK X mismatch"); assertEq(result.quorumApks[0].Y, expectedApk0.Y, "First quorum APK Y mismatch"); - // Second quorum APK should match what's in the registry - (BN254.G1Point memory expectedApk1) = blsApkRegistry.getApk(1); assertEq(result.quorumApks[1].X, expectedApk1.X, "Second quorum APK X mismatch"); assertEq(result.quorumApks[1].Y, expectedApk1.Y, "Second quorum APK Y mismatch"); - // Verify aggregate pubkey in G2 - // Since only one operator is a signer, the aggregate pubkey should only contain that operator's pubkey - BN254.G2Point memory expectedApkG2 = blsApkRegistry.getOperatorPubkeyG2(defaultOperator); - - assertEq(result.apkG2.X[0], expectedApkG2.X[0], "APK G2 X[0] mismatch"); - assertEq(result.apkG2.X[1], expectedApkG2.X[1], "APK G2 X[1] mismatch"); - assertEq(result.apkG2.Y[0], expectedApkG2.Y[0], "APK G2 Y[0] mismatch"); - assertEq(result.apkG2.Y[1], expectedApkG2.Y[1], "APK G2 Y[1] 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"); - // Verify sigma matches input + // Sigma assertEq(result.sigma.X, dummySigma.X, "Sigma X mismatch"); assertEq(result.sigma.Y, dummySigma.Y, "Sigma Y mismatch"); - // Verify indices + // 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.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"); - // Verify non-signer stake indices + // 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 indices"); - assertEq(result.nonSignerStakeIndices[1].length, 1, "Second quorum should have 1 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"); } - } From cd0de93b48e522a347468d5d6da001be79a5e49a Mon Sep 17 00:00:00 2001 From: hudsonhrh Date: Fri, 14 Mar 2025 12:54:16 -0500 Subject: [PATCH 07/32] test: added tests for diffferent revert scenariois --- test/unit/OperatorStateRetrieverUnit.t.sol | 327 ++++++++++++++++++++- 1 file changed, 324 insertions(+), 3 deletions(-) diff --git a/test/unit/OperatorStateRetrieverUnit.t.sol b/test/unit/OperatorStateRetrieverUnit.t.sol index f1057624..b82e8eb8 100644 --- a/test/unit/OperatorStateRetrieverUnit.t.sol +++ b/test/unit/OperatorStateRetrieverUnit.t.sol @@ -306,9 +306,9 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { ); // 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 + // the first operator (0) registered for quorum 1, (1) deregistered from quorum 1 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 + // the second operator (0) registered for quorum 1 and 2 (1) deregistered from quorum 2 assertEq(checkSignaturesIndices.nonSignerQuorumBitmapIndices[1], 2); // the operators, together, serve 2 quorums so there should be 2 quorumApkIndices assertEq(checkSignaturesIndices.quorumApkIndices.length, 2); @@ -919,7 +919,7 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { 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 + // 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"); @@ -943,4 +943,325 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { 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_noSigners() public { + // Setup: register operators in different quorums + uint256 quorumBitmap1 = 1; // Quorum 0 only + uint256 quorumBitmap2 = 2; // Quorum 1 only + + cheats.roll(registrationBlockNumber); + + // Register operators + _registerOperatorWithCoordinator(defaultOperator, quorumBitmap1, defaultPubKey); + + address secondOperator = _incrementAddress(defaultOperator, 1); + BN254.G1Point memory secondPubKey = BN254.G1Point(1, 2); + _registerOperatorWithCoordinator(secondOperator, quorumBitmap2, secondPubKey, defaultStake - 1); + + // Create G2 points for the operators + BN254.G2Point memory op1G2 = _makeG2Point(2); + BN254.G2Point memory op2G2 = _makeG2Point(3); + + // Mock the G2 point calls + 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.G1Point(123, 456); + + // No signing operators - empty array + address[] memory signingOperators = new address[](0); + + // Test both quorums + bytes memory quorumNumbers = new bytes(2); + quorumNumbers[0] = bytes1(uint8(0)); // Quorum 0 + quorumNumbers[1] = bytes1(uint8(1)); // Quorum 1 + + // Call the function under test + IBLSSignatureCheckerTypes.NonSignerStakesAndSignature memory result = + operatorStateRetriever.getNonSignerStakesAndSignature( + registryCoordinator, + quorumNumbers, + dummySigma, + signingOperators, + uint32(block.number) + ); + + // Both operators should be non-signers + assertEq(result.nonSignerQuorumBitmapIndices.length, 2, "Should have 2 non-signers"); + assertEq(result.nonSignerPubkeys.length, 2, "Should have 2 non-signer pubkeys"); + + // Since no one signed, the apkG2 should contain zero point + assertEq(result.apkG2.X[0], 0, "APK G2 X[0] should be 0 with no signers"); + assertEq(result.apkG2.X[1], 0, "APK G2 X[1] should be 0 with no signers"); + assertEq(result.apkG2.Y[0], 0, "APK G2 Y[0] should be 0 with no signers"); + assertEq(result.apkG2.Y[1], 0, "APK G2 Y[1] should be 0 with no signers"); + + // Sigma should match input + 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.totalStakeIndices.length, 2, "Should have 2 total stake indices"); + + // Non-signer stake indices + assertEq(result.nonSignerStakeIndices.length, 2, "Should have 2 arrays of non-signer stake indices"); + // Quorum 0 should have 1 non-signer (defaultOperator) + assertEq(result.nonSignerStakeIndices[0].length, 1, "Quorum 0 should have 1 non-signer"); + // Quorum 1 should have 1 non-signer (secondOperator) + assertEq(result.nonSignerStakeIndices[1].length, 1, "Quorum 1 should have 1 non-signer"); + } + + 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.G1Point(123, 456); + + // 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")); + operatorStateRetriever.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.G1Point(123, 456); + + // 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")); + operatorStateRetriever.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.G1Point(123, 456); + + // 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(OperatorStateRetriever.OperatorNotRegistered.selector); + operatorStateRetriever.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.G1Point(123, 456); + + // 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")); + operatorStateRetriever.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.G1Point(123, 456); + + // 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")); + operatorStateRetriever.getNonSignerStakesAndSignature( + registryCoordinator, + newQuorumNumbers, + dummySigma, + signingOperators, + initialBlock + ); + } } From 89aac24dedd691d05ece05efd924bcc3f4eb752d Mon Sep 17 00:00:00 2001 From: rubydusa Date: Sat, 15 Mar 2025 18:43:53 +0200 Subject: [PATCH 08/32] fix: _makeG2Point comments --- test/unit/OperatorStateRetrieverUnit.t.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/unit/OperatorStateRetrieverUnit.t.sol b/test/unit/OperatorStateRetrieverUnit.t.sol index b82e8eb8..ed7ed52d 100644 --- a/test/unit/OperatorStateRetrieverUnit.t.sol +++ b/test/unit/OperatorStateRetrieverUnit.t.sol @@ -740,13 +740,13 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { // 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 imX, uint256 reX, uint256 imY, uint256 reY) = + (uint256 reX, uint256 imX, uint256 reY, uint256 imY) = BN256G2.ECTwistMul(scalar, G2_X1, G2_X0, G2_Y1, G2_Y0); - // BN254.G2Point uses [re, im] ordering + // BN254.G2Point uses [im, re] ordering return BN254.G2Point( - [reX, imX], - [reY, imY] + [imX, reX], + [imY, reY] ); } From f65636d69058318d01f81c9a71b231c35390cd7d Mon Sep 17 00:00:00 2001 From: rubydusa Date: Sat, 15 Mar 2025 18:46:49 +0200 Subject: [PATCH 09/32] chore: use G2 coordinates from BN254 --- test/unit/OperatorStateRetrieverUnit.t.sol | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/test/unit/OperatorStateRetrieverUnit.t.sol b/test/unit/OperatorStateRetrieverUnit.t.sol index ed7ed52d..d0f1b8b6 100644 --- a/test/unit/OperatorStateRetrieverUnit.t.sol +++ b/test/unit/OperatorStateRetrieverUnit.t.sol @@ -731,17 +731,11 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { assertEq(operators[1], address(0), "Should return address(0) for unregistered ID"); } - // Some constants at the top level for BN256G2 usage: - uint256 constant G2_X0 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; // im - uint256 constant G2_X1 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; // re - uint256 constant G2_Y0 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; // im - uint256 constant G2_Y1 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; // re - // 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, G2_X1, G2_X0, G2_Y1, G2_Y0); + BN256G2.ECTwistMul(scalar, BN254.G2x0, BN254.G2x1, BN254.G2y0, BN254.G2y1); // BN254.G2Point uses [im, re] ordering return BN254.G2Point( From 1bb0c3f95d1240d27fe2c1ce19c5f09132fa05f5 Mon Sep 17 00:00:00 2001 From: rubydusa Date: Sat, 15 Mar 2025 19:30:20 +0200 Subject: [PATCH 10/32] fix: compute quorum APKs by timestamp --- src/OperatorStateRetriever.sol | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/OperatorStateRetriever.sol b/src/OperatorStateRetriever.sol index 389095bc..97df7460 100644 --- a/src/OperatorStateRetriever.sol +++ b/src/OperatorStateRetriever.sol @@ -292,10 +292,10 @@ contract OperatorStateRetriever { bytes32[] memory nonSignerOperatorIds = new bytes32[](16); // for every quorum for (uint256 i = 0; i < quorumNumbers.length; i++) { - // TODO: This function is not timestamped. - // I didn't understand how to use history of quorum apks. - m.quorumApks[i] = m.blsApkRegistry.getApk(uint8(quorumNumbers[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; @@ -360,4 +360,16 @@ contract OperatorStateRetriever { nonSignerStakeIndices: checkSignaturesIndices.nonSignerStakeIndices }); } + + 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; + } } From 22b355a9c67e57ef56d19a68e3d6eebe2d73dd23 Mon Sep 17 00:00:00 2001 From: rubydusa Date: Sat, 15 Mar 2025 19:45:57 +0200 Subject: [PATCH 11/32] refactor: safe gaurd against invalid sigmas --- src/OperatorStateRetriever.sol | 12 ++++++++++++ test/unit/OperatorStateRetrieverUnit.t.sol | 16 ++++++++-------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/OperatorStateRetriever.sol b/src/OperatorStateRetriever.sol index 97df7460..4320fb6a 100644 --- a/src/OperatorStateRetriever.sol +++ b/src/OperatorStateRetriever.sol @@ -30,6 +30,7 @@ contract OperatorStateRetriever { } error OperatorNotRegistered(); + error InvalidSigma(); /** * @notice This function is intended to to be called by AVS operators every time a new task is created (i.e.) @@ -255,6 +256,9 @@ contract OperatorStateRetriever { m.indexRegistry = registryCoordinator.indexRegistry(); m.blsApkRegistry = registryCoordinator.blsApkRegistry(); + // Safe guard AVSs from generating NonSignerStakesAndSignature with invalid sigma + require(_isOnCurve(sigma), InvalidSigma()); + m.operatorIds = new bytes32[](operators.length); for (uint256 i = 0; i < operators.length; i++) { m.operatorIds[i] = registryCoordinator.getOperatorId(operators[i]); @@ -372,4 +376,12 @@ contract OperatorStateRetriever { } return apk; } + + function _isOnCurve(BN254.G1Point memory p) internal view 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/test/unit/OperatorStateRetrieverUnit.t.sol b/test/unit/OperatorStateRetrieverUnit.t.sol index d0f1b8b6..0bc5039b 100644 --- a/test/unit/OperatorStateRetrieverUnit.t.sol +++ b/test/unit/OperatorStateRetrieverUnit.t.sol @@ -794,7 +794,7 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { ); // Prepare inputs - BN254.G1Point memory dummySigma = BN254.G1Point(123, 456); + BN254.G1Point memory dummySigma = BN254.scalar_mul_tiny(BN254.generatorG1(), 123); address[] memory signingOperators = new address[](2); signingOperators[0] = defaultOperator; signingOperators[1] = otherOperator; @@ -880,7 +880,7 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { ); // Prepare input - BN254.G1Point memory dummySigma = BN254.G1Point(123, 456); + BN254.G1Point memory dummySigma = BN254.scalar_mul_tiny(BN254.generatorG1(), 123); address[] memory signingOperators = new address[](1); signingOperators[0] = defaultOperator; // only op1 @@ -969,7 +969,7 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { ); // Create a dummy signature - BN254.G1Point memory dummySigma = BN254.G1Point(123, 456); + BN254.G1Point memory dummySigma = BN254.scalar_mul_tiny(BN254.generatorG1(), 123); // No signing operators - empty array address[] memory signingOperators = new address[](0); @@ -1031,7 +1031,7 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { ); // Create a dummy signature - BN254.G1Point memory dummySigma = BN254.G1Point(123, 456); + 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); @@ -1085,7 +1085,7 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { ); // Create a dummy signature - BN254.G1Point memory dummySigma = BN254.G1Point(123, 456); + BN254.G1Point memory dummySigma = BN254.scalar_mul_tiny(BN254.generatorG1(), 123); // Include both operators as signers address[] memory signingOperators = new address[](2); @@ -1138,7 +1138,7 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { registryCoordinator.deregisterOperator(BitmapUtils.bitmapToBytesArray(quorumBitmap)); // Create a dummy signature - BN254.G1Point memory dummySigma = BN254.G1Point(123, 456); + BN254.G1Point memory dummySigma = BN254.scalar_mul_tiny(BN254.generatorG1(), 123); // Include both operators as signers address[] memory signingOperators = new address[](2); @@ -1175,7 +1175,7 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { ); // Create a dummy signature - BN254.G1Point memory dummySigma = BN254.G1Point(123, 456); + BN254.G1Point memory dummySigma = BN254.scalar_mul_tiny(BN254.generatorG1(), 123); // Include the operator as a signer address[] memory signingOperators = new address[](1); @@ -1238,7 +1238,7 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { ); // Create a dummy signature - BN254.G1Point memory dummySigma = BN254.G1Point(123, 456); + BN254.G1Point memory dummySigma = BN254.scalar_mul_tiny(BN254.generatorG1(), 123); // Include the operator as a signer address[] memory signingOperators = new address[](1); From 22810cd9979d11dcf3ab9e1b3f18c1a0906775b0 Mon Sep 17 00:00:00 2001 From: rubydusa Date: Sat, 15 Mar 2025 20:07:14 +0200 Subject: [PATCH 12/32] test: getNonSignerStakesAndSignature changing Quorum set --- test/unit/OperatorStateRetrieverUnit.t.sol | 112 ++++++++++++++++++++- 1 file changed, 108 insertions(+), 4 deletions(-) diff --git a/test/unit/OperatorStateRetrieverUnit.t.sol b/test/unit/OperatorStateRetrieverUnit.t.sol index 0bc5039b..54d1eefc 100644 --- a/test/unit/OperatorStateRetrieverUnit.t.sol +++ b/test/unit/OperatorStateRetrieverUnit.t.sol @@ -819,8 +819,8 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { // Quorum APKs assertEq(result.quorumApks.length, 2, "Should have 2 quorum APKs"); - (BN254.G1Point memory expectedApk0) = blsApkRegistry.getApk(0); - (BN254.G1Point memory expectedApk1) = blsApkRegistry.getApk(1); + (BN254.G1Point memory expectedApk0) = _getApkAtBlocknumber(registryCoordinator, 0, block.number); + (BN254.G1Point memory expectedApk1) = _getApkAtBlocknumber(registryCoordinator, 1, 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"); @@ -906,8 +906,8 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { // Quorum APKs assertEq(result.quorumApks.length, 2, "Should have 2 quorum APKs"); - (BN254.G1Point memory expectedApk0) = blsApkRegistry.getApk(0); - (BN254.G1Point memory expectedApk1) = blsApkRegistry.getApk(1); + (BN254.G1Point memory expectedApk0) = _getApkAtBlocknumber(registryCoordinator, 0, block.number); + (BN254.G1Point memory expectedApk1) = _getApkAtBlocknumber(registryCoordinator, 1, 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"); @@ -1015,6 +1015,97 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { assertEq(result.nonSignerStakeIndices[1].length, 1, "Quorum 1 should have 1 non-signer"); } + 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 = + operatorStateRetriever.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, registrationBlockNumber); + (BN254.G1Point memory expectedApk1) = _getApkAtBlocknumber(registryCoordinator, 1, 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 @@ -1258,4 +1349,17 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { 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; + } } From 973e278e6794081fb2931323c0d6707fc6482d9a Mon Sep 17 00:00:00 2001 From: rubydusa Date: Sat, 15 Mar 2025 20:09:16 +0200 Subject: [PATCH 13/32] fix: convert blockNumber to uint32 --- test/unit/OperatorStateRetrieverUnit.t.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/unit/OperatorStateRetrieverUnit.t.sol b/test/unit/OperatorStateRetrieverUnit.t.sol index 54d1eefc..e3ccb15e 100644 --- a/test/unit/OperatorStateRetrieverUnit.t.sol +++ b/test/unit/OperatorStateRetrieverUnit.t.sol @@ -819,8 +819,8 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { // Quorum APKs assertEq(result.quorumApks.length, 2, "Should have 2 quorum APKs"); - (BN254.G1Point memory expectedApk0) = _getApkAtBlocknumber(registryCoordinator, 0, block.number); - (BN254.G1Point memory expectedApk1) = _getApkAtBlocknumber(registryCoordinator, 1, block.number); + (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"); @@ -906,8 +906,8 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { // Quorum APKs assertEq(result.quorumApks.length, 2, "Should have 2 quorum APKs"); - (BN254.G1Point memory expectedApk0) = _getApkAtBlocknumber(registryCoordinator, 0, block.number); - (BN254.G1Point memory expectedApk1) = _getApkAtBlocknumber(registryCoordinator, 1, block.number); + (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"); @@ -1074,8 +1074,8 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { // Quorum APKs assertEq(result.quorumApks.length, 2, "Should have 2 quorum APKs"); - (BN254.G1Point memory expectedApk0) = _getApkAtBlocknumber(registryCoordinator, 0, registrationBlockNumber); - (BN254.G1Point memory expectedApk1) = _getApkAtBlocknumber(registryCoordinator, 1, registrationBlockNumber); + (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"); From 2d7c073f8c338af49494f2117fd6e36ee95cefa7 Mon Sep 17 00:00:00 2001 From: rubydusa Date: Mon, 17 Mar 2025 20:39:17 +0200 Subject: [PATCH 14/32] chore: clarify operatorIds -> signingOperatorIds --- src/OperatorStateRetriever.sol | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/OperatorStateRetriever.sol b/src/OperatorStateRetriever.sol index 4320fb6a..4041bc47 100644 --- a/src/OperatorStateRetriever.sol +++ b/src/OperatorStateRetriever.sol @@ -242,7 +242,7 @@ contract OperatorStateRetriever { BN254.G2Point apkG2; IIndexRegistry indexRegistry; IBLSApkRegistry blsApkRegistry; - bytes32[] operatorIds; + bytes32[] signingOperatorIds; } function getNonSignerStakesAndSignature( ISlashingRegistryCoordinator registryCoordinator, @@ -259,9 +259,9 @@ contract OperatorStateRetriever { // Safe guard AVSs from generating NonSignerStakesAndSignature with invalid sigma require(_isOnCurve(sigma), InvalidSigma()); - m.operatorIds = new bytes32[](operators.length); + m.signingOperatorIds = new bytes32[](operators.length); for (uint256 i = 0; i < operators.length; i++) { - m.operatorIds[i] = registryCoordinator.getOperatorId(operators[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], @@ -277,17 +277,17 @@ contract OperatorStateRetriever { // extra scope for stack limit { - uint32[] memory operatorQuorumBitmapIndices = registryCoordinator - .getQuorumBitmapIndicesAtBlockNumber(blockNumber, m.operatorIds); + 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 operatorQuorumBitmap = registryCoordinator + uint192 signingOperatorQuorumBitmap = registryCoordinator .getQuorumBitmapAtBlockNumberByIndex( - m.operatorIds[i], + m.signingOperatorIds[i], blockNumber, - operatorQuorumBitmapIndices[i] + signingOperatorQuorumBitmapIndices[i] ); - require(operatorQuorumBitmap != 0, OperatorNotRegistered()); + require(signingOperatorQuorumBitmap != 0, OperatorNotRegistered()); } } @@ -304,8 +304,8 @@ contract OperatorStateRetriever { 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.operatorIds.length; k++) { - if (operatorIdsInQuorum[j] == m.operatorIds[k]) { + for (uint256 k = 0; k < m.signingOperatorIds.length; k++) { + if (operatorIdsInQuorum[j] == m.signingOperatorIds[k]) { isNewNonSigner = false; break; } From 76ffef747d8202381c5de898cbd43e5e15dd703b Mon Sep 17 00:00:00 2001 From: rubydusa Date: Tue, 18 Mar 2025 20:46:51 +0200 Subject: [PATCH 15/32] chore: delete non-sensical no signers test --- test/unit/OperatorStateRetrieverUnit.t.sol | 77 ---------------------- 1 file changed, 77 deletions(-) diff --git a/test/unit/OperatorStateRetrieverUnit.t.sol b/test/unit/OperatorStateRetrieverUnit.t.sol index e3ccb15e..5da67009 100644 --- a/test/unit/OperatorStateRetrieverUnit.t.sol +++ b/test/unit/OperatorStateRetrieverUnit.t.sol @@ -938,83 +938,6 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { assertEq(result.nonSignerStakeIndices[1].length, 1, "Second quorum should have 1 non-signer stake index"); } - function test_getNonSignerStakesAndSignature_noSigners() public { - // Setup: register operators in different quorums - uint256 quorumBitmap1 = 1; // Quorum 0 only - uint256 quorumBitmap2 = 2; // Quorum 1 only - - cheats.roll(registrationBlockNumber); - - // Register operators - _registerOperatorWithCoordinator(defaultOperator, quorumBitmap1, defaultPubKey); - - address secondOperator = _incrementAddress(defaultOperator, 1); - BN254.G1Point memory secondPubKey = BN254.G1Point(1, 2); - _registerOperatorWithCoordinator(secondOperator, quorumBitmap2, secondPubKey, defaultStake - 1); - - // Create G2 points for the operators - BN254.G2Point memory op1G2 = _makeG2Point(2); - BN254.G2Point memory op2G2 = _makeG2Point(3); - - // Mock the G2 point calls - 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); - - // No signing operators - empty array - address[] memory signingOperators = new address[](0); - - // Test both quorums - bytes memory quorumNumbers = new bytes(2); - quorumNumbers[0] = bytes1(uint8(0)); // Quorum 0 - quorumNumbers[1] = bytes1(uint8(1)); // Quorum 1 - - // Call the function under test - IBLSSignatureCheckerTypes.NonSignerStakesAndSignature memory result = - operatorStateRetriever.getNonSignerStakesAndSignature( - registryCoordinator, - quorumNumbers, - dummySigma, - signingOperators, - uint32(block.number) - ); - - // Both operators should be non-signers - assertEq(result.nonSignerQuorumBitmapIndices.length, 2, "Should have 2 non-signers"); - assertEq(result.nonSignerPubkeys.length, 2, "Should have 2 non-signer pubkeys"); - - // Since no one signed, the apkG2 should contain zero point - assertEq(result.apkG2.X[0], 0, "APK G2 X[0] should be 0 with no signers"); - assertEq(result.apkG2.X[1], 0, "APK G2 X[1] should be 0 with no signers"); - assertEq(result.apkG2.Y[0], 0, "APK G2 Y[0] should be 0 with no signers"); - assertEq(result.apkG2.Y[1], 0, "APK G2 Y[1] should be 0 with no signers"); - - // Sigma should match input - 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.totalStakeIndices.length, 2, "Should have 2 total stake indices"); - - // Non-signer stake indices - assertEq(result.nonSignerStakeIndices.length, 2, "Should have 2 arrays of non-signer stake indices"); - // Quorum 0 should have 1 non-signer (defaultOperator) - assertEq(result.nonSignerStakeIndices[0].length, 1, "Quorum 0 should have 1 non-signer"); - // Quorum 1 should have 1 non-signer (secondOperator) - assertEq(result.nonSignerStakeIndices[1].length, 1, "Quorum 1 should have 1 non-signer"); - } - function test_getNonSignerStakesAndSignature_changingQuorumOperatorSet() public { // setup uint256 quorumBitmapOne = 1; From e758ffaa48a47eeec916bece6a2ac1ef6c3b381d Mon Sep 17 00:00:00 2001 From: rubydusa Date: Thu, 27 Mar 2025 00:46:24 +0200 Subject: [PATCH 16/32] chore: add natspec to new functions in OperatorStateRetriever --- src/OperatorStateRetriever.sol | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/OperatorStateRetriever.sol b/src/OperatorStateRetriever.sol index 4041bc47..83d90982 100644 --- a/src/OperatorStateRetriever.sol +++ b/src/OperatorStateRetriever.sol @@ -244,6 +244,25 @@ contract OperatorStateRetriever { IBLSApkRegistry blsApkRegistry; bytes32[] signingOperatorIds; } + + /** + * @notice Returns the stakes and signature information for 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 signed across all specified quorums + */ function getNonSignerStakesAndSignature( ISlashingRegistryCoordinator registryCoordinator, bytes calldata quorumNumbers, @@ -365,6 +384,13 @@ contract OperatorStateRetriever { }); } + /** + * @notice Computes the aggregate public key (APK) in G1 for a list of operators + * @dev Aggregetes 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(); @@ -377,6 +403,12 @@ contract OperatorStateRetriever { return apk; } + /** + * @notice Checks if a point lies on the BN254 elliptic curve + * @dev The curve equation is y^2 = x^3 + 2 (mod p) (https://neuromancer.sk/std/bn/bn254) + * @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 view returns (bool) { uint256 y2 = mulmod(p.Y, p.Y, BN254.FP_MODULUS); uint256 x2 = mulmod(p.X, p.X, BN254.FP_MODULUS); From 021d32cf7294bfc0607369f9b4c65caf7950483f Mon Sep 17 00:00:00 2001 From: bagelface Date: Thu, 27 Mar 2025 11:44:15 -0400 Subject: [PATCH 17/32] docs: fix capitalization of comments and natspec for getNonSignerStakesAndSignature --- src/OperatorStateRetriever.sol | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/OperatorStateRetriever.sol b/src/OperatorStateRetriever.sol index 83d90982..a6b1c8d3 100644 --- a/src/OperatorStateRetriever.sol +++ b/src/OperatorStateRetriever.sol @@ -251,8 +251,8 @@ contract OperatorStateRetriever { * @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: + * @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 @@ -294,11 +294,11 @@ contract OperatorStateRetriever { ); } - // extra scope for stack limit + // 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) + // 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( @@ -310,35 +310,35 @@ contract OperatorStateRetriever { } } - // we use this as a dynamic array + // We use this as a dynamic array uint256 nonSignerOperatorsCount = 0; bytes32[] memory nonSignerOperatorIds = new bytes32[](16); - // for every quorum + // 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 + // 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 + // 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 + // 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 + // 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 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); From a2943095bb020d6443302a3fc68b17fb66474783 Mon Sep 17 00:00:00 2001 From: Hudson Headley <76409831+hudsonhrh@users.noreply.github.com> Date: Thu, 27 Mar 2025 14:20:01 -0500 Subject: [PATCH 18/32] revert comment changes --- test/unit/OperatorStateRetrieverUnit.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/OperatorStateRetrieverUnit.t.sol b/test/unit/OperatorStateRetrieverUnit.t.sol index 5da67009..aa8fc642 100644 --- a/test/unit/OperatorStateRetrieverUnit.t.sol +++ b/test/unit/OperatorStateRetrieverUnit.t.sol @@ -306,9 +306,9 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { ); // 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 + // 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 + // 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); From e0bd81f3fc84051523af9c1c9f3900af205d090a Mon Sep 17 00:00:00 2001 From: Sara Date: Sat, 29 Mar 2025 23:15:48 -0700 Subject: [PATCH 19/32] Update isOnCurve function as pure --- src/OperatorStateRetriever.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OperatorStateRetriever.sol b/src/OperatorStateRetriever.sol index a6b1c8d3..d79151ef 100644 --- a/src/OperatorStateRetriever.sol +++ b/src/OperatorStateRetriever.sol @@ -409,7 +409,7 @@ contract OperatorStateRetriever { * @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 view returns (bool) { + 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); From c3e1ba1cfd230bc94a7b7c3c98b9e23baac8b13d Mon Sep 17 00:00:00 2001 From: 0xR-code <59872956+diterra-code@users.noreply.github.com> Date: Sun, 30 Mar 2025 13:07:00 -0400 Subject: [PATCH 20/32] docs: fix "getNonSignerStakesAndSignature" natspec comment --- src/OperatorStateRetriever.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OperatorStateRetriever.sol b/src/OperatorStateRetriever.sol index d79151ef..5bbbffd3 100644 --- a/src/OperatorStateRetriever.sol +++ b/src/OperatorStateRetriever.sol @@ -246,7 +246,7 @@ contract OperatorStateRetriever { } /** - * @notice Returns the stakes and signature information for signing operators in specified quorums + * @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 @@ -261,7 +261,7 @@ contract OperatorStateRetriever { * - 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 signed across all specified quorums + * @dev Computes the indices of operators that did not sign across all specified quorums */ function getNonSignerStakesAndSignature( ISlashingRegistryCoordinator registryCoordinator, From 4f6b395070e43b1c6aa1e737f593936600427c7a Mon Sep 17 00:00:00 2001 From: cathschmidt Date: Wed, 2 Apr 2025 12:28:13 +0200 Subject: [PATCH 21/32] fix: formatting --- src/OperatorStateRetriever.sol | 60 ++--- src/libraries/BN256G2.sol | 413 ++++++++++++++------------------- 2 files changed, 209 insertions(+), 264 deletions(-) diff --git a/src/OperatorStateRetriever.sol b/src/OperatorStateRetriever.sol index 5bbbffd3..ef451fa2 100644 --- a/src/OperatorStateRetriever.sol +++ b/src/OperatorStateRetriever.sol @@ -283,39 +283,38 @@ contract OperatorStateRetriever { 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], + 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()); - } + 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 + // 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); + 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); @@ -362,15 +361,13 @@ contract OperatorStateRetriever { 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); + address nonSignerOperator = + registryCoordinator.getOperatorFromId(trimmedNonSignerOperatorIds[i]); + (nonSignerPubkeys[i],) = m.blsApkRegistry.getRegisteredPubkey(nonSignerOperator); } CheckSignaturesIndices memory checkSignaturesIndices = getCheckSignaturesIndices( - registryCoordinator, - blockNumber, - quorumNumbers, - trimmedNonSignerOperatorIds + registryCoordinator, blockNumber, quorumNumbers, trimmedNonSignerOperatorIds ); return IBLSSignatureCheckerTypes.NonSignerStakesAndSignature({ nonSignerQuorumBitmapIndices: checkSignaturesIndices.nonSignerQuorumBitmapIndices, @@ -391,7 +388,10 @@ contract OperatorStateRetriever { * @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) { + 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++) { @@ -409,7 +409,9 @@ contract OperatorStateRetriever { * @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) { + 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); diff --git a/src/libraries/BN256G2.sol b/src/libraries/BN256G2.sol index 923bbe3f..a1721df3 100644 --- a/src/libraries/BN256G2.sol +++ b/src/libraries/BN256G2.sol @@ -7,17 +7,19 @@ pragma solidity ^0.8.27; * @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; - uint internal constant PTXX = 0; - uint internal constant PTXY = 1; - uint internal constant PTYX = 2; - uint internal constant PTYY = 3; - uint internal constant PTZX = 4; - uint internal constant PTZY = 5; + 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 @@ -32,68 +34,32 @@ library BN256G2 { * @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 - )); + 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 - ); + 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 - )); + 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 - ); + 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] - ); + return _fromJacobian(pt3[PTXX], pt3[PTXY], pt3[PTYX], pt3[PTYY], pt3[PTZX], pt3[PTZY]); } /** @@ -107,39 +73,23 @@ library BN256G2 { */ function ECTwistMul( uint256 s, - uint256 pt1xx, uint256 pt1xy, - uint256 pt1yx, uint256 pt1yy - ) public view returns ( - uint256, uint256, - uint256, uint256 - ) { + 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 - ) { + if (pt1xx == 0 && pt1xy == 0 && pt1yx == 0 && pt1yy == 0) { pt1xx = 1; pt1yx = 1; pt1zx = 0; } else { - assert(_isOnCurve( - pt1xx, pt1xy, - pt1yx, pt1yy - )); + assert(_isOnCurve(pt1xx, pt1xy, pt1yx, pt1yy)); } - uint256[6] memory pt2 = _ECTwistMulJacobian( - s, - pt1xx, pt1xy, - pt1yx, pt1yy, - pt1zx, 0 - ); + 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] - ); + return _fromJacobian(pt2[PTXX], pt2[PTXY], pt2[PTYX], pt2[PTYY], pt2[PTZX], pt2[PTZY]); } /** @@ -155,8 +105,10 @@ library BN256G2 { } function _FQ2Mul( - uint256 xx, uint256 xy, - uint256 yx, uint256 yy + 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), @@ -164,55 +116,51 @@ library BN256G2 { ); } - 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 _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 + uint256 xx, + uint256 xy, + uint256 yx, + uint256 yy ) internal pure returns (uint256, uint256) { - return ( - addmod(xx, yx, FIELD_MODULUS), - addmod(xy, yy, FIELD_MODULUS) - ); + return (addmod(xx, yx, FIELD_MODULUS), addmod(xy, yy, FIELD_MODULUS)); } function _FQ2Sub( - uint256 xx, uint256 xy, - uint256 yx, uint256 yy + 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) - ); + return (submod(xx, yx, FIELD_MODULUS), submod(xy, yy, FIELD_MODULUS)); } function _FQ2Div( - uint256 xx, uint256 xy, - uint256 yx, uint256 yy + 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) + 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 + uint256 xx, + uint256 xy, + uint256 yx, + uint256 yy ) internal pure returns (bool) { uint256 yyx; uint256 yyy; @@ -231,11 +179,11 @@ library BN256G2 { 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) + 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) } @@ -243,13 +191,13 @@ library BN256G2 { } 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 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); @@ -258,139 +206,134 @@ library BN256G2 { } 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; - } + 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 + (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 - ); + 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 + (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 + 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 + (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) = _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) = _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) = _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 + (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 + 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); + 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 - ); + (pt1xx, pt1xy, pt1yx, pt1yy, pt1zx, pt1zy) = + _ECTwistDoubleJacobian(pt1xx, pt1xy, pt1yx, pt1yy, pt1zx, pt1zy); d = d / 2; } } -} \ No newline at end of file +} From beffb0be25c46815d8c0aaf8fd5c5791157861e3 Mon Sep 17 00:00:00 2001 From: astodialo Date: Sat, 5 Apr 2025 22:05:06 +0200 Subject: [PATCH 22/32] fix: clean up --- test/unit/OperatorStateRetrieverUnit.t.sol | 306 ++++++++++++--------- 1 file changed, 173 insertions(+), 133 deletions(-) diff --git a/test/unit/OperatorStateRetrieverUnit.t.sol b/test/unit/OperatorStateRetrieverUnit.t.sol index aa8fc642..5c7e8635 100644 --- a/test/unit/OperatorStateRetrieverUnit.t.sol +++ b/test/unit/OperatorStateRetrieverUnit.t.sol @@ -7,7 +7,6 @@ import {ISlashingRegistryCoordinatorTypes} from "../../src/interfaces/IRegistryC import {IBLSSignatureCheckerTypes} from "../../src/interfaces/IBLSSignatureChecker.sol"; import {BN256G2} from "../../src/libraries/BN256G2.sol"; - contract OperatorStateRetrieverUnitTests is MockAVSDeployer { using BN254 for BN254.G1Point; @@ -732,35 +731,39 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { } // helper function to generate a G2 point from a scalar - function _makeG2Point(uint256 scalar) internal returns (BN254.G2Point memory) { + 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] - ); + 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) - { + 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], + 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] + 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] + 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; } @@ -775,7 +778,9 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { address otherOperator = _incrementAddress(defaultOperator, 1); BN254.G1Point memory otherPubKey = BN254.G1Point(1, 2); - _registerOperatorWithCoordinator(otherOperator, quorumBitmapThree, otherPubKey, defaultStake - 1); + _registerOperatorWithCoordinator( + otherOperator, quorumBitmapThree, otherPubKey, defaultStake - 1 + ); // Generate actual G2 pubkeys BN254.G2Point memory op1G2 = _makeG2Point(2); @@ -804,14 +809,10 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { quorumNumbers[1] = bytes1(uint8(1)); // Call the function under test - IBLSSignatureCheckerTypes.NonSignerStakesAndSignature memory result = - operatorStateRetriever.getNonSignerStakesAndSignature( - registryCoordinator, - quorumNumbers, - dummySigma, - signingOperators, - uint32(block.number) - ); + IBLSSignatureCheckerTypes.NonSignerStakesAndSignature memory result = operatorStateRetriever + .getNonSignerStakesAndSignature( + registryCoordinator, quorumNumbers, dummySigma, signingOperators, uint32(block.number) + ); // Non-signers assertEq(result.nonSignerQuorumBitmapIndices.length, 0, "Should have no non-signer"); @@ -819,8 +820,10 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { // 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)); + (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"); @@ -846,7 +849,11 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { 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.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"); } @@ -861,7 +868,9 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { address otherOperator = _incrementAddress(defaultOperator, 1); BN254.G1Point memory otherPubKey = BN254.G1Point(1, 2); - _registerOperatorWithCoordinator(otherOperator, quorumBitmapThree, otherPubKey, defaultStake - 1); + _registerOperatorWithCoordinator( + otherOperator, quorumBitmapThree, otherPubKey, defaultStake - 1 + ); // Generate actual G2 pubkeys BN254.G2Point memory op1G2 = _makeG2Point(2); @@ -890,14 +899,10 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { quorumNumbers[1] = bytes1(uint8(1)); // Call under test - IBLSSignatureCheckerTypes.NonSignerStakesAndSignature memory result = - operatorStateRetriever.getNonSignerStakesAndSignature( - registryCoordinator, - quorumNumbers, - dummySigma, - signingOperators, - uint32(block.number) - ); + IBLSSignatureCheckerTypes.NonSignerStakesAndSignature memory result = operatorStateRetriever + .getNonSignerStakesAndSignature( + registryCoordinator, quorumNumbers, dummySigma, signingOperators, uint32(block.number) + ); // Validate // One non-signer => otherOperator @@ -906,8 +911,10 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { // 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)); + (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"); @@ -933,9 +940,21 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { // 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"); + 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 { @@ -948,7 +967,9 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { address otherOperator = _incrementAddress(defaultOperator, 1); BN254.G1Point memory otherPubKey = BN254.G1Point(1, 2); - _registerOperatorWithCoordinator(otherOperator, quorumBitmapThree, otherPubKey, defaultStake - 1); + _registerOperatorWithCoordinator( + otherOperator, quorumBitmapThree, otherPubKey, defaultStake - 1 + ); // Generate actual G2 pubkeys BN254.G2Point memory op1G2 = _makeG2Point(2); @@ -982,14 +1003,14 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { registryCoordinator.deregisterOperator(BitmapUtils.bitmapToBytesArray(quorumBitmapThree)); // Call the function under test - IBLSSignatureCheckerTypes.NonSignerStakesAndSignature memory result = - operatorStateRetriever.getNonSignerStakesAndSignature( - registryCoordinator, - quorumNumbers, - dummySigma, - signingOperators, - registrationBlockNumber - ); + IBLSSignatureCheckerTypes.NonSignerStakesAndSignature memory result = operatorStateRetriever + .getNonSignerStakesAndSignature( + registryCoordinator, + quorumNumbers, + dummySigma, + signingOperators, + registrationBlockNumber + ); // Non-signers assertEq(result.nonSignerQuorumBitmapIndices.length, 0, "Should have no non-signer"); @@ -997,8 +1018,10 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { // 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)); + (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"); @@ -1024,18 +1047,22 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { 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.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 - + 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( @@ -1043,50 +1070,54 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { 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 - + signingOperators[1] = unregisteredOperator; // This operator was never registered + bytes memory quorumNumbers = new bytes(1); - quorumNumbers[0] = bytes1(uint8(0)); // Quorum 0 - + 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")); + cheats.expectRevert( + bytes( + "RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId" + ) + ); operatorStateRetriever.getNonSignerStakesAndSignature( - registryCoordinator, - quorumNumbers, - dummySigma, - signingOperators, - uint32(block.number) + registryCoordinator, quorumNumbers, dummySigma, signingOperators, uint32(block.number) ); } - function test_getNonSignerStakesAndSignature_revert_signerRegisteredAfterReferenceBlock() public { + function test_getNonSignerStakesAndSignature_revert_signerRegisteredAfterReferenceBlock() + public + { // Setup - register one operator - uint256 quorumBitmap = 1; // Quorum 0 only - + 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); - + _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), @@ -1097,44 +1128,48 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { 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 - + 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")); + cheats.expectRevert( + bytes( + "RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId" + ) + ); operatorStateRetriever.getNonSignerStakesAndSignature( - registryCoordinator, - quorumNumbers, - dummySigma, - signingOperators, - initialBlock + 5 + registryCoordinator, quorumNumbers, dummySigma, signingOperators, initialBlock + 5 ); } - function test_getNonSignerStakesAndSignature_revert_signerDeregisteredAtReferenceBlock() public { + function test_getNonSignerStakesAndSignature_revert_signerDeregisteredAtReferenceBlock() + public + { // Setup - register two operators - uint256 quorumBitmap = 1; // Quorum 0 only - + 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); - + _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), @@ -1145,41 +1180,37 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { 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 - + signingOperators[1] = secondOperator; // This operator is deregistered + bytes memory quorumNumbers = new bytes(1); - quorumNumbers[0] = bytes1(uint8(0)); // Quorum 0 - + quorumNumbers[0] = bytes1(uint8(0)); // Quorum 0 + // Should revert because secondOperator was deregistered cheats.expectRevert(OperatorStateRetriever.OperatorNotRegistered.selector); operatorStateRetriever.getNonSignerStakesAndSignature( - registryCoordinator, - quorumNumbers, - dummySigma, - signingOperators, - uint32(block.number) + registryCoordinator, quorumNumbers, dummySigma, signingOperators, uint32(block.number) ); } function test_getNonSignerStakesAndSignature_revert_quorumNotCreatedAtCallTime() public { // Setup - register one operator - uint256 quorumBitmap = 1; // Quorum 0 only - + 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( @@ -1187,20 +1218,24 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { 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 - + 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")); + cheats.expectRevert( + bytes( + "IndexRegistry._operatorCountAtBlockNumber: quorum did not exist at given block number" + ) + ); operatorStateRetriever.getNonSignerStakesAndSignature( registryCoordinator, invalidQuorumNumbers, @@ -1212,17 +1247,17 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { function test_getNonSignerStakesAndSignature_revert_quorumNotCreatedAtReferenceBlock() public { // Setup - register one operator in quorum 0 - uint256 quorumBitmap = 1; - + 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, @@ -1242,7 +1277,7 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { registryCoordinator.createTotalDelegatedStakeQuorum( operatorSetParams, minimumStake, strategyParams ); - + // Create G2 points for the operator BN254.G2Point memory op1G2 = _makeG2Point(2); vm.mockCall( @@ -1250,31 +1285,36 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { 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)); - + 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")); + cheats.expectRevert( + bytes( + "IndexRegistry._operatorCountAtBlockNumber: quorum did not exist at given block number" + ) + ); operatorStateRetriever.getNonSignerStakesAndSignature( - registryCoordinator, - newQuorumNumbers, - dummySigma, - signingOperators, - initialBlock + 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); + 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++) { From 463166c5026708f2a2cd89a06dd67308e2448c76 Mon Sep 17 00:00:00 2001 From: rubydusa Date: Sun, 6 Apr 2025 19:14:47 +0300 Subject: [PATCH 23/32] fix: type in `_computeG1APK` natspec --- src/OperatorStateRetriever.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OperatorStateRetriever.sol b/src/OperatorStateRetriever.sol index ef451fa2..6f623576 100644 --- a/src/OperatorStateRetriever.sol +++ b/src/OperatorStateRetriever.sol @@ -383,7 +383,7 @@ contract OperatorStateRetriever { /** * @notice Computes the aggregate public key (APK) in G1 for a list of operators - * @dev Aggregetes the individual G1 public keys of operators by adding them together + * @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 From 0530e778a52e8ca3c36ae580b161430113a015cc Mon Sep 17 00:00:00 2001 From: rubydusa Date: Sun, 6 Apr 2025 19:15:33 +0300 Subject: [PATCH 24/32] fix: incorrect curve equation in doc comment --- src/OperatorStateRetriever.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OperatorStateRetriever.sol b/src/OperatorStateRetriever.sol index 6f623576..647e502e 100644 --- a/src/OperatorStateRetriever.sol +++ b/src/OperatorStateRetriever.sol @@ -405,7 +405,7 @@ contract OperatorStateRetriever { /** * @notice Checks if a point lies on the BN254 elliptic curve - * @dev The curve equation is y^2 = x^3 + 2 (mod p) (https://neuromancer.sk/std/bn/bn254) + * @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 */ From f5adbcac55d9336cd646ce71bc467aa7e20f1a12 Mon Sep 17 00:00:00 2001 From: steven <12021290+stevennevins@users.noreply.github.com> Date: Mon, 7 Apr 2025 10:42:00 -0400 Subject: [PATCH 25/32] test: upgrade on eigenda holesky (#447) **Motivation:** Add coverage for with eigenda's contracts **Modifications:** Add tests **Result:** Validate upgrade between m2 state and post slashing state of contracts --------- Co-authored-by: Alex <18387287+wadealexc@users.noreply.github.com> --- src/interfaces/IRegistryCoordinator.sol | 8 + test/fork/EigenDA.t.sol | 1130 +++++++++++++++++++++++ test/utils/EigenDA_Holesky.json | 30 + test/utils/OperatorLib.sol | 27 +- 4 files changed, 1189 insertions(+), 6 deletions(-) create mode 100644 test/fork/EigenDA.t.sol create mode 100644 test/utils/EigenDA_Holesky.json 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/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/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( From 2899e89fe236926cd4f314b1c10e40a7eb5a990c Mon Sep 17 00:00:00 2001 From: rubydusa Date: Wed, 9 Apr 2025 12:22:34 +0300 Subject: [PATCH 26/32] fix: typo --- src/OperatorStateRetriever.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OperatorStateRetriever.sol b/src/OperatorStateRetriever.sol index 647e502e..e08af251 100644 --- a/src/OperatorStateRetriever.sol +++ b/src/OperatorStateRetriever.sol @@ -237,7 +237,7 @@ contract OperatorStateRetriever { } // avoid stack too deep - struct GetNontSignerStakesAndSignatureMemory { + struct GetNonSignerStakesAndSignatureMemory { BN254.G1Point[] quorumApks; BN254.G2Point apkG2; IIndexRegistry indexRegistry; @@ -270,7 +270,7 @@ contract OperatorStateRetriever { address[] calldata operators, uint32 blockNumber ) external view returns (IBLSSignatureCheckerTypes.NonSignerStakesAndSignature memory) { - GetNontSignerStakesAndSignatureMemory memory m; + GetNonSignerStakesAndSignatureMemory memory m; m.quorumApks = new BN254.G1Point[](quorumNumbers.length); m.indexRegistry = registryCoordinator.indexRegistry(); m.blsApkRegistry = registryCoordinator.blsApkRegistry(); From 235a7d104e0eba2fb89b7b2bcff9a063a624b6cf Mon Sep 17 00:00:00 2001 From: rubydusa Date: Wed, 9 Apr 2025 12:25:38 +0300 Subject: [PATCH 27/32] chore: add comment on g2 apk loop --- src/OperatorStateRetriever.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/OperatorStateRetriever.sol b/src/OperatorStateRetriever.sol index e08af251..dde5c8f6 100644 --- a/src/OperatorStateRetriever.sol +++ b/src/OperatorStateRetriever.sol @@ -278,6 +278,7 @@ contract OperatorStateRetriever { // 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]); From ff9b6b9c8875cbde61ca3fc4b221fb36b7d1d46a Mon Sep 17 00:00:00 2001 From: rubydusa <97784987+rubydusa@users.noreply.github.com> Date: Thu, 10 Apr 2025 21:14:00 +0300 Subject: [PATCH 28/32] chore: explain `InvalidSigma()` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Tomás Grüner <47506558+MegaRedHand@users.noreply.github.com> --- src/OperatorStateRetriever.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/OperatorStateRetriever.sol b/src/OperatorStateRetriever.sol index dde5c8f6..8fa23c9e 100644 --- a/src/OperatorStateRetriever.sol +++ b/src/OperatorStateRetriever.sol @@ -30,6 +30,7 @@ contract OperatorStateRetriever { } error OperatorNotRegistered(); + /// @dev Thrown when the signature is not on the curve. error InvalidSigma(); /** From dd8aaffb5c603ab7da3b9665a1bc8c99c2ecf849 Mon Sep 17 00:00:00 2001 From: rubydusa Date: Thu, 10 Apr 2025 21:17:16 +0300 Subject: [PATCH 29/32] chore: add @dev comment about sigma --- src/OperatorStateRetriever.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/OperatorStateRetriever.sol b/src/OperatorStateRetriever.sol index 8fa23c9e..eef4520b 100644 --- a/src/OperatorStateRetriever.sol +++ b/src/OperatorStateRetriever.sol @@ -263,6 +263,7 @@ contract OperatorStateRetriever { * - 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, From 1e5736472ae23dd75b0460d4c5490c692a441e0d Mon Sep 17 00:00:00 2001 From: rubydusa Date: Thu, 10 Apr 2025 21:31:27 +0300 Subject: [PATCH 30/32] fix: check for correctness of indices and pubkeys in tests --- test/unit/OperatorStateRetrieverUnit.t.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/unit/OperatorStateRetrieverUnit.t.sol b/test/unit/OperatorStateRetrieverUnit.t.sol index 5c7e8635..15353f3d 100644 --- a/test/unit/OperatorStateRetrieverUnit.t.sol +++ b/test/unit/OperatorStateRetrieverUnit.t.sol @@ -907,7 +907,10 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { // 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"); From 32c57d9121f121068bbb49d5f454459a3e6a5569 Mon Sep 17 00:00:00 2001 From: rubydusa Date: Thu, 10 Apr 2025 22:40:36 +0300 Subject: [PATCH 31/32] chore: forge fmt for CI --- test/unit/OperatorStateRetrieverUnit.t.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/unit/OperatorStateRetrieverUnit.t.sol b/test/unit/OperatorStateRetrieverUnit.t.sol index 15353f3d..79c79746 100644 --- a/test/unit/OperatorStateRetrieverUnit.t.sol +++ b/test/unit/OperatorStateRetrieverUnit.t.sol @@ -907,7 +907,9 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { // 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.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"); From 3cf04f3b14ef0d45dcfc1126c8ade3cc9aac0362 Mon Sep 17 00:00:00 2001 From: Ron Turetzky Date: Tue, 22 Apr 2025 15:21:16 -0400 Subject: [PATCH 32/32] refactor: moving lib refactor: import path refactor: rename format: forge fmt --- src/OperatorStateRetriever.sol | 196 +-- .../BLSSigCheckOperatorStateRetriever.sol | 423 ++++++ src/{libraries => unaudited}/BN256G2.sol | 0 .../BLSSigCheckOperatorStateRetriever.t.sol | 1341 +++++++++++++++++ test/unit/OperatorStateRetrieverUnit.t.sol | 603 -------- 5 files changed, 1767 insertions(+), 796 deletions(-) create mode 100644 src/unaudited/BLSSigCheckOperatorStateRetriever.sol rename src/{libraries => unaudited}/BN256G2.sol (100%) create mode 100644 test/unit/BLSSigCheckOperatorStateRetriever.t.sol diff --git a/src/OperatorStateRetriever.sol b/src/OperatorStateRetriever.sol index eef4520b..d3e06490 100644 --- a/src/OperatorStateRetriever.sol +++ b/src/OperatorStateRetriever.sol @@ -3,13 +3,10 @@ pragma solidity ^0.8.27; import {ISlashingRegistryCoordinator} from "./interfaces/ISlashingRegistryCoordinator.sol"; 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 {BitmapUtils} from "./libraries/BitmapUtils.sol"; -import {BN254} from "./libraries/BN254.sol"; -import {BN256G2} from "./libraries/BN256G2.sol"; /** * @title OperatorStateRetriever with view functions that allow to retrieve the state of an AVSs registry system. @@ -30,8 +27,6 @@ contract OperatorStateRetriever { } 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.) @@ -116,9 +111,9 @@ contract OperatorStateRetriever { function getCheckSignaturesIndices( ISlashingRegistryCoordinator registryCoordinator, uint32 referenceBlockNumber, - bytes memory quorumNumbers, - bytes32[] memory nonSignerOperatorIds - ) public view returns (CheckSignaturesIndices memory) { + bytes calldata quorumNumbers, + bytes32[] calldata nonSignerOperatorIds + ) external view returns (CheckSignaturesIndices memory) { IStakeRegistry stakeRegistry = registryCoordinator.stakeRegistry(); CheckSignaturesIndices memory checkSignaturesIndices; @@ -236,189 +231,4 @@ contract OperatorStateRetriever { 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/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/libraries/BN256G2.sol b/src/unaudited/BN256G2.sol similarity index 100% rename from src/libraries/BN256G2.sol rename to src/unaudited/BN256G2.sol 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/unit/OperatorStateRetrieverUnit.t.sol b/test/unit/OperatorStateRetrieverUnit.t.sol index 79c79746..4020604f 100644 --- a/test/unit/OperatorStateRetrieverUnit.t.sol +++ b/test/unit/OperatorStateRetrieverUnit.t.sol @@ -4,8 +4,6 @@ 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/libraries/BN256G2.sol"; contract OperatorStateRetrieverUnitTests is MockAVSDeployer { using BN254 for BN254.G1Point; @@ -729,605 +727,4 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { 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 = operatorStateRetriever - .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 = operatorStateRetriever - .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 = operatorStateRetriever - .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" - ) - ); - operatorStateRetriever.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" - ) - ); - operatorStateRetriever.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(OperatorStateRetriever.OperatorNotRegistered.selector); - operatorStateRetriever.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" - ) - ); - operatorStateRetriever.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" - ) - ); - operatorStateRetriever.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; - } }