Skip to content
Merged
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
79 changes: 79 additions & 0 deletions src/contracts/multichain/ECDSATableCalculator.sol
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);
}
}
127 changes: 127 additions & 0 deletions src/contracts/multichain/ECDSATableCalculatorBase.sol
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;
}
}
Loading