Skip to content

Commit 065e5a6

Browse files
eigenmikemypatil12
authored andcommitted
feat: ecdsa table calculator (#1473)
**Motivation:** Multichain needs a table calculator base implementation for ECDSA signtures. **Modifications:** New contracts, `ECDSATableCalculator` and `ECDSATableCalculatorBase`. **Result:** Multichain contracts complete
1 parent ff466c6 commit 065e5a6

File tree

3 files changed

+702
-0
lines changed

3 files changed

+702
-0
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.27;
3+
4+
import "./ECDSATableCalculatorBase.sol";
5+
import "../interfaces/IAllocationManager.sol";
6+
7+
/**
8+
* @title ECDSATableCalculator
9+
* @notice Implementation that calculates ECDSA operator tables using the sum of the minimum slashable stake weights
10+
*/
11+
contract ECDSATableCalculator is ECDSATableCalculatorBase {
12+
// Immutables
13+
/// @notice AllocationManager contract for managing operator allocations
14+
IAllocationManager public immutable allocationManager;
15+
/// @notice The default lookahead blocks for the slashable stake lookup
16+
uint256 public immutable LOOKAHEAD_BLOCKS;
17+
18+
constructor(
19+
IKeyRegistrar _keyRegistrar,
20+
IAllocationManager _allocationManager,
21+
uint256 _LOOKAHEAD_BLOCKS
22+
) ECDSATableCalculatorBase(_keyRegistrar) {
23+
allocationManager = _allocationManager;
24+
LOOKAHEAD_BLOCKS = _LOOKAHEAD_BLOCKS;
25+
}
26+
27+
/**
28+
* @notice Get the operator weights for a given operatorSet based on the slashable stake.
29+
* @param operatorSet The operatorSet to get the weights for
30+
* @return operators The addresses of the operators in the operatorSet
31+
* @return weights The weights for each operator in the operatorSet, this is a 2D array where the first index is the operator
32+
* and the second index is the type of weight. In this case its of length 1 and returns the slashable stake for the operatorSet.
33+
*/
34+
function _getOperatorWeights(
35+
OperatorSet calldata operatorSet
36+
) internal view override returns (address[] memory operators, uint256[][] memory weights) {
37+
// Get all operators & strategies in the operatorSet
38+
address[] memory registeredOperators = allocationManager.getMembers(operatorSet);
39+
IStrategy[] memory strategies = allocationManager.getStrategiesInOperatorSet(operatorSet);
40+
41+
// Get the minimum slashable stake for each operator
42+
uint256[][] memory minSlashableStake = allocationManager.getMinimumSlashableStake({
43+
operatorSet: operatorSet,
44+
operators: registeredOperators,
45+
strategies: strategies,
46+
futureBlock: uint32(block.number + LOOKAHEAD_BLOCKS)
47+
});
48+
49+
operators = new address[](registeredOperators.length);
50+
weights = new uint256[][](registeredOperators.length);
51+
uint256 operatorCount = 0;
52+
for (uint256 i = 0; i < registeredOperators.length; ++i) {
53+
// For the given operator, loop through the strategies and sum together to calculate the operator's weight for the operatorSet
54+
uint256 totalWeight;
55+
for (uint256 stratIndex = 0; stratIndex < strategies.length; ++stratIndex) {
56+
totalWeight += minSlashableStake[i][stratIndex];
57+
}
58+
59+
// If the operator has nonzero slashable stake, add them to the operators array
60+
if (totalWeight > 0) {
61+
// Initialize operator weights array of length 1 just for slashable stake
62+
weights[operatorCount] = new uint256[](1);
63+
weights[operatorCount][0] = totalWeight;
64+
65+
// Add the operator to the operators array
66+
operators[operatorCount] = registeredOperators[i];
67+
operatorCount++;
68+
}
69+
}
70+
71+
// Resize arrays to be the size of the number of operators with nonzero slashable stake
72+
assembly {
73+
mstore(operators, operatorCount)
74+
mstore(weights, operatorCount)
75+
}
76+
77+
return (operators, weights);
78+
}
79+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.27;
3+
4+
import "../interfaces/IECDSATableCalculator.sol";
5+
import "../interfaces/IKeyRegistrar.sol";
6+
import "../libraries/Merkle.sol";
7+
8+
/**
9+
* @title ECDSATableCalculatorBase
10+
* @notice Abstract contract that provides base functionality for calculating ECDSA operator tables
11+
* @dev This contract contains all the core logic for operator table calculations,
12+
* with weight calculation left to be implemented by derived contracts
13+
*/
14+
abstract contract ECDSATableCalculatorBase is IECDSATableCalculator {
15+
using Merkle for bytes32[];
16+
17+
// Immutables
18+
/// @notice KeyRegistrar contract for managing operator keys
19+
IKeyRegistrar public immutable keyRegistrar;
20+
21+
constructor(
22+
IKeyRegistrar _keyRegistrar
23+
) {
24+
keyRegistrar = _keyRegistrar;
25+
}
26+
27+
/// @inheritdoc IECDSATableCalculator
28+
function calculateOperatorTable(
29+
OperatorSet calldata operatorSet
30+
) external view virtual returns (ECDSAOperatorInfo[] memory operatorInfos) {
31+
return _calculateOperatorTable(operatorSet);
32+
}
33+
34+
/// @inheritdoc IOperatorTableCalculator
35+
function calculateOperatorTableBytes(
36+
OperatorSet calldata operatorSet
37+
) external view virtual returns (bytes memory operatorTableBytes) {
38+
return abi.encode(_calculateOperatorTable(operatorSet));
39+
}
40+
41+
/// @inheritdoc IOperatorTableCalculator
42+
function getOperatorWeights(
43+
OperatorSet calldata operatorSet
44+
) external view virtual returns (address[] memory operators, uint256[][] memory weights) {
45+
return _getOperatorWeights(operatorSet);
46+
}
47+
48+
/// @inheritdoc IOperatorTableCalculator
49+
function getOperatorWeight(
50+
OperatorSet calldata operatorSet,
51+
address operator
52+
) external view virtual returns (uint256 weight) {
53+
(address[] memory operators, uint256[][] memory weights) = _getOperatorWeights(operatorSet);
54+
55+
// Find the index of the operator in the operators array
56+
for (uint256 i = 0; i < operators.length; i++) {
57+
if (operators[i] == operator) {
58+
return weights[i][0];
59+
}
60+
}
61+
62+
return 0;
63+
}
64+
65+
/**
66+
* @notice Abstract function to get the operator weights for a given operatorSet
67+
* @param operatorSet The operatorSet to get the weights for
68+
* @return operators The addresses of the operators in the operatorSet
69+
* @return weights The weights for each operator in the operatorSet, this is a 2D array where the first index is the operator
70+
* and the second index is the type of weight
71+
* @dev Must be implemented by derived contracts to define specific weight calculation logic
72+
*/
73+
function _getOperatorWeights(
74+
OperatorSet calldata operatorSet
75+
) internal view virtual returns (address[] memory operators, uint256[][] memory weights);
76+
77+
/**
78+
* @notice Calculates the operator table for a given operatorSet
79+
* @param operatorSet The operatorSet to calculate the operator table for
80+
* @return operatorInfos The operator table for the given operatorSet
81+
* @dev This function:
82+
* 1. Gets operator weights from the weight calculator
83+
* 2. Creates ECDSAOperatorInfo structs for each operator with registered ECDSA keys
84+
*/
85+
function _calculateOperatorTable(
86+
OperatorSet calldata operatorSet
87+
) internal view returns (ECDSAOperatorInfo[] memory operatorInfos) {
88+
// Get the weights for all operators in the operatorSet
89+
(address[] memory operators, uint256[][] memory weights) = _getOperatorWeights(operatorSet);
90+
91+
// If there are no weights, return an empty array
92+
if (weights.length == 0) {
93+
return new ECDSAOperatorInfo[](0);
94+
}
95+
96+
// Create the operator infos array with maximum possible size
97+
operatorInfos = new ECDSAOperatorInfo[](operators.length);
98+
uint256 operatorCount = 0;
99+
100+
for (uint256 i = 0; i < operators.length; i++) {
101+
// Skip if the operator has not registered their ECDSA key
102+
if (!keyRegistrar.isRegistered(operatorSet, operators[i])) {
103+
continue;
104+
}
105+
106+
// Get the ECDSA address (public key) for the operator
107+
address ecdsaAddress = keyRegistrar.getECDSAAddress(operatorSet, operators[i]);
108+
109+
// Create the ECDSAOperatorInfo struct
110+
operatorInfos[operatorCount] = ECDSAOperatorInfo({pubkey: ecdsaAddress, weights: weights[i]});
111+
112+
operatorCount++;
113+
}
114+
115+
// If no operators have registered keys, return empty array
116+
if (operatorCount == 0) {
117+
return new ECDSAOperatorInfo[](0);
118+
}
119+
120+
// Resize the array to the actual number of operators with registered keys
121+
assembly {
122+
mstore(operatorInfos, operatorCount)
123+
}
124+
125+
return operatorInfos;
126+
}
127+
}

0 commit comments

Comments
 (0)