-
Notifications
You must be signed in to change notification settings - Fork 438
feat: ecdsa table calculator #1473
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
eigenmikem
merged 3 commits into
release-dev/multichain
from
feat/mike-ecdsa-table-calculator
Jun 23, 2025
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity ^0.8.27; | ||
|
||
import "./ECDSATableCalculatorBase.sol"; | ||
import "../interfaces/IAllocationManager.sol"; | ||
|
||
/** | ||
* @title ECDSATableCalculator | ||
* @notice Implementation that calculates ECDSA operator tables using the sum of the minimum slashable stake weights | ||
*/ | ||
contract ECDSATableCalculator is ECDSATableCalculatorBase { | ||
// 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; | ||
|
||
constructor( | ||
IKeyRegistrar _keyRegistrar, | ||
IAllocationManager _allocationManager, | ||
uint256 _LOOKAHEAD_BLOCKS | ||
) ECDSATableCalculatorBase(_keyRegistrar) { | ||
allocationManager = _allocationManager; | ||
LOOKAHEAD_BLOCKS = _LOOKAHEAD_BLOCKS; | ||
} | ||
|
||
/** | ||
* @notice Get the operator weights for a given operatorSet based on the slashable stake. | ||
* @param operatorSet The operatorSet to get the weights for | ||
* @return operators The addresses of the operators in the operatorSet | ||
* @return weights The weights for each operator in the operatorSet, this is a 2D array where the first index is the operator | ||
* and the second index is the type of weight. In this case its of length 1 and returns the slashable stake for the operatorSet. | ||
*/ | ||
function _getOperatorWeights( | ||
OperatorSet calldata operatorSet | ||
) internal view override returns (address[] memory operators, uint256[][] memory weights) { | ||
// Get all operators & strategies in the operatorSet | ||
address[] memory registeredOperators = allocationManager.getMembers(operatorSet); | ||
IStrategy[] memory strategies = allocationManager.getStrategiesInOperatorSet(operatorSet); | ||
|
||
// Get the minimum slashable stake for each operator | ||
uint256[][] memory minSlashableStake = allocationManager.getMinimumSlashableStake({ | ||
operatorSet: operatorSet, | ||
operators: registeredOperators, | ||
strategies: strategies, | ||
futureBlock: uint32(block.number + LOOKAHEAD_BLOCKS) | ||
}); | ||
|
||
operators = new address[](registeredOperators.length); | ||
weights = new uint256[][](registeredOperators.length); | ||
uint256 operatorCount = 0; | ||
for (uint256 i = 0; i < registeredOperators.length; ++i) { | ||
// For the given operator, loop through the strategies and sum together to calculate the operator's weight for the operatorSet | ||
uint256 totalWeight; | ||
for (uint256 stratIndex = 0; stratIndex < strategies.length; ++stratIndex) { | ||
totalWeight += minSlashableStake[i][stratIndex]; | ||
} | ||
|
||
// If the operator has nonzero slashable stake, add them to the operators array | ||
if (totalWeight > 0) { | ||
// Initialize operator weights array of length 1 just for slashable stake | ||
weights[operatorCount] = new uint256[](1); | ||
weights[operatorCount][0] = totalWeight; | ||
|
||
// Add the operator to the operators array | ||
operators[operatorCount] = registeredOperators[i]; | ||
operatorCount++; | ||
} | ||
} | ||
|
||
// Resize arrays to be the size of the number of operators with nonzero slashable stake | ||
assembly { | ||
mstore(operators, operatorCount) | ||
mstore(weights, operatorCount) | ||
} | ||
|
||
return (operators, weights); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity ^0.8.27; | ||
|
||
import "../interfaces/IECDSATableCalculator.sol"; | ||
import "../interfaces/IKeyRegistrar.sol"; | ||
import "../libraries/Merkle.sol"; | ||
|
||
/** | ||
* @title ECDSATableCalculatorBase | ||
* @notice Abstract contract that provides base functionality for calculating ECDSA operator tables | ||
* @dev This contract contains all the core logic for operator table calculations, | ||
* with weight calculation left to be implemented by derived contracts | ||
*/ | ||
abstract contract ECDSATableCalculatorBase is IECDSATableCalculator { | ||
using Merkle for bytes32[]; | ||
|
||
// Immutables | ||
/// @notice KeyRegistrar contract for managing operator keys | ||
IKeyRegistrar public immutable keyRegistrar; | ||
|
||
constructor( | ||
IKeyRegistrar _keyRegistrar | ||
) { | ||
keyRegistrar = _keyRegistrar; | ||
} | ||
|
||
/// @inheritdoc IECDSATableCalculator | ||
function calculateOperatorTable( | ||
OperatorSet calldata operatorSet | ||
) external view virtual returns (ECDSAOperatorInfo[] memory operatorInfos) { | ||
return _calculateOperatorTable(operatorSet); | ||
} | ||
|
||
/// @inheritdoc IOperatorTableCalculator | ||
function calculateOperatorTableBytes( | ||
OperatorSet calldata operatorSet | ||
) external view virtual returns (bytes memory operatorTableBytes) { | ||
return abi.encode(_calculateOperatorTable(operatorSet)); | ||
} | ||
|
||
/// @inheritdoc IOperatorTableCalculator | ||
function getOperatorWeights( | ||
OperatorSet calldata operatorSet | ||
) external view virtual returns (address[] memory operators, uint256[][] memory weights) { | ||
return _getOperatorWeights(operatorSet); | ||
} | ||
|
||
/// @inheritdoc IOperatorTableCalculator | ||
function getOperatorWeight( | ||
OperatorSet calldata operatorSet, | ||
address operator | ||
) external view virtual returns (uint256 weight) { | ||
(address[] memory operators, uint256[][] memory weights) = _getOperatorWeights(operatorSet); | ||
|
||
// Find the index of the operator in the operators array | ||
for (uint256 i = 0; i < operators.length; i++) { | ||
if (operators[i] == operator) { | ||
return weights[i][0]; | ||
} | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
/** | ||
* @notice Abstract function to get the operator weights for a given operatorSet | ||
* @param operatorSet The operatorSet to get the weights for | ||
* @return operators The addresses of the operators in the operatorSet | ||
* @return weights The weights for each operator in the operatorSet, this is a 2D array where the first index is the operator | ||
* and the second index is the type of weight | ||
* @dev Must be implemented by derived contracts to define specific weight calculation logic | ||
*/ | ||
function _getOperatorWeights( | ||
OperatorSet calldata operatorSet | ||
) internal view virtual returns (address[] memory operators, uint256[][] memory weights); | ||
|
||
/** | ||
* @notice Calculates the operator table for a given operatorSet | ||
* @param operatorSet The operatorSet to calculate the operator table for | ||
* @return operatorInfos The operator table for the given operatorSet | ||
* @dev This function: | ||
* 1. Gets operator weights from the weight calculator | ||
* 2. Creates ECDSAOperatorInfo structs for each operator with registered ECDSA keys | ||
*/ | ||
function _calculateOperatorTable( | ||
OperatorSet calldata operatorSet | ||
) internal view returns (ECDSAOperatorInfo[] memory operatorInfos) { | ||
// Get the weights for all operators in the operatorSet | ||
(address[] memory operators, uint256[][] memory weights) = _getOperatorWeights(operatorSet); | ||
|
||
// If there are no weights, return an empty array | ||
if (weights.length == 0) { | ||
return new ECDSAOperatorInfo[](0); | ||
} | ||
|
||
// Create the operator infos array with maximum possible size | ||
operatorInfos = new ECDSAOperatorInfo[](operators.length); | ||
uint256 operatorCount = 0; | ||
|
||
for (uint256 i = 0; i < operators.length; i++) { | ||
// Skip if the operator has not registered their ECDSA key | ||
if (!keyRegistrar.isRegistered(operatorSet, operators[i])) { | ||
continue; | ||
} | ||
|
||
// Get the ECDSA address (public key) for the operator | ||
address ecdsaAddress = keyRegistrar.getECDSAAddress(operatorSet, operators[i]); | ||
|
||
// Create the ECDSAOperatorInfo struct | ||
operatorInfos[operatorCount] = ECDSAOperatorInfo({pubkey: ecdsaAddress, weights: weights[i]}); | ||
|
||
operatorCount++; | ||
} | ||
|
||
// If no operators have registered keys, return empty array | ||
if (operatorCount == 0) { | ||
return new ECDSAOperatorInfo[](0); | ||
} | ||
|
||
// Resize the array to the actual number of operators with registered keys | ||
assembly { | ||
mstore(operatorInfos, operatorCount) | ||
} | ||
|
||
return operatorInfos; | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.