Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/middlewareV2/tableCalculator/BN254TableCalculator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {IAllocationManager} from
"eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol";
import {IKeyRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IKeyRegistrar.sol";
import {BN254} from "eigenlayer-contracts/src/contracts/libraries/BN254.sol";

import "./BN254TableCalculatorBase.sol";

Expand All @@ -15,8 +16,10 @@ import "./BN254TableCalculatorBase.sol";
* @dev This contract assumes that slashable stake is valued the **same** across all strategies.
*/
contract BN254TableCalculator is BN254TableCalculatorBase {
using BN254 for BN254.G1Point;
// Immutables
/// @notice AllocationManager contract for managing operator allocations

IAllocationManager public immutable allocationManager;
/// @notice The default lookahead blocks for the slashable stake lookup
uint256 public immutable LOOKAHEAD_BLOCKS;
Expand Down
152 changes: 152 additions & 0 deletions src/middlewareV2/tableCalculator/BN254TableCalculatorBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {IOperatorTableCalculator} from
import {IKeyRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IKeyRegistrar.sol";
import {Merkle} from "eigenlayer-contracts/src/contracts/libraries/Merkle.sol";
import {BN254} from "eigenlayer-contracts/src/contracts/libraries/BN254.sol";
import {IBN254CertificateVerifierTypes} from
"eigenlayer-contracts/src/contracts/interfaces/IBN254CertificateVerifier.sol";
import {LeafCalculatorMixin} from
"eigenlayer-contracts/src/contracts/mixins/LeafCalculatorMixin.sol";
import {IBN254TableCalculator} from "../../interfaces/IBN254TableCalculator.sol";
Expand Down Expand Up @@ -98,6 +100,156 @@ abstract contract BN254TableCalculatorBase is IBN254TableCalculator, LeafCalcula
return operatorInfos;
}

/**
* @notice Returns the 0-based index of an operator within the operator table built by `_calculateOperatorTable`
* @param operatorSet The operator set context used to build the table
* @param operator The operator address whose index is requested
* @return found True if the operator is included in the table (registered), false otherwise
* @return index The 0-based index within the table when `found` is true; zero when `found` is false
* @dev The operator table is formed by iterating the arrays from `_getOperatorWeights(operatorSet)` and including
* only operators that are registered in `keyRegistrar` for the given `operatorSet`, preserving order.
* This function deterministically reconstructs that inclusion order to locate the operator's index.
*/
function getOperatorIndex(
OperatorSet calldata operatorSet,
address operator
) external view virtual returns (bool found, uint32 index) {
(address[] memory operators, uint256[][] memory weights) = _getOperatorWeights(operatorSet);

// Early return if there are no weights/operators
if (weights.length == 0) {
return (false, 0);
}

uint32 includedIndex = 0;
for (uint256 i = 0; i < operators.length; i++) {
// Skip unregistered operators, as they are not part of the operator table
if (!keyRegistrar.isRegistered(operatorSet, operators[i])) {
continue;
}

if (operators[i] == operator) {
return (true, includedIndex);
}

includedIndex++;
}

return (false, 0);
}

/**
* @notice Returns non-signer witnesses and aggregate non-signer G1 APK for a given set of signing operators
* @param operatorSet The operator set context
* @param signingOperators The list of operators that signed (addresses)
* @return nonSignerWitnesses The witnesses for operators that did not sign
* @return nonSignerApk The aggregate BN254 G1 public key of the non-signers
* @dev Reconstructs the operator info merkle tree deterministically to produce proofs and indices
*/
function getNonSignerWitnessesAndApk(
OperatorSet calldata operatorSet,
address[] calldata signingOperators
)
external
view
returns (
IBN254CertificateVerifierTypes.BN254OperatorInfoWitness[] memory nonSignerWitnesses,
BN254.G1Point memory nonSignerApk
)
{
// Fetch weights for all candidates in the operator set
(address[] memory operators, uint256[][] memory weights) = _getOperatorWeights(operatorSet);

// Early return if no operators with weights
if (weights.length == 0) {
return (
new IBN254CertificateVerifierTypes.BN254OperatorInfoWitness[](0),
BN254.G1Point(0, 0)
);
}

// Build merkle leaves and parallel arrays only for registered operators,
// preserving deterministic ordering required for operatorIndex
bytes32[] memory operatorInfoLeaves = new bytes32[](operators.length);
BN254OperatorInfo[] memory includedOperatorInfos = new BN254OperatorInfo[](operators.length);
address[] memory includedOperators = new address[](operators.length);
uint256 includedCount = 0;

for (uint256 i = 0; i < operators.length; i++) {
if (!keyRegistrar.isRegistered(operatorSet, operators[i])) {
continue;
}

(BN254.G1Point memory g1Point,) = keyRegistrar.getBN254Key(operatorSet, operators[i]);
BN254OperatorInfo memory info =
BN254OperatorInfo({pubkey: g1Point, weights: weights[i]});
includedOperatorInfos[includedCount] = info;
includedOperators[includedCount] = operators[i];
operatorInfoLeaves[includedCount] = calculateOperatorInfoLeaf(info);
includedCount++;
}

// If nothing included after filtering for registration, return empty
if (includedCount == 0) {
return (
new IBN254CertificateVerifierTypes.BN254OperatorInfoWitness[](0),
BN254.G1Point(0, 0)
);
}

// Resize arrays to included count
assembly {
mstore(operatorInfoLeaves, includedCount)
mstore(includedOperatorInfos, includedCount)
mstore(includedOperators, includedCount)
}

// Count non-signers
uint256 nonSignerCount = 0;
for (uint256 idx = 0; idx < includedCount; idx++) {
bool signerFound = false;
for (uint256 k = 0; k < signingOperators.length; k++) {
if (signingOperators[k] == includedOperators[idx]) {
signerFound = true;
break;
}
}
if (!signerFound) {
nonSignerCount++;
}
}

// Prepare outputs
nonSignerWitnesses =
new IBN254CertificateVerifierTypes.BN254OperatorInfoWitness[](nonSignerCount);
nonSignerApk = BN254.G1Point(0, 0);

// Populate witnesses in increasing operatorIndex order and accumulate APK
uint256 out = 0;
for (uint256 idx2 = 0; idx2 < includedCount; idx2++) {
bool signerFound2 = false;
for (uint256 k2 = 0; k2 < signingOperators.length; k2++) {
if (signingOperators[k2] == includedOperators[idx2]) {
signerFound2 = true;
break;
}
}
if (signerFound2) continue;

BN254OperatorInfo memory opInfo = includedOperatorInfos[idx2];
nonSignerApk = nonSignerApk.plus(opInfo.pubkey);

nonSignerWitnesses[out] = IBN254CertificateVerifierTypes.BN254OperatorInfoWitness({
operatorIndex: uint32(idx2),
operatorInfoProof: Merkle.getProofKeccak(operatorInfoLeaves, idx2),
operatorInfo: opInfo
});
out++;
}

return (nonSignerWitnesses, nonSignerApk);
}

/**
* @notice Abstract function to get the operator weights for a given operatorSet
* @param operatorSet The operatorSet to get the weights for
Expand Down
Loading
Loading