Skip to content

Instantly share code, notes, and snippets.

@ChaseTheLight01
Created February 28, 2024 19:31
Show Gist options
  • Save ChaseTheLight01/4a2c8f7d410854557ced3f2afa567682 to your computer and use it in GitHub Desktop.
Save ChaseTheLight01/4a2c8f7d410854557ced3f2afa567682 to your computer and use it in GitHub Desktop.
LightChaserV3_Cantina_EigenLayer_Middleware

LightChaser-V3

Twitter: @ChaseTheLight99

Generated for: Cantina : EigenLayer : eigenLayer-middleware

Generated on: 2024-02-28

Total findings: 190

Total HIGH findings: 0

Total Medium findings: 1

Total Low findings: 22

Total Gas findings: 64

Total Refactoring findings: 0

Total NonCritical findings: 103

Total Disputed findings: 0

Summary for Medium findings

Number Details Instances
[Medium-1] Privileged functions can create points of failure 10

Summary for Low findings

Number Details Instances
[Low-1] Missing checks for address(0x0) when updating address state variables 2
[Low-2] Low level calls in solidity versions preceding 0.8.14 can result in an optimiser bug 6
[Low-3] Upgradable contracts should have a __gap variable 4
[Low-4] Using > when declaring solidity version without specifying an upperbound can cause future vulnerabilities 1
[Low-5] Use SafeCast to safely downcast variables 26
[Low-6] Function calls within for loops 14
[Low-7] For loops in public or external functions should be avoided due to high gas costs and possible DOS 21
[Low-8] No limits when setting min/max amounts 1
[Low-9] Initializer function can be front run 1
[Low-10] Loss of precision 2
[Low-11] Use of onlyOwner functions can be lost 1
[Low-12] Critical functions should be a two step procedure 6
[Low-13] Constant decimal values 1
[Low-14] Arrays can grow in size without a way to shrink them 1
[Low-15] Draft imports may break in new minor versions 1
[Low-16] Unsafe uint to int conversion 1
[Low-17] Missing zero address check in initializer 1
[Low-18] Critical functions should have a timelock 6
[Low-19] Mapping arrays can grow in size without a way to shrink them 8
[Low-20] Consider implementing two-step procedure for updating protocol addresses 2
[Low-21] Prefer skip over revert model in iteration 4
[Low-22] Constructors missing validation 4

Summary for NonCritical findings

Number Details Instances
[NonCritical-1] A function which defines named returns in it's declaration doesn't need to use return 6
[NonCritical-2] Some if-statement can be converted to a ternary 5
[NonCritical-3] For loop iterates on arrays not indexed 7
[NonCritical-4] It is standard for all external and public functions to be override from an interface 81
[NonCritical-5] Using abi.encodePacked can result in hash collision when used in hashing functions 4
[NonCritical-6] Contracts do not use their OZ upgradable counterparts 1
[NonCritical-7] Default address(0) can be returned 4
[NonCritical-8] Consider adding emergency-stop functionality 5
[NonCritical-9] Employ Explicit Casting to Bytes or Bytes32 for Enhanced Code Clarity and Meaning 4
[NonCritical-10] It's not standard to end and begin a code object on the same line 1
[NonCritical-11] Consider making private state variables internal to increase flexibility 5
[NonCritical-12] Use of abi.encodePacked with dynamic types inside keccak256 4
[NonCritical-13] Inconsistent comment spacing 5
[NonCritical-14] Incorrect NatSpec Syntax 2
[NonCritical-15] Floating pragma should be avoided 1
[NonCritical-16] In functions which accept an address as a parameter, there should be a zero address check to prevent bugs 31
[NonCritical-17] Enum values should be used in place of constant array indexes 31
[NonCritical-18] Default address values are manually set 1
[NonCritical-19] Ownable2Step should be used in place of Ownable 2
[NonCritical-20] Revert statements within external and public functions can be used to perform DOS attacks 1
[NonCritical-21] Reverts should use customer errors instead of strings 2
[NonCritical-22] Functions which are either private or internal should have a preceding _ in their name 26
[NonCritical-23] Private and internal state variables should have a preceding _ in their name unless they are constants 1
[NonCritical-24] Contract lines should not be longer than 120 characters for readability 107
[NonCritical-25] Setters should prevent re-setting of the same value 6
[NonCritical-26] Use newer solidity versions 2
[NonCritical-27] Function names should differ to make the code more readable 16
[NonCritical-28] Functions should not be longer than 50 lines 1
[NonCritical-29] A function's visibility should be declared before any modifiers 1
[NonCritical-30] Functions within contracts are not ordered according to the solidity style guide 5
[NonCritical-31] Double type casts create complexity within the code 3
[NonCritical-32] Functions with array parameters should have length checks in place 6
[NonCritical-33] Interface imports should be declared first 4
[NonCritical-34] Upgradable contract constructor should have the initialize modifier 1
[NonCritical-35] Keccak hashes which never change can be made into a constant state variable 1
[NonCritical-36] Unused state variables present 2
[NonCritical-37] Constants should be on the left side of the 14
[NonCritical-38] Defined named returns not used within function 4
[NonCritical-39] Initialize functions do not emit an event 1
[NonCritical-40] Both immutable and constant state variables should be CONSTANT_CASE 10
[NonCritical-41] Consider using named mappings 8
[NonCritical-42] Use a single contract or library for system wide constants 1
[NonCritical-43] Consider using modifiers for address control 7
[NonCritical-44] Off-by-one timestamp error 1
[NonCritical-45] Variables should be used in place of magic numbers to improve readability 17
[NonCritical-46] Long powers of ten should use scientific notation 1eX 2
[NonCritical-47] Redundant else statement 6
[NonCritical-48] Constant array index within iteration 3
[NonCritical-49] Inconsistent usage of int/uint with int256/uint256 in contract/abstract/library/interface 1
[NonCritical-50] Use immutable not constant for keccak state variables 1
[NonCritical-51] Large or complicated code bases should implement invariant tests 1
[NonCritical-52] Empty bytes check is missing 26
[NonCritical-53] Use scopes sparingly 2
[NonCritical-54] Consider using SMTChecker 21
[NonCritical-55] Top level declarations should be separated by two blank lines 3
[NonCritical-56] Contracts should have full test coverage 6
[NonCritical-57] Assembly block creates dirty bits 3
[NonCritical-58] Whitespace in expressions 3
[NonCritical-59] Consider using named function calls 31
[NonCritical-60] Lack of space near the operator 3
[NonCritical-61] Lack Of Brace Spacing 44
[NonCritical-62] Using while for unbounded loops isn’t recommended 2
[NonCritical-63] Use of override is unnecessary 1
[NonCritical-64] Cyclomatic complexity in functions 1
[NonCritical-65] Owner can renounce while system is paused 1
[NonCritical-66] Contract does not follow the Solidity style guide's suggested layout ordering 2
[NonCritical-67] Consider adding formal verification proofs 6
[NonCritical-68] Unused import 1
[NonCritical-69] Consider bounding input array length 16
[NonCritical-70] Duplicated require() checks should be refactored to a modifier or function 1
[NonCritical-71] Missing events in sensitive functions 12
[NonCritical-72] Unchecked increments can overflow 1
[NonCritical-73] Unused local variable 1
[NonCritical-74] Don't only depend on Solidity's arithmetic ordering. 2
[NonCritical-75] It is best practice to use linear inheritance 1
[NonCritical-76] Contracts with only unimplemented functions can be labeled as abstract 1
[NonCritical-77] A event should be emitted if a non immutable state variable is set in a constructor 1
[NonCritical-78] Superfluous parameter can only be one value 5
[NonCritical-79] Immutable and constant integer state variables should not be casted 1
[NonCritical-80] Multiline comments should be terminated with '*/' and not '**/' 12
[NonCritical-81] Public variable declarations should have NatSpec descriptions 1
[NonCritical-82] Use the Modern Upgradeable Contract Paradigm 5
[NonCritical-83] Upgrade openzeppelin to the Latest Version - 5.0.0 3
[NonCritical-84] Use a struct to encapsulate multiple function parameters 6
[NonCritical-85] Contracts inherits pausable without utilising whenNotPaused 1
[NonCritical-86] Long numbers should include underscores to improve readability and prevent typos 13
[NonCritical-87] Consider using a format prettier or forge fmt 5
[NonCritical-88] Avoid defining a function in a single line including it's contents 80
[NonCritical-89] Use 'using' keyword when using specific imports rather than calling the specific import directly 64
[NonCritical-90] Use the same Solidity version in non library/interface files throughout the project 2
[NonCritical-91] Avoid declaring variables with the names of defined functions within the project 5
[NonCritical-92] Upgradeable contract uses non-upgradeable version of the OpenZeppelin libraries/contracts 1
[NonCritical-93] All verbatim blocks are considered identical by deduplicator and can incorrectly be unified 1
[NonCritical-94] Constructors should emit an event 10
[NonCritical-95] Avoid single line non empty object declarations 36
[NonCritical-96] Consider using 'using-for' syntax when using libraries 59
[NonCritical-97] Consider validating all user inputs 65
[NonCritical-98] Consider moving duplicated strings to constants 1
[NonCritical-99] Consider providing a ranged getter for array state variables 15
[NonCritical-100] Consider using named returns 75
[NonCritical-101] Avoid external calls in modifiers 9
[NonCritical-102] Consider migrating from Ownable to AccessControl 2
[NonCritical-103] While true/false loops can result in infinite loops 1

Summary for Gas findings

Number Details Instances Gas
[Gas-1] Multiple accesses of the same mapping/array key/index should be cached 7 4704
[Gas-2] The result of a function call should be cached rather than re-calling the function 1 100
[Gas-3] Shorten the array rather than copying to a new one 18 0.0
[Gas-4] Using bools for storage incurs overhead 4 1120
[Gas-5] Consider Using Solady's Gas Optimized Lib for Math 3 0.0
[Gas-6] It is a waste of GAS to emit variable literals 1 8
[Gas-7] There is a 32 byte length threshold for error strings, strings longer than this consume more gas 33 15246
[Gas-8] Public functions not used internally can be marked as external to save gas 1 0.0
[Gas-9] Calldata should be used in place of memory function parameters when not mutated 3 117
[Gas-10] Nested for loops should be avoided due to high gas costs resulting from O^2 time complexity 6 0.0
[Gas-11] Usage of smaller uint/int types causes overhead 96 506880
[Gas-12] Use != 0 instead of > 0 10 300
[Gas-13] Integer increments by one can be unchecked to save on gas fees 29 100920
[Gas-14] Default int values are manually reset 43 0.0
[Gas-15] Mappings used within a function more than once should be cached to save gas 1 100
[Gas-16] Use assembly to check for the zero address 2 0.0
[Gas-17] Divisions which do not divide by -X cannot overflow or overflow so such operations can be unchecked to save gas 3 0.0
[Gas-18] Redundant state variable getters 1 0.0
[Gas-19] Struct variables can be packed into fewer storage slots 2 10000
[Gas-20] Consider activating via-ir for deploying 21 110250
[Gas-21] Use bitmap to save gas 2 280
[Gas-22] Use assembly hashing 2 0.0
[Gas-23] Consider using OZ EnumerateSet in place of nested mappings 3 9000
[Gas-24] Use assembly to emit events 20 15200
[Gas-25] Use solady library where possible to save gas 1 1000
[Gas-26] Counting down in for statements is more gas efficient 22 0.0
[Gas-27] Using private rather than public for constants and immutables, saves gas 5 0.0
[Gas-28] Modulus operations that could be unchecked 3 765
[Gas-29] Mark Functions That Revert For Normal Users As payable 10 2500
[Gas-30] Function names can be optimized 6 4608
[Gas-31] Lack of unchecked in loops 23 40020
[Gas-32] Consider migrating require statements to custom errors 76 80864
[Gas-33] Consider not using libraries when implementing simple functionality. 2 4000
[Gas-34] Use assembly to validate msg.sender 8 0.0
[Gas-35] Simple checks for zero uint can be done using assembly to save gas 15 1350
[Gas-36] Use Unchecked for Divisions on Constant or Immutable Values 3 0.0
[Gas-37] Using nested if to save gas 4 96
[Gas-38] Optimize Deployment Size by Fine-tuning IPFS Hash 6 381600
[Gas-39] Avoid Unnecessary Public Variables 17 6358000
[Gas-40] Optimize Storage with Byte Truncation for Time Related State Variables 1 2000
[Gas-41] Stack variable cost less than state variables while used in emiting event 2 36
[Gas-42] Stack variable cost less than mappings while used in emiting event 6 324
[Gas-43] Avoid emitting event on every iteration 4 6000
[Gas-44] Inline modifiers used only once 1 0.0
[Gas-45] Use s.x = s.x + y instead of s.x += y for memory structs (same for -= etc) 2 400
[Gas-46] Time state variables can be truncated to uint32 1 20000
[Gas-47] Constants are cheaper than Enums 1 20000
[Gas-48] ++X costs slightly less gas than X++ (same with --) 36 6480
[Gas-49] Solidity versions 0.8.19 and above are more gas efficient 2 4000
[Gas-50] Variable declared within iteration 18 0.0
[Gas-51] Calling .length in a for loop wastes gas 19 38703
[Gas-52] Internal functions only used once can be inlined so save gas 16 7680
[Gas-53] Constructors can be marked as payable to save deployment gas 10 0.0
[Gas-54] Merge events to save gas 2 1500
[Gas-55] Use assembly scratch space to build calldata for external calls 102 2288880
[Gas-56] Use assembly scratch space to build calldata for event emits 14 43120
[Gas-57] Consider using solady's "FixedPointMathLib" 13 0.0
[Gas-58] Assigning to structs can be more efficient 8 8320
[Gas-59] Internal functions never used once can be removed 4 0.0
[Gas-60] Only emit event in setter function if the state variable was changed 5 0.0
[Gas-61] State variable read in a loop 5 173820
[Gas-62] Use uint256(1)/uint256(2) instead of true/false to save gas for changes 11 1034550
[Gas-63] Avoid emitting events in loops 4 6000
[Gas-64] Enable IR-based code generation 6 0.0

[Medium-1] Privileged functions can create points of failure

Resolution

Ensure such accounts are protected and consider implementing multi sig to prevent a single point of failure

Num of instances: 10

Findings

Click to show findings

['52']

52:     function setStaleStakesForbidden(bool value) external onlyCoordinatorOwner  // <= FOUND

['389']

385:     function createQuorum(
386:         OperatorSetParam memory operatorSetParams,
387:         uint96 minimumStake,
388:         IStakeRegistry.StrategyParams[] memory strategyParams
389:     ) external virtual onlyOwner  // <= FOUND

['403']

400:     function setOperatorSetParams(
401:         uint8 quorumNumber, 
402:         OperatorSetParam memory operatorSetParams
403:     ) external onlyOwner quorumExists(quorumNumber)  // <= FOUND

['413']

413:     function setChurnApprover(address _churnApprover) external onlyOwner  // <= FOUND

['422']

422:     function setEjector(address _ejector) external onlyOwner  // <= FOUND

['56']

56:     function setMetadataURI(string memory _metadataURI) public virtual onlyOwner  // <= FOUND

['211']

208:     function setMinimumStakeForQuorum(
209:         uint8 quorumNumber, 
210:         uint96 minimumStake
211:     ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber)  // <= FOUND

['224']

221:     function addStrategies(
222:         uint8 quorumNumber, 
223:         StrategyParams[] memory _strategyParams
224:     ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber)  // <= FOUND

['236']

233:     function removeStrategies(
234:         uint8 quorumNumber,
235:         uint256[] memory indicesToRemove
236:     ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber)  // <= FOUND

['265']

261:     function modifyStrategyParams(
262:         uint8 quorumNumber,
263:         uint256[] calldata strategyIndices,
264:         uint96[] calldata newMultipliers
265:     ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber)  // <= FOUND

[Low-1] Missing checks for address(0x0) when updating address state variables

Num of instances: 2

Findings

Click to show findings

['413']

413:     function setChurnApprover(address _churnApprover) external onlyOwner {
414:         _setChurnApprover(_churnApprover);
415:     }

['422']

422:     function setEjector(address _ejector) external onlyOwner {
423:         _setEjector(_ejector);
424:     }

[Low-2] Low level calls in solidity versions preceding 0.8.14 can result in an optimiser bug

Resolution

In Solidity versions 0.8.13 and 0.8.14, a known optimizer bug presents potential risks when a variable is used in a separate assembly block from the one in which it was stored. Specifically, the 'mstore' operation could be optimized out due to this bug, leading to the use of uninitialized memory. Although the current code does not exhibit this risky pattern of execution, it does utilize 'mstore' within assembly blocks, which introduces a vulnerability risk for future code modifications. As a preventative measure, it is advisable to avoid the usage of the afflicted Solidity versions, 0.8.13 and 0.8.14. Instead, consider utilizing a version that is not impacted by this optimizer bug to prevent potential memory initialization issues in your smart contract.

Num of instances: 6

Findings

Click to show findings

['108']

108:         assembly { // <= FOUND
109:             success := staticcall(sub(gas(), 2000), 6, input, 0x80, r, 0x40)
110:             
111:             switch success
112:             case 0 {
113:                 invalid()
114:             }
115:         }

['175']

175:         assembly { // <= FOUND
176:             success := staticcall(sub(gas(), 2000), 7, input, 0x60, r, 0x40)
177:             
178:             switch success
179:             case 0 {
180:                 invalid()
181:             }
182:         }

['261']

261:         assembly { // <= FOUND
262:             success := staticcall(pairingGas, 8, input, mul(12, 0x20), out, 0x20)
263:         }

['274']

274:        assembly { // <= FOUND
275:             mstore(0, mload(pk))
276:             mstore(0x20, mload(add(0x20, pk)))
277:             hashedG1 := keccak256(0, 0x40)
278:         }

['339']

339:         assembly { // <= FOUND
340:             success := staticcall(sub(gas(), 2000), 5, input, 0xc0, output, 0x20)
341:             
342:             switch success
343:             case 0 {
344:                 invalid()
345:             }
346:         }

['217']

217:         assembly { // <= FOUND
218:             success := staticcall(sub(gas(), 2000), 8, input, mul(12, 0x20), out, 0x20)
219:             
220:             switch success
221:             case 0 {
222:                 invalid()
223:             }

[Low-3] Upgradable contracts should have a __gap variable

Resolution

This is to ensure the team can add new state variables without compromising compatability.

Num of instances: 4

Findings

Click to show findings

['32']

32: contract RegistryCoordinator is 
33:     EIP712, 
34:     Initializable, 
35:     Pausable,
36:     OwnableUpgradeable,
37:     RegistryCoordinatorStorage, 
38:     ISocketUpdater, 
39:     ISignatureUtils
40: 

['11']

11: abstract contract BLSApkRegistryStorage is Initializable, IBLSApkRegistry 

['14']

14: abstract contract IndexRegistryStorage is Initializable, IIndexRegistry 

['19']

19: abstract contract ServiceManagerBase is IServiceManager, OwnableUpgradeable 

[Low-4] Using > when declaring solidity version without specifying an upperbound can cause future vulnerabilities

Resolution

Such instances should be locked to a particular solidity version or use the ^ attribute instead of > as this is more limited in what versions can be used.

Num of instances: 1

Findings

Click to show findings

['2']

2: pragma solidity >=0.5.0; // <= FOUND

[Low-5] Use SafeCast to safely downcast variables

Resolution

Downcasting int/uints in Solidity can be unsafe due to the potential for data loss and unintended behavior. When downcasting a larger integer type to a smaller one (e.g., uint256 to uint128), the value may exceed the range of the target type, leading to truncation and loss of significant digits. This data loss can result in unexpected state changes, incorrect calculations, or other contract vulnerabilities, ultimately compromising the contracts functionality and reliability. To prevent these risks, developers should carefully consider the range of values their variables may hold and ensure that proper checks are in place to prevent out-of-range values before performing downcasting. Also consider using OZ SafeCast functionality.

Num of instances: 26

Findings

Click to show findings

['42']

25:     function orderedBytesArrayToBitmap(bytes memory orderedBytesArray) internal pure returns (uint256) {
26:         
27:         require(orderedBytesArray.length <= MAX_BYTE_ARRAY_LENGTH,
28:             "BitmapUtils.orderedBytesArrayToBitmap: orderedBytesArray is too long");
29: 
30:         
31:         if (orderedBytesArray.length == 0) {
32:             return uint256(0);
33:         }
34: 
35:         
36:         uint256 bitmap;
37:         
38:         uint256 bitMask;
39: 
40:         
41:         
42:         bitmap = uint256(1 << uint8(orderedBytesArray[0])); // <= FOUND
43: 
44:         
45:         for (uint256 i = 1; i < orderedBytesArray.length; ++i) {
46:             
47:             bitMask = uint256(1 << uint8(orderedBytesArray[i])); // <= FOUND
48:             
49:             require(bitMask > bitmap, "BitmapUtils.orderedBytesArrayToBitmap: orderedBytesArray is not ordered");
50:             
51:             bitmap = (bitmap | bitMask);
52:         }
53:         return bitmap;
54:     }

['95']

79:     function isArrayStrictlyAscendingOrdered(bytes calldata bytesArray) internal pure returns (bool) {
80:         
81:         if (bytesArray.length > MAX_BYTE_ARRAY_LENGTH) {
82:             return false;
83:         }
84: 
85:         if (bytesArray.length == 0) {
86:             return true;
87:         }
88: 
89:         
90:         bytes1 singleByte = bytesArray[0];
91: 
92:         
93:         
94:         for (uint256 i = 1; i < bytesArray.length; ++i) {
95:             if (uint256(uint8(bytesArray[i])) <= uint256(uint8(singleByte))) { // <= FOUND
96:                 return false;
97:             }
98:             
99:             
100:             singleByte = bytesArray[i];
101:         }
102:         
103:         return true;
104:     }

['130']

112:     function bitmapToBytesArray(uint256 bitmap) internal pure returns (bytes memory ) {
113:         
114:         uint256 bitMask;
115:         
116:         bytes memory bytesArray = new bytes(countNumOnes(bitmap));
117:         
118:         uint256 arrayIndex = 0;
119:         
120: 
124:         for (uint256 i = 0; (arrayIndex < bytesArray.length) && (i < 256); ++i) {
125:             
126:             bitMask = uint256(1 << i);
127:             
128:             if (bitmap & bitMask != 0) {
129:                 
130:                 bytesArray[arrayIndex] = bytes1(uint8(i)); // <= FOUND
131:                 
132:                 unchecked{ ++arrayIndex; }
133:             }
134:         }
135:         return bytesArray;
136:     }

['155']

150:     function _processQuorumApkUpdate(bytes memory quorumNumbers, BN254.G1Point memory point) internal {
151:         BN254.G1Point memory newApk;
152: 
153:         for (uint256 i = 0; i < quorumNumbers.length; i++) {
154:             
155:             uint8 quorumNumber = uint8(quorumNumbers[i]); // <= FOUND
156:             uint256 historyLength = apkHistory[quorumNumber].length;
157:             require(historyLength != 0, "BLSApkRegistry._processQuorumApkUpdate: quorum does not exist");
158: 
159:             
160:             newApk = currentApk[quorumNumber].plus(point);
161:             currentApk[quorumNumber] = newApk;
162:             bytes24 newApkHash = bytes24(BN254.hashG1Point(newApk));
163: 
164:             
165:             
166:             ApkUpdate storage lastUpdate = apkHistory[quorumNumber][historyLength - 1];
167:             if (lastUpdate.updateBlockNumber == uint32(block.number)) { // <= FOUND
168:                 lastUpdate.apkHash = newApkHash;
169:             } else {
170:                 lastUpdate.nextUpdateBlockNumber = uint32(block.number); // <= FOUND
171:                 apkHistory[quorumNumber].push(ApkUpdate({
172:                     apkHash: newApkHash,
173:                     updateBlockNumber: uint32(block.number), // <= FOUND
174:                     nextUpdateBlockNumber: 0
175:                 }));
176:             }
177:         }
178:     }

['210']

203:     function getApkIndicesAtBlockNumber(
204:         bytes calldata quorumNumbers,
205:         uint256 blockNumber
206:     ) external view returns (uint32[] memory) {
207:         uint32[] memory indices = new uint32[](quorumNumbers.length);
208:         
209:         for (uint256 i = 0; i < quorumNumbers.length; i++) {
210:             uint8 quorumNumber = uint8(quorumNumbers[i]); // <= FOUND
211:             
212:             uint256 quorumApkUpdatesLength = apkHistory[quorumNumber].length;
213:             if (quorumApkUpdatesLength == 0 || blockNumber < apkHistory[quorumNumber][0].updateBlockNumber) {
214:                 revert("BLSApkRegistry.getApkIndicesAtBlockNumber: blockNumber is before the first update");
215:             }
216: 
217:             
218:             for (uint256 j = quorumApkUpdatesLength; j > 0; j--) {
219:                 if (apkHistory[quorumNumber][j - 1].updateBlockNumber <= blockNumber) {
220:                     indices[i] = uint32(j - 1); // <= FOUND
221:                     break;
222:                 }
223:             }
224:         }
225:         return indices;
226:     }

['115']

87:     function checkSignatures(
88:         bytes32 msgHash, 
89:         bytes calldata quorumNumbers,
90:         uint32 referenceBlockNumber, 
91:         NonSignerStakesAndSignature memory params
92:     ) 
93:         public 
94:         view
95:         returns (
96:             QuorumStakeTotals memory,
97:             bytes32
98:         )
99:     {
100:         require(quorumNumbers.length != 0, "BLSSignatureChecker.checkSignatures: empty quorum input");
101: 
102:         require(
103:             (quorumNumbers.length == params.quorumApks.length) &&
104:             (quorumNumbers.length == params.quorumApkIndices.length) &&
105:             (quorumNumbers.length == params.totalStakeIndices.length) &&
106:             (quorumNumbers.length == params.nonSignerStakeIndices.length),
107:             "BLSSignatureChecker.checkSignatures: input quorum length mismatch"
108:         );
109: 
110:         require(
111:             params.nonSignerPubkeys.length == params.nonSignerQuorumBitmapIndices.length,
112:             "BLSSignatureChecker.checkSignatures: input nonsigner length mismatch"
113:         );
114: 
115:         require(referenceBlockNumber < uint32(block.number), "BLSSignatureChecker.checkSignatures: invalid reference block"); // <= FOUND
116: 
117:         
118:         
119:         
120:         
121:         
122:         
123:         BN254.G1Point memory apk = BN254.G1Point(0, 0);
124: 
125:         
126:         
127:         
128:         QuorumStakeTotals memory stakeTotals;
129:         stakeTotals.totalStakeForQuorum = new uint96[](quorumNumbers.length);
130:         stakeTotals.signedStakeForQuorum = new uint96[](quorumNumbers.length);
131: 
132:         NonSignerInfo memory nonSigners;
133:         nonSigners.quorumBitmaps = new uint256[](params.nonSignerPubkeys.length);
134:         nonSigners.pubkeyHashes = new bytes32[](params.nonSignerPubkeys.length);
135: 
136:         {
137:             
138:             
139:             uint256 signingQuorumBitmap = BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, registryCoordinator.quorumCount());
140: 
141:             for (uint256 j = 0; j < params.nonSignerPubkeys.length; j++) {
142:                 
143:                 
144:                 
145:                 nonSigners.pubkeyHashes[j] = params.nonSignerPubkeys[j].hashG1Point();
146:                 if (j != 0) {
147:                     require(
148:                         uint256(nonSigners.pubkeyHashes[j]) > uint256(nonSigners.pubkeyHashes[j - 1]),
149:                         "BLSSignatureChecker.checkSignatures: nonSignerPubkeys not sorted"
150:                     );
151:                 }
152: 
153:                 
154:                 nonSigners.quorumBitmaps[j] = 
155:                     registryCoordinator.getQuorumBitmapAtBlockNumberByIndex({
156:                         operatorId: nonSigners.pubkeyHashes[j],
157:                         blockNumber: referenceBlockNumber,
158:                         index: params.nonSignerQuorumBitmapIndices[j]
159:                     });
160: 
161:                 
162:                 
163:                 
164:                 apk = apk.plus(
165:                     params.nonSignerPubkeys[j]
166:                         .scalar_mul_tiny(
167:                             BitmapUtils.countNumOnes(nonSigners.quorumBitmaps[j] & signingQuorumBitmap) 
168:                         )
169:                 );
170:             }
171:         }
172: 
173:         
174:         
175:         
176:         apk = apk.negate();
177: 
178:         
179: 
184:         {
185:             bool _staleStakesForbidden = staleStakesForbidden;
186:             uint256 withdrawalDelayBlocks = _staleStakesForbidden ? delegation.minWithdrawalDelayBlocks() : 0;
187: 
188:             for (uint256 i = 0; i < quorumNumbers.length; i++) {
189:                 
190:                 
191:                 if (_staleStakesForbidden) {
192:                     require(
193:                         registryCoordinator.quorumUpdateBlockNumber(uint8(quorumNumbers[i])) + withdrawalDelayBlocks >= referenceBlockNumber, // <= FOUND
194:                         "BLSSignatureChecker.checkSignatures: StakeRegistry updates must be within withdrawalDelayBlocks window"
195:                     );
196:                 }
197: 
198:                 
199:                 
200:                 require(
201:                     bytes24(params.quorumApks[i].hashG1Point()) == 
202:                         blsApkRegistry.getApkHashAtBlockNumberAndIndex({
203:                             quorumNumber: uint8(quorumNumbers[i]), // <= FOUND
204:                             blockNumber: referenceBlockNumber,
205:                             index: params.quorumApkIndices[i]
206:                         }),
207:                     "BLSSignatureChecker.checkSignatures: quorumApk hash in storage does not match provided quorum apk"
208:                 );
209:                 apk = apk.plus(params.quorumApks[i]);
210: 
211:                 
212:                 stakeTotals.totalStakeForQuorum[i] = 
213:                     stakeRegistry.getTotalStakeAtBlockNumberFromIndex({
214:                         quorumNumber: uint8(quorumNumbers[i]), // <= FOUND
215:                         blockNumber: referenceBlockNumber,
216:                         index: params.totalStakeIndices[i]
217:                     });
218:                 stakeTotals.signedStakeForQuorum[i] = stakeTotals.totalStakeForQuorum[i];
219: 
220:                 
221:                 uint256 nonSignerForQuorumIndex = 0;
222:                 
223:                 
224:                 
225:                 for (uint256 j = 0; j < params.nonSignerPubkeys.length; j++) {
226:                     
227:                     if (BitmapUtils.isSet(nonSigners.quorumBitmaps[j], uint8(quorumNumbers[i]))) { // <= FOUND
228:                         stakeTotals.signedStakeForQuorum[i] -=
229:                             stakeRegistry.getStakeAtBlockNumberAndIndex({
230:                                 quorumNumber: uint8(quorumNumbers[i]), // <= FOUND
231:                                 blockNumber: referenceBlockNumber,
232:                                 operatorId: nonSigners.pubkeyHashes[j],
233:                                 index: params.nonSignerStakeIndices[i][nonSignerForQuorumIndex]
234:                             });
235:                         unchecked {
236:                             ++nonSignerForQuorumIndex;
237:                         }
238:                     }
239:                 }
240:             }
241:         }
242:         {
243:             
244:             (bool pairingSuccessful, bool signatureIsValid) = trySignatureAndApkVerification(
245:                 msgHash, 
246:                 apk, 
247:                 params.apkG2, 
248:                 params.sigma
249:             );
250:             require(pairingSuccessful, "BLSSignatureChecker.checkSignatures: pairing precompile call failed");
251:             require(signatureIsValid, "BLSSignatureChecker.checkSignatures: signature is invalid");
252:         }
253:         
254:         bytes32 signatoryRecordHash = keccak256(abi.encodePacked(referenceBlockNumber, nonSigners.pubkeyHashes));
255: 
256:         
257:         return (stakeTotals, signatoryRecordHash);
258:     }

['48']

40:     function registerOperator(
41:         bytes32 operatorId, 
42:         bytes calldata quorumNumbers
43:     ) public virtual onlyRegistryCoordinator returns(uint32[] memory) {
44:         uint32[] memory numOperatorsPerQuorum = new uint32[](quorumNumbers.length);
45: 
46:         for (uint256 i = 0; i < quorumNumbers.length; i++) {
47:             
48:             uint8 quorumNumber = uint8(quorumNumbers[i]); // <= FOUND
49:             uint256 historyLength = _operatorCountHistory[quorumNumber].length;
50:             require(historyLength != 0, "IndexRegistry.registerOperator: quorum does not exist");
51: 
52:             
53: 
56:             uint32 newOperatorCount = _increaseOperatorCount(quorumNumber);
57:             _assignOperatorToIndex({
58:                 operatorId: operatorId,
59:                 quorumNumber: quorumNumber,
60:                 operatorIndex: newOperatorCount - 1
61:             });
62: 
63:             
64:             numOperatorsPerQuorum[i] = newOperatorCount;
65:         }
66: 
67:         return numOperatorsPerQuorum;
68:     }

['88']

82:     function deregisterOperator(
83:         bytes32 operatorId, 
84:         bytes calldata quorumNumbers
85:     ) public virtual onlyRegistryCoordinator {
86:         for (uint256 i = 0; i < quorumNumbers.length; i++) {
87:             
88:             uint8 quorumNumber = uint8(quorumNumbers[i]); // <= FOUND
89:             uint256 historyLength = _operatorCountHistory[quorumNumber].length;
90:             require(historyLength != 0, "IndexRegistry.registerOperator: quorum does not exist");
91:             uint32 operatorIndexToRemove = currentOperatorIndex[quorumNumber][operatorId];
92: 
93:             
94: 
99:             uint32 newOperatorCount = _decreaseOperatorCount(quorumNumber);
100:             bytes32 lastOperatorId = _popLastOperator(quorumNumber, newOperatorCount);
101:             if (operatorId != lastOperatorId) {
102:                 _assignOperatorToIndex({
103:                     operatorId: lastOperatorId,
104:                     quorumNumber: quorumNumber,
105:                     operatorIndex: operatorIndexToRemove
106:                 });
107:             }
108:         }
109:     }

['75']

64:     function getOperatorState(
65:         IRegistryCoordinator registryCoordinator, 
66:         bytes memory quorumNumbers, 
67:         uint32 blockNumber
68:     ) public view returns(Operator[][] memory) {
69:         IStakeRegistry stakeRegistry = registryCoordinator.stakeRegistry();
70:         IIndexRegistry indexRegistry = registryCoordinator.indexRegistry();
71:         IBLSApkRegistry blsApkRegistry = registryCoordinator.blsApkRegistry();
72: 
73:         Operator[][] memory operators = new Operator[][](quorumNumbers.length);
74:         for (uint256 i = 0; i < quorumNumbers.length; i++) {
75:             uint8 quorumNumber = uint8(quorumNumbers[i]); // <= FOUND
76:             bytes32[] memory operatorIds = indexRegistry.getOperatorListAtBlockNumber(quorumNumber, blockNumber);
77:             operators[i] = new Operator[](operatorIds.length);
78:             for (uint256 j = 0; j < operatorIds.length; j++) {
79:                 operators[i][j] = Operator({
80:                     operator: blsApkRegistry.getOperatorFromPubkeyHash(operatorIds[j]),
81:                     operatorId: bytes32(operatorIds[j]),
82:                     stake: stakeRegistry.getStakeAtBlockNumber(bytes32(operatorIds[j]), quorumNumber, blockNumber)
83:                 });
84:             }
85:         }
86:             
87:         return operators;
88:     }

['137']

104:     function getCheckSignaturesIndices(
105:         IRegistryCoordinator registryCoordinator,
106:         uint32 referenceBlockNumber, 
107:         bytes calldata quorumNumbers, 
108:         bytes32[] calldata nonSignerOperatorIds
109:     ) external view returns (CheckSignaturesIndices memory) {
110:         IStakeRegistry stakeRegistry = registryCoordinator.stakeRegistry();
111:         CheckSignaturesIndices memory checkSignaturesIndices;
112: 
113:         
114:         checkSignaturesIndices.nonSignerQuorumBitmapIndices = registryCoordinator.getQuorumBitmapIndicesAtBlockNumber(referenceBlockNumber, nonSignerOperatorIds);
115: 
116:         
117:         checkSignaturesIndices.totalStakeIndices = stakeRegistry.getTotalStakeIndicesAtBlockNumber(referenceBlockNumber, quorumNumbers);
118:         
119:         checkSignaturesIndices.nonSignerStakeIndices = new uint32[][](quorumNumbers.length);
120:         for (uint8 quorumNumberIndex = 0; quorumNumberIndex < quorumNumbers.length; quorumNumberIndex++) {
121:             uint256 numNonSignersForQuorum = 0;
122:             
123:             checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex] = new uint32[](nonSignerOperatorIds.length);
124: 
125:             for (uint i = 0; i < nonSignerOperatorIds.length; i++) {
126:                 
127:                 uint192 nonSignerQuorumBitmap = 
128:                     registryCoordinator.getQuorumBitmapAtBlockNumberByIndex(
129:                         nonSignerOperatorIds[i], 
130:                         referenceBlockNumber, 
131:                         checkSignaturesIndices.nonSignerQuorumBitmapIndices[i]
132:                     );
133:                 
134:                 require(nonSignerQuorumBitmap != 0, "OperatorStateRetriever.getCheckSignaturesIndices: operator must be registered at blocknumber");
135:                 
136:                 
137:                 if ((nonSignerQuorumBitmap >> uint8(quorumNumbers[quorumNumberIndex])) & 1 == 1) { // <= FOUND
138:                     
139:                     checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex][numNonSignersForQuorum] = stakeRegistry.getStakeUpdateIndexAtBlockNumber(
140:                         nonSignerOperatorIds[i],
141:                         uint8(quorumNumbers[quorumNumberIndex]), // <= FOUND
142:                         referenceBlockNumber
143:                     );
144:                     numNonSignersForQuorum++;
145:                 }
146:             }
147: 
148:             
149:             uint32[] memory nonSignerStakeIndicesForQuorum = new uint32[](numNonSignersForQuorum);
150:             for (uint i = 0; i < numNonSignersForQuorum; i++) {
151:                 nonSignerStakeIndicesForQuorum[i] = checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex][i];
152:             }
153:             checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex] = nonSignerStakeIndicesForQuorum;
154:         }
155: 
156:         IBLSApkRegistry blsApkRegistry = registryCoordinator.blsApkRegistry();
157:         
158:         checkSignaturesIndices.quorumApkIndices = blsApkRegistry.getApkIndicesAtBlockNumber(quorumNumbers, referenceBlockNumber);
159: 
160:         return checkSignaturesIndices;
161:     }

['156']

128:     function registerOperator(
129:         bytes calldata quorumNumbers,
130:         string calldata socket,
131:         IBLSApkRegistry.PubkeyRegistrationParams calldata params,
132:         SignatureWithSaltAndExpiry memory operatorSignature
133:     ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) {
134:         
135: 
141:         bytes32 operatorId = _getOrCreateOperatorId(msg.sender, params);
142: 
143:         
144:         
145:         uint32[] memory numOperatorsPerQuorum = _registerOperator({
146:             operator: msg.sender, 
147:             operatorId: operatorId,
148:             quorumNumbers: quorumNumbers, 
149:             socket: socket,
150:             operatorSignature: operatorSignature
151:         }).numOperatorsPerQuorum;
152: 
153:         
154:         
155:         for (uint256 i = 0; i < quorumNumbers.length; i++) {
156:             uint8 quorumNumber = uint8(quorumNumbers[i]); // <= FOUND
157: 
158:             require(
159:                 numOperatorsPerQuorum[i] <= _quorumParams[quorumNumber].maxOperatorCount,
160:                 "RegistryCoordinator.registerOperator: operator count exceeds maximum"
161:             );
162:         }
163:     }

['217']

177:     function registerOperatorWithChurn(
178:         bytes calldata quorumNumbers, 
179:         string calldata socket,
180:         IBLSApkRegistry.PubkeyRegistrationParams calldata params,
181:         OperatorKickParam[] calldata operatorKickParams,
182:         SignatureWithSaltAndExpiry memory churnApproverSignature,
183:         SignatureWithSaltAndExpiry memory operatorSignature
184:     ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) {
185:         require(operatorKickParams.length == quorumNumbers.length, "RegistryCoordinator.registerOperatorWithChurn: input length mismatch");
186:         
187:         
188: 
194:         bytes32 operatorId = _getOrCreateOperatorId(msg.sender, params);
195: 
196:         
197:         _verifyChurnApproverSignature({
198:             registeringOperator: msg.sender,
199:             registeringOperatorId: operatorId,
200:             operatorKickParams: operatorKickParams,
201:             churnApproverSignature: churnApproverSignature
202:         });
203: 
204:         
205:         
206:         RegisterResults memory results = _registerOperator({
207:             operator: msg.sender,
208:             operatorId: operatorId,
209:             quorumNumbers: quorumNumbers,
210:             socket: socket,
211:             operatorSignature: operatorSignature
212:         });
213: 
214:         
215:         
216:         for (uint256 i = 0; i < quorumNumbers.length; i++) {
217:             OperatorSetParam memory operatorSetParams = _quorumParams[uint8(quorumNumbers[i])]; // <= FOUND
218:             
219:             
220: 
223:             if (results.numOperatorsPerQuorum[i] > operatorSetParams.maxOperatorCount) {
224:                 _validateChurn({
225:                     quorumNumber: uint8(quorumNumbers[i]), // <= FOUND
226:                     totalQuorumStake: results.totalStakes[i],
227:                     newOperator: msg.sender,
228:                     newOperatorStake: results.operatorStakes[i],
229:                     kickParams: operatorKickParams[i],
230:                     setParams: operatorSetParams
231:                 });
232: 
233:                 _deregisterOperator(operatorKickParams[i].operator, quorumNumbers[i:i+1]);
234:             }
235:         }
236:     }

['292']

284:     function updateOperatorsForQuorum(
285:         address[][] calldata operatorsPerQuorum,
286:         bytes calldata quorumNumbers
287:     ) external onlyWhenNotPaused(PAUSED_UPDATE_OPERATOR) {
288:         
289:         
290:         
291:         
292:         uint192 quorumBitmap = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount)); // <= FOUND
293:         require(
294:             operatorsPerQuorum.length == quorumNumbers.length,
295:             "RegistryCoordinator.updateOperatorsForQuorum: input length mismatch"
296:         );
297: 
298:         
299:         for (uint256 i = 0; i < quorumNumbers.length; ++i) {
300:             uint8 quorumNumber = uint8(quorumNumbers[i]); // <= FOUND
301: 
302:             
303:             address[] calldata currQuorumOperators = operatorsPerQuorum[i];
304:             require(
305:                 currQuorumOperators.length == indexRegistry.totalOperatorsForQuorum(quorumNumber),
306:                 "RegistryCoordinator.updateOperatorsForQuorum: number of updated operators does not match quorum total"
307:             );
308: 
309:             address prevOperatorAddress = address(0);
310:             
311:             
312:             
313:             
314:             for (uint256 j = 0; j < currQuorumOperators.length; ++j) {
315:                 address operator = currQuorumOperators[j];
316:                 
317:                 OperatorInfo memory operatorInfo = _operatorInfo[operator];
318:                 bytes32 operatorId = operatorInfo.operatorId;
319:                 
320:                 {
321:                     uint192 currentBitmap = _currentOperatorBitmap(operatorId);
322:                     
323:                     require(
324:                         BitmapUtils.isSet(currentBitmap, quorumNumber),
325:                         "RegistryCoordinator.updateOperatorsForQuorum: operator not in quorum"
326:                     );
327:                     
328:                     require(
329:                         operator > prevOperatorAddress,
330:                         "RegistryCoordinator.updateOperatorsForQuorum: operators array must be sorted in ascending address order"
331:                     );
332:                 }
333:                 
334:                 
335:                 _updateOperator(operator, operatorInfo, quorumNumbers[i:i+1]);
336:                 prevOperatorAddress = operator;
337:             }
338: 
339:             
340:             quorumUpdateBlockNumber[quorumNumber] = block.number;
341:             emit QuorumBlockNumberUpdated(quorumNumber, block.number);
342:         }
343:     }

['95']

86:     function getRestakeableStrategies() external view returns (address[] memory) {
87:         uint256 quorumCount = _registryCoordinator.quorumCount();
88: 
89:         if (quorumCount == 0) {
90:             return new address[](0);
91:         }
92:         
93:         uint256 strategyCount;
94:         for(uint256 i = 0; i < quorumCount; i++) {
95:             strategyCount += _stakeRegistry.strategyParamsLength(uint8(i)); // <= FOUND
96:         }
97: 
98:         address[] memory restakedStrategies = new address[](strategyCount);
99:         uint256 index = 0;
100:         for(uint256 i = 0; i < _registryCoordinator.quorumCount(); i++) {
101:             uint256 strategyParamsLength = _stakeRegistry.strategyParamsLength(uint8(i)); // <= FOUND
102:             for (uint256 j = 0; j < strategyParamsLength; j++) {
103:                 restakedStrategies[index] = address(_stakeRegistry.strategyParamsByIndex(uint8(i), j).strategy); // <= FOUND
104:                 index++;
105:             }
106:         }
107:         return restakedStrategies;
108:     }

['129']

117:     function getOperatorRestakedStrategies(address operator) external view returns (address[] memory) {
118:         bytes32 operatorId = _registryCoordinator.getOperatorId(operator);
119:         uint192 operatorBitmap = _registryCoordinator.getCurrentQuorumBitmap(operatorId);
120: 
121:         if (operatorBitmap == 0 || _registryCoordinator.quorumCount() == 0) {
122:             return new address[](0);
123:         }
124: 
125:         
126:         bytes memory operatorRestakedQuorums = BitmapUtils.bitmapToBytesArray(operatorBitmap);
127:         uint256 strategyCount;
128:         for(uint256 i = 0; i < operatorRestakedQuorums.length; i++) {
129:             strategyCount += _stakeRegistry.strategyParamsLength(uint8(operatorRestakedQuorums[i])); // <= FOUND
130:         }
131: 
132:         
133:         address[] memory restakedStrategies = new address[](strategyCount);
134:         uint256 index = 0;
135:         for(uint256 i = 0; i < operatorRestakedQuorums.length; i++) {
136:             uint8 quorum = uint8(operatorRestakedQuorums[i]); // <= FOUND
137:             uint256 strategyParamsLength = _stakeRegistry.strategyParamsLength(quorum);
138:             for (uint256 j = 0; j < strategyParamsLength; j++) {
139:                 restakedStrategies[index] = address(_stakeRegistry.strategyParamsByIndex(quorum, j).strategy);
140:                 index++;
141:             }
142:         }
143:         return restakedStrategies;        
144:     }

['76']

66:     function registerOperator(
67:         address operator,
68:         bytes32 operatorId,
69:         bytes calldata quorumNumbers
70:     ) public virtual onlyRegistryCoordinator returns (uint96[] memory, uint96[] memory) {
71: 
72:         uint96[] memory currentStakes = new uint96[](quorumNumbers.length);
73:         uint96[] memory totalStakes = new uint96[](quorumNumbers.length);
74:         for (uint256 i = 0; i < quorumNumbers.length; i++) {            
75:             
76:             uint8 quorumNumber = uint8(quorumNumbers[i]); // <= FOUND
77:             require(_quorumExists(quorumNumber), "StakeRegistry.registerOperator: quorum does not exist");
78: 
79:             
80:             
81:             (uint96 currentStake, bool hasMinimumStake) = _weightOfOperatorForQuorum(quorumNumber, operator);
82:             require(
83:                 hasMinimumStake,
84:                 "StakeRegistry.registerOperator: Operator does not meet minimum stake requirement for quorum"
85:             );
86: 
87:             
88:             int256 stakeDelta = _recordOperatorStakeUpdate({
89:                 operatorId: operatorId, 
90:                 quorumNumber: quorumNumber,
91:                 newStake: currentStake
92:             });
93: 
94:             
95:             currentStakes[i] = currentStake;
96:             totalStakes[i] = _recordTotalStakeUpdate(quorumNumber, stakeDelta);
97:         }
98: 
99:         return (currentStakes, totalStakes);
100:     }

['123']

114:     function deregisterOperator(
115:         bytes32 operatorId,
116:         bytes calldata quorumNumbers
117:     ) public virtual onlyRegistryCoordinator {
118:         
119: 
122:         for (uint256 i = 0; i < quorumNumbers.length; i++) {
123:             uint8 quorumNumber = uint8(quorumNumbers[i]); // <= FOUND
124:             require(_quorumExists(quorumNumber), "StakeRegistry.deregisterOperator: quorum does not exist");
125: 
126:             
127:             int256 stakeDelta = _recordOperatorStakeUpdate({
128:                 operatorId: operatorId, 
129:                 quorumNumber: quorumNumber, 
130:                 newStake: 0
131:             });
132: 
133:             
134:             _recordTotalStakeUpdate(quorumNumber, stakeDelta);
135:         }
136:     }

['163']

147:     function updateOperatorStake(
148:         address operator, 
149:         bytes32 operatorId, 
150:         bytes calldata quorumNumbers
151:     ) external onlyRegistryCoordinator returns (uint192) {
152:         uint192 quorumsToRemove;
153: 
154:         
155: 
162:         for (uint256 i = 0; i < quorumNumbers.length; i++) {
163:             uint8 quorumNumber = uint8(quorumNumbers[i]); // <= FOUND
164:             require(_quorumExists(quorumNumber), "StakeRegistry.updateOperatorStake: quorum does not exist");
165: 
166:             
167:             
168:             (uint96 stakeWeight, bool hasMinimumStake) = _weightOfOperatorForQuorum(quorumNumber, operator);
169: 
170:             
171:             if (!hasMinimumStake) {
172:                 stakeWeight = 0;
173:                 quorumsToRemove = uint192(quorumsToRemove.setBit(quorumNumber)); // <= FOUND
174:             }
175: 
176:             
177:             
178:             int256 stakeDelta = _recordOperatorStakeUpdate({
179:                 operatorId: operatorId,
180:                 quorumNumber: quorumNumber,
181:                 newStake: stakeWeight
182:             });
183: 
184:             
185:             _recordTotalStakeUpdate(quorumNumber, stakeDelta);
186:         }
187: 
188:         return quorumsToRemove;
189:     }

['699']

693:     function getTotalStakeIndicesAtBlockNumber(
694:         uint32 blockNumber,
695:         bytes calldata quorumNumbers
696:     ) external view returns (uint32[] memory) {
697:         uint32[] memory indices = new uint32[](quorumNumbers.length);
698:         for (uint256 i = 0; i < quorumNumbers.length; i++) {
699:             uint8 quorumNumber = uint8(quorumNumbers[i]); // <= FOUND
700:             require(_quorumExists(quorumNumber), "StakeRegistry.getTotalStakeIndicesAtBlockNumber: quorum does not exist");
701:             require(
702:                 _totalStakeHistory[quorumNumber][0].updateBlockNumber <= blockNumber,
703:                 "StakeRegistry.getTotalStakeIndicesAtBlockNumber: quorum has no stake history at blockNumber"
704:             );
705:             uint256 length = _totalStakeHistory[quorumNumber].length;
706:             for (uint256 j = 0; j < length; j++) {
707:                 if (_totalStakeHistory[quorumNumber][length - j - 1].updateBlockNumber <= blockNumber) {
708:                     indices[i] = uint32(length - j - 1); // <= FOUND
709:                     break;
710:                 }
711:             }
712:         }
713:         return indices;
714:     }

['271']

270:     function getApkHistoryLength(uint8 quorumNumber) external view returns (uint32) {
271:         return uint32(apkHistory[quorumNumber].length); // <= FOUND
272:     }

['755']

746:     function _getQuorumBitmapIndexAtBlockNumber(
747:         uint32 blockNumber, 
748:         bytes32 operatorId
749:     ) internal view returns (uint32 index) {
750:         uint256 length = _operatorBitmapHistory[operatorId].length;
751: 
752:         
753:         
754:         for (uint256 i = 0; i < length; i++) {
755:             index = uint32(length - i - 1); // <= FOUND
756: 
757:             if (_operatorBitmapHistory[operatorId][index].updateBlockNumber <= blockNumber) {
758:                 return index;
759:             }
760:         }
761: 
762:         revert(
763:             "RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId at block number"
764:         );
765:     }

['293']

283:     function _getStakeUpdateIndexForOperatorAtBlockNumber(
284:         bytes32 operatorId,
285:         uint8 quorumNumber,
286:         uint32 blockNumber
287:     ) internal view returns (uint32) {
288:         uint256 length = operatorStakeHistory[operatorId][quorumNumber].length;
289: 
290:         
291:         for (uint256 i = length; i > 0; i--) {
292:             if (operatorStakeHistory[operatorId][quorumNumber][i - 1].updateBlockNumber <= blockNumber) {
293:                 return uint32(i - 1); // <= FOUND
294:             }
295:         }
296: 
297:         
298:         revert(
299:             "StakeRegistry._getStakeUpdateIndexForOperatorAtBlockNumber: no stake update found for operatorId and quorumNumber at block number"
300:         );
301:     }

['440']

438:     function _applyDelta(uint96 value, int256 delta) internal pure returns (uint96) {
439:         if (delta < 0) {
440:             return value - uint96(uint256(-delta)); // <= FOUND
441:         } else {
442:             return value + uint96(uint256(delta)); // <= FOUND
443:         }
444:     }

['484']

472:     function _weightOfOperatorForQuorum(uint8 quorumNumber, address operator) internal virtual view returns (uint96, bool) {
473:         uint96 weight;
474:         uint256 stratsLength = strategyParamsLength(quorumNumber);
475:         StrategyParams memory strategyAndMultiplier;
476: 
477:         uint256[] memory strategyShares = delegation.getOperatorShares(operator, strategiesPerQuorum[quorumNumber]);
478:         for (uint256 i = 0; i < stratsLength; i++) {
479:             
480:             strategyAndMultiplier = strategyParams[quorumNumber][i];
481: 
482:             
483:             if (strategyShares[i] > 0) {
484:                 weight += uint96(strategyShares[i] * strategyAndMultiplier.multiplier / WEIGHTING_DIVISOR); // <= FOUND
485:             }
486:         }
487: 
488:         
489:         bool hasMinimumStake = weight >= minimumStakeForQuorum[quorumNumber];
490:         return (weight, hasMinimumStake);
491:     }

['454']

440:     function _registerOperator(
441:         address operator, 
442:         bytes32 operatorId,
443:         bytes calldata quorumNumbers,
444:         string memory socket,
445:         SignatureWithSaltAndExpiry memory operatorSignature
446:     ) internal virtual returns (RegisterResults memory results) {
447:         
448: 
454:         uint192 quorumsToAdd = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount)); // <= FOUND
455:         uint192 currentBitmap = _currentOperatorBitmap(operatorId);
456:         require(!quorumsToAdd.isEmpty(), "RegistryCoordinator._registerOperator: bitmap cannot be 0");
457:         require(quorumsToAdd.noBitsInCommon(currentBitmap), "RegistryCoordinator._registerOperator: operator already registered for some quorums being registered for");
458:         uint192 newBitmap = uint192(currentBitmap.plus(quorumsToAdd)); // <= FOUND
459: 
460:         
461: 
464:         _updateOperatorBitmap({
465:             operatorId: operatorId,
466:             newBitmap: newBitmap
467:         });
468: 
469:         emit OperatorSocketUpdate(operatorId, socket);
470: 
471:         
472:         
473:         if (_operatorInfo[operator].status != OperatorStatus.REGISTERED) {
474:             _operatorInfo[operator] = OperatorInfo({
475:                 operatorId: operatorId,
476:                 status: OperatorStatus.REGISTERED
477:             });
478: 
479:             
480:             serviceManager.registerOperatorToAVS(operator, operatorSignature);
481: 
482:             emit OperatorRegistered(operator, operatorId);
483:         }
484: 
485:         
486:         blsApkRegistry.registerOperator(operator, quorumNumbers);
487:         (results.operatorStakes, results.totalStakes) = 
488:             stakeRegistry.registerOperator(operator, operatorId, quorumNumbers);
489:         results.numOperatorsPerQuorum = indexRegistry.registerOperator(operatorId, quorumNumbers);
490: 
491:         return results;
492:     }

['577']

561:     function _deregisterOperator(
562:         address operator, 
563:         bytes memory quorumNumbers
564:     ) internal virtual {
565:         
566:         OperatorInfo storage operatorInfo = _operatorInfo[operator];
567:         bytes32 operatorId = operatorInfo.operatorId;
568:         require(operatorInfo.status == OperatorStatus.REGISTERED, "RegistryCoordinator._deregisterOperator: operator is not registered");
569:         
570:         
571: 
577:         uint192 quorumsToRemove = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount)); // <= FOUND
578:         uint192 currentBitmap = _currentOperatorBitmap(operatorId);
579:         require(!quorumsToRemove.isEmpty(), "RegistryCoordinator._deregisterOperator: bitmap cannot be 0");
580:         require(quorumsToRemove.isSubsetOf(currentBitmap), "RegistryCoordinator._deregisterOperator: operator is not registered for specified quorums");
581:         uint192 newBitmap = uint192(currentBitmap.minus(quorumsToRemove)); // <= FOUND
582: 
583:         
584:         _updateOperatorBitmap({
585:             operatorId: operatorId,
586:             newBitmap: newBitmap
587:         });
588: 
589:         
590:         
591:         if (newBitmap.isEmpty()) {
592:             operatorInfo.status = OperatorStatus.DEREGISTERED;
593:             serviceManager.deregisterOperatorFromAVS(operator);
594:             emit OperatorDeregistered(operator, operatorId);
595:         }
596: 
597:         
598:         blsApkRegistry.deregisterOperator(operator, quorumNumbers);
599:         stakeRegistry.deregisterOperator(operatorId, quorumNumbers);
600:         indexRegistry.deregisterOperator(operatorId, quorumNumbers);
601:     }

[Low-6] Function calls within for loops

Resolution

Making function calls or external calls within loops in Solidity can lead to inefficient gas usage, potential bottlenecks, and increased vulnerability to attacks. Each function call or external call consumes gas, and when executed within a loop, the gas cost multiplies, potentially causing the transaction to run out of gas or exceed block gas limits. This can result in transaction failure or unpredictable behavior.

Num of instances: 14

Findings

Click to show findings

['258']

258:        for (uint256 i = 0; i < operators.length; i++) {
259:             address operator = operators[i];
260:             OperatorInfo memory operatorInfo = _operatorInfo[operator];
261:             bytes32 operatorId = operatorInfo.operatorId;
262: 
263:             
264:             uint192 currentBitmap = _currentOperatorBitmap(operatorId); // <= FOUND
265:             bytes memory quorumsToUpdate = BitmapUtils.bitmapToBytesArray(currentBitmap); // <= FOUND
266:             _updateOperator(operator, operatorInfo, quorumsToUpdate); // <= FOUND
267:         }

['188']

188:            for (uint256 i = 0; i < quorumNumbers.length; i++) {
189:                 
190:                 
191:                 if (_staleStakesForbidden) {
192:                     require(
193:                         registryCoordinator.quorumUpdateBlockNumber(uint8(quorumNumbers[i])) + withdrawalDelayBlocks >= referenceBlockNumber,
194:                         "BLSSignatureChecker.checkSignatures: StakeRegistry updates must be within withdrawalDelayBlocks window"
195:                     );
196:                 }
197: 
198:                 
199:                 
200:                 require(
201:                     bytes24(params.quorumApks[i].hashG1Point()) ==  // <= FOUND
202:                         blsApkRegistry.getApkHashAtBlockNumberAndIndex({ // <= FOUND
203:                             quorumNumber: uint8(quorumNumbers[i]),
204:                             blockNumber: referenceBlockNumber,
205:                             index: params.quorumApkIndices[i]
206:                         }),
207:                     "BLSSignatureChecker.checkSignatures: quorumApk hash in storage does not match provided quorum apk"
208:                 );
209:                 apk = apk.plus(params.quorumApks[i]); // <= FOUND
210: 
211:                 
212:                 stakeTotals.totalStakeForQuorum[i] = 
213:                     stakeRegistry.getTotalStakeAtBlockNumberFromIndex({ // <= FOUND
214:                         quorumNumber: uint8(quorumNumbers[i]),
215:                         blockNumber: referenceBlockNumber,
216:                         index: params.totalStakeIndices[i]
217:                     });
218:                 stakeTotals.signedStakeForQuorum[i] = stakeTotals.totalStakeForQuorum[i];
219: 
220:                 
221:                 uint256 nonSignerForQuorumIndex = 0;
222:                 
223:                 
224:                 
225:                 for (uint256 j = 0; j < params.nonSignerPubkeys.length; j++) {
226:                     
227:                     if (BitmapUtils.isSet(nonSigners.quorumBitmaps[j], uint8(quorumNumbers[i]))) { // <= FOUND
228:                         stakeTotals.signedStakeForQuorum[i] -=
229:                             stakeRegistry.getStakeAtBlockNumberAndIndex({ // <= FOUND
230:                                 quorumNumber: uint8(quorumNumbers[i]),
231:                                 blockNumber: referenceBlockNumber,
232:                                 operatorId: nonSigners.pubkeyHashes[j],
233:                                 index: params.nonSignerStakeIndices[i][nonSignerForQuorumIndex]
234:                             });
235:                         unchecked {
236:                             ++nonSignerForQuorumIndex;
237:                         }

['299']

299:        for (uint256 i = 0; i < quorumNumbers.length; ++i) {
300:             uint8 quorumNumber = uint8(quorumNumbers[i]);
301: 
302:             
303:             address[] calldata currQuorumOperators = operatorsPerQuorum[i];
304:             require(
305:                 currQuorumOperators.length == indexRegistry.totalOperatorsForQuorum(quorumNumber), // <= FOUND
306:                 "RegistryCoordinator.updateOperatorsForQuorum: number of updated operators does not match quorum total"
307:             );
308: 
309:             address prevOperatorAddress = address(0);
310:             
311:             
312:             
313:             
314:             for (uint256 j = 0; j < currQuorumOperators.length; ++j) {
315:                 address operator = currQuorumOperators[j];
316:                 
317:                 OperatorInfo memory operatorInfo = _operatorInfo[operator];
318:                 bytes32 operatorId = operatorInfo.operatorId;
319:                 
320:                 {
321:                     uint192 currentBitmap = _currentOperatorBitmap(operatorId); // <= FOUND
322:                     
323:                     require(
324:                         BitmapUtils.isSet(currentBitmap, quorumNumber), // <= FOUND
325:                         "RegistryCoordinator.updateOperatorsForQuorum: operator not in quorum"
326:                     );
327:                     
328:                     require(
329:                         operator > prevOperatorAddress,
330:                         "RegistryCoordinator.updateOperatorsForQuorum: operators array must be sorted in ascending address order"
331:                     );
332:                 }
333:                 
334:                 
335:                 _updateOperator(operator, operatorInfo, quorumNumbers[i:i+1]); // <= FOUND
336:                 prevOperatorAddress = operator;
337:             }
338: 
339:             
340:             quorumUpdateBlockNumber[quorumNumber] = block.number;
341:             emit QuorumBlockNumberUpdated(quorumNumber, block.number);
342:         }

['162']

162:        for (uint256 i = 0; i < quorumNumbers.length; i++) {
163:             uint8 quorumNumber = uint8(quorumNumbers[i]);
164:             require(_quorumExists(quorumNumber), "StakeRegistry.updateOperatorStake: quorum does not exist"); // <= FOUND
165: 
166:             
167:             
168:             (uint96 stakeWeight, bool hasMinimumStake) = _weightOfOperatorForQuorum(quorumNumber, operator); // <= FOUND
169: 
170:             
171:             if (!hasMinimumStake) {
172:                 stakeWeight = 0;
173:                 quorumsToRemove = uint192(quorumsToRemove.setBit(quorumNumber)); // <= FOUND
174:             }
175: 
176:             
177:             
178:             int256 stakeDelta = _recordOperatorStakeUpdate({ // <= FOUND
179:                 operatorId: operatorId,
180:                 quorumNumber: quorumNumber,
181:                 newStake: stakeWeight
182:             });
183: 
184:             
185:             _recordTotalStakeUpdate(quorumNumber, stakeDelta); // <= FOUND
186:         }

['153']

153:        for (uint256 i = 0; i < quorumNumbers.length; i++) {
154:             
155:             uint8 quorumNumber = uint8(quorumNumbers[i]);
156:             uint256 historyLength = apkHistory[quorumNumber].length;
157:             require(historyLength != 0, "BLSApkRegistry._processQuorumApkUpdate: quorum does not exist");
158: 
159:             
160:             newApk = currentApk[quorumNumber].plus(point); // <= FOUND
161:             currentApk[quorumNumber] = newApk;
162:             bytes24 newApkHash = bytes24(BN254.hashG1Point(newApk)); // <= FOUND
163: 
164:             
165:             
166:             ApkUpdate storage lastUpdate = apkHistory[quorumNumber][historyLength - 1];
167:             if (lastUpdate.updateBlockNumber == uint32(block.number)) {
168:                 lastUpdate.apkHash = newApkHash;
169:             } else {
170:                 lastUpdate.nextUpdateBlockNumber = uint32(block.number);
171:                 apkHistory[quorumNumber].push(ApkUpdate({
172:                     apkHash: newApkHash,
173:                     updateBlockNumber: uint32(block.number),
174:                     nextUpdateBlockNumber: 0
175:                 }));
176:             }
177:         }

['74']

74:        for (uint256 i = 0; i < quorumNumbers.length; i++) {
75:             uint8 quorumNumber = uint8(quorumNumbers[i]);
76:             bytes32[] memory operatorIds = indexRegistry.getOperatorListAtBlockNumber(quorumNumber, blockNumber); // <= FOUND
77:             operators[i] = new Operator[](operatorIds.length);
78:             for (uint256 j = 0; j < operatorIds.length; j++) {
79:                 operators[i][j] = Operator({
80:                     operator: blsApkRegistry.getOperatorFromPubkeyHash(operatorIds[j]), // <= FOUND
81:                     operatorId: bytes32(operatorIds[j]),
82:                     stake: stakeRegistry.getStakeAtBlockNumber(bytes32(operatorIds[j]), quorumNumber, blockNumber) // <= FOUND
83:                 });
84:             }
85:         }

['46']

46:        for (uint256 i = 0; i < quorumNumbers.length; i++) {
47:             
48:             uint8 quorumNumber = uint8(quorumNumbers[i]);
49:             uint256 historyLength = _operatorCountHistory[quorumNumber].length;
50:             require(historyLength != 0, "IndexRegistry.registerOperator: quorum does not exist");
51: 
52:             
53: 
56:             uint32 newOperatorCount = _increaseOperatorCount(quorumNumber); // <= FOUND
57:             _assignOperatorToIndex({ // <= FOUND
58:                 operatorId: operatorId,
59:                 quorumNumber: quorumNumber,
60:                 operatorIndex: newOperatorCount - 1
61:             });
62: 
63:             
64:             numOperatorsPerQuorum[i] = newOperatorCount;
65:         }

['86']

86:        for (uint256 i = 0; i < quorumNumbers.length; i++) {
87:             
88:             uint8 quorumNumber = uint8(quorumNumbers[i]);
89:             uint256 historyLength = _operatorCountHistory[quorumNumber].length;
90:             require(historyLength != 0, "IndexRegistry.registerOperator: quorum does not exist");
91:             uint32 operatorIndexToRemove = currentOperatorIndex[quorumNumber][operatorId];
92: 
93:             
94: 
99:             uint32 newOperatorCount = _decreaseOperatorCount(quorumNumber); // <= FOUND
100:             bytes32 lastOperatorId = _popLastOperator(quorumNumber, newOperatorCount); // <= FOUND
101:             if (operatorId != lastOperatorId) {
102:                 _assignOperatorToIndex({ // <= FOUND
103:                     operatorId: lastOperatorId,
104:                     quorumNumber: quorumNumber,
105:                     operatorIndex: operatorIndexToRemove
106:                 });
107:             }
108:         }

['328']

328:        for (uint256 i = 0; i < operatorCount; i++) {
329:             operatorList[i] = _operatorIdForIndexAtBlockNumber(quorumNumber, uint32(i), blockNumber); // <= FOUND
330:             require(
331:                 operatorList[i] != OPERATOR_DOES_NOT_EXIST_ID, 
332:                 "IndexRegistry.getOperatorListAtBlockNumber: operator does not exist at the given block number"
333:             );
334:         }

['109']

109:        for (uint256 i = 0; i < _operatorSetParams.length; i++) {
110:             _createQuorum(_operatorSetParams[i], _minimumStakes[i], _strategyParams[i]); // <= FOUND
111:         }

['821']

821:        for (uint256 i = 0; i < operatorIds.length; i++) {
822:             indices[i] = _getQuorumBitmapIndexAtBlockNumber(blockNumber, operatorIds[i]); // <= FOUND
823:         }

['120']

120:        for (uint8 quorumNumberIndex = 0; quorumNumberIndex < quorumNumbers.length; quorumNumberIndex++) {
121:             uint256 numNonSignersForQuorum = 0;
122:             
123:             checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex] = new uint32[](nonSignerOperatorIds.length);
124: 
125:             for (uint i = 0; i < nonSignerOperatorIds.length; i++) {
126:                 
127:                 uint192 nonSignerQuorumBitmap = 
128:                     registryCoordinator.getQuorumBitmapAtBlockNumberByIndex( // <= FOUND
129:                         nonSignerOperatorIds[i], 
130:                         referenceBlockNumber, 
131:                         checkSignaturesIndices.nonSignerQuorumBitmapIndices[i]
132:                     );
133:                 
134:                 require(nonSignerQuorumBitmap != 0, "OperatorStateRetriever.getCheckSignaturesIndices: operator must be registered at blocknumber");
135:                 
136:                 
137:                 if ((nonSignerQuorumBitmap >> uint8(quorumNumbers[quorumNumberIndex])) & 1 == 1) {
138:                     
139:                     checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex][numNonSignersForQuorum] = stakeRegistry.getStakeUpdateIndexAtBlockNumber( // <= FOUND
140:                         nonSignerOperatorIds[i],
141:                         uint8(quorumNumbers[quorumNumberIndex]),
142:                         referenceBlockNumber
143:                     );
144:                     numNonSignersForQuorum++;
145:                 }
146:             }
147: 
148:             
149:             uint32[] memory nonSignerStakeIndicesForQuorum = new uint32[](numNonSignersForQuorum);
150:             for (uint i = 0; i < numNonSignersForQuorum; i++) {
151:                 nonSignerStakeIndicesForQuorum[i] = checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex][i];
152:             }
153:             checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex] = nonSignerStakeIndicesForQuorum;
154:         }

['122']

122:        for (uint256 i = 0; i < quorumNumbers.length; i++) {
123:             uint8 quorumNumber = uint8(quorumNumbers[i]);
124:             require(_quorumExists(quorumNumber), "StakeRegistry.deregisterOperator: quorum does not exist"); // <= FOUND
125: 
126:             
127:             int256 stakeDelta = _recordOperatorStakeUpdate({ // <= FOUND
128:                 operatorId: operatorId, 
129:                 quorumNumber: quorumNumber, 
130:                 newStake: 0
131:             });
132: 
133:             
134:             _recordTotalStakeUpdate(quorumNumber, stakeDelta); // <= FOUND
135:         }

['698']

698:        for (uint256 i = 0; i < quorumNumbers.length; i++) {
699:             uint8 quorumNumber = uint8(quorumNumbers[i]);
700:             require(_quorumExists(quorumNumber), "StakeRegistry.getTotalStakeIndicesAtBlockNumber: quorum does not exist"); // <= FOUND
701:             require(
702:                 _totalStakeHistory[quorumNumber][0].updateBlockNumber <= blockNumber,
703:                 "StakeRegistry.getTotalStakeIndicesAtBlockNumber: quorum has no stake history at blockNumber"
704:             );
705:             uint256 length = _totalStakeHistory[quorumNumber].length;
706:             for (uint256 j = 0; j < length; j++) {
707:                 if (_totalStakeHistory[quorumNumber][length - j - 1].updateBlockNumber <= blockNumber) {
708:                     indices[i] = uint32(length - j - 1);
709:                     break;
710:                 }
711:             }
712:         }

[Low-7] For loops in public or external functions should be avoided due to high gas costs and possible DOS

Resolution

In Solidity, for loops can potentially cause Denial of Service (DoS) attacks if not handled carefully. DoS attacks can occur when an attacker intentionally exploits the gas cost of a function, causing it to run out of gas or making it too expensive for other users to call. Below are some scenarios where for loops can lead to DoS attacks: Nested for loops can become exceptionally gas expensive and should be used sparingly

Num of instances: 21

Findings

Click to show findings

['94']

86:     function getRestakeableStrategies() external view returns (address[] memory) {
87:         uint256 quorumCount = _registryCoordinator.quorumCount();
88: 
89:         if (quorumCount == 0) {
90:             return new address[](0);
91:         }
92:         
93:         uint256 strategyCount;
94:         for(uint256 i = 0; i < quorumCount; i++) { // <= FOUND
95:             strategyCount += _stakeRegistry.strategyParamsLength(uint8(i));
96:         }
97: 
98:         address[] memory restakedStrategies = new address[](strategyCount);
99:         uint256 index = 0;
100:         for(uint256 i = 0; i < _registryCoordinator.quorumCount(); i++) { // <= FOUND
101:             uint256 strategyParamsLength = _stakeRegistry.strategyParamsLength(uint8(i));
102:             for (uint256 j = 0; j < strategyParamsLength; j++) { // <= FOUND
103:                 restakedStrategies[index] = address(_stakeRegistry.strategyParamsByIndex(uint8(i), j).strategy);
104:                 index++;
105:             }
106:         }
107:         return restakedStrategies;
108:     }

['128']

117:     function getOperatorRestakedStrategies(address operator) external view returns (address[] memory) {
118:         bytes32 operatorId = _registryCoordinator.getOperatorId(operator);
119:         uint192 operatorBitmap = _registryCoordinator.getCurrentQuorumBitmap(operatorId);
120: 
121:         if (operatorBitmap == 0 || _registryCoordinator.quorumCount() == 0) {
122:             return new address[](0);
123:         }
124: 
125:         
126:         bytes memory operatorRestakedQuorums = BitmapUtils.bitmapToBytesArray(operatorBitmap);
127:         uint256 strategyCount;
128:         for(uint256 i = 0; i < operatorRestakedQuorums.length; i++) { // <= FOUND
129:             strategyCount += _stakeRegistry.strategyParamsLength(uint8(operatorRestakedQuorums[i]));
130:         }
131: 
132:         
133:         address[] memory restakedStrategies = new address[](strategyCount);
134:         uint256 index = 0;
135:         for(uint256 i = 0; i < operatorRestakedQuorums.length; i++) { // <= FOUND
136:             uint8 quorum = uint8(operatorRestakedQuorums[i]);
137:             uint256 strategyParamsLength = _stakeRegistry.strategyParamsLength(quorum);
138:             for (uint256 j = 0; j < strategyParamsLength; j++) { // <= FOUND
139:                 restakedStrategies[index] = address(_stakeRegistry.strategyParamsByIndex(quorum, j).strategy);
140:                 index++;
141:             }
142:         }
143:         return restakedStrategies;        
144:     }

['209']

203:     function getApkIndicesAtBlockNumber(
204:         bytes calldata quorumNumbers,
205:         uint256 blockNumber
206:     ) external view returns (uint32[] memory) {
207:         uint32[] memory indices = new uint32[](quorumNumbers.length);
208:         
209:         for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
210:             uint8 quorumNumber = uint8(quorumNumbers[i]);
211:             
212:             uint256 quorumApkUpdatesLength = apkHistory[quorumNumber].length;
213:             if (quorumApkUpdatesLength == 0 || blockNumber < apkHistory[quorumNumber][0].updateBlockNumber) {
214:                 revert("BLSApkRegistry.getApkIndicesAtBlockNumber: blockNumber is before the first update");
215:             }
216: 
217:             
218:             for (uint256 j = quorumApkUpdatesLength; j > 0; j--) { // <= FOUND
219:                 if (apkHistory[quorumNumber][j - 1].updateBlockNumber <= blockNumber) {
220:                     indices[i] = uint32(j - 1);
221:                     break;
222:                 }
223:             }
224:         }
225:         return indices;
226:     }

['328']

322:     function getOperatorListAtBlockNumber(
323:         uint8 quorumNumber, 
324:         uint32 blockNumber
325:     ) external view returns (bytes32[] memory){
326:         uint32 operatorCount = _operatorCountAtBlockNumber(quorumNumber, blockNumber);
327:         bytes32[] memory operatorList = new bytes32[](operatorCount);
328:         for (uint256 i = 0; i < operatorCount; i++) { // <= FOUND
329:             operatorList[i] = _operatorIdForIndexAtBlockNumber(quorumNumber, uint32(i), blockNumber);
330:             require(
331:                 operatorList[i] != OPERATOR_DOES_NOT_EXIST_ID, 
332:                 "IndexRegistry.getOperatorListAtBlockNumber: operator does not exist at the given block number"
333:             );
334:         }
335:         return operatorList;
336:     }

['120']

104:     function getCheckSignaturesIndices(
105:         IRegistryCoordinator registryCoordinator,
106:         uint32 referenceBlockNumber, 
107:         bytes calldata quorumNumbers, 
108:         bytes32[] calldata nonSignerOperatorIds
109:     ) external view returns (CheckSignaturesIndices memory) {
110:         IStakeRegistry stakeRegistry = registryCoordinator.stakeRegistry();
111:         CheckSignaturesIndices memory checkSignaturesIndices;
112: 
113:         
114:         checkSignaturesIndices.nonSignerQuorumBitmapIndices = registryCoordinator.getQuorumBitmapIndicesAtBlockNumber(referenceBlockNumber, nonSignerOperatorIds);
115: 
116:         
117:         checkSignaturesIndices.totalStakeIndices = stakeRegistry.getTotalStakeIndicesAtBlockNumber(referenceBlockNumber, quorumNumbers);
118:         
119:         checkSignaturesIndices.nonSignerStakeIndices = new uint32[][](quorumNumbers.length);
120:         for (uint8 quorumNumberIndex = 0; quorumNumberIndex < quorumNumbers.length; quorumNumberIndex++) { // <= FOUND
121:             uint256 numNonSignersForQuorum = 0;
122:             
123:             checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex] = new uint32[](nonSignerOperatorIds.length);
124: 
125:             for (uint i = 0; i < nonSignerOperatorIds.length; i++) { // <= FOUND
126:                 
127:                 uint192 nonSignerQuorumBitmap = 
128:                     registryCoordinator.getQuorumBitmapAtBlockNumberByIndex(
129:                         nonSignerOperatorIds[i], 
130:                         referenceBlockNumber, 
131:                         checkSignaturesIndices.nonSignerQuorumBitmapIndices[i]
132:                     );
133:                 
134:                 require(nonSignerQuorumBitmap != 0, "OperatorStateRetriever.getCheckSignaturesIndices: operator must be registered at blocknumber");
135:                 
136:                 
137:                 if ((nonSignerQuorumBitmap >> uint8(quorumNumbers[quorumNumberIndex])) & 1 == 1) {
138:                     
139:                     checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex][numNonSignersForQuorum] = stakeRegistry.getStakeUpdateIndexAtBlockNumber(
140:                         nonSignerOperatorIds[i],
141:                         uint8(quorumNumbers[quorumNumberIndex]),
142:                         referenceBlockNumber
143:                     );
144:                     numNonSignersForQuorum++;
145:                 }
146:             }
147: 
148:             
149:             uint32[] memory nonSignerStakeIndicesForQuorum = new uint32[](numNonSignersForQuorum);
150:             for (uint i = 0; i < numNonSignersForQuorum; i++) { // <= FOUND
151:                 nonSignerStakeIndicesForQuorum[i] = checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex][i];
152:             }
153:             checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex] = nonSignerStakeIndicesForQuorum;
154:         }
155: 
156:         IBLSApkRegistry blsApkRegistry = registryCoordinator.blsApkRegistry();
157:         
158:         checkSignaturesIndices.quorumApkIndices = blsApkRegistry.getApkIndicesAtBlockNumber(quorumNumbers, referenceBlockNumber);
159: 
160:         return checkSignaturesIndices;
161:     }

['109']

82:     function initialize(
83:         address _initialOwner,
84:         address _churnApprover,
85:         address _ejector,
86:         IPauserRegistry _pauserRegistry,
87:         uint256 _initialPausedStatus,
88:         OperatorSetParam[] memory _operatorSetParams,
89:         uint96[] memory _minimumStakes,
90:         IStakeRegistry.StrategyParams[][] memory _strategyParams
91:     ) external initializer {
92:         require(
93:             _operatorSetParams.length == _minimumStakes.length && _minimumStakes.length == _strategyParams.length,
94:             "RegistryCoordinator.initialize: input length mismatch"
95:         );
96:         
97:         
98:         _transferOwnership(_initialOwner);
99:         _initializePauser(_pauserRegistry, _initialPausedStatus);
100:         _setChurnApprover(_churnApprover);
101:         _setEjector(_ejector);
102: 
103:         
104:         registries.push(address(stakeRegistry));
105:         registries.push(address(blsApkRegistry));
106:         registries.push(address(indexRegistry));
107: 
108:         
109:         for (uint256 i = 0; i < _operatorSetParams.length; i++) { // <= FOUND
110:             _createQuorum(_operatorSetParams[i], _minimumStakes[i], _strategyParams[i]);
111:         }
112:     }

['155']

128:     function registerOperator(
129:         bytes calldata quorumNumbers,
130:         string calldata socket,
131:         IBLSApkRegistry.PubkeyRegistrationParams calldata params,
132:         SignatureWithSaltAndExpiry memory operatorSignature
133:     ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) {
134:         
135: 
141:         bytes32 operatorId = _getOrCreateOperatorId(msg.sender, params);
142: 
143:         
144:         
145:         uint32[] memory numOperatorsPerQuorum = _registerOperator({
146:             operator: msg.sender, 
147:             operatorId: operatorId,
148:             quorumNumbers: quorumNumbers, 
149:             socket: socket,
150:             operatorSignature: operatorSignature
151:         }).numOperatorsPerQuorum;
152: 
153:         
154:         
155:         for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
156:             uint8 quorumNumber = uint8(quorumNumbers[i]);
157: 
158:             require(
159:                 numOperatorsPerQuorum[i] <= _quorumParams[quorumNumber].maxOperatorCount,
160:                 "RegistryCoordinator.registerOperator: operator count exceeds maximum"
161:             );
162:         }
163:     }

['216']

177:     function registerOperatorWithChurn(
178:         bytes calldata quorumNumbers, 
179:         string calldata socket,
180:         IBLSApkRegistry.PubkeyRegistrationParams calldata params,
181:         OperatorKickParam[] calldata operatorKickParams,
182:         SignatureWithSaltAndExpiry memory churnApproverSignature,
183:         SignatureWithSaltAndExpiry memory operatorSignature
184:     ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) {
185:         require(operatorKickParams.length == quorumNumbers.length, "RegistryCoordinator.registerOperatorWithChurn: input length mismatch");
186:         
187:         
188: 
194:         bytes32 operatorId = _getOrCreateOperatorId(msg.sender, params);
195: 
196:         
197:         _verifyChurnApproverSignature({
198:             registeringOperator: msg.sender,
199:             registeringOperatorId: operatorId,
200:             operatorKickParams: operatorKickParams,
201:             churnApproverSignature: churnApproverSignature
202:         });
203: 
204:         
205:         
206:         RegisterResults memory results = _registerOperator({
207:             operator: msg.sender,
208:             operatorId: operatorId,
209:             quorumNumbers: quorumNumbers,
210:             socket: socket,
211:             operatorSignature: operatorSignature
212:         });
213: 
214:         
215:         
216:         for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
217:             OperatorSetParam memory operatorSetParams = _quorumParams[uint8(quorumNumbers[i])];
218:             
219:             
220: 
223:             if (results.numOperatorsPerQuorum[i] > operatorSetParams.maxOperatorCount) {
224:                 _validateChurn({
225:                     quorumNumber: uint8(quorumNumbers[i]),
226:                     totalQuorumStake: results.totalStakes[i],
227:                     newOperator: msg.sender,
228:                     newOperatorStake: results.operatorStakes[i],
229:                     kickParams: operatorKickParams[i],
230:                     setParams: operatorSetParams
231:                 });
232: 
233:                 _deregisterOperator(operatorKickParams[i].operator, quorumNumbers[i:i+1]);
234:             }
235:         }
236:     }

['258']

257:     function updateOperators(address[] calldata operators) external onlyWhenNotPaused(PAUSED_UPDATE_OPERATOR) {
258:         for (uint256 i = 0; i < operators.length; i++) { // <= FOUND
259:             address operator = operators[i];
260:             OperatorInfo memory operatorInfo = _operatorInfo[operator];
261:             bytes32 operatorId = operatorInfo.operatorId;
262: 
263:             
264:             uint192 currentBitmap = _currentOperatorBitmap(operatorId);
265:             bytes memory quorumsToUpdate = BitmapUtils.bitmapToBytesArray(currentBitmap);
266:             _updateOperator(operator, operatorInfo, quorumsToUpdate);
267:         }
268:     }

['299']

284:     function updateOperatorsForQuorum(
285:         address[][] calldata operatorsPerQuorum,
286:         bytes calldata quorumNumbers
287:     ) external onlyWhenNotPaused(PAUSED_UPDATE_OPERATOR) {
288:         
289:         
290:         
291:         
292:         uint192 quorumBitmap = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount));
293:         require(
294:             operatorsPerQuorum.length == quorumNumbers.length,
295:             "RegistryCoordinator.updateOperatorsForQuorum: input length mismatch"
296:         );
297: 
298:         
299:         for (uint256 i = 0; i < quorumNumbers.length; ++i) { // <= FOUND
300:             uint8 quorumNumber = uint8(quorumNumbers[i]);
301: 
302:             
303:             address[] calldata currQuorumOperators = operatorsPerQuorum[i];
304:             require(
305:                 currQuorumOperators.length == indexRegistry.totalOperatorsForQuorum(quorumNumber),
306:                 "RegistryCoordinator.updateOperatorsForQuorum: number of updated operators does not match quorum total"
307:             );
308: 
309:             address prevOperatorAddress = address(0);
310:             
311:             
312:             
313:             
314:             for (uint256 j = 0; j < currQuorumOperators.length; ++j) { // <= FOUND
315:                 address operator = currQuorumOperators[j];
316:                 
317:                 OperatorInfo memory operatorInfo = _operatorInfo[operator];
318:                 bytes32 operatorId = operatorInfo.operatorId;
319:                 
320:                 {
321:                     uint192 currentBitmap = _currentOperatorBitmap(operatorId);
322:                     
323:                     require(
324:                         BitmapUtils.isSet(currentBitmap, quorumNumber),
325:                         "RegistryCoordinator.updateOperatorsForQuorum: operator not in quorum"
326:                     );
327:                     
328:                     require(
329:                         operator > prevOperatorAddress,
330:                         "RegistryCoordinator.updateOperatorsForQuorum: operators array must be sorted in ascending address order"
331:                     );
332:                 }
333:                 
334:                 
335:                 _updateOperator(operator, operatorInfo, quorumNumbers[i:i+1]);
336:                 prevOperatorAddress = operator;
337:             }
338: 
339:             
340:             quorumUpdateBlockNumber[quorumNumber] = block.number;
341:             emit QuorumBlockNumberUpdated(quorumNumber, block.number);
342:         }
343:     }

['821']

816:     function getQuorumBitmapIndicesAtBlockNumber(
817:         uint32 blockNumber, 
818:         bytes32[] memory operatorIds
819:     ) external view returns (uint32[] memory) {
820:         uint32[] memory indices = new uint32[](operatorIds.length);
821:         for (uint256 i = 0; i < operatorIds.length; i++) { // <= FOUND
822:             indices[i] = _getQuorumBitmapIndexAtBlockNumber(blockNumber, operatorIds[i]);
823:         }
824:         return indices;
825:     }

['162']

147:     function updateOperatorStake(
148:         address operator, 
149:         bytes32 operatorId, 
150:         bytes calldata quorumNumbers
151:     ) external onlyRegistryCoordinator returns (uint192) {
152:         uint192 quorumsToRemove;
153: 
154:         
155: 
162:         for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
163:             uint8 quorumNumber = uint8(quorumNumbers[i]);
164:             require(_quorumExists(quorumNumber), "StakeRegistry.updateOperatorStake: quorum does not exist");
165: 
166:             
167:             
168:             (uint96 stakeWeight, bool hasMinimumStake) = _weightOfOperatorForQuorum(quorumNumber, operator);
169: 
170:             
171:             if (!hasMinimumStake) {
172:                 stakeWeight = 0;
173:                 quorumsToRemove = uint192(quorumsToRemove.setBit(quorumNumber));
174:             }
175: 
176:             
177:             
178:             int256 stakeDelta = _recordOperatorStakeUpdate({
179:                 operatorId: operatorId,
180:                 quorumNumber: quorumNumber,
181:                 newStake: stakeWeight
182:             });
183: 
184:             
185:             _recordTotalStakeUpdate(quorumNumber, stakeDelta);
186:         }
187: 
188:         return quorumsToRemove;
189:     }

['698']

693:     function getTotalStakeIndicesAtBlockNumber(
694:         uint32 blockNumber,
695:         bytes calldata quorumNumbers
696:     ) external view returns (uint32[] memory) {
697:         uint32[] memory indices = new uint32[](quorumNumbers.length);
698:         for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
699:             uint8 quorumNumber = uint8(quorumNumbers[i]);
700:             require(_quorumExists(quorumNumber), "StakeRegistry.getTotalStakeIndicesAtBlockNumber: quorum does not exist");
701:             require(
702:                 _totalStakeHistory[quorumNumber][0].updateBlockNumber <= blockNumber,
703:                 "StakeRegistry.getTotalStakeIndicesAtBlockNumber: quorum has no stake history at blockNumber"
704:             );
705:             uint256 length = _totalStakeHistory[quorumNumber].length;
706:             for (uint256 j = 0; j < length; j++) { // <= FOUND
707:                 if (_totalStakeHistory[quorumNumber][length - j - 1].updateBlockNumber <= blockNumber) {
708:                     indices[i] = uint32(length - j - 1);
709:                     break;
710:                 }
711:             }
712:         }
713:         return indices;
714:     }

['141']

87:     function checkSignatures(
88:         bytes32 msgHash, 
89:         bytes calldata quorumNumbers,
90:         uint32 referenceBlockNumber, 
91:         NonSignerStakesAndSignature memory params
92:     ) 
93:         public 
94:         view
95:         returns (
96:             QuorumStakeTotals memory,
97:             bytes32
98:         )
99:     {
100:         require(quorumNumbers.length != 0, "BLSSignatureChecker.checkSignatures: empty quorum input");
101: 
102:         require(
103:             (quorumNumbers.length == params.quorumApks.length) &&
104:             (quorumNumbers.length == params.quorumApkIndices.length) &&
105:             (quorumNumbers.length == params.totalStakeIndices.length) &&
106:             (quorumNumbers.length == params.nonSignerStakeIndices.length),
107:             "BLSSignatureChecker.checkSignatures: input quorum length mismatch"
108:         );
109: 
110:         require(
111:             params.nonSignerPubkeys.length == params.nonSignerQuorumBitmapIndices.length,
112:             "BLSSignatureChecker.checkSignatures: input nonsigner length mismatch"
113:         );
114: 
115:         require(referenceBlockNumber < uint32(block.number), "BLSSignatureChecker.checkSignatures: invalid reference block");
116: 
117:         
118:         
119:         
120:         
121:         
122:         
123:         BN254.G1Point memory apk = BN254.G1Point(0, 0);
124: 
125:         
126:         
127:         
128:         QuorumStakeTotals memory stakeTotals;
129:         stakeTotals.totalStakeForQuorum = new uint96[](quorumNumbers.length);
130:         stakeTotals.signedStakeForQuorum = new uint96[](quorumNumbers.length);
131: 
132:         NonSignerInfo memory nonSigners;
133:         nonSigners.quorumBitmaps = new uint256[](params.nonSignerPubkeys.length);
134:         nonSigners.pubkeyHashes = new bytes32[](params.nonSignerPubkeys.length);
135: 
136:         {
137:             
138:             
139:             uint256 signingQuorumBitmap = BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, registryCoordinator.quorumCount());
140: 
141:             for (uint256 j = 0; j < params.nonSignerPubkeys.length; j++) { // <= FOUND
142:                 
143:                 
144:                 
145:                 nonSigners.pubkeyHashes[j] = params.nonSignerPubkeys[j].hashG1Point();
146:                 if (j != 0) {
147:                     require(
148:                         uint256(nonSigners.pubkeyHashes[j]) > uint256(nonSigners.pubkeyHashes[j - 1]),
149:                         "BLSSignatureChecker.checkSignatures: nonSignerPubkeys not sorted"
150:                     );
151:                 }
152: 
153:                 
154:                 nonSigners.quorumBitmaps[j] = 
155:                     registryCoordinator.getQuorumBitmapAtBlockNumberByIndex({
156:                         operatorId: nonSigners.pubkeyHashes[j],
157:                         blockNumber: referenceBlockNumber,
158:                         index: params.nonSignerQuorumBitmapIndices[j]
159:                     });
160: 
161:                 
162:                 
163:                 
164:                 apk = apk.plus(
165:                     params.nonSignerPubkeys[j]
166:                         .scalar_mul_tiny(
167:                             BitmapUtils.countNumOnes(nonSigners.quorumBitmaps[j] & signingQuorumBitmap) 
168:                         )
169:                 );
170:             }
171:         }
172: 
173:         
174:         
175:         
176:         apk = apk.negate();
177: 
178:         
179: 
184:         {
185:             bool _staleStakesForbidden = staleStakesForbidden;
186:             uint256 withdrawalDelayBlocks = _staleStakesForbidden ? delegation.minWithdrawalDelayBlocks() : 0;
187: 
188:             for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
189:                 
190:                 
191:                 if (_staleStakesForbidden) {
192:                     require(
193:                         registryCoordinator.quorumUpdateBlockNumber(uint8(quorumNumbers[i])) + withdrawalDelayBlocks >= referenceBlockNumber,
194:                         "BLSSignatureChecker.checkSignatures: StakeRegistry updates must be within withdrawalDelayBlocks window"
195:                     );
196:                 }
197: 
198:                 
199:                 
200:                 require(
201:                     bytes24(params.quorumApks[i].hashG1Point()) == 
202:                         blsApkRegistry.getApkHashAtBlockNumberAndIndex({
203:                             quorumNumber: uint8(quorumNumbers[i]),
204:                             blockNumber: referenceBlockNumber,
205:                             index: params.quorumApkIndices[i]
206:                         }),
207:                     "BLSSignatureChecker.checkSignatures: quorumApk hash in storage does not match provided quorum apk"
208:                 );
209:                 apk = apk.plus(params.quorumApks[i]);
210: 
211:                 
212:                 stakeTotals.totalStakeForQuorum[i] = 
213:                     stakeRegistry.getTotalStakeAtBlockNumberFromIndex({
214:                         quorumNumber: uint8(quorumNumbers[i]),
215:                         blockNumber: referenceBlockNumber,
216:                         index: params.totalStakeIndices[i]
217:                     });
218:                 stakeTotals.signedStakeForQuorum[i] = stakeTotals.totalStakeForQuorum[i];
219: 
220:                 
221:                 uint256 nonSignerForQuorumIndex = 0;
222:                 
223:                 
224:                 
225:                 for (uint256 j = 0; j < params.nonSignerPubkeys.length; j++) { // <= FOUND
226:                     
227:                     if (BitmapUtils.isSet(nonSigners.quorumBitmaps[j], uint8(quorumNumbers[i]))) {
228:                         stakeTotals.signedStakeForQuorum[i] -=
229:                             stakeRegistry.getStakeAtBlockNumberAndIndex({
230:                                 quorumNumber: uint8(quorumNumbers[i]),
231:                                 blockNumber: referenceBlockNumber,
232:                                 operatorId: nonSigners.pubkeyHashes[j],
233:                                 index: params.nonSignerStakeIndices[i][nonSignerForQuorumIndex]
234:                             });
235:                         unchecked {
236:                             ++nonSignerForQuorumIndex;
237:                         }
238:                     }
239:                 }
240:             }
241:         }
242:         {
243:             
244:             (bool pairingSuccessful, bool signatureIsValid) = trySignatureAndApkVerification(
245:                 msgHash, 
246:                 apk, 
247:                 params.apkG2, 
248:                 params.sigma
249:             );
250:             require(pairingSuccessful, "BLSSignatureChecker.checkSignatures: pairing precompile call failed");
251:             require(signatureIsValid, "BLSSignatureChecker.checkSignatures: signature is invalid");
252:         }
253:         
254:         bytes32 signatoryRecordHash = keccak256(abi.encodePacked(referenceBlockNumber, nonSigners.pubkeyHashes));
255: 
256:         
257:         return (stakeTotals, signatoryRecordHash);
258:     }

['46']

40:     function registerOperator(
41:         bytes32 operatorId, 
42:         bytes calldata quorumNumbers
43:     ) public virtual onlyRegistryCoordinator returns(uint32[] memory) {
44:         uint32[] memory numOperatorsPerQuorum = new uint32[](quorumNumbers.length);
45: 
46:         for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
47:             
48:             uint8 quorumNumber = uint8(quorumNumbers[i]);
49:             uint256 historyLength = _operatorCountHistory[quorumNumber].length;
50:             require(historyLength != 0, "IndexRegistry.registerOperator: quorum does not exist");
51: 
52:             
53: 
56:             uint32 newOperatorCount = _increaseOperatorCount(quorumNumber);
57:             _assignOperatorToIndex({
58:                 operatorId: operatorId,
59:                 quorumNumber: quorumNumber,
60:                 operatorIndex: newOperatorCount - 1
61:             });
62: 
63:             
64:             numOperatorsPerQuorum[i] = newOperatorCount;
65:         }
66: 
67:         return numOperatorsPerQuorum;
68:     }

['86']

82:     function deregisterOperator(
83:         bytes32 operatorId, 
84:         bytes calldata quorumNumbers
85:     ) public virtual onlyRegistryCoordinator {
86:         for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
87:             
88:             uint8 quorumNumber = uint8(quorumNumbers[i]);
89:             uint256 historyLength = _operatorCountHistory[quorumNumber].length;
90:             require(historyLength != 0, "IndexRegistry.registerOperator: quorum does not exist");
91:             uint32 operatorIndexToRemove = currentOperatorIndex[quorumNumber][operatorId];
92: 
93:             
94: 
99:             uint32 newOperatorCount = _decreaseOperatorCount(quorumNumber);
100:             bytes32 lastOperatorId = _popLastOperator(quorumNumber, newOperatorCount);
101:             if (operatorId != lastOperatorId) {
102:                 _assignOperatorToIndex({
103:                     operatorId: lastOperatorId,
104:                     quorumNumber: quorumNumber,
105:                     operatorIndex: operatorIndexToRemove
106:                 });
107:             }
108:         }
109:     }

['74']

64:     function getOperatorState(
65:         IRegistryCoordinator registryCoordinator, 
66:         bytes memory quorumNumbers, 
67:         uint32 blockNumber
68:     ) public view returns(Operator[][] memory) {
69:         IStakeRegistry stakeRegistry = registryCoordinator.stakeRegistry();
70:         IIndexRegistry indexRegistry = registryCoordinator.indexRegistry();
71:         IBLSApkRegistry blsApkRegistry = registryCoordinator.blsApkRegistry();
72: 
73:         Operator[][] memory operators = new Operator[][](quorumNumbers.length);
74:         for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
75:             uint8 quorumNumber = uint8(quorumNumbers[i]);
76:             bytes32[] memory operatorIds = indexRegistry.getOperatorListAtBlockNumber(quorumNumber, blockNumber);
77:             operators[i] = new Operator[](operatorIds.length);
78:             for (uint256 j = 0; j < operatorIds.length; j++) { // <= FOUND
79:                 operators[i][j] = Operator({
80:                     operator: blsApkRegistry.getOperatorFromPubkeyHash(operatorIds[j]),
81:                     operatorId: bytes32(operatorIds[j]),
82:                     stake: stakeRegistry.getStakeAtBlockNumber(bytes32(operatorIds[j]), quorumNumber, blockNumber)
83:                 });
84:             }
85:         }
86:             
87:         return operators;
88:     }

['74']

66:     function registerOperator(
67:         address operator,
68:         bytes32 operatorId,
69:         bytes calldata quorumNumbers
70:     ) public virtual onlyRegistryCoordinator returns (uint96[] memory, uint96[] memory) {
71: 
72:         uint96[] memory currentStakes = new uint96[](quorumNumbers.length);
73:         uint96[] memory totalStakes = new uint96[](quorumNumbers.length);
74:         for (uint256 i = 0; i < quorumNumbers.length; i++) {             // <= FOUND
75:             
76:             uint8 quorumNumber = uint8(quorumNumbers[i]);
77:             require(_quorumExists(quorumNumber), "StakeRegistry.registerOperator: quorum does not exist");
78: 
79:             
80:             
81:             (uint96 currentStake, bool hasMinimumStake) = _weightOfOperatorForQuorum(quorumNumber, operator);
82:             require(
83:                 hasMinimumStake,
84:                 "StakeRegistry.registerOperator: Operator does not meet minimum stake requirement for quorum"
85:             );
86: 
87:             
88:             int256 stakeDelta = _recordOperatorStakeUpdate({
89:                 operatorId: operatorId, 
90:                 quorumNumber: quorumNumber,
91:                 newStake: currentStake
92:             });
93: 
94:             
95:             currentStakes[i] = currentStake;
96:             totalStakes[i] = _recordTotalStakeUpdate(quorumNumber, stakeDelta);
97:         }
98: 
99:         return (currentStakes, totalStakes);
100:     }

['122']

114:     function deregisterOperator(
115:         bytes32 operatorId,
116:         bytes calldata quorumNumbers
117:     ) public virtual onlyRegistryCoordinator {
118:         
119: 
122:         for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
123:             uint8 quorumNumber = uint8(quorumNumbers[i]);
124:             require(_quorumExists(quorumNumber), "StakeRegistry.deregisterOperator: quorum does not exist");
125: 
126:             
127:             int256 stakeDelta = _recordOperatorStakeUpdate({
128:                 operatorId: operatorId, 
129:                 quorumNumber: quorumNumber, 
130:                 newStake: 0
131:             });
132: 
133:             
134:             _recordTotalStakeUpdate(quorumNumber, stakeDelta);
135:         }
136:     }

['243']

233:     function removeStrategies(
234:         uint8 quorumNumber,
235:         uint256[] memory indicesToRemove
236:     ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber) {
237:         uint256 toRemoveLength = indicesToRemove.length;
238:         require(toRemoveLength > 0, "StakeRegistry.removeStrategies: no indices to remove provided");
239: 
240:         StrategyParams[] storage _strategyParams = strategyParams[quorumNumber];
241:         IStrategy[] storage _strategiesPerQuorum = strategiesPerQuorum[quorumNumber];
242: 
243:         for (uint256 i = 0; i < toRemoveLength; i++) { // <= FOUND
244:             emit StrategyRemovedFromQuorum(quorumNumber, _strategyParams[indicesToRemove[i]].strategy);
245:             emit StrategyMultiplierUpdated(quorumNumber, _strategyParams[indicesToRemove[i]].strategy, 0);
246: 
247:             
248:             _strategyParams[indicesToRemove[i]] = _strategyParams[_strategyParams.length - 1];
249:             _strategyParams.pop();
250:             _strategiesPerQuorum[indicesToRemove[i]] = _strategiesPerQuorum[_strategiesPerQuorum.length - 1];
251:             _strategiesPerQuorum.pop();
252:         }
253:     }

['272']

261:     function modifyStrategyParams(
262:         uint8 quorumNumber,
263:         uint256[] calldata strategyIndices,
264:         uint96[] calldata newMultipliers
265:     ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber) {
266:         uint256 numStrats = strategyIndices.length;
267:         require(numStrats > 0, "StakeRegistry.modifyStrategyParams: no strategy indices provided");
268:         require(newMultipliers.length == numStrats, "StakeRegistry.modifyStrategyParams: input length mismatch");
269: 
270:         StrategyParams[] storage _strategyParams = strategyParams[quorumNumber];
271: 
272:         for (uint256 i = 0; i < numStrats; i++) { // <= FOUND
273:             
274:             _strategyParams[strategyIndices[i]].multiplier = newMultipliers[i];
275:             emit StrategyMultiplierUpdated(quorumNumber, _strategyParams[strategyIndices[i]].strategy, newMultipliers[i]);
276:         }
277:     }

[Low-8] No limits when setting min/max amounts

Resolution

When settings min/max state variables, ensure there a require checks in place to prevent incorrect values from being set

Num of instances: 1

Findings

Click to show findings

['208']

208:     function setMinimumStakeForQuorum(
209:         uint8 quorumNumber, 
210:         uint96 minimumStake
211:     ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber) {
212:         _setMinimumStakeForQuorum(quorumNumber, minimumStake);
213:     }

[Low-9] Initializer function can be front run

Resolution

In Solidity contract deployment, not making the initialize() function call atomic with the contract creation can leave a vulnerability window. A malicious actor could exploit this time gap and call initialize() before the intended initialization. This action could disrupt the contract's setup, potentially necessitating a full contract re-deployment to ensure proper initialization. To mitigate such risks, it's advised to use a factory contract. This factory contract can be programmed to deploy and initialize a new contract in a single atomic transaction, closing the window of vulnerability and ensuring correct and secure contract initialization.

Num of instances: 1

Findings

Click to show findings

['91']

82:     function initialize(
83:         address _initialOwner,
84:         address _churnApprover,
85:         address _ejector,
86:         IPauserRegistry _pauserRegistry,
87:         uint256 _initialPausedStatus,
88:         OperatorSetParam[] memory _operatorSetParams,
89:         uint96[] memory _minimumStakes,
90:         IStakeRegistry.StrategyParams[][] memory _strategyParams
91:     ) external initializer  // <= FOUND

[Low-10] Loss of precision

Resolution

Dividing by large numbers in Solidity can cause a loss of precision due to the language's inherent integer division behavior. Solidity does not support floating-point arithmetic, and as a result, division between integers yields an integer result, truncating any fractional part. When dividing by a large number, the resulting value may become significantly smaller, leading to a loss of precision, as the fractional part is discarded.

Num of instances: 2

Findings

Click to show findings

['632']

632:     function _individualKickThreshold(uint96 operatorStake, OperatorSetParam memory setParams) internal pure returns (uint96) { // <= FOUND
633:         return operatorStake * setParams.kickBIPsOfOperatorStake / BIPS_DENOMINATOR; // <= FOUND
634:     }

['640']

640:     function _totalKickThreshold(uint96 totalStake, OperatorSetParam memory setParams) internal pure returns (uint96) { // <= FOUND
641:         return totalStake * setParams.kickBIPsOfTotalStake / BIPS_DENOMINATOR; // <= FOUND
642:     }

[Low-11] Use of onlyOwner functions can be lost

Resolution

In Solidity, renouncing ownership of a contract essentially transfers ownership to the zero address. This is an irreversible operation and has considerable security implications. If the renounceOwnership function is used, the contract will lose the ability to perform any operations that are limited to the owner. This can be problematic if there are any bugs, flaws, or unexpected events that require owner intervention to resolve. Therefore, in some instances, it is better to disable or omit the renounceOwnership function, and instead implement a secure transferOwnership function. This way, if necessary, ownership can be transferred to a new, trusted party without losing the potential for administrative intervention.

Num of instances: 1

Findings

Click to show findings

['36']

32: contract RegistryCoordinator is 
33:     EIP712, 
34:     Initializable, 
35:     Pausable,
36:     OwnableUpgradeable, // <= FOUND
37:     RegistryCoordinatorStorage, 
38:     ISocketUpdater, 
39:     ISignatureUtils
40: 

[Low-12] Critical functions should be a two step procedure

Resolution

Critical functions in Solidity contracts should follow a two-step procedure to enhance security, minimize human error, and ensure proper access control. By dividing sensitive operations into distinct phases, such as initiation and confirmation, developers can introduce a safeguard against unintended actions or unauthorized access.

Num of instances: 6

Findings

Click to show findings

['52']

52:     function setStaleStakesForbidden(bool value) external onlyCoordinatorOwner  // <= FOUND

['400']

400:     function setOperatorSetParams( // <= FOUND
401:         uint8 quorumNumber, 
402:         OperatorSetParam memory operatorSetParams
403:     ) external onlyOwner quorumExists(quorumNumber) 

['413']

413:     function setChurnApprover(address _churnApprover) external onlyOwner  // <= FOUND

['422']

422:     function setEjector(address _ejector) external onlyOwner  // <= FOUND

['56']

56:     function setMetadataURI(string memory _metadataURI) public virtual onlyOwner  // <= FOUND

['208']

208:     function setMinimumStakeForQuorum( // <= FOUND
209:         uint8 quorumNumber, 
210:         uint96 minimumStake
211:     ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber) 

[Low-13] Constant decimal values

Resolution

The use of fixed decimal values such as 1e18 or 1e8 in Solidity contracts can lead to inaccuracies, bugs, and vulnerabilities, particularly when interacting with tokens having different decimal configurations. Not all ERC20 tokens follow the standard 18 decimal places, and assumptions about decimal places can lead to miscalculations.

Resolution: Always retrieve and use the decimals() function from the token contract itself when performing calculations involving token amounts. This ensures that your contract correctly handles tokens with any number of decimal places, mitigating the risk of numerical errors or under/overflows that could jeopardize contract integrity and user funds.

Num of instances: 1

Findings

Click to show findings

['20']

18:     
20:     uint256 public constant WEIGHTING_DIVISOR = 1e18; // <= FOUND

[Low-14] Arrays can grow in size without a way to shrink them

Resolution

It's a good practice to maintain control over the size of array state variables in Solidity, especially if they are dynamically updated. If a contract includes a mechanism to push items into an array, it should ideally also provide a mechanism to remove items. This is because Solidity arrays don't automatically shrink when items are deleted - their length needs to be manually adjusted.

Ignoring this can lead to bloated and inefficient contracts. For instance, iterating over a large array can cause your contract to hit the block gas limit. Additionally, if entries are only marked for deletion but never actually removed, you may end up dealing with stale or irrelevant data, which can cause logical errors.

Therefore, implementing a method to 'pop' items from arrays helps manage contract's state, improve efficiency and prevent potential issues related to gas limits or stale data. Always ensure to handle potential underflow conditions when popping elements from the array.

Num of instances: 1

Findings

Click to show findings

['61']

61: address[] public registries; // <= FOUND

[Low-15] Draft imports may break in new minor versions

Resolution

Utilizing draft contracts from OpenZeppelin, despite their audit, carries potential risks due to their 'draft' status. These contracts are based on non-finalized EIPs, potentially leading to unforeseen changes in even minor updates. In case a flaw surfaces in this OpenZeppelin version, and the necessary upgrade version introduces breaking changes, this could result in unexpected delays in developing and testing replacement contracts. To mitigate this, ensure comprehensive test coverage, enabling automated detection of differences, and establish a contingency plan for thoroughly testing a new version if changes occur. It may be advisable to create a forked file version instead of importing directly from the package, permitting manual updates to your fork as necessary.

Num of instances: 1

Findings

Click to show findings

['19']

19: import {EIP712} from "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol"; // <= FOUND

[Low-16] Unsafe uint to int conversion

Resolution

Unsafe conversion from uint to int in Solidity can lead to unexpected overflows if the unsigned integer value exceeds the positive limit of the corresponding signed integer type. This is due to the fact that signed integers use one bit for the sign, effectively halving the positive range. An example is converting a uint256 number greater than type(uint128).max to an int256, which can result in an overflow. To mitigate this risk, consider using libraries like SafeCast, which include functions specifically designed to safely cast between different numerical types. They perform necessary checks and revert the transaction if an unsafe cast is attempted, thereby enhancing the security and robustness of the code.

Num of instances: 1

Findings

Click to show findings

['434']

434:         return int256(uint256(cur)) - int256(uint256(prev)); // <= FOUND

[Low-17] Missing zero address check in initializer

Resolution

Initializer functions in contracts often set important parameters or addresses. Failing to check for the zero address (0x0000000000000000000000000000000000000000) in initializers can lead to unintended behavior, as this address typically signifies an unset or default value. Transfers to or interactions with the zero address can result in permanent loss of assets or broken functionality. It's crucial to add checks using require(targetAddress != address(0), "Address cannot be zero") in initializers to prevent accidentally setting important state variables or parameters to this address, ensuring the system's integrity and user asset safety.

Num of instances: 1

Findings

Click to show findings

['82']

82:     function initialize(
83:         address _initialOwner,
84:         address _churnApprover,
85:         address _ejector,
86:         IPauserRegistry _pauserRegistry,
87:         uint256 _initialPausedStatus,
88:         OperatorSetParam[] memory _operatorSetParams,
89:         uint96[] memory _minimumStakes,
90:         IStakeRegistry.StrategyParams[][] memory _strategyParams
91:     ) external initializer {
92:         require(
93:             _operatorSetParams.length == _minimumStakes.length && _minimumStakes.length == _strategyParams.length,
94:             "RegistryCoordinator.initialize: input length mismatch"
95:         );
96:         
97:         
98:         _transferOwnership(_initialOwner);
99:         _initializePauser(_pauserRegistry, _initialPausedStatus);
100:         _setChurnApprover(_churnApprover);
101:         _setEjector(_ejector);
102: 
103:         
104:         registries.push(address(stakeRegistry));
105:         registries.push(address(blsApkRegistry));
106:         registries.push(address(indexRegistry));
107: 
108:         
109:         for (uint256 i = 0; i < _operatorSetParams.length; i++) {
110:             _createQuorum(_operatorSetParams[i], _minimumStakes[i], _strategyParams[i]);
111:         }
112:     }

[Low-18] Critical functions should have a timelock

Resolution

Critical functions, especially those affecting protocol parameters or user funds, are potential points of failure or exploitation. To mitigate risks, incorporating a timelock on such functions can be beneficial. A timelock requires a waiting period between the time an action is initiated and when it's executed, giving stakeholders time to react, potentially vetoing malicious or erroneous changes. To implement, integrate a smart contract like OpenZeppelin's TimelockController or build a custom mechanism. This ensures governance decisions or administrative changes are transparent and allows for community or multi-signature interventions, enhancing protocol security and trustworthiness.

Num of instances: 6

Findings

Click to show findings

['52']

52:     function setStaleStakesForbidden(bool value) external onlyCoordinatorOwner  // <= FOUND

['400']

400:     function setOperatorSetParams( // <= FOUND
401:         uint8 quorumNumber, 
402:         OperatorSetParam memory operatorSetParams
403:     ) external onlyOwner quorumExists(quorumNumber) 

['413']

413:     function setChurnApprover(address _churnApprover) external onlyOwner  // <= FOUND

['422']

422:     function setEjector(address _ejector) external onlyOwner  // <= FOUND

['56']

56:     function setMetadataURI(string memory _metadataURI) public virtual onlyOwner  // <= FOUND

['208']

208:     function setMinimumStakeForQuorum( // <= FOUND
209:         uint8 quorumNumber, 
210:         uint96 minimumStake
211:     ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber) 

[Low-19] Mapping arrays can grow in size without a way to shrink them

Resolution

It's a good practice to maintain control over the size of array mappings in Solidity, especially if they are dynamically updated. If a contract includes a mechanism to push items into an array, it should ideally also provide a mechanism to remove items. This is because Solidity arrays don't automatically shrink when items are deleted - their length needs to be manually adjusted.

Ignoring this can lead to bloated and inefficient contracts. For instance, iterating over a large array can cause your contract to hit the block gas limit. Additionally, if entries are only marked for deletion but never actually removed, you may end up dealing with stale or irrelevant data, which can cause logical errors.

Therefore, implementing a method to 'pop' items from mapping arrays helps manage contract's state, improve efficiency and prevent potential issues related to gas limits or stale data. Always ensure to handle potential underflow conditions when popping elements from the mapping array.

Num of instances: 8

Findings

Click to show findings

['28']

28:     mapping(uint8 => ApkUpdate[]) public apkHistory; // <= FOUND

['29']

29:     mapping(uint8 => mapping(uint32 => OperatorUpdate[])) internal _operatorIndexHistory; // <= FOUND

['31']

31:     mapping(uint8 => QuorumUpdate[]) internal _operatorCountHistory; // <= FOUND

['52']

52:     mapping(bytes32 => QuorumBitmapUpdate[]) internal _operatorBitmapHistory; // <= FOUND

['35']

35:     mapping(uint8 => StakeUpdate[]) internal _totalStakeHistory; // <= FOUND

['38']

38:     mapping(bytes32 => mapping(uint8 => StakeUpdate[])) internal operatorStakeHistory; // <= FOUND

['44']

44:     mapping(uint8 => StrategyParams[]) public strategyParams; // <= FOUND

['45']

45:     mapping(uint8 => IStrategy[]) public strategiesPerQuorum; // <= FOUND

[Low-20] Consider implementing two-step procedure for updating protocol addresses

Resolution

Implementing a two-step procedure for updating protocol addresses adds an extra layer of security. In such a system, the first step initiates the change, and the second step, after a predefined delay, confirms and finalizes it. This delay allows stakeholders or monitoring tools to observe and react to unintended or malicious changes. If an unauthorized change is detected, corrective actions can be taken before the change is finalized. To achieve this, introduce a "proposed address" state variable and a "delay period". Upon an update request, set the "proposed address". After the delay, if not contested, the main protocol address can be updated.

Num of instances: 2

Findings

Click to show findings

['413']

413:     function setChurnApprover(address _churnApprover) external onlyOwner { // <= FOUND
414:         _setChurnApprover(_churnApprover);
415:     }

['422']

422:     function setEjector(address _ejector) external onlyOwner { // <= FOUND
423:         _setEjector(_ejector);
424:     }

[Low-21] Prefer skip over revert model in iteration

Resolution

It is preferable to skip operations on an array index when a condition is not met rather than reverting the whole transaction as reverting can introduce the possiblity of malicous actors purposefully introducing array objects which fail conditional checks within for/while loops so group operations fail. As such it is recommended to simply skip such array indices over reverting unless there is a valid security or logic reason behind not doing so.

Num of instances: 4

Findings

Click to show findings

['188']

188:            for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
189:                 
190:                 
191:                 if (_staleStakesForbidden) {
192:                     require(
193:                         registryCoordinator.quorumUpdateBlockNumber(uint8(quorumNumbers[i])) + withdrawalDelayBlocks >= referenceBlockNumber,
194:                         "BLSSignatureChecker.checkSignatures: StakeRegistry updates must be within withdrawalDelayBlocks window"
195:                     );
196:                 }
197: 
198:                 
199:                 
200:                 require(
201:                     bytes24(params.quorumApks[i].hashG1Point()) == 
202:                         blsApkRegistry.getApkHashAtBlockNumberAndIndex({
203:                             quorumNumber: uint8(quorumNumbers[i]),
204:                             blockNumber: referenceBlockNumber,
205:                             index: params.quorumApkIndices[i]
206:                         }),
207:                     "BLSSignatureChecker.checkSignatures: quorumApk hash in storage does not match provided quorum apk"
208:                 );
209:                 apk = apk.plus(params.quorumApks[i]);
210: 
211:                 
212:                 stakeTotals.totalStakeForQuorum[i] = 
213:                     stakeRegistry.getTotalStakeAtBlockNumberFromIndex({
214:                         quorumNumber: uint8(quorumNumbers[i]),
215:                         blockNumber: referenceBlockNumber,
216:                         index: params.totalStakeIndices[i]
217:                     });
218:                 stakeTotals.signedStakeForQuorum[i] = stakeTotals.totalStakeForQuorum[i];
219: 
220:                 
221:                 uint256 nonSignerForQuorumIndex = 0;
222:                 
223:                 
224:                 
225:                 for (uint256 j = 0; j < params.nonSignerPubkeys.length; j++) { // <= FOUND
226:                     
227:                     if (BitmapUtils.isSet(nonSigners.quorumBitmaps[j], uint8(quorumNumbers[i]))) {
228:                         stakeTotals.signedStakeForQuorum[i] -=
229:                             stakeRegistry.getStakeAtBlockNumberAndIndex({
230:                                 quorumNumber: uint8(quorumNumbers[i]),
231:                                 blockNumber: referenceBlockNumber,
232:                                 operatorId: nonSigners.pubkeyHashes[j],
233:                                 index: params.nonSignerStakeIndices[i][nonSignerForQuorumIndex]
234:                             });
235:                         unchecked {
236:                             ++nonSignerForQuorumIndex;
237:                         }
238:                     }
239:                 }
240:             }

['328']

328:        for (uint256 i = 0; i < operatorCount; i++) { // <= FOUND
329:             operatorList[i] = _operatorIdForIndexAtBlockNumber(quorumNumber, uint32(i), blockNumber);
330:             require(
331:                 operatorList[i] != OPERATOR_DOES_NOT_EXIST_ID, 
332:                 "IndexRegistry.getOperatorListAtBlockNumber: operator does not exist at the given block number"
333:             );
334:         }

['409']

409:        for (uint256 i = 0; i < numStratsToAdd; i++) { // <= FOUND
410:             
411:             for (uint256 j = 0; j < (numStratsExisting + i); j++) {
412:                 require(
413:                     strategyParams[quorumNumber][j].strategy != _strategyParams[i].strategy,
414:                     "StakeRegistry._addStrategyParams: cannot add same strategy 2x"
415:                 );
416:             }
417:             require(
418:                 _strategyParams[i].multiplier > 0,
419:                 "StakeRegistry._addStrategyParams: cannot add strategy with zero weight"
420:             );
421:             strategyParams[quorumNumber].push(_strategyParams[i]);
422:             strategiesPerQuorum[quorumNumber].push(_strategyParams[i].strategy);
423:             emit StrategyAddedToQuorum(quorumNumber, _strategyParams[i].strategy);
424:             emit StrategyMultiplierUpdated(
425:                 quorumNumber,
426:                 _strategyParams[i].strategy,
427:                 _strategyParams[i].multiplier
428:             );
429:         }

['698']

698:        for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
699:             uint8 quorumNumber = uint8(quorumNumbers[i]);
700:             require(_quorumExists(quorumNumber), "StakeRegistry.getTotalStakeIndicesAtBlockNumber: quorum does not exist");
701:             require(
702:                 _totalStakeHistory[quorumNumber][0].updateBlockNumber <= blockNumber,
703:                 "StakeRegistry.getTotalStakeIndicesAtBlockNumber: quorum has no stake history at blockNumber"
704:             );
705:             uint256 length = _totalStakeHistory[quorumNumber].length;
706:             for (uint256 j = 0; j < length; j++) {
707:                 if (_totalStakeHistory[quorumNumber][length - j - 1].updateBlockNumber <= blockNumber) {
708:                     indices[i] = uint32(length - j - 1);
709:                     break;
710:                 }
711:             }
712:         }

[Low-22] Constructors missing validation

Resolution

In Solidity, when values are being assigned in constructors to unsigned or integer variables, it's crucial to ensure the provided values adhere to the protocol's specific operational boundaries as laid out in the project specifications and documentation. If the constructors lack appropriate validation checks, there's a risk of setting state variables with values that could cause unexpected and potentially detrimental behavior within the contract's operations, violating the intended logic of the protocol. This can compromise the contract's security and impact the maintainability and reliability of the system. In order to avoid such issues, it is recommended to incorporate rigorous validation checks in constructors. These checks should align with the project's defined rules and constraints, making use of Solidity's built-in require function to enforce these conditions. If the validation checks fail, the require function will cause the transaction to revert, ensuring the integrity and adherence to the protocol's expected behavior.

Num of instances: 4

Findings

Click to show findings

['38']

38:     constructor(IRegistryCoordinator _registryCoordinator) {
39:         registryCoordinator = _registryCoordinator; // <= FOUND
40:         stakeRegistry = _registryCoordinator.stakeRegistry();
41:         blsApkRegistry = _registryCoordinator.blsApkRegistry();
42:         delegation = stakeRegistry.delegation();
43:         
44:         staleStakesForbidden = true;
45:     }

['67']

67:     constructor(
68:         IServiceManager _serviceManager,
69:         IStakeRegistry _stakeRegistry,
70:         IBLSApkRegistry _blsApkRegistry,
71:         IIndexRegistry _indexRegistry
72:     ) {
73:         serviceManager = _serviceManager; // <= FOUND
74:         stakeRegistry = _stakeRegistry; // <= FOUND
75:         blsApkRegistry = _blsApkRegistry; // <= FOUND
76:         indexRegistry = _indexRegistry; // <= FOUND
77:     }

['36']

36:     constructor(
37:         IAVSDirectory __avsDirectory,
38:         IRegistryCoordinator __registryCoordinator,
39:         IStakeRegistry __stakeRegistry
40:     ) {
41:         _avsDirectory = __avsDirectory; // <= FOUND
42:         _registryCoordinator = __registryCoordinator; // <= FOUND
43:         _stakeRegistry = __stakeRegistry; // <= FOUND
44:         _disableInitializers();
45:     }

['48']

48:     constructor(
49:         IRegistryCoordinator _registryCoordinator, 
50:         IDelegationManager _delegationManager
51:     ) {
52:         registryCoordinator = address(_registryCoordinator);
53:         delegation = _delegationManager; // <= FOUND
54:     }

[NonCritical-1] A function which defines named returns in it's declaration doesn't need to use return

Resolution

Remove the return statement once ensuring it is safe to do so

Num of instances: 6

Findings

Click to show findings

['143']

100:     function registerBLSPublicKey(
101:         address operator,
102:         PubkeyRegistrationParams calldata params,
103:         BN254.G1Point calldata pubkeyRegistrationMessageHash
104:     ) external onlyRegistryCoordinator returns (bytes32 operatorId) {
105:         bytes32 pubkeyHash = BN254.hashG1Point(params.pubkeyG1);
106:         require(
107:             pubkeyHash != ZERO_PK_HASH, "BLSApkRegistry.registerBLSPublicKey: cannot register zero pubkey"
108:         );
109:         require(
110:             operatorToPubkeyHash[operator] == bytes32(0),
111:             "BLSApkRegistry.registerBLSPublicKey: operator already registered pubkey"
112:         );
113:         require(
114:             pubkeyHashToOperator[pubkeyHash] == address(0),
115:             "BLSApkRegistry.registerBLSPublicKey: public key already registered"
116:         );
117: 
118:         
119:         uint256 gamma = uint256(keccak256(abi.encodePacked(
120:             params.pubkeyRegistrationSignature.X, 
121:             params.pubkeyRegistrationSignature.Y, 
122:             params.pubkeyG1.X, 
123:             params.pubkeyG1.Y, 
124:             params.pubkeyG2.X, 
125:             params.pubkeyG2.Y, 
126:             pubkeyRegistrationMessageHash.X, 
127:             pubkeyRegistrationMessageHash.Y
128:         ))) % BN254.FR_MODULUS;
129:         
130:         
131:         require(BN254.pairing(
132:             params.pubkeyRegistrationSignature.plus(params.pubkeyG1.scalar_mul(gamma)),
133:             BN254.negGeneratorG2(),
134:             pubkeyRegistrationMessageHash.plus(BN254.generatorG1().scalar_mul(gamma)),
135:             params.pubkeyG2
136:         ), "BLSApkRegistry.registerBLSPublicKey: either the G1 signature is wrong, or G1 and G2 private key do not match");
137: 
138:         operatorToPubkey[operator] = params.pubkeyG1;
139:         operatorToPubkeyHash[operator] = pubkeyHash;
140:         pubkeyHashToOperator[pubkeyHash] = operator;
141: 
142:         emit NewPubkeyRegistration(operator, params.pubkeyG1, params.pubkeyG2);
143:         return pubkeyHash; // <= FOUND
144:     }

['348']

329:     function expMod(uint256 _base, uint256 _exponent, uint256 _modulus) internal view returns (uint256 retval) {
330:         bool success;
331:         uint256[1] memory output;
332:         uint[6] memory input;
333:         input[0] = 0x20; 
334:         input[1] = 0x20; 
335:         input[2] = 0x20; 
336:         input[3] = _base;
337:         input[4] = _exponent;
338:         input[5] = _modulus;
339:         assembly {
340:             success := staticcall(sub(gas(), 2000), 5, input, 0xc0, output, 0x20)
341:             
342:             switch success
343:             case 0 {
344:                 invalid()
345:             }
346:         }
347:         require(success, "BN254.expMod: call failure");
348:         return output[0]; // <= FOUND
349:     }

['237']

235:     function _latestQuorumUpdate(uint8 quorumNumber) internal view returns (QuorumUpdate storage) {
236:         uint256 historyLength = _operatorCountHistory[quorumNumber].length;
237:         return _operatorCountHistory[quorumNumber][historyLength - 1]; // <= FOUND
238:     }

['244']

242:     function _latestOperatorIndexUpdate(uint8 quorumNumber, uint32 operatorIndex) internal view returns (OperatorUpdate storage) {
243:         uint256 historyLength = _operatorIndexHistory[quorumNumber][operatorIndex].length;
244:         return _operatorIndexHistory[quorumNumber][operatorIndex][historyLength - 1]; // <= FOUND
245:     }

['510']

502:     function _getOrCreateOperatorId(
503:         address operator,
504:         IBLSApkRegistry.PubkeyRegistrationParams calldata params
505:     ) internal returns (bytes32 operatorId) {
506:         operatorId = blsApkRegistry.getOperatorId(operator);
507:         if (operatorId == 0) {
508:             operatorId = blsApkRegistry.registerBLSPublicKey(operator, params, pubkeyRegistrationMessageHash(operator));
509:         }
510:         return operatorId; // <= FOUND
511:     }

['758']

746:     function _getQuorumBitmapIndexAtBlockNumber(
747:         uint32 blockNumber, 
748:         bytes32 operatorId
749:     ) internal view returns (uint32 index) {
750:         uint256 length = _operatorBitmapHistory[operatorId].length;
751: 
752:         
753:         
754:         for (uint256 i = 0; i < length; i++) {
755:             index = uint32(length - i - 1);
756: 
757:             if (_operatorBitmapHistory[operatorId][index].updateBlockNumber <= blockNumber) {
758:                 return index; // <= FOUND
759:             }
760:         }
761: 
762:         revert(
763:             "RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId at block number"
764:         );
765:     }

[NonCritical-2] Some if-statement can be converted to a ternary

Resolution

Improving code readability and compactness is an integral part of optimal programming practices. The use of ternary operators in place of if-else conditions is one such measure. Ternary operators allow us to write conditional statements in a more concise manner, thereby enhancing readability and simplicity. They follow the syntax condition ? exprIfTrue : exprIfFalse, which interprets as "if the condition is true, evaluate to exprIfTrue, else evaluate to exprIfFalse". By adopting this approach, we make our code more streamlined and intuitive, which could potentially aid in better understanding and maintenance of the codebase.

Num of instances: 5

Findings

Click to show findings

['171']

171:        if (lastUpdate.fromBlockNumber == uint32(block.number)) {
172:             lastUpdate.numOperators = newOperatorCount; // <= FOUND
173:         }

['223']

223:        if (lastUpdate.fromBlockNumber == uint32(block.number)) {
224:             lastUpdate.operatorId = newOperatorId; // <= FOUND
225:         }

['507']

507:         if (operatorId == 0) {
508:             operatorId = blsApkRegistry.registerBLSPublicKey(operator, params, pubkeyRegistrationMessageHash(operator)); // <= FOUND
509:         }

['717']

717:             if (lastUpdate.updateBlockNumber == uint32(block.number)) {
718:                 lastUpdate.quorumBitmap = newBitmap; // <= FOUND
719:             }

['378']

378:         if (lastStakeUpdate.updateBlockNumber == uint32(block.number)) {
379:             lastStakeUpdate.stake = newStake; // <= FOUND
380:         }

[NonCritical-3] For loop iterates on arrays not indexed

Resolution

Ensuring matching input array lengths in functions is crucial to prevent logical errors and inconsistencies in smart contracts. Mismatched array lengths can lead to incomplete operations or incorrect data handling, particularly if one array's length is assumed to reflect another's. For instance, iterating over a shorter array than intended could result in unprocessed elements from a longer array, potentially causing unintended consequences in contract execution. Validating that all input arrays have intended lengths before performing operations helps safeguard against such errors, ensuring that all elements are appropriately processed and the contract behaves as expected.

Num of instances: 7

Findings

Click to show findings

['209']

209:        for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
210:             uint8 quorumNumber = uint8(quorumNumbers[i]);
211:             
212:             uint256 quorumApkUpdatesLength = apkHistory[quorumNumber].length;
213:             if (quorumApkUpdatesLength == 0 || blockNumber < apkHistory[quorumNumber][0].updateBlockNumber) {
214:                 revert("BLSApkRegistry.getApkIndicesAtBlockNumber: blockNumber is before the first update");
215:             }
216: 
217:             
218:             for (uint256 j = quorumApkUpdatesLength; j > 0; j--) {
219:                 if (apkHistory[quorumNumber][j - 1].updateBlockNumber <= blockNumber) {
220:                     indices[i] = uint32(j - 1); // <= FOUND
221:                     break;
222:                 }
223:             }
224:         }

['188']

188:            for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
189:                 
190:                 
191:                 if (_staleStakesForbidden) {
192:                     require(
193:                         registryCoordinator.quorumUpdateBlockNumber(uint8(quorumNumbers[i])) + withdrawalDelayBlocks >= referenceBlockNumber,
194:                         "BLSSignatureChecker.checkSignatures: StakeRegistry updates must be within withdrawalDelayBlocks window"
195:                     );
196:                 }
197: 
198:                 
199:                 
200:                 require(
201:                     bytes24(params.quorumApks[i].hashG1Point()) == 
202:                         blsApkRegistry.getApkHashAtBlockNumberAndIndex({
203:                             quorumNumber: uint8(quorumNumbers[i]),
204:                             blockNumber: referenceBlockNumber,
205:                             index: params.quorumApkIndices[i]
206:                         }),
207:                     "BLSSignatureChecker.checkSignatures: quorumApk hash in storage does not match provided quorum apk"
208:                 );
209:                 apk = apk.plus(params.quorumApks[i]); // <= FOUND
210: 
211:                 
212:                 stakeTotals.totalStakeForQuorum[i] = 
213:                     stakeRegistry.getTotalStakeAtBlockNumberFromIndex({
214:                         quorumNumber: uint8(quorumNumbers[i]),
215:                         blockNumber: referenceBlockNumber,
216:                         index: params.totalStakeIndices[i]
217:                     });
218:                 stakeTotals.signedStakeForQuorum[i] = stakeTotals.totalStakeForQuorum[i]; // <= FOUND
219: 
220:                 
221:                 uint256 nonSignerForQuorumIndex = 0;
222:                 
223:                 
224:                 
225:                 for (uint256 j = 0; j < params.nonSignerPubkeys.length; j++) {
226:                     
227:                     if (BitmapUtils.isSet(nonSigners.quorumBitmaps[j], uint8(quorumNumbers[i]))) {
228:                         stakeTotals.signedStakeForQuorum[i] -=
229:                             stakeRegistry.getStakeAtBlockNumberAndIndex({
230:                                 quorumNumber: uint8(quorumNumbers[i]),
231:                                 blockNumber: referenceBlockNumber,
232:                                 operatorId: nonSigners.pubkeyHashes[j],
233:                                 index: params.nonSignerStakeIndices[i][nonSignerForQuorumIndex]
234:                             });
235:                         unchecked {
236:                             ++nonSignerForQuorumIndex;
237:                         }
238:                     }
239:                 }
240:             }

['46']

46:        for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
47:             
48:             uint8 quorumNumber = uint8(quorumNumbers[i]);
49:             uint256 historyLength = _operatorCountHistory[quorumNumber].length;
50:             require(historyLength != 0, "IndexRegistry.registerOperator: quorum does not exist");
51: 
52:             
53: 
56:             uint32 newOperatorCount = _increaseOperatorCount(quorumNumber);
57:             _assignOperatorToIndex({
58:                 operatorId: operatorId,
59:                 quorumNumber: quorumNumber,
60:                 operatorIndex: newOperatorCount - 1
61:             });
62: 
63:             
64:             numOperatorsPerQuorum[i] = newOperatorCount;
65:         }

['74']

74:        for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
75:             uint8 quorumNumber = uint8(quorumNumbers[i]);
76:             bytes32[] memory operatorIds = indexRegistry.getOperatorListAtBlockNumber(quorumNumber, blockNumber);
77:             operators[i] = new Operator[](operatorIds.length); // <= FOUND
78:             for (uint256 j = 0; j < operatorIds.length; j++) {
79:                 operators[i][j] = Operator({ // <= FOUND
80:                     operator: blsApkRegistry.getOperatorFromPubkeyHash(operatorIds[j]),
81:                     operatorId: bytes32(operatorIds[j]),
82:                     stake: stakeRegistry.getStakeAtBlockNumber(bytes32(operatorIds[j]), quorumNumber, blockNumber)
83:                 });
84:             }
85:         }

['120']

120:        for (uint8 quorumNumberIndex = 0; quorumNumberIndex < quorumNumbers.length; quorumNumberIndex++) { // <= FOUND
121:             uint256 numNonSignersForQuorum = 0;
122:             
123:             checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex] = new uint32[](nonSignerOperatorIds.length);
124: 
125:             for (uint i = 0; i < nonSignerOperatorIds.length; i++) {
126:                 
127:                 uint192 nonSignerQuorumBitmap = 
128:                     registryCoordinator.getQuorumBitmapAtBlockNumberByIndex(
129:                         nonSignerOperatorIds[i], 
130:                         referenceBlockNumber, 
131:                         checkSignaturesIndices.nonSignerQuorumBitmapIndices[i]
132:                     );
133:                 
134:                 require(nonSignerQuorumBitmap != 0, "OperatorStateRetriever.getCheckSignaturesIndices: operator must be registered at blocknumber");
135:                 
136:                 
137:                 if ((nonSignerQuorumBitmap >> uint8(quorumNumbers[quorumNumberIndex])) & 1 == 1) {
138:                     
139:                     checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex][numNonSignersForQuorum] = stakeRegistry.getStakeUpdateIndexAtBlockNumber(
140:                         nonSignerOperatorIds[i],
141:                         uint8(quorumNumbers[quorumNumberIndex]),
142:                         referenceBlockNumber
143:                     );
144:                     numNonSignersForQuorum++;
145:                 }
146:             }
147: 
148:             
149:             uint32[] memory nonSignerStakeIndicesForQuorum = new uint32[](numNonSignersForQuorum);
150:             for (uint i = 0; i < numNonSignersForQuorum; i++) {
151:                 nonSignerStakeIndicesForQuorum[i] = checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex][i]; // <= FOUND
152:             }
153:             checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex] = nonSignerStakeIndicesForQuorum;
154:         }

['299']

299:        for (uint256 i = 0; i < quorumNumbers.length; ++i) { // <= FOUND
300:             uint8 quorumNumber = uint8(quorumNumbers[i]);
301: 
302:             
303:             address[] calldata currQuorumOperators = operatorsPerQuorum[i];
304:             require(
305:                 currQuorumOperators.length == indexRegistry.totalOperatorsForQuorum(quorumNumber),
306:                 "RegistryCoordinator.updateOperatorsForQuorum: number of updated operators does not match quorum total"
307:             );
308: 
309:             address prevOperatorAddress = address(0);
310:             
311:             
312:             
313:             
314:             for (uint256 j = 0; j < currQuorumOperators.length; ++j) {
315:                 address operator = currQuorumOperators[j];
316:                 
317:                 OperatorInfo memory operatorInfo = _operatorInfo[operator];
318:                 bytes32 operatorId = operatorInfo.operatorId;
319:                 
320:                 {
321:                     uint192 currentBitmap = _currentOperatorBitmap(operatorId);
322:                     
323:                     require(
324:                         BitmapUtils.isSet(currentBitmap, quorumNumber),
325:                         "RegistryCoordinator.updateOperatorsForQuorum: operator not in quorum"
326:                     );
327:                     
328:                     require(
329:                         operator > prevOperatorAddress,
330:                         "RegistryCoordinator.updateOperatorsForQuorum: operators array must be sorted in ascending address order"
331:                     );
332:                 }
333:                 
334:                 
335:                 _updateOperator(operator, operatorInfo, quorumNumbers[i:i+1]);
336:                 prevOperatorAddress = operator;
337:             }
338: 
339:             
340:             quorumUpdateBlockNumber[quorumNumber] = block.number;
341:             emit QuorumBlockNumberUpdated(quorumNumber, block.number);
342:         }

['698']

698:        for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
699:             uint8 quorumNumber = uint8(quorumNumbers[i]);
700:             require(_quorumExists(quorumNumber), "StakeRegistry.getTotalStakeIndicesAtBlockNumber: quorum does not exist");
701:             require(
702:                 _totalStakeHistory[quorumNumber][0].updateBlockNumber <= blockNumber,
703:                 "StakeRegistry.getTotalStakeIndicesAtBlockNumber: quorum has no stake history at blockNumber"
704:             );
705:             uint256 length = _totalStakeHistory[quorumNumber].length;
706:             for (uint256 j = 0; j < length; j++) {
707:                 if (_totalStakeHistory[quorumNumber][length - j - 1].updateBlockNumber <= blockNumber) {
708:                     indices[i] = uint32(length - j - 1); // <= FOUND
709:                     break;
710:                 }
711:             }
712:         }

[NonCritical-4] It is standard for all external and public functions to be override from an interface

Resolution

This is to ensure the whole API is extracted in a interface

Num of instances: 81

Findings

Click to show findings

['100']

100:     function registerBLSPublicKey(
101:         address operator,
102:         PubkeyRegistrationParams calldata params,
103:         BN254.G1Point calldata pubkeyRegistrationMessageHash
104:     ) external onlyRegistryCoordinator returns (bytes32 operatorId) 

['203']

203:     function getApkIndicesAtBlockNumber(
204:         bytes calldata quorumNumbers,
205:         uint256 blockNumber
206:     ) external view returns (uint32[] memory) 

['229']

229:     function getApk(uint8 quorumNumber) external view returns (BN254.G1Point memory) 

['234']

234:     function getApkUpdateAtIndex(uint8 quorumNumber, uint256 index) external view returns (ApkUpdate memory) 

['245']

245:     function getApkHashAtBlockNumberAndIndex(
246:         uint8 quorumNumber,
247:         uint32 blockNumber,
248:         uint256 index
249:     ) external view returns (bytes24) 

['270']

270:     function getApkHistoryLength(uint8 quorumNumber) external view returns (uint32) 

['52']

52:     function setStaleStakesForbidden(bool value) external onlyCoordinatorOwner 

['300']

300:     function getOperatorUpdateAtIndex(uint8 quorumNumber, uint32 operatorIndex, uint32 arrayIndex) external view returns (OperatorUpdate memory) 

['305']

305:     function getQuorumUpdateAtIndex(uint8 quorumNumber, uint32 quorumIndex) external view returns (QuorumUpdate memory) 

['311']

311:     function getLatestQuorumUpdate(uint8 quorumNumber) external view returns (QuorumUpdate memory) 

['317']

317:     function getLatestOperatorUpdate(uint8 quorumNumber, uint32 operatorIndex) external view returns (OperatorUpdate memory) 

['322']

322:     function getOperatorListAtBlockNumber(
323:         uint8 quorumNumber, 
324:         uint32 blockNumber
325:     ) external view returns (bytes32[] memory)

['86']

86:     function totalOperatorsForQuorum(uint8 quorumNumber) external view returns (uint32)

['40']

40:     function getOperatorState(
41:         IRegistryCoordinator registryCoordinator, 
42:         bytes32 operatorId, 
43:         uint32 blockNumber
44:     ) external view returns (uint256, Operator[][] memory) 

['104']

104:     function getCheckSignaturesIndices(
105:         IRegistryCoordinator registryCoordinator,
106:         uint32 referenceBlockNumber, 
107:         bytes calldata quorumNumbers, 
108:         bytes32[] calldata nonSignerOperatorIds
109:     ) external view returns (CheckSignaturesIndices memory) 

['82']

82:     function initialize(
83:         address _initialOwner,
84:         address _churnApprover,
85:         address _ejector,
86:         IPauserRegistry _pauserRegistry,
87:         uint256 _initialPausedStatus,
88:         OperatorSetParam[] memory _operatorSetParams,
89:         uint96[] memory _minimumStakes,
90:         IStakeRegistry.StrategyParams[][] memory _strategyParams
91:     ) external initializer 

['128']

128:     function registerOperator(
129:         bytes calldata quorumNumbers,
130:         string calldata socket,
131:         IBLSApkRegistry.PubkeyRegistrationParams calldata params,
132:         SignatureWithSaltAndExpiry memory operatorSignature
133:     ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) 

['177']

177:     function registerOperatorWithChurn(
178:         bytes calldata quorumNumbers, 
179:         string calldata socket,
180:         IBLSApkRegistry.PubkeyRegistrationParams calldata params,
181:         OperatorKickParam[] calldata operatorKickParams,
182:         SignatureWithSaltAndExpiry memory churnApproverSignature,
183:         SignatureWithSaltAndExpiry memory operatorSignature
184:     ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) 

['242']

242:     function deregisterOperator(
243:         bytes calldata quorumNumbers
244:     ) external onlyWhenNotPaused(PAUSED_DEREGISTER_OPERATOR) 

['257']

257:     function updateOperators(address[] calldata operators) external onlyWhenNotPaused(PAUSED_UPDATE_OPERATOR) 

['284']

284:     function updateOperatorsForQuorum(
285:         address[][] calldata operatorsPerQuorum,
286:         bytes calldata quorumNumbers
287:     ) external onlyWhenNotPaused(PAUSED_UPDATE_OPERATOR) 

['349']

349:     function updateSocket(string memory socket) external 

['363']

363:     function ejectOperator(
364:         address operator, 
365:         bytes calldata quorumNumbers
366:     ) external onlyEjector 

['385']

385:     function createQuorum(
386:         OperatorSetParam memory operatorSetParams,
387:         uint96 minimumStake,
388:         IStakeRegistry.StrategyParams[] memory strategyParams
389:     ) external virtual onlyOwner 

['400']

400:     function setOperatorSetParams(
401:         uint8 quorumNumber, 
402:         OperatorSetParam memory operatorSetParams
403:     ) external onlyOwner quorumExists(quorumNumber) 

['413']

413:     function setChurnApprover(address _churnApprover) external onlyOwner 

['422']

422:     function setEjector(address _ejector) external onlyOwner 

['787']

787:     function getOperatorSetParams(uint8 quorumNumber) external view returns (OperatorSetParam memory) 

['792']

792:     function getOperator(address operator) external view returns (OperatorInfo memory) 

['797']

797:     function getOperatorId(address operator) external view returns (bytes32) 

['802']

802:     function getOperatorFromId(bytes32 operatorId) external view returns (address) 

['807']

807:     function getOperatorStatus(address operator) external view returns (IRegistryCoordinator.OperatorStatus) 

['816']

816:     function getQuorumBitmapIndicesAtBlockNumber(
817:         uint32 blockNumber, 
818:         bytes32[] memory operatorIds
819:     ) external view returns (uint32[] memory) 

['833']

833:     function getQuorumBitmapAtBlockNumberByIndex(
834:         bytes32 operatorId, 
835:         uint32 blockNumber, 
836:         uint256 index
837:     ) external view returns (uint192) 

['858']

858:     function getQuorumBitmapUpdateByIndex(
859:         bytes32 operatorId, 
860:         uint256 index
861:     ) external view returns (QuorumBitmapUpdate memory) 

['866']

866:     function getCurrentQuorumBitmap(bytes32 operatorId) external view returns (uint192) 

['871']

871:     function getQuorumBitmapHistoryLength(bytes32 operatorId) external view returns (uint256) 

['876']

876:     function numRegistries() external view returns (uint256) 

['86']

86:     function getRestakeableStrategies() external view returns (address[] memory) 

['117']

117:     function getOperatorRestakedStrategies(address operator) external view returns (address[] memory) 

['147']

147:     function updateOperatorStake(
148:         address operator, 
149:         bytes32 operatorId, 
150:         bytes calldata quorumNumbers
151:     ) external onlyRegistryCoordinator returns (uint192) 

['535']

535:     function getStakeHistoryLength(
536:         bytes32 operatorId,
537:         uint8 quorumNumber
538:     ) external view returns (uint256) 

['547']

547:     function getStakeHistory(
548:         bytes32 operatorId, 
549:         uint8 quorumNumber
550:     ) external view returns (StakeUpdate[] memory) 

['558']

558:     function getCurrentStake(bytes32 operatorId, uint8 quorumNumber) external view returns (uint96) 

['588']

588:     function getStakeUpdateAtIndex(
589:         uint8 quorumNumber,
590:         bytes32 operatorId,
591:         uint256 index
592:     ) external view returns (StakeUpdate memory) 

['597']

597:     function getStakeAtBlockNumber(
598:         bytes32 operatorId,
599:         uint8 quorumNumber,
600:         uint32 blockNumber
601:     ) external view returns (uint96) 

['609']

609:     function getStakeUpdateIndexAtBlockNumber(
610:         bytes32 operatorId,
611:         uint8 quorumNumber,
612:         uint32 blockNumber
613:     ) external view returns (uint32) 

['627']

627:     function getStakeAtBlockNumberAndIndex(
628:         uint8 quorumNumber,
629:         uint32 blockNumber,
630:         bytes32 operatorId,
631:         uint256 index
632:     ) external view returns (uint96) 

['645']

645:     function getTotalStakeHistoryLength(uint8 quorumNumber) external view returns (uint256) 

['653']

653:     function getCurrentTotalStake(uint8 quorumNumber) external view returns (uint96) 

['662']

662:     function getTotalStakeUpdateAtIndex(
663:         uint8 quorumNumber,
664:         uint256 index
665:     ) external view returns (StakeUpdate memory) 

['677']

677:     function getTotalStakeAtBlockNumberFromIndex(
678:         uint8 quorumNumber,
679:         uint32 blockNumber,
680:         uint256 index
681:     ) external view returns (uint96) 

['693']

693:     function getTotalStakeIndicesAtBlockNumber(
694:         uint32 blockNumber,
695:         bytes calldata quorumNumbers
696:     ) external view returns (uint32[] memory) 

['42']

42:     function registerOperator(
43:         address operator,
44:         bytes memory quorumNumbers
45:     ) public virtual onlyRegistryCoordinator 

['68']

68:     function deregisterOperator(
69:         address operator,
70:         bytes memory quorumNumbers
71:     ) public virtual onlyRegistryCoordinator 

['115']

115:     function initializeQuorum(uint8 quorumNumber) public virtual onlyRegistryCoordinator 

['187']

187:     function getRegisteredPubkey(address operator) public view returns (BN254.G1Point memory, bytes32) 

['275']

275:     function getOperatorFromPubkeyHash(bytes32 pubkeyHash) public view returns (address) 

['281']

281:     function getOperatorId(address operator) public view returns (bytes32) 

['87']

87:     function checkSignatures(
88:         bytes32 msgHash, 
89:         bytes calldata quorumNumbers,
90:         uint32 referenceBlockNumber, 
91:         NonSignerStakesAndSignature memory params
92:     ) 
93:         public 
94:         view
95:         returns (
96:             QuorumStakeTotals memory,
97:             bytes32
98:         )
99:     

['269']

269:     function trySignatureAndApkVerification(
270:         bytes32 msgHash,
271:         BN254.G1Point memory apk,
272:         BN254.G2Point memory apkG2,
273:         BN254.G1Point memory sigma
274:     ) public view returns(bool pairingSuccessful, bool siganatureIsValid) 

['40']

40:     function registerOperator(
41:         bytes32 operatorId, 
42:         bytes calldata quorumNumbers
43:     ) public virtual onlyRegistryCoordinator returns(uint32[] memory) 

['82']

82:     function deregisterOperator(
83:         bytes32 operatorId, 
84:         bytes calldata quorumNumbers
85:     ) public virtual onlyRegistryCoordinator 

['115']

115:     function initializeQuorum(uint8 quorumNumber) public virtual onlyRegistryCoordinator 

['64']

64:     function getOperatorState(
65:         IRegistryCoordinator registryCoordinator, 
66:         bytes memory quorumNumbers, 
67:         uint32 blockNumber
68:     ) public view returns(Operator[][] memory) 

['887']

887:     function calculateOperatorChurnApprovalDigestHash(
888:         address registeringOperator,
889:         bytes32 registeringOperatorId,
890:         OperatorKickParam[] memory operatorKickParams,
891:         bytes32 salt,
892:         uint256 expiry
893:     ) public view returns (bytes32) 

['902']

902:     function pubkeyRegistrationMessageHash(address operator) public view returns (BN254.G1Point memory) 

['56']

56:     function setMetadataURI(string memory _metadataURI) public virtual onlyOwner 

['65']

65:     function registerOperatorToAVS(
66:         address operator,
67:         ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
68:     ) public virtual onlyRegistryCoordinator 

['76']

76:     function deregisterOperatorFromAVS(address operator) public virtual onlyRegistryCoordinator 

['66']

66:     function registerOperator(
67:         address operator,
68:         bytes32 operatorId,
69:         bytes calldata quorumNumbers
70:     ) public virtual onlyRegistryCoordinator returns (uint96[] memory, uint96[] memory) 

['114']

114:     function deregisterOperator(
115:         bytes32 operatorId,
116:         bytes calldata quorumNumbers
117:     ) public virtual onlyRegistryCoordinator 

['192']

192:     function initializeQuorum(
193:         uint8 quorumNumber,
194:         uint96 minimumStake,
195:         StrategyParams[] memory _strategyParams
196:     ) public virtual onlyRegistryCoordinator 

['208']

208:     function setMinimumStakeForQuorum(
209:         uint8 quorumNumber, 
210:         uint96 minimumStake
211:     ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber) 

['221']

221:     function addStrategies(
222:         uint8 quorumNumber, 
223:         StrategyParams[] memory _strategyParams
224:     ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber) 

['233']

233:     function removeStrategies(
234:         uint8 quorumNumber,
235:         uint256[] memory indicesToRemove
236:     ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber) 

['261']

261:     function modifyStrategyParams(
262:         uint8 quorumNumber,
263:         uint256[] calldata strategyIndices,
264:         uint96[] calldata newMultipliers
265:     ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber) 

['506']

506:     function weightOfOperatorForQuorum(
507:         uint8 quorumNumber, 
508:         address operator
509:     ) public virtual view quorumExists(quorumNumber) returns (uint96) 

['515']

515:     function strategyParamsLength(uint8 quorumNumber) public view returns (uint256) 

['520']

520:     function strategyParamsByIndex(
521:         uint8 quorumNumber, 
522:         uint256 index
523:     ) public view returns (StrategyParams memory)
524:     

['567']

567:     function getLatestStakeUpdate(
568:         bytes32 operatorId,
569:         uint8 quorumNumber
570:     ) public view returns (StakeUpdate memory) 

[NonCritical-5] Using abi.encodePacked can result in hash collision when used in hashing functions

Resolution

Consider using abi.encode as this pads data to 32 byte segments

Num of instances: 4

Findings

Click to show findings

['121']

119: 
120:         
121:         uint256 gamma = uint256(keccak256(abi.encodePacked( // <= FOUND
122:             params.pubkeyRegistrationSignature.X, 
123:             params.pubkeyRegistrationSignature.Y, 
124:             params.pubkeyG1.X, 
125:             params.pubkeyG1.Y, 
126:             params.pubkeyG2.X, 
127:             params.pubkeyG2.Y, 
128:             pubkeyRegistrationMessageHash.X, 
129:             pubkeyRegistrationMessageHash.Y
130:         ))) % BN254.FR_MODULUS;

['255']

254:         
255:         bytes32 signatoryRecordHash = keccak256(abi.encodePacked(referenceBlockNumber, nonSigners.pubkeyHashes)); // <= FOUND

['277']

276:         
277:         uint256 gamma = uint256(keccak256(abi.encodePacked(msgHash, apk.X, apk.Y, apkG2.X[0], apkG2.X[1], apkG2.Y[0], apkG2.Y[1], sigma.X, sigma.Y))) % BN254.FR_MODULUS; // <= FOUND

['286']

286:         return keccak256(abi.encodePacked(pk.X[0], pk.X[1], pk.Y[0], pk.Y[1])); // <= FOUND

[NonCritical-6] Contracts do not use their OZ upgradable counterparts

Resolution

Using the upgradeable counterpart of the OpenZeppelin (OZ) library in Solidity is beneficial for creating contracts that can be updated in the future. OpenZeppelin's upgradeable contracts library is designed with proxy patterns in mind, which allow the logic of contracts to be upgraded while preserving the contract's state and address. This can be crucial for long-lived contracts where future requirements or improvements may not be fully known at the time of deployment. The upgradeable OZ contracts also include protection against a class of vulnerabilities related to initialization of storage variables in upgradeable contracts. Hence, it's a good idea to use them when developing contracts that may need to be upgraded in the future, as they provide a solid foundation for secure and upgradeable smart contracts.

Num of instances: 1

Findings

Click to show findings

['32']

32: contract RegistryCoordinator is 
33:     EIP712,  // <= FOUND 'EIP712,'
34:     Initializable, 
35:     Pausable, // <= FOUND 'Pausable,'
36:     OwnableUpgradeable,
37:     RegistryCoordinatorStorage, 
38:     ISocketUpdater, 
39:     ISignatureUtils
40: 

[NonCritical-7] Default address(0) can be returned

Resolution

Allowing a function in Solidity to return the default address (address(0)) can be problematic as it can represent uninitialized or invalid addresses. If such an address is utilized in transfer operations or other sensitive actions, it could lead to loss of funds or unpredicted behavior. It's prudent to include checks in your functions to prevent the return of the zero address, enhancing contract security.

Num of instances: 4

Findings

Click to show findings

['275']

275:     function getOperatorFromPubkeyHash(bytes32 pubkeyHash) public view returns (address) {
276:         return pubkeyHashToOperator[pubkeyHash];
277:     }

['802']

802:     function getOperatorFromId(bytes32 operatorId) external view returns (address) {
803:         return blsApkRegistry.getOperatorFromPubkeyHash(operatorId);
804:     }

['911']

911:     function owner()
912:         public
913:         view
914:         override(OwnableUpgradeable, IRegistryCoordinator)
915:         returns (address)
916:     {
917:         return OwnableUpgradeable.owner();
918:     }

['147']

147:     function avsDirectory() external view override returns (address) {
148:         return address(_avsDirectory);
149:     }

[NonCritical-8] Consider adding emergency-stop functionality

Resolution

In the event of a security breach or any unforeseen emergency, swiftly suspending all protocol operations becomes crucial. Having a mechanism in place to halt all functions collectively, instead of pausing individual contracts separately, substantially enhances the efficiency of mitigating ongoing attacks or vulnerabilities. This not only quickens the response time to potential threats but also reduces operational stress during these critical periods. Therefore, consider integrating a 'circuit breaker' or 'emergency stop' function into the smart contract system architecture. Such a feature would provide the capability to suspend the entire protocol instantly, which could prove invaluable during a time-sensitive crisis management situation.

Num of instances: 5

Findings

Click to show findings

['10']

10: contract BLSApkRegistry is BLSApkRegistryStorage 

['18']

18: contract BLSSignatureChecker is IBLSSignatureChecker 

['11']

11: contract IndexRegistry is IndexRegistryStorage 

['15']

15: contract OperatorStateRetriever 

['22']

22: contract StakeRegistry is StakeRegistryStorage 

[NonCritical-9] Employ Explicit Casting to Bytes or Bytes32 for Enhanced Code Clarity and Meaning

Resolution

Smart contracts are complex entities, and clarity in their operations is fundamental to ensure that they function as intended. Casting a single argument instead of utilizing 'abi.encodePacked()' improves the transparency of the operation. It elucidates the intent of the code, reducing ambiguity and making it easier for auditors and developers to understand the code’s purpose. Such practices promote readability and maintainability, thus reducing the likelihood of errors and misunderstandings. Therefore, it's recommended to employ explicit casts for single arguments where possible, to increase the contract's comprehensibility and ensure a smoother review process.

Num of instances: 4

Findings

Click to show findings

['119']

100:     function registerBLSPublicKey(
101:         address operator,
102:         PubkeyRegistrationParams calldata params,
103:         BN254.G1Point calldata pubkeyRegistrationMessageHash
104:     ) external onlyRegistryCoordinator returns (bytes32 operatorId) {
105:         bytes32 pubkeyHash = BN254.hashG1Point(params.pubkeyG1);
106:         require(
107:             pubkeyHash != ZERO_PK_HASH, "BLSApkRegistry.registerBLSPublicKey: cannot register zero pubkey"
108:         );
109:         require(
110:             operatorToPubkeyHash[operator] == bytes32(0),
111:             "BLSApkRegistry.registerBLSPublicKey: operator already registered pubkey"
112:         );
113:         require(
114:             pubkeyHashToOperator[pubkeyHash] == address(0),
115:             "BLSApkRegistry.registerBLSPublicKey: public key already registered"
116:         );
117: 
118:         
119:         uint256 gamma = uint256(keccak256(abi.encodePacked( // <= FOUND
120:             params.pubkeyRegistrationSignature.X, 
121:             params.pubkeyRegistrationSignature.Y, 
122:             params.pubkeyG1.X, 
123:             params.pubkeyG1.Y, 
124:             params.pubkeyG2.X, 
125:             params.pubkeyG2.Y, 
126:             pubkeyRegistrationMessageHash.X, 
127:             pubkeyRegistrationMessageHash.Y
128:         ))) % BN254.FR_MODULUS;
129:         
130:         
131:         require(BN254.pairing(
132:             params.pubkeyRegistrationSignature.plus(params.pubkeyG1.scalar_mul(gamma)),
133:             BN254.negGeneratorG2(),
134:             pubkeyRegistrationMessageHash.plus(BN254.generatorG1().scalar_mul(gamma)),
135:             params.pubkeyG2
136:         ), "BLSApkRegistry.registerBLSPublicKey: either the G1 signature is wrong, or G1 and G2 private key do not match");
137: 
138:         operatorToPubkey[operator] = params.pubkeyG1;
139:         operatorToPubkeyHash[operator] = pubkeyHash;
140:         pubkeyHashToOperator[pubkeyHash] = operator;
141: 
142:         emit NewPubkeyRegistration(operator, params.pubkeyG1, params.pubkeyG2);
143:         return pubkeyHash;
144:     }

['254']

87:     function checkSignatures(
88:         bytes32 msgHash, 
89:         bytes calldata quorumNumbers,
90:         uint32 referenceBlockNumber, 
91:         NonSignerStakesAndSignature memory params
92:     ) 
93:         public 
94:         view
95:         returns (
96:             QuorumStakeTotals memory,
97:             bytes32
98:         )
99:     {
100:         require(quorumNumbers.length != 0, "BLSSignatureChecker.checkSignatures: empty quorum input");
101: 
102:         require(
103:             (quorumNumbers.length == params.quorumApks.length) &&
104:             (quorumNumbers.length == params.quorumApkIndices.length) &&
105:             (quorumNumbers.length == params.totalStakeIndices.length) &&
106:             (quorumNumbers.length == params.nonSignerStakeIndices.length),
107:             "BLSSignatureChecker.checkSignatures: input quorum length mismatch"
108:         );
109: 
110:         require(
111:             params.nonSignerPubkeys.length == params.nonSignerQuorumBitmapIndices.length,
112:             "BLSSignatureChecker.checkSignatures: input nonsigner length mismatch"
113:         );
114: 
115:         require(referenceBlockNumber < uint32(block.number), "BLSSignatureChecker.checkSignatures: invalid reference block");
116: 
117:         
118:         
119:         
120:         
121:         
122:         
123:         BN254.G1Point memory apk = BN254.G1Point(0, 0);
124: 
125:         
126:         
127:         
128:         QuorumStakeTotals memory stakeTotals;
129:         stakeTotals.totalStakeForQuorum = new uint96[](quorumNumbers.length);
130:         stakeTotals.signedStakeForQuorum = new uint96[](quorumNumbers.length);
131: 
132:         NonSignerInfo memory nonSigners;
133:         nonSigners.quorumBitmaps = new uint256[](params.nonSignerPubkeys.length);
134:         nonSigners.pubkeyHashes = new bytes32[](params.nonSignerPubkeys.length);
135: 
136:         {
137:             
138:             
139:             uint256 signingQuorumBitmap = BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, registryCoordinator.quorumCount());
140: 
141:             for (uint256 j = 0; j < params.nonSignerPubkeys.length; j++) {
142:                 
143:                 
144:                 
145:                 nonSigners.pubkeyHashes[j] = params.nonSignerPubkeys[j].hashG1Point();
146:                 if (j != 0) {
147:                     require(
148:                         uint256(nonSigners.pubkeyHashes[j]) > uint256(nonSigners.pubkeyHashes[j - 1]),
149:                         "BLSSignatureChecker.checkSignatures: nonSignerPubkeys not sorted"
150:                     );
151:                 }
152: 
153:                 
154:                 nonSigners.quorumBitmaps[j] = 
155:                     registryCoordinator.getQuorumBitmapAtBlockNumberByIndex({
156:                         operatorId: nonSigners.pubkeyHashes[j],
157:                         blockNumber: referenceBlockNumber,
158:                         index: params.nonSignerQuorumBitmapIndices[j]
159:                     });
160: 
161:                 
162:                 
163:                 
164:                 apk = apk.plus(
165:                     params.nonSignerPubkeys[j]
166:                         .scalar_mul_tiny(
167:                             BitmapUtils.countNumOnes(nonSigners.quorumBitmaps[j] & signingQuorumBitmap) 
168:                         )
169:                 );
170:             }
171:         }
172: 
173:         
174:         
175:         
176:         apk = apk.negate();
177: 
178:         
179: 
184:         {
185:             bool _staleStakesForbidden = staleStakesForbidden;
186:             uint256 withdrawalDelayBlocks = _staleStakesForbidden ? delegation.minWithdrawalDelayBlocks() : 0;
187: 
188:             for (uint256 i = 0; i < quorumNumbers.length; i++) {
189:                 
190:                 
191:                 if (_staleStakesForbidden) {
192:                     require(
193:                         registryCoordinator.quorumUpdateBlockNumber(uint8(quorumNumbers[i])) + withdrawalDelayBlocks >= referenceBlockNumber,
194:                         "BLSSignatureChecker.checkSignatures: StakeRegistry updates must be within withdrawalDelayBlocks window"
195:                     );
196:                 }
197: 
198:                 
199:                 
200:                 require(
201:                     bytes24(params.quorumApks[i].hashG1Point()) == 
202:                         blsApkRegistry.getApkHashAtBlockNumberAndIndex({
203:                             quorumNumber: uint8(quorumNumbers[i]),
204:                             blockNumber: referenceBlockNumber,
205:                             index: params.quorumApkIndices[i]
206:                         }),
207:                     "BLSSignatureChecker.checkSignatures: quorumApk hash in storage does not match provided quorum apk"
208:                 );
209:                 apk = apk.plus(params.quorumApks[i]);
210: 
211:                 
212:                 stakeTotals.totalStakeForQuorum[i] = 
213:                     stakeRegistry.getTotalStakeAtBlockNumberFromIndex({
214:                         quorumNumber: uint8(quorumNumbers[i]),
215:                         blockNumber: referenceBlockNumber,
216:                         index: params.totalStakeIndices[i]
217:                     });
218:                 stakeTotals.signedStakeForQuorum[i] = stakeTotals.totalStakeForQuorum[i];
219: 
220:                 
221:                 uint256 nonSignerForQuorumIndex = 0;
222:                 
223:                 
224:                 
225:                 for (uint256 j = 0; j < params.nonSignerPubkeys.length; j++) {
226:                     
227:                     if (BitmapUtils.isSet(nonSigners.quorumBitmaps[j], uint8(quorumNumbers[i]))) {
228:                         stakeTotals.signedStakeForQuorum[i] -=
229:                             stakeRegistry.getStakeAtBlockNumberAndIndex({
230:                                 quorumNumber: uint8(quorumNumbers[i]),
231:                                 blockNumber: referenceBlockNumber,
232:                                 operatorId: nonSigners.pubkeyHashes[j],
233:                                 index: params.nonSignerStakeIndices[i][nonSignerForQuorumIndex]
234:                             });
235:                         unchecked {
236:                             ++nonSignerForQuorumIndex;
237:                         }
238:                     }
239:                 }
240:             }
241:         }
242:         {
243:             
244:             (bool pairingSuccessful, bool signatureIsValid) = trySignatureAndApkVerification(
245:                 msgHash, 
246:                 apk, 
247:                 params.apkG2, 
248:                 params.sigma
249:             );
250:             require(pairingSuccessful, "BLSSignatureChecker.checkSignatures: pairing precompile call failed");
251:             require(signatureIsValid, "BLSSignatureChecker.checkSignatures: signature is invalid");
252:         }
253:         
254:         bytes32 signatoryRecordHash = keccak256(abi.encodePacked(referenceBlockNumber, nonSigners.pubkeyHashes)); // <= FOUND
255: 
256:         
257:         return (stakeTotals, signatoryRecordHash);
258:     }

['276']

269:     function trySignatureAndApkVerification(
270:         bytes32 msgHash,
271:         BN254.G1Point memory apk,
272:         BN254.G2Point memory apkG2,
273:         BN254.G1Point memory sigma
274:     ) public view returns(bool pairingSuccessful, bool siganatureIsValid) {
275:         
276:         uint256 gamma = uint256(keccak256(abi.encodePacked(msgHash, apk.X, apk.Y, apkG2.X[0], apkG2.X[1], apkG2.Y[0], apkG2.Y[1], sigma.X, sigma.Y))) % BN254.FR_MODULUS; // <= FOUND
277:         
278:         (pairingSuccessful, siganatureIsValid) = BN254.safePairing(
279:                 sigma.plus(apk.scalar_mul(gamma)),
280:                 BN254.negGeneratorG2(),
281:                 BN254.hashToG1(msgHash).plus(BN254.generatorG1().scalar_mul(gamma)),
282:                 apkG2,
283:                 PAIRING_EQUALITY_CHECK_GAS
284:             );
285:     }

['286']

283:     function hashG2Point(
284:         BN254.G2Point memory pk
285:     ) internal pure returns (bytes32) {
286:         return keccak256(abi.encodePacked(pk.X[0], pk.X[1], pk.Y[0], pk.Y[1])); // <= FOUND
287:     }

[NonCritical-10] It's not standard to end and begin a code object on the same line

Resolution

Placing the closing and opening brackets of distinct code blocks on the same line can impair code readability and clarity, leading to potential misinterpretations. To improve maintainability and reduce the chances of introducing errors, it is advisable to start each code block on a new line, making the boundaries between different code sections clearer and easier to discern unless it is an else statement.

Num of instances: 1

Findings

Click to show findings

['132']

132:                 
133:                 unchecked{ ++arrayIndex; } // <= FOUND

[NonCritical-11] Consider making private state variables internal to increase flexibility

Resolution

In Solidity, private state variables are strictly confined to the contract they are defined in and can't be accessed or modified by its derived contracts. While this offers strong encapsulation, it can limit contract extensibility and modification in inheritance chains. On the other hand, internal variables can be accessed and potentially overridden by child contracts, granting more flexibility in contract development and upgrades. Therefore, it's recommended to use private only when you explicitly want to prevent child contract access. Otherwise, prefer internal to maintain a balance between encapsulation and the flexibility offered by inheritance patterns in Solidity.

Num of instances: 5

Findings

Click to show findings

['289']

289: uint256[49] private __GAP; // <= FOUND

['39']

39: uint256[45] private __GAP; // <= FOUND

['42']

42: uint256[47] private __GAP; // <= FOUND

['81']

81: uint256[41] private __GAP; // <= FOUND

['153']

153: uint256[50] private __GAP; // <= FOUND

[NonCritical-12] Use of abi.encodePacked with dynamic types inside keccak256

Resolution

Using abi.encodePacked with dynamic types for hashing functions like keccak256 can be risky due to the potential for hash collisions. This function concatenates arguments tightly, without padding, which might lead to different inputs producing the same hash. This is especially problematic with dynamic types, where the boundaries between inputs can blur. To mitigate this, use abi.encode instead. abi.encode pads its arguments to 32 bytes, creating clear distinctions between different inputs and significantly reducing the chance of hash collisions. This approach ensures more reliable and collision-resistant hashing, crucial for maintaining data integrity and security in smart contracts.

Num of instances: 4

Findings

Click to show findings

['119']

119: 
120:         
121:         uint256 gamma = uint256(keccak256(abi.encodePacked( // <= FOUND
122:             params.pubkeyRegistrationSignature.X,  // <= FOUND
123:             params.pubkeyRegistrationSignature.Y,  // <= FOUND
124:             params.pubkeyG1.X,  // <= FOUND
125:             params.pubkeyG1.Y,  // <= FOUND
126:             params.pubkeyG2.X,  // <= FOUND
127:             params.pubkeyG2.Y,  // <= FOUND
128:             pubkeyRegistrationMessageHash.X,  // <= FOUND
129:             pubkeyRegistrationMessageHash.Y
130:         ))) % BN254.FR_MODULUS;

['254']

254:         
255:         bytes32 signatoryRecordHash = keccak256(abi.encodePacked(referenceBlockNumber, nonSigners.pubkeyHashes)); // <= FOUND

['276']

276:         
277:         uint256 gamma = uint256(keccak256(abi.encodePacked(msgHash, apk.X, apk.Y, apkG2.X[0], apkG2.X[1], apkG2.Y[0], apkG2.Y[1], sigma.X, sigma.Y))) % BN254.FR_MODULUS; // <= FOUND

['286']

286:         return keccak256(abi.encodePacked(pk.X[0], pk.X[1], pk.Y[0], pk.Y[1])); // <= FOUND

[NonCritical-13] Inconsistent comment spacing

Resolution

Some comments use // X and others //X Amend comments to use only use // X or //X consistently

Num of instances: 5

Findings

Click to show findings

['13']

13: //docs.eigenlayer.xyz/overview/terms-of-service

['143']

143: //loop until we reach the most significant bit

['265']

265: //Out is the output of the pairing precompile, either 0 or 1 based on whether the two pairings are equal.

['266']

266: //Success is true if the precompile actually goes through (aka all inputs are valid)

['2']

2: //github.com/HarryR/solcrypto/blob/master/contracts/altbn128.sol

[NonCritical-14] Incorrect NatSpec Syntax

Resolution

In Solidity, just like in most other programming languages, regular comments serve to make code more understandable for developers. These are usually denoted by // for single line comments, or /* ... */ for multi-line comments, and are ignored by the compiler.

On the other hand, NatSpec comments in Solidity, denoted by /// for single-line comments, or /** ... */ for multi-line comments, serve a different purpose. Besides aiding developer comprehension, they also form a part of the contract's interface, as they can be parsed and used by tools such as automated documentation generators or IDEs to provide users with details about the contract's functions, parameters and behavior. NatSpec comments can also be retrieved via JSON interfaces, and as such, they're included in the contract's ABI.

Thus, using /// and /** ... */ appropriately ensures not only proper documentation for developers, but also helps create a richer and more informative interface for users and external tools interacting with your contract.

Num of instances: 2

Findings

Click to show findings

['40']

40: // @notice Emitted when a new operator pubkey is registered for a set of quorums // <= FOUND

['46']

46: // @notice Emitted when an operator pubkey is removed from a set of quorums // <= FOUND

[NonCritical-15] Floating pragma should be avoided

Num of instances: 1

Findings

Click to show findings

['2']

2: pragma solidity >=0.5.0; // <= FOUND

[NonCritical-16] In functions which accept an address as a parameter, there should be a zero address check to prevent bugs

Resolution

In smart contract development, especially with Solidity, it's crucial to validate inputs to functions. When a function accepts an Ethereum address as a parameter, implementing a zero address check (i.e., ensuring the address is not 0x0) is a best practice to prevent potential bugs and vulnerabilities. The zero address (0x0) is a default value and generally indicates an uninitialized or invalid state. Passing the zero address to certain functions can lead to unintended behaviors, like funds getting locked permanently or transactions failing silently. By checking for and rejecting the zero address, developers can ensure that the function operates as intended and interacts only with valid Ethereum addresses. This check enhances the contract's robustness and security.

Num of instances: 31

Findings

Click to show findings

['42']

42:     function registerOperator(
43:         address operator,
44:         bytes memory quorumNumbers
45:     ) public virtual onlyRegistryCoordinator 

['68']

68:     function deregisterOperator(
69:         address operator,
70:         bytes memory quorumNumbers
71:     ) public virtual onlyRegistryCoordinator 

['187']

187:     function getRegisteredPubkey(address operator) public view returns (BN254.G1Point memory, bytes32) 

['281']

281:     function getOperatorId(address operator) public view returns (bytes32) 

['82']

82:     function initialize(
83:         address _initialOwner,
84:         address _churnApprover,
85:         address _ejector,
86:         IPauserRegistry _pauserRegistry,
87:         uint256 _initialPausedStatus,
88:         OperatorSetParam[] memory _operatorSetParams,
89:         uint96[] memory _minimumStakes,
90:         IStakeRegistry.StrategyParams[][] memory _strategyParams
91:     ) external initializer 

['257']

257:     function updateOperators(address[] calldata operators) external onlyWhenNotPaused(PAUSED_UPDATE_OPERATOR) 

['284']

284:     function updateOperatorsForQuorum(
285:         address[][] calldata operatorsPerQuorum,
286:         bytes calldata quorumNumbers
287:     ) external onlyWhenNotPaused(PAUSED_UPDATE_OPERATOR) 

['363']

363:     function ejectOperator(
364:         address operator, 
365:         bytes calldata quorumNumbers
366:     ) external onlyEjector 

['413']

413:     function setChurnApprover(address _churnApprover) external onlyOwner 

['422']

422:     function setEjector(address _ejector) external onlyOwner 

['440']

440:     function _registerOperator(
441:         address operator, 
442:         bytes32 operatorId,
443:         bytes calldata quorumNumbers,
444:         string memory socket,
445:         SignatureWithSaltAndExpiry memory operatorSignature
446:     ) internal virtual returns (RegisterResults memory results) 

['502']

502:     function _getOrCreateOperatorId(
503:         address operator,
504:         IBLSApkRegistry.PubkeyRegistrationParams calldata params
505:     ) internal returns (bytes32 operatorId) 

['531']

531:     function _validateChurn(
532:         uint8 quorumNumber, 
533:         uint96 totalQuorumStake,
534:         address newOperator, 
535:         uint96 newOperatorStake,
536:         OperatorKickParam memory kickParams, 
537:         OperatorSetParam memory setParams
538:     ) internal view 

['561']

561:     function _deregisterOperator(
562:         address operator, 
563:         bytes memory quorumNumbers
564:     ) internal virtual 

['609']

609:     function _updateOperator(
610:         address operator,
611:         OperatorInfo memory operatorInfo,
612:         bytes memory quorumsToUpdate
613:     ) internal 

['645']

645:     function _verifyChurnApproverSignature(
646:         address registeringOperator,
647:         bytes32 registeringOperatorId, 
648:         OperatorKickParam[] memory operatorKickParams, 
649:         SignatureWithSaltAndExpiry memory churnApproverSignature
650:     ) internal 

['772']

772:     function _setChurnApprover(address newChurnApprover) internal 

['777']

777:     function _setEjector(address newEjector) internal 

['792']

792:     function getOperator(address operator) external view returns (OperatorInfo memory) 

['797']

797:     function getOperatorId(address operator) external view returns (bytes32) 

['807']

807:     function getOperatorStatus(address operator) external view returns (IRegistryCoordinator.OperatorStatus) 

['887']

887:     function calculateOperatorChurnApprovalDigestHash(
888:         address registeringOperator,
889:         bytes32 registeringOperatorId,
890:         OperatorKickParam[] memory operatorKickParams,
891:         bytes32 salt,
892:         uint256 expiry
893:     ) public view returns (bytes32) 

['902']

902:     function pubkeyRegistrationMessageHash(address operator) public view returns (BN254.G1Point memory) 

['47']

47:     function __ServiceManagerBase_init(address initialOwner) internal virtual onlyInitializing 

['65']

65:     function registerOperatorToAVS(
66:         address operator,
67:         ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
68:     ) public virtual onlyRegistryCoordinator 

['76']

76:     function deregisterOperatorFromAVS(address operator) public virtual onlyRegistryCoordinator 

['117']

117:     function getOperatorRestakedStrategies(address operator) external view returns (address[] memory) 

['66']

66:     function registerOperator(
67:         address operator,
68:         bytes32 operatorId,
69:         bytes calldata quorumNumbers
70:     ) public virtual onlyRegistryCoordinator returns (uint96[] memory, uint96[] memory) 

['147']

147:     function updateOperatorStake(
148:         address operator, 
149:         bytes32 operatorId, 
150:         bytes calldata quorumNumbers
151:     ) external onlyRegistryCoordinator returns (uint192) 

['472']

472:     function _weightOfOperatorForQuorum(uint8 quorumNumber, address operator) internal virtual view returns (uint96, bool) 

['506']

506:     function weightOfOperatorForQuorum(
507:         uint8 quorumNumber, 
508:         address operator
509:     ) public virtual view quorumExists(quorumNumber) returns (uint96) 

[NonCritical-17] Enum values should be used in place of constant array indexes

Resolution

Create a commented enum value to use in place of constant array indexes, this makes the code far easier to understand

Num of instances: 31

Findings

Click to show findings

['42']

42: 
43:         
44:         
45:         bitmap = uint256(1 << uint8(orderedBytesArray[0])); // <= FOUND

['90']

90: 
91:         
92:         bytes1 singleByte = bytesArray[0]; // <= FOUND

['213']

213:             if (quorumApkUpdatesLength == 0 || blockNumber < apkHistory[quorumNumber][0].updateBlockNumber) { // <= FOUND

['276']

276:         
277:         uint256 gamma = uint256(keccak256(abi.encodePacked(msgHash, apk.X, apk.Y, apkG2.X[0], apkG2.X[1], apkG2.Y[0], apkG2.Y[1], sigma.X, sigma.Y))) % BN254.FR_MODULUS; // <= FOUND

['101']

101:         input[0] = p1.X; // <= FOUND

['170']

170:         input[0] = p.X; // <= FOUND

['207']

207:             input[j + 2] = p2[i].X[0]; // <= FOUND

['209']

209:             input[j + 4] = p2[i].Y[0]; // <= FOUND

['228']

228: 
229:         return out[0] != 0; // <= FOUND

['268']

268: 
269:         
270:         
271: 
272:         return (success, out[0] != 0); // <= FOUND

['286']

286:         return keccak256(abi.encodePacked(pk.X[0], pk.X[1], pk.Y[0], pk.Y[1])); // <= FOUND

['333']

333:         input[0] = 0x20;  // <= FOUND

['348']

348:         return output[0]; // <= FOUND

['46']

46:         operatorIds[0] = operatorId; // <= FOUND

['47']

47:         uint256 index = registryCoordinator.getQuorumBitmapIndicesAtBlockNumber(blockNumber, operatorIds)[0]; // <= FOUND

['701']

701:             require(
702:                 _totalStakeHistory[quorumNumber][0].updateBlockNumber <= blockNumber, // <= FOUND
703:                 "StakeRegistry.getTotalStakeIndicesAtBlockNumber: quorum has no stake history at blockNumber"
704:             );

['102']

102:         input[1] = p1.Y; // <= FOUND

['171']

171:         input[1] = p.Y; // <= FOUND

['208']

208:             input[j + 3] = p2[i].X[1]; // <= FOUND

['210']

210:             input[j + 5] = p2[i].Y[1]; // <= FOUND

['334']

334:         input[1] = 0x20;  // <= FOUND

['103']

103:         input[2] = p2.X; // <= FOUND

['172']

172:         input[2] = s; // <= FOUND

['198']

198:         G1Point[2] memory p1 = [a1, b1]; // <= FOUND

['199']

199:         G2Point[2] memory p2 = [a2, b2]; // <= FOUND

['335']

335:         input[2] = 0x20;  // <= FOUND

['104']

104:         input[3] = p2.Y; // <= FOUND

['336']

336:         input[3] = _base; // <= FOUND

['337']

337:         input[4] = _exponent; // <= FOUND

['338']

338:         input[5] = _modulus; // <= FOUND

['332']

332:         uint[6] memory input; // <= FOUND

[NonCritical-18] Default address values are manually set

Resolution

In instances where a new variable is defined, there is no need to set it to it's default value.

Num of instances: 1

Findings

Click to show findings

['309']

309: 
310:             address prevOperatorAddress = address(0); // <= FOUND

[NonCritical-19] Ownable2Step should be used in place of Ownable

Resolution

Ownable2Step further prevents risks posed by centralised privileges as there is a smaller likelihood of the owner being wrongfully changed

Num of instances: 2

Findings

Click to show findings

['32']

32: contract RegistryCoordinator is 
33:     EIP712, 
34:     Initializable, 
35:     Pausable,
36:     OwnableUpgradeable, // <= FOUND
37:     RegistryCoordinatorStorage, 
38:     ISocketUpdater, 
39:     ISignatureUtils
40: 

['19']

19: abstract contract ServiceManagerBase is IServiceManager, OwnableUpgradeable  // <= FOUND

[NonCritical-20] Revert statements within external and public functions can be used to perform DOS attacks

Resolution

In Solidity, 'revert' statements are used to undo changes and throw an exception when certain conditions are not met. However, in public and external functions, improper use of revert can be exploited for Denial of Service (DoS) attacks. An attacker can intentionally trigger these 'revert' conditions, causing legitimate transactions to consistently fail. For example, if a function relies on specific conditions from user input or contract state, an attacker could manipulate these to continually force reverts, blocking the function's execution. Therefore, it's crucial to design contract logic to handle exceptions properly and avoid scenarios where revert can be predictably triggered by malicious actors. This includes careful input validation and considering alternative design patterns that are less susceptible to such abuses.

Num of instances: 1

Findings

Click to show findings

['214']

203:     function getApkIndicesAtBlockNumber(
204:         bytes calldata quorumNumbers,
205:         uint256 blockNumber
206:     ) external view returns (uint32[] memory) {
207:         uint32[] memory indices = new uint32[](quorumNumbers.length);
208:         
209:         for (uint256 i = 0; i < quorumNumbers.length; i++) {
210:             uint8 quorumNumber = uint8(quorumNumbers[i]);
211:             
212:             uint256 quorumApkUpdatesLength = apkHistory[quorumNumber].length;
213:             if (quorumApkUpdatesLength == 0 || blockNumber < apkHistory[quorumNumber][0].updateBlockNumber) {
214:                 revert("BLSApkRegistry.getApkIndicesAtBlockNumber: blockNumber is before the first update"); // <= FOUND
215:             }
216: 
217:             
218:             for (uint256 j = quorumApkUpdatesLength; j > 0; j--) {
219:                 if (apkHistory[quorumNumber][j - 1].updateBlockNumber <= blockNumber) {
220:                     indices[i] = uint32(j - 1);
221:                     break;
222:                 }
223:             }
224:         }
225:         return indices;
226:     }

[NonCritical-21] Reverts should use customer errors instead of strings

Resolution

Custom error codes should be used in place of strings for revert statements in Solidity contracts to enhance efficiency and reduce gas costs. String-based error messages consume more bytecode and storage, increasing the overall gas consumption during contract deployment and execution

Num of instances: 2

Findings

Click to show findings

['214']

214:                 revert("BLSApkRegistry.getApkIndicesAtBlockNumber: blockNumber is before the first update"); // <= FOUND

['267']

266:         
267:         revert("IndexRegistry._operatorCountAtBlockNumber: quorum did not exist at given block number"); // <= FOUND

[NonCritical-22] Functions which are either private or internal should have a preceding _ in their name

Resolution

Add a preceding underscore to the function name, take care to refactor where there functions are called

Num of instances: 26

Findings

Click to show findings

['25']

25:     function orderedBytesArrayToBitmap(bytes memory orderedBytesArray) internal pure returns (uint256) 

['62']

62:     function orderedBytesArrayToBitmap(bytes memory orderedBytesArray, uint8 bitUpperBound) internal pure returns (uint256) 

['79']

79:     function isArrayStrictlyAscendingOrdered(bytes calldata bytesArray) internal pure returns (bool) 

['112']

112:     function bitmapToBytesArray(uint256 bitmap) internal pure returns (bytes memory ) 

['139']

139:     function countNumOnes(uint256 n) internal pure returns (uint16) 

['149']

149:     function isSet(uint256 bitmap, uint8 bit) internal pure returns (bool) 

['159']

159:     function setBit(uint256 bitmap, uint8 bit) internal pure returns (uint256) 

['166']

166:     function isEmpty(uint256 bitmap) internal pure returns (bool) 

['173']

173:     function noBitsInCommon(uint256 a, uint256 b) internal pure returns (bool) 

['180']

180:     function isSubsetOf(uint256 a, uint256 b) internal pure returns (bool) 

['188']

188:     function plus(uint256 a, uint256 b) internal pure returns (uint256) 

['196']

196:     function minus(uint256 a, uint256 b) internal pure returns (uint256) 

['49']

49:     function generatorG1() internal pure returns (G1Point memory) 

['65']

65:     function generatorG2() internal pure returns (G2Point memory) 

['76']

76:     function negGeneratorG2() internal pure returns (G2Point memory) 

['87']

87:     function negate(G1Point memory p) internal pure returns (G1Point memory) 

['99']

99:     function plus(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) 

['126']

126:     function scalar_mul_tiny(BN254.G1Point memory p, uint16 s) internal view returns (BN254.G1Point memory) 

['168']

168:     function scalar_mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) 

['192']

192:     function pairing(
193:         G1Point memory a1,
194:         G2Point memory a2,
195:         G1Point memory b1,
196:         G2Point memory b2
197:     ) internal view returns (bool) 

['235']

235:     function safePairing(
236:         G1Point memory a1,
237:         G2Point memory a2,
238:         G1Point memory b1,
239:         G2Point memory b2,
240:         uint256 pairingGas
241:     ) internal view returns (bool, bool) 

['273']

273:     function hashG1Point(BN254.G1Point memory pk) internal pure returns (bytes32 hashedG1) 

['283']

283:     function hashG2Point(
284:         BN254.G2Point memory pk
285:     ) internal pure returns (bytes32) 

['292']

292:     function hashToG1(bytes32 _x) internal view returns (G1Point memory) 

['318']

318:     function findYFromX(uint256 x) internal view returns (uint256, uint256) 

['329']

329:     function expMod(uint256 _base, uint256 _exponent, uint256 _modulus) internal view returns (uint256 retval) 

[NonCritical-23] Private and internal state variables should have a preceding _ in their name unless they are constants

Resolution

Add a preceding underscore to the state variable name, take care to refactor where there variables are read/wrote

Num of instances: 1

Findings

Click to show findings

['18']

18: keccak256("OperatorChurnApproval(address registeringOperator,bytes32 registeringOperatorId,OperatorKickParam[] operatorKickParams,bytes32 salt,uint256 expiry)OperatorKickParam(uint8 quorumNumber,address operator)");

[NonCritical-24] Contract lines should not be longer than 120 characters for readability

Resolution

Consider spreading these lines over multiple lines to aid in readability and the support of VIM users everywhere.

Num of instances: 107

Findings

Click to show findings

['7']

7:  * @title Interface for a `Registry`-type contract that keeps track of an ordered list of operators for up to 256 quorums. // <= FOUND

['13']

13:     // emitted when an operator's index in the ordered operator list for the quorum with number `quorumNumber` is updated // <= FOUND

['29']

29:         // The total number of operators at a `blockNumber` is the first entry such that `blockNumber >= entry.fromBlockNumber` // <= FOUND

['39']

39:      * @return numOperatorsPerQuorum is a list of the number of operators (including the registering operator) in each of the quorums the operator is registered for // <= FOUND

['69']

69:     /// @notice Returns the OperatorUpdate entry for the specified `operatorIndex` and `quorumNumber` at the specified `arrayIndex` // <= FOUND

['80']

80:     function getLatestOperatorUpdate(uint8 quorumNumber, uint32 operatorIndex) external view returns (OperatorUpdate memory); // <= FOUND

['88']

88:     /// @notice Returns an ordered list of operators of the services for the given `quorumNumber` at the given `blockNumber` // <= FOUND

['89']

89:     function getOperatorListAtBlockNumber(uint8 quorumNumber, uint32 blockNumber) external view returns (bytes32[] memory); // <= FOUND

['18']

18:         keccak256("OperatorChurnApproval(address registeringOperator,bytes32 registeringOperatorId,OperatorKickParam[] operatorKickParams,bytes32 salt,uint256 expiry)OperatorKickParam(uint8 quorumNumber,address operator)"); // <= FOUND

['36']

36:     /// @notice the BLS Aggregate Pubkey Registry contract that will keep track of operators' aggregate BLS public keys per quorum // <= FOUND

['62']

62:     /// @notice the address of the entity allowed to sign off on operators getting kicked out of the AVS during registration // <= FOUND

['11']

11:  * @title Used for checking BLS aggregate signatures from the operators of a EigenLayer AVS with the RegistryCoordinator/BLSApkRegistry/StakeRegistry architechture. // <= FOUND

['66']

66:      * @dev Before signature verification, the function verifies operator stake information.  This includes ensuring that the provided `referenceBlockNumber` // <= FOUND

['67']

67:      * is correct, i.e., ensure that the stake returned from the specified block number is recent enough and that the stake is either the most recent update // <= FOUND

['54']

54:      * quorums the operator is registered for starting at (inclusive)`updateBlockNumber` and ending at (exclusive) `nextUpdateBlockNumber` // <= FOUND

['66']

66:      * `kickBIPsOfOperatorStake` is the basis points of a new operator needs to have of an operator they are trying to kick from the quorum, // <= FOUND

['67']

67:      * and `kickBIPsOfTotalStake` is the basis points of the total stake of the quorum that an operator needs to be below to be kicked. // <= FOUND

['76']

76:      * @notice Data structure for the parameters needed to kick an operator from a quorum with number `quorumNumber`, used during registration churn. // <= FOUND

['88']

88:     /// @notice the BLS Aggregate Pubkey Registry contract that will keep track of operators' BLS aggregate pubkeys per quorum // <= FOUND

['119']

119:     function getQuorumBitmapIndicesAtBlockNumber(uint32 blockNumber, bytes32[] memory operatorIds) external view returns (uint32[] memory); // <= FOUND

['125']

125:     function getQuorumBitmapAtBlockNumberByIndex(bytes32 operatorId, uint32 blockNumber, uint256 index) external view returns (uint192); // <= FOUND

['128']

128:     function getQuorumBitmapUpdateByIndex(bytes32 operatorId, uint256 index) external view returns (QuorumBitmapUpdate memory); // <= FOUND

['97']

97:      * @return 1) the indices of the quorumBitmaps for each of the operators in the @param nonSignerOperatorIds array at the given blocknumber // <= FOUND

['114']

114:         checkSignaturesIndices.nonSignerQuorumBitmapIndices = registryCoordinator.getQuorumBitmapIndicesAtBlockNumber(referenceBlockNumber, nonSignerOperatorIds); // <= FOUND

['117']

117:         checkSignaturesIndices.totalStakeIndices = stakeRegistry.getTotalStakeIndicesAtBlockNumber(referenceBlockNumber, quorumNumbers); // <= FOUND

['122']

122:             // this array's length will be at most the number of nonSignerOperatorIds, this will be trimmed after it is filled // <= FOUND

['134']

134:                 require(nonSignerQuorumBitmap != 0, "OperatorStateRetriever.getCheckSignaturesIndices: operator must be registered at blocknumber"); // <= FOUND

['139']

139:                     checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex][numNonSignersForQuorum] = stakeRegistry.getStakeUpdateIndexAtBlockNumber( // <= FOUND

['158']

158:         checkSignaturesIndices.quorumApkIndices = blsApkRegistry.getApkIndicesAtBlockNumber(quorumNumbers, referenceBlockNumber); // <= FOUND

['55']

55:      * @param quorumNumbers The quorum numbers the operator is registering for, where each byte is an 8 bit integer quorumNumber. // <= FOUND

['68']

68:      * @param quorumNumbers The quorum numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber. // <= FOUND

['87']

87:      * Returns *zero* if the `operator` has never registered, and otherwise returns the hash of the public key of the operator. // <= FOUND

['94']

94:      * and otherwise returns the (unique) registered operator who owns the BLS public key that is the preimage of `pubkeyHash`. // <= FOUND

['120']

120:     function getApkIndicesAtBlockNumber(bytes calldata quorumNumbers, uint256 blockNumber) external view returns(uint32[] memory); // <= FOUND

['135']

135:     function getApkHashAtBlockNumberAndIndex(uint8 quorumNumber, uint32 blockNumber, uint256 index) external view returns (bytes24); // <= FOUND

['2']

2: // several functions are taken or adapted from https://github.com/HarryR/solcrypto/blob/master/contracts/altbn128.sol (MIT license): // <= FOUND

['28']

28:  * @notice Contains BN254 parameters, common operations (addition, scalar mul, pairing), and BLS signature functionality. // <= FOUND

['8']

8:  * @title Minimal interface for a ServiceManager-type contract that forms the single point for an AVS to push updates to EigenLayer // <= FOUND

['29']

29:      * @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator deregistration from the AVS // <= FOUND

['27']

27:  *      2) a `BLSApkRegistry` that keeps track of operators' BLS public keys and aggregate BLS public keys for each quorum // <= FOUND

['124']

124:      * @param operatorSignature is the signature of the operator used by the AVS to register the operator in the delegation manager // <= FOUND

['185']

185:         require(operatorKickParams.length == quorumNumbers.length, "RegistryCoordinator.registerOperatorWithChurn: input length mismatch"); // <= FOUND

['271']

271:      * @notice For each quorum in `quorumNumbers`, updates the StakeRegistry's view of ALL its registered operators' stakes. // <= FOUND

['272']

272:      * Each quorum's `quorumUpdateBlockNumber` is also updated, which tracks the most recent block number when ALL registered // <= FOUND

['275']

275:      * @param operatorsPerQuorum for each quorum in `quorumNumbers`, this has a corresponding list of operators to update. // <= FOUND

['277']

277:      * @dev Each list of operator addresses MUST represent the entire list of registered operators for the corresponding quorum // <= FOUND

['279']

279:      * @dev invariant: Each list of `operatorsPerQuorum` MUST be a sorted version of `IndexRegistry.getOperatorListAtBlockNumber` // <= FOUND

['281']

281:      * @dev note on race condition: if an operator registers/deregisters for any quorum in `quorumNumbers` after a txn to  // <= FOUND

['330']

330:                         "RegistryCoordinator.updateOperatorsForQuorum: operators array must be sorted in ascending address order" // <= FOUND

['350']

350:         require(_operatorInfo[msg.sender].status == OperatorStatus.REGISTERED, "RegistryCoordinator.updateSocket: operator is not registered"); // <= FOUND

['457']

457:         require(quorumsToAdd.noBitsInCommon(currentBitmap), "RegistryCoordinator._registerOperator: operator already registered for some quorums being registered for"); // <= FOUND

['542']

542:         require(kickParams.quorumNumber == quorumNumber, "RegistryCoordinator._validateChurn: quorumNumber not the same as signed"); // <= FOUND

['568']

568:         require(operatorInfo.status == OperatorStatus.REGISTERED, "RegistryCoordinator._deregisterOperator: operator is not registered"); // <= FOUND

['580']

580:         require(quorumsToRemove.isSubsetOf(currentBitmap), "RegistryCoordinator._deregisterOperator: operator is not registered for specified quorums"); // <= FOUND

['632']

632:     function _individualKickThreshold(uint96 operatorStake, OperatorSetParam memory setParams) internal pure returns (uint96) { // <= FOUND

['652']

652:         require(!isChurnApproverSaltUsed[churnApproverSignature.salt], "RegistryCoordinator._verifyChurnApproverSignature: churnApprover salt already used"); // <= FOUND

['653']

653:         require(churnApproverSignature.expiry >= block.timestamp, "RegistryCoordinator._verifyChurnApproverSignature: churnApprover signature expired");    // <= FOUND

['661']

661:             calculateOperatorChurnApprovalDigestHash(registeringOperator, registeringOperatorId, operatorKickParams, churnApproverSignature.salt, churnApproverSignature.expiry),  // <= FOUND

['763']

763:             "RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId at block number" // <= FOUND

['865']

865:     /// @notice Returns the current quorum bitmap for the given `operatorId` or 0 if the operator is not registered for any quorum // <= FOUND

['881']

881:      * @notice Public function for the the churnApprover signature hash calculation when operators are being kicked from quorums // <= FOUND

['883']

883:      * @param operatorKickParams The parameters needed to kick the operator from the quorums that have reached their caps // <= FOUND

['895']

895:         return _hashTypedDataV4(keccak256(abi.encode(OPERATOR_CHURN_APPROVAL_TYPEHASH, registeringOperator, registeringOperatorId, operatorKickParams, salt, expiry))); // <= FOUND

['19']

19:  * It allows an additional functionality (in addition to registering and deregistering) to update the stake of an operator. // <= FOUND

['35']

35:         require(msg.sender == IRegistryCoordinator(registryCoordinator).owner(), "StakeRegistry.onlyCoordinatorOwner: caller is not the owner of the registryCoordinator"); // <= FOUND

['217']

217:      * @dev Checks to make sure that the *same* strategy cannot be added multiple times (checks against both against existing and new strategies). // <= FOUND

['218']

218:      * @dev This function has no check to make sure that the strategies for a single quorum have the same underlying asset. This is a concious choice, // <= FOUND

['219']

219:      * since a middleware may want, e.g., a stablecoin quorum that accepts USDC, USDT, DAI, etc. as underlying assets and trades them as "equivalent". // <= FOUND

['275']

275:             emit StrategyMultiplierUpdated(quorumNumber, _strategyParams[strategyIndices[i]].strategy, newMultipliers[i]); // <= FOUND

['299']

299:             "StakeRegistry._getStakeUpdateIndexForOperatorAtBlockNumber: no stake update found for operatorId and quorumNumber at block number" // <= FOUND

['395']

395:      * @dev This function has no check to make sure that the strategies for a single quorum have the same underlying asset. This is a conscious choice, // <= FOUND

['472']

472:     function _weightOfOperatorForQuorum(uint8 quorumNumber, address operator) internal virtual view returns (uint96, bool) { // <= FOUND

['565']

565:      * @dev Function returns an StakeUpdate struct with **every entry equal to 0** in the event that the operator has no stake history // <= FOUND

['658']

658:      * @notice Returns the `index`-th entry in the dynamic array of total stake, `_totalStakeHistory` for quorum `quorumNumber`. // <= FOUND

['700']

700:             require(_quorumExists(quorumNumber), "StakeRegistry.getTotalStakeIndicesAtBlockNumber: quorum does not exist"); // <= FOUND

['15']

15:         require(msg.sender == address(registryCoordinator), "IndexRegistry.onlyRegistryCoordinator: caller is not the registry coordinator"); // <= FOUND

['242']

242:     function _latestOperatorIndexUpdate(uint8 quorumNumber, uint32 operatorIndex) internal view returns (OperatorUpdate storage) { // <= FOUND

['248']

248:      * @notice Returns the total number of operators of the service for the given `quorumNumber` at the given `blockNumber` // <= FOUND

['285']

285:                 // Special case: this will be OPERATOR_DOES_NOT_EXIST_ID if this operatorIndex was not used at the block number // <= FOUND

['300']

300:     function getOperatorUpdateAtIndex(uint8 quorumNumber, uint32 operatorIndex, uint32 arrayIndex) external view returns (OperatorUpdate memory) { // <= FOUND

['305']

305:     function getQuorumUpdateAtIndex(uint8 quorumNumber, uint32 quorumIndex) external view returns (QuorumUpdate memory) { // <= FOUND

['317']

317:     function getLatestOperatorUpdate(uint8 quorumNumber, uint32 operatorIndex) external view returns (OperatorUpdate memory) { // <= FOUND

['30']

30:     /// @notice If true, check the staleness of the operator stakes and that its within the delegation withdrawalDelayBlocks window. // <= FOUND

['34']

34:         require(msg.sender == registryCoordinator.owner(), "BLSSignatureChecker.onlyCoordinatorOwner: caller is not the owner of the registryCoordinator"); // <= FOUND

['83']

83:      * @param params is the struct containing information on nonsigners, stakes, quorum apks, and the aggregate signature // <= FOUND

['115']

115:         require(referenceBlockNumber < uint32(block.number), "BLSSignatureChecker.checkSignatures: invalid reference block"); // <= FOUND

['139']

139:             uint256 signingQuorumBitmap = BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, registryCoordinator.quorumCount()); // <= FOUND

['193']

193:                         registryCoordinator.quorumUpdateBlockNumber(uint8(quorumNumbers[i])) + withdrawalDelayBlocks >= referenceBlockNumber, // <= FOUND

['194']

194:                         "BLSSignatureChecker.checkSignatures: StakeRegistry updates must be within withdrawalDelayBlocks window" // <= FOUND

['256']

256:         // return the total stakes that signed for each quorum, and a hash of the information required to prove the exact signers and stake // <= FOUND

['276']

276:         uint256 gamma = uint256(keccak256(abi.encodePacked(msgHash, apk.X, apk.Y, apkG2.X[0], apkG2.X[1], apkG2.Y[0], apkG2.Y[1], sigma.X, sigma.Y))) % BN254.FR_MODULUS; // <= FOUND

['6']

6:  * @title Library for Bitmap utilities such as converting between an array of bytes and a bitmap and finding the number of 1s in a bitmap. // <= FOUND

['19']

19:      * @param orderedBytesArray The array of bytes to convert/compress into a bitmap. Must be in strictly ascending order. // <= FOUND

['22']

22:      * @dev This function will eventually revert in the event that the `orderedBytesArray` is not properly ordered (in ascending order). // <= FOUND

['23']

23:      * @dev This function will also revert if the `orderedBytesArray` input contains any duplicate entries (i.e. duplicate bytes). // <= FOUND

['40']

40:         // perform the 0-th loop iteration with the ordering check *omitted* (since it is unnecessary / will always pass) // <= FOUND

['41']

41:         // construct a single-bit mask from the numerical value of the 0th byte of the array, and immediately add it to the bitmap // <= FOUND

['48']

48:             // check strictly ascending array ordering by comparing the mask to the bitmap so far (revert if mask isn't greater than bitmap) // <= FOUND

['62']

62:     function orderedBytesArrayToBitmap(bytes memory orderedBytesArray, uint8 bitUpperBound) internal pure returns (uint256) { // <= FOUND

['77']

77:      * It also returns 'false' early for arrays with length in excess of MAX_BYTE_ARRAY_LENGTH (i.e. so long that they cannot be strictly ordered) // <= FOUND

['136']

136:         ), "BLSApkRegistry.registerBLSPublicKey: either the G1 signature is wrong, or G1 and G2 private key do not match"); // <= FOUND

['17']

17:     /// @notice struct used to store the stakes of an individual operator or the sum of all operators' stakes, for storage // <= FOUND

['90']

90:      * @notice Initialize a new quorum created by the registry coordinator by setting strategies, weights, and minimum stake // <= FOUND

['155']

155:      * @notice Returns the `index`-th entry in the dynamic array of total stake, `totalStakeHistory` for quorum `quorumNumber`. // <= FOUND

['168']

168:     function getTotalStakeIndicesAtBlockNumber(uint32 blockNumber, bytes calldata quorumNumbers) external view returns(uint32[] memory) ; // <= FOUND

['174']

174:      * @param index Array index for lookup, within the dynamic array `operatorIdToStakeHistory[operatorId][quorumNumber]`. // <= FOUND

['214']

214:     function getTotalStakeAtBlockNumberFromIndex(uint8 quorumNumber, uint32 blockNumber, uint256 index) external view returns (uint96); // <= FOUND

[NonCritical-25] Setters should prevent re-setting of the same value

Resolution

In Solidity, manipulating contract storage comes with significant gas costs. One can optimize gas usage by preventing unnecessary storage updates when the new value is the same as the existing one. If an existing value is the same as the new one, not reassigning it to the storage could potentially save substantial amounts of gas, notably 2900 gas for a 'Gsreset'. This saving may come at the expense of a cold storage load operation ('Gcoldsload'), which costs 2100 gas, or a warm storage access operation ('Gwarmaccess'), which costs 100 gas. Therefore, the gas efficiency of your contract can be significantly improved by adding a check that compares the new value with the current one before any storage update operation. If the values are the same, you can bypass the storage operation, thereby saving gas.

Num of instances: 6

Findings

Click to show findings

['52']

52:     function setStaleStakesForbidden(bool value) external onlyCoordinatorOwner {
53:         staleStakesForbidden = value;
54:         emit StaleStakesForbiddenUpdate(value);
55:     }

['400']

400:     function setOperatorSetParams(
401:         uint8 quorumNumber, 
402:         OperatorSetParam memory operatorSetParams
403:     ) external onlyOwner quorumExists(quorumNumber) {
404:         _setOperatorSetParams(quorumNumber, operatorSetParams);
405:     }

['413']

413:     function setChurnApprover(address _churnApprover) external onlyOwner {
414:         _setChurnApprover(_churnApprover);
415:     }

['422']

422:     function setEjector(address _ejector) external onlyOwner {
423:         _setEjector(_ejector);
424:     }

['56']

56:     function setMetadataURI(string memory _metadataURI) public virtual onlyOwner {
57:         _avsDirectory.updateAVSMetadataURI(_metadataURI);
58:     }

['208']

208:     function setMinimumStakeForQuorum(
209:         uint8 quorumNumber, 
210:         uint96 minimumStake
211:     ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber) {
212:         _setMinimumStakeForQuorum(quorumNumber, minimumStake);
213:     }

[NonCritical-26] Use newer solidity versions

Resolution

Newer solidity versions have new functionality and are generally more gas efficient too (0.8.19) as such it makes sense to use them provided it is safe to do so

Num of instances: 2

Findings

Click to show findings

['2']

2: pragma solidity =0.8.12; // <= FOUND

[]

2: pragma solidity >=0.5.0;

[NonCritical-27] Function names should differ to make the code more readable

Resolution

In Solidity, while function overriding allows for functions with the same name to coexist, it is advisable to avoid this practice to enhance code readability and maintainability. Having multiple functions with the same name, even with different parameters or in inherited contracts, can cause confusion and increase the likelihood of errors during development, testing, and debugging. Using distinct and descriptive function names not only clarifies the purpose and behavior of each function, but also helps prevent unintended function calls or incorrect overriding. By adopting a clear and consistent naming convention, developers can create more comprehensible and maintainable smart contracts.

Num of instances: 16

Findings

Click to show findings

['25']

25:     function orderedBytesArrayToBitmap(bytes memory orderedBytesArray) internal pure returns (uint256)  // <= FOUND

['62']

62:     function orderedBytesArrayToBitmap(bytes memory orderedBytesArray, uint8 bitUpperBound) internal pure returns (uint256)  // <= FOUND

['188']

188:     function plus(uint256 a, uint256 b) internal pure returns (uint256)  // <= FOUND

['99']

99:     function plus(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r)  // <= FOUND

['42']

42:     function registerOperator( // <= FOUND
43:         address operator,
44:         bytes memory quorumNumbers
45:     ) public virtual onlyRegistryCoordinator 

['40']

40:     function registerOperator( // <= FOUND
41:         bytes32 operatorId, 
42:         bytes calldata quorumNumbers
43:     ) public virtual onlyRegistryCoordinator returns(uint32[] memory) 

['128']

128:     function registerOperator( // <= FOUND
129:         bytes calldata quorumNumbers,
130:         string calldata socket,
131:         IBLSApkRegistry.PubkeyRegistrationParams calldata params,
132:         SignatureWithSaltAndExpiry memory operatorSignature
133:     ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) 

['66']

66:     function registerOperator( // <= FOUND
67:         address operator,
68:         bytes32 operatorId,
69:         bytes calldata quorumNumbers
70:     ) public virtual onlyRegistryCoordinator returns (uint96[] memory, uint96[] memory) 

['68']

68:     function deregisterOperator( // <= FOUND
69:         address operator,
70:         bytes memory quorumNumbers
71:     ) public virtual onlyRegistryCoordinator 

['82']

82:     function deregisterOperator( // <= FOUND
83:         bytes32 operatorId, 
84:         bytes calldata quorumNumbers
85:     ) public virtual onlyRegistryCoordinator 

['242']

242:     function deregisterOperator( // <= FOUND
243:         bytes calldata quorumNumbers
244:     ) external onlyWhenNotPaused(PAUSED_DEREGISTER_OPERATOR) 

['114']

114:     function deregisterOperator( // <= FOUND
115:         bytes32 operatorId,
116:         bytes calldata quorumNumbers
117:     ) public virtual onlyRegistryCoordinator 

['40']

40:     function getOperatorState( // <= FOUND
41:         IRegistryCoordinator registryCoordinator, 
42:         bytes32 operatorId, 
43:         uint32 blockNumber
44:     ) external view returns (uint256, Operator[][] memory) 

['64']

64:     function getOperatorState( // <= FOUND
65:         IRegistryCoordinator registryCoordinator, 
66:         bytes memory quorumNumbers, 
67:         uint32 blockNumber
68:     ) public view returns(Operator[][] memory) 

['281']

281:     function getOperatorId(address operator) public view returns (bytes32)  // <= FOUND

['797']

797:     function getOperatorId(address operator) external view returns (bytes32)  // <= FOUND

[NonCritical-28] Functions should not be longer than 50 lines

Resolution

Overly complex code can make understanding functionality more difficult, try to further modularise your code to ensure readability

Num of instances: 1

Findings

Click to show findings

[]

87:     function checkSignatures(
88:         bytes32 msgHash, 
89:         bytes calldata quorumNumbers,
90:         uint32 referenceBlockNumber, 
91:         NonSignerStakesAndSignature memory params
92:     ) 
93:         public 
94:         view
95:         returns (
96:             QuorumStakeTotals memory,
97:             bytes32
98:         )
99:     

[NonCritical-29] A function's visibility should be declared before any modifiers

Resolution

This is a style convention and should be followed for readability and compliance to standards

Num of instances: 1

Findings

Click to show findings

['87']

87:     function checkSignatures(
88:         bytes32 msgHash, 
89:         bytes calldata quorumNumbers,
90:         uint32 referenceBlockNumber, 
91:         NonSignerStakesAndSignature memory params
92:     ) 
93:         public 
94:         view
95:         returns (
96:             QuorumStakeTotals memory,
97:             bytes32
98:         )
99:     {
100:         require(quorumNumbers.length != 0, "BLSSignatureChecker.checkSignatures: empty quorum input");
101: 
102:         require(
103:             (quorumNumbers.length == params.quorumApks.length) &&
104:             (quorumNumbers.length == params.quorumApkIndices.length) &&
105:             (quorumNumbers.length == params.totalStakeIndices.length) &&
106:             (quorumNumbers.length == params.nonSignerStakeIndices.length),
107:             "BLSSignatureChecker.checkSignatures: input quorum length mismatch"
108:         );
109: 
110:         require(
111:             params.nonSignerPubkeys.length == params.nonSignerQuorumBitmapIndices.length,
112:             "BLSSignatureChecker.checkSignatures: input nonsigner length mismatch"
113:         );
114: 
115:         require(referenceBlockNumber < uint32(block.number), "BLSSignatureChecker.checkSignatures: invalid reference block");
116: 
117:         
118:         
119:         
120:         
121:         
122:         
123:         BN254.G1Point memory apk = BN254.G1Point(0, 0);
124: 
125:         
126:         
127:         
128:         QuorumStakeTotals memory stakeTotals;
129:         stakeTotals.totalStakeForQuorum = new uint96[](quorumNumbers.length);
130:         stakeTotals.signedStakeForQuorum = new uint96[](quorumNumbers.length);
131: 
132:         NonSignerInfo memory nonSigners;
133:         nonSigners.quorumBitmaps = new uint256[](params.nonSignerPubkeys.length);
134:         nonSigners.pubkeyHashes = new bytes32[](params.nonSignerPubkeys.length);
135: 
136:         {
137:             
138:             
139:             uint256 signingQuorumBitmap = BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, registryCoordinator.quorumCount());
140: 
141:             for (uint256 j = 0; j < params.nonSignerPubkeys.length; j++) {
142:                 
143:                 
144:                 
145:                 nonSigners.pubkeyHashes[j] = params.nonSignerPubkeys[j].hashG1Point();
146:                 if (j != 0) {
147:                     require(
148:                         uint256(nonSigners.pubkeyHashes[j]) > uint256(nonSigners.pubkeyHashes[j - 1]),
149:                         "BLSSignatureChecker.checkSignatures: nonSignerPubkeys not sorted"
150:                     );
151:                 }
152: 
153:                 
154:                 nonSigners.quorumBitmaps[j] = 
155:                     registryCoordinator.getQuorumBitmapAtBlockNumberByIndex({
156:                         operatorId: nonSigners.pubkeyHashes[j],
157:                         blockNumber: referenceBlockNumber,
158:                         index: params.nonSignerQuorumBitmapIndices[j]
159:                     });
160: 
161:                 
162:                 
163:                 
164:                 apk = apk.plus(
165:                     params.nonSignerPubkeys[j]
166:                         .scalar_mul_tiny(
167:                             BitmapUtils.countNumOnes(nonSigners.quorumBitmaps[j] & signingQuorumBitmap) 
168:                         )
169:                 );
170:             }
171:         }
172: 
173:         
174:         
175:         
176:         apk = apk.negate();
177: 
178:         
179: 
184:         {
185:             bool _staleStakesForbidden = staleStakesForbidden;
186:             uint256 withdrawalDelayBlocks = _staleStakesForbidden ? delegation.minWithdrawalDelayBlocks() : 0;
187: 
188:             for (uint256 i = 0; i < quorumNumbers.length; i++) {
189:                 
190:                 
191:                 if (_staleStakesForbidden) {
192:                     require(
193:                         registryCoordinator.quorumUpdateBlockNumber(uint8(quorumNumbers[i])) + withdrawalDelayBlocks >= referenceBlockNumber,
194:                         "BLSSignatureChecker.checkSignatures: StakeRegistry updates must be within withdrawalDelayBlocks window"
195:                     );
196:                 }
197: 
198:                 
199:                 
200:                 require(
201:                     bytes24(params.quorumApks[i].hashG1Point()) == 
202:                         blsApkRegistry.getApkHashAtBlockNumberAndIndex({
203:                             quorumNumber: uint8(quorumNumbers[i]),
204:                             blockNumber: referenceBlockNumber,
205:                             index: params.quorumApkIndices[i]
206:                         }),
207:                     "BLSSignatureChecker.checkSignatures: quorumApk hash in storage does not match provided quorum apk"
208:                 );
209:                 apk = apk.plus(params.quorumApks[i]);
210: 
211:                 
212:                 stakeTotals.totalStakeForQuorum[i] = 
213:                     stakeRegistry.getTotalStakeAtBlockNumberFromIndex({
214:                         quorumNumber: uint8(quorumNumbers[i]),
215:                         blockNumber: referenceBlockNumber,
216:                         index: params.totalStakeIndices[i]
217:                     });
218:                 stakeTotals.signedStakeForQuorum[i] = stakeTotals.totalStakeForQuorum[i];
219: 
220:                 
221:                 uint256 nonSignerForQuorumIndex = 0;
222:                 
223:                 
224:                 
225:                 for (uint256 j = 0; j < params.nonSignerPubkeys.length; j++) {
226:                     
227:                     if (BitmapUtils.isSet(nonSigners.quorumBitmaps[j], uint8(quorumNumbers[i]))) {
228:                         stakeTotals.signedStakeForQuorum[i] -=
229:                             stakeRegistry.getStakeAtBlockNumberAndIndex({
230:                                 quorumNumber: uint8(quorumNumbers[i]),
231:                                 blockNumber: referenceBlockNumber,
232:                                 operatorId: nonSigners.pubkeyHashes[j],
233:                                 index: params.nonSignerStakeIndices[i][nonSignerForQuorumIndex]
234:                             });
235:                         unchecked {
236:                             ++nonSignerForQuorumIndex;
237:                         }
238:                     }
239:                 }
240:             }
241:         }
242:         {
243:             
244:             (bool pairingSuccessful, bool signatureIsValid) = trySignatureAndApkVerification(
245:                 msgHash, 
246:                 apk, 
247:                 params.apkG2, 
248:                 params.sigma
249:             );
250:             require(pairingSuccessful, "BLSSignatureChecker.checkSignatures: pairing precompile call failed");
251:             require(signatureIsValid, "BLSSignatureChecker.checkSignatures: signature is invalid");
252:         }
253:         
254:         bytes32 signatoryRecordHash = keccak256(abi.encodePacked(referenceBlockNumber, nonSigners.pubkeyHashes));
255: 
256:         
257:         return (stakeTotals, signatoryRecordHash);
258:     }

[NonCritical-30] Functions within contracts are not ordered according to the solidity style guide

Resolution

The following order should be used within contracts

constructor

receive function (if exists)

fallback function (if exists)

external

public

internal

private

Rearrange the contract functions and contructors to fit this ordering

Num of instances: 5

Findings

Click to show findings

['10']

10: contract BLSApkRegistry is BLSApkRegistryStorage  // <= FOUND

['11']

11: contract IndexRegistry is IndexRegistryStorage  // <= FOUND

['15']

15: contract OperatorStateRetriever  // <= FOUND

[]

32: contract RegistryCoordinator is 
33:     EIP712, 
34:     Initializable, 
35:     Pausable,
36:     OwnableUpgradeable,
37:     RegistryCoordinatorStorage, 
38:     ISocketUpdater, 
39:     ISignatureUtils
40: 

['22']

22: contract StakeRegistry is StakeRegistryStorage  // <= FOUND

[NonCritical-31] Double type casts create complexity within the code

Resolution

Double type casting should be avoided in Solidity contracts to prevent unintended consequences and ensure accurate data representation. Performing multiple type casts in succession can lead to unexpected truncation, rounding errors, or loss of precision, potentially compromising the contract's functionality and reliability. Furthermore, double type casting can make the code less readable and harder to maintain, increasing the likelihood of errors and misunderstandings during development and debugging. To ensure precise and consistent data handling, developers should use appropriate data types and avoid unnecessary or excessive type casting, promoting a more robust and dependable contract execution.

Num of instances: 3

Findings

Click to show findings

['131']

130:                 
131:                 bytesArray[arrayIndex] = bytes1(uint8(i)); // <= FOUND

['440']

440:             return value - uint96(uint256(-delta)); // <= FOUND

['442']

442:             return value + uint96(uint256(delta)); // <= FOUND

[NonCritical-32] Functions with array parameters should have length checks in place

Resolution

Functions in Solidity that accept array parameters should incorporate length checks as a security measure. This is to prevent potential overflow errors, unwanted gas consumption, and manipulation attempts. Without length checks, an attacker could pass excessively large arrays as input, causing excessive computation and potentially causing the function to exceed the block gas limit, leading to a denial-of-service. Additionally, unexpected array sizes could lead to logic errors within the function. As a resolution, always validate array length at the start of functions handling array inputs, ensuring it aligns with the expectations of the function logic. This makes the code more robust and predictable.

Num of instances: 6

Findings

Click to show findings

['385']

385:     function createQuorum(
386:         OperatorSetParam memory operatorSetParams,
387:         uint96 minimumStake,
388:         IStakeRegistry.StrategyParams[] memory strategyParams // <= FOUND
389:     ) external virtual onlyOwner {
390:         _createQuorum(operatorSetParams, minimumStake, strategyParams);
391:     }

['645']

645:     function _verifyChurnApproverSignature(
646:         address registeringOperator,
647:         bytes32 registeringOperatorId, 
648:         OperatorKickParam[] memory operatorKickParams,  // <= FOUND
649:         SignatureWithSaltAndExpiry memory churnApproverSignature
650:     ) internal {
651:         
652:         require(!isChurnApproverSaltUsed[churnApproverSignature.salt], "RegistryCoordinator._verifyChurnApproverSignature: churnApprover salt already used");
653:         require(churnApproverSignature.expiry >= block.timestamp, "RegistryCoordinator._verifyChurnApproverSignature: churnApprover signature expired");   
654: 
655:         
656:         isChurnApproverSaltUsed[churnApproverSignature.salt] = true;    
657: 
658:         
659:         EIP1271SignatureUtils.checkSignature_EIP1271(
660:             churnApprover, 
661:             calculateOperatorChurnApprovalDigestHash(registeringOperator, registeringOperatorId, operatorKickParams, churnApproverSignature.salt, churnApproverSignature.expiry), 
662:             churnApproverSignature.signature
663:         );
664:     }

['674']

674:     function _createQuorum(
675:         OperatorSetParam memory operatorSetParams,
676:         uint96 minimumStake,
677:         IStakeRegistry.StrategyParams[] memory strategyParams // <= FOUND
678:     ) internal {
679:         
680:         uint8 prevQuorumCount = quorumCount;
681:         require(prevQuorumCount < MAX_QUORUM_COUNT, "RegistryCoordinator.createQuorum: max quorums reached");
682:         quorumCount = prevQuorumCount + 1;
683:         
684:         
685:         uint8 quorumNumber = prevQuorumCount;
686: 
687:         
688:         _setOperatorSetParams(quorumNumber, operatorSetParams);
689:         stakeRegistry.initializeQuorum(quorumNumber, minimumStake, strategyParams);
690:         indexRegistry.initializeQuorum(quorumNumber);
691:         blsApkRegistry.initializeQuorum(quorumNumber);
692:     }

['887']

887:     function calculateOperatorChurnApprovalDigestHash(
888:         address registeringOperator,
889:         bytes32 registeringOperatorId,
890:         OperatorKickParam[] memory operatorKickParams, // <= FOUND
891:         bytes32 salt,
892:         uint256 expiry
893:     ) public view returns (bytes32) {
894:         
895:         return _hashTypedDataV4(keccak256(abi.encode(OPERATOR_CHURN_APPROVAL_TYPEHASH, registeringOperator, registeringOperatorId, operatorKickParams, salt, expiry)));
896:     }

['192']

192:     function initializeQuorum(
193:         uint8 quorumNumber,
194:         uint96 minimumStake,
195:         StrategyParams[] memory _strategyParams // <= FOUND
196:     ) public virtual onlyRegistryCoordinator {
197:         require(!_quorumExists(quorumNumber), "StakeRegistry.initializeQuorum: quorum already exists");
198:         _addStrategyParams(quorumNumber, _strategyParams);
199:         _setMinimumStakeForQuorum(quorumNumber, minimumStake);
200: 
201:         _totalStakeHistory[quorumNumber].push(StakeUpdate({
202:             updateBlockNumber: uint32(block.number),
203:             nextUpdateBlockNumber: 0,
204:             stake: 0
205:         }));
206:     }

['221']

221:     function addStrategies(
222:         uint8 quorumNumber, 
223:         StrategyParams[] memory _strategyParams // <= FOUND
224:     ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber) {
225:         _addStrategyParams(quorumNumber, _strategyParams);
226:     }

[NonCritical-33] Interface imports should be declared first

Resolution

Amend the ordering of imports to import interfaces first followed by other imports

Num of instances: 4

Findings

Click to show findings

['5']

2: 
3: pragma solidity =0.8.12;
4: 
5: import {IPauserRegistry} from "eigenlayer-contracts/src/contracts/interfaces/IPauserRegistry.sol"; // <= FOUND
6: import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; // <= FOUND
7: import {ISocketUpdater} from "./interfaces/ISocketUpdater.sol"; // <= FOUND
8: import {IBLSApkRegistry} from "./interfaces/IBLSApkRegistry.sol"; // <= FOUND
9: import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol"; // <= FOUND
10: import {IIndexRegistry} from "./interfaces/IIndexRegistry.sol"; // <= FOUND
11: import {IServiceManager} from "./interfaces/IServiceManager.sol"; // <= FOUND
12: import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol"; // <= FOUND
13: 
14: import {EIP1271SignatureUtils} from "eigenlayer-contracts/src/contracts/libraries/EIP1271SignatureUtils.sol"; // <= FOUND
15: import {BitmapUtils} from "./libraries/BitmapUtils.sol"; // <= FOUND
16: import {BN254} from "./libraries/BN254.sol"; // <= FOUND
17: 
18: import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; // <= FOUND
19: import {Initializable} from "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; // <= FOUND
20: import {EIP712} from "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol"; // <= FOUND
21: 
22: import {Pausable} from "eigenlayer-contracts/src/contracts/permissions/Pausable.sol"; // <= FOUND
23: import {RegistryCoordinatorStorage} from "./RegistryCoordinatorStorage.sol"; // <= FOUND
24: 
33: contract RegistryCoordinator is 
34:     EIP712, 
35:     Initializable, 
36:     Pausable,
37:     OwnableUpgradeable,
38:     RegistryCoordinatorStorage, 
39:     ISocketUpdater, 
40:     ISignatureUtils
41: {
42:     using BitmapUtils for *;
43:     using BN254 for BN254.G1Point;
44: 
45:     modifier onlyEjector {
46:         require(msg.sender == ejector, "RegistryCoordinator.onlyEjector: caller is not the ejector");
47:         _;
48:     }
49: 
52:     modifier quorumExists(uint8 quorumNumber) {
53:         require(
54:             quorumNumber < quorumCount, 
55:             "RegistryCoordinator.quorumExists: quorum does not exist"
56:         );
57:         _;
58:     }
59: 
60:     constructor(
61:         IServiceManager _serviceManager,
62:         IStakeRegistry _stakeRegistry,
63:         IBLSApkRegistry _blsApkRegistry,
64:         IIndexRegistry _indexRegistry
65:     ) 
66:         RegistryCoordinatorStorage(_serviceManager, _stakeRegistry, _blsApkRegistry, _indexRegistry)
67:         EIP712("AVSRegistryCoordinator", "v0.0.1") 
68:     {
69:         _disableInitializers();
70:     }
71: 
83:     function initialize(
84:         address _initialOwner,
85:         address _churnApprover,
86:         address _ejector,
87:         IPauserRegistry _pauserRegistry,
88:         uint256 _initialPausedStatus,
89:         OperatorSetParam[] memory _operatorSetParams,
90:         uint96[] memory _minimumStakes,
91:         IStakeRegistry.StrategyParams[][] memory _strategyParams
92:     ) external initializer {
93:         require(

['5']

2: 
3: pragma solidity =0.8.12;
4: 
5: import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; // <= FOUND
6: 
7: import {StakeRegistryStorage, IStrategy} from "./StakeRegistryStorage.sol"; // <= FOUND
8: 
9: import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol"; // <= FOUND
10: import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol"; // <= FOUND
11: 
12: import {BitmapUtils} from "./libraries/BitmapUtils.sol"; // <= FOUND
13: 
23: contract StakeRegistry is StakeRegistryStorage {
24: 
25:     using BitmapUtils for *;
26:     
27:     modifier onlyRegistryCoordinator() {
28:         require(
29:             msg.sender == address(registryCoordinator),
30:             "StakeRegistry.onlyRegistryCoordinator: caller is not the RegistryCoordinator"
31:         );
32:         _;
33:     }
34: 
35:     modifier onlyCoordinatorOwner() {
36:         require(msg.sender == IRegistryCoordinator(registryCoordinator).owner(), "StakeRegistry.onlyCoordinatorOwner: caller is not the owner of the registryCoordinator");
37:         _;
38:     }
39: 
40:     modifier quorumExists(uint8 quorumNumber) {
41:         require(_quorumExists(quorumNumber), "StakeRegistry.quorumExists: quorum does not exist");
42:         _;
43:     }
44: 
45:     constructor(
46:         IRegistryCoordinator _registryCoordinator,
47:         IDelegationManager _delegationManager
48:     ) StakeRegistryStorage(_registryCoordinator, _delegationManager) {}
49: 
67:     function registerOperator(
68:         address operator,
69:         bytes32 operatorId,
70:         bytes calldata quorumNumbers
71:     ) public virtual onlyRegistryCoordinator returns (uint96[] memory, uint96[] memory) {
72: 
73:         uint96[] memory currentStakes = new uint96[](quorumNumbers.length);
74:         uint96[] memory totalStakes = new uint96[](quorumNumbers.length);
75:         for (uint256 i = 0; i < quorumNumbers.length; i++) {            
76:             
77:             uint8 quorumNumber = uint8(quorumNumbers[i]);
78:             require(_quorumExists(quorumNumber), "StakeRegistry.registerOperator: quorum does not exist");
79: 
80:             
81:             
82:             (uint96 currentStake, bool hasMinimumStake) = _weightOfOperatorForQuorum(quorumNumber, operator);
83:             require(
84:                 hasMinimumStake,
85:                 "StakeRegistry.registerOperator: Operator does not meet minimum stake requirement for quorum"
86:             );
87: 

['5']

2: 
3: pragma solidity =0.8.12;
4: 
5: import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; // <= FOUND
6: 
7: import {BitmapUtils} from "./libraries/BitmapUtils.sol";  // <= FOUND
8: import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; // <= FOUND
9: import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; // <= FOUND
10: 
11: import {IServiceManager} from "./interfaces/IServiceManager.sol"; // <= FOUND
12: import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol"; // <= FOUND
13: import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol"; // <= FOUND
14: 
20: abstract contract ServiceManagerBase is IServiceManager, OwnableUpgradeable {
21:     using BitmapUtils for *;
22: 
23:     IRegistryCoordinator internal immutable _registryCoordinator;
24:     IStakeRegistry internal immutable _stakeRegistry;
25:     IAVSDirectory internal immutable _avsDirectory;
26: 
28:     modifier onlyRegistryCoordinator() {
29:         require(
30:             msg.sender == address(_registryCoordinator),
31:             "ServiceManagerBase.onlyRegistryCoordinator: caller is not the registry coordinator"
32:         );
33:         _;
34:     }
35: 
37:     constructor(
38:         IAVSDirectory __avsDirectory,
39:         IRegistryCoordinator __registryCoordinator,
40:         IStakeRegistry __stakeRegistry
41:     ) {
42:         _avsDirectory = __avsDirectory;
43:         _registryCoordinator = __registryCoordinator;
44:         _stakeRegistry = __stakeRegistry;
45:         _disableInitializers();
46:     }
47: 
48:     function __ServiceManagerBase_init(address initialOwner) internal virtual onlyInitializing {
49:         _transferOwnership(initialOwner);
50:     }
51: 
57:     function setMetadataURI(string memory _metadataURI) public virtual onlyOwner {
58:         _avsDirectory.updateAVSMetadataURI(_metadataURI);
59:     }
60: 
66:     function registerOperatorToAVS(
67:         address operator,
68:         ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
69:     ) public virtual onlyRegistryCoordinator {
70:         _avsDirectory.registerOperatorToAVS(operator, operatorSignature);
71:     }
72: 
77:     function deregisterOperatorFromAVS(address operator) public virtual onlyRegistryCoordinator {
78:         _avsDirectory.deregisterOperatorFromAVS(operator);
79:     }
80: 
87:     function getRestakeableStrategies() external view returns (address[] memory) {
88:         uint256 quorumCount = _registryCoordinator.quorumCount();
89: 

['5']

2: 
3: pragma solidity =0.8.12;
4: 
5: import {BLSApkRegistryStorage} from "./BLSApkRegistryStorage.sol"; // <= FOUND
6: 
7: import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol"; // <= FOUND
8: 
9: import {BN254} from "./libraries/BN254.sol"; // <= FOUND
10: 
11: contract BLSApkRegistry is BLSApkRegistryStorage {
12:     using BN254 for BN254.G1Point;
13: 
15:     modifier onlyRegistryCoordinator() {
16:         require(
17:             msg.sender == address(registryCoordinator),
18:             "BLSApkRegistry.onlyRegistryCoordinator: caller is not the registry coordinator"
19:         );
20:         _;
21:     }
22: 
24:     constructor(
25:         IRegistryCoordinator _registryCoordinator
26:     ) BLSApkRegistryStorage(_registryCoordinator) {}
27: 
43:     function registerOperator(
44:         address operator,
45:         bytes memory quorumNumbers
46:     ) public virtual onlyRegistryCoordinator {
47:         
48:         (BN254.G1Point memory pubkey, ) = getRegisteredPubkey(operator);
49: 
50:         
51:         _processQuorumApkUpdate(quorumNumbers, pubkey);
52: 
53:         
54:         emit OperatorAddedToQuorums(operator, quorumNumbers);
55:     }
56: 
69:     function deregisterOperator(
70:         address operator,
71:         bytes memory quorumNumbers
72:     ) public virtual onlyRegistryCoordinator {
73:         
74:         (BN254.G1Point memory pubkey, ) = getRegisteredPubkey(operator);
75: 
76:         
77:         _processQuorumApkUpdate(quorumNumbers, pubkey.negate());
78:         emit OperatorRemovedFromQuorums(operator, quorumNumbers);
79:     }
80: 
85:     function initializeQuorum(uint8 quorumNumber) public virtual onlyRegistryCoordinator {
86:         require(apkHistory[quorumNumber].length == 0, "BLSApkRegistry.initializeQuorum: quorum already exists");
87: 
88:         apkHistory[quorumNumber].push(ApkUpdate({
89:             apkHash: bytes24(0),
90:             updateBlockNumber: uint32(block.number),
91:             nextUpdateBlockNumber: 0

[NonCritical-34] Upgradable contract constructor should have the initialize modifier

Num of instances: 1

Findings

Click to show findings

['59']

32: contract RegistryCoordinator is 
33:     EIP712, 
34:     Initializable, 
35:     Pausable,
36:     OwnableUpgradeable,
37:     RegistryCoordinatorStorage, 
38:     ISocketUpdater, 
39:     ISignatureUtils
40: {
41:     using BitmapUtils for *;
42:     using BN254 for BN254.G1Point;
43: 
44:     modifier onlyEjector {
45:         require(msg.sender == ejector, "RegistryCoordinator.onlyEjector: caller is not the ejector");
46:         _;
47:     }
48: 
51:     modifier quorumExists(uint8 quorumNumber) {
52:         require(
53:             quorumNumber < quorumCount, 
54:             "RegistryCoordinator.quorumExists: quorum does not exist"
55:         );
56:         _;
57:     }
58: 
59:     constructor( // <= FOUND
60:         IServiceManager _serviceManager,
61:         IStakeRegistry _stakeRegistry,
62:         IBLSApkRegistry _blsApkRegistry,
63:         IIndexRegistry _indexRegistry
64:     ) 
65:         RegistryCoordinatorStorage(_serviceManager, _stakeRegistry, _blsApkRegistry, _indexRegistry)
66:         EIP712("AVSRegistryCoordinator", "v0.0.1") 
67:     {
68:         _disableInitializers();
69:     }
70: 
82:     function initialize(
83:         address _initialOwner,
84:         address _churnApprover,
85:         address _ejector,
86:         IPauserRegistry _pauserRegistry,
87:         uint256 _initialPausedStatus,
88:         OperatorSetParam[] memory _operatorSetParams,
89:         uint96[] memory _minimumStakes,
90:         IStakeRegistry.StrategyParams[][] memory _strategyParams
91:     ) external initializer {
92:         require(
93:             _operatorSetParams.length == _minimumStakes.length && _minimumStakes.length == _strategyParams.length,
94:             "RegistryCoordinator.initialize: input length mismatch"
95:         );
96:         
97:         
98:         _transferOwnership(_initialOwner);
99:         _initializePauser(_pauserRegistry, _initialPausedStatus);
100:         _setChurnApprover(_churnApprover);
101:         _setEjector(_ejector);
102: 
103:         
104:         registries.push(address(stakeRegistry));
105:         registries.push(address(blsApkRegistry));
106:         registries.push(address(indexRegistry));
107: 
108:         
109:         for (uint256 i = 0; i < _operatorSetParams.length; i++) {
110:             _createQuorum(_operatorSetParams[i], _minimumStakes[i], _strategyParams[i]);
111:         }
112:     }
113: 
128:     function registerOperator(
129:         bytes calldata quorumNumbers,
130:         string calldata socket,
131:         IBLSApkRegistry.PubkeyRegistrationParams calldata params,
132:         SignatureWithSaltAndExpiry memory operatorSignature
133:     ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) {
134:         
135: 

[NonCritical-35] Keccak hashes which never change can be made into a constant state variable

Resolution

There is no point in using GAS to calculate a kaccak hash which never changes

Num of instances: 1

Findings

Click to show findings

['21']

20:     
21:     bytes32 public constant PUBKEY_REGISTRATION_TYPEHASH = keccak256("BN254PubkeyRegistration(address operator)"); // <= FOUND

[NonCritical-36] Unused state variables present

Resolution

If these serve no purpose, they should be safely removed

Num of instances: 2

Findings

Click to show findings

['22']

22: uint256 internal constant MAX_QUORUM_BITMAP = type(uint192).max; // <= FOUND

['22']

22: uint256 internal constant MAX_BIPS = 10000; // <= FOUND

[NonCritical-37] Constants should be on the left side of the

Resolution

Putting constants on the left side of a comparison operator like == or < is a best practice known as "Yoda conditions", which can help prevent accidental assignment instead of comparison. In some programming languages, if a variable is mistakenly put on the left with a single = instead of ==, it assigns the constant's value to the variable without any compiler error. However, doing this with the constant on the left would generate an error, as constants cannot be assigned values. Although Solidity's static typing system prevents accidental assignments within conditionals, adopting this practice enhances code readability and consistency, especially when developers are working across multiple languages that support this convention.

Num of instances: 14

Findings

Click to show findings

['31']

31:         if (orderedBytesArray.length == 0)  // <= FOUND

['85']

85:         if (bytesArray.length == 0)  // <= FOUND

['89']

89:         if (p.X == 0 && p.Y == 0)  // <= FOUND

['139']

139:         if (_operatorIndexHistory[quorumNumber][newOperatorCount - 1].length == 0)  // <= FOUND

['507']

507:         if (operatorId == 0)  // <= FOUND

['702']

702:         if (historyLength == 0)  // <= FOUND

['702']

702:         if (historyLength == 0)  // <= FOUND

['89']

89:         if (quorumCount == 0)  // <= FOUND

['121']

121:         if (operatorBitmap == 0 || _registryCoordinator.quorumCount() == 0)  // <= FOUND

['702']

702:         if (historyLength == 0)  // <= FOUND

['367']

367:         if (stakeDelta == 0)  // <= FOUND

['702']

702:         if (historyLength == 0)  // <= FOUND

['147']

147:                 if ((s >> i) & 1 == 1)  // <= FOUND

['439']

439:        if (delta < 0)  // <= FOUND

[NonCritical-38] Defined named returns not used within function

Resolution

Such instances can be replaced with unnamed returns

Num of instances: 4

Findings

Click to show findings

['100']

100:     function registerBLSPublicKey( // <= FOUND
101:         address operator,
102:         PubkeyRegistrationParams calldata params,
103:         BN254.G1Point calldata pubkeyRegistrationMessageHash
104:     ) external onlyRegistryCoordinator returns (bytes32 operatorId) { // <= FOUND
105:         bytes32 pubkeyHash = BN254.hashG1Point(params.pubkeyG1);
106:         require(
107:             pubkeyHash != ZERO_PK_HASH, "BLSApkRegistry.registerBLSPublicKey: cannot register zero pubkey"
108:         );
109:         require(
110:             operatorToPubkeyHash[operator] == bytes32(0),
111:             "BLSApkRegistry.registerBLSPublicKey: operator already registered pubkey"
112:         );
113:         require(
114:             pubkeyHashToOperator[pubkeyHash] == address(0),
115:             "BLSApkRegistry.registerBLSPublicKey: public key already registered"
116:         );
117: 
118:         
119:         uint256 gamma = uint256(keccak256(abi.encodePacked(
120:             params.pubkeyRegistrationSignature.X, 
121:             params.pubkeyRegistrationSignature.Y, 
122:             params.pubkeyG1.X, 
123:             params.pubkeyG1.Y, 
124:             params.pubkeyG2.X, 
125:             params.pubkeyG2.Y, 
126:             pubkeyRegistrationMessageHash.X, 
127:             pubkeyRegistrationMessageHash.Y
128:         ))) % BN254.FR_MODULUS;
129:         
130:         
131:         require(BN254.pairing(
132:             params.pubkeyRegistrationSignature.plus(params.pubkeyG1.scalar_mul(gamma)),
133:             BN254.negGeneratorG2(),
134:             pubkeyRegistrationMessageHash.plus(BN254.generatorG1().scalar_mul(gamma)),
135:             params.pubkeyG2
136:         ), "BLSApkRegistry.registerBLSPublicKey: either the G1 signature is wrong, or G1 and G2 private key do not match");
137: 
138:         operatorToPubkey[operator] = params.pubkeyG1;
139:         operatorToPubkeyHash[operator] = pubkeyHash;
140:         pubkeyHashToOperator[pubkeyHash] = operator;
141: 
142:         emit NewPubkeyRegistration(operator, params.pubkeyG1, params.pubkeyG2);
143:         return pubkeyHash;
144:     }

['329']

329:     function expMod(uint256 _base, uint256 _exponent, uint256 _modulus) internal view returns (uint256 retval) { // <= FOUND
330:         bool success;
331:         uint256[1] memory output;
332:         uint[6] memory input;
333:         input[0] = 0x20; 
334:         input[1] = 0x20; 
335:         input[2] = 0x20; 
336:         input[3] = _base;
337:         input[4] = _exponent;
338:         input[5] = _modulus;
339:         assembly {
340:             success := staticcall(sub(gas(), 2000), 5, input, 0xc0, output, 0x20)
341:             
342:             switch success
343:             case 0 {
344:                 invalid()
345:             }
346:         }
347:         require(success, "BN254.expMod: call failure");
348:         return output[0];
349:     }

['235']

235:     function _latestQuorumUpdate(uint8 quorumNumber) internal view returns (QuorumUpdate storage) { // <= FOUND
236:         uint256 historyLength = _operatorCountHistory[quorumNumber].length;
237:         return _operatorCountHistory[quorumNumber][historyLength - 1];
238:     }

['242']

242:     function _latestOperatorIndexUpdate(uint8 quorumNumber, uint32 operatorIndex) internal view returns (OperatorUpdate storage) { // <= FOUND
243:         uint256 historyLength = _operatorIndexHistory[quorumNumber][operatorIndex].length;
244:         return _operatorIndexHistory[quorumNumber][operatorIndex][historyLength - 1];
245:     }

[NonCritical-39] Initialize functions do not emit an event

Resolution

Emitting an event within initializer functions in Solidity is a best practice for providing transparency and traceability. Initializer functions set the initial state and values of an upgradeable contract. Emitting an event during initialization allows anyone to verify and audit the initial state of the contract via the transaction logs. This can be particularly useful for verifying the parameters set during initialization, tracking the contract's deployment, and troubleshooting or debugging. Therefore, developers should include an event emission in their initializer functions, providing a clear record of the contract's initialization and enhancing the contract's transparency and security.

Num of instances: 1

Findings

Click to show findings

['82']

82:     function initialize( // <= FOUND
83:         address _initialOwner,
84:         address _churnApprover,
85:         address _ejector,
86:         IPauserRegistry _pauserRegistry,
87:         uint256 _initialPausedStatus,
88:         OperatorSetParam[] memory _operatorSetParams,
89:         uint96[] memory _minimumStakes,
90:         IStakeRegistry.StrategyParams[][] memory _strategyParams
91:     ) external initializer {
92:         require(
93:             _operatorSetParams.length == _minimumStakes.length && _minimumStakes.length == _strategyParams.length,
94:             "RegistryCoordinator.initialize: input length mismatch"
95:         );
96:         
97:         
98:         _transferOwnership(_initialOwner);
99:         _initializePauser(_pauserRegistry, _initialPausedStatus);
100:         _setChurnApprover(_churnApprover);
101:         _setEjector(_ejector);
102: 
103:         
104:         registries.push(address(stakeRegistry));
105:         registries.push(address(blsApkRegistry));
106:         registries.push(address(indexRegistry));
107: 
108:         
109:         for (uint256 i = 0; i < _operatorSetParams.length; i++) {
110:             _createQuorum(_operatorSetParams[i], _minimumStakes[i], _strategyParams[i]);
111:         }
112:     }

[NonCritical-40] Both immutable and constant state variables should be CONSTANT_CASE

Resolution

Make found instants CAPITAL_CASE

Num of instances: 10

Findings

Click to show findings

['26']

26: IRegistryCoordinator public immutable registryCoordinator; // <= FOUND

['39']

39: IStakeRegistry public immutable stakeRegistry; // <= FOUND

['29']

29: IDelegationManager public immutable delegation; // <= FOUND

['16']

16: address public immutable registryCoordinator; // <= FOUND

['35']

35: IServiceManager public immutable serviceManager; // <= FOUND

['22']

22: IRegistryCoordinator internal immutable _registryCoordinator; // <= FOUND

['23']

23: IStakeRegistry internal immutable _stakeRegistry; // <= FOUND

['24']

24: IAVSDirectory internal immutable _avsDirectory; // <= FOUND

['37']

37: IBLSApkRegistry public immutable blsApkRegistry; // <= FOUND

['41']

41: IIndexRegistry public immutable indexRegistry; // <= FOUND

[NonCritical-41] Consider using named mappings

Resolution

In Solidity version 0.8.18 and beyond mapping parameters can be named. This makes the purpose and function of a given mapping far clearer which in turn improves readability.

Num of instances: 8

Findings

Click to show findings

['56']

56:     mapping(bytes32 => bool) public isChurnApproverSaltUsed; // <= FOUND

['22']

22:     mapping(bytes32 => address) public pubkeyHashToOperator; // <= FOUND

['24']

24:     mapping(address => BN254.G1Point) public operatorToPubkey; // <= FOUND

['30']

30:     mapping(uint8 => BN254.G1Point) public currentApk; // <= FOUND

['20']

20:     mapping(address => bytes32) public operatorToPubkeyHash; // <= FOUND

['27']

27:     mapping(uint8 => mapping(bytes32 => uint32)) public currentOperatorIndex; // <= FOUND

['58']

58:     mapping(uint8 => uint256) public quorumUpdateBlockNumber; // <= FOUND

['32']

32:     mapping(uint8 => uint96) public minimumStakeForQuorum; // <= FOUND

[NonCritical-42] Use a single contract or library for system wide constants

Num of instances: 1

Findings

Click to show findings

['24']

18: contract BLSSignatureChecker is IBLSSignatureChecker {
19:     using BN254 for BN254.G1Point;
20:     
24:     uint256 internal constant PAIRING_EQUALITY_CHECK_GAS = 120000; // <= FOUND
25: 
26:     IRegistryCoordinator public immutable registryCoordinator;
27:     IStakeRegistry public immutable stakeRegistry;
28:     IBLSApkRegistry public immutable blsApkRegistry;
29:     IDelegationManager public immutable delegation;
30:     
31:     bool public staleStakesForbidden;
32: 
86:     uint256[49] private __GAP;
87: }

[NonCritical-43] Consider using modifiers for address control

Resolution

Modifiers in Solidity can improve code readability and modularity by encapsulating repetitive checks, such as address validity checks, into a reusable construct. For example, an onlyOwner modifier can be used to replace repetitive require(msg.sender == owner) checks across several functions, reducing code redundancy and enhancing maintainability. To implement, define a modifier with the check, then apply the modifier to relevant functions.

Num of instances: 7

Findings

Click to show findings

['15']

15:         require(
16:             msg.sender == address(registryCoordinator), // <= FOUND
17:             "BLSApkRegistry.onlyRegistryCoordinator: caller is not the registry coordinator"
18:         );

['34']

34:         require(msg.sender == registryCoordinator.owner(), "BLSSignatureChecker.onlyCoordinatorOwner: caller is not the owner of the registryCoordinator"); // <= FOUND

['15']

15:         require(msg.sender == address(registryCoordinator), "IndexRegistry.onlyRegistryCoordinator: caller is not the registry coordinator"); // <= FOUND

['45']

45:         require(msg.sender == ejector, "RegistryCoordinator.onlyEjector: caller is not the ejector"); // <= FOUND

['28']

28:         require(
29:             msg.sender == address(_registryCoordinator), // <= FOUND
30:             "ServiceManagerBase.onlyRegistryCoordinator: caller is not the registry coordinator"
31:         );

['27']

27:         require(
28:             msg.sender == address(registryCoordinator), // <= FOUND
29:             "StakeRegistry.onlyRegistryCoordinator: caller is not the RegistryCoordinator"
30:         );

['35']

35:         require(msg.sender == IRegistryCoordinator(registryCoordinator).owner(), "StakeRegistry.onlyCoordinatorOwner: caller is not the owner of the registryCoordinator"); // <= FOUND

[NonCritical-44] Off-by-one timestamp error

Resolution

In Solidity, using >= or <= to compare against block.timestamp (alias now) may introduce off-by-one errors due to the fact that block.timestamp is only updated once per block and its value remains constant throughout the block's execution. If an operation happens at the exact second when block.timestamp changes, it could result in unexpected behavior. To avoid this, it's safer to use strict inequality operators (> or <). For instance, if a condition should only be met after a certain time, use block.timestamp > time rather than block.timestamp >= time. This way, potential off-by-one errors due to the exact timing of block mining are mitigated, leading to safer, more predictable contract behavior.

Num of instances: 1

Findings

Click to show findings

['645']

645:     function _verifyChurnApproverSignature(
646:         address registeringOperator,
647:         bytes32 registeringOperatorId, 
648:         OperatorKickParam[] memory operatorKickParams, 
649:         SignatureWithSaltAndExpiry memory churnApproverSignature
650:     ) internal {
651:         
652:         require(!isChurnApproverSaltUsed[churnApproverSignature.salt], "RegistryCoordinator._verifyChurnApproverSignature: churnApprover salt already used");
653:         require(churnApproverSignature.expiry >= block.timestamp, "RegistryCoordinator._verifyChurnApproverSignature: churnApprover signature expired");    // <= FOUND
654: 
655:         
656:         isChurnApproverSaltUsed[churnApproverSignature.salt] = true;    
657: 
658:         
659:         EIP1271SignatureUtils.checkSignature_EIP1271(
660:             churnApprover, 
661:             calculateOperatorChurnApprovalDigestHash(registeringOperator, registeringOperatorId, operatorKickParams, churnApproverSignature.salt, churnApproverSignature.expiry), 
662:             churnApproverSignature.signature
663:         );
664:     }

[NonCritical-45] Variables should be used in place of magic numbers to improve readability

Resolution

Magic numbers should be avoided in Solidity code to enhance readability, maintainability, and reduce the likelihood of errors. Magic numbers are hard-coded values with no clear meaning or context, which can create confusion and make the code harder to understand for developers. Using well-defined constants or variables with descriptive names instead of magic numbers not only clarifies the purpose and significance of the value but also simplifies code updates and modifications.

Num of instances: 17

Findings

Click to show findings

['58']

58:                 operatorId: operatorId,
59:                 quorumNumber: quorumNumber,
60:                 operatorIndex: newOperatorCount - 1 // <= FOUND
61:             });

['124']

124:         
125: 
129:         for (uint256 i = 0; (arrayIndex < bytesArray.length) && (i < 256); ++i) { // <= FOUND

['32']

32:     
33:     uint256 internal constant FP_MODULUS =
34:         21888242871839275222246405745257275088696311157297823662689037894645226208583; // <= FOUND

['35']

35:     
36:     uint256 internal constant FR_MODULUS =
37:         21888242871839275222246405745257275088548364400416034343698204186575808495617; // <= FOUND

['50']

50:         return G1Point(1, 2); // <= FOUND

['109']

109:             success := staticcall(sub(gas(), 2000), 6, input, 0x80, r, 0x40) // <= FOUND
110:             
111:             switch success
112:             case 0 {

['176']

176:             success := staticcall(sub(gas(), 2000), 7, input, 0x60, r, 0x40) // <= FOUND
177:             
178:             switch success
179:             case 0 {

['203']

203: 
204:         for (uint256 i = 0; i < 2; i++) { // <= FOUND

['207']

207:             input[j + 2] = p2[i].X[0]; // <= FOUND

['218']

218:             success := staticcall(sub(gas(), 2000), 8, input, mul(12, 0x20), out, 0x20) // <= FOUND
219:             
220:             switch success
221:             case 0 {

['340']

340:             success := staticcall(sub(gas(), 2000), 5, input, 0xc0, output, 0x20) // <= FOUND
341:             
342:             switch success
343:             case 0 {

['208']

208:             input[j + 3] = p2[i].X[1]; // <= FOUND

['320']

320:         
321:         uint256 beta = addmod(mulmod(mulmod(x, x, FP_MODULUS), x, FP_MODULUS), 3, FP_MODULUS); // <= FOUND

['209']

209:             input[j + 4] = p2[i].Y[0]; // <= FOUND

['210']

210:             input[j + 5] = p2[i].Y[1]; // <= FOUND

['204']

204:             uint256 j = i * 6; // <= FOUND

['262']

262:             success := staticcall(pairingGas, 8, input, mul(12, 0x20), out, 0x20) // <= FOUND
263:         }

[NonCritical-46] Long powers of ten should use scientific notation 1eX

Resolution

A large number such as 1000000 is far more readable as 1e6, this will help prevent unintended bugs in the code

Num of instances: 2

Findings

Click to show findings

['24']

24:     
25:     uint16 internal constant BIPS_DENOMINATOR = 10000; // <= FOUND

['22']

22:     
23:     uint256 internal constant MAX_BIPS = 10000; // <= FOUND

[NonCritical-47] Redundant else statement

Num of instances: 6

Findings

Click to show findings

['87']

87:     function negate(G1Point memory p) internal pure returns (G1Point memory) { // <= FOUND
88:         
89:         if (p.X == 0 && p.Y == 0) {
90:             return G1Point(0, 0);
91:         } else {
92:             return G1Point(p.X, FP_MODULUS - (p.Y % FP_MODULUS));
93:         }
94:     }

['732']

732:     function _currentOperatorBitmap(bytes32 operatorId) internal view returns (uint192) { // <= FOUND
733:         uint256 historyLength = _operatorBitmapHistory[operatorId].length;
734:         if (historyLength == 0) {
735:             return 0;
736:         } else {
737:             return _operatorBitmapHistory[operatorId][historyLength - 1].quorumBitmap;
738:         }
739:     }

[]

312:     function _recordOperatorStakeUpdate(
313:         bytes32 operatorId,
314:         uint8 quorumNumber,
315:         uint96 newStake
316:     ) internal returns (int256) {
317: 
318:         uint96 prevStake;
319:         uint256 historyLength = operatorStakeHistory[operatorId][quorumNumber].length;
320: 
321:         if (historyLength == 0) {
322:             
323:             operatorStakeHistory[operatorId][quorumNumber].push(StakeUpdate({
324:                 updateBlockNumber: uint32(block.number),
325:                 nextUpdateBlockNumber: 0,
326:                 stake: newStake
327:             }));
328:         } else {
329:             
330:             StakeUpdate storage lastUpdate = operatorStakeHistory[operatorId][quorumNumber][historyLength-1]; 
331:             prevStake = lastUpdate.stake;
332: 
333:             
334:             if (prevStake == newStake) {
335:                 return 0;
336:             }
337: 
338:             
339: 
342:             if (lastUpdate.updateBlockNumber == uint32(block.number)) {
343:                 lastUpdate.stake = newStake;
344:             } else {
345:                 lastUpdate.nextUpdateBlockNumber = uint32(block.number);
346:                 operatorStakeHistory[operatorId][quorumNumber].push(StakeUpdate({
347:                     updateBlockNumber: uint32(block.number),
348:                     nextUpdateBlockNumber: 0,
349:                     stake: newStake
350:                 }));
351:             }
352:         }
353: 
354:         
355:         emit OperatorStakeUpdate(operatorId, quorumNumber, newStake);
356:         return _calculateDelta({ prev: prevStake, cur: newStake });
357:     }

['361']

361:     function _recordTotalStakeUpdate(uint8 quorumNumber, int256 stakeDelta) internal returns (uint96) { // <= FOUND
362:         
363:         uint256 historyLength = _totalStakeHistory[quorumNumber].length;
364:         StakeUpdate storage lastStakeUpdate = _totalStakeHistory[quorumNumber][historyLength - 1];
365: 
366:         
367:         if (stakeDelta == 0) {
368:             return lastStakeUpdate.stake;
369:         }
370:         
371:         
372:         uint96 newStake = _applyDelta(lastStakeUpdate.stake, stakeDelta);
373: 
374:         
375: 
378:         if (lastStakeUpdate.updateBlockNumber == uint32(block.number)) {
379:             lastStakeUpdate.stake = newStake;
380:         } else {
381:             lastStakeUpdate.nextUpdateBlockNumber = uint32(block.number);
382:             _totalStakeHistory[quorumNumber].push(StakeUpdate({
383:                 updateBlockNumber: uint32(block.number),
384:                 nextUpdateBlockNumber: 0,
385:                 stake: newStake
386:             }));
387:         }
388: 
389:         return newStake;
390:     }

['438']

438:     function _applyDelta(uint96 value, int256 delta) internal pure returns (uint96) { // <= FOUND
439:         if (delta < 0) {
440:             return value - uint96(uint256(-delta));
441:         } else {
442:             return value + uint96(uint256(delta));
443:         }
444:     }

[]

567:     function getLatestStakeUpdate(
568:         bytes32 operatorId,
569:         uint8 quorumNumber
570:     ) public view returns (StakeUpdate memory) {
571:         uint256 historyLength = operatorStakeHistory[operatorId][quorumNumber].length;
572:         StakeUpdate memory operatorStakeUpdate;
573:         if (historyLength == 0) {
574:             return operatorStakeUpdate;
575:         } else {
576:             operatorStakeUpdate = operatorStakeHistory[operatorId][quorumNumber][historyLength - 1];
577:             return operatorStakeUpdate;
578:         }
579:     }

[NonCritical-48] Constant array index within iteration

Resolution

Using constant array indexes within for loops is error prone as typically [i] is used instead as by using a constant index such as [0] means that only one value will be used/modified within an array, historically many bugs have been introduced through using a constant index rather than the array iteration index i.e i,j. Provided this is intentional and not a typo consider using enum values to make this distinction clear.

Num of instances: 3

Findings

Click to show findings

['213']

209:        for (uint256 i = 0; i < quorumNumbers.length; i++) {
210:             uint8 quorumNumber = uint8(quorumNumbers[i]);
211:             
212:             uint256 quorumApkUpdatesLength = apkHistory[quorumNumber].length;
213:             if (quorumApkUpdatesLength == 0 || blockNumber < apkHistory[quorumNumber][0].updateBlockNumber) { // <= FOUND
214:                 revert("BLSApkRegistry.getApkIndicesAtBlockNumber: blockNumber is before the first update");
215:             }
216: 
217:             
218:             for (uint256 j = quorumApkUpdatesLength; j > 0; j--) {
219:                 if (apkHistory[quorumNumber][j - 1].updateBlockNumber <= blockNumber) {
220:                     indices[i] = uint32(j - 1);
221:                     break;
222:                 }
223:             }
224:         }

['207']

203:        for (uint256 i = 0; i < 2; i++) {
204:             uint256 j = i * 6;
205:             input[j + 0] = p1[i].X;
206:             input[j + 1] = p1[i].Y;
207:             input[j + 2] = p2[i].X[0]; // <= FOUND
208:             input[j + 3] = p2[i].X[1]; // <= FOUND
209:             input[j + 4] = p2[i].Y[0]; // <= FOUND
210:             input[j + 5] = p2[i].Y[1]; // <= FOUND
211:         }

['702']

698:        for (uint256 i = 0; i < quorumNumbers.length; i++) {
699:             uint8 quorumNumber = uint8(quorumNumbers[i]);
700:             require(_quorumExists(quorumNumber), "StakeRegistry.getTotalStakeIndicesAtBlockNumber: quorum does not exist");
701:             require(
702:                 _totalStakeHistory[quorumNumber][0].updateBlockNumber <= blockNumber, // <= FOUND
703:                 "StakeRegistry.getTotalStakeIndicesAtBlockNumber: quorum has no stake history at blockNumber"
704:             );
705:             uint256 length = _totalStakeHistory[quorumNumber].length;
706:             for (uint256 j = 0; j < length; j++) {
707:                 if (_totalStakeHistory[quorumNumber][length - j - 1].updateBlockNumber <= blockNumber) {
708:                     indices[i] = uint32(length - j - 1);
709:                     break;
710:                 }
711:             }
712:         }

[NonCritical-49] Inconsistent usage of int/uint with int256/uint256 in contract/abstract/library/interface

Resolution

Use uint256/int256 consistently in place of uint/int to prevent ambiguity rather than using both within the same contract/abstract/library/interface

Num of instances: 1

Findings

Click to show findings

['15']

15: contract OperatorStateRetriever {
16:     struct Operator {
44:     ) external view returns (uint256, Operator[][] memory) { // <= FOUND

47:         uint256 index = registryCoordinator.getQuorumBitmapIndicesAtBlockNumber(blockNumber, operatorIds)[0]; // <= FOUND

49:         uint256 quorumBitmap = registryCoordinator.getQuorumBitmapAtBlockNumberByIndex(operatorId, blockNumber, index); // <= FOUND

74:         for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND

78:             for (uint256 j = 0; j < operatorIds.length; j++) { // <= FOUND

121:             uint256 numNonSignersForQuorum = 0; // <= FOUND

125:             for (uint i = 0; i < nonSignerOperatorIds.length; i++) { // <= FOUND

150:             for (uint i = 0; i < numNonSignersForQuorum; i++) { // <= FOUND

[NonCritical-50] Use immutable not constant for keccak state variables

Resolution

It's crucial to leverage the right features for the appropriate contexts in Solidity, despite the compiler's ability to correct common developer mistakes. Both constant and immutable variables have distinct uses. Constant variables are best suited for hard-coded, literal values within your contract, where the value doesn't need to be computed or modified.

On the other hand, immutable variables are ideal for situations where the value might be the result of an expression or received from a constructor. While both serve to define unchanging variables, they function differently and their proper utilization contributes to clearer, more efficient code. Remember, even if the compiler can correct certain mistakes, best practices dictate using the correct feature for the task at hand.

Num of instances: 1

Findings

Click to show findings

['20']

20: bytes32 public constant PUBKEY_REGISTRATION_TYPEHASH = keccak256("BN254PubkeyRegistration(address operator)"); // <= FOUND

[NonCritical-51] Large or complicated code bases should implement invariant tests

Resolution

Reason: Contracts with large codebases, complex inline-assembly, intricate math, or multifaceted inter-contract interactions can harbor elusive bugs, even with full code coverage. These bugs can surface depending on the sequence of operations performed by the user.

Resolution: Implement invariant fuzzing tests using tools like Echidna. Define comprehensive invariants – conditions that must always hold true – and let the fuzzer generate various input and function call sequences to test them. This way, you can significantly mitigate the risks posed by unexplored operation orders and bolster the contract's robustness.

Num of instances: 1

Findings

Click to show findings

[]

87:     function checkSignatures(
88:         bytes32 msgHash, 
89:         bytes calldata quorumNumbers,
90:         uint32 referenceBlockNumber, 
91:         NonSignerStakesAndSignature memory params
92:     ) 
93:         public 
94:         view
95:         returns (
96:             QuorumStakeTotals memory,
97:             bytes32
98:         )
99:     

[NonCritical-52] Empty bytes check is missing

Resolution

When developing smart contracts in Solidity, it's crucial to validate the inputs of your functions. This includes ensuring that the bytes parameters are not empty, especially when they represent crucial data such as addresses, identifiers, or raw data that the contract needs to process.

Missing empty bytes checks can lead to unexpected behaviour in your contract. For instance, certain operations might fail, produce incorrect results, or consume unnecessary gas when performed with empty bytes. Moreover, missing input validation can potentially expose your contract to malicious activity, including exploitation of unhandled edge cases.

To mitigate these issues, always validate that bytes parameters are not empty when the logic of your contract requires it.

Num of instances: 26

Findings

Click to show findings

['62']

62:     function orderedBytesArrayToBitmap(bytes memory orderedBytesArray, uint8 bitUpperBound) internal pure returns (uint256) {
63:         uint256 bitmap = orderedBytesArrayToBitmap(orderedBytesArray);
64: 
65:         require((1 << bitUpperBound) > bitmap, 
66:             "BitmapUtils.orderedBytesArrayToBitmap: bitmap exceeds max value"
67:         );
68: 
69:         return bitmap;
70:     }

['42']

42:     function registerOperator(
43:         address operator,
44:         bytes memory quorumNumbers
45:     ) public virtual onlyRegistryCoordinator {
46:         
47:         (BN254.G1Point memory pubkey, ) = getRegisteredPubkey(operator);
48: 
49:         
50:         _processQuorumApkUpdate(quorumNumbers, pubkey);
51: 
52:         
53:         emit OperatorAddedToQuorums(operator, quorumNumbers);
54:     }

['68']

68:     function deregisterOperator(
69:         address operator,
70:         bytes memory quorumNumbers
71:     ) public virtual onlyRegistryCoordinator {
72:         
73:         (BN254.G1Point memory pubkey, ) = getRegisteredPubkey(operator);
74: 
75:         
76:         _processQuorumApkUpdate(quorumNumbers, pubkey.negate());
77:         emit OperatorRemovedFromQuorums(operator, quorumNumbers);
78:     }

['275']

275:     function getOperatorFromPubkeyHash(bytes32 pubkeyHash) public view returns (address) {
276:         return pubkeyHashToOperator[pubkeyHash];
277:     }

['269']

269:     function trySignatureAndApkVerification(
270:         bytes32 msgHash,
271:         BN254.G1Point memory apk,
272:         BN254.G2Point memory apkG2,
273:         BN254.G1Point memory sigma
274:     ) public view returns(bool pairingSuccessful, bool siganatureIsValid) {
275:         
276:         uint256 gamma = uint256(keccak256(abi.encodePacked(msgHash, apk.X, apk.Y, apkG2.X[0], apkG2.X[1], apkG2.Y[0], apkG2.Y[1], sigma.X, sigma.Y))) % BN254.FR_MODULUS;
277:         
278:         (pairingSuccessful, siganatureIsValid) = BN254.safePairing(
279:                 sigma.plus(apk.scalar_mul(gamma)),
280:                 BN254.negGeneratorG2(),
281:                 BN254.hashToG1(msgHash).plus(BN254.generatorG1().scalar_mul(gamma)),
282:                 apkG2,
283:                 PAIRING_EQUALITY_CHECK_GAS
284:             );
285:     }

['292']

292:     function hashToG1(bytes32 _x) internal view returns (G1Point memory) {
293:         uint256 beta = 0;
294:         uint256 y = 0;
295: 
296:         uint256 x = uint256(_x) % FP_MODULUS;
297: 
298:         while (true) {
299:             (beta, y) = findYFromX(x);
300: 
301:             
302:             if( beta == mulmod(y, y, FP_MODULUS) ) {
303:                 return G1Point(x, y);
304:             }
305: 
306:             x = addmod(x, 1, FP_MODULUS);
307:         }
308:         return G1Point(0, 0);
309:     }

['202']

202:     function _assignOperatorToIndex(bytes32 operatorId, uint8 quorumNumber, uint32 operatorIndex) internal {
203:         OperatorUpdate storage lastUpdate = _latestOperatorIndexUpdate(quorumNumber, operatorIndex);
204: 
205:         _updateOperatorIndexHistory(quorumNumber, operatorIndex, lastUpdate, operatorId);
206: 
207:         
208:         currentOperatorIndex[quorumNumber][operatorId] = operatorIndex;
209:         emit QuorumIndexUpdate(operatorId, quorumNumber, operatorIndex);
210:     }

['217']

217:     function _updateOperatorIndexHistory(
218:         uint8 quorumNumber,
219:         uint32 operatorIndex,
220:         OperatorUpdate storage lastUpdate,
221:         bytes32 newOperatorId
222:     ) internal {
223:         if (lastUpdate.fromBlockNumber == uint32(block.number)) {
224:             lastUpdate.operatorId = newOperatorId;
225:         } else {
226:             _operatorIndexHistory[quorumNumber][operatorIndex].push(OperatorUpdate({
227:                 operatorId: newOperatorId,
228:                 fromBlockNumber: uint32(block.number)
229:             }));
230:         }
231:     }

['40']

40:     function getOperatorState(
41:         IRegistryCoordinator registryCoordinator, 
42:         bytes32 operatorId, 
43:         uint32 blockNumber
44:     ) external view returns (uint256, Operator[][] memory) {
45:         bytes32[] memory operatorIds = new bytes32[](1);
46:         operatorIds[0] = operatorId;
47:         uint256 index = registryCoordinator.getQuorumBitmapIndicesAtBlockNumber(blockNumber, operatorIds)[0];
48:     
49:         uint256 quorumBitmap = registryCoordinator.getQuorumBitmapAtBlockNumberByIndex(operatorId, blockNumber, index);
50: 
51:         bytes memory quorumNumbers = BitmapUtils.bitmapToBytesArray(quorumBitmap);
52: 
53:         return (quorumBitmap, getOperatorState(registryCoordinator, quorumNumbers, blockNumber));
54:     }

['242']

242:     function deregisterOperator(
243:         bytes calldata quorumNumbers
244:     ) external onlyWhenNotPaused(PAUSED_DEREGISTER_OPERATOR) {
245:         _deregisterOperator({
246:             operator: msg.sender, 
247:             quorumNumbers: quorumNumbers
248:         });
249:     }

['363']

363:     function ejectOperator(
364:         address operator, 
365:         bytes calldata quorumNumbers
366:     ) external onlyEjector {
367:         _deregisterOperator({
368:             operator: operator, 
369:             quorumNumbers: quorumNumbers
370:         });
371:     }

['440']

440:     function _registerOperator(
441:         address operator, 
442:         bytes32 operatorId,
443:         bytes calldata quorumNumbers,
444:         string memory socket,
445:         SignatureWithSaltAndExpiry memory operatorSignature
446:     ) internal virtual returns (RegisterResults memory results) {
447:         
448: 
454:         uint192 quorumsToAdd = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount));
455:         uint192 currentBitmap = _currentOperatorBitmap(operatorId);
456:         require(!quorumsToAdd.isEmpty(), "RegistryCoordinator._registerOperator: bitmap cannot be 0");
457:         require(quorumsToAdd.noBitsInCommon(currentBitmap), "RegistryCoordinator._registerOperator: operator already registered for some quorums being registered for");
458:         uint192 newBitmap = uint192(currentBitmap.plus(quorumsToAdd));
459: 
460:         
461: 
464:         _updateOperatorBitmap({
465:             operatorId: operatorId,
466:             newBitmap: newBitmap
467:         });
468: 
469:         emit OperatorSocketUpdate(operatorId, socket);
470: 
471:         
472:         
473:         if (_operatorInfo[operator].status != OperatorStatus.REGISTERED) {
474:             _operatorInfo[operator] = OperatorInfo({
475:                 operatorId: operatorId,
476:                 status: OperatorStatus.REGISTERED
477:             });
478: 
479:             
480:             serviceManager.registerOperatorToAVS(operator, operatorSignature);
481: 
482:             emit OperatorRegistered(operator, operatorId);
483:         }
484: 
485:         
486:         blsApkRegistry.registerOperator(operator, quorumNumbers);
487:         (results.operatorStakes, results.totalStakes) = 
488:             stakeRegistry.registerOperator(operator, operatorId, quorumNumbers);
489:         results.numOperatorsPerQuorum = indexRegistry.registerOperator(operatorId, quorumNumbers);
490: 
491:         return results;
492:     }

['561']

561:     function _deregisterOperator(
562:         address operator, 
563:         bytes memory quorumNumbers
564:     ) internal virtual {
565:         
566:         OperatorInfo storage operatorInfo = _operatorInfo[operator];
567:         bytes32 operatorId = operatorInfo.operatorId;
568:         require(operatorInfo.status == OperatorStatus.REGISTERED, "RegistryCoordinator._deregisterOperator: operator is not registered");
569:         
570:         
571: 
577:         uint192 quorumsToRemove = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount));
578:         uint192 currentBitmap = _currentOperatorBitmap(operatorId);
579:         require(!quorumsToRemove.isEmpty(), "RegistryCoordinator._deregisterOperator: bitmap cannot be 0");
580:         require(quorumsToRemove.isSubsetOf(currentBitmap), "RegistryCoordinator._deregisterOperator: operator is not registered for specified quorums");
581:         uint192 newBitmap = uint192(currentBitmap.minus(quorumsToRemove));
582: 
583:         
584:         _updateOperatorBitmap({
585:             operatorId: operatorId,
586:             newBitmap: newBitmap
587:         });
588: 
589:         
590:         
591:         if (newBitmap.isEmpty()) {
592:             operatorInfo.status = OperatorStatus.DEREGISTERED;
593:             serviceManager.deregisterOperatorFromAVS(operator);
594:             emit OperatorDeregistered(operator, operatorId);
595:         }
596: 
597:         
598:         blsApkRegistry.deregisterOperator(operator, quorumNumbers);
599:         stakeRegistry.deregisterOperator(operatorId, quorumNumbers);
600:         indexRegistry.deregisterOperator(operatorId, quorumNumbers);
601:     }

['609']

609:     function _updateOperator(
610:         address operator,
611:         OperatorInfo memory operatorInfo,
612:         bytes memory quorumsToUpdate
613:     ) internal {
614:         if (operatorInfo.status != OperatorStatus.REGISTERED) {
615:             return;
616:         }
617:         bytes32 operatorId = operatorInfo.operatorId;
618:         uint192 quorumsToRemove = stakeRegistry.updateOperatorStake(operator, operatorId, quorumsToUpdate);
619: 
620:         if (!quorumsToRemove.isEmpty()) {
621:             _deregisterOperator({
622:                 operator: operator,
623:                 quorumNumbers: BitmapUtils.bitmapToBytesArray(quorumsToRemove)
624:             });    
625:         }
626:     }

['645']

645:     function _verifyChurnApproverSignature(
646:         address registeringOperator,
647:         bytes32 registeringOperatorId, 
648:         OperatorKickParam[] memory operatorKickParams, 
649:         SignatureWithSaltAndExpiry memory churnApproverSignature
650:     ) internal {
651:         
652:         require(!isChurnApproverSaltUsed[churnApproverSignature.salt], "RegistryCoordinator._verifyChurnApproverSignature: churnApprover salt already used");
653:         require(churnApproverSignature.expiry >= block.timestamp, "RegistryCoordinator._verifyChurnApproverSignature: churnApprover signature expired");   
654: 
655:         
656:         isChurnApproverSaltUsed[churnApproverSignature.salt] = true;    
657: 
658:         
659:         EIP1271SignatureUtils.checkSignature_EIP1271(
660:             churnApprover, 
661:             calculateOperatorChurnApprovalDigestHash(registeringOperator, registeringOperatorId, operatorKickParams, churnApproverSignature.salt, churnApproverSignature.expiry), 
662:             churnApproverSignature.signature
663:         );
664:     }

['802']

802:     function getOperatorFromId(bytes32 operatorId) external view returns (address) {
803:         return blsApkRegistry.getOperatorFromPubkeyHash(operatorId);
804:     }

['833']

833:     function getQuorumBitmapAtBlockNumberByIndex(
834:         bytes32 operatorId, 
835:         uint32 blockNumber, 
836:         uint256 index
837:     ) external view returns (uint192) {
838:         QuorumBitmapUpdate memory quorumBitmapUpdate = _operatorBitmapHistory[operatorId][index];
839:         
840:         
841: 
845:         require(
846:             blockNumber >= quorumBitmapUpdate.updateBlockNumber, 
847:             "RegistryCoordinator.getQuorumBitmapAtBlockNumberByIndex: quorumBitmapUpdate is from after blockNumber"
848:         );
849:         require(
850:             quorumBitmapUpdate.nextUpdateBlockNumber == 0 || blockNumber < quorumBitmapUpdate.nextUpdateBlockNumber,
851:             "RegistryCoordinator.getQuorumBitmapAtBlockNumberByIndex: quorumBitmapUpdate is from before blockNumber"
852:         );
853: 
854:         return quorumBitmapUpdate.quorumBitmap;
855:     }

['858']

858:     function getQuorumBitmapUpdateByIndex(
859:         bytes32 operatorId, 
860:         uint256 index
861:     ) external view returns (QuorumBitmapUpdate memory) {
862:         return _operatorBitmapHistory[operatorId][index];
863:     }

['866']

866:     function getCurrentQuorumBitmap(bytes32 operatorId) external view returns (uint192) {
867:         return _currentOperatorBitmap(operatorId);
868:     }

['887']

887:     function calculateOperatorChurnApprovalDigestHash(
888:         address registeringOperator,
889:         bytes32 registeringOperatorId,
890:         OperatorKickParam[] memory operatorKickParams,
891:         bytes32 salt,
892:         uint256 expiry
893:     ) public view returns (bytes32) {
894:         
895:         return _hashTypedDataV4(keccak256(abi.encode(OPERATOR_CHURN_APPROVAL_TYPEHASH, registeringOperator, registeringOperatorId, operatorKickParams, salt, expiry)));
896:     }

['547']

547:     function getStakeHistory(
548:         bytes32 operatorId, 
549:         uint8 quorumNumber
550:     ) external view returns (StakeUpdate[] memory) {
551:         return operatorStakeHistory[operatorId][quorumNumber];
552:     }

['558']

558:     function getCurrentStake(bytes32 operatorId, uint8 quorumNumber) external view returns (uint96) {
559:         StakeUpdate memory operatorStakeUpdate = getLatestStakeUpdate(operatorId, quorumNumber);
560:         return operatorStakeUpdate.stake;
561:     }

['588']

588:     function getStakeUpdateAtIndex(
589:         uint8 quorumNumber,
590:         bytes32 operatorId,
591:         uint256 index
592:     ) external view returns (StakeUpdate memory) {
593:         return operatorStakeHistory[operatorId][quorumNumber][index];
594:     }

['597']

597:     function getStakeAtBlockNumber(
598:         bytes32 operatorId,
599:         uint8 quorumNumber,
600:         uint32 blockNumber
601:     ) external view returns (uint96) {
602:         return
603:             operatorStakeHistory[operatorId][quorumNumber][
604:                 _getStakeUpdateIndexForOperatorAtBlockNumber(operatorId, quorumNumber, blockNumber)
605:             ].stake;
606:     }

['609']

609:     function getStakeUpdateIndexAtBlockNumber(
610:         bytes32 operatorId,
611:         uint8 quorumNumber,
612:         uint32 blockNumber
613:     ) external view returns (uint32) {
614:         return _getStakeUpdateIndexForOperatorAtBlockNumber(operatorId, quorumNumber, blockNumber);
615:     }

['627']

627:     function getStakeAtBlockNumberAndIndex(
628:         uint8 quorumNumber,
629:         uint32 blockNumber,
630:         bytes32 operatorId,
631:         uint256 index
632:     ) external view returns (uint96) {
633:         StakeUpdate memory operatorStakeUpdate = operatorStakeHistory[operatorId][quorumNumber][index];
634:         _validateStakeUpdateAtBlockNumber(operatorStakeUpdate, blockNumber);
635:         return operatorStakeUpdate.stake;
636:     }

[NonCritical-53] Use scopes sparingly

Resolution

In Solidity programming, the use of scoped blocks, denoted by {} without a preceding control structure like if, for, etc., allows for the creation of isolated scopes within a function. While this can be useful for managing memory and preventing naming conflicts, it should be used sparingly. Excessive use of these scope blocks can obscure the code's logic flow and make it more difficult to understand, impeding code maintainability. As a best practice, only employ scoped blocks when necessary for memory management or to avoid clear naming conflicts. Otherwise, aim for clarity and simplicity in your code structure for optimal readability and maintainability.

Num of instances: 2

Findings

Click to show findings

['242']

242:         { // <= FOUND
243:             
244:             (bool pairingSuccessful, bool signatureIsValid) = trySignatureAndApkVerification(
245:                 msgHash, 
246:                 apk, 
247:                 params.apkG2, 
248:                 params.sigma
249:             );
250:             require(pairingSuccessful, "BLSSignatureChecker.checkSignatures: pairing precompile call failed");
251:             require(signatureIsValid, "BLSSignatureChecker.checkSignatures: signature is invalid");
252:         }

['184']

184:         { // <= FOUND
185:             bool _staleStakesForbidden = staleStakesForbidden;
186:             uint256 withdrawalDelayBlocks = _staleStakesForbidden ? delegation.minWithdrawalDelayBlocks() : 0;
187: 
188:             for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
189:                 
190:                 
191:                 if (_staleStakesForbidden) { // <= FOUND
192:                     require(
193:                         registryCoordinator.quorumUpdateBlockNumber(uint8(quorumNumbers[i])) + withdrawalDelayBlocks >= referenceBlockNumber,
194:                         "BLSSignatureChecker.checkSignatures: StakeRegistry updates must be within withdrawalDelayBlocks window"
195:                     );
196:                 }

[NonCritical-54] Consider using SMTChecker

Resolution

The SMTChecker is a valuable tool for Solidity developers as it helps detect potential vulnerabilities and logical errors in the contract's code. By utilizing Satisfiability Modulo Theories (SMT) solvers, it can reason about the potential states a contract can be in, and therefore, identify conditions that could lead to undesirable behavior. This automatic formal verification can catch issues that might otherwise be missed in manual code reviews or standard testing, enhancing the overall contract's security and reliability.

Num of instances: 21

Findings

Click to show findings

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import {IRegistry} from "./IRegistry.sol";
5: 
6: /**
7:  * @title Interface for a `Registry`-type contract that keeps track of an ordered list of operators for up to 256 quorums.
8:  * @author Layr Labs, Inc.
9:  */
10: interface IIndexRegistry is IRegistry {
11:     // EVENTS
12:     
13:     // emitted when an operator's index in the ordered operator list for the quorum with number `quorumNumber` is updated
14:     event QuorumIndexUpdate(bytes32 indexed operatorId, uint8 quorumNumber, uint32 newOperatorIndex);
15: 
16:     // DATA STRUCTURES
17: 
18:     // struct used to give definitive ordering to operators at each blockNumber. 
19:     struct OperatorUpdate {
20:         // blockNumber number from which `operatorIndex` was the operators index
21:         // the operator's index is the first entry such that `blockNumber >= entry.fromBlockNumber`
22:         uint32 fromBlockNumber;
23:         // the operator at this index
24:         bytes32 operatorId;
25:     }
26: 
27:     // struct used to denote the number of operators in a quorum at a given blockNumber
28:     struct QuorumUpdate {
29:         // The total number of operators at a `blockNumber` is the first entry such that `blockNumber >= entry.fromBlockNumber`
30:         uint32 fromBlockNumber;
31:         // The number of operators at `fromBlockNumber`
32:         uint32 numOperators;
33:     }
34: 
35:     /**
36:      * @notice Registers the operator with the specified `operatorId` for the quorums specified by `quorumNumbers`.
37:      * @param operatorId is the id of the operator that is being registered
38:      * @param quorumNumbers is the quorum numbers the operator is registered for
39:      * @return numOperatorsPerQuorum is a list of the number of operators (including the registering operator) in each of the quorums the operator is registered for
40:      * @dev access restricted to the RegistryCoordinator
41:      * @dev Preconditions (these are assumed, not validated in this contract):
42:      *         1) `quorumNumbers` has no duplicates
43:      *         2) `quorumNumbers.length` != 0
44:      *         3) `quorumNumbers` is ordered in ascending order
45:      *         4) the operator is not already registered
46:      */
47:     function registerOperator(bytes32 operatorId, bytes calldata quorumNumbers) external returns(uint32[] memory);
48: 
49:     /**
50:      * @notice Deregisters the operator with the specified `operatorId` for the quorums specified by `quorumNumbers`.
51:      * @param operatorId is the id of the operator that is being deregistered
52:      * @param quorumNumbers is the quorum numbers the operator is deregistered for
53:      * @dev access restricted to the RegistryCoordinator
54:      * @dev Preconditions (these are assumed, not validated in this contract):
55:      *         1) `quorumNumbers` has no duplicates
56:      *         2) `quorumNumbers.length` != 0
57:      *         3) `quorumNumbers` is ordered in ascending order
58:      *         4) the operator is not already deregistered
59:      *         5) `quorumNumbers` is a subset of the quorumNumbers that the operator is registered for
60:      */
61:     function deregisterOperator(bytes32 operatorId, bytes calldata quorumNumbers) external;
62: 
63:     /**
64:      * @notice Initialize a quorum by pushing its first quorum update
65:      * @param quorumNumber The number of the new quorum
66:      */
67:     function initializeQuorum(uint8 quorumNumber) external;
68: 
69:     /// @notice Returns the OperatorUpdate entry for the specified `operatorIndex` and `quorumNumber` at the specified `arrayIndex`
70:     function getOperatorUpdateAtIndex(
71:         uint8 quorumNumber,
72:         uint32 operatorIndex,
73:         uint32 arrayIndex
74:     ) external view returns (OperatorUpdate memory);
75: 
76:     /// @notice Returns the QuorumUpdate entry for the specified `quorumNumber` at the specified `quorumIndex`
77:     function getQuorumUpdateAtIndex(uint8 quorumNumber, uint32 quorumIndex) external view returns (QuorumUpdate memory);
78: 
79:     /// @notice Returns the most recent OperatorUpdate entry for the specified quorumNumber and operatorIndex
80:     function getLatestOperatorUpdate(uint8 quorumNumber, uint32 operatorIndex) external view returns (OperatorUpdate memory);
81: 
82:     /// @notice Returns the most recent QuorumUpdate entry for the specified quorumNumber
83:     function getLatestQuorumUpdate(uint8 quorumNumber) external view returns (QuorumUpdate memory);
84: 
85:     /// @notice Returns the current number of operators of this service for `quorumNumber`.
86:     function totalOperatorsForQuorum(uint8 quorumNumber) external view returns (uint32);
87: 
88:     /// @notice Returns an ordered list of operators of the services for the given `quorumNumber` at the given `blockNumber`
89:     function getOperatorListAtBlockNumber(uint8 quorumNumber, uint32 blockNumber) external view returns (bytes32[] memory);
90: }

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import {IBLSApkRegistry} from "./interfaces/IBLSApkRegistry.sol";
5: import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol";
6: import {IIndexRegistry} from "./interfaces/IIndexRegistry.sol";
7: import {IServiceManager} from "./interfaces/IServiceManager.sol";
8: import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol";
9: 
10: abstract contract RegistryCoordinatorStorage is IRegistryCoordinator {
11: 
12:     /*******************************************************************************
13:                                CONSTANTS AND IMMUTABLES 
14:     *******************************************************************************/
15: 
16:     /// @notice The EIP-712 typehash for the `DelegationApproval` struct used by the contract
17:     bytes32 public constant OPERATOR_CHURN_APPROVAL_TYPEHASH =
18:         keccak256("OperatorChurnApproval(address registeringOperator,bytes32 registeringOperatorId,OperatorKickParam[] operatorKickParams,bytes32 salt,uint256 expiry)OperatorKickParam(uint8 quorumNumber,address operator)");
19:     /// @notice The EIP-712 typehash used for registering BLS public keys
20:     bytes32 public constant PUBKEY_REGISTRATION_TYPEHASH = keccak256("BN254PubkeyRegistration(address operator)");
21:     /// @notice The maximum value of a quorum bitmap
22:     uint256 internal constant MAX_QUORUM_BITMAP = type(uint192).max;
23:     /// @notice The basis point denominator
24:     uint16 internal constant BIPS_DENOMINATOR = 10000;
25:     /// @notice Index for flag that pauses operator registration
26:     uint8 internal constant PAUSED_REGISTER_OPERATOR = 0;
27:     /// @notice Index for flag that pauses operator deregistration
28:     uint8 internal constant PAUSED_DEREGISTER_OPERATOR = 1;
29:     /// @notice Index for flag pausing operator stake updates
30:     uint8 internal constant PAUSED_UPDATE_OPERATOR = 2;
31:     /// @notice The maximum number of quorums this contract supports
32:     uint8 internal constant MAX_QUORUM_COUNT = 192;
33: 
34:     /// @notice the ServiceManager for this AVS, which forwards calls onto EigenLayer's core contracts
35:     IServiceManager public immutable serviceManager;
36:     /// @notice the BLS Aggregate Pubkey Registry contract that will keep track of operators' aggregate BLS public keys per quorum
37:     IBLSApkRegistry public immutable blsApkRegistry;
38:     /// @notice the Stake Registry contract that will keep track of operators' stakes
39:     IStakeRegistry public immutable stakeRegistry;
40:     /// @notice the Index Registry contract that will keep track of operators' indexes
41:     IIndexRegistry public immutable indexRegistry;
42: 
43:     /*******************************************************************************
44:                                        STATE 
45:     *******************************************************************************/
46: 
47:     /// @notice the current number of quorums supported by the registry coordinator
48:     uint8 public quorumCount;
49:     /// @notice maps quorum number => operator cap and kick params
50:     mapping(uint8 => OperatorSetParam) internal _quorumParams;
51:     /// @notice maps operator id => historical quorums they registered for
52:     mapping(bytes32 => QuorumBitmapUpdate[]) internal _operatorBitmapHistory;
53:     /// @notice maps operator address => operator id and status
54:     mapping(address => OperatorInfo) internal _operatorInfo;
55:     /// @notice whether the salt has been used for an operator churn approval
56:     mapping(bytes32 => bool) public isChurnApproverSaltUsed;
57:     /// @notice mapping from quorum number to the latest block that all quorums were updated all at once
58:     mapping(uint8 => uint256) public quorumUpdateBlockNumber;
59: 
60:     /// @notice the dynamic-length array of the registries this coordinator is coordinating
61:     address[] public registries;
62:     /// @notice the address of the entity allowed to sign off on operators getting kicked out of the AVS during registration
63:     address public churnApprover;
64:     /// @notice the address of the entity allowed to eject operators from the AVS
65:     address public ejector;
66: 
67:     constructor(
68:         IServiceManager _serviceManager,
69:         IStakeRegistry _stakeRegistry,
70:         IBLSApkRegistry _blsApkRegistry,
71:         IIndexRegistry _indexRegistry
72:     ) {
73:         serviceManager = _serviceManager;
74:         stakeRegistry = _stakeRegistry;
75:         blsApkRegistry = _blsApkRegistry;
76:         indexRegistry = _indexRegistry;
77:     }
78: 
79:     // storage gap for upgradeability
80:     // slither-disable-next-line shadowing-state
81:     uint256[41] private __GAP;
82: }

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import {IBLSApkRegistry} from "./interfaces/IBLSApkRegistry.sol";
5: import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol";
6: 
7: import {Initializable} from "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
8: 
9: import {BN254} from "./libraries/BN254.sol";
10: 
11: abstract contract BLSApkRegistryStorage is Initializable, IBLSApkRegistry {
12:     /// @notice the hash of the zero pubkey aka BN254.G1Point(0,0)
13:     bytes32 internal constant ZERO_PK_HASH = hex"ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5";
14: 
15:     /// @notice the registry coordinator contract
16:     address public immutable registryCoordinator;
17: 
18:     // storage for individual pubkeys
19:     /// @notice maps operator address to pubkey hash
20:     mapping(address => bytes32) public operatorToPubkeyHash;
21:     /// @notice maps pubkey hash to operator address
22:     mapping(bytes32 => address) public pubkeyHashToOperator;
23:     /// @notice maps operator address to pubkeyG1
24:     mapping(address => BN254.G1Point) public operatorToPubkey;
25: 
26:     // storage for aggregate pubkeys (APKs)
27:     /// @notice maps quorumNumber => historical aggregate pubkey updates
28:     mapping(uint8 => ApkUpdate[]) public apkHistory;
29:     /// @notice maps quorumNumber => current aggregate pubkey of quorum
30:     mapping(uint8 => BN254.G1Point) public currentApk;
31: 
32:     constructor(IRegistryCoordinator _registryCoordinator) {
33:         registryCoordinator = address(_registryCoordinator);
34:         // disable initializers so that the implementation contract cannot be initialized
35:         _disableInitializers();
36:     }
37: 
38:     // storage gap for upgradeability
39:     uint256[45] private __GAP;
40: }
41: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import {IRegistryCoordinator} from "./IRegistryCoordinator.sol";
5: import {IBLSApkRegistry} from "./IBLSApkRegistry.sol";
6: import {IStakeRegistry, IDelegationManager} from "./IStakeRegistry.sol";
7: 
8: import {BN254} from "../libraries/BN254.sol";
9: 
10: /**
11:  * @title Used for checking BLS aggregate signatures from the operators of a EigenLayer AVS with the RegistryCoordinator/BLSApkRegistry/StakeRegistry architechture.
12:  * @author Layr Labs, Inc.
13:  * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
14:  * @notice This is the contract for checking the validity of aggregate operator signatures.
15:  */
16: interface IBLSSignatureChecker {
17:     // DATA STRUCTURES
18: 
19:     struct NonSignerStakesAndSignature {
20:         uint32[] nonSignerQuorumBitmapIndices; // is the indices of all nonsigner quorum bitmaps
21:         BN254.G1Point[] nonSignerPubkeys; // is the G1 pubkeys of all nonsigners
22:         BN254.G1Point[] quorumApks; // is the aggregate G1 pubkey of each quorum
23:         BN254.G2Point apkG2; // is the aggregate G2 pubkey of all signers
24:         BN254.G1Point sigma; // is the aggregate G1 signature of all signers
25:         uint32[] quorumApkIndices; // is the indices of each quorum aggregate pubkey
26:         uint32[] totalStakeIndices; // is the indices of each quorums total stake
27:         uint32[][] nonSignerStakeIndices; // is the indices of each non signers stake within a quorum
28:     }
29: 
30:     /**
31:      * @notice this data structure is used for recording the details on the total stake of the registered
32:      * operators and those operators who are part of the quorum for a particular taskNumber
33:      */
34: 
35:     struct QuorumStakeTotals {
36:         // total stake of the operators in each quorum
37:         uint96[] signedStakeForQuorum;
38:         // total amount staked by all operators in each quorum
39:         uint96[] totalStakeForQuorum;
40:     }
41: 
42:     // EVENTS
43: 
44:     /// @notice Emitted when `staleStakesForbiddenUpdate` is set
45:     event StaleStakesForbiddenUpdate(bool value);   
46:     
47:     // CONSTANTS & IMMUTABLES
48: 
49:     function registryCoordinator() external view returns (IRegistryCoordinator);
50:     function stakeRegistry() external view returns (IStakeRegistry);
51:     function blsApkRegistry() external view returns (IBLSApkRegistry);
52:     function delegation() external view returns (IDelegationManager);
53: 
54:     /**
55:      * @notice This function is called by disperser when it has aggregated all the signatures of the operators
56:      * that are part of the quorum for a particular taskNumber and is asserting them into onchain. The function
57:      * checks that the claim for aggregated signatures are valid.
58:      *
59:      * The thesis of this procedure entails:
60:      * - getting the aggregated pubkey of all registered nodes at the time of pre-commit by the
61:      * disperser (represented by apk in the parameters),
62:      * - subtracting the pubkeys of all the signers not in the quorum (nonSignerPubkeys) and storing 
63:      * the output in apk to get aggregated pubkey of all operators that are part of quorum.
64:      * - use this aggregated pubkey to verify the aggregated signature under BLS scheme.
65:      * 
66:      * @dev Before signature verification, the function verifies operator stake information.  This includes ensuring that the provided `referenceBlockNumber`
67:      * is correct, i.e., ensure that the stake returned from the specified block number is recent enough and that the stake is either the most recent update
68:      * for the total stake (or the operator) or latest before the referenceBlockNumber.
69:      */
70:     function checkSignatures(
71:         bytes32 msgHash, 
72:         bytes calldata quorumNumbers,
73:         uint32 referenceBlockNumber, 
74:         NonSignerStakesAndSignature memory nonSignerStakesAndSignature
75:     ) 
76:         external 
77:         view
78:         returns (
79:             QuorumStakeTotals memory,
80:             bytes32
81:         );
82: }
83: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import {IBLSApkRegistry} from "./IBLSApkRegistry.sol";
5: import {IStakeRegistry} from "./IStakeRegistry.sol";
6: import {IIndexRegistry} from "./IIndexRegistry.sol";
7: import {BN254} from "../libraries/BN254.sol";
8: 
9: /**
10:  * @title Interface for a contract that coordinates between various registries for an AVS.
11:  * @author Layr Labs, Inc.
12:  */
13: interface IRegistryCoordinator {
14:     // EVENTS
15: 
16:     /// Emits when an operator is registered
17:     event OperatorRegistered(address indexed operator, bytes32 indexed operatorId);
18: 
19:     /// Emits when an operator is deregistered
20:     event OperatorDeregistered(address indexed operator, bytes32 indexed operatorId);
21: 
22:     event OperatorSetParamsUpdated(uint8 indexed quorumNumber, OperatorSetParam operatorSetParams);
23: 
24:     event ChurnApproverUpdated(address prevChurnApprover, address newChurnApprover);
25: 
26:     event EjectorUpdated(address prevEjector, address newEjector);
27: 
28:     /// @notice emitted when all the operators for a quorum are updated at once
29:     event QuorumBlockNumberUpdated(uint8 indexed quorumNumber, uint256 blocknumber);
30: 
31:     // DATA STRUCTURES
32:     enum OperatorStatus
33:     {
34:         // default is NEVER_REGISTERED
35:         NEVER_REGISTERED,
36:         REGISTERED,
37:         DEREGISTERED
38:     }
39: 
40:     // STRUCTS
41: 
42:     /**
43:      * @notice Data structure for storing info on operators
44:      */
45:     struct OperatorInfo {
46:         // the id of the operator, which is likely the keccak256 hash of the operator's public key if using BLSRegistry
47:         bytes32 operatorId;
48:         // indicates whether the operator is actively registered for serving the middleware or not
49:         OperatorStatus status;
50:     }
51: 
52:     /**
53:      * @notice Data structure for storing info on quorum bitmap updates where the `quorumBitmap` is the bitmap of the 
54:      * quorums the operator is registered for starting at (inclusive)`updateBlockNumber` and ending at (exclusive) `nextUpdateBlockNumber`
55:      * @dev nextUpdateBlockNumber is initialized to 0 for the latest update
56:      */
57:     struct QuorumBitmapUpdate {
58:         uint32 updateBlockNumber;
59:         uint32 nextUpdateBlockNumber;
60:         uint192 quorumBitmap;
61:     }
62: 
63:     /**
64:      * @notice Data structure for storing operator set params for a given quorum. Specifically the 
65:      * `maxOperatorCount` is the maximum number of operators that can be registered for the quorum,
66:      * `kickBIPsOfOperatorStake` is the basis points of a new operator needs to have of an operator they are trying to kick from the quorum,
67:      * and `kickBIPsOfTotalStake` is the basis points of the total stake of the quorum that an operator needs to be below to be kicked.
68:      */ 
69:      struct OperatorSetParam {
70:         uint32 maxOperatorCount;
71:         uint16 kickBIPsOfOperatorStake;
72:         uint16 kickBIPsOfTotalStake;
73:     }
74: 
75:     /**
76:      * @notice Data structure for the parameters needed to kick an operator from a quorum with number `quorumNumber`, used during registration churn.
77:      * `operator` is the address of the operator to kick
78:      */
79:     struct OperatorKickParam {
80:         uint8 quorumNumber;
81:         address operator;
82:     }
83: 
84:     /// @notice Returns the operator set params for the given `quorumNumber`
85:     function getOperatorSetParams(uint8 quorumNumber) external view returns (OperatorSetParam memory);
86:     /// @notice the Stake registry contract that will keep track of operators' stakes
87:     function stakeRegistry() external view returns (IStakeRegistry);
88:     /// @notice the BLS Aggregate Pubkey Registry contract that will keep track of operators' BLS aggregate pubkeys per quorum
89:     function blsApkRegistry() external view returns (IBLSApkRegistry);
90:     /// @notice the index Registry contract that will keep track of operators' indexes
91:     function indexRegistry() external view returns (IIndexRegistry);
92: 
93:     /**
94:      * @notice Ejects the provided operator from the provided quorums from the AVS
95:      * @param operator is the operator to eject
96:      * @param quorumNumbers are the quorum numbers to eject the operator from
97:      */
98:     function ejectOperator(
99:         address operator, 
100:         bytes calldata quorumNumbers
101:     ) external;
102: 
103:     /// @notice Returns the number of quorums the registry coordinator has created
104:     function quorumCount() external view returns (uint8);
105: 
106:     /// @notice Returns the operator struct for the given `operator`
107:     function getOperator(address operator) external view returns (OperatorInfo memory);
108: 
109:     /// @notice Returns the operatorId for the given `operator`
110:     function getOperatorId(address operator) external view returns (bytes32);
111: 
112:     /// @notice Returns the operator address for the given `operatorId`
113:     function getOperatorFromId(bytes32 operatorId) external view returns (address operator);
114: 
115:     /// @notice Returns the status for the given `operator`
116:     function getOperatorStatus(address operator) external view returns (IRegistryCoordinator.OperatorStatus);
117: 
118:     /// @notice Returns the indices of the quorumBitmaps for the provided `operatorIds` at the given `blockNumber`
119:     function getQuorumBitmapIndicesAtBlockNumber(uint32 blockNumber, bytes32[] memory operatorIds) external view returns (uint32[] memory);
120: 
121:     /**
122:      * @notice Returns the quorum bitmap for the given `operatorId` at the given `blockNumber` via the `index`
123:      * @dev reverts if `index` is incorrect 
124:      */ 
125:     function getQuorumBitmapAtBlockNumberByIndex(bytes32 operatorId, uint32 blockNumber, uint256 index) external view returns (uint192);
126: 
127:     /// @notice Returns the `index`th entry in the operator with `operatorId`'s bitmap history
128:     function getQuorumBitmapUpdateByIndex(bytes32 operatorId, uint256 index) external view returns (QuorumBitmapUpdate memory);
129: 
130:     /// @notice Returns the current quorum bitmap for the given `operatorId`
131:     function getCurrentQuorumBitmap(bytes32 operatorId) external view returns (uint192);
132: 
133:     /// @notice Returns the length of the quorum bitmap history for the given `operatorId`
134:     function getQuorumBitmapHistoryLength(bytes32 operatorId) external view returns (uint256);
135: 
136:     /// @notice Returns the registry at the desired index
137:     function registries(uint256) external view returns (address);
138: 
139:     /// @notice Returns the number of registries
140:     function numRegistries() external view returns (uint256);
141: 
142:     /**
143:      * @notice Returns the message hash that an operator must sign to register their BLS public key.
144:      * @param operator is the address of the operator registering their BLS public key
145:      */
146:     function pubkeyRegistrationMessageHash(address operator) external view returns (BN254.G1Point memory);
147: 
148:     /// @notice returns the blocknumber the quorum was last updated all at once for all operators
149:     function quorumUpdateBlockNumber(uint8 quorumNumber) external view returns (uint256);
150: 
151:     /// @notice The owner of the registry coordinator
152:     function owner() external view returns (address);
153: }
154: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol";
5: import {IBLSApkRegistry} from "./interfaces/IBLSApkRegistry.sol";
6: import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol";
7: import {IIndexRegistry} from "./interfaces/IIndexRegistry.sol";
8: 
9: import {BitmapUtils} from "./libraries/BitmapUtils.sol";
10: 
11: /**
12:  * @title OperatorStateRetriever with view functions that allow to retrieve the state of an AVSs registry system.
13:  * @author Layr Labs Inc.
14:  */
15: contract OperatorStateRetriever {
16:     struct Operator {
17:         address operator;
18:         bytes32 operatorId;
19:         uint96 stake;
20:     }
21: 
22:     struct CheckSignaturesIndices {
23:         uint32[] nonSignerQuorumBitmapIndices;
24:         uint32[] quorumApkIndices;
25:         uint32[] totalStakeIndices;  
26:         uint32[][] nonSignerStakeIndices; // nonSignerStakeIndices[quorumNumberIndex][nonSignerIndex]
27:     }
28: 
29:     /**
30:      * @notice This function is intended to to be called by AVS operators every time a new task is created (i.e.)
31:      * the AVS coordinator makes a request to AVS operators. Since all of the crucial information is kept onchain, 
32:      * operators don't need to run indexers to fetch the data.
33:      * @param registryCoordinator is the registry coordinator to fetch the AVS registry information from
34:      * @param operatorId the id of the operator to fetch the quorums lists 
35:      * @param blockNumber is the block number to get the operator state for
36:      * @return 1) the quorumBitmap of the operator at the given blockNumber
37:      *         2) 2d array of Operator structs. For each quorum the provided operator 
38:      *            was a part of at `blockNumber`, an ordered list of operators.
39:      */
40:     function getOperatorState(
41:         IRegistryCoordinator registryCoordinator, 
42:         bytes32 operatorId, 
43:         uint32 blockNumber
44:     ) external view returns (uint256, Operator[][] memory) {
45:         bytes32[] memory operatorIds = new bytes32[](1);
46:         operatorIds[0] = operatorId;
47:         uint256 index = registryCoordinator.getQuorumBitmapIndicesAtBlockNumber(blockNumber, operatorIds)[0];
48:     
49:         uint256 quorumBitmap = registryCoordinator.getQuorumBitmapAtBlockNumberByIndex(operatorId, blockNumber, index);
50: 
51:         bytes memory quorumNumbers = BitmapUtils.bitmapToBytesArray(quorumBitmap);
52: 
53:         return (quorumBitmap, getOperatorState(registryCoordinator, quorumNumbers, blockNumber));
54:     }
55: 
56:     /**
57:      * @notice returns the ordered list of operators (id and stake) for each quorum. The AVS coordinator 
58:      * may call this function directly to get the operator state for a given block number
59:      * @param registryCoordinator is the registry coordinator to fetch the AVS registry information from
60:      * @param quorumNumbers are the ids of the quorums to get the operator state for
61:      * @param blockNumber is the block number to get the operator state for
62:      * @return 2d array of Operators. For each quorum, an ordered list of Operators
63:      */
64:     function getOperatorState(
65:         IRegistryCoordinator registryCoordinator, 
66:         bytes memory quorumNumbers, 
67:         uint32 blockNumber
68:     ) public view returns(Operator[][] memory) {
69:         IStakeRegistry stakeRegistry = registryCoordinator.stakeRegistry();
70:         IIndexRegistry indexRegistry = registryCoordinator.indexRegistry();
71:         IBLSApkRegistry blsApkRegistry = registryCoordinator.blsApkRegistry();
72: 
73:         Operator[][] memory operators = new Operator[][](quorumNumbers.length);
74:         for (uint256 i = 0; i < quorumNumbers.length; i++) {
75:             uint8 quorumNumber = uint8(quorumNumbers[i]);
76:             bytes32[] memory operatorIds = indexRegistry.getOperatorListAtBlockNumber(quorumNumber, blockNumber);
77:             operators[i] = new Operator[](operatorIds.length);
78:             for (uint256 j = 0; j < operatorIds.length; j++) {
79:                 operators[i][j] = Operator({
80:                     operator: blsApkRegistry.getOperatorFromPubkeyHash(operatorIds[j]),
81:                     operatorId: bytes32(operatorIds[j]),
82:                     stake: stakeRegistry.getStakeAtBlockNumber(bytes32(operatorIds[j]), quorumNumber, blockNumber)
83:                 });
84:             }
85:         }
86:             
87:         return operators;
88:     }
89: 
90:     /**
91:      * @notice this is called by the AVS operator to get the relevant indices for the checkSignatures function
92:      * if they are not running an indexer    
93:      * @param registryCoordinator is the registry coordinator to fetch the AVS registry information from
94:      * @param referenceBlockNumber is the block number to get the indices for
95:      * @param quorumNumbers are the ids of the quorums to get the operator state for
96:      * @param nonSignerOperatorIds are the ids of the nonsigning operators
97:      * @return 1) the indices of the quorumBitmaps for each of the operators in the @param nonSignerOperatorIds array at the given blocknumber
98:      *         2) the indices of the total stakes entries for the given quorums at the given blocknumber
99:      *         3) the indices of the stakes of each of the nonsigners in each of the quorums they were a 
100:      *            part of (for each nonsigner, an array of length the number of quorums they were a part of
101:      *            that are also part of the provided quorumNumbers) at the given blocknumber
102:      *         4) the indices of the quorum apks for each of the provided quorums at the given blocknumber
103:      */
104:     function getCheckSignaturesIndices(
105:         IRegistryCoordinator registryCoordinator,
106:         uint32 referenceBlockNumber, 
107:         bytes calldata quorumNumbers, 
108:         bytes32[] calldata nonSignerOperatorIds
109:     ) external view returns (CheckSignaturesIndices memory) {
110:         IStakeRegistry stakeRegistry = registryCoordinator.stakeRegistry();
111:         CheckSignaturesIndices memory checkSignaturesIndices;
112: 
113:         // get the indices of the quorumBitmap updates for each of the operators in the nonSignerOperatorIds array
114:         checkSignaturesIndices.nonSignerQuorumBitmapIndices = registryCoordinator.getQuorumBitmapIndicesAtBlockNumber(referenceBlockNumber, nonSignerOperatorIds);
115: 
116:         // get the indices of the totalStake updates for each of the quorums in the quorumNumbers array
117:         checkSignaturesIndices.totalStakeIndices = stakeRegistry.getTotalStakeIndicesAtBlockNumber(referenceBlockNumber, quorumNumbers);
118:         
119:         checkSignaturesIndices.nonSignerStakeIndices = new uint32[][](quorumNumbers.length);
120:         for (uint8 quorumNumberIndex = 0; quorumNumberIndex < quorumNumbers.length; quorumNumberIndex++) {
121:             uint256 numNonSignersForQuorum = 0;
122:             // this array's length will be at most the number of nonSignerOperatorIds, this will be trimmed after it is filled
123:             checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex] = new uint32[](nonSignerOperatorIds.length);
124: 
125:             for (uint i = 0; i < nonSignerOperatorIds.length; i++) {
126:                 // get the quorumBitmap for the operator at the given blocknumber and index
127:                 uint192 nonSignerQuorumBitmap = 
128:                     registryCoordinator.getQuorumBitmapAtBlockNumberByIndex(
129:                         nonSignerOperatorIds[i], 
130:                         referenceBlockNumber, 
131:                         checkSignaturesIndices.nonSignerQuorumBitmapIndices[i]
132:                     );
133:                 
134:                 require(nonSignerQuorumBitmap != 0, "OperatorStateRetriever.getCheckSignaturesIndices: operator must be registered at blocknumber");
135:                 
136:                 // if the operator was a part of the quorum and the quorum is a part of the provided quorumNumbers
137:                 if ((nonSignerQuorumBitmap >> uint8(quorumNumbers[quorumNumberIndex])) & 1 == 1) {
138:                     // get the index of the stake update for the operator at the given blocknumber and quorum number
139:                     checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex][numNonSignersForQuorum] = stakeRegistry.getStakeUpdateIndexAtBlockNumber(
140:                         nonSignerOperatorIds[i],
141:                         uint8(quorumNumbers[quorumNumberIndex]),
142:                         referenceBlockNumber
143:                     );
144:                     numNonSignersForQuorum++;
145:                 }
146:             }
147: 
148:             // resize the array to the number of nonSigners for this quorum
149:             uint32[] memory nonSignerStakeIndicesForQuorum = new uint32[](numNonSignersForQuorum);
150:             for (uint i = 0; i < numNonSignersForQuorum; i++) {
151:                 nonSignerStakeIndicesForQuorum[i] = checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex][i];
152:             }
153:             checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex] = nonSignerStakeIndicesForQuorum;
154:         }
155: 
156:         IBLSApkRegistry blsApkRegistry = registryCoordinator.blsApkRegistry();
157:         // get the indices of the quorum apks for each of the provided quorums at the given blocknumber
158:         checkSignaturesIndices.quorumApkIndices = blsApkRegistry.getApkIndicesAtBlockNumber(quorumNumbers, referenceBlockNumber);
159: 
160:         return checkSignaturesIndices;
161:     }
162: }
163: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity >=0.5.0;
3: 
4: /**
5:  * @title Minimal interface for a `Registry`-type contract.
6:  * @author Layr Labs, Inc.
7:  * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
8:  * @notice Functions related to the registration process itself have been intentionally excluded
9:  * because their function signatures may vary significantly.
10:  */
11: interface IRegistry {
12:     function registryCoordinator() external view returns (address);
13: }
14: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import {IRegistry} from "./IRegistry.sol";
5: 
6: import {BN254} from "../libraries/BN254.sol";
7: 
8: /**
9:  * @title Minimal interface for a registry that keeps track of aggregate operator public keys across many quorums.
10:  * @author Layr Labs, Inc.
11:  */
12: interface IBLSApkRegistry is IRegistry {
13:     // STRUCTS
14:     /// @notice Data structure used to track the history of the Aggregate Public Key of all operators
15:     struct ApkUpdate {
16:         // first 24 bytes of keccak256(apk_x0, apk_x1, apk_y0, apk_y1)
17:         bytes24 apkHash;
18:         // block number at which the update occurred
19:         uint32 updateBlockNumber;
20:         // block number at which the next update occurred
21:         uint32 nextUpdateBlockNumber;
22:     }
23: 
24:     /**
25:      * @notice Struct used when registering a new public key
26:      * @param signedMessageHash is the registration message hash signed by the private key of the operator
27:      * @param pubkeyG1 is the corresponding G1 public key of the operator 
28:      * @param pubkeyG2 is the corresponding G2 public key of the operator
29:      */     
30:     struct PubkeyRegistrationParams {
31:         BN254.G1Point pubkeyRegistrationSignature;
32:         BN254.G1Point pubkeyG1;
33:         BN254.G2Point pubkeyG2;
34:     }
35: 
36:     // EVENTS
37:     /// @notice Emitted when `operator` registers with the public keys `pubkeyG1` and `pubkeyG2`.
38:     event NewPubkeyRegistration(address indexed operator, BN254.G1Point pubkeyG1, BN254.G2Point pubkeyG2);
39: 
40:     // @notice Emitted when a new operator pubkey is registered for a set of quorums
41:     event OperatorAddedToQuorums(
42:         address operator,
43:         bytes quorumNumbers
44:     );
45: 
46:     // @notice Emitted when an operator pubkey is removed from a set of quorums
47:     event OperatorRemovedFromQuorums(
48:         address operator, 
49:         bytes quorumNumbers
50:     );
51: 
52:     /**
53:      * @notice Registers the `operator`'s pubkey for the specified `quorumNumbers`.
54:      * @param operator The address of the operator to register.
55:      * @param quorumNumbers The quorum numbers the operator is registering for, where each byte is an 8 bit integer quorumNumber.
56:      * @dev access restricted to the RegistryCoordinator
57:      * @dev Preconditions (these are assumed, not validated in this contract):
58:      *         1) `quorumNumbers` has no duplicates
59:      *         2) `quorumNumbers.length` != 0
60:      *         3) `quorumNumbers` is ordered in ascending order
61:      *         4) the operator is not already registered
62:      */
63:     function registerOperator(address operator, bytes calldata quorumNumbers) external;
64: 
65:     /**
66:      * @notice Deregisters the `operator`'s pubkey for the specified `quorumNumbers`.
67:      * @param operator The address of the operator to deregister.
68:      * @param quorumNumbers The quorum numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber.
69:      * @dev access restricted to the RegistryCoordinator
70:      * @dev Preconditions (these are assumed, not validated in this contract):
71:      *         1) `quorumNumbers` has no duplicates
72:      *         2) `quorumNumbers.length` != 0
73:      *         3) `quorumNumbers` is ordered in ascending order
74:      *         4) the operator is not already deregistered
75:      *         5) `quorumNumbers` is a subset of the quorumNumbers that the operator is registered for
76:      */ 
77:     function deregisterOperator(address operator, bytes calldata quorumNumbers) external;
78:     
79:     /**
80:      * @notice Initializes a new quorum by pushing its first apk update
81:      * @param quorumNumber The number of the new quorum
82:      */
83:     function initializeQuorum(uint8 quorumNumber) external;
84: 
85:     /**
86:      * @notice mapping from operator address to pubkey hash.
87:      * Returns *zero* if the `operator` has never registered, and otherwise returns the hash of the public key of the operator.
88:      */
89:     function operatorToPubkeyHash(address operator) external view returns (bytes32);
90: 
91:     /**
92:      * @notice mapping from pubkey hash to operator address.
93:      * Returns *zero* if no operator has ever registered the public key corresponding to `pubkeyHash`,
94:      * and otherwise returns the (unique) registered operator who owns the BLS public key that is the preimage of `pubkeyHash`.
95:      */
96:     function pubkeyHashToOperator(bytes32 pubkeyHash) external view returns (address);
97: 
98:     /**
99:      * @notice Called by the RegistryCoordinator register an operator as the owner of a BLS public key.
100:      * @param operator is the operator for whom the key is being registered
101:      * @param params contains the G1 & G2 public keys of the operator, and a signature proving their ownership
102:      * @param pubkeyRegistrationMessageHash is a hash that the operator must sign to prove key ownership
103:      */
104:     function registerBLSPublicKey(
105:         address operator,
106:         PubkeyRegistrationParams calldata params,
107:         BN254.G1Point calldata pubkeyRegistrationMessageHash
108:     ) external returns (bytes32 operatorId);
109: 
110:     /**
111:      * @notice Returns the pubkey and pubkey hash of an operator
112:      * @dev Reverts if the operator has not registered a valid pubkey
113:      */
114:     function getRegisteredPubkey(address operator) external view returns (BN254.G1Point memory, bytes32);
115: 
116:     /// @notice Returns the current APK for the provided `quorumNumber `
117:     function getApk(uint8 quorumNumber) external view returns (BN254.G1Point memory);
118: 
119:     /// @notice Returns the index of the quorumApk index at `blockNumber` for the provided `quorumNumber`
120:     function getApkIndicesAtBlockNumber(bytes calldata quorumNumbers, uint256 blockNumber) external view returns(uint32[] memory);
121: 
122:     /// @notice Returns the `ApkUpdate` struct at `index` in the list of APK updates for the `quorumNumber`
123:     function getApkUpdateAtIndex(uint8 quorumNumber, uint256 index) external view returns (ApkUpdate memory);
124: 
125:     /// @notice Returns the operator address for the given `pubkeyHash`
126:     function getOperatorFromPubkeyHash(bytes32 pubkeyHash) external view returns (address);
127: 
128:     /**
129:      * @notice get 24 byte hash of the apk of `quorumNumber` at `blockNumber` using the provided `index`;
130:      * called by checkSignatures in BLSSignatureChecker.sol.
131:      * @param quorumNumber is the quorum whose ApkHash is being retrieved
132:      * @param blockNumber is the number of the block for which the latest ApkHash will be retrieved
133:      * @param index is the index of the apkUpdate being retrieved from the list of quorum apkUpdates in storage
134:      */
135:     function getApkHashAtBlockNumberAndIndex(uint8 quorumNumber, uint32 blockNumber, uint256 index) external view returns (bytes24);
136: 
137:     /// @notice returns the ID used to identify the `operator` within this AVS.
138:     /// @dev Returns zero in the event that the `operator` has never registered for the AVS
139:     function getOperatorId(address operator) external view returns (bytes32);
140: }
141: 

['1']

1: // SPDX-License-Identifier: MIT
2: // several functions are taken or adapted from https://github.com/HarryR/solcrypto/blob/master/contracts/altbn128.sol (MIT license):
3: // Copyright 2017 Christian Reitwiessner
4: // Permission is hereby granted, free of charge, to any person obtaining a copy
5: // of this software and associated documentation files (the "Software"), to
6: // deal in the Software without restriction, including without limitation the
7: // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8: // sell copies of the Software, and to permit persons to whom the Software is
9: // furnished to do so, subject to the following conditions:
10: // The above copyright notice and this permission notice shall be included in
11: // all copies or substantial portions of the Software.
12: // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13: // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14: // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
15: // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
16: // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
17: // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
18: // IN THE SOFTWARE.
19: 
20: // The remainder of the code in this library is written by LayrLabs Inc. and is also under an MIT license
21: 
22: pragma solidity =0.8.12;
23: 
24: /**
25:  * @title Library for operations on the BN254 elliptic curve.
26:  * @author Layr Labs, Inc.
27:  * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
28:  * @notice Contains BN254 parameters, common operations (addition, scalar mul, pairing), and BLS signature functionality.
29:  */
30: library BN254 {
31:     // modulus for the underlying field F_p of the elliptic curve
32:     uint256 internal constant FP_MODULUS =
33:         21888242871839275222246405745257275088696311157297823662689037894645226208583;
34:     // modulus for the underlying field F_r of the elliptic curve
35:     uint256 internal constant FR_MODULUS =
36:         21888242871839275222246405745257275088548364400416034343698204186575808495617;
37: 
38:     struct G1Point {
39:         uint256 X;
40:         uint256 Y;
41:     }
42: 
43:     // Encoding of field elements is: X[1] * i + X[0]
44:     struct G2Point {
45:         uint256[2] X;
46:         uint256[2] Y;
47:     }
48: 
49:     function generatorG1() internal pure returns (G1Point memory) {
50:         return G1Point(1, 2);
51:     }
52: 
53:     // generator of group G2
54:     /// @dev Generator point in F_q2 is of the form: (x0 + ix1, y0 + iy1).
55:     uint256 internal constant G2x1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634;
56:     uint256 internal constant G2x0 = 10857046999023057135944570762232829481370756359578518086990519993285655852781;
57:     uint256 internal constant G2y1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531;
58:     uint256 internal constant G2y0 = 8495653923123431417604973247489272438418190587263600148770280649306958101930;
59: 
60:     /// @notice returns the G2 generator
61:     /// @dev mind the ordering of the 1s and 0s!
62:     ///      this is because of the (unknown to us) convention used in the bn254 pairing precompile contract
63:     ///      "Elements a * i + b of F_p^2 are encoded as two elements of F_p, (a, b)."
64:     ///      https://github.com/ethereum/EIPs/blob/master/EIPS/eip-197.md#encoding
65:     function generatorG2() internal pure returns (G2Point memory) {
66:         return G2Point([G2x1, G2x0], [G2y1, G2y0]);
67:     }
68: 
69:     // negation of the generator of group G2
70:     /// @dev Generator point in F_q2 is of the form: (x0 + ix1, y0 + iy1).
71:     uint256 internal constant nG2x1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634;
72:     uint256 internal constant nG2x0 = 10857046999023057135944570762232829481370756359578518086990519993285655852781;
73:     uint256 internal constant nG2y1 = 17805874995975841540914202342111839520379459829704422454583296818431106115052;
74:     uint256 internal constant nG2y0 = 13392588948715843804641432497768002650278120570034223513918757245338268106653;
75: 
76:     function negGeneratorG2() internal pure returns (G2Point memory) {
77:         return G2Point([nG2x1, nG2x0], [nG2y1, nG2y0]);
78:     }
79: 
80:     bytes32 internal constant powersOfTauMerkleRoot =
81:         0x22c998e49752bbb1918ba87d6d59dd0e83620a311ba91dd4b2cc84990b31b56f;
82: 
83:     /**
84:      * @param p Some point in G1.
85:      * @return The negation of `p`, i.e. p.plus(p.negate()) should be zero.
86:      */
87:     function negate(G1Point memory p) internal pure returns (G1Point memory) {
88:         // The prime q in the base field F_q for G1
89:         if (p.X == 0 && p.Y == 0) {
90:             return G1Point(0, 0);
91:         } else {
92:             return G1Point(p.X, FP_MODULUS - (p.Y % FP_MODULUS));
93:         }
94:     }
95: 
96:     /**
97:      * @return r the sum of two points of G1
98:      */
99:     function plus(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) {
100:         uint256[4] memory input;
101:         input[0] = p1.X;
102:         input[1] = p1.Y;
103:         input[2] = p2.X;
104:         input[3] = p2.Y;
105:         bool success;
106: 
107:         // solium-disable-next-line security/no-inline-assembly
108:         assembly {
109:             success := staticcall(sub(gas(), 2000), 6, input, 0x80, r, 0x40)
110:             // Use "invalid" to make gas estimation work
111:             switch success
112:             case 0 {
113:                 invalid()
114:             }
115:         }
116: 
117:         require(success, "ec-add-failed");
118:     }
119: 
120:     /**
121:      * @notice an optimized ecMul implementation that takes O(log_2(s)) ecAdds
122:      * @param p the point to multiply
123:      * @param s the scalar to multiply by
124:      * @dev this function is only safe to use if the scalar is 9 bits or less
125:      */ 
126:     function scalar_mul_tiny(BN254.G1Point memory p, uint16 s) internal view returns (BN254.G1Point memory) {
127:         require(s < 2**9, "scalar-too-large");
128: 
129:         // if s is 1 return p
130:         if(s == 1) {
131:             return p;
132:         }
133: 
134:         // the accumulated product to return
135:         BN254.G1Point memory acc = BN254.G1Point(0, 0);
136:         // the 2^n*p to add to the accumulated product in each iteration
137:         BN254.G1Point memory p2n = p;
138:         // value of most significant bit
139:         uint16 m = 1;
140:         // index of most significant bit
141:         uint8 i = 0;
142: 
143:         //loop until we reach the most significant bit
144:         while(s >= m){
145:             unchecked {
146:                 // if the  current bit is 1, add the 2^n*p to the accumulated product
147:                 if ((s >> i) & 1 == 1) {
148:                     acc = plus(acc, p2n);
149:                 }
150:                 // double the 2^n*p for the next iteration
151:                 p2n = plus(p2n, p2n);
152: 
153:                 // increment the index and double the value of the most significant bit
154:                 m <<= 1;
155:                 ++i;
156:             }
157:         }
158:         
159:         // return the accumulated product
160:         return acc;
161:     }
162: 
163:     /**
164:      * @return r the product of a point on G1 and a scalar, i.e.
165:      *         p == p.scalar_mul(1) and p.plus(p) == p.scalar_mul(2) for all
166:      *         points p.
167:      */
168:     function scalar_mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) {
169:         uint256[3] memory input;
170:         input[0] = p.X;
171:         input[1] = p.Y;
172:         input[2] = s;
173:         bool success;
174:         // solium-disable-next-line security/no-inline-assembly
175:         assembly {
176:             success := staticcall(sub(gas(), 2000), 7, input, 0x60, r, 0x40)
177:             // Use "invalid" to make gas estimation work
178:             switch success
179:             case 0 {
180:                 invalid()
181:             }
182:         }
183:         require(success, "ec-mul-failed");
184:     }
185: 
186:     /**
187:      *  @return The result of computing the pairing check
188:      *         e(p1[0], p2[0]) *  .... * e(p1[n], p2[n]) == 1
189:      *         For example,
190:      *         pairing([P1(), P1().negate()], [P2(), P2()]) should return true.
191:      */
192:     function pairing(
193:         G1Point memory a1,
194:         G2Point memory a2,
195:         G1Point memory b1,
196:         G2Point memory b2
197:     ) internal view returns (bool) {
198:         G1Point[2] memory p1 = [a1, b1];
199:         G2Point[2] memory p2 = [a2, b2];
200: 
201:         uint256[12] memory input;
202: 
203:         for (uint256 i = 0; i < 2; i++) {
204:             uint256 j = i * 6;
205:             input[j + 0] = p1[i].X;
206:             input[j + 1] = p1[i].Y;
207:             input[j + 2] = p2[i].X[0];
208:             input[j + 3] = p2[i].X[1];
209:             input[j + 4] = p2[i].Y[0];
210:             input[j + 5] = p2[i].Y[1];
211:         }
212: 
213:         uint256[1] memory out;
214:         bool success;
215: 
216:         // solium-disable-next-line security/no-inline-assembly
217:         assembly {
218:             success := staticcall(sub(gas(), 2000), 8, input, mul(12, 0x20), out, 0x20)
219:             // Use "invalid" to make gas estimation work
220:             switch success
221:             case 0 {
222:                 invalid()
223:             }
224:         }
225: 
226:         require(success, "pairing-opcode-failed");
227: 
228:         return out[0] != 0;
229:     }
230: 
231:     /**
232:      * @notice This function is functionally the same as pairing(), however it specifies a gas limit
233:      *         the user can set, as a precompile may use the entire gas budget if it reverts.
234:      */
235:     function safePairing(
236:         G1Point memory a1,
237:         G2Point memory a2,
238:         G1Point memory b1,
239:         G2Point memory b2,
240:         uint256 pairingGas
241:     ) internal view returns (bool, bool) {
242:         G1Point[2] memory p1 = [a1, b1];
243:         G2Point[2] memory p2 = [a2, b2];
244: 
245:         uint256[12] memory input;
246: 
247:         for (uint256 i = 0; i < 2; i++) {
248:             uint256 j = i * 6;
249:             input[j + 0] = p1[i].X;
250:             input[j + 1] = p1[i].Y;
251:             input[j + 2] = p2[i].X[0];
252:             input[j + 3] = p2[i].X[1];
253:             input[j + 4] = p2[i].Y[0];
254:             input[j + 5] = p2[i].Y[1];
255:         }
256: 
257:         uint256[1] memory out;
258:         bool success;
259: 
260:         // solium-disable-next-line security/no-inline-assembly
261:         assembly {
262:             success := staticcall(pairingGas, 8, input, mul(12, 0x20), out, 0x20)
263:         }
264: 
265:         //Out is the output of the pairing precompile, either 0 or 1 based on whether the two pairings are equal.
266:         //Success is true if the precompile actually goes through (aka all inputs are valid)
267: 
268:         return (success, out[0] != 0);
269:     }
270: 
271:     /// @return hashedG1 the keccak256 hash of the G1 Point
272:     /// @dev used for BLS signatures
273:     function hashG1Point(BN254.G1Point memory pk) internal pure returns (bytes32 hashedG1) {
274:         assembly {
275:             mstore(0, mload(pk))
276:             mstore(0x20, mload(add(0x20, pk)))
277:             hashedG1 := keccak256(0, 0x40)
278:         }
279:     }
280: 
281:     /// @return the keccak256 hash of the G2 Point
282:     /// @dev used for BLS signatures
283:     function hashG2Point(
284:         BN254.G2Point memory pk
285:     ) internal pure returns (bytes32) {
286:         return keccak256(abi.encodePacked(pk.X[0], pk.X[1], pk.Y[0], pk.Y[1]));
287:     }
288: 
289:     /**
290:      * @notice adapted from https://github.com/HarryR/solcrypto/blob/master/contracts/altbn128.sol
291:      */
292:     function hashToG1(bytes32 _x) internal view returns (G1Point memory) {
293:         uint256 beta = 0;
294:         uint256 y = 0;
295: 
296:         uint256 x = uint256(_x) % FP_MODULUS;
297: 
298:         while (true) {
299:             (beta, y) = findYFromX(x);
300: 
301:             // y^2 == beta
302:             if( beta == mulmod(y, y, FP_MODULUS) ) {
303:                 return G1Point(x, y);
304:             }
305: 
306:             x = addmod(x, 1, FP_MODULUS);
307:         }
308:         return G1Point(0, 0);
309:     }
310: 
311:     /**
312:      * Given X, find Y
313:      *
314:      *   where y = sqrt(x^3 + b)
315:      *
316:      * Returns: (x^3 + b), y
317:      */
318:     function findYFromX(uint256 x) internal view returns (uint256, uint256) {
319:         // beta = (x^3 + b) % p
320:         uint256 beta = addmod(mulmod(mulmod(x, x, FP_MODULUS), x, FP_MODULUS), 3, FP_MODULUS);
321: 
322:         // y^2 = x^3 + b
323:         // this acts like: y = sqrt(beta) = beta^((p+1) / 4)
324:         uint256 y = expMod(beta, 0xc19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52, FP_MODULUS);
325: 
326:         return (beta, y);
327:     }
328: 
329:     function expMod(uint256 _base, uint256 _exponent, uint256 _modulus) internal view returns (uint256 retval) {
330:         bool success;
331:         uint256[1] memory output;
332:         uint[6] memory input;
333:         input[0] = 0x20; // baseLen = new(big.Int).SetBytes(getData(input, 0, 32))
334:         input[1] = 0x20; // expLen  = new(big.Int).SetBytes(getData(input, 32, 32))
335:         input[2] = 0x20; // modLen  = new(big.Int).SetBytes(getData(input, 64, 32))
336:         input[3] = _base;
337:         input[4] = _exponent;
338:         input[5] = _modulus;
339:         assembly {
340:             success := staticcall(sub(gas(), 2000), 5, input, 0xc0, output, 0x20)
341:             // Use "invalid" to make gas estimation work
342:             switch success
343:             case 0 {
344:                 invalid()
345:             }
346:         }
347:         require(success, "BN254.expMod: call failure");
348:         return output[0];
349:     }
350: }
351: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity >=0.5.0;
3: 
4: import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol";
5: import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol";
6: 
7: /**
8:  * @title Minimal interface for a ServiceManager-type contract that forms the single point for an AVS to push updates to EigenLayer
9:  * @author Layr Labs, Inc.
10:  */
11: interface IServiceManager {
12:     /**
13:      * @notice Sets the metadata URI for the AVS
14:      * @param _metadataURI is the metadata URI for the AVS
15:      */
16:     function setMetadataURI(string memory _metadataURI) external;
17: 
18:     /**
19:      * @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator registration with the AVS
20:      * @param operator The address of the operator to register.
21:      * @param operatorSignature The signature, salt, and expiry of the operator's signature.
22:      */
23:     function registerOperatorToAVS(
24:         address operator,
25:         ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
26:     ) external;
27: 
28:     /**
29:      * @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator deregistration from the AVS
30:      * @param operator The address of the operator to deregister.
31:      */
32:     function deregisterOperatorFromAVS(address operator) external;
33: 
34:     /**
35:      * @notice Returns the list of strategies that the operator has potentially restaked on the AVS
36:      * @param operator The address of the operator to get restaked strategies for
37:      * @dev This function is intended to be called off-chain
38:      * @dev No guarantee is made on whether the operator has shares for a strategy in a quorum or uniqueness 
39:      *      of each element in the returned array. The off-chain service should do that validation separately
40:      */
41:     function getOperatorRestakedStrategies(address operator) external view returns (address[] memory);
42: 
43:     /**
44:      * @notice Returns the list of strategies that the AVS supports for restaking
45:      * @dev This function is intended to be called off-chain
46:      * @dev No guarantee is made on uniqueness of each element in the returned array. 
47:      *      The off-chain service should do that validation separately
48:      */
49:     function getRestakeableStrategies() external view returns (address[] memory);
50: 
51:     /// @notice Returns the EigenLayer AVSDirectory contract.
52:     function avsDirectory() external view returns (address);
53: }
54: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import {IPauserRegistry} from "eigenlayer-contracts/src/contracts/interfaces/IPauserRegistry.sol";
5: import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol";
6: import {ISocketUpdater} from "./interfaces/ISocketUpdater.sol";
7: import {IBLSApkRegistry} from "./interfaces/IBLSApkRegistry.sol";
8: import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol";
9: import {IIndexRegistry} from "./interfaces/IIndexRegistry.sol";
10: import {IServiceManager} from "./interfaces/IServiceManager.sol";
11: import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol";
12: 
13: import {EIP1271SignatureUtils} from "eigenlayer-contracts/src/contracts/libraries/EIP1271SignatureUtils.sol";
14: import {BitmapUtils} from "./libraries/BitmapUtils.sol";
15: import {BN254} from "./libraries/BN254.sol";
16: 
17: import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
18: import {Initializable} from "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
19: import {EIP712} from "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol";
20: 
21: import {Pausable} from "eigenlayer-contracts/src/contracts/permissions/Pausable.sol";
22: import {RegistryCoordinatorStorage} from "./RegistryCoordinatorStorage.sol";
23: 
24: /**
25:  * @title A `RegistryCoordinator` that has three registries:
26:  *      1) a `StakeRegistry` that keeps track of operators' stakes
27:  *      2) a `BLSApkRegistry` that keeps track of operators' BLS public keys and aggregate BLS public keys for each quorum
28:  *      3) an `IndexRegistry` that keeps track of an ordered list of operators for each quorum
29:  * 
30:  * @author Layr Labs, Inc.
31:  */
32: contract RegistryCoordinator is 
33:     EIP712, 
34:     Initializable, 
35:     Pausable,
36:     OwnableUpgradeable,
37:     RegistryCoordinatorStorage, 
38:     ISocketUpdater, 
39:     ISignatureUtils
40: {
41:     using BitmapUtils for *;
42:     using BN254 for BN254.G1Point;
43: 
44:     modifier onlyEjector {
45:         require(msg.sender == ejector, "RegistryCoordinator.onlyEjector: caller is not the ejector");
46:         _;
47:     }
48: 
49:     /// @dev Checks that `quorumNumber` corresponds to a quorum that has been created
50:     /// via `initialize` or `createQuorum`
51:     modifier quorumExists(uint8 quorumNumber) {
52:         require(
53:             quorumNumber < quorumCount, 
54:             "RegistryCoordinator.quorumExists: quorum does not exist"
55:         );
56:         _;
57:     }
58: 
59:     constructor(
60:         IServiceManager _serviceManager,
61:         IStakeRegistry _stakeRegistry,
62:         IBLSApkRegistry _blsApkRegistry,
63:         IIndexRegistry _indexRegistry
64:     ) 
65:         RegistryCoordinatorStorage(_serviceManager, _stakeRegistry, _blsApkRegistry, _indexRegistry)
66:         EIP712("AVSRegistryCoordinator", "v0.0.1") 
67:     {
68:         _disableInitializers();
69:     }
70: 
71:     /**
72:      * @param _initialOwner will hold the owner role
73:      * @param _churnApprover will hold the churnApprover role, which authorizes registering with churn
74:      * @param _ejector will hold the ejector role, which can force-eject operators from quorums
75:      * @param _pauserRegistry a registry of addresses that can pause the contract
76:      * @param _initialPausedStatus pause status after calling initialize
77:      * Config for initial quorums (see `createQuorum`):
78:      * @param _operatorSetParams max operator count and operator churn parameters
79:      * @param _minimumStakes minimum stake weight to allow an operator to register
80:      * @param _strategyParams which Strategies/multipliers a quorum considers when calculating stake weight
81:      */
82:     function initialize(
83:         address _initialOwner,
84:         address _churnApprover,
85:         address _ejector,
86:         IPauserRegistry _pauserRegistry,
87:         uint256 _initialPausedStatus,
88:         OperatorSetParam[] memory _operatorSetParams,
89:         uint96[] memory _minimumStakes,
90:         IStakeRegistry.StrategyParams[][] memory _strategyParams
91:     ) external initializer {
92:         require(
93:             _operatorSetParams.length == _minimumStakes.length && _minimumStakes.length == _strategyParams.length,
94:             "RegistryCoordinator.initialize: input length mismatch"
95:         );
96:         
97:         // Initialize roles
98:         _transferOwnership(_initialOwner);
99:         _initializePauser(_pauserRegistry, _initialPausedStatus);
100:         _setChurnApprover(_churnApprover);
101:         _setEjector(_ejector);
102: 
103:         // Add registry contracts to the registries array
104:         registries.push(address(stakeRegistry));
105:         registries.push(address(blsApkRegistry));
106:         registries.push(address(indexRegistry));
107: 
108:         // Create quorums
109:         for (uint256 i = 0; i < _operatorSetParams.length; i++) {
110:             _createQuorum(_operatorSetParams[i], _minimumStakes[i], _strategyParams[i]);
111:         }
112:     }
113: 
114:     /*******************************************************************************
115:                             EXTERNAL FUNCTIONS 
116:     *******************************************************************************/
117: 
118:     /**
119:      * @notice Registers msg.sender as an operator for one or more quorums. If any quorum exceeds its maximum
120:      * operator capacity after the operator is registered, this method will fail.
121:      * @param quorumNumbers is an ordered byte array containing the quorum numbers being registered for
122:      * @param socket is the socket of the operator (typically an IP address)
123:      * @param params contains the G1 & G2 public keys of the operator, and a signature proving their ownership
124:      * @param operatorSignature is the signature of the operator used by the AVS to register the operator in the delegation manager
125:      * @dev `params` is ignored if the caller has previously registered a public key
126:      * @dev `operatorSignature` is ignored if the operator's status is already REGISTERED
127:      */
128:     function registerOperator(
129:         bytes calldata quorumNumbers,
130:         string calldata socket,
131:         IBLSApkRegistry.PubkeyRegistrationParams calldata params,
132:         SignatureWithSaltAndExpiry memory operatorSignature
133:     ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) {
134:         /**
135:          * If the operator has NEVER registered a pubkey before, use `params` to register
136:          * their pubkey in blsApkRegistry
137:          *
138:          * If the operator HAS registered a pubkey, `params` is ignored and the pubkey hash
139:          * (operatorId) is fetched instead
140:          */
141:         bytes32 operatorId = _getOrCreateOperatorId(msg.sender, params);
142: 
143:         // Register the operator in each of the registry contracts and update the operator's
144:         // quorum bitmap and registration status
145:         uint32[] memory numOperatorsPerQuorum = _registerOperator({
146:             operator: msg.sender, 
147:             operatorId: operatorId,
148:             quorumNumbers: quorumNumbers, 
149:             socket: socket,
150:             operatorSignature: operatorSignature
151:         }).numOperatorsPerQuorum;
152: 
153:         // For each quorum, validate that the new operator count does not exceed the maximum
154:         // (If it does, an operator needs to be replaced -- see `registerOperatorWithChurn`)
155:         for (uint256 i = 0; i < quorumNumbers.length; i++) {
156:             uint8 quorumNumber = uint8(quorumNumbers[i]);
157: 
158:             require(
159:                 numOperatorsPerQuorum[i] <= _quorumParams[quorumNumber].maxOperatorCount,
160:                 "RegistryCoordinator.registerOperator: operator count exceeds maximum"
161:             );
162:         }
163:     }
164: 
165:     /**
166:      * @notice Registers msg.sender as an operator for one or more quorums. If any quorum reaches its maximum operator
167:      * capacity, `operatorKickParams` is used to replace an old operator with the new one.
168:      * @param quorumNumbers is an ordered byte array containing the quorum numbers being registered for
169:      * @param params contains the G1 & G2 public keys of the operator, and a signature proving their ownership
170:      * @param operatorKickParams used to determine which operator is removed to maintain quorum capacity as the
171:      * operator registers for quorums
172:      * @param churnApproverSignature is the signature of the churnApprover over the `operatorKickParams`
173:      * @param operatorSignature is the signature of the operator used by the AVS to register the operator in the delegation manager
174:      * @dev `params` is ignored if the caller has previously registered a public key
175:      * @dev `operatorSignature` is ignored if the operator's status is already REGISTERED
176:      */
177:     function registerOperatorWithChurn(
178:         bytes calldata quorumNumbers, 
179:         string calldata socket,
180:         IBLSApkRegistry.PubkeyRegistrationParams calldata params,
181:         OperatorKickParam[] calldata operatorKickParams,
182:         SignatureWithSaltAndExpiry memory churnApproverSignature,
183:         SignatureWithSaltAndExpiry memory operatorSignature
184:     ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) {
185:         require(operatorKickParams.length == quorumNumbers.length, "RegistryCoordinator.registerOperatorWithChurn: input length mismatch");
186:         
187:         /**
188:          * If the operator has NEVER registered a pubkey before, use `params` to register
189:          * their pubkey in blsApkRegistry
190:          *
191:          * If the operator HAS registered a pubkey, `params` is ignored and the pubkey hash
192:          * (operatorId) is fetched instead
193:          */
194:         bytes32 operatorId = _getOrCreateOperatorId(msg.sender, params);
195: 
196:         // Verify the churn approver's signature for the registering operator and kick params
197:         _verifyChurnApproverSignature({
198:             registeringOperator: msg.sender,
199:             registeringOperatorId: operatorId,
200:             operatorKickParams: operatorKickParams,
201:             churnApproverSignature: churnApproverSignature
202:         });
203: 
204:         // Register the operator in each of the registry contracts and update the operator's
205:         // quorum bitmap and registration status
206:         RegisterResults memory results = _registerOperator({
207:             operator: msg.sender,
208:             operatorId: operatorId,
209:             quorumNumbers: quorumNumbers,
210:             socket: socket,
211:             operatorSignature: operatorSignature
212:         });
213: 
214:         // Check that each quorum's operator count is below the configured maximum. If the max
215:         // is exceeded, use `operatorKickParams` to deregister an existing operator to make space
216:         for (uint256 i = 0; i < quorumNumbers.length; i++) {
217:             OperatorSetParam memory operatorSetParams = _quorumParams[uint8(quorumNumbers[i])];
218:             
219:             /**
220:              * If the new operator count for any quorum exceeds the maximum, validate
221:              * that churn can be performed, then deregister the specified operator
222:              */
223:             if (results.numOperatorsPerQuorum[i] > operatorSetParams.maxOperatorCount) {
224:                 _validateChurn({
225:                     quorumNumber: uint8(quorumNumbers[i]),
226:                     totalQuorumStake: results.totalStakes[i],
227:                     newOperator: msg.sender,
228:                     newOperatorStake: results.operatorStakes[i],
229:                     kickParams: operatorKickParams[i],
230:                     setParams: operatorSetParams
231:                 });
232: 
233:                 _deregisterOperator(operatorKickParams[i].operator, quorumNumbers[i:i+1]);
234:             }
235:         }
236:     }
237: 
238:     /**
239:      * @notice Deregisters the caller from one or more quorums
240:      * @param quorumNumbers is an ordered byte array containing the quorum numbers being deregistered from
241:      */
242:     function deregisterOperator(
243:         bytes calldata quorumNumbers
244:     ) external onlyWhenNotPaused(PAUSED_DEREGISTER_OPERATOR) {
245:         _deregisterOperator({
246:             operator: msg.sender, 
247:             quorumNumbers: quorumNumbers
248:         });
249:     }
250: 
251:     /**
252:      * @notice Updates the StakeRegistry's view of one or more operators' stakes. If any operator
253:      * is found to be below the minimum stake for the quorum, they are deregistered.
254:      * @dev stakes are queried from the Eigenlayer core DelegationManager contract
255:      * @param operators a list of operator addresses to update
256:      */
257:     function updateOperators(address[] calldata operators) external onlyWhenNotPaused(PAUSED_UPDATE_OPERATOR) {
258:         for (uint256 i = 0; i < operators.length; i++) {
259:             address operator = operators[i];
260:             OperatorInfo memory operatorInfo = _operatorInfo[operator];
261:             bytes32 operatorId = operatorInfo.operatorId;
262: 
263:             // Update the operator's stake for their active quorums
264:             uint192 currentBitmap = _currentOperatorBitmap(operatorId);
265:             bytes memory quorumsToUpdate = BitmapUtils.bitmapToBytesArray(currentBitmap);
266:             _updateOperator(operator, operatorInfo, quorumsToUpdate);
267:         }
268:     }
269: 
270:     /**
271:      * @notice For each quorum in `quorumNumbers`, updates the StakeRegistry's view of ALL its registered operators' stakes.
272:      * Each quorum's `quorumUpdateBlockNumber` is also updated, which tracks the most recent block number when ALL registered
273:      * operators were updated.
274:      * @dev stakes are queried from the Eigenlayer core DelegationManager contract
275:      * @param operatorsPerQuorum for each quorum in `quorumNumbers`, this has a corresponding list of operators to update.
276:      * @dev Each list of operator addresses MUST be sorted in ascending order
277:      * @dev Each list of operator addresses MUST represent the entire list of registered operators for the corresponding quorum
278:      * @param quorumNumbers is an ordered byte array containing the quorum numbers being updated
279:      * @dev invariant: Each list of `operatorsPerQuorum` MUST be a sorted version of `IndexRegistry.getOperatorListAtBlockNumber`
280:      * for the corresponding quorum.
281:      * @dev note on race condition: if an operator registers/deregisters for any quorum in `quorumNumbers` after a txn to 
282:      * this method is broadcast (but before it is executed), the method will fail
283:      */
284:     function updateOperatorsForQuorum(
285:         address[][] calldata operatorsPerQuorum,
286:         bytes calldata quorumNumbers
287:     ) external onlyWhenNotPaused(PAUSED_UPDATE_OPERATOR) {
288:         // Input validation 
289:         // - all quorums should exist (checked against `quorumCount` in orderedBytesArrayToBitmap)
290:         // - there should be no duplicates in `quorumNumbers`
291:         // - there should be one list of operators per quorum
292:         uint192 quorumBitmap = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount));
293:         require(
294:             operatorsPerQuorum.length == quorumNumbers.length,
295:             "RegistryCoordinator.updateOperatorsForQuorum: input length mismatch"
296:         );
297: 
298:         // For each quorum, update ALL registered operators
299:         for (uint256 i = 0; i < quorumNumbers.length; ++i) {
300:             uint8 quorumNumber = uint8(quorumNumbers[i]);
301: 
302:             // Ensure we've passed in the correct number of operators for this quorum
303:             address[] calldata currQuorumOperators = operatorsPerQuorum[i];
304:             require(
305:                 currQuorumOperators.length == indexRegistry.totalOperatorsForQuorum(quorumNumber),
306:                 "RegistryCoordinator.updateOperatorsForQuorum: number of updated operators does not match quorum total"
307:             );
308: 
309:             address prevOperatorAddress = address(0);
310:             // For each operator:
311:             // - check that they are registered for this quorum
312:             // - check that their address is strictly greater than the last operator
313:             // ... then, update their stakes
314:             for (uint256 j = 0; j < currQuorumOperators.length; ++j) {
315:                 address operator = currQuorumOperators[j];
316:                 
317:                 OperatorInfo memory operatorInfo = _operatorInfo[operator];
318:                 bytes32 operatorId = operatorInfo.operatorId;
319:                 
320:                 {
321:                     uint192 currentBitmap = _currentOperatorBitmap(operatorId);
322:                     // Check that the operator is registered
323:                     require(
324:                         BitmapUtils.isSet(currentBitmap, quorumNumber),
325:                         "RegistryCoordinator.updateOperatorsForQuorum: operator not in quorum"
326:                     );
327:                     // Prevent duplicate operators
328:                     require(
329:                         operator > prevOperatorAddress,
330:                         "RegistryCoordinator.updateOperatorsForQuorum: operators array must be sorted in ascending address order"
331:                     );
332:                 }
333:                 
334:                 // Update the operator
335:                 _updateOperator(operator, operatorInfo, quorumNumbers[i:i+1]);
336:                 prevOperatorAddress = operator;
337:             }
338: 
339:             // Update timestamp that all operators in quorum have been updated all at once
340:             quorumUpdateBlockNumber[quorumNumber] = block.number;
341:             emit QuorumBlockNumberUpdated(quorumNumber, block.number);
342:         }
343:     }
344: 
345:     /**
346:      * @notice Updates the socket of the msg.sender given they are a registered operator
347:      * @param socket is the new socket of the operator
348:      */
349:     function updateSocket(string memory socket) external {
350:         require(_operatorInfo[msg.sender].status == OperatorStatus.REGISTERED, "RegistryCoordinator.updateSocket: operator is not registered");
351:         emit OperatorSocketUpdate(_operatorInfo[msg.sender].operatorId, socket);
352:     }
353: 
354:     /*******************************************************************************
355:                             EXTERNAL FUNCTIONS - EJECTOR
356:     *******************************************************************************/
357: 
358:     /**
359:      * @notice Forcibly deregisters an operator from one or more quorums
360:      * @param operator the operator to eject
361:      * @param quorumNumbers the quorum numbers to eject the operator from
362:      */
363:     function ejectOperator(
364:         address operator, 
365:         bytes calldata quorumNumbers
366:     ) external onlyEjector {
367:         _deregisterOperator({
368:             operator: operator, 
369:             quorumNumbers: quorumNumbers
370:         });
371:     }
372: 
373:     /*******************************************************************************
374:                             EXTERNAL FUNCTIONS - OWNER
375:     *******************************************************************************/
376: 
377:     /**
378:      * @notice Creates a quorum and initializes it in each registry contract
379:      * @param operatorSetParams configures the quorum's max operator count and churn parameters
380:      * @param minimumStake sets the minimum stake required for an operator to register or remain
381:      * registered
382:      * @param strategyParams a list of strategies and multipliers used by the StakeRegistry to
383:      * calculate an operator's stake weight for the quorum
384:      */
385:     function createQuorum(
386:         OperatorSetParam memory operatorSetParams,
387:         uint96 minimumStake,
388:         IStakeRegistry.StrategyParams[] memory strategyParams
389:     ) external virtual onlyOwner {
390:         _createQuorum(operatorSetParams, minimumStake, strategyParams);
391:     }
392: 
393:     /**
394:      * @notice Updates an existing quorum's configuration with a new max operator count
395:      * and operator churn parameters
396:      * @param quorumNumber the quorum number to update
397:      * @param operatorSetParams the new config
398:      * @dev only callable by the owner
399:      */
400:     function setOperatorSetParams(
401:         uint8 quorumNumber, 
402:         OperatorSetParam memory operatorSetParams
403:     ) external onlyOwner quorumExists(quorumNumber) {
404:         _setOperatorSetParams(quorumNumber, operatorSetParams);
405:     }
406: 
407:     /**
408:      * @notice Sets the churnApprover, which approves operator registration with churn
409:      * (see `registerOperatorWithChurn`)
410:      * @param _churnApprover the new churn approver
411:      * @dev only callable by the owner
412:      */
413:     function setChurnApprover(address _churnApprover) external onlyOwner {
414:         _setChurnApprover(_churnApprover);
415:     }
416: 
417:     /**
418:      * @notice Sets the ejector, which can force-deregister operators from quorums
419:      * @param _ejector the new ejector
420:      * @dev only callable by the owner
421:      */
422:     function setEjector(address _ejector) external onlyOwner {
423:         _setEjector(_ejector);
424:     }
425: 
426:     /*******************************************************************************
427:                             INTERNAL FUNCTIONS
428:     *******************************************************************************/
429: 
430:     struct RegisterResults {
431:         uint32[] numOperatorsPerQuorum;
432:         uint96[] operatorStakes;
433:         uint96[] totalStakes;
434:     }
435: 
436:     /** 
437:      * @notice Register the operator for one or more quorums. This method updates the
438:      * operator's quorum bitmap, socket, and status, then registers them with each registry.
439:      */
440:     function _registerOperator(
441:         address operator, 
442:         bytes32 operatorId,
443:         bytes calldata quorumNumbers,
444:         string memory socket,
445:         SignatureWithSaltAndExpiry memory operatorSignature
446:     ) internal virtual returns (RegisterResults memory results) {
447:         /**
448:          * Get bitmap of quorums to register for and operator's current bitmap. Validate that:
449:          * - we're trying to register for at least 1 quorum
450:          * - the quorums we're registering for exist (checked against `quorumCount` in orderedBytesArrayToBitmap)
451:          * - the operator is not currently registered for any quorums we're registering for
452:          * Then, calculate the operator's new bitmap after registration
453:          */
454:         uint192 quorumsToAdd = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount));
455:         uint192 currentBitmap = _currentOperatorBitmap(operatorId);
456:         require(!quorumsToAdd.isEmpty(), "RegistryCoordinator._registerOperator: bitmap cannot be 0");
457:         require(quorumsToAdd.noBitsInCommon(currentBitmap), "RegistryCoordinator._registerOperator: operator already registered for some quorums being registered for");
458:         uint192 newBitmap = uint192(currentBitmap.plus(quorumsToAdd));
459: 
460:         /**
461:          * Update operator's bitmap, socket, and status. Only update operatorInfo if needed:
462:          * if we're `REGISTERED`, the operatorId and status are already correct.
463:          */
464:         _updateOperatorBitmap({
465:             operatorId: operatorId,
466:             newBitmap: newBitmap
467:         });
468: 
469:         emit OperatorSocketUpdate(operatorId, socket);
470: 
471:         // If the operator wasn't registered for any quorums, update their status
472:         // and register them with this AVS in EigenLayer core (DelegationManager)
473:         if (_operatorInfo[operator].status != OperatorStatus.REGISTERED) {
474:             _operatorInfo[operator] = OperatorInfo({
475:                 operatorId: operatorId,
476:                 status: OperatorStatus.REGISTERED
477:             });
478: 
479:             // Register the operator with the EigenLayer core contracts via this AVS's ServiceManager
480:             serviceManager.registerOperatorToAVS(operator, operatorSignature);
481: 
482:             emit OperatorRegistered(operator, operatorId);
483:         }
484: 
485:         // Register the operator with the BLSApkRegistry, StakeRegistry, and IndexRegistry
486:         blsApkRegistry.registerOperator(operator, quorumNumbers);
487:         (results.operatorStakes, results.totalStakes) = 
488:             stakeRegistry.registerOperator(operator, operatorId, quorumNumbers);
489:         results.numOperatorsPerQuorum = indexRegistry.registerOperator(operatorId, quorumNumbers);
490: 
491:         return results;
492:     }
493: 
494:     /**
495:      * @notice Fetches an operator's pubkey hash from the BLSApkRegistry. If the
496:      * operator has not registered a pubkey, attempts to register a pubkey using
497:      * `params`
498:      * @param operator the operator whose pubkey to query from the BLSApkRegistry
499:      * @param params contains the G1 & G2 public keys of the operator, and a signature proving their ownership
500:      * @dev `params` can be empty if the operator has already registered a pubkey in the BLSApkRegistry
501:      */
502:     function _getOrCreateOperatorId(
503:         address operator,
504:         IBLSApkRegistry.PubkeyRegistrationParams calldata params
505:     ) internal returns (bytes32 operatorId) {
506:         operatorId = blsApkRegistry.getOperatorId(operator);
507:         if (operatorId == 0) {
508:             operatorId = blsApkRegistry.registerBLSPublicKey(operator, params, pubkeyRegistrationMessageHash(operator));
509:         }
510:         return operatorId;
511:     }
512: 
513:     /**
514:      * @notice Validates that an incoming operator is eligible to replace an existing
515:      * operator based on the stake of both
516:      * @dev In order to churn, the incoming operator needs to have more stake than the
517:      * existing operator by a proportion given by `kickBIPsOfOperatorStake`
518:      * @dev In order to be churned out, the existing operator needs to have a proportion
519:      * of the total quorum stake less than `kickBIPsOfTotalStake`
520:      * @param quorumNumber `newOperator` is trying to replace an operator in this quorum
521:      * @param totalQuorumStake the total stake of all operators in the quorum, after the
522:      * `newOperator` registers
523:      * @param newOperator the incoming operator
524:      * @param newOperatorStake the incoming operator's stake
525:      * @param kickParams the quorum number and existing operator to replace
526:      * @dev the existing operator's registration to this quorum isn't checked here, but
527:      * if we attempt to deregister them, this will be checked in `_deregisterOperator`
528:      * @param setParams config for this quorum containing `kickBIPsX` stake proportions
529:      * mentioned above
530:      */
531:     function _validateChurn(
532:         uint8 quorumNumber, 
533:         uint96 totalQuorumStake,
534:         address newOperator, 
535:         uint96 newOperatorStake,
536:         OperatorKickParam memory kickParams, 
537:         OperatorSetParam memory setParams
538:     ) internal view {
539:         address operatorToKick = kickParams.operator;
540:         bytes32 idToKick = _operatorInfo[operatorToKick].operatorId;
541:         require(newOperator != operatorToKick, "RegistryCoordinator._validateChurn: cannot churn self");
542:         require(kickParams.quorumNumber == quorumNumber, "RegistryCoordinator._validateChurn: quorumNumber not the same as signed");
543: 
544:         // Get the target operator's stake and check that it is below the kick thresholds
545:         uint96 operatorToKickStake = stakeRegistry.getCurrentStake(idToKick, quorumNumber);
546:         require(
547:             newOperatorStake > _individualKickThreshold(operatorToKickStake, setParams),
548:             "RegistryCoordinator._validateChurn: incoming operator has insufficient stake for churn"
549:         );
550:         require(
551:             operatorToKickStake < _totalKickThreshold(totalQuorumStake, setParams),
552:             "RegistryCoordinator._validateChurn: cannot kick operator with more than kickBIPsOfTotalStake"
553:         );
554:     }
555: 
556:     /**
557:      * @dev Deregister the operator from one or more quorums
558:      * This method updates the operator's quorum bitmap and status, then deregisters
559:      * the operator with the BLSApkRegistry, IndexRegistry, and StakeRegistry
560:      */
561:     function _deregisterOperator(
562:         address operator, 
563:         bytes memory quorumNumbers
564:     ) internal virtual {
565:         // Fetch the operator's info and ensure they are registered
566:         OperatorInfo storage operatorInfo = _operatorInfo[operator];
567:         bytes32 operatorId = operatorInfo.operatorId;
568:         require(operatorInfo.status == OperatorStatus.REGISTERED, "RegistryCoordinator._deregisterOperator: operator is not registered");
569:         
570:         /**
571:          * Get bitmap of quorums to deregister from and operator's current bitmap. Validate that:
572:          * - we're trying to deregister from at least 1 quorum
573:          * - the quorums we're deregistering from exist (checked against `quorumCount` in orderedBytesArrayToBitmap)
574:          * - the operator is currently registered for any quorums we're trying to deregister from
575:          * Then, calculate the operator's new bitmap after deregistration
576:          */
577:         uint192 quorumsToRemove = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount));
578:         uint192 currentBitmap = _currentOperatorBitmap(operatorId);
579:         require(!quorumsToRemove.isEmpty(), "RegistryCoordinator._deregisterOperator: bitmap cannot be 0");
580:         require(quorumsToRemove.isSubsetOf(currentBitmap), "RegistryCoordinator._deregisterOperator: operator is not registered for specified quorums");
581:         uint192 newBitmap = uint192(currentBitmap.minus(quorumsToRemove));
582: 
583:         // Update operator's bitmap and status
584:         _updateOperatorBitmap({
585:             operatorId: operatorId,
586:             newBitmap: newBitmap
587:         });
588: 
589:         // If the operator is no longer registered for any quorums, update their status and deregister 
590:         // them from the AVS via the EigenLayer core contracts
591:         if (newBitmap.isEmpty()) {
592:             operatorInfo.status = OperatorStatus.DEREGISTERED;
593:             serviceManager.deregisterOperatorFromAVS(operator);
594:             emit OperatorDeregistered(operator, operatorId);
595:         }
596: 
597:         // Deregister operator with each of the registry contracts
598:         blsApkRegistry.deregisterOperator(operator, quorumNumbers);
599:         stakeRegistry.deregisterOperator(operatorId, quorumNumbers);
600:         indexRegistry.deregisterOperator(operatorId, quorumNumbers);
601:     }
602: 
603:     /**
604:      * @notice Updates the StakeRegistry's view of the operator's stake in one or more quorums.
605:      * For any quorums where the StakeRegistry finds the operator is under the configured minimum
606:      * stake, `quorumsToRemove` is returned and used to deregister the operator from those quorums
607:      * @dev does nothing if operator is not registered for any quorums.
608:      */
609:     function _updateOperator(
610:         address operator,
611:         OperatorInfo memory operatorInfo,
612:         bytes memory quorumsToUpdate
613:     ) internal {
614:         if (operatorInfo.status != OperatorStatus.REGISTERED) {
615:             return;
616:         }
617:         bytes32 operatorId = operatorInfo.operatorId;
618:         uint192 quorumsToRemove = stakeRegistry.updateOperatorStake(operator, operatorId, quorumsToUpdate);
619: 
620:         if (!quorumsToRemove.isEmpty()) {
621:             _deregisterOperator({
622:                 operator: operator,
623:                 quorumNumbers: BitmapUtils.bitmapToBytesArray(quorumsToRemove)
624:             });    
625:         }
626:     }
627: 
628:     /**
629:      * @notice Returns the stake threshold required for an incoming operator to replace an existing operator
630:      * The incoming operator must have more stake than the return value.
631:      */
632:     function _individualKickThreshold(uint96 operatorStake, OperatorSetParam memory setParams) internal pure returns (uint96) {
633:         return operatorStake * setParams.kickBIPsOfOperatorStake / BIPS_DENOMINATOR;
634:     }
635: 
636:     /**
637:      * @notice Returns the total stake threshold required for an operator to remain in a quorum.
638:      * The operator must have at least the returned stake amount to keep their position.
639:      */
640:     function _totalKickThreshold(uint96 totalStake, OperatorSetParam memory setParams) internal pure returns (uint96) {
641:         return totalStake * setParams.kickBIPsOfTotalStake / BIPS_DENOMINATOR;
642:     }
643: 
644:     /// @notice verifies churnApprover's signature on operator churn approval and increments the churnApprover nonce
645:     function _verifyChurnApproverSignature(
646:         address registeringOperator,
647:         bytes32 registeringOperatorId, 
648:         OperatorKickParam[] memory operatorKickParams, 
649:         SignatureWithSaltAndExpiry memory churnApproverSignature
650:     ) internal {
651:         // make sure the salt hasn't been used already
652:         require(!isChurnApproverSaltUsed[churnApproverSignature.salt], "RegistryCoordinator._verifyChurnApproverSignature: churnApprover salt already used");
653:         require(churnApproverSignature.expiry >= block.timestamp, "RegistryCoordinator._verifyChurnApproverSignature: churnApprover signature expired");   
654: 
655:         // set salt used to true
656:         isChurnApproverSaltUsed[churnApproverSignature.salt] = true;    
657: 
658:         // check the churnApprover's signature 
659:         EIP1271SignatureUtils.checkSignature_EIP1271(
660:             churnApprover, 
661:             calculateOperatorChurnApprovalDigestHash(registeringOperator, registeringOperatorId, operatorKickParams, churnApproverSignature.salt, churnApproverSignature.expiry), 
662:             churnApproverSignature.signature
663:         );
664:     }
665: 
666:     /**
667:      * @notice Creates a quorum and initializes it in each registry contract
668:      * @param operatorSetParams configures the quorum's max operator count and churn parameters
669:      * @param minimumStake sets the minimum stake required for an operator to register or remain
670:      * registered
671:      * @param strategyParams a list of strategies and multipliers used by the StakeRegistry to
672:      * calculate an operator's stake weight for the quorum
673:      */
674:     function _createQuorum(
675:         OperatorSetParam memory operatorSetParams,
676:         uint96 minimumStake,
677:         IStakeRegistry.StrategyParams[] memory strategyParams
678:     ) internal {
679:         // Increment the total quorum count. Fails if we're already at the max
680:         uint8 prevQuorumCount = quorumCount;
681:         require(prevQuorumCount < MAX_QUORUM_COUNT, "RegistryCoordinator.createQuorum: max quorums reached");
682:         quorumCount = prevQuorumCount + 1;
683:         
684:         // The previous count is the new quorum's number
685:         uint8 quorumNumber = prevQuorumCount;
686: 
687:         // Initialize the quorum here and in each registry
688:         _setOperatorSetParams(quorumNumber, operatorSetParams);
689:         stakeRegistry.initializeQuorum(quorumNumber, minimumStake, strategyParams);
690:         indexRegistry.initializeQuorum(quorumNumber);
691:         blsApkRegistry.initializeQuorum(quorumNumber);
692:     }
693: 
694:     /**
695:      * @notice Record an update to an operator's quorum bitmap.
696:      * @param newBitmap is the most up-to-date set of bitmaps the operator is registered for
697:      */
698:     function _updateOperatorBitmap(bytes32 operatorId, uint192 newBitmap) internal {
699: 
700:         uint256 historyLength = _operatorBitmapHistory[operatorId].length;
701: 
702:         if (historyLength == 0) {
703:             // No prior bitmap history - push our first entry
704:             _operatorBitmapHistory[operatorId].push(QuorumBitmapUpdate({
705:                 updateBlockNumber: uint32(block.number),
706:                 nextUpdateBlockNumber: 0,
707:                 quorumBitmap: newBitmap
708:             }));
709:         } else {
710:             // We have prior history - fetch our last-recorded update
711:             QuorumBitmapUpdate storage lastUpdate = _operatorBitmapHistory[operatorId][historyLength - 1];
712: 
713:             /**
714:              * If the last update was made in the current block, update the entry.
715:              * Otherwise, push a new entry and update the previous entry's "next" field
716:              */
717:             if (lastUpdate.updateBlockNumber == uint32(block.number)) {
718:                 lastUpdate.quorumBitmap = newBitmap;
719:             } else {
720:                 lastUpdate.nextUpdateBlockNumber = uint32(block.number);
721:                 _operatorBitmapHistory[operatorId].push(QuorumBitmapUpdate({
722:                     updateBlockNumber: uint32(block.number),
723:                     nextUpdateBlockNumber: 0,
724:                     quorumBitmap: newBitmap
725:                 }));
726:             }
727:         }
728:     }
729: 
730:     /// @notice Get the most recent bitmap for the operator, returning an empty bitmap if
731:     /// the operator is not registered.
732:     function _currentOperatorBitmap(bytes32 operatorId) internal view returns (uint192) {
733:         uint256 historyLength = _operatorBitmapHistory[operatorId].length;
734:         if (historyLength == 0) {
735:             return 0;
736:         } else {
737:             return _operatorBitmapHistory[operatorId][historyLength - 1].quorumBitmap;
738:         }
739:     }
740: 
741:     /**
742:      * @notice Returns the index of the quorumBitmap for the provided `operatorId` at the given `blockNumber`
743:      * @dev Reverts if the operator had not yet (ever) registered at `blockNumber`
744:      * @dev This function is designed to find proper inputs to the `getQuorumBitmapAtBlockNumberByIndex` function
745:      */
746:     function _getQuorumBitmapIndexAtBlockNumber(
747:         uint32 blockNumber, 
748:         bytes32 operatorId
749:     ) internal view returns (uint32 index) {
750:         uint256 length = _operatorBitmapHistory[operatorId].length;
751: 
752:         // Traverse the operator's bitmap history in reverse, returning the first index
753:         // corresponding to an update made before or at `blockNumber`
754:         for (uint256 i = 0; i < length; i++) {
755:             index = uint32(length - i - 1);
756: 
757:             if (_operatorBitmapHistory[operatorId][index].updateBlockNumber <= blockNumber) {
758:                 return index;
759:             }
760:         }
761: 
762:         revert(
763:             "RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId at block number"
764:         );
765:     }
766: 
767:     function _setOperatorSetParams(uint8 quorumNumber, OperatorSetParam memory operatorSetParams) internal {
768:         _quorumParams[quorumNumber] = operatorSetParams;
769:         emit OperatorSetParamsUpdated(quorumNumber, operatorSetParams);
770:     }
771:     
772:     function _setChurnApprover(address newChurnApprover) internal {
773:         emit ChurnApproverUpdated(churnApprover, newChurnApprover);
774:         churnApprover = newChurnApprover;
775:     }
776: 
777:     function _setEjector(address newEjector) internal {
778:         emit EjectorUpdated(ejector, newEjector);
779:         ejector = newEjector;
780:     }
781: 
782:     /*******************************************************************************
783:                             VIEW FUNCTIONS
784:     *******************************************************************************/
785: 
786:     /// @notice Returns the operator set params for the given `quorumNumber`
787:     function getOperatorSetParams(uint8 quorumNumber) external view returns (OperatorSetParam memory) {
788:         return _quorumParams[quorumNumber];
789:     }
790: 
791:     /// @notice Returns the operator struct for the given `operator`
792:     function getOperator(address operator) external view returns (OperatorInfo memory) {
793:         return _operatorInfo[operator];
794:     }
795: 
796:     /// @notice Returns the operatorId for the given `operator`
797:     function getOperatorId(address operator) external view returns (bytes32) {
798:         return _operatorInfo[operator].operatorId;
799:     }
800: 
801:     /// @notice Returns the operator address for the given `operatorId`
802:     function getOperatorFromId(bytes32 operatorId) external view returns (address) {
803:         return blsApkRegistry.getOperatorFromPubkeyHash(operatorId);
804:     }
805: 
806:     /// @notice Returns the status for the given `operator`
807:     function getOperatorStatus(address operator) external view returns (IRegistryCoordinator.OperatorStatus) {
808:         return _operatorInfo[operator].status;
809:     }
810: 
811:     /**
812:      * @notice Returns the indices of the quorumBitmaps for the provided `operatorIds` at the given `blockNumber`
813:      * @dev Reverts if any of the `operatorIds` was not (yet) registered at `blockNumber`
814:      * @dev This function is designed to find proper inputs to the `getQuorumBitmapAtBlockNumberByIndex` function
815:      */
816:     function getQuorumBitmapIndicesAtBlockNumber(
817:         uint32 blockNumber, 
818:         bytes32[] memory operatorIds
819:     ) external view returns (uint32[] memory) {
820:         uint32[] memory indices = new uint32[](operatorIds.length);
821:         for (uint256 i = 0; i < operatorIds.length; i++) {
822:             indices[i] = _getQuorumBitmapIndexAtBlockNumber(blockNumber, operatorIds[i]);
823:         }
824:         return indices;
825:     }
826: 
827:     /**
828:      * @notice Returns the quorum bitmap for the given `operatorId` at the given `blockNumber` via the `index`,
829:      * reverting if `index` is incorrect
830:      * @dev This function is meant to be used in concert with `getQuorumBitmapIndicesAtBlockNumber`, which
831:      * helps off-chain processes to fetch the correct `index` input
832:      */ 
833:     function getQuorumBitmapAtBlockNumberByIndex(
834:         bytes32 operatorId, 
835:         uint32 blockNumber, 
836:         uint256 index
837:     ) external view returns (uint192) {
838:         QuorumBitmapUpdate memory quorumBitmapUpdate = _operatorBitmapHistory[operatorId][index];
839:         
840:         /**
841:          * Validate that the update is valid for the given blockNumber:
842:          * - blockNumber should be >= the update block number
843:          * - the next update block number should be either 0 or strictly greater than blockNumber
844:          */
845:         require(
846:             blockNumber >= quorumBitmapUpdate.updateBlockNumber, 
847:             "RegistryCoordinator.getQuorumBitmapAtBlockNumberByIndex: quorumBitmapUpdate is from after blockNumber"
848:         );
849:         require(
850:             quorumBitmapUpdate.nextUpdateBlockNumber == 0 || blockNumber < quorumBitmapUpdate.nextUpdateBlockNumber,
851:             "RegistryCoordinator.getQuorumBitmapAtBlockNumberByIndex: quorumBitmapUpdate is from before blockNumber"
852:         );
853: 
854:         return quorumBitmapUpdate.quorumBitmap;
855:     }
856: 
857:     /// @notice Returns the `index`th entry in the operator with `operatorId`'s bitmap history
858:     function getQuorumBitmapUpdateByIndex(
859:         bytes32 operatorId, 
860:         uint256 index
861:     ) external view returns (QuorumBitmapUpdate memory) {
862:         return _operatorBitmapHistory[operatorId][index];
863:     }
864: 
865:     /// @notice Returns the current quorum bitmap for the given `operatorId` or 0 if the operator is not registered for any quorum
866:     function getCurrentQuorumBitmap(bytes32 operatorId) external view returns (uint192) {
867:         return _currentOperatorBitmap(operatorId);
868:     }
869: 
870:     /// @notice Returns the length of the quorum bitmap history for the given `operatorId`
871:     function getQuorumBitmapHistoryLength(bytes32 operatorId) external view returns (uint256) {
872:         return _operatorBitmapHistory[operatorId].length;
873:     }
874: 
875:     /// @notice Returns the number of registries
876:     function numRegistries() external view returns (uint256) {
877:         return registries.length;
878:     }
879: 
880:     /**
881:      * @notice Public function for the the churnApprover signature hash calculation when operators are being kicked from quorums
882:      * @param registeringOperatorId The id of the registering operator 
883:      * @param operatorKickParams The parameters needed to kick the operator from the quorums that have reached their caps
884:      * @param salt The salt to use for the churnApprover's signature
885:      * @param expiry The desired expiry time of the churnApprover's signature
886:      */
887:     function calculateOperatorChurnApprovalDigestHash(
888:         address registeringOperator,
889:         bytes32 registeringOperatorId,
890:         OperatorKickParam[] memory operatorKickParams,
891:         bytes32 salt,
892:         uint256 expiry
893:     ) public view returns (bytes32) {
894:         // calculate the digest hash
895:         return _hashTypedDataV4(keccak256(abi.encode(OPERATOR_CHURN_APPROVAL_TYPEHASH, registeringOperator, registeringOperatorId, operatorKickParams, salt, expiry)));
896:     }
897: 
898:     /**
899:      * @notice Returns the message hash that an operator must sign to register their BLS public key.
900:      * @param operator is the address of the operator registering their BLS public key
901:      */
902:     function pubkeyRegistrationMessageHash(address operator) public view returns (BN254.G1Point memory) {
903:         return BN254.hashToG1(
904:             _hashTypedDataV4(
905:                 keccak256(abi.encode(PUBKEY_REGISTRATION_TYPEHASH, operator))
906:             )
907:         );
908:     }
909: 
910:     /// @dev need to override function here since its defined in both these contracts
911:     function owner()
912:         public
913:         view
914:         override(OwnableUpgradeable, IRegistryCoordinator)
915:         returns (address)
916:     {
917:         return OwnableUpgradeable.owner();
918:     }
919: }
920: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol";
5: 
6: import {StakeRegistryStorage, IStrategy} from "./StakeRegistryStorage.sol";
7: 
8: import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol";
9: import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol";
10: 
11: import {BitmapUtils} from "./libraries/BitmapUtils.sol";
12: 
13: /**
14:  * @title A `Registry` that keeps track of stakes of operators for up to 256 quorums.
15:  * Specifically, it keeps track of
16:  *      1) The stake of each operator in all the quorums they are a part of for block ranges
17:  *      2) The total stake of all operators in each quorum for block ranges
18:  *      3) The minimum stake required to register for each quorum
19:  * It allows an additional functionality (in addition to registering and deregistering) to update the stake of an operator.
20:  * @author Layr Labs, Inc.
21:  */
22: contract StakeRegistry is StakeRegistryStorage {
23: 
24:     using BitmapUtils for *;
25:     
26:     modifier onlyRegistryCoordinator() {
27:         require(
28:             msg.sender == address(registryCoordinator),
29:             "StakeRegistry.onlyRegistryCoordinator: caller is not the RegistryCoordinator"
30:         );
31:         _;
32:     }
33: 
34:     modifier onlyCoordinatorOwner() {
35:         require(msg.sender == IRegistryCoordinator(registryCoordinator).owner(), "StakeRegistry.onlyCoordinatorOwner: caller is not the owner of the registryCoordinator");
36:         _;
37:     }
38: 
39:     modifier quorumExists(uint8 quorumNumber) {
40:         require(_quorumExists(quorumNumber), "StakeRegistry.quorumExists: quorum does not exist");
41:         _;
42:     }
43: 
44:     constructor(
45:         IRegistryCoordinator _registryCoordinator,
46:         IDelegationManager _delegationManager
47:     ) StakeRegistryStorage(_registryCoordinator, _delegationManager) {}
48: 
49:     /*******************************************************************************
50:                       EXTERNAL FUNCTIONS - REGISTRY COORDINATOR
51:     *******************************************************************************/
52: 
53:     /**
54:      * @notice Registers the `operator` with `operatorId` for the specified `quorumNumbers`.
55:      * @param operator The address of the operator to register.
56:      * @param operatorId The id of the operator to register.
57:      * @param quorumNumbers The quorum numbers the operator is registering for, where each byte is an 8 bit integer quorumNumber.
58:      * @return The operator's current stake for each quorum, and the total stake for each quorum
59:      * @dev access restricted to the RegistryCoordinator
60:      * @dev Preconditions (these are assumed, not validated in this contract):
61:      *         1) `quorumNumbers` has no duplicates
62:      *         2) `quorumNumbers.length` != 0
63:      *         3) `quorumNumbers` is ordered in ascending order
64:      *         4) the operator is not already registered
65:      */
66:     function registerOperator(
67:         address operator,
68:         bytes32 operatorId,
69:         bytes calldata quorumNumbers
70:     ) public virtual onlyRegistryCoordinator returns (uint96[] memory, uint96[] memory) {
71: 
72:         uint96[] memory currentStakes = new uint96[](quorumNumbers.length);
73:         uint96[] memory totalStakes = new uint96[](quorumNumbers.length);
74:         for (uint256 i = 0; i < quorumNumbers.length; i++) {            
75:             
76:             uint8 quorumNumber = uint8(quorumNumbers[i]);
77:             require(_quorumExists(quorumNumber), "StakeRegistry.registerOperator: quorum does not exist");
78: 
79:             // Retrieve the operator's current weighted stake for the quorum, reverting if they have not met
80:             // the minimum.
81:             (uint96 currentStake, bool hasMinimumStake) = _weightOfOperatorForQuorum(quorumNumber, operator);
82:             require(
83:                 hasMinimumStake,
84:                 "StakeRegistry.registerOperator: Operator does not meet minimum stake requirement for quorum"
85:             );
86: 
87:             // Update the operator's stake
88:             int256 stakeDelta = _recordOperatorStakeUpdate({
89:                 operatorId: operatorId, 
90:                 quorumNumber: quorumNumber,
91:                 newStake: currentStake
92:             });
93: 
94:             // Update this quorum's total stake by applying the operator's delta
95:             currentStakes[i] = currentStake;
96:             totalStakes[i] = _recordTotalStakeUpdate(quorumNumber, stakeDelta);
97:         }
98: 
99:         return (currentStakes, totalStakes);
100:     }
101: 
102:     /**
103:      * @notice Deregisters the operator with `operatorId` for the specified `quorumNumbers`.
104:      * @param operatorId The id of the operator to deregister.
105:      * @param quorumNumbers The quorum numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber.
106:      * @dev access restricted to the RegistryCoordinator
107:      * @dev Preconditions (these are assumed, not validated in this contract):
108:      *         1) `quorumNumbers` has no duplicates
109:      *         2) `quorumNumbers.length` != 0
110:      *         3) `quorumNumbers` is ordered in ascending order
111:      *         4) the operator is not already deregistered
112:      *         5) `quorumNumbers` is a subset of the quorumNumbers that the operator is registered for
113:      */
114:     function deregisterOperator(
115:         bytes32 operatorId,
116:         bytes calldata quorumNumbers
117:     ) public virtual onlyRegistryCoordinator {
118:         /**
119:          * For each quorum, remove the operator's stake for the quorum and update
120:          * the quorum's total stake to account for the removal
121:          */
122:         for (uint256 i = 0; i < quorumNumbers.length; i++) {
123:             uint8 quorumNumber = uint8(quorumNumbers[i]);
124:             require(_quorumExists(quorumNumber), "StakeRegistry.deregisterOperator: quorum does not exist");
125: 
126:             // Update the operator's stake for the quorum and retrieve the shares removed
127:             int256 stakeDelta = _recordOperatorStakeUpdate({
128:                 operatorId: operatorId, 
129:                 quorumNumber: quorumNumber, 
130:                 newStake: 0
131:             });
132: 
133:             // Apply the operator's stake delta to the total stake for this quorum
134:             _recordTotalStakeUpdate(quorumNumber, stakeDelta);
135:         }
136:     }
137: 
138:     /**
139:      * @notice Called by the registry coordinator to update an operator's stake for one
140:      * or more quorums.
141:      *
142:      * If the operator no longer has the minimum stake required for a quorum, they are
143:      * added to the `quorumsToRemove`, which is returned to the registry coordinator
144:      * @return A bitmap of quorums where the operator no longer meets the minimum stake
145:      * and should be deregistered.
146:      */
147:     function updateOperatorStake(
148:         address operator, 
149:         bytes32 operatorId, 
150:         bytes calldata quorumNumbers
151:     ) external onlyRegistryCoordinator returns (uint192) {
152:         uint192 quorumsToRemove;
153: 
154:         /**
155:          * For each quorum, update the operator's stake and record the delta
156:          * in the quorum's total stake.
157:          *
158:          * If the operator no longer has the minimum stake required to be registered
159:          * in the quorum, the quorum number is added to `quorumsToRemove`, which
160:          * is returned to the registry coordinator.
161:          */
162:         for (uint256 i = 0; i < quorumNumbers.length; i++) {
163:             uint8 quorumNumber = uint8(quorumNumbers[i]);
164:             require(_quorumExists(quorumNumber), "StakeRegistry.updateOperatorStake: quorum does not exist");
165: 
166:             // Fetch the operator's current stake, applying weighting parameters and checking
167:             // against the minimum stake requirements for the quorum.
168:             (uint96 stakeWeight, bool hasMinimumStake) = _weightOfOperatorForQuorum(quorumNumber, operator);
169: 
170:             // If the operator no longer meets the minimum stake, set their stake to zero and mark them for removal
171:             if (!hasMinimumStake) {
172:                 stakeWeight = 0;
173:                 quorumsToRemove = uint192(quorumsToRemove.setBit(quorumNumber));
174:             }
175: 
176:             // Update the operator's stake and retrieve the delta
177:             // If we're deregistering them, their weight is set to 0
178:             int256 stakeDelta = _recordOperatorStakeUpdate({
179:                 operatorId: operatorId,
180:                 quorumNumber: quorumNumber,
181:                 newStake: stakeWeight
182:             });
183: 
184:             // Apply the delta to the quorum's total stake
185:             _recordTotalStakeUpdate(quorumNumber, stakeDelta);
186:         }
187: 
188:         return quorumsToRemove;
189:     }
190: 
191:     /// @notice Initialize a new quorum and push its first history update
192:     function initializeQuorum(
193:         uint8 quorumNumber,
194:         uint96 minimumStake,
195:         StrategyParams[] memory _strategyParams
196:     ) public virtual onlyRegistryCoordinator {
197:         require(!_quorumExists(quorumNumber), "StakeRegistry.initializeQuorum: quorum already exists");
198:         _addStrategyParams(quorumNumber, _strategyParams);
199:         _setMinimumStakeForQuorum(quorumNumber, minimumStake);
200: 
201:         _totalStakeHistory[quorumNumber].push(StakeUpdate({
202:             updateBlockNumber: uint32(block.number),
203:             nextUpdateBlockNumber: 0,
204:             stake: 0
205:         }));
206:     }
207: 
208:     function setMinimumStakeForQuorum(
209:         uint8 quorumNumber, 
210:         uint96 minimumStake
211:     ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber) {
212:         _setMinimumStakeForQuorum(quorumNumber, minimumStake);
213:     }
214: 
215:     /** 
216:      * @notice Adds strategies and weights to the quorum
217:      * @dev Checks to make sure that the *same* strategy cannot be added multiple times (checks against both against existing and new strategies).
218:      * @dev This function has no check to make sure that the strategies for a single quorum have the same underlying asset. This is a concious choice,
219:      * since a middleware may want, e.g., a stablecoin quorum that accepts USDC, USDT, DAI, etc. as underlying assets and trades them as "equivalent".
220:      */
221:     function addStrategies(
222:         uint8 quorumNumber, 
223:         StrategyParams[] memory _strategyParams
224:     ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber) {
225:         _addStrategyParams(quorumNumber, _strategyParams);
226:     }
227: 
228:     /**
229:      * @notice Remove strategies and their associated weights from the quorum's considered strategies
230:      * @dev higher indices should be *first* in the list of @param indicesToRemove, since otherwise
231:      * the removal of lower index entries will cause a shift in the indices of the other strategies to remove
232:      */
233:     function removeStrategies(
234:         uint8 quorumNumber,
235:         uint256[] memory indicesToRemove
236:     ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber) {
237:         uint256 toRemoveLength = indicesToRemove.length;
238:         require(toRemoveLength > 0, "StakeRegistry.removeStrategies: no indices to remove provided");
239: 
240:         StrategyParams[] storage _strategyParams = strategyParams[quorumNumber];
241:         IStrategy[] storage _strategiesPerQuorum = strategiesPerQuorum[quorumNumber];
242: 
243:         for (uint256 i = 0; i < toRemoveLength; i++) {
244:             emit StrategyRemovedFromQuorum(quorumNumber, _strategyParams[indicesToRemove[i]].strategy);
245:             emit StrategyMultiplierUpdated(quorumNumber, _strategyParams[indicesToRemove[i]].strategy, 0);
246: 
247:             // Replace index to remove with the last item in the list, then pop the last item
248:             _strategyParams[indicesToRemove[i]] = _strategyParams[_strategyParams.length - 1];
249:             _strategyParams.pop();
250:             _strategiesPerQuorum[indicesToRemove[i]] = _strategiesPerQuorum[_strategiesPerQuorum.length - 1];
251:             _strategiesPerQuorum.pop();
252:         }
253:     }
254: 
255:     /**
256:      * @notice Modifies the weights of existing strategies for a specific quorum
257:      * @param quorumNumber is the quorum number to which the strategies belong
258:      * @param strategyIndices are the indices of the strategies to change
259:      * @param newMultipliers are the new multipliers for the strategies
260:      */
261:     function modifyStrategyParams(
262:         uint8 quorumNumber,
263:         uint256[] calldata strategyIndices,
264:         uint96[] calldata newMultipliers
265:     ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber) {
266:         uint256 numStrats = strategyIndices.length;
267:         require(numStrats > 0, "StakeRegistry.modifyStrategyParams: no strategy indices provided");
268:         require(newMultipliers.length == numStrats, "StakeRegistry.modifyStrategyParams: input length mismatch");
269: 
270:         StrategyParams[] storage _strategyParams = strategyParams[quorumNumber];
271: 
272:         for (uint256 i = 0; i < numStrats; i++) {
273:             // Change the strategy's associated multiplier
274:             _strategyParams[strategyIndices[i]].multiplier = newMultipliers[i];
275:             emit StrategyMultiplierUpdated(quorumNumber, _strategyParams[strategyIndices[i]].strategy, newMultipliers[i]);
276:         }
277:     }
278: 
279:     /*******************************************************************************
280:                             INTERNAL FUNCTIONS
281:     *******************************************************************************/
282: 
283:     function _getStakeUpdateIndexForOperatorAtBlockNumber(
284:         bytes32 operatorId,
285:         uint8 quorumNumber,
286:         uint32 blockNumber
287:     ) internal view returns (uint32) {
288:         uint256 length = operatorStakeHistory[operatorId][quorumNumber].length;
289: 
290:         // Iterate backwards through operatorStakeHistory until we find an update that preceeds blockNumber
291:         for (uint256 i = length; i > 0; i--) {
292:             if (operatorStakeHistory[operatorId][quorumNumber][i - 1].updateBlockNumber <= blockNumber) {
293:                 return uint32(i - 1);
294:             }
295:         }
296: 
297:         // If we hit this point, no stake update exists at blockNumber
298:         revert(
299:             "StakeRegistry._getStakeUpdateIndexForOperatorAtBlockNumber: no stake update found for operatorId and quorumNumber at block number"
300:         );
301:     }
302: 
303:     function _setMinimumStakeForQuorum(uint8 quorumNumber, uint96 minimumStake) internal {
304:         minimumStakeForQuorum[quorumNumber] = minimumStake;
305:         emit MinimumStakeForQuorumUpdated(quorumNumber, minimumStake);
306:     }
307: 
308:     /**
309:      * @notice Records that `operatorId`'s current stake for `quorumNumber` is now `newStake`
310:      * @return The change in the operator's stake as a signed int256
311:      */
312:     function _recordOperatorStakeUpdate(
313:         bytes32 operatorId,
314:         uint8 quorumNumber,
315:         uint96 newStake
316:     ) internal returns (int256) {
317: 
318:         uint96 prevStake;
319:         uint256 historyLength = operatorStakeHistory[operatorId][quorumNumber].length;
320: 
321:         if (historyLength == 0) {
322:             // No prior stake history - push our first entry
323:             operatorStakeHistory[operatorId][quorumNumber].push(StakeUpdate({
324:                 updateBlockNumber: uint32(block.number),
325:                 nextUpdateBlockNumber: 0,
326:                 stake: newStake
327:             }));
328:         } else {
329:             // We have prior stake history - fetch our last-recorded stake
330:             StakeUpdate storage lastUpdate = operatorStakeHistory[operatorId][quorumNumber][historyLength-1]; 
331:             prevStake = lastUpdate.stake;
332: 
333:             // Short-circuit in case there's no change in stake
334:             if (prevStake == newStake) {
335:                 return 0;
336:             }
337: 
338:             /**
339:              * If our last stake entry was made in the current block, update the entry
340:              * Otherwise, push a new entry and update the previous entry's "next" field
341:              */ 
342:             if (lastUpdate.updateBlockNumber == uint32(block.number)) {
343:                 lastUpdate.stake = newStake;
344:             } else {
345:                 lastUpdate.nextUpdateBlockNumber = uint32(block.number);
346:                 operatorStakeHistory[operatorId][quorumNumber].push(StakeUpdate({
347:                     updateBlockNumber: uint32(block.number),
348:                     nextUpdateBlockNumber: 0,
349:                     stake: newStake
350:                 }));
351:             }
352:         }
353: 
354:         // Log update and return stake delta
355:         emit OperatorStakeUpdate(operatorId, quorumNumber, newStake);
356:         return _calculateDelta({ prev: prevStake, cur: newStake });
357:     }
358: 
359:     /// @notice Applies a delta to the total stake recorded for `quorumNumber`
360:     /// @return Returns the new total stake for the quorum
361:     function _recordTotalStakeUpdate(uint8 quorumNumber, int256 stakeDelta) internal returns (uint96) {
362:         // Get our last-recorded stake update
363:         uint256 historyLength = _totalStakeHistory[quorumNumber].length;
364:         StakeUpdate storage lastStakeUpdate = _totalStakeHistory[quorumNumber][historyLength - 1];
365: 
366:         // Return early if no update is needed
367:         if (stakeDelta == 0) {
368:             return lastStakeUpdate.stake;
369:         }
370:         
371:         // Calculate the new total stake by applying the delta to our previous stake
372:         uint96 newStake = _applyDelta(lastStakeUpdate.stake, stakeDelta);
373: 
374:         /**
375:          * If our last stake entry was made in the current block, update the entry
376:          * Otherwise, push a new entry and update the previous entry's "next" field
377:          */
378:         if (lastStakeUpdate.updateBlockNumber == uint32(block.number)) {
379:             lastStakeUpdate.stake = newStake;
380:         } else {
381:             lastStakeUpdate.nextUpdateBlockNumber = uint32(block.number);
382:             _totalStakeHistory[quorumNumber].push(StakeUpdate({
383:                 updateBlockNumber: uint32(block.number),
384:                 nextUpdateBlockNumber: 0,
385:                 stake: newStake
386:             }));
387:         }
388: 
389:         return newStake;
390:     }
391: 
392:     /** 
393:      * @notice Adds `strategyParams` to the `quorumNumber`-th quorum.
394:      * @dev Checks to make sure that the *same* strategy cannot be added multiple times (checks against both against existing and new strategies).
395:      * @dev This function has no check to make sure that the strategies for a single quorum have the same underlying asset. This is a conscious choice,
396:      * since a middleware may want, e.g., a stablecoin quorum that accepts USDC, USDT, DAI, etc. as underlying assets and trades them as "equivalent".
397:      */
398:     function _addStrategyParams(
399:         uint8 quorumNumber,
400:         StrategyParams[] memory _strategyParams
401:     ) internal {
402:         require(_strategyParams.length > 0, "StakeRegistry._addStrategyParams: no strategies provided");
403:         uint256 numStratsToAdd = _strategyParams.length;
404:         uint256 numStratsExisting = strategyParams[quorumNumber].length;
405:         require(
406:             numStratsExisting + numStratsToAdd <= MAX_WEIGHING_FUNCTION_LENGTH,
407:             "StakeRegistry._addStrategyParams: exceed MAX_WEIGHING_FUNCTION_LENGTH"
408:         );
409:         for (uint256 i = 0; i < numStratsToAdd; i++) {
410:             // fairly gas-expensive internal loop to make sure that the *same* strategy cannot be added multiple times
411:             for (uint256 j = 0; j < (numStratsExisting + i); j++) {
412:                 require(
413:                     strategyParams[quorumNumber][j].strategy != _strategyParams[i].strategy,
414:                     "StakeRegistry._addStrategyParams: cannot add same strategy 2x"
415:                 );
416:             }
417:             require(
418:                 _strategyParams[i].multiplier > 0,
419:                 "StakeRegistry._addStrategyParams: cannot add strategy with zero weight"
420:             );
421:             strategyParams[quorumNumber].push(_strategyParams[i]);
422:             strategiesPerQuorum[quorumNumber].push(_strategyParams[i].strategy);
423:             emit StrategyAddedToQuorum(quorumNumber, _strategyParams[i].strategy);
424:             emit StrategyMultiplierUpdated(
425:                 quorumNumber,
426:                 _strategyParams[i].strategy,
427:                 _strategyParams[i].multiplier
428:             );
429:         }
430:     }
431: 
432:     /// @notice Returns the change between a previous and current value as a signed int
433:     function _calculateDelta(uint96 prev, uint96 cur) internal pure returns (int256) {
434:         return int256(uint256(cur)) - int256(uint256(prev));
435:     }
436: 
437:     /// @notice Adds or subtracts delta from value, according to its sign
438:     function _applyDelta(uint96 value, int256 delta) internal pure returns (uint96) {
439:         if (delta < 0) {
440:             return value - uint96(uint256(-delta));
441:         } else {
442:             return value + uint96(uint256(delta));
443:         }
444:     }
445: 
446:     /// @notice Checks that the `stakeUpdate` was valid at the given `blockNumber`
447:     function _validateStakeUpdateAtBlockNumber(
448:         StakeUpdate memory stakeUpdate,
449:         uint32 blockNumber
450:     ) internal pure {
451:         /**
452:          * Check that the update is valid for the given blockNumber:
453:          * - blockNumber should be >= the update block number
454:          * - the next update block number should be either 0 or strictly greater than blockNumber
455:          */
456:         require(
457:             blockNumber >= stakeUpdate.updateBlockNumber,
458:             "StakeRegistry._validateStakeUpdateAtBlockNumber: stakeUpdate is from after blockNumber"
459:         );
460:         require(
461:             stakeUpdate.nextUpdateBlockNumber == 0 || blockNumber < stakeUpdate.nextUpdateBlockNumber,
462:             "StakeRegistry._validateStakeUpdateAtBlockNumber: there is a newer stakeUpdate available before blockNumber"
463:         );
464:     }
465: 
466:     /**
467:      * @notice This function computes the total weight of the @param operator in the quorum @param quorumNumber.
468:      * @dev this method DOES NOT check that the quorum exists
469:      * @return `uint96` The weighted sum of the operator's shares across each strategy considered by the quorum
470:      * @return `bool` True if the operator meets the quorum's minimum stake
471:      */
472:     function _weightOfOperatorForQuorum(uint8 quorumNumber, address operator) internal virtual view returns (uint96, bool) {
473:         uint96 weight;
474:         uint256 stratsLength = strategyParamsLength(quorumNumber);
475:         StrategyParams memory strategyAndMultiplier;
476: 
477:         uint256[] memory strategyShares = delegation.getOperatorShares(operator, strategiesPerQuorum[quorumNumber]);
478:         for (uint256 i = 0; i < stratsLength; i++) {
479:             // accessing i^th StrategyParams struct for the quorumNumber
480:             strategyAndMultiplier = strategyParams[quorumNumber][i];
481: 
482:             // add the weight from the shares for this strategy to the total weight
483:             if (strategyShares[i] > 0) {
484:                 weight += uint96(strategyShares[i] * strategyAndMultiplier.multiplier / WEIGHTING_DIVISOR);
485:             }
486:         }
487: 
488:         // Return the weight, and `true` if the operator meets the quorum's minimum stake
489:         bool hasMinimumStake = weight >= minimumStakeForQuorum[quorumNumber];
490:         return (weight, hasMinimumStake);
491:     }
492: 
493:     /// @notice Returns `true` if the quorum has been initialized
494:     function _quorumExists(uint8 quorumNumber) internal view returns (bool) {
495:         return _totalStakeHistory[quorumNumber].length != 0;
496:     }
497: 
498:     /*******************************************************************************
499:                             VIEW FUNCTIONS
500:     *******************************************************************************/
501: 
502:     /**
503:      * @notice This function computes the total weight of the @param operator in the quorum @param quorumNumber.
504:      * @dev reverts if the quorum does not exist
505:      */
506:     function weightOfOperatorForQuorum(
507:         uint8 quorumNumber, 
508:         address operator
509:     ) public virtual view quorumExists(quorumNumber) returns (uint96) {
510:         (uint96 stake, ) = _weightOfOperatorForQuorum(quorumNumber, operator);
511:         return stake;
512:     }
513: 
514:     /// @notice Returns the length of the dynamic array stored in `strategyParams[quorumNumber]`.
515:     function strategyParamsLength(uint8 quorumNumber) public view returns (uint256) {
516:         return strategyParams[quorumNumber].length;
517:     }
518: 
519:     /// @notice Returns the strategy and weight multiplier for the `index`'th strategy in the quorum `quorumNumber`
520:     function strategyParamsByIndex(
521:         uint8 quorumNumber, 
522:         uint256 index
523:     ) public view returns (StrategyParams memory)
524:     {
525:         return strategyParams[quorumNumber][index];
526:     }
527: 
528:     /*******************************************************************************
529:                       VIEW FUNCTIONS - Operator Stake History
530:     *******************************************************************************/
531: 
532:     /**
533:      * @notice Returns the length of an operator's stake history for the given quorum
534:      */
535:     function getStakeHistoryLength(
536:         bytes32 operatorId,
537:         uint8 quorumNumber
538:     ) external view returns (uint256) {
539:         return operatorStakeHistory[operatorId][quorumNumber].length;
540:     }
541: 
542:     /**
543:      * @notice Returns the entire `operatorStakeHistory[operatorId][quorumNumber]` array.
544:      * @param operatorId The id of the operator of interest.
545:      * @param quorumNumber The quorum number to get the stake for.
546:      */
547:     function getStakeHistory(
548:         bytes32 operatorId, 
549:         uint8 quorumNumber
550:     ) external view returns (StakeUpdate[] memory) {
551:         return operatorStakeHistory[operatorId][quorumNumber];
552:     }
553: 
554:     /**
555:      * @notice Returns the most recent stake weight for the `operatorId` for quorum `quorumNumber`
556:      * @dev Function returns weight of **0** in the event that the operator has no stake history
557:      */
558:     function getCurrentStake(bytes32 operatorId, uint8 quorumNumber) external view returns (uint96) {
559:         StakeUpdate memory operatorStakeUpdate = getLatestStakeUpdate(operatorId, quorumNumber);
560:         return operatorStakeUpdate.stake;
561:     }
562: 
563:     /**
564:      * @notice Returns the most recent stake weight for the `operatorId` for a certain quorum
565:      * @dev Function returns an StakeUpdate struct with **every entry equal to 0** in the event that the operator has no stake history
566:      */
567:     function getLatestStakeUpdate(
568:         bytes32 operatorId,
569:         uint8 quorumNumber
570:     ) public view returns (StakeUpdate memory) {
571:         uint256 historyLength = operatorStakeHistory[operatorId][quorumNumber].length;
572:         StakeUpdate memory operatorStakeUpdate;
573:         if (historyLength == 0) {
574:             return operatorStakeUpdate;
575:         } else {
576:             operatorStakeUpdate = operatorStakeHistory[operatorId][quorumNumber][historyLength - 1];
577:             return operatorStakeUpdate;
578:         }
579:     }
580: 
581:     /**
582:      * @notice Returns the `index`-th entry in the `operatorStakeHistory[operatorId][quorumNumber]` array.
583:      * @param quorumNumber The quorum number to get the stake for.
584:      * @param operatorId The id of the operator of interest.
585:      * @param index Array index for lookup, within the dynamic array `operatorStakeHistory[operatorId][quorumNumber]`.
586:      * @dev Function will revert if `index` is out-of-bounds.
587:      */
588:     function getStakeUpdateAtIndex(
589:         uint8 quorumNumber,
590:         bytes32 operatorId,
591:         uint256 index
592:     ) external view returns (StakeUpdate memory) {
593:         return operatorStakeHistory[operatorId][quorumNumber][index];
594:     }
595: 
596:     /// @notice Returns the stake of the operator for the provided `quorumNumber` at the given `blockNumber`
597:     function getStakeAtBlockNumber(
598:         bytes32 operatorId,
599:         uint8 quorumNumber,
600:         uint32 blockNumber
601:     ) external view returns (uint96) {
602:         return
603:             operatorStakeHistory[operatorId][quorumNumber][
604:                 _getStakeUpdateIndexForOperatorAtBlockNumber(operatorId, quorumNumber, blockNumber)
605:             ].stake;
606:     }
607: 
608:     /// @notice Returns the indices of the operator stakes for the provided `quorumNumber` at the given `blockNumber`
609:     function getStakeUpdateIndexAtBlockNumber(
610:         bytes32 operatorId,
611:         uint8 quorumNumber,
612:         uint32 blockNumber
613:     ) external view returns (uint32) {
614:         return _getStakeUpdateIndexForOperatorAtBlockNumber(operatorId, quorumNumber, blockNumber);
615:     }
616: 
617:     /**
618:      * @notice Returns the stake weight corresponding to `operatorId` for quorum `quorumNumber`, at the
619:      * `index`-th entry in the `operatorStakeHistory[operatorId][quorumNumber]` array if it was the operator's
620:      * stake at `blockNumber`. Reverts otherwise.
621:      * @param quorumNumber The quorum number to get the stake for.
622:      * @param operatorId The id of the operator of interest.
623:      * @param index Array index for lookup, within the dynamic array `operatorStakeHistory[operatorId][quorumNumber]`.
624:      * @param blockNumber Block number to make sure the stake is from.
625:      * @dev Function will revert if `index` is out-of-bounds.
626:      */
627:     function getStakeAtBlockNumberAndIndex(
628:         uint8 quorumNumber,
629:         uint32 blockNumber,
630:         bytes32 operatorId,
631:         uint256 index
632:     ) external view returns (uint96) {
633:         StakeUpdate memory operatorStakeUpdate = operatorStakeHistory[operatorId][quorumNumber][index];
634:         _validateStakeUpdateAtBlockNumber(operatorStakeUpdate, blockNumber);
635:         return operatorStakeUpdate.stake;
636:     }
637: 
638:     /*******************************************************************************
639:                         VIEW FUNCTIONS - Total Stake History
640:     *******************************************************************************/
641: 
642:     /**
643:      * @notice Returns the length of the total stake history for the given quorum
644:      */
645:     function getTotalStakeHistoryLength(uint8 quorumNumber) external view returns (uint256) {
646:         return _totalStakeHistory[quorumNumber].length;
647:     }
648: 
649:     /**
650:      * @notice Returns the stake weight from the latest entry in `_totalStakeHistory` for quorum `quorumNumber`.
651:      * @dev Will revert if `_totalStakeHistory[quorumNumber]` is empty.
652:      */
653:     function getCurrentTotalStake(uint8 quorumNumber) external view returns (uint96) {
654:         return _totalStakeHistory[quorumNumber][_totalStakeHistory[quorumNumber].length - 1].stake;
655:     }
656: 
657:     /**
658:      * @notice Returns the `index`-th entry in the dynamic array of total stake, `_totalStakeHistory` for quorum `quorumNumber`.
659:      * @param quorumNumber The quorum number to get the stake for.
660:      * @param index Array index for lookup, within the dynamic array `_totalStakeHistory[quorumNumber]`.
661:      */
662:     function getTotalStakeUpdateAtIndex(
663:         uint8 quorumNumber,
664:         uint256 index
665:     ) external view returns (StakeUpdate memory) {
666:         return _totalStakeHistory[quorumNumber][index];
667:     } 
668: 
669:     /**
670:      * @notice Returns the total stake weight for quorum `quorumNumber`, at the `index`-th entry in the
671:      * `_totalStakeHistory[quorumNumber]` array if it was the stake at `blockNumber`. Reverts otherwise.
672:      * @param quorumNumber The quorum number to get the stake for.
673:      * @param index Array index for lookup, within the dynamic array `_totalStakeHistory[quorumNumber]`.
674:      * @param blockNumber Block number to make sure the stake is from.
675:      * @dev Function will revert if `index` is out-of-bounds.
676:      */
677:     function getTotalStakeAtBlockNumberFromIndex(
678:         uint8 quorumNumber,
679:         uint32 blockNumber,
680:         uint256 index
681:     ) external view returns (uint96) {
682:         StakeUpdate memory totalStakeUpdate = _totalStakeHistory[quorumNumber][index];
683:         _validateStakeUpdateAtBlockNumber(totalStakeUpdate, blockNumber);
684:         return totalStakeUpdate.stake;
685:     }
686: 
687:     /**
688:      * @notice Returns the indices of the total stakes for the provided `quorumNumbers` at the given `blockNumber`
689:      * @param blockNumber Block number to retrieve the stake indices from.
690:      * @param quorumNumbers The quorum numbers to get the stake indices for.
691:      * @dev Function will revert if there are no indices for the given `blockNumber`
692:      */
693:     function getTotalStakeIndicesAtBlockNumber(
694:         uint32 blockNumber,
695:         bytes calldata quorumNumbers
696:     ) external view returns (uint32[] memory) {
697:         uint32[] memory indices = new uint32[](quorumNumbers.length);
698:         for (uint256 i = 0; i < quorumNumbers.length; i++) {
699:             uint8 quorumNumber = uint8(quorumNumbers[i]);
700:             require(_quorumExists(quorumNumber), "StakeRegistry.getTotalStakeIndicesAtBlockNumber: quorum does not exist");
701:             require(
702:                 _totalStakeHistory[quorumNumber][0].updateBlockNumber <= blockNumber,
703:                 "StakeRegistry.getTotalStakeIndicesAtBlockNumber: quorum has no stake history at blockNumber"
704:             );
705:             uint256 length = _totalStakeHistory[quorumNumber].length;
706:             for (uint256 j = 0; j < length; j++) {
707:                 if (_totalStakeHistory[quorumNumber][length - j - 1].updateBlockNumber <= blockNumber) {
708:                     indices[i] = uint32(length - j - 1);
709:                     break;
710:                 }
711:             }
712:         }
713:         return indices;
714:     }
715: }
716: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import {IndexRegistryStorage} from "./IndexRegistryStorage.sol";
5: import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol";
6: 
7: /**
8:  * @title A `Registry` that keeps track of an ordered list of operators for each quorum
9:  * @author Layr Labs, Inc.
10:  */
11: contract IndexRegistry is IndexRegistryStorage {
12: 
13:     /// @notice when applied to a function, only allows the RegistryCoordinator to call it
14:     modifier onlyRegistryCoordinator() {
15:         require(msg.sender == address(registryCoordinator), "IndexRegistry.onlyRegistryCoordinator: caller is not the registry coordinator");
16:         _;
17:     }
18: 
19:     /// @notice sets the (immutable) `registryCoordinator` address
20:     constructor(
21:         IRegistryCoordinator _registryCoordinator
22:     ) IndexRegistryStorage(_registryCoordinator) {}
23: 
24:     /*******************************************************************************
25:                       EXTERNAL FUNCTIONS - REGISTRY COORDINATOR
26:     *******************************************************************************/
27: 
28:     /**
29:      * @notice Registers the operator with the specified `operatorId` for the quorums specified by `quorumNumbers`.
30:      * @param operatorId is the id of the operator that is being registered
31:      * @param quorumNumbers is the quorum numbers the operator is registered for
32:      * @return numOperatorsPerQuorum is a list of the number of operators (including the registering operator) in each of the quorums the operator is registered for
33:      * @dev access restricted to the RegistryCoordinator
34:      * @dev Preconditions (these are assumed, not validated in this contract):
35:      *         1) `quorumNumbers` has no duplicates
36:      *         2) `quorumNumbers.length` != 0
37:      *         3) `quorumNumbers` is ordered in ascending order
38:      *         4) the operator is not already registered
39:      */
40:     function registerOperator(
41:         bytes32 operatorId, 
42:         bytes calldata quorumNumbers
43:     ) public virtual onlyRegistryCoordinator returns(uint32[] memory) {
44:         uint32[] memory numOperatorsPerQuorum = new uint32[](quorumNumbers.length);
45: 
46:         for (uint256 i = 0; i < quorumNumbers.length; i++) {
47:             // Validate quorum exists and get current operator count
48:             uint8 quorumNumber = uint8(quorumNumbers[i]);
49:             uint256 historyLength = _operatorCountHistory[quorumNumber].length;
50:             require(historyLength != 0, "IndexRegistry.registerOperator: quorum does not exist");
51: 
52:             /**
53:              * Increase the number of operators currently active for this quorum,
54:              * and assign the operator to the last operatorIndex available
55:              */
56:             uint32 newOperatorCount = _increaseOperatorCount(quorumNumber);
57:             _assignOperatorToIndex({
58:                 operatorId: operatorId,
59:                 quorumNumber: quorumNumber,
60:                 operatorIndex: newOperatorCount - 1
61:             });
62: 
63:             // Record the current operator count for each quorum
64:             numOperatorsPerQuorum[i] = newOperatorCount;
65:         }
66: 
67:         return numOperatorsPerQuorum;
68:     }
69: 
70:     /**
71:      * @notice Deregisters the operator with the specified `operatorId` for the quorums specified by `quorumNumbers`.
72:      * @param operatorId is the id of the operator that is being deregistered
73:      * @param quorumNumbers is the quorum numbers the operator is deregistered for
74:      * @dev access restricted to the RegistryCoordinator
75:      * @dev Preconditions (these are assumed, not validated in this contract):
76:      *         1) `quorumNumbers` has no duplicates
77:      *         2) `quorumNumbers.length` != 0
78:      *         3) `quorumNumbers` is ordered in ascending order
79:      *         4) the operator is not already deregistered
80:      *         5) `quorumNumbers` is a subset of the quorumNumbers that the operator is registered for
81:      */
82:     function deregisterOperator(
83:         bytes32 operatorId, 
84:         bytes calldata quorumNumbers
85:     ) public virtual onlyRegistryCoordinator {
86:         for (uint256 i = 0; i < quorumNumbers.length; i++) {
87:             // Validate quorum exists and get the operatorIndex of the operator being deregistered
88:             uint8 quorumNumber = uint8(quorumNumbers[i]);
89:             uint256 historyLength = _operatorCountHistory[quorumNumber].length;
90:             require(historyLength != 0, "IndexRegistry.registerOperator: quorum does not exist");
91:             uint32 operatorIndexToRemove = currentOperatorIndex[quorumNumber][operatorId];
92: 
93:             /**
94:              * "Pop" the operator from the registry:
95:              * 1. Decrease the operator count for the quorum
96:              * 2. Remove the last operator associated with the count
97:              * 3. Place the last operator in the deregistered operator's old position
98:              */
99:             uint32 newOperatorCount = _decreaseOperatorCount(quorumNumber);
100:             bytes32 lastOperatorId = _popLastOperator(quorumNumber, newOperatorCount);
101:             if (operatorId != lastOperatorId) {
102:                 _assignOperatorToIndex({
103:                     operatorId: lastOperatorId,
104:                     quorumNumber: quorumNumber,
105:                     operatorIndex: operatorIndexToRemove
106:                 });
107:             }
108:         }
109:     }
110: 
111:     /**
112:      * @notice Initialize a quorum by pushing its first quorum update
113:      * @param quorumNumber The number of the new quorum
114:      */
115:     function initializeQuorum(uint8 quorumNumber) public virtual onlyRegistryCoordinator {
116:         require(_operatorCountHistory[quorumNumber].length == 0, "IndexRegistry.createQuorum: quorum already exists");
117: 
118:         _operatorCountHistory[quorumNumber].push(QuorumUpdate({
119:             numOperators: 0,
120:             fromBlockNumber: uint32(block.number)
121:         }));
122:     }
123: 
124:     /*******************************************************************************
125:                                 INTERNAL FUNCTIONS
126:     *******************************************************************************/
127: 
128:     /**
129:      * @notice Increases the historical operator count by 1 and returns the new count
130:      */
131:     function _increaseOperatorCount(uint8 quorumNumber) internal returns (uint32) {
132:         QuorumUpdate storage lastUpdate = _latestQuorumUpdate(quorumNumber);
133:         uint32 newOperatorCount = lastUpdate.numOperators + 1;
134:         
135:         _updateOperatorCountHistory(quorumNumber, lastUpdate, newOperatorCount);
136: 
137:         // If this is the first time we're using this operatorIndex, push its first update
138:         // This maintains an invariant: existing indices have nonzero history
139:         if (_operatorIndexHistory[quorumNumber][newOperatorCount - 1].length == 0) {
140:             _operatorIndexHistory[quorumNumber][newOperatorCount - 1].push(OperatorUpdate({
141:                 operatorId: OPERATOR_DOES_NOT_EXIST_ID,
142:                 fromBlockNumber: uint32(block.number)
143:             }));
144:         }
145: 
146:         return newOperatorCount;
147:     }
148: 
149:     /**
150:      * @notice Decreases the historical operator count by 1 and returns the new count
151:      */
152:     function _decreaseOperatorCount(uint8 quorumNumber) internal returns (uint32) {
153:         QuorumUpdate storage lastUpdate = _latestQuorumUpdate(quorumNumber);
154:         uint32 newOperatorCount = lastUpdate.numOperators - 1;
155:         
156:         _updateOperatorCountHistory(quorumNumber, lastUpdate, newOperatorCount);
157:         
158:         return newOperatorCount;
159:     }
160: 
161:     /**
162:      * @notice Update `_operatorCountHistory` with a new operator count
163:      * @dev If the lastUpdate was made in the this block, update the entry.
164:      * Otherwise, push a new historical entry.
165:      */
166:     function _updateOperatorCountHistory(
167:         uint8 quorumNumber,
168:         QuorumUpdate storage lastUpdate,
169:         uint32 newOperatorCount
170:     ) internal {
171:         if (lastUpdate.fromBlockNumber == uint32(block.number)) {
172:             lastUpdate.numOperators = newOperatorCount;
173:         } else {
174:             _operatorCountHistory[quorumNumber].push(QuorumUpdate({
175:                 numOperators: newOperatorCount,
176:                 fromBlockNumber: uint32(block.number)
177:             }));
178:         }
179:     }
180: 
181:     /**
182:      * @notice For a given quorum and operatorIndex, pop and return the last operatorId in the history
183:      * @dev The last entry's operatorId is updated to OPERATOR_DOES_NOT_EXIST_ID
184:      * @return The removed operatorId
185:      */
186:     function _popLastOperator(uint8 quorumNumber, uint32 operatorIndex) internal returns (bytes32) {
187:         OperatorUpdate storage lastUpdate = _latestOperatorIndexUpdate(quorumNumber, operatorIndex);
188:         bytes32 removedOperatorId = lastUpdate.operatorId;
189: 
190:         // Set the current operator id for this operatorIndex to 0
191:         _updateOperatorIndexHistory(quorumNumber, operatorIndex, lastUpdate, OPERATOR_DOES_NOT_EXIST_ID);
192: 
193:         return removedOperatorId;
194:     }
195: 
196:     /**
197:      * @notice Assign an operator to an index and update the index history
198:      * @param operatorId operatorId of the operator to update
199:      * @param quorumNumber quorumNumber of the operator to update
200:      * @param operatorIndex the latest index of that operator in the list of operators registered for this quorum
201:      */ 
202:     function _assignOperatorToIndex(bytes32 operatorId, uint8 quorumNumber, uint32 operatorIndex) internal {
203:         OperatorUpdate storage lastUpdate = _latestOperatorIndexUpdate(quorumNumber, operatorIndex);
204: 
205:         _updateOperatorIndexHistory(quorumNumber, operatorIndex, lastUpdate, operatorId);
206: 
207:         // Assign the operator to their new current operatorIndex
208:         currentOperatorIndex[quorumNumber][operatorId] = operatorIndex;
209:         emit QuorumIndexUpdate(operatorId, quorumNumber, operatorIndex);
210:     }
211: 
212:     /**
213:      * @notice Update `_operatorIndexHistory` with a new operator id for the current block
214:      * @dev If the lastUpdate was made in the this block, update the entry.
215:      * Otherwise, push a new historical entry.
216:      */
217:     function _updateOperatorIndexHistory(
218:         uint8 quorumNumber,
219:         uint32 operatorIndex,
220:         OperatorUpdate storage lastUpdate,
221:         bytes32 newOperatorId
222:     ) internal {
223:         if (lastUpdate.fromBlockNumber == uint32(block.number)) {
224:             lastUpdate.operatorId = newOperatorId;
225:         } else {
226:             _operatorIndexHistory[quorumNumber][operatorIndex].push(OperatorUpdate({
227:                 operatorId: newOperatorId,
228:                 fromBlockNumber: uint32(block.number)
229:             }));
230:         }
231:     }
232: 
233:     /// @notice Returns the most recent operator count update for a quorum
234:     /// @dev Reverts if the quorum does not exist (history length == 0)
235:     function _latestQuorumUpdate(uint8 quorumNumber) internal view returns (QuorumUpdate storage) {
236:         uint256 historyLength = _operatorCountHistory[quorumNumber].length;
237:         return _operatorCountHistory[quorumNumber][historyLength - 1];
238:     }
239: 
240:     /// @notice Returns the most recent operator id update for an index
241:     /// @dev Reverts if the index has never been used (history length == 0)
242:     function _latestOperatorIndexUpdate(uint8 quorumNumber, uint32 operatorIndex) internal view returns (OperatorUpdate storage) {
243:         uint256 historyLength = _operatorIndexHistory[quorumNumber][operatorIndex].length;
244:         return _operatorIndexHistory[quorumNumber][operatorIndex][historyLength - 1];
245:     }
246: 
247:     /**
248:      * @notice Returns the total number of operators of the service for the given `quorumNumber` at the given `blockNumber`
249:      * @dev Reverts if the quorum does not exist, or if the blockNumber is from before the quorum existed
250:      */
251:     function _operatorCountAtBlockNumber(
252:         uint8 quorumNumber, 
253:         uint32 blockNumber
254:     ) internal view returns (uint32){
255:         uint256 historyLength = _operatorCountHistory[quorumNumber].length;
256: 
257:         // Loop backwards through _operatorCountHistory until we find an entry that preceeds `blockNumber`
258:         for (uint256 i = historyLength; i > 0; i--) {
259:             QuorumUpdate memory quorumUpdate = _operatorCountHistory[quorumNumber][i - 1];
260: 
261:             if (quorumUpdate.fromBlockNumber <= blockNumber) {
262:                 return quorumUpdate.numOperators;
263:             }
264:         }
265:         
266:         revert("IndexRegistry._operatorCountAtBlockNumber: quorum did not exist at given block number");
267:     }
268:     
269:     /**
270:      * @return operatorId at the given `operatorIndex` at the given `blockNumber` for the given `quorumNumber`
271:      * Precondition: requires that the operatorIndex was used active at the given block number for quorum
272:      */
273:     function _operatorIdForIndexAtBlockNumber(
274:         uint8 quorumNumber, 
275:         uint32 operatorIndex, 
276:         uint32 blockNumber
277:     ) internal view returns(bytes32) {
278:         uint256 historyLength = _operatorIndexHistory[quorumNumber][operatorIndex].length;
279: 
280:         // Loop backward through _operatorIndexHistory until we find an entry that preceeds `blockNumber`
281:         for (uint256 i = historyLength; i > 0; i--) {
282:             OperatorUpdate memory operatorIndexUpdate = _operatorIndexHistory[quorumNumber][operatorIndex][i - 1];
283: 
284:             if (operatorIndexUpdate.fromBlockNumber <= blockNumber) {
285:                 // Special case: this will be OPERATOR_DOES_NOT_EXIST_ID if this operatorIndex was not used at the block number
286:                 return operatorIndexUpdate.operatorId;
287:             }
288:         }
289: 
290:         // we should only hit this if the operatorIndex was never used before blockNumber
291:         return OPERATOR_DOES_NOT_EXIST_ID;
292:     }
293: 
294:     /*******************************************************************************
295:                                  VIEW FUNCTIONS
296:     *******************************************************************************/
297: 
298:     /// @notice Returns the _operatorIndexHistory entry for the specified `operatorIndex` and `quorumNumber`
299:     /// at the specified `arrayIndex`
300:     function getOperatorUpdateAtIndex(uint8 quorumNumber, uint32 operatorIndex, uint32 arrayIndex) external view returns (OperatorUpdate memory) {
301:         return _operatorIndexHistory[quorumNumber][operatorIndex][arrayIndex];
302:     }
303: 
304:     /// @notice Returns the _operatorCountHistory entry for the specified `quorumNumber` at the specified `quorumIndex`
305:     function getQuorumUpdateAtIndex(uint8 quorumNumber, uint32 quorumIndex) external view returns (QuorumUpdate memory) {
306:         return _operatorCountHistory[quorumNumber][quorumIndex];
307:     }
308: 
309:     /// @notice Returns the most recent QuorumUpdate entry for the specified quorumNumber
310:     /// @dev Reverts if the quorum does not exist
311:     function getLatestQuorumUpdate(uint8 quorumNumber) external view returns (QuorumUpdate memory) {
312:         return _latestQuorumUpdate(quorumNumber);
313:     }
314: 
315:     /// @notice Returns the most recent OperatorUpdate entry for the specified quorumNumber and operatorIndex
316:     /// @dev Reverts if there is no update for the given operatorIndex
317:     function getLatestOperatorUpdate(uint8 quorumNumber, uint32 operatorIndex) external view returns (OperatorUpdate memory) {
318:         return _latestOperatorIndexUpdate(quorumNumber, operatorIndex);
319:     }
320: 
321:     /// @notice Returns an ordered list of operators of the services for the given `quorumNumber` at the given `blockNumber`
322:     function getOperatorListAtBlockNumber(
323:         uint8 quorumNumber, 
324:         uint32 blockNumber
325:     ) external view returns (bytes32[] memory){
326:         uint32 operatorCount = _operatorCountAtBlockNumber(quorumNumber, blockNumber);
327:         bytes32[] memory operatorList = new bytes32[](operatorCount);
328:         for (uint256 i = 0; i < operatorCount; i++) {
329:             operatorList[i] = _operatorIdForIndexAtBlockNumber(quorumNumber, uint32(i), blockNumber);
330:             require(
331:                 operatorList[i] != OPERATOR_DOES_NOT_EXIST_ID, 
332:                 "IndexRegistry.getOperatorListAtBlockNumber: operator does not exist at the given block number"
333:             );
334:         }
335:         return operatorList;
336:     }
337: 
338:     /// @notice Returns the total number of operators for a given `quorumNumber`
339:     /// @dev This will revert if the quorum does not exist
340:     function totalOperatorsForQuorum(uint8 quorumNumber) external view returns (uint32){
341:         return _latestQuorumUpdate(quorumNumber).numOperators;
342:     }
343: }
344: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import {Initializable} from "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
5: 
6: import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol";
7: import {IIndexRegistry} from "./interfaces/IIndexRegistry.sol";
8: 
9: /**
10:  * @title Storage variables for the `IndexRegistry` contract.
11:  * @author Layr Labs, Inc.
12:  * @notice This storage contract is separate from the logic to simplify the upgrade process.
13:  */
14: abstract contract IndexRegistryStorage is Initializable, IIndexRegistry {
15: 
16:     /// @notice The value that is returned when an operator does not exist at an index at a certain block
17:     bytes32 public constant OPERATOR_DOES_NOT_EXIST_ID = bytes32(0);
18: 
19:     /// @notice The RegistryCoordinator contract for this middleware
20:     address public immutable registryCoordinator;
21: 
22:     /// @notice maps quorumNumber => operator id => current operatorIndex
23:     /// NOTE: This mapping is NOT updated when an operator is deregistered,
24:     /// so it's possible that an index retrieved from this mapping is inaccurate.
25:     /// If you're querying for an operator that might be deregistered, ALWAYS 
26:     /// check this index against the latest `_operatorIndexHistory` entry
27:     mapping(uint8 => mapping(bytes32 => uint32)) public currentOperatorIndex;
28:     /// @notice maps quorumNumber => operatorIndex => historical operator ids at that index
29:     mapping(uint8 => mapping(uint32 => OperatorUpdate[])) internal _operatorIndexHistory;
30:     /// @notice maps quorumNumber => historical number of unique registered operators
31:     mapping(uint8 => QuorumUpdate[]) internal _operatorCountHistory;
32: 
33:     constructor(
34:         IRegistryCoordinator _registryCoordinator
35:     ){
36:         registryCoordinator = address(_registryCoordinator);
37:         // disable initializers so that the implementation contract cannot be initialized
38:         _disableInitializers();
39:     }
40: 
41:     // storage gap for upgradeability
42:     uint256[47] private __GAP;
43: }
44: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import {IBLSSignatureChecker} from "./interfaces/IBLSSignatureChecker.sol";
5: import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol";
6: import {IBLSApkRegistry} from "./interfaces/IBLSApkRegistry.sol";
7: import {IStakeRegistry, IDelegationManager} from "./interfaces/IStakeRegistry.sol";
8: 
9: import {BitmapUtils} from "./libraries/BitmapUtils.sol";
10: import {BN254} from "./libraries/BN254.sol";
11: 
12: /**
13:  * @title Used for checking BLS aggregate signatures from the operators of a `BLSRegistry`.
14:  * @author Layr Labs, Inc.
15:  * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
16:  * @notice This is the contract for checking the validity of aggregate operator signatures.
17:  */
18: contract BLSSignatureChecker is IBLSSignatureChecker {
19:     using BN254 for BN254.G1Point;
20:     
21:     // CONSTANTS & IMMUTABLES
22: 
23:     // gas cost of multiplying 2 pairings
24:     uint256 internal constant PAIRING_EQUALITY_CHECK_GAS = 120000;
25: 
26:     IRegistryCoordinator public immutable registryCoordinator;
27:     IStakeRegistry public immutable stakeRegistry;
28:     IBLSApkRegistry public immutable blsApkRegistry;
29:     IDelegationManager public immutable delegation;
30:     /// @notice If true, check the staleness of the operator stakes and that its within the delegation withdrawalDelayBlocks window.
31:     bool public staleStakesForbidden;
32: 
33:     modifier onlyCoordinatorOwner() {
34:         require(msg.sender == registryCoordinator.owner(), "BLSSignatureChecker.onlyCoordinatorOwner: caller is not the owner of the registryCoordinator");
35:         _;
36:     }
37: 
38:     constructor(IRegistryCoordinator _registryCoordinator) {
39:         registryCoordinator = _registryCoordinator;
40:         stakeRegistry = _registryCoordinator.stakeRegistry();
41:         blsApkRegistry = _registryCoordinator.blsApkRegistry();
42:         delegation = stakeRegistry.delegation();
43:         
44:         staleStakesForbidden = true;
45:     }
46: 
47:     /**
48:      * RegistryCoordinator owner can either enforce or not that operator stakes are staler
49:      * than the delegation.minWithdrawalDelayBlocks() window.
50:      * @param value to toggle staleStakesForbidden
51:      */
52:     function setStaleStakesForbidden(bool value) external onlyCoordinatorOwner {
53:         staleStakesForbidden = value;
54:         emit StaleStakesForbiddenUpdate(value);
55:     }
56: 
57:     struct NonSignerInfo {
58:         uint256[] quorumBitmaps;
59:         bytes32[] pubkeyHashes;
60:     }
61: 
62:     /**
63:      * @notice This function is called by disperser when it has aggregated all the signatures of the operators
64:      * that are part of the quorum for a particular taskNumber and is asserting them into onchain. The function
65:      * checks that the claim for aggregated signatures are valid.
66:      *
67:      * The thesis of this procedure entails:
68:      * - getting the aggregated pubkey of all registered nodes at the time of pre-commit by the
69:      * disperser (represented by apk in the parameters),
70:      * - subtracting the pubkeys of all the signers not in the quorum (nonSignerPubkeys) and storing 
71:      * the output in apk to get aggregated pubkey of all operators that are part of quorum.
72:      * - use this aggregated pubkey to verify the aggregated signature under BLS scheme.
73:      * 
74:      * @dev Before signature verification, the function verifies operator stake information.  This includes ensuring that the provided `referenceBlockNumber`
75:      * is correct, i.e., ensure that the stake returned from the specified block number is recent enough and that the stake is either the most recent update
76:      * for the total stake (of the operator) or latest before the referenceBlockNumber.
77:      * @param msgHash is the hash being signed
78:      * @dev NOTE: Be careful to ensure `msgHash` is collision-resistant! This method does not hash 
79:      * `msgHash` in any way, so if an attacker is able to pass in an arbitrary value, they may be able
80:      * to tamper with signature verification.
81:      * @param quorumNumbers is the bytes array of quorum numbers that are being signed for
82:      * @param referenceBlockNumber is the block number at which the stake information is being verified
83:      * @param params is the struct containing information on nonsigners, stakes, quorum apks, and the aggregate signature
84:      * @return quorumStakeTotals is the struct containing the total and signed stake for each quorum
85:      * @return signatoryRecordHash is the hash of the signatory record, which is used for fraud proofs
86:      */
87:     function checkSignatures(
88:         bytes32 msgHash, 
89:         bytes calldata quorumNumbers,
90:         uint32 referenceBlockNumber, 
91:         NonSignerStakesAndSignature memory params
92:     ) 
93:         public 
94:         view
95:         returns (
96:             QuorumStakeTotals memory,
97:             bytes32
98:         )
99:     {
100:         require(quorumNumbers.length != 0, "BLSSignatureChecker.checkSignatures: empty quorum input");
101: 
102:         require(
103:             (quorumNumbers.length == params.quorumApks.length) &&
104:             (quorumNumbers.length == params.quorumApkIndices.length) &&
105:             (quorumNumbers.length == params.totalStakeIndices.length) &&
106:             (quorumNumbers.length == params.nonSignerStakeIndices.length),
107:             "BLSSignatureChecker.checkSignatures: input quorum length mismatch"
108:         );
109: 
110:         require(
111:             params.nonSignerPubkeys.length == params.nonSignerQuorumBitmapIndices.length,
112:             "BLSSignatureChecker.checkSignatures: input nonsigner length mismatch"
113:         );
114: 
115:         require(referenceBlockNumber < uint32(block.number), "BLSSignatureChecker.checkSignatures: invalid reference block");
116: 
117:         // This method needs to calculate the aggregate pubkey for all signing operators across
118:         // all signing quorums. To do that, we can query the aggregate pubkey for each quorum
119:         // and subtract out the pubkey for each nonsigning operator registered to that quorum.
120:         //
121:         // In practice, we do this in reverse - calculating an aggregate pubkey for all nonsigners,
122:         // negating that pubkey, then adding the aggregate pubkey for each quorum.
123:         BN254.G1Point memory apk = BN254.G1Point(0, 0);
124: 
125:         // For each quorum, we're also going to query the total stake for all registered operators
126:         // at the referenceBlockNumber, and derive the stake held by signers by subtracting out
127:         // stakes held by nonsigners.
128:         QuorumStakeTotals memory stakeTotals;
129:         stakeTotals.totalStakeForQuorum = new uint96[](quorumNumbers.length);
130:         stakeTotals.signedStakeForQuorum = new uint96[](quorumNumbers.length);
131: 
132:         NonSignerInfo memory nonSigners;
133:         nonSigners.quorumBitmaps = new uint256[](params.nonSignerPubkeys.length);
134:         nonSigners.pubkeyHashes = new bytes32[](params.nonSignerPubkeys.length);
135: 
136:         {
137:             // Get a bitmap of the quorums signing the message, and validate that
138:             // quorumNumbers contains only unique, valid quorum numbers
139:             uint256 signingQuorumBitmap = BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, registryCoordinator.quorumCount());
140: 
141:             for (uint256 j = 0; j < params.nonSignerPubkeys.length; j++) {
142:                 // The nonsigner's pubkey hash doubles as their operatorId
143:                 // The check below validates that these operatorIds are sorted (and therefore
144:                 // free of duplicates)
145:                 nonSigners.pubkeyHashes[j] = params.nonSignerPubkeys[j].hashG1Point();
146:                 if (j != 0) {
147:                     require(
148:                         uint256(nonSigners.pubkeyHashes[j]) > uint256(nonSigners.pubkeyHashes[j - 1]),
149:                         "BLSSignatureChecker.checkSignatures: nonSignerPubkeys not sorted"
150:                     );
151:                 }
152: 
153:                 // Get the quorums the nonsigner was registered for at referenceBlockNumber
154:                 nonSigners.quorumBitmaps[j] = 
155:                     registryCoordinator.getQuorumBitmapAtBlockNumberByIndex({
156:                         operatorId: nonSigners.pubkeyHashes[j],
157:                         blockNumber: referenceBlockNumber,
158:                         index: params.nonSignerQuorumBitmapIndices[j]
159:                     });
160: 
161:                 // Add the nonsigner's pubkey to the total apk, multiplied by the number
162:                 // of quorums they have in common with the signing quorums, because their
163:                 // public key will be a part of each signing quorum's aggregate pubkey
164:                 apk = apk.plus(
165:                     params.nonSignerPubkeys[j]
166:                         .scalar_mul_tiny(
167:                             BitmapUtils.countNumOnes(nonSigners.quorumBitmaps[j] & signingQuorumBitmap) 
168:                         )
169:                 );
170:             }
171:         }
172: 
173:         // Negate the sum of the nonsigner aggregate pubkeys - from here, we'll add the
174:         // total aggregate pubkey from each quorum. Because the nonsigners' pubkeys are
175:         // in these quorums, this initial negation ensures they're cancelled out
176:         apk = apk.negate();
177: 
178:         /**
179:          * For each quorum (at referenceBlockNumber):
180:          * - add the apk for all registered operators
181:          * - query the total stake for each quorum
182:          * - subtract the stake for each nonsigner to calculate the stake belonging to signers
183:          */
184:         {
185:             bool _staleStakesForbidden = staleStakesForbidden;
186:             uint256 withdrawalDelayBlocks = _staleStakesForbidden ? delegation.minWithdrawalDelayBlocks() : 0;
187: 
188:             for (uint256 i = 0; i < quorumNumbers.length; i++) {
189:                 // If we're disallowing stale stake updates, check that each quorum's last update block
190:                 // is within withdrawalDelayBlocks
191:                 if (_staleStakesForbidden) {
192:                     require(
193:                         registryCoordinator.quorumUpdateBlockNumber(uint8(quorumNumbers[i])) + withdrawalDelayBlocks >= referenceBlockNumber,
194:                         "BLSSignatureChecker.checkSignatures: StakeRegistry updates must be within withdrawalDelayBlocks window"
195:                     );
196:                 }
197: 
198:                 // Validate params.quorumApks is correct for this quorum at the referenceBlockNumber,
199:                 // then add it to the total apk
200:                 require(
201:                     bytes24(params.quorumApks[i].hashG1Point()) == 
202:                         blsApkRegistry.getApkHashAtBlockNumberAndIndex({
203:                             quorumNumber: uint8(quorumNumbers[i]),
204:                             blockNumber: referenceBlockNumber,
205:                             index: params.quorumApkIndices[i]
206:                         }),
207:                     "BLSSignatureChecker.checkSignatures: quorumApk hash in storage does not match provided quorum apk"
208:                 );
209:                 apk = apk.plus(params.quorumApks[i]);
210: 
211:                 // Get the total and starting signed stake for the quorum at referenceBlockNumber
212:                 stakeTotals.totalStakeForQuorum[i] = 
213:                     stakeRegistry.getTotalStakeAtBlockNumberFromIndex({
214:                         quorumNumber: uint8(quorumNumbers[i]),
215:                         blockNumber: referenceBlockNumber,
216:                         index: params.totalStakeIndices[i]
217:                     });
218:                 stakeTotals.signedStakeForQuorum[i] = stakeTotals.totalStakeForQuorum[i];
219: 
220:                 // Keep track of the nonSigners index in the quorum
221:                 uint256 nonSignerForQuorumIndex = 0;
222:                 
223:                 // loop through all nonSigners, checking that they are a part of the quorum via their quorumBitmap
224:                 // if so, load their stake at referenceBlockNumber and subtract it from running stake signed
225:                 for (uint256 j = 0; j < params.nonSignerPubkeys.length; j++) {
226:                     // if the nonSigner is a part of the quorum, subtract their stake from the running total
227:                     if (BitmapUtils.isSet(nonSigners.quorumBitmaps[j], uint8(quorumNumbers[i]))) {
228:                         stakeTotals.signedStakeForQuorum[i] -=
229:                             stakeRegistry.getStakeAtBlockNumberAndIndex({
230:                                 quorumNumber: uint8(quorumNumbers[i]),
231:                                 blockNumber: referenceBlockNumber,
232:                                 operatorId: nonSigners.pubkeyHashes[j],
233:                                 index: params.nonSignerStakeIndices[i][nonSignerForQuorumIndex]
234:                             });
235:                         unchecked {
236:                             ++nonSignerForQuorumIndex;
237:                         }
238:                     }
239:                 }
240:             }
241:         }
242:         {
243:             // verify the signature
244:             (bool pairingSuccessful, bool signatureIsValid) = trySignatureAndApkVerification(
245:                 msgHash, 
246:                 apk, 
247:                 params.apkG2, 
248:                 params.sigma
249:             );
250:             require(pairingSuccessful, "BLSSignatureChecker.checkSignatures: pairing precompile call failed");
251:             require(signatureIsValid, "BLSSignatureChecker.checkSignatures: signature is invalid");
252:         }
253:         // set signatoryRecordHash variable used for fraudproofs
254:         bytes32 signatoryRecordHash = keccak256(abi.encodePacked(referenceBlockNumber, nonSigners.pubkeyHashes));
255: 
256:         // return the total stakes that signed for each quorum, and a hash of the information required to prove the exact signers and stake
257:         return (stakeTotals, signatoryRecordHash);
258:     }
259: 
260:     /**
261:      * trySignatureAndApkVerification verifies a BLS aggregate signature and the veracity of a calculated G1 Public key
262:      * @param msgHash is the hash being signed
263:      * @param apk is the claimed G1 public key
264:      * @param apkG2 is provided G2 public key
265:      * @param sigma is the G1 point signature
266:      * @return pairingSuccessful is true if the pairing precompile call was successful
267:      * @return siganatureIsValid is true if the signature is valid
268:      */
269:     function trySignatureAndApkVerification(
270:         bytes32 msgHash,
271:         BN254.G1Point memory apk,
272:         BN254.G2Point memory apkG2,
273:         BN254.G1Point memory sigma
274:     ) public view returns(bool pairingSuccessful, bool siganatureIsValid) {
275:         // gamma = keccak256(abi.encodePacked(msgHash, apk, apkG2, sigma))
276:         uint256 gamma = uint256(keccak256(abi.encodePacked(msgHash, apk.X, apk.Y, apkG2.X[0], apkG2.X[1], apkG2.Y[0], apkG2.Y[1], sigma.X, sigma.Y))) % BN254.FR_MODULUS;
277:         // verify the signature
278:         (pairingSuccessful, siganatureIsValid) = BN254.safePairing(
279:                 sigma.plus(apk.scalar_mul(gamma)),
280:                 BN254.negGeneratorG2(),
281:                 BN254.hashToG1(msgHash).plus(BN254.generatorG1().scalar_mul(gamma)),
282:                 apkG2,
283:                 PAIRING_EQUALITY_CHECK_GAS
284:             );
285:     }
286: 
287:     // storage gap for upgradeability
288:     // slither-disable-next-line shadowing-state
289:     uint256[49] private __GAP;
290: }
291: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: 
3: pragma solidity =0.8.12;
4: 
5: /**
6:  * @title Library for Bitmap utilities such as converting between an array of bytes and a bitmap and finding the number of 1s in a bitmap.
7:  * @author Layr Labs, Inc.
8:  */
9: library BitmapUtils {
10:     /**
11:      * @notice Byte arrays are meant to contain unique bytes.
12:      * If the array length exceeds 256, then it's impossible for all entries to be unique.
13:      * This constant captures the max allowed array length (inclusive, i.e. 256 is allowed).
14:      */
15:     uint256 internal constant MAX_BYTE_ARRAY_LENGTH = 256;
16: 
17:     /**
18:      * @notice Converts an ordered array of bytes into a bitmap.
19:      * @param orderedBytesArray The array of bytes to convert/compress into a bitmap. Must be in strictly ascending order.
20:      * @return The resulting bitmap.
21:      * @dev Each byte in the input is processed as indicating a single bit to flip in the bitmap.
22:      * @dev This function will eventually revert in the event that the `orderedBytesArray` is not properly ordered (in ascending order).
23:      * @dev This function will also revert if the `orderedBytesArray` input contains any duplicate entries (i.e. duplicate bytes).
24:      */
25:     function orderedBytesArrayToBitmap(bytes memory orderedBytesArray) internal pure returns (uint256) {
26:         // sanity-check on input. a too-long input would fail later on due to having duplicate entry(s)
27:         require(orderedBytesArray.length <= MAX_BYTE_ARRAY_LENGTH,
28:             "BitmapUtils.orderedBytesArrayToBitmap: orderedBytesArray is too long");
29: 
30:         // return empty bitmap early if length of array is 0
31:         if (orderedBytesArray.length == 0) {
32:             return uint256(0);
33:         }
34: 
35:         // initialize the empty bitmap, to be built inside the loop
36:         uint256 bitmap;
37:         // initialize an empty uint256 to be used as a bitmask inside the loop
38:         uint256 bitMask;
39: 
40:         // perform the 0-th loop iteration with the ordering check *omitted* (since it is unnecessary / will always pass)
41:         // construct a single-bit mask from the numerical value of the 0th byte of the array, and immediately add it to the bitmap
42:         bitmap = uint256(1 << uint8(orderedBytesArray[0]));
43: 
44:         // loop through each byte in the array to construct the bitmap
45:         for (uint256 i = 1; i < orderedBytesArray.length; ++i) {
46:             // construct a single-bit mask from the numerical value of the next byte of the array
47:             bitMask = uint256(1 << uint8(orderedBytesArray[i]));
48:             // check strictly ascending array ordering by comparing the mask to the bitmap so far (revert if mask isn't greater than bitmap)
49:             require(bitMask > bitmap, "BitmapUtils.orderedBytesArrayToBitmap: orderedBytesArray is not ordered");
50:             // add the entry to the bitmap
51:             bitmap = (bitmap | bitMask);
52:         }
53:         return bitmap;
54:     }
55: 
56:     /**
57:      * @notice Converts an ordered byte array to a bitmap, validating that all bits are less than `bitUpperBound`
58:      * @param orderedBytesArray The array to convert to a bitmap; must be in strictly ascending order
59:      * @param bitUpperBound The exclusive largest bit. Each bit must be strictly less than this value.
60:      * @dev Reverts if bitmap contains a bit greater than or equal to `bitUpperBound`
61:      */
62:     function orderedBytesArrayToBitmap(bytes memory orderedBytesArray, uint8 bitUpperBound) internal pure returns (uint256) {
63:         uint256 bitmap = orderedBytesArrayToBitmap(orderedBytesArray);
64: 
65:         require((1 << bitUpperBound) > bitmap, 
66:             "BitmapUtils.orderedBytesArrayToBitmap: bitmap exceeds max value"
67:         );
68: 
69:         return bitmap;
70:     }
71: 
72:     /**
73:      * @notice Utility function for checking if a bytes array is strictly ordered, in ascending order.
74:      * @param bytesArray the bytes array of interest
75:      * @return Returns 'true' if the array is ordered in strictly ascending order, and 'false' otherwise.
76:      * @dev This function returns 'true' for the edge case of the `bytesArray` having zero length.
77:      * It also returns 'false' early for arrays with length in excess of MAX_BYTE_ARRAY_LENGTH (i.e. so long that they cannot be strictly ordered)
78:      */
79:     function isArrayStrictlyAscendingOrdered(bytes calldata bytesArray) internal pure returns (bool) {
80:         // Return early if the array is too long, or has a length of 0
81:         if (bytesArray.length > MAX_BYTE_ARRAY_LENGTH) {
82:             return false;
83:         }
84: 
85:         if (bytesArray.length == 0) {
86:             return true;
87:         }
88: 
89:         // Perform the 0-th loop iteration by pulling the 0th byte out of the array
90:         bytes1 singleByte = bytesArray[0];
91: 
92:         // For each byte, validate that each entry is *strictly greater than* the previous
93:         // If it isn't, return false as the array is not ordered
94:         for (uint256 i = 1; i < bytesArray.length; ++i) {
95:             if (uint256(uint8(bytesArray[i])) <= uint256(uint8(singleByte))) {
96:                 return false;
97:             }
98:             
99:             // Pull the next byte out of the array
100:             singleByte = bytesArray[i];
101:         }
102:         
103:         return true;
104:     }
105: 
106:     /**
107:      * @notice Converts a bitmap into an array of bytes.
108:      * @param bitmap The bitmap to decompress/convert to an array of bytes.
109:      * @return bytesArray The resulting bitmap array of bytes.
110:      * @dev Each byte in the input is processed as indicating a single bit to flip in the bitmap
111:      */
112:     function bitmapToBytesArray(uint256 bitmap) internal pure returns (bytes memory /*bytesArray*/) {
113:         // initialize an empty uint256 to be used as a bitmask inside the loop
114:         uint256 bitMask;
115:         // allocate only the needed amount of memory
116:         bytes memory bytesArray = new bytes(countNumOnes(bitmap));
117:         // track the array index to assign to
118:         uint256 arrayIndex = 0;
119:         /**
120:          * loop through each index in the bitmap to construct the array,
121:          * but short-circuit the loop if we reach the number of ones and thus are done
122:          * assigning to memory
123:          */
124:         for (uint256 i = 0; (arrayIndex < bytesArray.length) && (i < 256); ++i) {
125:             // construct a single-bit mask for the i-th bit
126:             bitMask = uint256(1 << i);
127:             // check if the i-th bit is flipped in the bitmap
128:             if (bitmap & bitMask != 0) {
129:                 // if the i-th bit is flipped, then add a byte encoding the value 'i' to the `bytesArray`
130:                 bytesArray[arrayIndex] = bytes1(uint8(i));
131:                 // increment the bytesArray slot since we've assigned one more byte of memory
132:                 unchecked{ ++arrayIndex; }
133:             }
134:         }
135:         return bytesArray;
136:     }
137: 
138:     /// @return count number of ones in binary representation of `n`
139:     function countNumOnes(uint256 n) internal pure returns (uint16) {
140:         uint16 count = 0;
141:         while (n > 0) {
142:             n &= (n - 1); // Clear the least significant bit (turn off the rightmost set bit).
143:             count++; // Increment the count for each cleared bit (each one encountered).
144:         }
145:         return count; // Return the total count of ones in the binary representation of n.
146:     }
147: 
148:     /// @notice Returns `true` if `bit` is in `bitmap`. Returns `false` otherwise.
149:     function isSet(uint256 bitmap, uint8 bit) internal pure returns (bool) {
150:         return 1 == ((bitmap >> bit) & 1);
151:     }
152:     
153:     /**
154:      * @notice Returns a copy of `bitmap` with `bit` set. 
155:      * @dev IMPORTANT: we're dealing with stack values here, so this doesn't modify
156:      * the original bitmap. Using this correctly requires an assignment statement:
157:      * `bitmap = bitmap.setBit(bit);`
158:      */
159:     function setBit(uint256 bitmap, uint8 bit) internal pure returns (uint256) {
160:         return bitmap | (1 << bit);
161:     }
162: 
163:     /**
164:      * @notice Returns true if `bitmap` has no set bits
165:      */
166:     function isEmpty(uint256 bitmap) internal pure returns (bool) {
167:         return bitmap == 0;
168:     }
169: 
170:     /**
171:      * @notice Returns true if `a` and `b` have no common set bits
172:      */
173:     function noBitsInCommon(uint256 a, uint256 b) internal pure returns (bool) {
174:         return a & b == 0;
175:     }
176: 
177:     /**
178:      * @notice Returns true if `a` is a subset of `b`: ALL of the bits in `a` are also in `b`
179:      */
180:     function isSubsetOf(uint256 a, uint256 b) internal pure returns (bool) {
181:         return a & b == a;
182:     }
183: 
184:     /**
185:      * @notice Returns a new bitmap that contains all bits set in either `a` or `b`
186:      * @dev Result is the union of `a` and `b`
187:      */
188:     function plus(uint256 a, uint256 b) internal pure returns (uint256) {
189:         return a | b;
190:     }
191: 
192:     /**
193:      * @notice Returns a new bitmap that clears all set bits of `b` from `a`
194:      * @dev Negates `b` and returns the intersection of the result with `a`
195:      */
196:     function minus(uint256 a, uint256 b) internal pure returns (uint256) {
197:         return a & ~b;
198:     }
199: }

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: /**
5:  * @title Interface for an `ISocketUpdater` where operators can update their sockets.
6:  * @author Layr Labs, Inc.
7:  */
8: interface ISocketUpdater {
9:     // EVENTS
10: 
11:     event OperatorSocketUpdate(bytes32 indexed operatorId, string socket);
12: 
13:     // FUNCTIONS
14:     
15:     /**
16:      * @notice Updates the socket of the msg.sender given they are a registered operator
17:      * @param socket is the new socket of the operator
18:      */
19:     function updateSocket(string memory socket) external;
20: }
21: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
5: 
6: import {BitmapUtils} from "./libraries/BitmapUtils.sol"; 
7: import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol";
8: import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol";
9: 
10: import {IServiceManager} from "./interfaces/IServiceManager.sol";
11: import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol";
12: import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol";
13: 
14: /**
15:  * @title Minimal implementation of a ServiceManager-type contract.
16:  * This contract can be inherited from or simply used as a point-of-reference.
17:  * @author Layr Labs, Inc.
18:  */
19: abstract contract ServiceManagerBase is IServiceManager, OwnableUpgradeable {
20:     using BitmapUtils for *;
21: 
22:     IRegistryCoordinator internal immutable _registryCoordinator;
23:     IStakeRegistry internal immutable _stakeRegistry;
24:     IAVSDirectory internal immutable _avsDirectory;
25: 
26:     /// @notice when applied to a function, only allows the RegistryCoordinator to call it
27:     modifier onlyRegistryCoordinator() {
28:         require(
29:             msg.sender == address(_registryCoordinator),
30:             "ServiceManagerBase.onlyRegistryCoordinator: caller is not the registry coordinator"
31:         );
32:         _;
33:     }
34: 
35:     /// @notice Sets the (immutable) `_registryCoordinator` address
36:     constructor(
37:         IAVSDirectory __avsDirectory,
38:         IRegistryCoordinator __registryCoordinator,
39:         IStakeRegistry __stakeRegistry
40:     ) {
41:         _avsDirectory = __avsDirectory;
42:         _registryCoordinator = __registryCoordinator;
43:         _stakeRegistry = __stakeRegistry;
44:         _disableInitializers();
45:     }
46: 
47:     function __ServiceManagerBase_init(address initialOwner) internal virtual onlyInitializing {
48:         _transferOwnership(initialOwner);
49:     }
50: 
51:     /**
52:      * @notice Sets the metadata URI for the AVS
53:      * @param _metadataURI is the metadata URI for the AVS
54:      * @dev only callable by the owner
55:      */
56:     function setMetadataURI(string memory _metadataURI) public virtual onlyOwner {
57:         _avsDirectory.updateAVSMetadataURI(_metadataURI);
58:     }
59: 
60:     /**
61:      * @notice Forwards a call to EigenLayer's AVSDirectory contract to confirm operator registration with the AVS
62:      * @param operator The address of the operator to register.
63:      * @param operatorSignature The signature, salt, and expiry of the operator's signature.
64:      */
65:     function registerOperatorToAVS(
66:         address operator,
67:         ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
68:     ) public virtual onlyRegistryCoordinator {
69:         _avsDirectory.registerOperatorToAVS(operator, operatorSignature);
70:     }
71: 
72:     /**
73:      * @notice Forwards a call to EigenLayer's AVSDirectory contract to confirm operator deregistration from the AVS
74:      * @param operator The address of the operator to deregister.
75:      */
76:     function deregisterOperatorFromAVS(address operator) public virtual onlyRegistryCoordinator {
77:         _avsDirectory.deregisterOperatorFromAVS(operator);
78:     }
79: 
80:     /**
81:      * @notice Returns the list of strategies that the AVS supports for restaking
82:      * @dev This function is intended to be called off-chain
83:      * @dev No guarantee is made on uniqueness of each element in the returned array. 
84:      *      The off-chain service should do that validation separately
85:      */
86:     function getRestakeableStrategies() external view returns (address[] memory) {
87:         uint256 quorumCount = _registryCoordinator.quorumCount();
88: 
89:         if (quorumCount == 0) {
90:             return new address[](0);
91:         }
92:         
93:         uint256 strategyCount;
94:         for(uint256 i = 0; i < quorumCount; i++) {
95:             strategyCount += _stakeRegistry.strategyParamsLength(uint8(i));
96:         }
97: 
98:         address[] memory restakedStrategies = new address[](strategyCount);
99:         uint256 index = 0;
100:         for(uint256 i = 0; i < _registryCoordinator.quorumCount(); i++) {
101:             uint256 strategyParamsLength = _stakeRegistry.strategyParamsLength(uint8(i));
102:             for (uint256 j = 0; j < strategyParamsLength; j++) {
103:                 restakedStrategies[index] = address(_stakeRegistry.strategyParamsByIndex(uint8(i), j).strategy);
104:                 index++;
105:             }
106:         }
107:         return restakedStrategies;
108:     }
109: 
110:     /**
111:      * @notice Returns the list of strategies that the operator has potentially restaked on the AVS
112:      * @param operator The address of the operator to get restaked strategies for
113:      * @dev This function is intended to be called off-chain
114:      * @dev No guarantee is made on whether the operator has shares for a strategy in a quorum or uniqueness 
115:      *      of each element in the returned array. The off-chain service should do that validation separately
116:      */
117:     function getOperatorRestakedStrategies(address operator) external view returns (address[] memory) {
118:         bytes32 operatorId = _registryCoordinator.getOperatorId(operator);
119:         uint192 operatorBitmap = _registryCoordinator.getCurrentQuorumBitmap(operatorId);
120: 
121:         if (operatorBitmap == 0 || _registryCoordinator.quorumCount() == 0) {
122:             return new address[](0);
123:         }
124: 
125:         // Get number of strategies for each quorum in operator bitmap
126:         bytes memory operatorRestakedQuorums = BitmapUtils.bitmapToBytesArray(operatorBitmap);
127:         uint256 strategyCount;
128:         for(uint256 i = 0; i < operatorRestakedQuorums.length; i++) {
129:             strategyCount += _stakeRegistry.strategyParamsLength(uint8(operatorRestakedQuorums[i]));
130:         }
131: 
132:         // Get strategies for each quorum in operator bitmap
133:         address[] memory restakedStrategies = new address[](strategyCount);
134:         uint256 index = 0;
135:         for(uint256 i = 0; i < operatorRestakedQuorums.length; i++) {
136:             uint8 quorum = uint8(operatorRestakedQuorums[i]);
137:             uint256 strategyParamsLength = _stakeRegistry.strategyParamsLength(quorum);
138:             for (uint256 j = 0; j < strategyParamsLength; j++) {
139:                 restakedStrategies[index] = address(_stakeRegistry.strategyParamsByIndex(quorum, j).strategy);
140:                 index++;
141:             }
142:         }
143:         return restakedStrategies;        
144:     }
145: 
146:     /// @notice Returns the EigenLayer AVSDirectory contract.
147:     function avsDirectory() external view override returns (address) {
148:         return address(_avsDirectory);
149:     }
150:     
151:     // storage gap for upgradeability
152:     // slither-disable-next-line shadowing-state
153:     uint256[50] private __GAP;
154: }
155: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import {BLSApkRegistryStorage} from "./BLSApkRegistryStorage.sol";
5: 
6: import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol";
7: 
8: import {BN254} from "./libraries/BN254.sol";
9: 
10: contract BLSApkRegistry is BLSApkRegistryStorage {
11:     using BN254 for BN254.G1Point;
12: 
13:     /// @notice when applied to a function, only allows the RegistryCoordinator to call it
14:     modifier onlyRegistryCoordinator() {
15:         require(
16:             msg.sender == address(registryCoordinator),
17:             "BLSApkRegistry.onlyRegistryCoordinator: caller is not the registry coordinator"
18:         );
19:         _;
20:     }
21: 
22:     /// @notice Sets the (immutable) `registryCoordinator` address
23:     constructor(
24:         IRegistryCoordinator _registryCoordinator
25:     ) BLSApkRegistryStorage(_registryCoordinator) {}
26: 
27:     /*******************************************************************************
28:                       EXTERNAL FUNCTIONS - REGISTRY COORDINATOR
29:     *******************************************************************************/
30: 
31:     /**
32:      * @notice Registers the `operator`'s pubkey for the specified `quorumNumbers`.
33:      * @param operator The address of the operator to register.
34:      * @param quorumNumbers The quorum numbers the operator is registering for, where each byte is an 8 bit integer quorumNumber.
35:      * @dev access restricted to the RegistryCoordinator
36:      * @dev Preconditions (these are assumed, not validated in this contract):
37:      *         1) `quorumNumbers` has no duplicates
38:      *         2) `quorumNumbers.length` != 0
39:      *         3) `quorumNumbers` is ordered in ascending order
40:      *         4) the operator is not already registered
41:      */
42:     function registerOperator(
43:         address operator,
44:         bytes memory quorumNumbers
45:     ) public virtual onlyRegistryCoordinator {
46:         // Get the operator's pubkey. Reverts if they have not registered a key
47:         (BN254.G1Point memory pubkey, ) = getRegisteredPubkey(operator);
48: 
49:         // Update each quorum's aggregate pubkey
50:         _processQuorumApkUpdate(quorumNumbers, pubkey);
51: 
52:         // Return pubkeyHash, which will become the operator's unique id
53:         emit OperatorAddedToQuorums(operator, quorumNumbers);
54:     }
55: 
56:     /**
57:      * @notice Deregisters the `operator`'s pubkey for the specified `quorumNumbers`.
58:      * @param operator The address of the operator to deregister.
59:      * @param quorumNumbers The quorum numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber.
60:      * @dev access restricted to the RegistryCoordinator
61:      * @dev Preconditions (these are assumed, not validated in this contract):
62:      *         1) `quorumNumbers` has no duplicates
63:      *         2) `quorumNumbers.length` != 0
64:      *         3) `quorumNumbers` is ordered in ascending order
65:      *         4) the operator is not already deregistered
66:      *         5) `quorumNumbers` is a subset of the quorumNumbers that the operator is registered for
67:      */
68:     function deregisterOperator(
69:         address operator,
70:         bytes memory quorumNumbers
71:     ) public virtual onlyRegistryCoordinator {
72:         // Get the operator's pubkey. Reverts if they have not registered a key
73:         (BN254.G1Point memory pubkey, ) = getRegisteredPubkey(operator);
74: 
75:         // Update each quorum's aggregate pubkey
76:         _processQuorumApkUpdate(quorumNumbers, pubkey.negate());
77:         emit OperatorRemovedFromQuorums(operator, quorumNumbers);
78:     }
79: 
80:     /**
81:      * @notice Initializes a new quorum by pushing its first apk update
82:      * @param quorumNumber The number of the new quorum
83:      */
84:     function initializeQuorum(uint8 quorumNumber) public virtual onlyRegistryCoordinator {
85:         require(apkHistory[quorumNumber].length == 0, "BLSApkRegistry.initializeQuorum: quorum already exists");
86: 
87:         apkHistory[quorumNumber].push(ApkUpdate({
88:             apkHash: bytes24(0),
89:             updateBlockNumber: uint32(block.number),
90:             nextUpdateBlockNumber: 0
91:         }));
92:     }
93: 
94:     /**
95:      * @notice Called by the RegistryCoordinator register an operator as the owner of a BLS public key.
96:      * @param operator is the operator for whom the key is being registered
97:      * @param params contains the G1 & G2 public keys of the operator, and a signature proving their ownership
98:      * @param pubkeyRegistrationMessageHash is a hash that the operator must sign to prove key ownership
99:      */
100:     function registerBLSPublicKey(
101:         address operator,
102:         PubkeyRegistrationParams calldata params,
103:         BN254.G1Point calldata pubkeyRegistrationMessageHash
104:     ) external onlyRegistryCoordinator returns (bytes32 operatorId) {
105:         bytes32 pubkeyHash = BN254.hashG1Point(params.pubkeyG1);
106:         require(
107:             pubkeyHash != ZERO_PK_HASH, "BLSApkRegistry.registerBLSPublicKey: cannot register zero pubkey"
108:         );
109:         require(
110:             operatorToPubkeyHash[operator] == bytes32(0),
111:             "BLSApkRegistry.registerBLSPublicKey: operator already registered pubkey"
112:         );
113:         require(
114:             pubkeyHashToOperator[pubkeyHash] == address(0),
115:             "BLSApkRegistry.registerBLSPublicKey: public key already registered"
116:         );
117: 
118:         // gamma = h(sigma, P, P', H(m))
119:         uint256 gamma = uint256(keccak256(abi.encodePacked(
120:             params.pubkeyRegistrationSignature.X, 
121:             params.pubkeyRegistrationSignature.Y, 
122:             params.pubkeyG1.X, 
123:             params.pubkeyG1.Y, 
124:             params.pubkeyG2.X, 
125:             params.pubkeyG2.Y, 
126:             pubkeyRegistrationMessageHash.X, 
127:             pubkeyRegistrationMessageHash.Y
128:         ))) % BN254.FR_MODULUS;
129:         
130:         // e(sigma + P * gamma, [-1]_2) = e(H(m) + [1]_1 * gamma, P') 
131:         require(BN254.pairing(
132:             params.pubkeyRegistrationSignature.plus(params.pubkeyG1.scalar_mul(gamma)),
133:             BN254.negGeneratorG2(),
134:             pubkeyRegistrationMessageHash.plus(BN254.generatorG1().scalar_mul(gamma)),
135:             params.pubkeyG2
136:         ), "BLSApkRegistry.registerBLSPublicKey: either the G1 signature is wrong, or G1 and G2 private key do not match");
137: 
138:         operatorToPubkey[operator] = params.pubkeyG1;
139:         operatorToPubkeyHash[operator] = pubkeyHash;
140:         pubkeyHashToOperator[pubkeyHash] = operator;
141: 
142:         emit NewPubkeyRegistration(operator, params.pubkeyG1, params.pubkeyG2);
143:         return pubkeyHash;
144:     }
145: 
146:     /*******************************************************************************
147:                             INTERNAL FUNCTIONS
148:     *******************************************************************************/
149: 
150:     function _processQuorumApkUpdate(bytes memory quorumNumbers, BN254.G1Point memory point) internal {
151:         BN254.G1Point memory newApk;
152: 
153:         for (uint256 i = 0; i < quorumNumbers.length; i++) {
154:             // Validate quorum exists and get history length
155:             uint8 quorumNumber = uint8(quorumNumbers[i]);
156:             uint256 historyLength = apkHistory[quorumNumber].length;
157:             require(historyLength != 0, "BLSApkRegistry._processQuorumApkUpdate: quorum does not exist");
158: 
159:             // Update aggregate public key for this quorum
160:             newApk = currentApk[quorumNumber].plus(point);
161:             currentApk[quorumNumber] = newApk;
162:             bytes24 newApkHash = bytes24(BN254.hashG1Point(newApk));
163: 
164:             // Update apk history. If the last update was made in this block, update the entry
165:             // Otherwise, push a new historical entry and update the prev->next pointer
166:             ApkUpdate storage lastUpdate = apkHistory[quorumNumber][historyLength - 1];
167:             if (lastUpdate.updateBlockNumber == uint32(block.number)) {
168:                 lastUpdate.apkHash = newApkHash;
169:             } else {
170:                 lastUpdate.nextUpdateBlockNumber = uint32(block.number);
171:                 apkHistory[quorumNumber].push(ApkUpdate({
172:                     apkHash: newApkHash,
173:                     updateBlockNumber: uint32(block.number),
174:                     nextUpdateBlockNumber: 0
175:                 }));
176:             }
177:         }
178:     }
179: 
180:     /*******************************************************************************
181:                             VIEW FUNCTIONS
182:     *******************************************************************************/
183:     /**
184:      * @notice Returns the pubkey and pubkey hash of an operator
185:      * @dev Reverts if the operator has not registered a valid pubkey
186:      */
187:     function getRegisteredPubkey(address operator) public view returns (BN254.G1Point memory, bytes32) {
188:         BN254.G1Point memory pubkey = operatorToPubkey[operator];
189:         bytes32 pubkeyHash = operatorToPubkeyHash[operator];
190: 
191:         require(
192:             pubkeyHash != bytes32(0),
193:             "BLSApkRegistry.getRegisteredPubkey: operator is not registered"
194:         );
195:         
196:         return (pubkey, pubkeyHash);
197:     }
198: 
199:     /**
200:      * @notice Returns the indices of the quorumApks index at `blockNumber` for the provided `quorumNumbers`
201:      * @dev Returns the current indices if `blockNumber >= block.number`
202:      */
203:     function getApkIndicesAtBlockNumber(
204:         bytes calldata quorumNumbers,
205:         uint256 blockNumber
206:     ) external view returns (uint32[] memory) {
207:         uint32[] memory indices = new uint32[](quorumNumbers.length);
208:         
209:         for (uint256 i = 0; i < quorumNumbers.length; i++) {
210:             uint8 quorumNumber = uint8(quorumNumbers[i]);
211:             
212:             uint256 quorumApkUpdatesLength = apkHistory[quorumNumber].length;
213:             if (quorumApkUpdatesLength == 0 || blockNumber < apkHistory[quorumNumber][0].updateBlockNumber) {
214:                 revert("BLSApkRegistry.getApkIndicesAtBlockNumber: blockNumber is before the first update");
215:             }
216: 
217:             // Loop backward through apkHistory until we find an entry that preceeds `blockNumber`
218:             for (uint256 j = quorumApkUpdatesLength; j > 0; j--) {
219:                 if (apkHistory[quorumNumber][j - 1].updateBlockNumber <= blockNumber) {
220:                     indices[i] = uint32(j - 1);
221:                     break;
222:                 }
223:             }
224:         }
225:         return indices;
226:     }
227: 
228:     /// @notice Returns the current APK for the provided `quorumNumber `
229:     function getApk(uint8 quorumNumber) external view returns (BN254.G1Point memory) {
230:         return currentApk[quorumNumber];
231:     }
232: 
233:     /// @notice Returns the `ApkUpdate` struct at `index` in the list of APK updates for the `quorumNumber`
234:     function getApkUpdateAtIndex(uint8 quorumNumber, uint256 index) external view returns (ApkUpdate memory) {
235:         return apkHistory[quorumNumber][index];
236:     }
237: 
238:     /**
239:      * @notice get hash of the apk of `quorumNumber` at `blockNumber` using the provided `index`;
240:      * called by checkSignatures in BLSSignatureChecker.sol.
241:      * @param quorumNumber is the quorum whose ApkHash is being retrieved
242:      * @param blockNumber is the number of the block for which the latest ApkHash will be retrieved
243:      * @param index is the index of the apkUpdate being retrieved from the list of quorum apkUpdates in storage
244:      */
245:     function getApkHashAtBlockNumberAndIndex(
246:         uint8 quorumNumber,
247:         uint32 blockNumber,
248:         uint256 index
249:     ) external view returns (bytes24) {
250:         ApkUpdate memory quorumApkUpdate = apkHistory[quorumNumber][index];
251: 
252:         /**
253:          * Validate that the update is valid for the given blockNumber:
254:          * - blockNumber should be >= the update block number
255:          * - the next update block number should be either 0 or strictly greater than blockNumber
256:          */
257:         require(
258:             blockNumber >= quorumApkUpdate.updateBlockNumber,
259:             "BLSApkRegistry._validateApkHashAtBlockNumber: index too recent"
260:         );
261:         require(
262:             quorumApkUpdate.nextUpdateBlockNumber == 0 || blockNumber < quorumApkUpdate.nextUpdateBlockNumber,
263:             "BLSApkRegistry._validateApkHashAtBlockNumber: not latest apk update"
264:         );
265: 
266:         return quorumApkUpdate.apkHash;
267:     }
268: 
269:     /// @notice Returns the length of ApkUpdates for the provided `quorumNumber`
270:     function getApkHistoryLength(uint8 quorumNumber) external view returns (uint32) {
271:         return uint32(apkHistory[quorumNumber].length);
272:     }
273: 
274:     /// @notice Returns the operator address for the given `pubkeyHash`
275:     function getOperatorFromPubkeyHash(bytes32 pubkeyHash) public view returns (address) {
276:         return pubkeyHashToOperator[pubkeyHash];
277:     }
278: 
279:     /// @notice returns the ID used to identify the `operator` within this AVS
280:     /// @dev Returns zero in the event that the `operator` has never registered for the AVS
281:     function getOperatorId(address operator) public view returns (bytes32) {
282:         return operatorToPubkeyHash[operator];
283:     }
284: }
285: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol";
5: import {IStrategyManager, IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol";
6: 
7: import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol";
8: import {IStakeRegistry} from  "./interfaces/IStakeRegistry.sol";
9: 
10: /**
11:  * @title Storage variables for the `StakeRegistry` contract.
12:  * @author Layr Labs, Inc.
13:  * @notice This storage contract is separate from the logic to simplify the upgrade process.
14:  */
15: abstract contract StakeRegistryStorage is IStakeRegistry {
16:     
17:     /// @notice Constant used as a divisor in calculating weights.
18:     uint256 public constant WEIGHTING_DIVISOR = 1e18;
19:     /// @notice Maximum length of dynamic arrays in the `strategyParams` mapping.
20:     uint8 public constant MAX_WEIGHING_FUNCTION_LENGTH = 32;
21:     /// @notice Constant used as a divisor in dealing with BIPS amounts.
22:     uint256 internal constant MAX_BIPS = 10000;
23: 
24:     /// @notice The address of the Delegation contract for EigenLayer.
25:     IDelegationManager public immutable delegation;
26: 
27:     /// @notice the coordinator contract that this registry is associated with
28:     address public immutable registryCoordinator;
29: 
30:     /// @notice In order to register for a quorum i, an operator must have at least `minimumStakeForQuorum[i]`
31:     /// evaluated by this contract's 'VoteWeigher' logic.
32:     mapping(uint8 => uint96) public minimumStakeForQuorum;
33: 
34:     /// @notice History of the total stakes for each quorum
35:     mapping(uint8 => StakeUpdate[]) internal _totalStakeHistory;
36: 
37:     /// @notice mapping from operator's operatorId to the history of their stake updates
38:     mapping(bytes32 => mapping(uint8 => StakeUpdate[])) internal operatorStakeHistory;
39: 
40:     /**
41:      * @notice mapping from quorum number to the list of strategies considered and their
42:      * corresponding multipliers for that specific quorum
43:      */
44:     mapping(uint8 => StrategyParams[]) public strategyParams;
45:     mapping(uint8 => IStrategy[]) public strategiesPerQuorum;
46: 
48:     constructor(
49:         IRegistryCoordinator _registryCoordinator, 
50:         IDelegationManager _delegationManager
51:     ) {
52:         registryCoordinator = address(_registryCoordinator);
53:         delegation = _delegationManager;
54:     }
55: 
56:     // storage gap for upgradeability
57:     // slither-disable-next-line shadowing-state
58:     uint256[45] private __GAP;
59: }
60: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol";
5: import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol";
6: 
7: import {IRegistry} from "./IRegistry.sol";
8: 
9: /**
10:  * @title Interface for a `Registry` that keeps track of stakes of operators for up to 256 quorums.
11:  * @author Layr Labs, Inc.
12:  */
13: interface IStakeRegistry is IRegistry {
14:     
15:     // DATA STRUCTURES
16: 
17:     /// @notice struct used to store the stakes of an individual operator or the sum of all operators' stakes, for storage
18:     struct StakeUpdate {
19:         // the block number at which the stake amounts were updated and stored
20:         uint32 updateBlockNumber;
21:         // the block number at which the *next update* occurred.
22:         /// @notice This entry has the value **0** until another update takes place.
23:         uint32 nextUpdateBlockNumber;
24:         // stake weight for the quorum
25:         uint96 stake;
26:     }
27: 
28:     /**
29:      * @notice In weighing a particular strategy, the amount of underlying asset for that strategy is
30:      * multiplied by its multiplier, then divided by WEIGHTING_DIVISOR
31:      */
32:     struct StrategyParams {
33:         IStrategy strategy;
34:         uint96 multiplier;
35:     }
36: 
37:     // EVENTS
38: 
39:     /// @notice emitted whenever the stake of `operator` is updated
40:     event OperatorStakeUpdate(
41:         bytes32 indexed operatorId,
42:         uint8 quorumNumber,
43:         uint96 stake
44:     );
45:     /// @notice emitted when the minimum stake for a quorum is updated
46:     event MinimumStakeForQuorumUpdated(uint8 indexed quorumNumber, uint96 minimumStake);
47:     /// @notice emitted when a new quorum is created
48:     event QuorumCreated(uint8 indexed quorumNumber);
49:     /// @notice emitted when `strategy` has been added to the array at `strategyParams[quorumNumber]`
50:     event StrategyAddedToQuorum(uint8 indexed quorumNumber, IStrategy strategy);
51:     /// @notice emitted when `strategy` has removed from the array at `strategyParams[quorumNumber]`
52:     event StrategyRemovedFromQuorum(uint8 indexed quorumNumber, IStrategy strategy);
53:     /// @notice emitted when `strategy` has its `multiplier` updated in the array at `strategyParams[quorumNumber]`
54:     event StrategyMultiplierUpdated(uint8 indexed quorumNumber, IStrategy strategy, uint256 multiplier);
55: 
56:     /**
57:      * @notice Registers the `operator` with `operatorId` for the specified `quorumNumbers`.
58:      * @param operator The address of the operator to register.
59:      * @param operatorId The id of the operator to register.
60:      * @param quorumNumbers The quorum numbers the operator is registering for, where each byte is an 8 bit integer quorumNumber.
61:      * @return The operator's current stake for each quorum, and the total stake for each quorum
62:      * @dev access restricted to the RegistryCoordinator
63:      * @dev Preconditions (these are assumed, not validated in this contract):
64:      *         1) `quorumNumbers` has no duplicates
65:      *         2) `quorumNumbers.length` != 0
66:      *         3) `quorumNumbers` is ordered in ascending order
67:      *         4) the operator is not already registered
68:      */
69:     function registerOperator(
70:         address operator, 
71:         bytes32 operatorId, 
72:         bytes memory quorumNumbers
73:     ) external returns (uint96[] memory, uint96[] memory);
74: 
75:     /**
76:      * @notice Deregisters the operator with `operatorId` for the specified `quorumNumbers`.
77:      * @param operatorId The id of the operator to deregister.
78:      * @param quorumNumbers The quorum numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber.
79:      * @dev access restricted to the RegistryCoordinator
80:      * @dev Preconditions (these are assumed, not validated in this contract):
81:      *         1) `quorumNumbers` has no duplicates
82:      *         2) `quorumNumbers.length` != 0
83:      *         3) `quorumNumbers` is ordered in ascending order
84:      *         4) the operator is not already deregistered
85:      *         5) `quorumNumbers` is a subset of the quorumNumbers that the operator is registered for
86:      */
87:     function deregisterOperator(bytes32 operatorId, bytes memory quorumNumbers) external;
88: 
89:     /**
90:      * @notice Initialize a new quorum created by the registry coordinator by setting strategies, weights, and minimum stake
91:      */
92:     function initializeQuorum(uint8 quorumNumber, uint96 minimumStake, StrategyParams[] memory strategyParams) external;
93: 
94:     /// @notice Adds new strategies and the associated multipliers to the @param quorumNumber.
95:     function addStrategies(
96:         uint8 quorumNumber,
97:         StrategyParams[] memory strategyParams
98:     ) external;
99: 
100:     /**
101:      * @notice This function is used for removing strategies and their associated weights from the
102:      * mapping strategyParams for a specific @param quorumNumber.
103:      * @dev higher indices should be *first* in the list of @param indicesToRemove, since otherwise
104:      * the removal of lower index entries will cause a shift in the indices of the other strategiesToRemove
105:      */
106:     function removeStrategies(uint8 quorumNumber, uint256[] calldata indicesToRemove) external;
107: 
108:     /**
109:      * @notice This function is used for modifying the weights of strategies that are already in the
110:      * mapping strategyParams for a specific
111:      * @param quorumNumber is the quorum number to change the strategy for
112:      * @param strategyIndices are the indices of the strategies to change
113:      * @param newMultipliers are the new multipliers for the strategies
114:      */
115:     function modifyStrategyParams(
116:         uint8 quorumNumber,
117:         uint256[] calldata strategyIndices,
118:         uint96[] calldata newMultipliers
119:     ) external;
120: 
121:     /// @notice Constant used as a divisor in calculating weights.
122:     function WEIGHTING_DIVISOR() external pure returns (uint256);
123: 
124:     /// @notice Returns the EigenLayer delegation manager contract.
125:     function delegation() external view returns (IDelegationManager);
126: 
127:     /// @notice In order to register for a quorum i, an operator must have at least `minimumStakeForQuorum[i]`
128:     function minimumStakeForQuorum(uint8 quorumNumber) external view returns (uint96);
129: 
130:     /// @notice Returns the length of the dynamic array stored in `strategyParams[quorumNumber]`.
131:     function strategyParamsLength(uint8 quorumNumber) external view returns (uint256);
132: 
133:     /// @notice Returns the strategy and weight multiplier for the `index`'th strategy in the quorum `quorumNumber`
134:     function strategyParamsByIndex(
135:         uint8 quorumNumber,
136:         uint256 index
137:     ) external view returns (StrategyParams memory);
138: 
139:     /**
140:      * @notice This function computes the total weight of the @param operator in the quorum @param quorumNumber.
141:      * @dev reverts in the case that `quorumNumber` is greater than or equal to `quorumCount`
142:      */
143:     function weightOfOperatorForQuorum(uint8 quorumNumber, address operator) external view returns (uint96);
144: 
145:     /**
146:      * @notice Returns the entire `operatorIdToStakeHistory[operatorId][quorumNumber]` array.
147:      * @param operatorId The id of the operator of interest.
148:      * @param quorumNumber The quorum number to get the stake for.
149:      */
150:     function getStakeHistory(bytes32 operatorId, uint8 quorumNumber) external view returns (StakeUpdate[] memory);
151: 
152:     function getTotalStakeHistoryLength(uint8 quorumNumber) external view returns (uint256);
153: 
154:     /**
155:      * @notice Returns the `index`-th entry in the dynamic array of total stake, `totalStakeHistory` for quorum `quorumNumber`.
156:      * @param quorumNumber The quorum number to get the stake for.
157:      * @param index Array index for lookup, within the dynamic array `totalStakeHistory[quorumNumber]`.
158:      */
159:     function getTotalStakeUpdateAtIndex(uint8 quorumNumber, uint256 index) external view returns (StakeUpdate memory);
160: 
161:     /// @notice Returns the indices of the operator stakes for the provided `quorumNumber` at the given `blockNumber`
162:     function getStakeUpdateIndexAtBlockNumber(bytes32 operatorId, uint8 quorumNumber, uint32 blockNumber)
163:         external
164:         view
165:         returns (uint32);
166: 
167:     /// @notice Returns the indices of the total stakes for the provided `quorumNumbers` at the given `blockNumber`
168:     function getTotalStakeIndicesAtBlockNumber(uint32 blockNumber, bytes calldata quorumNumbers) external view returns(uint32[] memory) ;
169: 
170:     /**
171:      * @notice Returns the `index`-th entry in the `operatorIdToStakeHistory[operatorId][quorumNumber]` array.
172:      * @param quorumNumber The quorum number to get the stake for.
173:      * @param operatorId The id of the operator of interest.
174:      * @param index Array index for lookup, within the dynamic array `operatorIdToStakeHistory[operatorId][quorumNumber]`.
175:      * @dev Function will revert if `index` is out-of-bounds.
176:      */
177:     function getStakeUpdateAtIndex(uint8 quorumNumber, bytes32 operatorId, uint256 index)
178:         external
179:         view
180:         returns (StakeUpdate memory);
181: 
182:     /**
183:      * @notice Returns the most recent stake weight for the `operatorId` for a certain quorum
184:      * @dev Function returns an StakeUpdate struct with **every entry equal to 0** in the event that the operator has no stake history
185:      */
186:     function getLatestStakeUpdate(bytes32 operatorId, uint8 quorumNumber) external view returns (StakeUpdate memory);
187: 
188:     /**
189:      * @notice Returns the stake weight corresponding to `operatorId` for quorum `quorumNumber`, at the
190:      * `index`-th entry in the `operatorIdToStakeHistory[operatorId][quorumNumber]` array if the entry 
191:      * corresponds to the operator's stake at `blockNumber`. Reverts otherwise.
192:      * @param quorumNumber The quorum number to get the stake for.
193:      * @param operatorId The id of the operator of interest.
194:      * @param index Array index for lookup, within the dynamic array `operatorIdToStakeHistory[operatorId][quorumNumber]`.
195:      * @param blockNumber Block number to make sure the stake is from.
196:      * @dev Function will revert if `index` is out-of-bounds.
197:      * @dev used the BLSSignatureChecker to get past stakes of signing operators
198:      */
199:     function getStakeAtBlockNumberAndIndex(uint8 quorumNumber, uint32 blockNumber, bytes32 operatorId, uint256 index)
200:         external
201:         view
202:         returns (uint96);
203: 
204:     /**
205:      * @notice Returns the total stake weight for quorum `quorumNumber`, at the `index`-th entry in the 
206:      * `totalStakeHistory[quorumNumber]` array if the entry corresponds to the total stake at `blockNumber`. 
207:      * Reverts otherwise.
208:      * @param quorumNumber The quorum number to get the stake for.
209:      * @param index Array index for lookup, within the dynamic array `totalStakeHistory[quorumNumber]`.
210:      * @param blockNumber Block number to make sure the stake is from.
211:      * @dev Function will revert if `index` is out-of-bounds.
212:      * @dev used the BLSSignatureChecker to get past stakes of signing operators
213:      */
214:     function getTotalStakeAtBlockNumberFromIndex(uint8 quorumNumber, uint32 blockNumber, uint256 index) external view returns (uint96);
215: 
216:     /**
217:      * @notice Returns the most recent stake weight for the `operatorId` for quorum `quorumNumber`
218:      * @dev Function returns weight of **0** in the event that the operator has no stake history
219:      */
220:     function getCurrentStake(bytes32 operatorId, uint8 quorumNumber) external view returns (uint96);
221: 
222:     /// @notice Returns the stake of the operator for the provided `quorumNumber` at the given `blockNumber`
223:     function getStakeAtBlockNumber(bytes32 operatorId, uint8 quorumNumber, uint32 blockNumber)
224:         external
225:         view
226:         returns (uint96);
227: 
228:     /**
229:      * @notice Returns the stake weight from the latest entry in `_totalStakeHistory` for quorum `quorumNumber`.
230:      * @dev Will revert if `_totalStakeHistory[quorumNumber]` is empty.
231:      */
232:     function getCurrentTotalStake(uint8 quorumNumber) external view returns (uint96);
233: 
234:     /**
235:      * @notice Called by the registry coordinator to update an operator's stake for one
236:      * or more quorums.
237:      *
238:      * If the operator no longer has the minimum stake required for a quorum, they are
239:      * added to the
240:      * @return A bitmap of quorums where the operator no longer meets the minimum stake
241:      * and should be deregistered.
242:      */
243:     function updateOperatorStake(
244:         address operator, 
245:         bytes32 operatorId, 
246:         bytes calldata quorumNumbers
247:     ) external returns (uint192);
248: }

[NonCritical-55] Top level declarations should be separated by two blank lines

Num of instances: 3

Findings

Click to show findings

['775']

775:     } // <= FOUND
776: 
777:     function _setEjector(address newEjector) internal { // <= FOUND

['206']

206:     } // <= FOUND
207: 
208:     function setMinimumStakeForQuorum( // <= FOUND

['32']

32:     } // <= FOUND
33: 
34:     modifier onlyCoordinatorOwner() { // <= FOUND

[NonCritical-56] Contracts should have full test coverage

Resolution

Attaining 100% code coverage is not an assurance of a bug-free codebase, but it significantly improves the likelihood of identifying simple bugs and aids in maintaining a stable codebase by preventing regressions during code modifications. Additionally, to achieve complete coverage, code writers usually have to structure their code more modularly, which implies testing each component independently. This reduces the complex interdependencies between modules and layers, creating a more understandable and auditable codebase. Consequently, this practice aids in enhancing code maintainability and reduces the risk of introducing bugs during future changes.

Num of instances: 6

Findings

Click to show findings

['10']

10: contract BLSApkRegistry is BLSApkRegistryStorage  // <= FOUND

['18']

18: contract BLSSignatureChecker is IBLSSignatureChecker  // <= FOUND

['11']

11: contract IndexRegistry is IndexRegistryStorage  // <= FOUND

['15']

15: contract OperatorStateRetriever  // <= FOUND

['32']

32: contract RegistryCoordinator is  // <= FOUND
33:     EIP712,  // <= FOUND
34:     Initializable,  // <= FOUND
35:     Pausable, // <= FOUND
36:     OwnableUpgradeable, // <= FOUND
37:     RegistryCoordinatorStorage,  // <= FOUND
38:     ISocketUpdater,  // <= FOUND
39:     ISignatureUtils // <= FOUND
40:  // <= FOUND

['22']

22: contract StakeRegistry is StakeRegistryStorage  // <= FOUND

[NonCritical-57] Assembly block creates dirty bits

Resolution

Manipulating data directly at the free memory pointer location without subsequently adjusting the pointer can lead to unwanted data remnants, or "dirty bits", in that memory spot. This can cause challenges for the Solidity optimizer, making it difficult to determine if memory cleaning is required before reuse, potentially resulting in less efficient optimization. To mitigate this issue, it's advised to always update the free memory pointer following any data write operation. Furthermore, using the assembly ("memory-safe") { ... } annotation will clearly indicate to the optimizer the sections of your code that are memory-safe, improving code efficiency and reducing the potential for errors.

Num of instances: 3

Findings

Click to show findings

['109']

108:         assembly {
109:             success := staticcall(sub(gas(), 2000), 6, input, 0x80, r, 0x40) // <= FOUND
110:             
111:             switch success
112:             case 0 {
113:                 invalid()
114:             }
115:         }

['176']

175:         assembly {
176:             success := staticcall(sub(gas(), 2000), 7, input, 0x60, r, 0x40) // <= FOUND
177:             
178:             switch success
179:             case 0 {
180:                 invalid()
181:             }
182:         }

['277']

274:        assembly {
275:             mstore(0, mload(pk))
276:             mstore(0x20, mload(add(0x20, pk)))
277:             hashedG1 := keccak256(0, 0x40) // <= FOUND
278:         }

[NonCritical-58] Whitespace in expressions

Resolution

Avoid unnecessary whitespace in contract lines such as ' ;' and ', )'

Num of instances: 3

Findings

Click to show findings

['48']

47:         
48:         (BN254.G1Point memory pubkey, ) = getRegisteredPubkey(operator); // <= FOUND

['510']

510:         (uint96 stake, ) = _weightOfOperatorForQuorum(quorumNumber, operator); // <= FOUND

['170']

168: 
170:     function getTotalStakeIndicesAtBlockNumber(uint32 blockNumber, bytes calldata quorumNumbers) external view returns(uint32[] memory) ; // <= FOUND

[NonCritical-59] Consider using named function calls

Resolution

Named function calls in Solidity greatly improve code readability by explicitly mapping arguments to their respective parameter names. This clarity becomes critical when dealing with functions that have numerous or complex parameters, reducing potential errors due to misordered arguments. Therefore, adopting named function calls contributes to more maintainable and less error-prone code.

Num of instances: 31

Findings

Click to show findings

['148']

148:                     acc = plus(acc, p2n); // <= FOUND

['151']

151:                 
152:                 p2n = plus(p2n, p2n); // <= FOUND

['50']

50: 
51:         
52:         _processQuorumApkUpdate(quorumNumbers, pubkey); // <= FOUND

['299']

299:             (beta, y) = findYFromX(x); // <= FOUND

['324']

324: 
325:         
326:         
327:         uint256 y = expMod(beta, 0xc19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52, FP_MODULUS); // <= FOUND

['135']

135:         
136:         _updateOperatorCountHistory(quorumNumber, lastUpdate, newOperatorCount); // <= FOUND

['100']

100:             bytes32 lastOperatorId = _popLastOperator(quorumNumber, newOperatorCount); // <= FOUND

['191']

191: 
192:         
193:         _updateOperatorIndexHistory(quorumNumber, operatorIndex, lastUpdate, OPERATOR_DOES_NOT_EXIST_ID); // <= FOUND

['205']

205: 
206:         _updateOperatorIndexHistory(quorumNumber, operatorIndex, lastUpdate, operatorId); // <= FOUND

['187']

187:         OperatorUpdate storage lastUpdate = _latestOperatorIndexUpdate(quorumNumber, operatorIndex); // <= FOUND

['318']

318:         return _latestOperatorIndexUpdate(quorumNumber, operatorIndex); // <= FOUND

['326']

326:         uint32 operatorCount = _operatorCountAtBlockNumber(quorumNumber, blockNumber); // <= FOUND

['329']

329:             operatorList[i] = _operatorIdForIndexAtBlockNumber(quorumNumber, uint32(i), blockNumber); // <= FOUND

['53']

53: 
54:         return (quorumBitmap, getOperatorState(registryCoordinator, quorumNumbers, blockNumber)); // <= FOUND

['110']

110:             _createQuorum(_operatorSetParams[i], _minimumStakes[i], _strategyParams[i]); // <= FOUND

['390']

390:         _createQuorum(operatorSetParams, minimumStake, strategyParams); // <= FOUND

['404']

404:         _setOperatorSetParams(quorumNumber, operatorSetParams); // <= FOUND

['404']

404: 
405:         
406:         _setOperatorSetParams(quorumNumber, operatorSetParams); // <= FOUND

['266']

266:             _updateOperator(operator, operatorInfo, quorumsToUpdate); // <= FOUND

['822']

822:             indices[i] = _getQuorumBitmapIndexAtBlockNumber(blockNumber, operatorIds[i]); // <= FOUND

['199']

199:         _setMinimumStakeForQuorum(quorumNumber, minimumStake); // <= FOUND

['614']

614:         return _getStakeUpdateIndexForOperatorAtBlockNumber(operatorId, quorumNumber, blockNumber); // <= FOUND

['96']

96:             totalStakes[i] = _recordTotalStakeUpdate(quorumNumber, stakeDelta); // <= FOUND

['134']

134: 
135:             
136:             _recordTotalStakeUpdate(quorumNumber, stakeDelta); // <= FOUND

['198']

198:         _addStrategyParams(quorumNumber, _strategyParams); // <= FOUND

['634']

634:         _validateStakeUpdateAtBlockNumber(operatorStakeUpdate, blockNumber); // <= FOUND

['683']

683:         _validateStakeUpdateAtBlockNumber(totalStakeUpdate, blockNumber); // <= FOUND

['81']

81: 
82:             
83:             
84:             (uint96 currentStake, bool hasMinimumStake) = _weightOfOperatorForQuorum(quorumNumber, operator); // <= FOUND

['168']

168: 
169:             
170:             
171:             (uint96 stakeWeight, bool hasMinimumStake) = _weightOfOperatorForQuorum(quorumNumber, operator); // <= FOUND

['510']

510:         (uint96 stake, ) = _weightOfOperatorForQuorum(quorumNumber, operator); // <= FOUND

['559']

559:         StakeUpdate memory operatorStakeUpdate = getLatestStakeUpdate(operatorId, quorumNumber); // <= FOUND

[NonCritical-60] Lack of space near the operator

Resolution

Lack of space near operators in code can lead to reduced readability, making it more challenging to distinguish between different elements and understand the logic quickly. As a resolution, always include spaces around operators to ensure a clear visual separation, which promotes better maintainability and comprehension of the code.

Num of instances: 3

Findings

Click to show findings

['41']

41:     using BitmapUtils for *; // <= FOUND

['233']

233:                 _deregisterOperator(operatorKickParams[i].operator, quorumNumbers[i:i+1]); // <= FOUND

['335']

335:                 _updateOperator(operator, operatorInfo, quorumNumbers[i:i+1]); // <= FOUND

[NonCritical-61] Lack Of Brace Spacing

Resolution

Lack of brace spacing in coding refers to the absence of spaces around braces, which can hinder code readability. In Solidity, as in many programming languages, spacing can enhance the visual distinction between different parts of the code, making it easier to follow. A lack of spacing can lead to a dense, confusing appearance. The resolution to this issue is to follow a consistent style guide that defines rules for brace spacing. By including spaces around braces, such as { statement } instead of {statement}, developers can ensure that the code is more legible and maintainable, especially in larger codebases.

Num of instances: 44

Findings

Click to show findings

['4']

4: 
5: import {BLSApkRegistryStorage} from "./BLSApkRegistryStorage.sol"; // <= FOUND

['8']

8: 
9: import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol"; // <= FOUND

['9']

9: 
10: import {BN254} from "./libraries/BN254.sol"; // <= FOUND

['4']

4: 
5: import {IBLSApkRegistry} from "./interfaces/IBLSApkRegistry.sol"; // <= FOUND

['8']

8: import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol"; // <= FOUND

['7']

7: 
8: import {Initializable} from "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; // <= FOUND

['4']

4: 
5: import {IBLSSignatureChecker} from "./interfaces/IBLSSignatureChecker.sol"; // <= FOUND

['4']

4: import {IBLSApkRegistry} from "./interfaces/IBLSApkRegistry.sol"; // <= FOUND

['7']

7: import {IStakeRegistry, IDelegationManager} from "./interfaces/IStakeRegistry.sol"; // <= FOUND

['9']

9: 
10: import {BitmapUtils} from "./libraries/BitmapUtils.sol"; // <= FOUND

['9']

9: import {BN254} from "./libraries/BN254.sol"; // <= FOUND

['4']

4: 
5: import {IRegistry} from "./IRegistry.sol"; // <= FOUND

['8']

8: 
9: import {BN254} from "../libraries/BN254.sol"; // <= FOUND

['4']

4: 
5: import {IRegistryCoordinator} from "./IRegistryCoordinator.sol"; // <= FOUND

['5']

5: import {IBLSApkRegistry} from "./IBLSApkRegistry.sol"; // <= FOUND

['6']

6: import {IStakeRegistry, IDelegationManager} from "./IStakeRegistry.sol"; // <= FOUND

['4']

4: 
5: import {IndexRegistryStorage} from "./IndexRegistryStorage.sol"; // <= FOUND

['6']

6: import {IIndexRegistry} from "./interfaces/IIndexRegistry.sol"; // <= FOUND

['5']

5: 
6: import {IBLSApkRegistry} from "./IBLSApkRegistry.sol"; // <= FOUND

['5']

5: import {IStakeRegistry} from "./IStakeRegistry.sol"; // <= FOUND

['6']

6: import {IIndexRegistry} from "./IIndexRegistry.sol"; // <= FOUND

['8']

8: import {BN254} from "../libraries/BN254.sol"; // <= FOUND

['4']

4: 
5: import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; // <= FOUND

['5']

5: import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; // <= FOUND

['5']

5: 
6: import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; // <= FOUND

['5']

5: import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; // <= FOUND

['5']

5: import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol"; // <= FOUND

['4']

4: 
5: import {IPauserRegistry} from "eigenlayer-contracts/src/contracts/interfaces/IPauserRegistry.sol"; // <= FOUND

['4']

4: import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; // <= FOUND

['6']

6: import {ISocketUpdater} from "./interfaces/ISocketUpdater.sol"; // <= FOUND

['7']

7: import {IServiceManager} from "./interfaces/IServiceManager.sol"; // <= FOUND

['13']

13: 
14: import {EIP1271SignatureUtils} from "eigenlayer-contracts/src/contracts/libraries/EIP1271SignatureUtils.sol"; // <= FOUND

['9']

9: import {BitmapUtils} from "./libraries/BitmapUtils.sol"; // <= FOUND

['17']

17: 
18: import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; // <= FOUND

['7']

7: import {Initializable} from "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; // <= FOUND

['19']

19: import {EIP712} from "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol"; // <= FOUND

['21']

21: 
22: import {Pausable} from "eigenlayer-contracts/src/contracts/permissions/Pausable.sol"; // <= FOUND

['22']

22: import {RegistryCoordinatorStorage} from "./RegistryCoordinatorStorage.sol"; // <= FOUND

['6']

6: 
7: import {BitmapUtils} from "./libraries/BitmapUtils.sol";  // <= FOUND

['8']

8: import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; // <= FOUND

['7']

7: 
8: import {IServiceManager} from "./interfaces/IServiceManager.sol"; // <= FOUND

['6']

6: 
7: import {StakeRegistryStorage, IStrategy} from "./StakeRegistryStorage.sol"; // <= FOUND

['5']

5: import {IStrategyManager, IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol"; // <= FOUND

['8']

8: import {IStakeRegistry} from  "./interfaces/IStakeRegistry.sol"; // <= FOUND

[NonCritical-62] Using while for unbounded loops isn’t recommended

Resolution

Utilizing a while loop for unbounded iterations in Solidity isn't advisable as it can lead to unintended infinite loops. Such loops could consume all the available gas, causing transactions to fail and losing the gas spent. This can lead to a denial-of-service vulnerability if used maliciously. It's generally better to impose an upper limit on iterations or apply a pattern that ensures termination. If an unbounded loop is necessary, implementing safeguards, careful logic, and possibly an emergency stop mechanism can mitigate the risks. Using tools to analyze loop conditions and behavior can also help avoid potential issues.

Num of instances: 2

Findings

Click to show findings

['141']

141:         while (n > 0)  // <= FOUND

['298']

298:         while (true)  // <= FOUND

[NonCritical-63] Use of override is unnecessary

Resolution

Starting with Solidity version 0.8.8, the use of the override keyword is simplified. If a function solely overrides an interface function and does not exist in multiple base contracts, specifying override becomes unnecessary. This change streamlines the code and makes it less verbose. Removing unnecessary use of override in these situations can make the code cleaner and more maintainable, aligning with the newer Solidity guidelines. It's a good practice to adapt to this updated behavior to stay consistent with the language's evolution and current best practices.

Num of instances: 1

Findings

Click to show findings

['147']

147:     function avsDirectory() external view override returns (address)  // <= FOUND

[NonCritical-64] Cyclomatic complexity in functions

Resolution

Cyclomatic complexity is a software metric used to measure the complexity of a program. It quantifies the number of linearly independent paths through a program's source code, giving an idea of how complex the control flow is. High cyclomatic complexity may indicate a higher risk of defects and can make the code harder to understand, test, and maintain. It often suggests that a function or method is trying to do too much, and a refactor might be needed. By breaking down complex functions into smaller, more focused pieces, you can improve readability, ease of testing, and overall maintainability.

Num of instances: 1

Findings

Click to show findings

['79']

79:     function isArrayStrictlyAscendingOrdered(bytes calldata bytesArray) internal pure returns (bool) { // <= FOUND
80:         
81:         if (bytesArray.length > MAX_BYTE_ARRAY_LENGTH) {
82:             return false;
83:         }
84: 
85:         if (bytesArray.length == 0) {
86:             return true;
87:         }
88: 
89:         
90:         bytes1 singleByte = bytesArray[0];
91: 
92:         
93:         
94:         for (uint256 i = 1; i < bytesArray.length; ++i) {
95:             if (uint256(uint8(bytesArray[i])) <= uint256(uint8(singleByte))) {
96:                 return false;
97:             }
98:             
99:             
100:             singleByte = bytesArray[i];
101:         }
102:         
103:         return true;
104:     }

[NonCritical-65] Owner can renounce while system is paused

Resolution

If an owner renounces their role while a system is paused, it could lead to permanent inaccessibility of assets stored in the contract. Such a scenario jeopardizes the trust and functionality of the protocol, as no one would be able to unpause the system or access funds. To mitigate this risk, it's essential to implement a mechanism that either prevents the owner from renouncing while the system is paused or requires multiple signatories to perform such critical actions. By introducing such safeguards, it ensures that user assets are not rendered inaccessible due to a single user's action or oversight.

Num of instances: 1

Findings

Click to show findings

['32']

32: contract RegistryCoordinator is 
33:     EIP712, 
34:     Initializable, 
35:     Pausable, // <= FOUND
36:     OwnableUpgradeable, // <= FOUND
37:     RegistryCoordinatorStorage, 
38:     ISocketUpdater, 
39:     ISignatureUtils
40: 

[NonCritical-66] Contract does not follow the Solidity style guide's suggested layout ordering

Num of instances: 2

Findings

Click to show findings

['18']

18: contract BLSSignatureChecker is IBLSSignatureChecker  // <= FOUND

[]

32: contract RegistryCoordinator is 
33:     EIP712, 
34:     Initializable, 
35:     Pausable,
36:     OwnableUpgradeable,
37:     RegistryCoordinatorStorage, 
38:     ISocketUpdater, 
39:     ISignatureUtils
40: 

[NonCritical-67] Consider adding formal verification proofs

Num of instances: 6

Findings

Click to show findings

['10']

10: contract BLSApkRegistry is BLSApkRegistryStorage  // <= FOUND

['18']

18: contract BLSSignatureChecker is IBLSSignatureChecker  // <= FOUND

['11']

11: contract IndexRegistry is IndexRegistryStorage  // <= FOUND

['15']

15: contract OperatorStateRetriever  // <= FOUND

['32']

32: contract RegistryCoordinator is  // <= FOUND
33:     EIP712, 
34:     Initializable, 
35:     Pausable,
36:     OwnableUpgradeable,
37:     RegistryCoordinatorStorage, 
38:     ISocketUpdater, 
39:     ISignatureUtils
40: 

['22']

22: contract StakeRegistry is StakeRegistryStorage  // <= FOUND

[NonCritical-68] Unused import

Resolution

If these serve no purpose, they should be safely removed

Num of instances: 1

Findings

Click to show findings

['5']

5: import {IStrategyManager, IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol"; // <= FOUND

[NonCritical-69] Consider bounding input array length

Resolution

Unbounded array inputs in functions can lead to unintentional excessive gas consumption, potentially causing a transaction to revert after expending substantial gas. To enhance user experience and prevent such scenarios, consider implementing a require() statement that limits the array length to a defined maximum. This constraint ensures that transactions won't proceed if they're likely to hit gas limits due to array size, saving users from unnecessary gas costs and offering a more predictable interaction with the contract.

Num of instances: 16

Findings

Click to show findings

['124']

124:        for (uint256 i = 0; (arrayIndex < bytesArray.length) && (i < 256); ++i) { // <= FOUND
125:             
126:             bitMask = uint256(1 << i);
127:             
128:             if (bitmap & bitMask != 0) {
129:                 
130:                 bytesArray[arrayIndex] = bytes1(uint8(i));
131:                 
132:                 unchecked{ ++arrayIndex; }
133:             }
134:         }

['153']

153:        for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
154:             
155:             uint8 quorumNumber = uint8(quorumNumbers[i]);
156:             uint256 historyLength = apkHistory[quorumNumber].length; // <= FOUND
157:             require(historyLength != 0, "BLSApkRegistry._processQuorumApkUpdate: quorum does not exist");
158: 
159:             
160:             newApk = currentApk[quorumNumber].plus(point);
161:             currentApk[quorumNumber] = newApk;
162:             bytes24 newApkHash = bytes24(BN254.hashG1Point(newApk));
163: 
164:             
165:             
166:             ApkUpdate storage lastUpdate = apkHistory[quorumNumber][historyLength - 1];
167:             if (lastUpdate.updateBlockNumber == uint32(block.number)) {
168:                 lastUpdate.apkHash = newApkHash;
169:             } else {
170:                 lastUpdate.nextUpdateBlockNumber = uint32(block.number);
171:                 apkHistory[quorumNumber].push(ApkUpdate({
172:                     apkHash: newApkHash,
173:                     updateBlockNumber: uint32(block.number),
174:                     nextUpdateBlockNumber: 0
175:                 }));
176:             }
177:         }

['209']

209:        for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
210:             uint8 quorumNumber = uint8(quorumNumbers[i]);
211:             
212:             uint256 quorumApkUpdatesLength = apkHistory[quorumNumber].length; // <= FOUND
213:             if (quorumApkUpdatesLength == 0 || blockNumber < apkHistory[quorumNumber][0].updateBlockNumber) {
214:                 revert("BLSApkRegistry.getApkIndicesAtBlockNumber: blockNumber is before the first update");
215:             }
216: 
217:             
218:             for (uint256 j = quorumApkUpdatesLength; j > 0; j--) {
219:                 if (apkHistory[quorumNumber][j - 1].updateBlockNumber <= blockNumber) {
220:                     indices[i] = uint32(j - 1);
221:                     break;
222:                 }
223:             }
224:         }

['188']

188:            for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
189:                 
190:                 
191:                 if (_staleStakesForbidden) {
192:                     require(
193:                         registryCoordinator.quorumUpdateBlockNumber(uint8(quorumNumbers[i])) + withdrawalDelayBlocks >= referenceBlockNumber,
194:                         "BLSSignatureChecker.checkSignatures: StakeRegistry updates must be within withdrawalDelayBlocks window"
195:                     );
196:                 }
197: 
198:                 
199:                 
200:                 require(
201:                     bytes24(params.quorumApks[i].hashG1Point()) == 
202:                         blsApkRegistry.getApkHashAtBlockNumberAndIndex({
203:                             quorumNumber: uint8(quorumNumbers[i]),
204:                             blockNumber: referenceBlockNumber,
205:                             index: params.quorumApkIndices[i]
206:                         }),
207:                     "BLSSignatureChecker.checkSignatures: quorumApk hash in storage does not match provided quorum apk"
208:                 );
209:                 apk = apk.plus(params.quorumApks[i]);
210: 
211:                 
212:                 stakeTotals.totalStakeForQuorum[i] = 
213:                     stakeRegistry.getTotalStakeAtBlockNumberFromIndex({
214:                         quorumNumber: uint8(quorumNumbers[i]),
215:                         blockNumber: referenceBlockNumber,
216:                         index: params.totalStakeIndices[i]
217:                     });
218:                 stakeTotals.signedStakeForQuorum[i] = stakeTotals.totalStakeForQuorum[i];
219: 
220:                 
221:                 uint256 nonSignerForQuorumIndex = 0;
222:                 
223:                 
224:                 
225:                 for (uint256 j = 0; j < params.nonSignerPubkeys.length; j++) { // <= FOUND
226:                     
227:                     if (BitmapUtils.isSet(nonSigners.quorumBitmaps[j], uint8(quorumNumbers[i]))) {
228:                         stakeTotals.signedStakeForQuorum[i] -=
229:                             stakeRegistry.getStakeAtBlockNumberAndIndex({
230:                                 quorumNumber: uint8(quorumNumbers[i]),
231:                                 blockNumber: referenceBlockNumber,
232:                                 operatorId: nonSigners.pubkeyHashes[j],
233:                                 index: params.nonSignerStakeIndices[i][nonSignerForQuorumIndex]
234:                             });
235:                         unchecked {
236:                             ++nonSignerForQuorumIndex;
237:                         }
238:                     }
239:                 }
240:             }

['46']

46:        for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
47:             
48:             uint8 quorumNumber = uint8(quorumNumbers[i]);
49:             uint256 historyLength = _operatorCountHistory[quorumNumber].length; // <= FOUND
50:             require(historyLength != 0, "IndexRegistry.registerOperator: quorum does not exist");
51: 
52:             
53: 
56:             uint32 newOperatorCount = _increaseOperatorCount(quorumNumber);
57:             _assignOperatorToIndex({
58:                 operatorId: operatorId,
59:                 quorumNumber: quorumNumber,
60:                 operatorIndex: newOperatorCount - 1
61:             });
62: 
63:             
64:             numOperatorsPerQuorum[i] = newOperatorCount;
65:         }

['86']

86:        for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
87:             
88:             uint8 quorumNumber = uint8(quorumNumbers[i]);
89:             uint256 historyLength = _operatorCountHistory[quorumNumber].length; // <= FOUND
90:             require(historyLength != 0, "IndexRegistry.registerOperator: quorum does not exist");
91:             uint32 operatorIndexToRemove = currentOperatorIndex[quorumNumber][operatorId];
92: 
93:             
94: 
99:             uint32 newOperatorCount = _decreaseOperatorCount(quorumNumber);
100:             bytes32 lastOperatorId = _popLastOperator(quorumNumber, newOperatorCount);
101:             if (operatorId != lastOperatorId) {
102:                 _assignOperatorToIndex({
103:                     operatorId: lastOperatorId,
104:                     quorumNumber: quorumNumber,
105:                     operatorIndex: operatorIndexToRemove
106:                 });
107:             }
108:         }

['74']

74:        for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
75:             uint8 quorumNumber = uint8(quorumNumbers[i]);
76:             bytes32[] memory operatorIds = indexRegistry.getOperatorListAtBlockNumber(quorumNumber, blockNumber);
77:             operators[i] = new Operator[](operatorIds.length); // <= FOUND
78:             for (uint256 j = 0; j < operatorIds.length; j++) { // <= FOUND
79:                 operators[i][j] = Operator({
80:                     operator: blsApkRegistry.getOperatorFromPubkeyHash(operatorIds[j]),
81:                     operatorId: bytes32(operatorIds[j]),
82:                     stake: stakeRegistry.getStakeAtBlockNumber(bytes32(operatorIds[j]), quorumNumber, blockNumber)
83:                 });
84:             }
85:         }

['120']

120:        for (uint8 quorumNumberIndex = 0; quorumNumberIndex < quorumNumbers.length; quorumNumberIndex++) { // <= FOUND
121:             uint256 numNonSignersForQuorum = 0;
122:             
123:             checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex] = new uint32[](nonSignerOperatorIds.length); // <= FOUND
124: 
125:             for (uint i = 0; i < nonSignerOperatorIds.length; i++) { // <= FOUND
126:                 
127:                 uint192 nonSignerQuorumBitmap = 
128:                     registryCoordinator.getQuorumBitmapAtBlockNumberByIndex(
129:                         nonSignerOperatorIds[i], 
130:                         referenceBlockNumber, 
131:                         checkSignaturesIndices.nonSignerQuorumBitmapIndices[i]
132:                     );
133:                 
134:                 require(nonSignerQuorumBitmap != 0, "OperatorStateRetriever.getCheckSignaturesIndices: operator must be registered at blocknumber");
135:                 
136:                 
137:                 if ((nonSignerQuorumBitmap >> uint8(quorumNumbers[quorumNumberIndex])) & 1 == 1) {
138:                     
139:                     checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex][numNonSignersForQuorum] = stakeRegistry.getStakeUpdateIndexAtBlockNumber(
140:                         nonSignerOperatorIds[i],
141:                         uint8(quorumNumbers[quorumNumberIndex]),
142:                         referenceBlockNumber
143:                     );
144:                     numNonSignersForQuorum++;
145:                 }
146:             }
147: 
148:             
149:             uint32[] memory nonSignerStakeIndicesForQuorum = new uint32[](numNonSignersForQuorum);
150:             for (uint i = 0; i < numNonSignersForQuorum; i++) {
151:                 nonSignerStakeIndicesForQuorum[i] = checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex][i];
152:             }
153:             checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex] = nonSignerStakeIndicesForQuorum;
154:         }

['109']

109:        for (uint256 i = 0; i < _operatorSetParams.length; i++) { // <= FOUND
110:             _createQuorum(_operatorSetParams[i], _minimumStakes[i], _strategyParams[i]);
111:         }

['258']

258:        for (uint256 i = 0; i < operators.length; i++) { // <= FOUND
259:             address operator = operators[i];
260:             OperatorInfo memory operatorInfo = _operatorInfo[operator];
261:             bytes32 operatorId = operatorInfo.operatorId;
262: 
263:             
264:             uint192 currentBitmap = _currentOperatorBitmap(operatorId);
265:             bytes memory quorumsToUpdate = BitmapUtils.bitmapToBytesArray(currentBitmap);
266:             _updateOperator(operator, operatorInfo, quorumsToUpdate);
267:         }

['299']

299:        for (uint256 i = 0; i < quorumNumbers.length; ++i) { // <= FOUND
300:             uint8 quorumNumber = uint8(quorumNumbers[i]);
301: 
302:             
303:             address[] calldata currQuorumOperators = operatorsPerQuorum[i];
304:             require(
305:                 currQuorumOperators.length == indexRegistry.totalOperatorsForQuorum(quorumNumber), // <= FOUND
306:                 "RegistryCoordinator.updateOperatorsForQuorum: number of updated operators does not match quorum total"
307:             );
308: 
309:             address prevOperatorAddress = address(0);
310:             
311:             
312:             
313:             
314:             for (uint256 j = 0; j < currQuorumOperators.length; ++j) { // <= FOUND
315:                 address operator = currQuorumOperators[j];
316:                 
317:                 OperatorInfo memory operatorInfo = _operatorInfo[operator];
318:                 bytes32 operatorId = operatorInfo.operatorId;
319:                 
320:                 {
321:                     uint192 currentBitmap = _currentOperatorBitmap(operatorId);
322:                     
323:                     require(
324:                         BitmapUtils.isSet(currentBitmap, quorumNumber),
325:                         "RegistryCoordinator.updateOperatorsForQuorum: operator not in quorum"
326:                     );
327:                     
328:                     require(
329:                         operator > prevOperatorAddress,
330:                         "RegistryCoordinator.updateOperatorsForQuorum: operators array must be sorted in ascending address order"
331:                     );
332:                 }
333:                 
334:                 
335:                 _updateOperator(operator, operatorInfo, quorumNumbers[i:i+1]);
336:                 prevOperatorAddress = operator;
337:             }
338: 
339:             
340:             quorumUpdateBlockNumber[quorumNumber] = block.number;
341:             emit QuorumBlockNumberUpdated(quorumNumber, block.number);
342:         }

['821']

821:        for (uint256 i = 0; i < operatorIds.length; i++) { // <= FOUND
822:             indices[i] = _getQuorumBitmapIndexAtBlockNumber(blockNumber, operatorIds[i]);
823:         }

['122']

122:        for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
123:             uint8 quorumNumber = uint8(quorumNumbers[i]);
124:             require(_quorumExists(quorumNumber), "StakeRegistry.deregisterOperator: quorum does not exist");
125: 
126:             
127:             int256 stakeDelta = _recordOperatorStakeUpdate({
128:                 operatorId: operatorId, 
129:                 quorumNumber: quorumNumber, 
130:                 newStake: 0
131:             });
132: 
133:             
134:             _recordTotalStakeUpdate(quorumNumber, stakeDelta);
135:         }

['162']

162:        for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
163:             uint8 quorumNumber = uint8(quorumNumbers[i]);
164:             require(_quorumExists(quorumNumber), "StakeRegistry.updateOperatorStake: quorum does not exist");
165: 
166:             
167:             
168:             (uint96 stakeWeight, bool hasMinimumStake) = _weightOfOperatorForQuorum(quorumNumber, operator);
169: 
170:             
171:             if (!hasMinimumStake) {
172:                 stakeWeight = 0;
173:                 quorumsToRemove = uint192(quorumsToRemove.setBit(quorumNumber));
174:             }
175: 
176:             
177:             
178:             int256 stakeDelta = _recordOperatorStakeUpdate({
179:                 operatorId: operatorId,
180:                 quorumNumber: quorumNumber,
181:                 newStake: stakeWeight
182:             });
183: 
184:             
185:             _recordTotalStakeUpdate(quorumNumber, stakeDelta);
186:         }

['248']

243:        for (uint256 i = 0; i < toRemoveLength; i++) {
244:             emit StrategyRemovedFromQuorum(quorumNumber, _strategyParams[indicesToRemove[i]].strategy);
245:             emit StrategyMultiplierUpdated(quorumNumber, _strategyParams[indicesToRemove[i]].strategy, 0);
246: 
247:             
248:             _strategyParams[indicesToRemove[i]] = _strategyParams[_strategyParams.length - 1]; // <= FOUND
249:             _strategyParams.pop();
250:             _strategiesPerQuorum[indicesToRemove[i]] = _strategiesPerQuorum[_strategiesPerQuorum.length - 1]; // <= FOUND
251:             _strategiesPerQuorum.pop();
252:         }

['698']

698:        for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
699:             uint8 quorumNumber = uint8(quorumNumbers[i]);
700:             require(_quorumExists(quorumNumber), "StakeRegistry.getTotalStakeIndicesAtBlockNumber: quorum does not exist");
701:             require(
702:                 _totalStakeHistory[quorumNumber][0].updateBlockNumber <= blockNumber,
703:                 "StakeRegistry.getTotalStakeIndicesAtBlockNumber: quorum has no stake history at blockNumber"
704:             );
705:             uint256 length = _totalStakeHistory[quorumNumber].length; // <= FOUND
706:             for (uint256 j = 0; j < length; j++) {
707:                 if (_totalStakeHistory[quorumNumber][length - j - 1].updateBlockNumber <= blockNumber) {
708:                     indices[i] = uint32(length - j - 1);
709:                     break;
710:                 }
711:             }
712:         }

[NonCritical-70] Duplicated require() checks should be refactored to a modifier or function

Num of instances: 1

Findings

Click to show findings

['50']

50: require(historyLength != 0, "IndexRegistry.registerOperator: quorum does not exist"); // <= FOUND

[NonCritical-71] Missing events in sensitive functions

Resolution

Sensitive setter functions in smart contracts often alter critical state variables. Without events emitted in these functions, external observers or dApps cannot easily track or react to these state changes. Missing events can obscure contract activity, hampering transparency and making integration more challenging. To resolve this, incorporate appropriate event emissions within these functions. Events offer an efficient way to log crucial changes, aiding in real-time tracking and post-transaction verification.

Num of instances: 12

Findings

Click to show findings

['159']

159:     function setBit(uint256 bitmap, uint8 bit) internal pure returns (uint256) { // <= FOUND
160:         return bitmap | (1 << bit);
161:     }

['400']

400:     function setOperatorSetParams( // <= FOUND
401:         uint8 quorumNumber, 
402:         OperatorSetParam memory operatorSetParams
403:     ) external onlyOwner quorumExists(quorumNumber) {
404:         _setOperatorSetParams(quorumNumber, operatorSetParams);
405:     }

['413']

413:     function setChurnApprover(address _churnApprover) external onlyOwner { // <= FOUND
414:         _setChurnApprover(_churnApprover);
415:     }

['422']

422:     function setEjector(address _ejector) external onlyOwner { // <= FOUND
423:         _setEjector(_ejector);
424:     }

['56']

56:     function setMetadataURI(string memory _metadataURI) public virtual onlyOwner { // <= FOUND
57:         _avsDirectory.updateAVSMetadataURI(_metadataURI);
58:     }

['208']

208:     function setMinimumStakeForQuorum( // <= FOUND
209:         uint8 quorumNumber, 
210:         uint96 minimumStake
211:     ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber) {
212:         _setMinimumStakeForQuorum(quorumNumber, minimumStake);
213:     }

['257']

257:     function updateOperators(address[] calldata operators) external onlyWhenNotPaused(PAUSED_UPDATE_OPERATOR) { // <= FOUND
258:         for (uint256 i = 0; i < operators.length; i++) {
259:             address operator = operators[i];
260:             OperatorInfo memory operatorInfo = _operatorInfo[operator];
261:             bytes32 operatorId = operatorInfo.operatorId;
262: 
263:             
264:             uint192 currentBitmap = _currentOperatorBitmap(operatorId);
265:             bytes memory quorumsToUpdate = BitmapUtils.bitmapToBytesArray(currentBitmap);
266:             _updateOperator(operator, operatorInfo, quorumsToUpdate);
267:         }
268:     }

['147']

147:     function updateOperatorStake( // <= FOUND
148:         address operator, 
149:         bytes32 operatorId, 
150:         bytes calldata quorumNumbers
151:     ) external onlyRegistryCoordinator returns (uint192) {
152:         uint192 quorumsToRemove;
153: 
154:         
155: 
162:         for (uint256 i = 0; i < quorumNumbers.length; i++) {
163:             uint8 quorumNumber = uint8(quorumNumbers[i]);
164:             require(_quorumExists(quorumNumber), "StakeRegistry.updateOperatorStake: quorum does not exist");
165: 
166:             
167:             
168:             (uint96 stakeWeight, bool hasMinimumStake) = _weightOfOperatorForQuorum(quorumNumber, operator);
169: 
170:             
171:             if (!hasMinimumStake) {
172:                 stakeWeight = 0;
173:                 quorumsToRemove = uint192(quorumsToRemove.setBit(quorumNumber));
174:             }
175: 
176:             
177:             
178:             int256 stakeDelta = _recordOperatorStakeUpdate({
179:                 operatorId: operatorId,
180:                 quorumNumber: quorumNumber,
181:                 newStake: stakeWeight
182:             });
183: 
184:             
185:             _recordTotalStakeUpdate(quorumNumber, stakeDelta);
186:         }
187: 
188:         return quorumsToRemove;
189:     }

['166']

166:     function _updateOperatorCountHistory( // <= FOUND
167:         uint8 quorumNumber,
168:         QuorumUpdate storage lastUpdate,
169:         uint32 newOperatorCount
170:     ) internal {
171:         if (lastUpdate.fromBlockNumber == uint32(block.number)) {
172:             lastUpdate.numOperators = newOperatorCount;
173:         } else {
174:             _operatorCountHistory[quorumNumber].push(QuorumUpdate({
175:                 numOperators: newOperatorCount,
176:                 fromBlockNumber: uint32(block.number)
177:             }));
178:         }
179:     }

['217']

217:     function _updateOperatorIndexHistory( // <= FOUND
218:         uint8 quorumNumber,
219:         uint32 operatorIndex,
220:         OperatorUpdate storage lastUpdate,
221:         bytes32 newOperatorId
222:     ) internal {
223:         if (lastUpdate.fromBlockNumber == uint32(block.number)) {
224:             lastUpdate.operatorId = newOperatorId;
225:         } else {
226:             _operatorIndexHistory[quorumNumber][operatorIndex].push(OperatorUpdate({
227:                 operatorId: newOperatorId,
228:                 fromBlockNumber: uint32(block.number)
229:             }));
230:         }
231:     }

['609']

609:     function _updateOperator( // <= FOUND
610:         address operator,
611:         OperatorInfo memory operatorInfo,
612:         bytes memory quorumsToUpdate
613:     ) internal {
614:         if (operatorInfo.status != OperatorStatus.REGISTERED) {
615:             return;
616:         }
617:         bytes32 operatorId = operatorInfo.operatorId;
618:         uint192 quorumsToRemove = stakeRegistry.updateOperatorStake(operator, operatorId, quorumsToUpdate);
619: 
620:         if (!quorumsToRemove.isEmpty()) {
621:             _deregisterOperator({
622:                 operator: operator,
623:                 quorumNumbers: BitmapUtils.bitmapToBytesArray(quorumsToRemove)
624:             });    
625:         }
626:     }

['698']

698:     function _updateOperatorBitmap(bytes32 operatorId, uint192 newBitmap) internal { // <= FOUND
699: 
700:         uint256 historyLength = _operatorBitmapHistory[operatorId].length;
701: 
702:         if (historyLength == 0) {
703:             
704:             _operatorBitmapHistory[operatorId].push(QuorumBitmapUpdate({
705:                 updateBlockNumber: uint32(block.number),
706:                 nextUpdateBlockNumber: 0,
707:                 quorumBitmap: newBitmap
708:             }));
709:         } else {
710:             
711:             QuorumBitmapUpdate storage lastUpdate = _operatorBitmapHistory[operatorId][historyLength - 1];
712: 
713:             
714: 
717:             if (lastUpdate.updateBlockNumber == uint32(block.number)) {
718:                 lastUpdate.quorumBitmap = newBitmap;
719:             } else {
720:                 lastUpdate.nextUpdateBlockNumber = uint32(block.number);
721:                 _operatorBitmapHistory[operatorId].push(QuorumBitmapUpdate({
722:                     updateBlockNumber: uint32(block.number),
723:                     nextUpdateBlockNumber: 0,
724:                     quorumBitmap: newBitmap
725:                 }));
726:             }
727:         }
728:     }

[NonCritical-72] Unchecked increments can overflow

Resolution

Unchecked increments in variables can lead to overflow, causing values to wrap around unexpectedly. This can disrupt contract logic. Always validate before incrementing.

Num of instances: 1

Findings

Click to show findings

['132']

132:                 unchecked{ ++arrayIndex; } // <= FOUND

[NonCritical-73] Unused local variable

Num of instances: 1

Findings

Click to show findings

['284']

284:     function updateOperatorsForQuorum(
285:         address[][] calldata operatorsPerQuorum,
286:         bytes calldata quorumNumbers
287:     ) external onlyWhenNotPaused(PAUSED_UPDATE_OPERATOR) {
288:         
289:         
290:         
291:         
292:         uint192 quorumBitmap = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount));
293:         require(
294:             operatorsPerQuorum.length == quorumNumbers.length,
295:             "RegistryCoordinator.updateOperatorsForQuorum: input length mismatch"
296:         );
297: 
298:         
299:         for (uint256 i = 0; i < quorumNumbers.length; ++i) {
300:             uint8 quorumNumber = uint8(quorumNumbers[i]);
301: 
302:             
303:             address[] calldata currQuorumOperators = operatorsPerQuorum[i];
304:             require(
305:                 currQuorumOperators.length == indexRegistry.totalOperatorsForQuorum(quorumNumber),
306:                 "RegistryCoordinator.updateOperatorsForQuorum: number of updated operators does not match quorum total"
307:             );
308: 
309:             address prevOperatorAddress = address(0);
310:             
311:             
312:             
313:             
314:             for (uint256 j = 0; j < currQuorumOperators.length; ++j) {
315:                 address operator = currQuorumOperators[j];
316:                 
317:                 OperatorInfo memory operatorInfo = _operatorInfo[operator];
318:                 bytes32 operatorId = operatorInfo.operatorId;
319:                 
320:                 {
321:                     uint192 currentBitmap = _currentOperatorBitmap(operatorId);
322:                     
323:                     require(
324:                         BitmapUtils.isSet(currentBitmap, quorumNumber),
325:                         "RegistryCoordinator.updateOperatorsForQuorum: operator not in quorum"
326:                     );
327:                     
328:                     require(
329:                         operator > prevOperatorAddress,
330:                         "RegistryCoordinator.updateOperatorsForQuorum: operators array must be sorted in ascending address order" // <= FOUND 'order"'
331:                     );
332:                 }
333:                 
334:                 
335:                 _updateOperator(operator, operatorInfo, quorumNumbers[i:i+1]);
336:                 prevOperatorAddress = operator;
337:             }
338: 
339:             
340:             quorumUpdateBlockNumber[quorumNumber] = block.number;
341:             emit QuorumBlockNumberUpdated(quorumNumber, block.number);
342:         }
343:     }

[NonCritical-74] Don't only depend on Solidity's arithmetic ordering.

Resolution

Relying on Solidity's default arithmetic operator precedence can lead to misunderstood or overlooked nuances in code execution. Misinterpretation risks can be significant, especially when different developers or auditors review the code. To ensure clarity and minimize potential errors, it's recommended to use parentheses. These not only override the default precedence when needed but also emphasize the intended order of operations. By deliberately showing the intended calculation sequence using parentheses, developers make the code's logic explicit, reducing the risk of unintended behaviors and making the codebase more maintainable and understandable for all stakeholders.

Num of instances: 2

Findings

Click to show findings

['633']

633:         return operatorStake * setParams.kickBIPsOfOperatorStake / BIPS_DENOMINATOR; // <= FOUND

['641']

641:         return totalStake * setParams.kickBIPsOfTotalStake / BIPS_DENOMINATOR; // <= FOUND

[NonCritical-75] It is best practice to use linear inheritance

Resolution

In Solidity, complex inheritance structures can obfuscate code understanding, introducing potential security risks. Multiple inheritance, especially with overlapping function names or state variables, can cause unintentional overrides or ambiguous behavior. Resolution: Strive for linear and simple inheritance chains. Avoid diamond or circular inheritance patterns. Clearly document the purpose and relationships of base contracts, ensuring that overrides are intentional. Tools like Remix or Hardhat can visualize inheritance chains, assisting in verification. Keeping inheritance streamlined aids in better code readability, reduces potential errors, and ensures smoother audits and upgrades.

Num of instances: 1

Findings

Click to show findings

['33']

32: contract RegistryCoordinator is 
33:     EIP712,  // <= FOUND
34:     Initializable,  // <= FOUND
35:     Pausable, // <= FOUND
36:     OwnableUpgradeable, // <= FOUND
37:     RegistryCoordinatorStorage,  // <= FOUND
38:     ISocketUpdater,  // <= FOUND
39:     ISignatureUtils
40: 

[NonCritical-76] Contracts with only unimplemented functions can be labeled as abstract

Resolution

In Solidity, a contract that's not meant to be deployed on its own but is intended to be inherited by other contracts should be marked abstract. This ensures that developers recognize the contract's incomplete or intended-to-be-extended nature. If a contract has unimplemented functions or is designed with the intention that another contract should extend its functionality, it should be explicitly labeled as abstract. This helps prevent inadvertent deployments and clearly communicates the contract's purpose to other developers. Resolution: Review the contract, and if it's not supposed to function standalone, mark it as abstract to make the intention clear.

Num of instances: 1

Findings

Click to show findings

['15']

15: contract OperatorStateRetriever 

[NonCritical-77] A event should be emitted if a non immutable state variable is set in a constructor

Num of instances: 1

Findings

Click to show findings

['38']

38:     constructor(IRegistryCoordinator _registryCoordinator) {
39:         registryCoordinator = _registryCoordinator;
40:         stakeRegistry = _registryCoordinator.stakeRegistry();
41:         blsApkRegistry = _registryCoordinator.blsApkRegistry();
42:         delegation = stakeRegistry.delegation();
43:         
44:         staleStakesForbidden = true; // <= FOUND
45:     }

[NonCritical-78] Superfluous parameter can only be one value

Resolution

Using redundant parameters in smart contracts can lead to unnecessary complexity and potential vulnerabilities. When a function parameter is always constrained to a specific value due to an if or require statement, it renders the parameter superfluous. Including such parameters can be misleading to developers or users, suggesting a flexibility that doesn't exist in reality. Additionally, unnecessary parameters increase the gas cost for transactions. Resolution: Analyze the contract to identify parameters that are rendered static by conditional checks. Remove these parameters from the function signature and update the function logic accordingly. This simplifies the code, reduces gas costs, and enhances clarity and security.

Num of instances: 5

Findings

Click to show findings

['531']

531:     function _validateChurn(
532:         uint8 quorumNumber, 
533:         uint96 totalQuorumStake,
534:         address newOperator, 
535:         uint96 newOperatorStake,
536:         OperatorKickParam memory kickParams, 
537:         OperatorSetParam memory setParams
538:     ) internal view {
539:         address operatorToKick = kickParams.operator;
540:         bytes32 idToKick = _operatorInfo[operatorToKick].operatorId;
541:         require(newOperator != operatorToKick, "RegistryCoordinator._validateChurn: cannot churn self");
542:         require(kickParams.quorumNumber == quorumNumber, "RegistryCoordinator._validateChurn: quorumNumber not the same as signed"); // <= FOUND
543: 
544:         
545:         uint96 operatorToKickStake = stakeRegistry.getCurrentStake(idToKick, quorumNumber);
546:         require(
547:             newOperatorStake > _individualKickThreshold(operatorToKickStake, setParams),
548:             "RegistryCoordinator._validateChurn: incoming operator has insufficient stake for churn"
549:         );
550:         require(
551:             operatorToKickStake < _totalKickThreshold(totalQuorumStake, setParams),
552:             "RegistryCoordinator._validateChurn: cannot kick operator with more than kickBIPsOfTotalStake"
553:         );
554:     }

['87']

87:     function checkSignatures(
88:         bytes32 msgHash, 
89:         bytes calldata quorumNumbers,
90:         uint32 referenceBlockNumber, 
91:         NonSignerStakesAndSignature memory params
92:     ) 
93:         public 
94:         view
95:         returns (
96:             QuorumStakeTotals memory,
97:             bytes32
98:         )
99:     {
100:         require(quorumNumbers.length != 0, "BLSSignatureChecker.checkSignatures: empty quorum input");
101: 
102:         require(
103:             (quorumNumbers.length == params.quorumApks.length) &&
104:             (quorumNumbers.length == params.quorumApkIndices.length) &&
105:             (quorumNumbers.length == params.totalStakeIndices.length) &&
106:             (quorumNumbers.length == params.nonSignerStakeIndices.length),
107:             "BLSSignatureChecker.checkSignatures: input quorum length mismatch"
108:         );
109: 
110:         require(
111:             params.nonSignerPubkeys.length == params.nonSignerQuorumBitmapIndices.length,
112:             "BLSSignatureChecker.checkSignatures: input nonsigner length mismatch"
113:         );
114: 
115:         require(referenceBlockNumber < uint32(block.number), "BLSSignatureChecker.checkSignatures: invalid reference block");
116: 
117:         
118:         
119:         
120:         
121:         
122:         
123:         BN254.G1Point memory apk = BN254.G1Point(0, 0);
124: 
125:         
126:         
127:         
128:         QuorumStakeTotals memory stakeTotals;
129:         stakeTotals.totalStakeForQuorum = new uint96[](quorumNumbers.length);
130:         stakeTotals.signedStakeForQuorum = new uint96[](quorumNumbers.length);
131: 
132:         NonSignerInfo memory nonSigners;
133:         nonSigners.quorumBitmaps = new uint256[](params.nonSignerPubkeys.length);
134:         nonSigners.pubkeyHashes = new bytes32[](params.nonSignerPubkeys.length);
135: 
136:         {
137:             
138:             
139:             uint256 signingQuorumBitmap = BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, registryCoordinator.quorumCount());
140: 
141:             for (uint256 j = 0; j < params.nonSignerPubkeys.length; j++) {
142:                 
143:                 
144:                 
145:                 nonSigners.pubkeyHashes[j] = params.nonSignerPubkeys[j].hashG1Point();
146:                 if (j != 0) {
147:                     require(
148:                         uint256(nonSigners.pubkeyHashes[j]) > uint256(nonSigners.pubkeyHashes[j - 1]),
149:                         "BLSSignatureChecker.checkSignatures: nonSignerPubkeys not sorted"
150:                     );
151:                 }
152: 
153:                 
154:                 nonSigners.quorumBitmaps[j] = 
155:                     registryCoordinator.getQuorumBitmapAtBlockNumberByIndex({
156:                         operatorId: nonSigners.pubkeyHashes[j],
157:                         blockNumber: referenceBlockNumber,
158:                         index: params.nonSignerQuorumBitmapIndices[j]
159:                     });
160: 
161:                 
162:                 
163:                 
164:                 apk = apk.plus(
165:                     params.nonSignerPubkeys[j]
166:                         .scalar_mul_tiny(
167:                             BitmapUtils.countNumOnes(nonSigners.quorumBitmaps[j] & signingQuorumBitmap) 
168:                         )
169:                 );
170:             }
171:         }
172: 
173:         
174:         
175:         
176:         apk = apk.negate();
177: 
178:         
179: 
184:         {
185:             bool _staleStakesForbidden = staleStakesForbidden;
186:             uint256 withdrawalDelayBlocks = _staleStakesForbidden ? delegation.minWithdrawalDelayBlocks() : 0;
187: 
188:             for (uint256 i = 0; i < quorumNumbers.length; i++) {
189:                 
190:                 
191:                 if (_staleStakesForbidden) {
192:                     require(
193:                         registryCoordinator.quorumUpdateBlockNumber(uint8(quorumNumbers[i])) + withdrawalDelayBlocks >= referenceBlockNumber,
194:                         "BLSSignatureChecker.checkSignatures: StakeRegistry updates must be within withdrawalDelayBlocks window"
195:                     );
196:                 }
197: 
198:                 
199:                 
200:                 require(
201:                     bytes24(params.quorumApks[i].hashG1Point()) == 
202:                         blsApkRegistry.getApkHashAtBlockNumberAndIndex({
203:                             quorumNumber: uint8(quorumNumbers[i]),
204:                             blockNumber: referenceBlockNumber,
205:                             index: params.quorumApkIndices[i]
206:                         }),
207:                     "BLSSignatureChecker.checkSignatures: quorumApk hash in storage does not match provided quorum apk"
208:                 );
209:                 apk = apk.plus(params.quorumApks[i]);
210: 
211:                 
212:                 stakeTotals.totalStakeForQuorum[i] = 
213:                     stakeRegistry.getTotalStakeAtBlockNumberFromIndex({
214:                         quorumNumber: uint8(quorumNumbers[i]),
215:                         blockNumber: referenceBlockNumber,
216:                         index: params.totalStakeIndices[i]
217:                     });
218:                 stakeTotals.signedStakeForQuorum[i] = stakeTotals.totalStakeForQuorum[i];
219: 
220:                 
221:                 uint256 nonSignerForQuorumIndex = 0;
222:                 
223:                 
224:                 
225:                 for (uint256 j = 0; j < params.nonSignerPubkeys.length; j++) {
226:                     
227:                     if (BitmapUtils.isSet(nonSigners.quorumBitmaps[j], uint8(quorumNumbers[i]))) {
228:                         stakeTotals.signedStakeForQuorum[i] -=
229:                             stakeRegistry.getStakeAtBlockNumberAndIndex({
230:                                 quorumNumber: uint8(quorumNumbers[i]),
231:                                 blockNumber: referenceBlockNumber,
232:                                 operatorId: nonSigners.pubkeyHashes[j],
233:                                 index: params.nonSignerStakeIndices[i][nonSignerForQuorumIndex]
234:                             });
235:                         unchecked {
236:                             ++nonSignerForQuorumIndex;
237:                         }
238:                     }
239:                 }
240:             }
241:         }
242:         {
243:             
244:             (bool pairingSuccessful, bool signatureIsValid) = trySignatureAndApkVerification(
245:                 msgHash, 
246:                 apk, 
247:                 params.apkG2, 
248:                 params.sigma
249:             );
250:             require(pairingSuccessful, "BLSSignatureChecker.checkSignatures: pairing precompile call failed");
251:             require(signatureIsValid, "BLSSignatureChecker.checkSignatures: signature is invalid");
252:         }
253:         
254:         bytes32 signatoryRecordHash = keccak256(abi.encodePacked(referenceBlockNumber, nonSigners.pubkeyHashes));
255: 
256:         
257:         return (stakeTotals, signatoryRecordHash);
258:     }

['82']

82:     function initialize(
83:         address _initialOwner,
84:         address _churnApprover,
85:         address _ejector,
86:         IPauserRegistry _pauserRegistry,
87:         uint256 _initialPausedStatus,
88:         OperatorSetParam[] memory _operatorSetParams,
89:         uint96[] memory _minimumStakes,
90:         IStakeRegistry.StrategyParams[][] memory _strategyParams
91:     ) external initializer {
92:         require(
93:             _operatorSetParams.length == _minimumStakes.length && _minimumStakes.length == _strategyParams.length,
94:             "RegistryCoordinator.initialize: input length mismatch"
95:         );
96:         
97:         
98:         _transferOwnership(_initialOwner);
99:         _initializePauser(_pauserRegistry, _initialPausedStatus);
100:         _setChurnApprover(_churnApprover);
101:         _setEjector(_ejector);
102: 
103:         
104:         registries.push(address(stakeRegistry));
105:         registries.push(address(blsApkRegistry));
106:         registries.push(address(indexRegistry));
107: 
108:         
109:         for (uint256 i = 0; i < _operatorSetParams.length; i++) {
110:             _createQuorum(_operatorSetParams[i], _minimumStakes[i], _strategyParams[i]);
111:         }
112:     }

['177']

177:     function registerOperatorWithChurn(
178:         bytes calldata quorumNumbers, 
179:         string calldata socket,
180:         IBLSApkRegistry.PubkeyRegistrationParams calldata params,
181:         OperatorKickParam[] calldata operatorKickParams,
182:         SignatureWithSaltAndExpiry memory churnApproverSignature,
183:         SignatureWithSaltAndExpiry memory operatorSignature
184:     ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) {
185:         require(operatorKickParams.length == quorumNumbers.length, "RegistryCoordinator.registerOperatorWithChurn: input length mismatch"); // <= FOUND
186:         
187:         
188: 
194:         bytes32 operatorId = _getOrCreateOperatorId(msg.sender, params);
195: 
196:         
197:         _verifyChurnApproverSignature({
198:             registeringOperator: msg.sender,
199:             registeringOperatorId: operatorId,
200:             operatorKickParams: operatorKickParams,
201:             churnApproverSignature: churnApproverSignature
202:         });
203: 
204:         
205:         
206:         RegisterResults memory results = _registerOperator({
207:             operator: msg.sender,
208:             operatorId: operatorId,
209:             quorumNumbers: quorumNumbers,
210:             socket: socket,
211:             operatorSignature: operatorSignature
212:         });
213: 
214:         
215:         
216:         for (uint256 i = 0; i < quorumNumbers.length; i++) {
217:             OperatorSetParam memory operatorSetParams = _quorumParams[uint8(quorumNumbers[i])];
218:             
219:             
220: 
223:             if (results.numOperatorsPerQuorum[i] > operatorSetParams.maxOperatorCount) {
224:                 _validateChurn({
225:                     quorumNumber: uint8(quorumNumbers[i]),
226:                     totalQuorumStake: results.totalStakes[i],
227:                     newOperator: msg.sender,
228:                     newOperatorStake: results.operatorStakes[i],
229:                     kickParams: operatorKickParams[i],
230:                     setParams: operatorSetParams
231:                 });
232: 
233:                 _deregisterOperator(operatorKickParams[i].operator, quorumNumbers[i:i+1]);
234:             }
235:         }
236:     }

['284']

284:     function updateOperatorsForQuorum(
285:         address[][] calldata operatorsPerQuorum,
286:         bytes calldata quorumNumbers
287:     ) external onlyWhenNotPaused(PAUSED_UPDATE_OPERATOR) {
288:         
289:         
290:         
291:         
292:         uint192 quorumBitmap = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount));
293:         require(
294:             operatorsPerQuorum.length == quorumNumbers.length,
295:             "RegistryCoordinator.updateOperatorsForQuorum: input length mismatch"
296:         );
297: 
298:         
299:         for (uint256 i = 0; i < quorumNumbers.length; ++i) {
300:             uint8 quorumNumber = uint8(quorumNumbers[i]);
301: 
302:             
303:             address[] calldata currQuorumOperators = operatorsPerQuorum[i];
304:             require(
305:                 currQuorumOperators.length == indexRegistry.totalOperatorsForQuorum(quorumNumber),
306:                 "RegistryCoordinator.updateOperatorsForQuorum: number of updated operators does not match quorum total"
307:             );
308: 
309:             address prevOperatorAddress = address(0);
310:             
311:             
312:             
313:             
314:             for (uint256 j = 0; j < currQuorumOperators.length; ++j) {
315:                 address operator = currQuorumOperators[j];
316:                 
317:                 OperatorInfo memory operatorInfo = _operatorInfo[operator];
318:                 bytes32 operatorId = operatorInfo.operatorId;
319:                 
320:                 {
321:                     uint192 currentBitmap = _currentOperatorBitmap(operatorId);
322:                     
323:                     require(
324:                         BitmapUtils.isSet(currentBitmap, quorumNumber),
325:                         "RegistryCoordinator.updateOperatorsForQuorum: operator not in quorum"
326:                     );
327:                     
328:                     require(
329:                         operator > prevOperatorAddress,
330:                         "RegistryCoordinator.updateOperatorsForQuorum: operators array must be sorted in ascending address order"
331:                     );
332:                 }
333:                 
334:                 
335:                 _updateOperator(operator, operatorInfo, quorumNumbers[i:i+1]);
336:                 prevOperatorAddress = operator;
337:             }
338: 
339:             
340:             quorumUpdateBlockNumber[quorumNumber] = block.number;
341:             emit QuorumBlockNumberUpdated(quorumNumber, block.number);
342:         }
343:     }

[NonCritical-79] Immutable and constant integer state variables should not be casted

Resolution

The definition of a constant or immutable variable is that they do not change, casting such variables can potentially push more than one 'value' to a constant, for example a uin128 constant can have its original value and that of when it's casted to uint64 (i.e it has some bytes truncated). This can create confusion and inconsistencies within the code which can inadvertently increase the attack surface of the project. It is thus advise to either change the uint byte size in the constant/immutable definition of the variable or introduce a second state variable to cover the differing casts that are expected such as having a uint128 constant and a separate uint64 constant.

Num of instances: 1

Findings

Click to show findings

['147']

147:     function avsDirectory() external view override returns (address) {
148:         return address(_avsDirectory); // <= FOUND
149:     }

[NonCritical-80] Multiline comments should be terminated with '*/' and not '**/'

Resolution

https://docs.soliditylang.org/en/latest/natspec-format.html#:~:text=For%20Solidity%20you%20may%20choose%20///%20for%20single%20or%20multi%2Dline%20comments%2C%20or%20/**%20and%20ending%20with%20*/.

Num of instances: 12

Findings

Click to show findings

['49']

49: /******************************************************************************* // <= FOUND
50:                       EXTERNAL FUNCTIONS - REGISTRY COORDINATOR
51:     *******************************************************************************/ // <= FOUND

['426']

426: /******************************************************************************* // <= FOUND
427:                             INTERNAL FUNCTIONS
428:     *******************************************************************************/ // <= FOUND

['782']

782: /******************************************************************************* // <= FOUND
783:                             VIEW FUNCTIONS
784:     *******************************************************************************/ // <= FOUND

['124']

124: /******************************************************************************* // <= FOUND
125:                                 INTERNAL FUNCTIONS
126:     *******************************************************************************/ // <= FOUND

['294']

294: /******************************************************************************* // <= FOUND
295:                                  VIEW FUNCTIONS
296:     *******************************************************************************/ // <= FOUND

['114']

114: /******************************************************************************* // <= FOUND
115:                             EXTERNAL FUNCTIONS 
116:     *******************************************************************************/ // <= FOUND

['354']

354: /******************************************************************************* // <= FOUND
355:                             EXTERNAL FUNCTIONS - EJECTOR
356:     *******************************************************************************/ // <= FOUND

['373']

373: /******************************************************************************* // <= FOUND
374:                             EXTERNAL FUNCTIONS - OWNER
375:     *******************************************************************************/ // <= FOUND

['12']

12: /******************************************************************************* // <= FOUND
13:                                CONSTANTS AND IMMUTABLES 
14:     *******************************************************************************/ // <= FOUND

['43']

43: /******************************************************************************* // <= FOUND
44:                                        STATE 
45:     *******************************************************************************/ // <= FOUND

['528']

528: /******************************************************************************* // <= FOUND
529:                       VIEW FUNCTIONS - Operator Stake History
530:     *******************************************************************************/ // <= FOUND

['638']

638: /******************************************************************************* // <= FOUND
639:                         VIEW FUNCTIONS - Total Stake History
640:     *******************************************************************************/ // <= FOUND

[NonCritical-81] Public variable declarations should have NatSpec descriptions

Resolution

Public variable declarations in smart contracts should ideally be accompanied by NatSpec comments to enhance code readability and provide clear documentation. NatSpec (Natural Language Specification) is a standard for writing comments in Ethereum smart contracts that can generate user-friendly documentation, improving the transparency of the contract's functionality. This is particularly crucial for public variables, as they are accessible externally, and understanding their role and impact is vital for both developers and users interacting with the contract

Num of instances: 1

Findings

Click to show findings

['26']

26:     IRegistryCoordinator public immutable registryCoordinator; // <= FOUND
27:     IStakeRegistry public immutable stakeRegistry; // <= FOUND
28:     IBLSApkRegistry public immutable blsApkRegistry; // <= FOUND

[NonCritical-82] Use the Modern Upgradeable Contract Paradigm

Resolution

Modern smart contract development often employs upgradeable contract structures, utilizing proxy patterns like OpenZeppelin’s Upgradeable Contracts. This paradigm separates logic and state, allowing developers to amend and enhance the contract's functionality without altering its state or the deployed contract address. Transitioning to this approach enhances long-term maintainability.

Resolution: Adopt a well-established proxy pattern for upgradeability, ensuring proper initialization and employing transparent proxies to mitigate potential risks. Embrace comprehensive testing and audit practices, particularly when updating contract logic, to ensure state consistency and security are preserved across upgrades. This ensures your contract remains robust and adaptable to future requirements.

Num of instances: 5

Findings

Click to show findings

['10']

10: contract BLSApkRegistry is BLSApkRegistryStorage 

['18']

18: contract BLSSignatureChecker is IBLSSignatureChecker 

['11']

11: contract IndexRegistry is IndexRegistryStorage 

['15']

15: contract OperatorStateRetriever 

['22']

22: contract StakeRegistry is StakeRegistryStorage 

[NonCritical-83] Upgrade openzeppelin to the Latest Version - 5.0.0

Num of instances: 3

Findings

Click to show findings

['7']

7: import {Initializable} from "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; // <= FOUND

['17']

17: import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; // <= FOUND

['19']

19: import {EIP712} from "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol"; // <= FOUND

[NonCritical-84] Use a struct to encapsulate multiple function parameters

Resolution

Using a struct to encapsulate multiple parameters in Solidity functions can significantly enhance code readability and maintainability. Instead of passing a long list of arguments, which can be error-prone and hard to manage, a struct allows grouping related data into a single, coherent entity. This approach simplifies function signatures and makes the code more organized. It also enhances code clarity, as developers can easily understand the relationship between the parameters. Moreover, it aids in future code modifications and expansions, as adding or modifying a parameter only requires changes in the struct definition, rather than in every function that uses these parameters.

Num of instances: 6

Findings

Click to show findings

['236']

235:     function safePairing(
236:         G1Point memory a1, // <= FOUND
237:         G2Point memory a2, // <= FOUND
238:         G1Point memory b1, // <= FOUND
239:         G2Point memory b2, // <= FOUND
240:         uint256 pairingGas
241:     ) internal view returns (bool, bool)  // <= FOUND

['83']

82:     function initialize(
83:         address _initialOwner, // <= FOUND
84:         address _churnApprover, // <= FOUND
85:         address _ejector, // <= FOUND
86:         IPauserRegistry _pauserRegistry, // <= FOUND
87:         uint256 _initialPausedStatus, // <= FOUND
88:         OperatorSetParam[] memory _operatorSetParams, // <= FOUND
89:         uint96[] memory _minimumStakes, // <= FOUND
90:         IStakeRegistry.StrategyParams[][] memory _strategyParams
91:     ) external initializer 

['178']

177:     function registerOperatorWithChurn(
178:         bytes calldata quorumNumbers,  // <= FOUND
179:         string calldata socket, // <= FOUND
180:         IBLSApkRegistry.PubkeyRegistrationParams calldata params, // <= FOUND
181:         OperatorKickParam[] calldata operatorKickParams, // <= FOUND
182:         SignatureWithSaltAndExpiry memory churnApproverSignature, // <= FOUND
183:         SignatureWithSaltAndExpiry memory operatorSignature
184:     ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) 

['441']

440:     function _registerOperator(
441:         address operator,  // <= FOUND
442:         bytes32 operatorId, // <= FOUND
443:         bytes calldata quorumNumbers, // <= FOUND
444:         string memory socket, // <= FOUND
445:         SignatureWithSaltAndExpiry memory operatorSignature
446:     ) internal virtual returns (RegisterResults memory results) 

['532']

531:     function _validateChurn(
532:         uint8 quorumNumber,  // <= FOUND
533:         uint96 totalQuorumStake, // <= FOUND
534:         address newOperator,  // <= FOUND
535:         uint96 newOperatorStake, // <= FOUND
536:         OperatorKickParam memory kickParams,  // <= FOUND
537:         OperatorSetParam memory setParams
538:     ) internal view 

['888']

887:     function calculateOperatorChurnApprovalDigestHash(
888:         address registeringOperator, // <= FOUND
889:         bytes32 registeringOperatorId, // <= FOUND
890:         OperatorKickParam[] memory operatorKickParams, // <= FOUND
891:         bytes32 salt, // <= FOUND
892:         uint256 expiry
893:     ) public view returns (bytes32) 

[NonCritical-85] Contracts inherits pausable without utilising whenNotPaused

Resolution

In Solidity, inheriting a Pausable contract without utilizing its whenNotPaused modifier can be an oversight. The Pausable pattern is designed to add an emergency stop mechanism, typically controlled by an admin role. By not using whenNotPaused, the contract fails to leverage this safety feature. To rectify this, functions that should be restricted during the pause state must include the whenNotPaused modifier. This ensures that critical functions are inaccessible when the contract is paused, enhancing security and control. It's important to judiciously apply this modifier to sensitive functions, balancing security with functionality.

Num of instances: 1

Findings

Click to show findings

['32']

32: contract RegistryCoordinator is 
33:     EIP712, 
34:     Initializable, 
35:     Pausable,
36:     OwnableUpgradeable,
37:     RegistryCoordinatorStorage, 
38:     ISocketUpdater, 
39:     ISignatureUtils
40: 

[NonCritical-86] Long numbers should include underscores to improve readability and prevent typos

Resolution

A large number such as 2000000 is far more readable as 2_000_000, this will help prevent unintended bugs in the code

Num of instances: 13

Findings

Click to show findings

['28']

24:     
28:     uint256 internal constant PAIRING_EQUALITY_CHECK_GAS = 120000; // <= FOUND

['34']

32:     
33:     uint256 internal constant FP_MODULUS =
34:         21888242871839275222246405745257275088696311157297823662689037894645226208583; // <= FOUND

['37']

35:     
36:     uint256 internal constant FR_MODULUS =
37:         21888242871839275222246405745257275088548364400416034343698204186575808495617; // <= FOUND

['58']

55: 
58:     uint256 internal constant G2x1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; // <= FOUND

['56']

56:     uint256 internal constant G2x0 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; // <= FOUND

['57']

57:     uint256 internal constant G2y1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; // <= FOUND

['58']

58:     uint256 internal constant G2y0 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; // <= FOUND

['74']

71: 
74:     uint256 internal constant nG2x1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; // <= FOUND

['72']

72:     uint256 internal constant nG2x0 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; // <= FOUND

['73']

73:     uint256 internal constant nG2y1 = 17805874995975841540914202342111839520379459829704422454583296818431106115052; // <= FOUND

['74']

74:     uint256 internal constant nG2y0 = 13392588948715843804641432497768002650278120570034223513918757245338268106653; // <= FOUND

['25']

24:     
25:     uint16 internal constant BIPS_DENOMINATOR = 10000; // <= FOUND

['23']

22:     
23:     uint256 internal constant MAX_BIPS = 10000; // <= FOUND

[NonCritical-87] Consider using a format prettier or forge fmt

Resolution

Some comments use // X and others //X Amend comments to use only use // X or //X consistently such style inconsistencies can be resolved by running the project through a format prettier or by using forge fmt.

Num of instances: 5

Findings

Click to show findings

['13']

13: //docs.eigenlayer.xyz/overview/terms-of-service

['143']

143: //loop until we reach the most significant bit

['265']

265: //Out is the output of the pairing precompile, either 0 or 1 based on whether the two pairings are equal.

['266']

266: //Success is true if the precompile actually goes through (aka all inputs are valid)

['2']

2: //github.com/HarryR/solcrypto/blob/master/contracts/altbn128.sol

[NonCritical-88] Avoid defining a function in a single line including it's contents

Num of instances: 80

Findings

Click to show findings

['63']

63: 
75:     function registerOperator(address operator, bytes calldata quorumNumbers) external; // <= FOUND

['77']

77: 
90:     function deregisterOperator(address operator, bytes calldata quorumNumbers) external; // <= FOUND

['67']

67:     
72:     function initializeQuorum(uint8 quorumNumber) external; // <= FOUND

['89']

89: 
94:     function operatorToPubkeyHash(address operator) external view returns (bytes32); // <= FOUND

['96']

96: 
102:     function pubkeyHashToOperator(bytes32 pubkeyHash) external view returns (address); // <= FOUND

['104']

104: 
111:     function registerBLSPublicKey( // <= FOUND
112:         address operator,
113:         PubkeyRegistrationParams calldata params,
114:         BN254.G1Point calldata pubkeyRegistrationMessageHash
115:     ) external returns (bytes32 operatorId); // <= FOUND

['114']

114: 
119:     function getRegisteredPubkey(address operator) external view returns (BN254.G1Point memory, bytes32); // <= FOUND

['117']

117: 
119:     function getApk(uint8 quorumNumber) external view returns (BN254.G1Point memory); // <= FOUND

['120']

120: 
122:     function getApkIndicesAtBlockNumber(bytes calldata quorumNumbers, uint256 blockNumber) external view returns(uint32[] memory); // <= FOUND

['123']

123: 
125:     function getApkUpdateAtIndex(uint8 quorumNumber, uint256 index) external view returns (ApkUpdate memory); // <= FOUND

['126']

126: 
128:     function getOperatorFromPubkeyHash(bytes32 pubkeyHash) external view returns (address); // <= FOUND

['135']

135: 
143:     function getApkHashAtBlockNumberAndIndex(uint8 quorumNumber, uint32 blockNumber, uint256 index) external view returns (bytes24); // <= FOUND

['110']

110: 
113:     function getOperatorId(address operator) external view returns (bytes32); // <= FOUND

['49']

49:     
52:     function registryCoordinator() external view returns (IRegistryCoordinator); // <= FOUND

['50']

50:     function stakeRegistry() external view returns (IStakeRegistry); // <= FOUND

['51']

51:     function blsApkRegistry() external view returns (IBLSApkRegistry); // <= FOUND

['52']

52:     function delegation() external view returns (IDelegationManager); // <= FOUND

['70']

70: 
87:     function checkSignatures( // <= FOUND
88:         bytes32 msgHash, 
89:         bytes calldata quorumNumbers,
90:         uint32 referenceBlockNumber, 
91:         NonSignerStakesAndSignature memory nonSignerStakesAndSignature
92:     ) 
93:         external 
94:         view
95:         returns (
96:             QuorumStakeTotals memory,
97:             bytes32
98:         ); // <= FOUND

['47']

47: 
60:     function registerOperator(bytes32 operatorId, bytes calldata quorumNumbers) external returns(uint32[] memory); // <= FOUND

['61']

61: 
74:     function deregisterOperator(bytes32 operatorId, bytes calldata quorumNumbers) external; // <= FOUND

['67']

67: 
72:     function initializeQuorum(uint8 quorumNumber) external; // <= FOUND

['70']

70: 
72:     function getOperatorUpdateAtIndex( // <= FOUND
73:         uint8 quorumNumber,
74:         uint32 operatorIndex,
75:         uint32 arrayIndex
76:     ) external view returns (OperatorUpdate memory); // <= FOUND

['77']

77: 
79:     function getQuorumUpdateAtIndex(uint8 quorumNumber, uint32 quorumIndex) external view returns (QuorumUpdate memory); // <= FOUND

['80']

80: 
82:     function getLatestOperatorUpdate(uint8 quorumNumber, uint32 operatorIndex) external view returns (OperatorUpdate memory); // <= FOUND

['83']

83: 
85:     function getLatestQuorumUpdate(uint8 quorumNumber) external view returns (QuorumUpdate memory); // <= FOUND

['86']

86: 
88:     function totalOperatorsForQuorum(uint8 quorumNumber) external view returns (uint32); // <= FOUND

['89']

89: 
91:     function getOperatorListAtBlockNumber(uint8 quorumNumber, uint32 blockNumber) external view returns (bytes32[] memory); // <= FOUND

['12']

12:     function registryCoordinator() external view returns (address); // <= FOUND

['85']

85: 
87:     function getOperatorSetParams(uint8 quorumNumber) external view returns (OperatorSetParam memory); // <= FOUND

['50']

50:     
51:     function stakeRegistry() external view returns (IStakeRegistry); // <= FOUND

['51']

51:     
52:     function blsApkRegistry() external view returns (IBLSApkRegistry); // <= FOUND

['91']

91:     
92:     function indexRegistry() external view returns (IIndexRegistry); // <= FOUND

['98']

98: 
104:     function ejectOperator( // <= FOUND
105:         address operator, 
106:         bytes calldata quorumNumbers
107:     ) external; // <= FOUND

['104']

104: 
106:     function quorumCount() external view returns (uint8); // <= FOUND

['107']

107: 
109:     function getOperator(address operator) external view returns (OperatorInfo memory); // <= FOUND

['110']

110: 
112:     function getOperatorId(address operator) external view returns (bytes32); // <= FOUND

['113']

113: 
115:     function getOperatorFromId(bytes32 operatorId) external view returns (address operator); // <= FOUND

['116']

116: 
118:     function getOperatorStatus(address operator) external view returns (IRegistryCoordinator.OperatorStatus); // <= FOUND

['119']

119: 
121:     function getQuorumBitmapIndicesAtBlockNumber(uint32 blockNumber, bytes32[] memory operatorIds) external view returns (uint32[] memory); // <= FOUND

['125']

125: 
130:     function getQuorumBitmapAtBlockNumberByIndex(bytes32 operatorId, uint32 blockNumber, uint256 index) external view returns (uint192); // <= FOUND

['128']

128: 
130:     function getQuorumBitmapUpdateByIndex(bytes32 operatorId, uint256 index) external view returns (QuorumBitmapUpdate memory); // <= FOUND

['131']

131: 
133:     function getCurrentQuorumBitmap(bytes32 operatorId) external view returns (uint192); // <= FOUND

['134']

134: 
136:     function getQuorumBitmapHistoryLength(bytes32 operatorId) external view returns (uint256); // <= FOUND

['137']

137: 
139:     function registries(uint256) external view returns (address); // <= FOUND

['140']

140: 
142:     function numRegistries() external view returns (uint256); // <= FOUND

['146']

146: 
151:     function pubkeyRegistrationMessageHash(address operator) external view returns (BN254.G1Point memory); // <= FOUND

['149']

149: 
151:     function quorumUpdateBlockNumber(uint8 quorumNumber) external view returns (uint256); // <= FOUND

['152']

152: 
154:     function owner() external view returns (address); // <= FOUND

['16']

16:     
20:     function setMetadataURI(string memory _metadataURI) external; // <= FOUND

['23']

23: 
29:     function registerOperatorToAVS( // <= FOUND
30:         address operator,
31:         ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
32:     ) external; // <= FOUND

['32']

32: 
37:     function deregisterOperatorFromAVS(address operator) external; // <= FOUND

['41']

41: 
49:     function getOperatorRestakedStrategies(address operator) external view returns (address[] memory); // <= FOUND

['49']

49: 
56:     function getRestakeableStrategies() external view returns (address[] memory); // <= FOUND

['52']

52: 
54:     function avsDirectory() external view returns (address); // <= FOUND

['19']

19: 
26:     function updateSocket(string memory socket) external; // <= FOUND

['69']

69: 
83:     function registerOperator( // <= FOUND
84:         address operator, 
85:         bytes32 operatorId, 
86:         bytes memory quorumNumbers
87:     ) external returns (uint96[] memory, uint96[] memory); // <= FOUND

['87']

87: 
100:     function deregisterOperator(bytes32 operatorId, bytes memory quorumNumbers) external; // <= FOUND

['92']

92: 
96:     function initializeQuorum(uint8 quorumNumber, uint96 minimumStake, StrategyParams[] memory strategyParams) external; // <= FOUND

['95']

95: 
97:     function addStrategies( // <= FOUND
98:         uint8 quorumNumber,
99:         StrategyParams[] memory strategyParams
100:     ) external; // <= FOUND

['106']

106: 
113:     function removeStrategies(uint8 quorumNumber, uint256[] calldata indicesToRemove) external; // <= FOUND

['115']

115: 
123:     function modifyStrategyParams( // <= FOUND
124:         uint8 quorumNumber,
125:         uint256[] calldata strategyIndices,
126:         uint96[] calldata newMultipliers
127:     ) external; // <= FOUND

['122']

122: 
124:     function WEIGHTING_DIVISOR() external pure returns (uint256); // <= FOUND

['52']

52: 
54:     function delegation() external view returns (IDelegationManager); // <= FOUND

['128']

128: 
130:     function minimumStakeForQuorum(uint8 quorumNumber) external view returns (uint96); // <= FOUND

['131']

131: 
133:     function strategyParamsLength(uint8 quorumNumber) external view returns (uint256); // <= FOUND

['134']

134: 
136:     function strategyParamsByIndex( // <= FOUND
137:         uint8 quorumNumber,
138:         uint256 index
139:     ) external view returns (StrategyParams memory); // <= FOUND

['143']

143: 
148:     function weightOfOperatorForQuorum(uint8 quorumNumber, address operator) external view returns (uint96); // <= FOUND

['150']

150: 
156:     function getStakeHistory(bytes32 operatorId, uint8 quorumNumber) external view returns (StakeUpdate[] memory); // <= FOUND

['152']

152: 
153:     function getTotalStakeHistoryLength(uint8 quorumNumber) external view returns (uint256); // <= FOUND

['159']

159: 
165:     function getTotalStakeUpdateAtIndex(uint8 quorumNumber, uint256 index) external view returns (StakeUpdate memory); // <= FOUND

['162']

162: 
164:     function getStakeUpdateIndexAtBlockNumber(bytes32 operatorId, uint8 quorumNumber, uint32 blockNumber) // <= FOUND
165:         external
166:         view
167:         returns (uint32); // <= FOUND

['168']

168: 
170:     function getTotalStakeIndicesAtBlockNumber(uint32 blockNumber, bytes calldata quorumNumbers) external view returns(uint32[] memory) ; // <= FOUND

['177']

177: 
185:     function getStakeUpdateAtIndex(uint8 quorumNumber, bytes32 operatorId, uint256 index) // <= FOUND
186:         external
187:         view
188:         returns (StakeUpdate memory); // <= FOUND

['186']

186: 
191:     function getLatestStakeUpdate(bytes32 operatorId, uint8 quorumNumber) external view returns (StakeUpdate memory); // <= FOUND

['199']

199: 
211:     function getStakeAtBlockNumberAndIndex(uint8 quorumNumber, uint32 blockNumber, bytes32 operatorId, uint256 index) // <= FOUND
212:         external
213:         view
214:         returns (uint96); // <= FOUND

['214']

214: 
225:     function getTotalStakeAtBlockNumberFromIndex(uint8 quorumNumber, uint32 blockNumber, uint256 index) external view returns (uint96); // <= FOUND

['220']

220: 
225:     function getCurrentStake(bytes32 operatorId, uint8 quorumNumber) external view returns (uint96); // <= FOUND

['223']

223: 
225:     function getStakeAtBlockNumber(bytes32 operatorId, uint8 quorumNumber, uint32 blockNumber) // <= FOUND
226:         external
227:         view
228:         returns (uint96); // <= FOUND

['232']

232: 
237:     function getCurrentTotalStake(uint8 quorumNumber) external view returns (uint96); // <= FOUND

['243']

243: 
253:     function updateOperatorStake( // <= FOUND
254:         address operator, 
255:         bytes32 operatorId, 
256:         bytes calldata quorumNumbers
257:     ) external returns (uint192); // <= FOUND

[NonCritical-89] Use 'using' keyword when using specific imports rather than calling the specific import directly

Resolution

In Solidity, the using keyword can streamline the use of library functions for specific types. Instead of calling library functions directly with their full import paths, you can declare a library once with using for a specific type. This approach makes your code more readable and concise. For example, instead of LibraryName.functionName(variable), you would first declare using LibraryName for TypeName; at the contract level. After this, you can call library functions directly on variables of TypeName like variable.functionName(). This method not only enhances code clarity but also promotes cleaner and more organized code, especially when multiple functions from the same library are used frequently.

Num of instances: 64

Findings

Click to show findings

['116']

116: 
118:     function getOperatorStatus(address operator) external view returns (IRegistryCoordinator.OperatorStatus); // <= FOUND 'IRegistryCoordinator.'

['807']

807: 
809:     function getOperatorStatus(address operator) external view returns (IRegistryCoordinator.OperatorStatus) { // <= FOUND 'IRegistryCoordinator.'

['42']

42:     using BN254 for BN254.G1Point; // <= FOUND 'BN254.'

['47']

47:         
48:         (BN254.G1Point memory pubkey, ) = getRegisteredPubkey(operator); // <= FOUND 'BN254.'

['100']

100: 
107:     function registerBLSPublicKey(
108:         address operator,
109:         PubkeyRegistrationParams calldata params,
110:         BN254.G1Point calldata pubkeyRegistrationMessageHash // <= FOUND 'BN254.'
111:     ) external onlyRegistryCoordinator returns (bytes32 operatorId) {

['105']

105:         bytes32 pubkeyHash = BN254.hashG1Point(params.pubkeyG1); // <= FOUND 'BN254.'

['119']

119: 
120:         
121:         uint256 gamma = uint256(keccak256(abi.encodePacked(
122:             params.pubkeyRegistrationSignature.X, 
123:             params.pubkeyRegistrationSignature.Y, 
124:             params.pubkeyG1.X, 
125:             params.pubkeyG1.Y, 
126:             params.pubkeyG2.X, 
127:             params.pubkeyG2.Y, 
128:             pubkeyRegistrationMessageHash.X, 
129:             pubkeyRegistrationMessageHash.Y
130:         ))) % BN254.FR_MODULUS; // <= FOUND 'BN254.'

['131']

131:         
132:         
133:         require(BN254.pairing( // <= FOUND 'BN254.'
134:             params.pubkeyRegistrationSignature.plus(params.pubkeyG1.scalar_mul(gamma)),
135:             BN254.negGeneratorG2(), // <= FOUND 'BN254.'
136:             pubkeyRegistrationMessageHash.plus(BN254.generatorG1().scalar_mul(gamma)), // <= FOUND 'BN254.'
137:             params.pubkeyG2
138:         ), "BLSApkRegistry.registerBLSPublicKey: either the G1 signature is wrong, or G1 and G2 private key do not match");

['150']

150: 
155:     function _processQuorumApkUpdate(bytes memory quorumNumbers, BN254.G1Point memory point) internal { // <= FOUND 'BN254.'

['151']

151:         BN254.G1Point memory newApk; // <= FOUND 'BN254.'

['162']

162:             bytes24 newApkHash = bytes24(BN254.hashG1Point(newApk)); // <= FOUND 'BN254.'

['187']

187: 
195:     function getRegisteredPubkey(address operator) public view returns (BN254.G1Point memory, bytes32) { // <= FOUND 'BN254.'

['188']

188:         BN254.G1Point memory pubkey = operatorToPubkey[operator]; // <= FOUND 'BN254.'

['229']

229: 
231:     function getApk(uint8 quorumNumber) external view returns (BN254.G1Point memory) { // <= FOUND 'BN254.'

['24']

24:     
25:     mapping(address => BN254.G1Point) public operatorToPubkey; // <= FOUND 'BN254.'

['30']

30:     
31:     mapping(uint8 => BN254.G1Point) public currentApk; // <= FOUND 'BN254.'

['123']

123: 
124:         
125:         
126:         
127:         
128:         
129:         
130:         BN254.G1Point memory apk = BN254.G1Point(0, 0); // <= FOUND 'BN254.'

['269']

269: 
279:     function trySignatureAndApkVerification(
280:         bytes32 msgHash,
281:         BN254.G1Point memory apk, // <= FOUND 'BN254.'
282:         BN254.G2Point memory apkG2, // <= FOUND 'BN254.'
283:         BN254.G1Point memory sigma // <= FOUND 'BN254.'
284:     ) public view returns(bool pairingSuccessful, bool siganatureIsValid) {

['276']

276:         
277:         uint256 gamma = uint256(keccak256(abi.encodePacked(msgHash, apk.X, apk.Y, apkG2.X[0], apkG2.X[1], apkG2.Y[0], apkG2.Y[1], sigma.X, sigma.Y))) % BN254.FR_MODULUS; // <= FOUND 'BN254.'

['278']

278:         
279:         (pairingSuccessful, siganatureIsValid) = BN254.safePairing( // <= FOUND 'BN254.'
280:                 sigma.plus(apk.scalar_mul(gamma)),
281:                 BN254.negGeneratorG2(), // <= FOUND 'BN254.'
282:                 BN254.hashToG1(msgHash).plus(BN254.generatorG1().scalar_mul(gamma)), // <= FOUND 'BN254.'
283:                 apkG2,
284:                 PAIRING_EQUALITY_CHECK_GAS
285:             );

['126']

126: 
133:     function scalar_mul_tiny(BN254.G1Point memory p, uint16 s) internal view returns (BN254.G1Point memory) { // <= FOUND 'BN254.'

['135']

135: 
136:         
137:         BN254.G1Point memory acc = BN254.G1Point(0, 0); // <= FOUND 'BN254.'

['137']

137:         
138:         BN254.G1Point memory p2n = p; // <= FOUND 'BN254.'

['273']

273: 
276:     function hashG1Point(BN254.G1Point memory pk) internal pure returns (bytes32 hashedG1) { // <= FOUND 'BN254.'

['283']

283: 
286:     function hashG2Point(
287:         BN254.G2Point memory pk // <= FOUND 'BN254.'
288:     ) internal pure returns (bytes32) {

['347']

347:         require(success, "BN254.expMod: call failure"); // <= FOUND 'BN254.'

['31']

31:         BN254.G1Point pubkeyRegistrationSignature; // <= FOUND 'BN254.'

['32']

32:         BN254.G1Point pubkeyG1; // <= FOUND 'BN254.'

['33']

33:         BN254.G2Point pubkeyG2; // <= FOUND 'BN254.'

['38']

38: 
41:     event NewPubkeyRegistration(address indexed operator, BN254.G1Point pubkeyG1, BN254.G2Point pubkeyG2); // <= FOUND 'BN254.'

['104']

104: 
111:     function registerBLSPublicKey(
112:         address operator,
113:         PubkeyRegistrationParams calldata params,
114:         BN254.G1Point calldata pubkeyRegistrationMessageHash // <= FOUND 'BN254.'
115:     ) external returns (bytes32 operatorId);

['114']

114: 
119:     function getRegisteredPubkey(address operator) external view returns (BN254.G1Point memory, bytes32); // <= FOUND 'BN254.'

['117']

117: 
119:     function getApk(uint8 quorumNumber) external view returns (BN254.G1Point memory); // <= FOUND 'BN254.'

['21']

21:         BN254.G1Point[] nonSignerPubkeys;  // <= FOUND 'BN254.'

['22']

22:         BN254.G1Point[] quorumApks;  // <= FOUND 'BN254.'

['23']

23:         BN254.G2Point apkG2;  // <= FOUND 'BN254.'

['24']

24:         BN254.G1Point sigma;  // <= FOUND 'BN254.'

['146']

146: 
151:     function pubkeyRegistrationMessageHash(address operator) external view returns (BN254.G1Point memory); // <= FOUND 'BN254.'

['902']

902: 
907:     function pubkeyRegistrationMessageHash(address operator) public view returns (BN254.G1Point memory) { // <= FOUND 'BN254.'

['903']

903:         return BN254.hashToG1( // <= FOUND 'BN254.'
904:             _hashTypedDataV4(
905:                 keccak256(abi.encode(PUBKEY_REGISTRATION_TYPEHASH, operator))
906:             )
907:         );

['128']

128: 
143:     function registerOperator(
144:         bytes calldata quorumNumbers,
145:         string calldata socket,
146:         IBLSApkRegistry.PubkeyRegistrationParams calldata params, // <= FOUND 'IBLSApkRegistry.'
147:         SignatureWithSaltAndExpiry memory operatorSignature
148:     ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) {

['177']

177: 
190:     function registerOperatorWithChurn(
191:         bytes calldata quorumNumbers, 
192:         string calldata socket,
193:         IBLSApkRegistry.PubkeyRegistrationParams calldata params, // <= FOUND 'IBLSApkRegistry.'
194:         OperatorKickParam[] calldata operatorKickParams,
195:         SignatureWithSaltAndExpiry memory churnApproverSignature,
196:         SignatureWithSaltAndExpiry memory operatorSignature
197:     ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) {

['502']

502: 
511:     function _getOrCreateOperatorId(
512:         address operator,
513:         IBLSApkRegistry.PubkeyRegistrationParams calldata params // <= FOUND 'IBLSApkRegistry.'
514:     ) internal returns (bytes32 operatorId) {

['82']

82: 
94:     function initialize(
95:         address _initialOwner,
96:         address _churnApprover,
97:         address _ejector,
98:         IPauserRegistry _pauserRegistry,
99:         uint256 _initialPausedStatus,
100:         OperatorSetParam[] memory _operatorSetParams,
101:         uint96[] memory _minimumStakes,
102:         IStakeRegistry.StrategyParams[][] memory _strategyParams // <= FOUND 'IStakeRegistry.'
103:     ) external initializer {

['385']

385: 
398:     function createQuorum(
399:         OperatorSetParam memory operatorSetParams,
400:         uint96 minimumStake,
401:         IStakeRegistry.StrategyParams[] memory strategyParams // <= FOUND 'IStakeRegistry.'
402:     ) external virtual onlyOwner {

['674']

674: 
683:     function _createQuorum(
684:         OperatorSetParam memory operatorSetParams,
685:         uint96 minimumStake,
686:         IStakeRegistry.StrategyParams[] memory strategyParams // <= FOUND 'IStakeRegistry.'
687:     ) internal {

['27']

27:         
28:         require(orderedBytesArray.length <= MAX_BYTE_ARRAY_LENGTH,
29:             "BitmapUtils.orderedBytesArrayToBitmap: orderedBytesArray is too long"); // <= FOUND 'BitmapUtils.'

['49']

49:             
50:             require(bitMask > bitmap, "BitmapUtils.orderedBytesArrayToBitmap: orderedBytesArray is not ordered"); // <= FOUND 'BitmapUtils.'

['65']

65: 
66:         require((1 << bitUpperBound) > bitmap, 
67:             "BitmapUtils.orderedBytesArrayToBitmap: bitmap exceeds max value" // <= FOUND 'BitmapUtils.'
68:         );

['139']

139:             
140:             
141:             uint256 signingQuorumBitmap = BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, registryCoordinator.quorumCount()); // <= FOUND 'BitmapUtils.'

['164']

164: 
165:                 
166:                 
167:                 
168:                 apk = apk.plus(
169:                     params.nonSignerPubkeys[j]
170:                         .scalar_mul_tiny(
171:                             BitmapUtils.countNumOnes(nonSigners.quorumBitmaps[j] & signingQuorumBitmap)  // <= FOUND 'BitmapUtils.'
172:                         )
173:                 );

['227']

227:                     
228:                     if (BitmapUtils.isSet(nonSigners.quorumBitmaps[j], uint8(quorumNumbers[i]))) { // <= FOUND 'BitmapUtils.'

['51']

51: 
52:         bytes memory quorumNumbers = BitmapUtils.bitmapToBytesArray(quorumBitmap); // <= FOUND 'BitmapUtils.'

['265']

265:             bytes memory quorumsToUpdate = BitmapUtils.bitmapToBytesArray(currentBitmap); // <= FOUND 'BitmapUtils.'

['292']

292:         
293:         
294:         
295:         
296:         uint192 quorumBitmap = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount)); // <= FOUND 'BitmapUtils.'

['323']

323:                     
324:                     require(
325:                         BitmapUtils.isSet(currentBitmap, quorumNumber), // <= FOUND 'BitmapUtils.'
326:                         "RegistryCoordinator.updateOperatorsForQuorum: operator not in quorum"
327:                     );

['454']

454:         
455: 
461:         uint192 quorumsToAdd = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount)); // <= FOUND 'BitmapUtils.'

['577']

577:         
578:         
579: 
585:         uint192 quorumsToRemove = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount)); // <= FOUND 'BitmapUtils.'

['622']

622:                 operator: operator,
623:                 quorumNumbers: BitmapUtils.bitmapToBytesArray(quorumsToRemove) // <= FOUND 'BitmapUtils.'
624:             });    

['126']

126: 
127:         
128:         bytes memory operatorRestakedQuorums = BitmapUtils.bitmapToBytesArray(operatorBitmap); // <= FOUND 'BitmapUtils.'

['23']

23: 
29:     function registerOperatorToAVS(
30:         address operator,
31:         ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature // <= FOUND 'ISignatureUtils.'
32:     ) external;

['65']

65: 
71:     function registerOperatorToAVS(
72:         address operator,
73:         ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature // <= FOUND 'ISignatureUtils.'
74:     ) public virtual onlyRegistryCoordinator {

['659']

659: 
660:         
661:         EIP1271SignatureUtils.checkSignature_EIP1271( // <= FOUND 'EIP1271SignatureUtils.'
662:             churnApprover, 
663:             calculateOperatorChurnApprovalDigestHash(registeringOperator, registeringOperatorId, operatorKickParams, churnApproverSignature.salt, churnApproverSignature.expiry), 
664:             churnApproverSignature.signature
665:         );

['917']

917:         return OwnableUpgradeable.owner(); // <= FOUND 'OwnableUpgradeable.'

[NonCritical-90] Use the same Solidity version in non library/interface files throughout the project

Resolution

Using the same Solidity version across non-library/interface files in a project is important for consistency and to avoid potential compatibility issues. Solidity follows Semantic Versioning (SemVer), where each version number indicates the type and extent of changes. This system ensures that each version is compatible within its major version. Mismatched versions can lead to errors or warnings when compiling, especially in larger projects. It's crucial to maintain version consistency to avoid these issues and ensure smooth functioning of the smart contracts within the project.

Num of instances: 2

Findings

Click to show findings

['2']

2: pragma solidity >=0.5.0; // <= FOUND

['2']

2: pragma solidity =0.8.12; // <= FOUND

[NonCritical-91] Avoid declaring variables with the names of defined functions within the project

Resolution

Having such variables can create confusion in both developers and in users of the project. Consider renaming these variables to improve code clarity.

Num of instances: 5

Findings

Click to show findings

['245']

235: 
240:     function safePairing(
241:         G1Point memory a1,
242:         G2Point memory a2,
243:         G1Point memory b1,
244:         G2Point memory b2,
245:         uint256 pairingGas // <= FOUND
246:     ) internal view returns (bool, bool) {

['101']

101:             uint256 strategyParamsLength = _stakeRegistry.strategyParamsLength(uint8(i)); // <= FOUND

['137']

137:             uint256 strategyParamsLength = _stakeRegistry.strategyParamsLength(quorum); // <= FOUND

['245']

244:             
245:             (bool pairingSuccessful, bool signatureIsValid) = trySignatureAndApkVerification( // <= FOUND
246:                 msgHash, 
247:                 apk, 
248:                 params.apkG2, 
249:                 params.sigma
250:             );

['284']

269: 
279:     function trySignatureAndApkVerification(
280:         bytes32 msgHash,
281:         BN254.G1Point memory apk,
282:         BN254.G2Point memory apkG2,
283:         BN254.G1Point memory sigma
284:     ) public view returns(bool pairingSuccessful, bool siganatureIsValid) { // <= FOUND

[NonCritical-92] Upgradeable contract uses non-upgradeable version of the OpenZeppelin libraries/contracts

Resolution

Using the upgradeable counterpart of the OpenZeppelin (OZ) library in Solidity is beneficial for creating contracts that can be updated in the future. OpenZeppelin's upgradeable contracts library is designed with proxy patterns in mind, which allow the logic of contracts to be upgraded while preserving the contract's state and address. This can be crucial for long-lived contracts where future requirements or improvements may not be fully known at the time of deployment. The upgradeable OZ contracts also include protection against a class of vulnerabilities related to initialization of storage variables in upgradeable contracts. Hence, it's a good idea to use them when developing contracts that may need to be upgraded in the future, as they provide a solid foundation for secure and upgradeable smart contracts.

Num of instances: 1

Findings

Click to show findings

['19']

19: import {EIP712} from "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol"; // <= FOUND

[NonCritical-93] All verbatim blocks are considered identical by deduplicator and can incorrectly be unified

Resolution

The Solidity Team reported a bug on October 24, 2023, affecting Yul code using the verbatim builtin, specifically in the Block Deduplicator optimizer step. This bug, present since Solidity version 0.8.5, caused incorrect deduplication of verbatim assembly items surrounded by identical opcodes, considering them identical regardless of their data. The bug was confined to pure Yul compilation with optimization enabled and was unlikely to be exploited as an attack vector. The conditions triggering the bug were very specific, and its occurrence was deemed to have a low likelihood. The bug was rated with an overall low score due to these factors.

Num of instances: 1

Findings

Click to show findings

['2']

2: pragma solidity =0.8.12; // <= FOUND

[NonCritical-94] Constructors should emit an event

Resolution

Emitting an event in a constructor of a smart contract provides transparency and traceability in blockchain applications. This event logs the contract’s creation, aiding in monitoring and verifying contract deployment. Although constructors are executed only once, the emitted event ensures the contract's initialization is recorded on the blockchain.

Num of instances: 10

Findings

Click to show findings

['44']

44:     constructor( // <= FOUND
45:         IRegistryCoordinator _registryCoordinator
46:     ) BLSApkRegistryStorage(_registryCoordinator) {}

['32']

32:     constructor(IRegistryCoordinator _registryCoordinator) { // <= FOUND
33:         registryCoordinator = address(_registryCoordinator);
34:         
35:         _disableInitializers();
36:     }

['38']

38:     constructor(IRegistryCoordinator _registryCoordinator) { // <= FOUND
39:         registryCoordinator = _registryCoordinator;
40:         stakeRegistry = _registryCoordinator.stakeRegistry();
41:         blsApkRegistry = _registryCoordinator.blsApkRegistry();
42:         delegation = stakeRegistry.delegation();
43:         
44:         staleStakesForbidden = true;
45:     }

['44']

44:     constructor( // <= FOUND
45:         IRegistryCoordinator _registryCoordinator
46:     ) IndexRegistryStorage(_registryCoordinator) {}

['33']

33:     constructor( // <= FOUND
34:         IRegistryCoordinator _registryCoordinator
35:     ){
36:         registryCoordinator = address(_registryCoordinator);
37:         
38:         _disableInitializers();
39:     }

['59']

59:     constructor( // <= FOUND
60:         IServiceManager _serviceManager,
61:         IStakeRegistry _stakeRegistry,
62:         IBLSApkRegistry _blsApkRegistry,
63:         IIndexRegistry _indexRegistry
64:     ) 
65:         RegistryCoordinatorStorage(_serviceManager, _stakeRegistry, _blsApkRegistry, _indexRegistry)
66:         EIP712("AVSRegistryCoordinator", "v0.0.1") 
67:     {
68:         _disableInitializers();
69:     }

['67']

67:     constructor( // <= FOUND
68:         IServiceManager _serviceManager,
69:         IStakeRegistry _stakeRegistry,
70:         IBLSApkRegistry _blsApkRegistry,
71:         IIndexRegistry _indexRegistry
72:     ) {
73:         serviceManager = _serviceManager;
74:         stakeRegistry = _stakeRegistry;
75:         blsApkRegistry = _blsApkRegistry;
76:         indexRegistry = _indexRegistry;
77:     }

['36']

36:     constructor( // <= FOUND
37:         IAVSDirectory __avsDirectory,
38:         IRegistryCoordinator __registryCoordinator,
39:         IStakeRegistry __stakeRegistry
40:     ) {
41:         _avsDirectory = __avsDirectory;
42:         _registryCoordinator = __registryCoordinator;
43:         _stakeRegistry = __stakeRegistry;
44:         _disableInitializers();
45:     }

['44']

44:     constructor( // <= FOUND
45:         IRegistryCoordinator _registryCoordinator,
46:         IDelegationManager _delegationManager
47:     ) StakeRegistryStorage(_registryCoordinator, _delegationManager) {}

['48']

48:     constructor( // <= FOUND
49:         IRegistryCoordinator _registryCoordinator, 
50:         IDelegationManager _delegationManager
51:     ) {
52:         registryCoordinator = address(_registryCoordinator);
53:         delegation = _delegationManager;
54:     }

[NonCritical-95] Avoid single line non empty object declarations

Resolution

In programming, avoiding single-line, non-empty object declarations enhances readability and maintainability. When properties are declared in a single line, it becomes challenging to scan and differentiate between them, especially as the object grows. Multi-line declarations improve clarity, making it easier to add, remove, or modify properties and to track changes in version control systems.

Num of instances: 36

Findings

Click to show findings

['132']

132:                 unchecked{ ++arrayIndex; } // <= FOUND

['4']

4: import {BLSApkRegistryStorage} from "./BLSApkRegistryStorage.sol"; // <= FOUND

['8']

8: import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol"; // <= FOUND

['9']

9: import {BN254} from "./libraries/BN254.sol"; // <= FOUND

['4']

4: import {IBLSApkRegistry} from "./interfaces/IBLSApkRegistry.sol"; // <= FOUND

['7']

7: import {Initializable} from "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; // <= FOUND

['4']

4: import {IBLSSignatureChecker} from "./interfaces/IBLSSignatureChecker.sol"; // <= FOUND

['7']

7: import {IStakeRegistry, IDelegationManager} from "./interfaces/IStakeRegistry.sol"; // <= FOUND

['9']

9: import {BitmapUtils} from "./libraries/BitmapUtils.sol"; // <= FOUND

['4']

4: import {IRegistry} from "./IRegistry.sol"; // <= FOUND

['8']

8: import {BN254} from "../libraries/BN254.sol"; // <= FOUND

['4']

4: import {IRegistryCoordinator} from "./IRegistryCoordinator.sol"; // <= FOUND

['5']

5: import {IBLSApkRegistry} from "./IBLSApkRegistry.sol"; // <= FOUND

['6']

6: import {IStakeRegistry, IDelegationManager} from "./IStakeRegistry.sol"; // <= FOUND

['4']

4: import {IndexRegistryStorage} from "./IndexRegistryStorage.sol"; // <= FOUND

['6']

6: import {IIndexRegistry} from "./interfaces/IIndexRegistry.sol"; // <= FOUND

['5']

5: import {IStakeRegistry} from "./IStakeRegistry.sol"; // <= FOUND

['6']

6: import {IIndexRegistry} from "./IIndexRegistry.sol"; // <= FOUND

['4']

4: import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; // <= FOUND

['5']

5: import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; // <= FOUND

['5']

5: import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; // <= FOUND

['5']

5: import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol"; // <= FOUND

['4']

4: import {IPauserRegistry} from "eigenlayer-contracts/src/contracts/interfaces/IPauserRegistry.sol"; // <= FOUND

['6']

6: import {ISocketUpdater} from "./interfaces/ISocketUpdater.sol"; // <= FOUND

['7']

7: import {IServiceManager} from "./interfaces/IServiceManager.sol"; // <= FOUND

['13']

13: import {EIP1271SignatureUtils} from "eigenlayer-contracts/src/contracts/libraries/EIP1271SignatureUtils.sol"; // <= FOUND

['17']

17: import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; // <= FOUND

['19']

19: import {EIP712} from "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol"; // <= FOUND

['21']

21: import {Pausable} from "eigenlayer-contracts/src/contracts/permissions/Pausable.sol"; // <= FOUND

['22']

22: import {RegistryCoordinatorStorage} from "./RegistryCoordinatorStorage.sol"; // <= FOUND

['6']

6: import {BitmapUtils} from "./libraries/BitmapUtils.sol";  // <= FOUND

['8']

8: import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; // <= FOUND

['6']

6: import {StakeRegistryStorage, IStrategy} from "./StakeRegistryStorage.sol"; // <= FOUND

['356']

356:         return _calculateDelta({ prev: prevStake, cur: newStake }); // <= FOUND

['5']

5: import {IStrategyManager, IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol"; // <= FOUND

['8']

8: import {IStakeRegistry} from  "./interfaces/IStakeRegistry.sol"; // <= FOUND

[NonCritical-96] Consider using 'using-for' syntax when using libraries

Resolution

The using-for directive in Solidity allows smart contracts to utilize library functions as if they were methods of the type being passed. This syntactical feature streamlines code by enabling type-specific function applications without the need for explicit function calls. When a contract uses a library with the using-for declaration, it implicitly attaches the library's functions to a given type, enhancing code readability and efficiency. This approach is particularly beneficial for common operations on data types, such as array manipulation or complex mathematical computations, making it a valuable tool for developing more maintainable and intuitive smart contract code.

Num of instances: 59

Findings

Click to show findings

['29']

27:         
28:         require(orderedBytesArray.length <= MAX_BYTE_ARRAY_LENGTH,
29:             "BitmapUtils.orderedBytesArrayToBitmap: orderedBytesArray is too long"); // <= FOUND

['50']

49:             
50:             require(bitMask > bitmap, "BitmapUtils.orderedBytesArrayToBitmap: orderedBytesArray is not ordered"); // <= FOUND

['67']

65: 
66:         require((1 << bitUpperBound) > bitmap, 
67:             "BitmapUtils.orderedBytesArrayToBitmap: bitmap exceeds max value" // <= FOUND
68:         );

['10']

9: 
10: import {BitmapUtils} from "./libraries/BitmapUtils.sol"; // <= FOUND

['141']

139:             
140:             
141:             uint256 signingQuorumBitmap = BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, registryCoordinator.quorumCount()); // <= FOUND

['171']

164: 
165:                 
166:                 
167:                 
168:                 apk = apk.plus(
169:                     params.nonSignerPubkeys[j]
170:                         .scalar_mul_tiny(
171:                             BitmapUtils.countNumOnes(nonSigners.quorumBitmaps[j] & signingQuorumBitmap)  // <= FOUND
172:                         )
173:                 );

['228']

227:                     
228:                     if (BitmapUtils.isSet(nonSigners.quorumBitmaps[j], uint8(quorumNumbers[i]))) { // <= FOUND

['52']

51: 
52:         bytes memory quorumNumbers = BitmapUtils.bitmapToBytesArray(quorumBitmap); // <= FOUND

['9']

9: import {BitmapUtils} from "./libraries/BitmapUtils.sol"; // <= FOUND

['265']

265:             bytes memory quorumsToUpdate = BitmapUtils.bitmapToBytesArray(currentBitmap); // <= FOUND

['296']

292:         
293:         
294:         
295:         
296:         uint192 quorumBitmap = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount)); // <= FOUND

['325']

323:                     
324:                     require(
325:                         BitmapUtils.isSet(currentBitmap, quorumNumber), // <= FOUND
326:                         "RegistryCoordinator.updateOperatorsForQuorum: operator not in quorum"
327:                     );

['461']

454:         
455: 
461:         uint192 quorumsToAdd = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount)); // <= FOUND

['585']

577:         
578:         
579: 
585:         uint192 quorumsToRemove = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount)); // <= FOUND

['623']

622:                 operator: operator,
623:                 quorumNumbers: BitmapUtils.bitmapToBytesArray(quorumsToRemove) // <= FOUND
624:             });    

['7']

6: 
7: import {BitmapUtils} from "./libraries/BitmapUtils.sol";  // <= FOUND

['128']

126: 
127:         
128:         bytes memory operatorRestakedQuorums = BitmapUtils.bitmapToBytesArray(operatorBitmap); // <= FOUND

['10']

9: 
10: import {BN254} from "./libraries/BN254.sol"; // <= FOUND

['42']

42:     using BN254 for BN254.G1Point; // <= FOUND

['48']

47:         
48:         (BN254.G1Point memory pubkey, ) = getRegisteredPubkey(operator); // <= FOUND

['110']

100: 
107:     function registerBLSPublicKey(
108:         address operator,
109:         PubkeyRegistrationParams calldata params,
110:         BN254.G1Point calldata pubkeyRegistrationMessageHash // <= FOUND
111:     ) external onlyRegistryCoordinator returns (bytes32 operatorId) {

['105']

105:         bytes32 pubkeyHash = BN254.hashG1Point(params.pubkeyG1); // <= FOUND

['130']

119: 
120:         
121:         uint256 gamma = uint256(keccak256(abi.encodePacked(
122:             params.pubkeyRegistrationSignature.X, 
123:             params.pubkeyRegistrationSignature.Y, 
124:             params.pubkeyG1.X, 
125:             params.pubkeyG1.Y, 
126:             params.pubkeyG2.X, 
127:             params.pubkeyG2.Y, 
128:             pubkeyRegistrationMessageHash.X, 
129:             pubkeyRegistrationMessageHash.Y
130:         ))) % BN254.FR_MODULUS; // <= FOUND

['133']

131:         
132:         
133:         require(BN254.pairing( // <= FOUND
134:             params.pubkeyRegistrationSignature.plus(params.pubkeyG1.scalar_mul(gamma)),
135:             BN254.negGeneratorG2(), // <= FOUND
136:             pubkeyRegistrationMessageHash.plus(BN254.generatorG1().scalar_mul(gamma)), // <= FOUND
137:             params.pubkeyG2
138:         ), "BLSApkRegistry.registerBLSPublicKey: either the G1 signature is wrong, or G1 and G2 private key do not match");

['155']

150: 
155:     function _processQuorumApkUpdate(bytes memory quorumNumbers, BN254.G1Point memory point) internal { // <= FOUND

['151']

151:         BN254.G1Point memory newApk; // <= FOUND

['162']

162:             bytes24 newApkHash = bytes24(BN254.hashG1Point(newApk)); // <= FOUND

['195']

187: 
195:     function getRegisteredPubkey(address operator) public view returns (BN254.G1Point memory, bytes32) { // <= FOUND

['188']

188:         BN254.G1Point memory pubkey = operatorToPubkey[operator]; // <= FOUND

['231']

229: 
231:     function getApk(uint8 quorumNumber) external view returns (BN254.G1Point memory) { // <= FOUND

['25']

24:     
25:     mapping(address => BN254.G1Point) public operatorToPubkey; // <= FOUND

['31']

30:     
31:     mapping(uint8 => BN254.G1Point) public currentApk; // <= FOUND

['9']

9: import {BN254} from "./libraries/BN254.sol"; // <= FOUND

['130']

123: 
124:         
125:         
126:         
127:         
128:         
129:         
130:         BN254.G1Point memory apk = BN254.G1Point(0, 0); // <= FOUND

['281']

269: 
279:     function trySignatureAndApkVerification(
280:         bytes32 msgHash,
281:         BN254.G1Point memory apk, // <= FOUND
282:         BN254.G2Point memory apkG2, // <= FOUND
283:         BN254.G1Point memory sigma // <= FOUND
284:     ) public view returns(bool pairingSuccessful, bool siganatureIsValid) {

['277']

276:         
277:         uint256 gamma = uint256(keccak256(abi.encodePacked(msgHash, apk.X, apk.Y, apkG2.X[0], apkG2.X[1], apkG2.Y[0], apkG2.Y[1], sigma.X, sigma.Y))) % BN254.FR_MODULUS; // <= FOUND

['279']

278:         
279:         (pairingSuccessful, siganatureIsValid) = BN254.safePairing( // <= FOUND
280:                 sigma.plus(apk.scalar_mul(gamma)),
281:                 BN254.negGeneratorG2(), // <= FOUND
282:                 BN254.hashToG1(msgHash).plus(BN254.generatorG1().scalar_mul(gamma)), // <= FOUND
283:                 apkG2,
284:                 PAIRING_EQUALITY_CHECK_GAS
285:             );

['133']

126: 
133:     function scalar_mul_tiny(BN254.G1Point memory p, uint16 s) internal view returns (BN254.G1Point memory) { // <= FOUND

['137']

135: 
136:         
137:         BN254.G1Point memory acc = BN254.G1Point(0, 0); // <= FOUND

['138']

137:         
138:         BN254.G1Point memory p2n = p; // <= FOUND

['276']

273: 
276:     function hashG1Point(BN254.G1Point memory pk) internal pure returns (bytes32 hashedG1) { // <= FOUND

['287']

283: 
286:     function hashG2Point(
287:         BN254.G2Point memory pk // <= FOUND
288:     ) internal pure returns (bytes32) {

['347']

347:         require(success, "BN254.expMod: call failure"); // <= FOUND

['9']

8: 
9: import {BN254} from "../libraries/BN254.sol"; // <= FOUND

['31']

31:         BN254.G1Point pubkeyRegistrationSignature; // <= FOUND

['32']

32:         BN254.G1Point pubkeyG1; // <= FOUND

['33']

33:         BN254.G2Point pubkeyG2; // <= FOUND

['41']

38: 
41:     event NewPubkeyRegistration(address indexed operator, BN254.G1Point pubkeyG1, BN254.G2Point pubkeyG2); // <= FOUND

['114']

104: 
111:     function registerBLSPublicKey(
112:         address operator,
113:         PubkeyRegistrationParams calldata params,
114:         BN254.G1Point calldata pubkeyRegistrationMessageHash // <= FOUND
115:     ) external returns (bytes32 operatorId);

['119']

114: 
119:     function getRegisteredPubkey(address operator) external view returns (BN254.G1Point memory, bytes32); // <= FOUND

['119']

117: 
119:     function getApk(uint8 quorumNumber) external view returns (BN254.G1Point memory); // <= FOUND

['21']

21:         BN254.G1Point[] nonSignerPubkeys;  // <= FOUND

['22']

22:         BN254.G1Point[] quorumApks;  // <= FOUND

['23']

23:         BN254.G2Point apkG2;  // <= FOUND

['24']

24:         BN254.G1Point sigma;  // <= FOUND

['8']

8: import {BN254} from "../libraries/BN254.sol"; // <= FOUND

['151']

146: 
151:     function pubkeyRegistrationMessageHash(address operator) external view returns (BN254.G1Point memory); // <= FOUND

['907']

902: 
907:     function pubkeyRegistrationMessageHash(address operator) public view returns (BN254.G1Point memory) { // <= FOUND

['903']

903:         return BN254.hashToG1( // <= FOUND
904:             _hashTypedDataV4(
905:                 keccak256(abi.encode(PUBKEY_REGISTRATION_TYPEHASH, operator))
906:             )
907:         );

[NonCritical-97] Consider validating all user inputs

Resolution

Validating user inputs in external and public functions is a critical security practice in smart contract development. This approach prevents malicious or erroneous data from compromising contract integrity or behavior. By checking inputs against expected formats, ranges, or conditions before processing, contracts can avoid common vulnerabilities like reentrancy, overflow/underflow, and unauthorized access.

Num of instances: 65

Findings

Click to show findings

['42']

42:     function registerOperator( // <= FOUND
43:         address operator, // <= FOUND
44:         bytes memory quorumNumbers // <= FOUND
45:     ) public virtual onlyRegistryCoordinator { // <= FOUND
46:         
47:         (BN254.G1Point memory pubkey, ) = getRegisteredPubkey(operator); // <= FOUND
48: 
49:         
50:         _processQuorumApkUpdate(quorumNumbers, pubkey); // <= FOUND
51: 
52:         
53:         emit OperatorAddedToQuorums(operator, quorumNumbers); // <= FOUND
54:     }

['68']

68:     function deregisterOperator( // <= FOUND
69:         address operator, // <= FOUND
70:         bytes memory quorumNumbers // <= FOUND
71:     ) public virtual onlyRegistryCoordinator { // <= FOUND
72:         
73:         (BN254.G1Point memory pubkey, ) = getRegisteredPubkey(operator); // <= FOUND
74: 
75:         
76:         _processQuorumApkUpdate(quorumNumbers, pubkey.negate()); // <= FOUND
77:         emit OperatorRemovedFromQuorums(operator, quorumNumbers); // <= FOUND
78:     }

['84']

84:     function initializeQuorum(uint8 quorumNumber) public virtual onlyRegistryCoordinator { // <= FOUND
85:         require(apkHistory[quorumNumber].length == 0, "BLSApkRegistry.initializeQuorum: quorum already exists"); // <= FOUND
86: 
87:         apkHistory[quorumNumber].push(ApkUpdate({ // <= FOUND
88:             apkHash: bytes24(0), // <= FOUND
89:             updateBlockNumber: uint32(block.number), // <= FOUND
90:             nextUpdateBlockNumber: 0 // <= FOUND
91:         }));
92:     }

['115']

115:     function initializeQuorum(uint8 quorumNumber) public virtual onlyRegistryCoordinator { // <= FOUND
116:         require(_operatorCountHistory[quorumNumber].length == 0, "IndexRegistry.createQuorum: quorum already exists"); // <= FOUND
117: 
118:         _operatorCountHistory[quorumNumber].push(QuorumUpdate({ // <= FOUND
119:             numOperators: 0, // <= FOUND
120:             fromBlockNumber: uint32(block.number) // <= FOUND
121:         }));
122:     }

['100']

100:     function registerBLSPublicKey( // <= FOUND
101:         address operator, // <= FOUND
102:         PubkeyRegistrationParams calldata params, // <= FOUND
103:         BN254.G1Point calldata pubkeyRegistrationMessageHash // <= FOUND
104:     ) external onlyRegistryCoordinator returns (bytes32 operatorId) { // <= FOUND
105:         bytes32 pubkeyHash = BN254.hashG1Point(params.pubkeyG1); // <= FOUND
106:         require(
107:             pubkeyHash != ZERO_PK_HASH, "BLSApkRegistry.registerBLSPublicKey: cannot register zero pubkey" // <= FOUND
108:         );
109:         require(
110:             operatorToPubkeyHash[operator] == bytes32(0), // <= FOUND
111:             "BLSApkRegistry.registerBLSPublicKey: operator already registered pubkey" // <= FOUND
112:         );
113:         require(
114:             pubkeyHashToOperator[pubkeyHash] == address(0), // <= FOUND
115:             "BLSApkRegistry.registerBLSPublicKey: public key already registered" // <= FOUND
116:         );
117: 
118:         
119:         uint256 gamma = uint256(keccak256(abi.encodePacked( // <= FOUND
120:             params.pubkeyRegistrationSignature.X,  // <= FOUND
121:             params.pubkeyRegistrationSignature.Y,  // <= FOUND
122:             params.pubkeyG1.X,  // <= FOUND
123:             params.pubkeyG1.Y,  // <= FOUND
124:             params.pubkeyG2.X,  // <= FOUND
125:             params.pubkeyG2.Y,  // <= FOUND
126:             pubkeyRegistrationMessageHash.X,  // <= FOUND
127:             pubkeyRegistrationMessageHash.Y // <= FOUND
128:         ))) % BN254.FR_MODULUS;
129:         
130:         
131:         require(BN254.pairing( // <= FOUND
132:             params.pubkeyRegistrationSignature.plus(params.pubkeyG1.scalar_mul(gamma)), // <= FOUND
133:             BN254.negGeneratorG2(), // <= FOUND
134:             pubkeyRegistrationMessageHash.plus(BN254.generatorG1().scalar_mul(gamma)), // <= FOUND
135:             params.pubkeyG2 // <= FOUND
136:         ), "BLSApkRegistry.registerBLSPublicKey: either the G1 signature is wrong, or G1 and G2 private key do not match"); // <= FOUND
137: 
138:         operatorToPubkey[operator] = params.pubkeyG1; // <= FOUND
139:         operatorToPubkeyHash[operator] = pubkeyHash; // <= FOUND
140:         pubkeyHashToOperator[pubkeyHash] = operator; // <= FOUND
141: 
142:         emit NewPubkeyRegistration(operator, params.pubkeyG1, params.pubkeyG2); // <= FOUND
143:         return pubkeyHash; // <= FOUND
144:     }

['187']

187:     function getRegisteredPubkey(address operator) public view returns (BN254.G1Point memory, bytes32) { // <= FOUND
188:         BN254.G1Point memory pubkey = operatorToPubkey[operator]; // <= FOUND
189:         bytes32 pubkeyHash = operatorToPubkeyHash[operator]; // <= FOUND
190: 
191:         require(
192:             pubkeyHash != bytes32(0), // <= FOUND
193:             "BLSApkRegistry.getRegisteredPubkey: operator is not registered" // <= FOUND
194:         );
195:         
196:         return (pubkey, pubkeyHash); // <= FOUND
197:     }

['229']

229:     function getApk(uint8 quorumNumber) external view returns (BN254.G1Point memory) { // <= FOUND
230:         return currentApk[quorumNumber]; // <= FOUND
231:     }

['234']

234:     function getApkUpdateAtIndex(uint8 quorumNumber, uint256 index) external view returns (ApkUpdate memory) { // <= FOUND
235:         return apkHistory[quorumNumber][index]; // <= FOUND
236:     }

['245']

245:     function getApkHashAtBlockNumberAndIndex( // <= FOUND
246:         uint8 quorumNumber, // <= FOUND
247:         uint32 blockNumber, // <= FOUND
248:         uint256 index // <= FOUND
249:     ) external view returns (bytes24) { // <= FOUND
250:         ApkUpdate memory quorumApkUpdate = apkHistory[quorumNumber][index]; // <= FOUND
251: 
252:         
253: 
257:         require(
258:             blockNumber >= quorumApkUpdate.updateBlockNumber, // <= FOUND
259:             "BLSApkRegistry._validateApkHashAtBlockNumber: index too recent" // <= FOUND
260:         );
261:         require(
262:             quorumApkUpdate.nextUpdateBlockNumber == 0 || blockNumber < quorumApkUpdate.nextUpdateBlockNumber, // <= FOUND
263:             "BLSApkRegistry._validateApkHashAtBlockNumber: not latest apk update" // <= FOUND
264:         );
265: 
266:         return quorumApkUpdate.apkHash; // <= FOUND
267:     }

['270']

270:     function getApkHistoryLength(uint8 quorumNumber) external view returns (uint32) { // <= FOUND
271:         return uint32(apkHistory[quorumNumber].length); // <= FOUND
272:     }

['275']

275:     function getOperatorFromPubkeyHash(bytes32 pubkeyHash) public view returns (address) { // <= FOUND
276:         return pubkeyHashToOperator[pubkeyHash]; // <= FOUND
277:     }

['281']

281:     function getOperatorId(address operator) public view returns (bytes32) { // <= FOUND
282:         return operatorToPubkeyHash[operator]; // <= FOUND
283:     }

['87']

87:     function checkSignatures( // <= FOUND
88:         bytes32 msgHash,  // <= FOUND
89:         bytes calldata quorumNumbers, // <= FOUND
90:         uint32 referenceBlockNumber,  // <= FOUND
91:         NonSignerStakesAndSignature memory params // <= FOUND
92:     ) 
93:         public  // <= FOUND
94:         view
95:         returns ( // <= FOUND
96:             QuorumStakeTotals memory, // <= FOUND
97:             bytes32 // <= FOUND
98:         )
99:     {
100:         require(quorumNumbers.length != 0, "BLSSignatureChecker.checkSignatures: empty quorum input"); // <= FOUND
101: 
102:         require(
103:             (quorumNumbers.length == params.quorumApks.length) && // <= FOUND
104:             (quorumNumbers.length == params.quorumApkIndices.length) && // <= FOUND
105:             (quorumNumbers.length == params.totalStakeIndices.length) && // <= FOUND
106:             (quorumNumbers.length == params.nonSignerStakeIndices.length), // <= FOUND
107:             "BLSSignatureChecker.checkSignatures: input quorum length mismatch" // <= FOUND
108:         );
109: 
110:         require(
111:             params.nonSignerPubkeys.length == params.nonSignerQuorumBitmapIndices.length, // <= FOUND
112:             "BLSSignatureChecker.checkSignatures: input nonsigner length mismatch" // <= FOUND
113:         );
114: 
115:         require(referenceBlockNumber < uint32(block.number), "BLSSignatureChecker.checkSignatures: invalid reference block"); // <= FOUND
116: 
117:         
118:         
119:         
120:         
121:         
122:         
123:         BN254.G1Point memory apk = BN254.G1Point(0, 0); // <= FOUND
124: 
125:         
126:         
127:         
128:         QuorumStakeTotals memory stakeTotals; // <= FOUND
129:         stakeTotals.totalStakeForQuorum = new uint96[](quorumNumbers.length); // <= FOUND
130:         stakeTotals.signedStakeForQuorum = new uint96[](quorumNumbers.length); // <= FOUND
131: 
132:         NonSignerInfo memory nonSigners; // <= FOUND
133:         nonSigners.quorumBitmaps = new uint256[](params.nonSignerPubkeys.length); // <= FOUND
134:         nonSigners.pubkeyHashes = new bytes32[](params.nonSignerPubkeys.length); // <= FOUND
135: 
136:         {
137:             
138:             
139:             uint256 signingQuorumBitmap = BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, registryCoordinator.quorumCount()); // <= FOUND
140: 
141:             for (uint256 j = 0; j < params.nonSignerPubkeys.length; j++) { // <= FOUND
142:                 
143:                 
144:                 
145:                 nonSigners.pubkeyHashes[j] = params.nonSignerPubkeys[j].hashG1Point(); // <= FOUND
146:                 if (j != 0) {
147:                     require(
148:                         uint256(nonSigners.pubkeyHashes[j]) > uint256(nonSigners.pubkeyHashes[j - 1]), // <= FOUND
149:                         "BLSSignatureChecker.checkSignatures: nonSignerPubkeys not sorted" // <= FOUND
150:                     );
151:                 }
152: 
153:                 
154:                 nonSigners.quorumBitmaps[j] =  // <= FOUND
155:                     registryCoordinator.getQuorumBitmapAtBlockNumberByIndex({ // <= FOUND
156:                         operatorId: nonSigners.pubkeyHashes[j], // <= FOUND
157:                         blockNumber: referenceBlockNumber, // <= FOUND
158:                         index: params.nonSignerQuorumBitmapIndices[j] // <= FOUND
159:                     });
160: 
161:                 
162:                 
163:                 
164:                 apk = apk.plus( // <= FOUND
165:                     params.nonSignerPubkeys[j] // <= FOUND
166:                         .scalar_mul_tiny( // <= FOUND
167:                             BitmapUtils.countNumOnes(nonSigners.quorumBitmaps[j] & signingQuorumBitmap)  // <= FOUND
168:                         )
169:                 );
170:             }
171:         }
172: 
173:         
174:         
175:         
176:         apk = apk.negate(); // <= FOUND
177: 
178:         
179: 
184:         {
185:             bool _staleStakesForbidden = staleStakesForbidden; // <= FOUND
186:             uint256 withdrawalDelayBlocks = _staleStakesForbidden ? delegation.minWithdrawalDelayBlocks() : 0; // <= FOUND
187: 
188:             for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
189:                 
190:                 
191:                 if (_staleStakesForbidden) { // <= FOUND
192:                     require(
193:                         registryCoordinator.quorumUpdateBlockNumber(uint8(quorumNumbers[i])) + withdrawalDelayBlocks >= referenceBlockNumber, // <= FOUND
194:                         "BLSSignatureChecker.checkSignatures: StakeRegistry updates must be within withdrawalDelayBlocks window" // <= FOUND
195:                     );
196:                 }
197: 
198:                 
199:                 
200:                 require(
201:                     bytes24(params.quorumApks[i].hashG1Point()) ==  // <= FOUND
202:                         blsApkRegistry.getApkHashAtBlockNumberAndIndex({ // <= FOUND
203:                             quorumNumber: uint8(quorumNumbers[i]), // <= FOUND
204:                             blockNumber: referenceBlockNumber, // <= FOUND
205:                             index: params.quorumApkIndices[i] // <= FOUND
206:                         }),
207:                     "BLSSignatureChecker.checkSignatures: quorumApk hash in storage does not match provided quorum apk" // <= FOUND
208:                 );
209:                 apk = apk.plus(params.quorumApks[i]); // <= FOUND
210: 
211:                 
212:                 stakeTotals.totalStakeForQuorum[i] =  // <= FOUND
213:                     stakeRegistry.getTotalStakeAtBlockNumberFromIndex({ // <= FOUND
214:                         quorumNumber: uint8(quorumNumbers[i]), // <= FOUND
215:                         blockNumber: referenceBlockNumber, // <= FOUND
216:                         index: params.totalStakeIndices[i] // <= FOUND
217:                     });
218:                 stakeTotals.signedStakeForQuorum[i] = stakeTotals.totalStakeForQuorum[i]; // <= FOUND
219: 
220:                 
221:                 uint256 nonSignerForQuorumIndex = 0; // <= FOUND
222:                 
223:                 
224:                 
225:                 for (uint256 j = 0; j < params.nonSignerPubkeys.length; j++) { // <= FOUND
226:                     
227:                     if (BitmapUtils.isSet(nonSigners.quorumBitmaps[j], uint8(quorumNumbers[i]))) { // <= FOUND
228:                         stakeTotals.signedStakeForQuorum[i] -= // <= FOUND
229:                             stakeRegistry.getStakeAtBlockNumberAndIndex({ // <= FOUND
230:                                 quorumNumber: uint8(quorumNumbers[i]), // <= FOUND
231:                                 blockNumber: referenceBlockNumber, // <= FOUND
232:                                 operatorId: nonSigners.pubkeyHashes[j], // <= FOUND
233:                                 index: params.nonSignerStakeIndices[i][nonSignerForQuorumIndex] // <= FOUND
234:                             });
235:                         unchecked {
236:                             ++nonSignerForQuorumIndex; // <= FOUND
237:                         }
238:                     }
239:                 }
240:             }
241:         }
242:         {
243:             
244:             (bool pairingSuccessful, bool signatureIsValid) = trySignatureAndApkVerification( // <= FOUND
245:                 msgHash,  // <= FOUND
246:                 apk,  // <= FOUND
247:                 params.apkG2,  // <= FOUND
248:                 params.sigma // <= FOUND
249:             );
250:             require(pairingSuccessful, "BLSSignatureChecker.checkSignatures: pairing precompile call failed"); // <= FOUND
251:             require(signatureIsValid, "BLSSignatureChecker.checkSignatures: signature is invalid"); // <= FOUND
252:         }
253:         
254:         bytes32 signatoryRecordHash = keccak256(abi.encodePacked(referenceBlockNumber, nonSigners.pubkeyHashes)); // <= FOUND
255: 
256:         
257:         return (stakeTotals, signatoryRecordHash); // <= FOUND
258:     }

['269']

269:     function trySignatureAndApkVerification( // <= FOUND
270:         bytes32 msgHash, // <= FOUND
271:         BN254.G1Point memory apk, // <= FOUND
272:         BN254.G2Point memory apkG2, // <= FOUND
273:         BN254.G1Point memory sigma // <= FOUND
274:     ) public view returns(bool pairingSuccessful, bool siganatureIsValid) { // <= FOUND
275:         
276:         uint256 gamma = uint256(keccak256(abi.encodePacked(msgHash, apk.X, apk.Y, apkG2.X[0], apkG2.X[1], apkG2.Y[0], apkG2.Y[1], sigma.X, sigma.Y))) % BN254.FR_MODULUS; // <= FOUND
277:         
278:         (pairingSuccessful, siganatureIsValid) = BN254.safePairing( // <= FOUND
279:                 sigma.plus(apk.scalar_mul(gamma)), // <= FOUND
280:                 BN254.negGeneratorG2(), // <= FOUND
281:                 BN254.hashToG1(msgHash).plus(BN254.generatorG1().scalar_mul(gamma)), // <= FOUND
282:                 apkG2, // <= FOUND
283:                 PAIRING_EQUALITY_CHECK_GAS
284:             );
285:     }

['40']

40:     function registerOperator( // <= FOUND
41:         bytes32 operatorId,  // <= FOUND
42:         bytes calldata quorumNumbers // <= FOUND
43:     ) public virtual onlyRegistryCoordinator returns(uint32[] memory) { // <= FOUND
44:         uint32[] memory numOperatorsPerQuorum = new uint32[](quorumNumbers.length); // <= FOUND
45: 
46:         for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
47:             
48:             uint8 quorumNumber = uint8(quorumNumbers[i]); // <= FOUND
49:             uint256 historyLength = _operatorCountHistory[quorumNumber].length; // <= FOUND
50:             require(historyLength != 0, "IndexRegistry.registerOperator: quorum does not exist"); // <= FOUND
51: 
52:             
53: 
56:             uint32 newOperatorCount = _increaseOperatorCount(quorumNumber); // <= FOUND
57:             _assignOperatorToIndex({ // <= FOUND
58:                 operatorId: operatorId, // <= FOUND
59:                 quorumNumber: quorumNumber, // <= FOUND
60:                 operatorIndex: newOperatorCount - 1 // <= FOUND
61:             });
62: 
63:             
64:             numOperatorsPerQuorum[i] = newOperatorCount; // <= FOUND
65:         }
66: 
67:         return numOperatorsPerQuorum; // <= FOUND
68:     }

['300']

300:     function getOperatorUpdateAtIndex(uint8 quorumNumber, uint32 operatorIndex, uint32 arrayIndex) external view returns (OperatorUpdate memory) { // <= FOUND
301:         return _operatorIndexHistory[quorumNumber][operatorIndex][arrayIndex]; // <= FOUND
302:     }

['305']

305:     function getQuorumUpdateAtIndex(uint8 quorumNumber, uint32 quorumIndex) external view returns (QuorumUpdate memory) { // <= FOUND
306:         return _operatorCountHistory[quorumNumber][quorumIndex]; // <= FOUND
307:     }

['317']

317:     function getLatestOperatorUpdate(uint8 quorumNumber, uint32 operatorIndex) external view returns (OperatorUpdate memory) { // <= FOUND
318:         return _latestOperatorIndexUpdate(quorumNumber, operatorIndex); // <= FOUND
319:     }

['322']

322:     function getOperatorListAtBlockNumber( // <= FOUND
323:         uint8 quorumNumber,  // <= FOUND
324:         uint32 blockNumber // <= FOUND
325:     ) external view returns (bytes32[] memory){ // <= FOUND
326:         uint32 operatorCount = _operatorCountAtBlockNumber(quorumNumber, blockNumber); // <= FOUND
327:         bytes32[] memory operatorList = new bytes32[](operatorCount); // <= FOUND
328:         for (uint256 i = 0; i < operatorCount; i++) { // <= FOUND
329:             operatorList[i] = _operatorIdForIndexAtBlockNumber(quorumNumber, uint32(i), blockNumber); // <= FOUND
330:             require(
331:                 operatorList[i] != OPERATOR_DOES_NOT_EXIST_ID,  // <= FOUND
332:                 "IndexRegistry.getOperatorListAtBlockNumber: operator does not exist at the given block number" // <= FOUND
333:             );
334:         }
335:         return operatorList; // <= FOUND
336:     }

['40']

40:     function getOperatorState( // <= FOUND
41:         IRegistryCoordinator registryCoordinator,  // <= FOUND
42:         bytes32 operatorId,  // <= FOUND
43:         uint32 blockNumber // <= FOUND
44:     ) external view returns (uint256, Operator[][] memory) { // <= FOUND
45:         bytes32[] memory operatorIds = new bytes32[](1); // <= FOUND
46:         operatorIds[0] = operatorId; // <= FOUND
47:         uint256 index = registryCoordinator.getQuorumBitmapIndicesAtBlockNumber(blockNumber, operatorIds)[0]; // <= FOUND
48:     
49:         uint256 quorumBitmap = registryCoordinator.getQuorumBitmapAtBlockNumberByIndex(operatorId, blockNumber, index); // <= FOUND
50: 
51:         bytes memory quorumNumbers = BitmapUtils.bitmapToBytesArray(quorumBitmap); // <= FOUND
52: 
53:         return (quorumBitmap, getOperatorState(registryCoordinator, quorumNumbers, blockNumber)); // <= FOUND
54:     }

['64']

64:     function getOperatorState( // <= FOUND
65:         IRegistryCoordinator registryCoordinator,  // <= FOUND
66:         bytes memory quorumNumbers,  // <= FOUND
67:         uint32 blockNumber // <= FOUND
68:     ) public view returns(Operator[][] memory) { // <= FOUND
69:         IStakeRegistry stakeRegistry = registryCoordinator.stakeRegistry(); // <= FOUND
70:         IIndexRegistry indexRegistry = registryCoordinator.indexRegistry(); // <= FOUND
71:         IBLSApkRegistry blsApkRegistry = registryCoordinator.blsApkRegistry(); // <= FOUND
72: 
73:         Operator[][] memory operators = new Operator[][](quorumNumbers.length); // <= FOUND
74:         for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
75:             uint8 quorumNumber = uint8(quorumNumbers[i]); // <= FOUND
76:             bytes32[] memory operatorIds = indexRegistry.getOperatorListAtBlockNumber(quorumNumber, blockNumber); // <= FOUND
77:             operators[i] = new Operator[](operatorIds.length); // <= FOUND
78:             for (uint256 j = 0; j < operatorIds.length; j++) { // <= FOUND
79:                 operators[i][j] = Operator({ // <= FOUND
80:                     operator: blsApkRegistry.getOperatorFromPubkeyHash(operatorIds[j]), // <= FOUND
81:                     operatorId: bytes32(operatorIds[j]), // <= FOUND
82:                     stake: stakeRegistry.getStakeAtBlockNumber(bytes32(operatorIds[j]), quorumNumber, blockNumber) // <= FOUND
83:                 });
84:             }
85:         }
86:             
87:         return operators; // <= FOUND
88:     }

['104']

104:     function getCheckSignaturesIndices( // <= FOUND
105:         IRegistryCoordinator registryCoordinator, // <= FOUND
106:         uint32 referenceBlockNumber,  // <= FOUND
107:         bytes calldata quorumNumbers,  // <= FOUND
108:         bytes32[] calldata nonSignerOperatorIds // <= FOUND
109:     ) external view returns (CheckSignaturesIndices memory) { // <= FOUND
110:         IStakeRegistry stakeRegistry = registryCoordinator.stakeRegistry(); // <= FOUND
111:         CheckSignaturesIndices memory checkSignaturesIndices; // <= FOUND
112: 
113:         
114:         checkSignaturesIndices.nonSignerQuorumBitmapIndices = registryCoordinator.getQuorumBitmapIndicesAtBlockNumber(referenceBlockNumber, nonSignerOperatorIds); // <= FOUND
115: 
116:         
117:         checkSignaturesIndices.totalStakeIndices = stakeRegistry.getTotalStakeIndicesAtBlockNumber(referenceBlockNumber, quorumNumbers); // <= FOUND
118:         
119:         checkSignaturesIndices.nonSignerStakeIndices = new uint32[][](quorumNumbers.length); // <= FOUND
120:         for (uint8 quorumNumberIndex = 0; quorumNumberIndex < quorumNumbers.length; quorumNumberIndex++) { // <= FOUND
121:             uint256 numNonSignersForQuorum = 0; // <= FOUND
122:             
123:             checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex] = new uint32[](nonSignerOperatorIds.length); // <= FOUND
124: 
125:             for (uint i = 0; i < nonSignerOperatorIds.length; i++) { // <= FOUND
126:                 
127:                 uint192 nonSignerQuorumBitmap =  // <= FOUND
128:                     registryCoordinator.getQuorumBitmapAtBlockNumberByIndex( // <= FOUND
129:                         nonSignerOperatorIds[i],  // <= FOUND
130:                         referenceBlockNumber,  // <= FOUND
131:                         checkSignaturesIndices.nonSignerQuorumBitmapIndices[i] // <= FOUND
132:                     );
133:                 
134:                 require(nonSignerQuorumBitmap != 0, "OperatorStateRetriever.getCheckSignaturesIndices: operator must be registered at blocknumber"); // <= FOUND
135:                 
136:                 
137:                 if ((nonSignerQuorumBitmap >> uint8(quorumNumbers[quorumNumberIndex])) & 1 == 1) { // <= FOUND
138:                     
139:                     checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex][numNonSignersForQuorum] = stakeRegistry.getStakeUpdateIndexAtBlockNumber( // <= FOUND
140:                         nonSignerOperatorIds[i], // <= FOUND
141:                         uint8(quorumNumbers[quorumNumberIndex]), // <= FOUND
142:                         referenceBlockNumber // <= FOUND
143:                     );
144:                     numNonSignersForQuorum++; // <= FOUND
145:                 }
146:             }
147: 
148:             
149:             uint32[] memory nonSignerStakeIndicesForQuorum = new uint32[](numNonSignersForQuorum); // <= FOUND
150:             for (uint i = 0; i < numNonSignersForQuorum; i++) { // <= FOUND
151:                 nonSignerStakeIndicesForQuorum[i] = checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex][i]; // <= FOUND
152:             }
153:             checkSignaturesIndices.nonSignerStakeIndices[quorumNumberIndex] = nonSignerStakeIndicesForQuorum; // <= FOUND
154:         }
155: 
156:         IBLSApkRegistry blsApkRegistry = registryCoordinator.blsApkRegistry(); // <= FOUND
157:         
158:         checkSignaturesIndices.quorumApkIndices = blsApkRegistry.getApkIndicesAtBlockNumber(quorumNumbers, referenceBlockNumber); // <= FOUND
159: 
160:         return checkSignaturesIndices; // <= FOUND
161:     }

['82']

82:     function initialize( // <= FOUND
83:         address _initialOwner, // <= FOUND
84:         address _churnApprover, // <= FOUND
85:         address _ejector, // <= FOUND
86:         IPauserRegistry _pauserRegistry, // <= FOUND
87:         uint256 _initialPausedStatus, // <= FOUND
88:         OperatorSetParam[] memory _operatorSetParams, // <= FOUND
89:         uint96[] memory _minimumStakes, // <= FOUND
90:         IStakeRegistry.StrategyParams[][] memory _strategyParams // <= FOUND
91:     ) external initializer { // <= FOUND
92:         require(
93:             _operatorSetParams.length == _minimumStakes.length && _minimumStakes.length == _strategyParams.length, // <= FOUND
94:             "RegistryCoordinator.initialize: input length mismatch" // <= FOUND
95:         );
96:         
97:         
98:         _transferOwnership(_initialOwner); // <= FOUND
99:         _initializePauser(_pauserRegistry, _initialPausedStatus); // <= FOUND
100:         _setChurnApprover(_churnApprover); // <= FOUND
101:         _setEjector(_ejector); // <= FOUND
102: 
103:         
104:         registries.push(address(stakeRegistry)); // <= FOUND
105:         registries.push(address(blsApkRegistry)); // <= FOUND
106:         registries.push(address(indexRegistry)); // <= FOUND
107: 
108:         
109:         for (uint256 i = 0; i < _operatorSetParams.length; i++) { // <= FOUND
110:             _createQuorum(_operatorSetParams[i], _minimumStakes[i], _strategyParams[i]); // <= FOUND
111:         }
112:     }

['128']

128:     function registerOperator( // <= FOUND
129:         bytes calldata quorumNumbers, // <= FOUND
130:         string calldata socket, // <= FOUND
131:         IBLSApkRegistry.PubkeyRegistrationParams calldata params, // <= FOUND
132:         SignatureWithSaltAndExpiry memory operatorSignature // <= FOUND
133:     ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) { // <= FOUND
134:         
135: 
141:         bytes32 operatorId = _getOrCreateOperatorId(msg.sender, params); // <= FOUND
142: 
143:         
144:         
145:         uint32[] memory numOperatorsPerQuorum = _registerOperator({ // <= FOUND
146:             operator: msg.sender,  // <= FOUND
147:             operatorId: operatorId, // <= FOUND
148:             quorumNumbers: quorumNumbers,  // <= FOUND
149:             socket: socket, // <= FOUND
150:             operatorSignature: operatorSignature // <= FOUND
151:         }).numOperatorsPerQuorum; // <= FOUND
152: 
153:         
154:         
155:         for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
156:             uint8 quorumNumber = uint8(quorumNumbers[i]); // <= FOUND
157: 
158:             require(
159:                 numOperatorsPerQuorum[i] <= _quorumParams[quorumNumber].maxOperatorCount, // <= FOUND
160:                 "RegistryCoordinator.registerOperator: operator count exceeds maximum" // <= FOUND
161:             );
162:         }
163:     }

['177']

177:     function registerOperatorWithChurn( // <= FOUND
178:         bytes calldata quorumNumbers,  // <= FOUND
179:         string calldata socket, // <= FOUND
180:         IBLSApkRegistry.PubkeyRegistrationParams calldata params, // <= FOUND
181:         OperatorKickParam[] calldata operatorKickParams, // <= FOUND
182:         SignatureWithSaltAndExpiry memory churnApproverSignature, // <= FOUND
183:         SignatureWithSaltAndExpiry memory operatorSignature // <= FOUND
184:     ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) { // <= FOUND
185:         require(operatorKickParams.length == quorumNumbers.length, "RegistryCoordinator.registerOperatorWithChurn: input length mismatch"); // <= FOUND
186:         
187:         
188: 
194:         bytes32 operatorId = _getOrCreateOperatorId(msg.sender, params); // <= FOUND
195: 
196:         
197:         _verifyChurnApproverSignature({ // <= FOUND
198:             registeringOperator: msg.sender, // <= FOUND
199:             registeringOperatorId: operatorId, // <= FOUND
200:             operatorKickParams: operatorKickParams, // <= FOUND
201:             churnApproverSignature: churnApproverSignature // <= FOUND
202:         });
203: 
204:         
205:         
206:         RegisterResults memory results = _registerOperator({ // <= FOUND
207:             operator: msg.sender, // <= FOUND
208:             operatorId: operatorId, // <= FOUND
209:             quorumNumbers: quorumNumbers, // <= FOUND
210:             socket: socket, // <= FOUND
211:             operatorSignature: operatorSignature // <= FOUND
212:         });
213: 
214:         
215:         
216:         for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
217:             OperatorSetParam memory operatorSetParams = _quorumParams[uint8(quorumNumbers[i])]; // <= FOUND
218:             
219:             
220: 
223:             if (results.numOperatorsPerQuorum[i] > operatorSetParams.maxOperatorCount) { // <= FOUND
224:                 _validateChurn({ // <= FOUND
225:                     quorumNumber: uint8(quorumNumbers[i]), // <= FOUND
226:                     totalQuorumStake: results.totalStakes[i], // <= FOUND
227:                     newOperator: msg.sender, // <= FOUND
228:                     newOperatorStake: results.operatorStakes[i], // <= FOUND
229:                     kickParams: operatorKickParams[i], // <= FOUND
230:                     setParams: operatorSetParams // <= FOUND
231:                 });
232: 
233:                 _deregisterOperator(operatorKickParams[i].operator, quorumNumbers[i:i+1]); // <= FOUND
234:             }
235:         }
236:     }

['242']

242:     function deregisterOperator( // <= FOUND
243:         bytes calldata quorumNumbers // <= FOUND
244:     ) external onlyWhenNotPaused(PAUSED_DEREGISTER_OPERATOR) { // <= FOUND
245:         _deregisterOperator({ // <= FOUND
246:             operator: msg.sender,  // <= FOUND
247:             quorumNumbers: quorumNumbers // <= FOUND
248:         });
249:     }

['284']

284:     function updateOperatorsForQuorum( // <= FOUND
285:         address[][] calldata operatorsPerQuorum, // <= FOUND
286:         bytes calldata quorumNumbers // <= FOUND
287:     ) external onlyWhenNotPaused(PAUSED_UPDATE_OPERATOR) { // <= FOUND
288:         
289:         
290:         
291:         
292:         uint192 quorumBitmap = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount)); // <= FOUND
293:         require(
294:             operatorsPerQuorum.length == quorumNumbers.length, // <= FOUND
295:             "RegistryCoordinator.updateOperatorsForQuorum: input length mismatch" // <= FOUND
296:         );
297: 
298:         
299:         for (uint256 i = 0; i < quorumNumbers.length; ++i) { // <= FOUND
300:             uint8 quorumNumber = uint8(quorumNumbers[i]); // <= FOUND
301: 
302:             
303:             address[] calldata currQuorumOperators = operatorsPerQuorum[i]; // <= FOUND
304:             require(
305:                 currQuorumOperators.length == indexRegistry.totalOperatorsForQuorum(quorumNumber), // <= FOUND
306:                 "RegistryCoordinator.updateOperatorsForQuorum: number of updated operators does not match quorum total" // <= FOUND
307:             );
308: 
309:             address prevOperatorAddress = address(0); // <= FOUND
310:             
311:             
312:             
313:             
314:             for (uint256 j = 0; j < currQuorumOperators.length; ++j) { // <= FOUND
315:                 address operator = currQuorumOperators[j]; // <= FOUND
316:                 
317:                 OperatorInfo memory operatorInfo = _operatorInfo[operator]; // <= FOUND
318:                 bytes32 operatorId = operatorInfo.operatorId; // <= FOUND
319:                 
320:                 {
321:                     uint192 currentBitmap = _currentOperatorBitmap(operatorId); // <= FOUND
322:                     
323:                     require(
324:                         BitmapUtils.isSet(currentBitmap, quorumNumber), // <= FOUND
325:                         "RegistryCoordinator.updateOperatorsForQuorum: operator not in quorum" // <= FOUND
326:                     );
327:                     
328:                     require(
329:                         operator > prevOperatorAddress, // <= FOUND
330:                         "RegistryCoordinator.updateOperatorsForQuorum: operators array must be sorted in ascending address order" // <= FOUND
331:                     );
332:                 }
333:                 
334:                 
335:                 _updateOperator(operator, operatorInfo, quorumNumbers[i:i+1]); // <= FOUND
336:                 prevOperatorAddress = operator; // <= FOUND
337:             }
338: 
339:             
340:             quorumUpdateBlockNumber[quorumNumber] = block.number; // <= FOUND
341:             emit QuorumBlockNumberUpdated(quorumNumber, block.number); // <= FOUND
342:         }
343:     }

['349']

349:     function updateSocket(string memory socket) external { // <= FOUND
350:         require(_operatorInfo[msg.sender].status == OperatorStatus.REGISTERED, "RegistryCoordinator.updateSocket: operator is not registered"); // <= FOUND
351:         emit OperatorSocketUpdate(_operatorInfo[msg.sender].operatorId, socket); // <= FOUND
352:     }

['363']

363:     function ejectOperator( // <= FOUND
364:         address operator,  // <= FOUND
365:         bytes calldata quorumNumbers // <= FOUND
366:     ) external onlyEjector { // <= FOUND
367:         _deregisterOperator({ // <= FOUND
368:             operator: operator,  // <= FOUND
369:             quorumNumbers: quorumNumbers // <= FOUND
370:         });
371:     }

['385']

385:     function createQuorum( // <= FOUND
386:         OperatorSetParam memory operatorSetParams, // <= FOUND
387:         uint96 minimumStake, // <= FOUND
388:         IStakeRegistry.StrategyParams[] memory strategyParams // <= FOUND
389:     ) external virtual onlyOwner { // <= FOUND
390:         _createQuorum(operatorSetParams, minimumStake, strategyParams); // <= FOUND
391:     }

['400']

400:     function setOperatorSetParams( // <= FOUND
401:         uint8 quorumNumber,  // <= FOUND
402:         OperatorSetParam memory operatorSetParams // <= FOUND
403:     ) external onlyOwner quorumExists(quorumNumber) { // <= FOUND
404:         _setOperatorSetParams(quorumNumber, operatorSetParams); // <= FOUND
405:     }

['787']

787:     function getOperatorSetParams(uint8 quorumNumber) external view returns (OperatorSetParam memory) { // <= FOUND
788:         return _quorumParams[quorumNumber]; // <= FOUND
789:     }

['792']

792:     function getOperator(address operator) external view returns (OperatorInfo memory) { // <= FOUND
793:         return _operatorInfo[operator]; // <= FOUND
794:     }

['797']

797:     function getOperatorId(address operator) external view returns (bytes32) { // <= FOUND
798:         return _operatorInfo[operator].operatorId; // <= FOUND
799:     }

['807']

807:     function getOperatorStatus(address operator) external view returns (IRegistryCoordinator.OperatorStatus) { // <= FOUND
808:         return _operatorInfo[operator].status; // <= FOUND
809:     }

['816']

816:     function getQuorumBitmapIndicesAtBlockNumber( // <= FOUND
817:         uint32 blockNumber,  // <= FOUND
818:         bytes32[] memory operatorIds // <= FOUND
819:     ) external view returns (uint32[] memory) { // <= FOUND
820:         uint32[] memory indices = new uint32[](operatorIds.length); // <= FOUND
821:         for (uint256 i = 0; i < operatorIds.length; i++) { // <= FOUND
822:             indices[i] = _getQuorumBitmapIndexAtBlockNumber(blockNumber, operatorIds[i]); // <= FOUND
823:         }
824:         return indices; // <= FOUND
825:     }

['833']

833:     function getQuorumBitmapAtBlockNumberByIndex( // <= FOUND
834:         bytes32 operatorId,  // <= FOUND
835:         uint32 blockNumber,  // <= FOUND
836:         uint256 index // <= FOUND
837:     ) external view returns (uint192) { // <= FOUND
838:         QuorumBitmapUpdate memory quorumBitmapUpdate = _operatorBitmapHistory[operatorId][index]; // <= FOUND
839:         
840:         
841: 
845:         require(
846:             blockNumber >= quorumBitmapUpdate.updateBlockNumber,  // <= FOUND
847:             "RegistryCoordinator.getQuorumBitmapAtBlockNumberByIndex: quorumBitmapUpdate is from after blockNumber" // <= FOUND
848:         );
849:         require(
850:             quorumBitmapUpdate.nextUpdateBlockNumber == 0 || blockNumber < quorumBitmapUpdate.nextUpdateBlockNumber, // <= FOUND
851:             "RegistryCoordinator.getQuorumBitmapAtBlockNumberByIndex: quorumBitmapUpdate is from before blockNumber" // <= FOUND
852:         );
853: 
854:         return quorumBitmapUpdate.quorumBitmap; // <= FOUND
855:     }

['858']

858:     function getQuorumBitmapUpdateByIndex( // <= FOUND
859:         bytes32 operatorId,  // <= FOUND
860:         uint256 index // <= FOUND
861:     ) external view returns (QuorumBitmapUpdate memory) { // <= FOUND
862:         return _operatorBitmapHistory[operatorId][index]; // <= FOUND
863:     }

['871']

871:     function getQuorumBitmapHistoryLength(bytes32 operatorId) external view returns (uint256) { // <= FOUND
872:         return _operatorBitmapHistory[operatorId].length; // <= FOUND
873:     }

['887']

887:     function calculateOperatorChurnApprovalDigestHash( // <= FOUND
888:         address registeringOperator, // <= FOUND
889:         bytes32 registeringOperatorId, // <= FOUND
890:         OperatorKickParam[] memory operatorKickParams, // <= FOUND
891:         bytes32 salt, // <= FOUND
892:         uint256 expiry // <= FOUND
893:     ) public view returns (bytes32) { // <= FOUND
894:         
895:         return _hashTypedDataV4(keccak256(abi.encode(OPERATOR_CHURN_APPROVAL_TYPEHASH, registeringOperator, registeringOperatorId, operatorKickParams, salt, expiry))); // <= FOUND
896:     }

['902']

902:     function pubkeyRegistrationMessageHash(address operator) public view returns (BN254.G1Point memory) { // <= FOUND
903:         return BN254.hashToG1( // <= FOUND
904:             _hashTypedDataV4( // <= FOUND
905:                 keccak256(abi.encode(PUBKEY_REGISTRATION_TYPEHASH, operator)) // <= FOUND
906:             )
907:         );
908:     }

['65']

65:     function registerOperatorToAVS( // <= FOUND
66:         address operator, // <= FOUND
67:         ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature // <= FOUND
68:     ) public virtual onlyRegistryCoordinator { // <= FOUND
69:         _avsDirectory.registerOperatorToAVS(operator, operatorSignature); // <= FOUND
70:     }

['66']

66:     function registerOperator( // <= FOUND
67:         address operator, // <= FOUND
68:         bytes32 operatorId, // <= FOUND
69:         bytes calldata quorumNumbers // <= FOUND
70:     ) public virtual onlyRegistryCoordinator returns (uint96[] memory, uint96[] memory) { // <= FOUND
71: 
72:         uint96[] memory currentStakes = new uint96[](quorumNumbers.length); // <= FOUND
73:         uint96[] memory totalStakes = new uint96[](quorumNumbers.length); // <= FOUND
74:         for (uint256 i = 0; i < quorumNumbers.length; i++) {             // <= FOUND
75:             
76:             uint8 quorumNumber = uint8(quorumNumbers[i]); // <= FOUND
77:             require(_quorumExists(quorumNumber), "StakeRegistry.registerOperator: quorum does not exist"); // <= FOUND
78: 
79:             
80:             
81:             (uint96 currentStake, bool hasMinimumStake) = _weightOfOperatorForQuorum(quorumNumber, operator); // <= FOUND
82:             require(
83:                 hasMinimumStake, // <= FOUND
84:                 "StakeRegistry.registerOperator: Operator does not meet minimum stake requirement for quorum" // <= FOUND
85:             );
86: 
87:             
88:             int256 stakeDelta = _recordOperatorStakeUpdate({ // <= FOUND
89:                 operatorId: operatorId,  // <= FOUND
90:                 quorumNumber: quorumNumber, // <= FOUND
91:                 newStake: currentStake // <= FOUND
92:             });
93: 
94:             
95:             currentStakes[i] = currentStake; // <= FOUND
96:             totalStakes[i] = _recordTotalStakeUpdate(quorumNumber, stakeDelta); // <= FOUND
97:         }
98: 
99:         return (currentStakes, totalStakes); // <= FOUND
100:     }

['114']

114:     function deregisterOperator( // <= FOUND
115:         bytes32 operatorId, // <= FOUND
116:         bytes calldata quorumNumbers // <= FOUND
117:     ) public virtual onlyRegistryCoordinator { // <= FOUND
118:         
119: 
122:         for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
123:             uint8 quorumNumber = uint8(quorumNumbers[i]); // <= FOUND
124:             require(_quorumExists(quorumNumber), "StakeRegistry.deregisterOperator: quorum does not exist"); // <= FOUND
125: 
126:             
127:             int256 stakeDelta = _recordOperatorStakeUpdate({ // <= FOUND
128:                 operatorId: operatorId,  // <= FOUND
129:                 quorumNumber: quorumNumber,  // <= FOUND
130:                 newStake: 0 // <= FOUND
131:             });
132: 
133:             
134:             _recordTotalStakeUpdate(quorumNumber, stakeDelta); // <= FOUND
135:         }
136:     }

['147']

147:     function updateOperatorStake( // <= FOUND
148:         address operator,  // <= FOUND
149:         bytes32 operatorId,  // <= FOUND
150:         bytes calldata quorumNumbers // <= FOUND
151:     ) external onlyRegistryCoordinator returns (uint192) { // <= FOUND
152:         uint192 quorumsToRemove; // <= FOUND
153: 
154:         
155: 
162:         for (uint256 i = 0; i < quorumNumbers.length; i++) { // <= FOUND
163:             uint8 quorumNumber = uint8(quorumNumbers[i]); // <= FOUND
164:             require(_quorumExists(quorumNumber), "StakeRegistry.updateOperatorStake: quorum does not exist"); // <= FOUND
165: 
166:             
167:             
168:             (uint96 stakeWeight, bool hasMinimumStake) = _weightOfOperatorForQuorum(quorumNumber, operator); // <= FOUND
169: 
170:             
171:             if (!hasMinimumStake) { // <= FOUND
172:                 stakeWeight = 0; // <= FOUND
173:                 quorumsToRemove = uint192(quorumsToRemove.setBit(quorumNumber)); // <= FOUND
174:             }
175: 
176:             
177:             
178:             int256 stakeDelta = _recordOperatorStakeUpdate({ // <= FOUND
179:                 operatorId: operatorId, // <= FOUND
180:                 quorumNumber: quorumNumber, // <= FOUND
181:                 newStake: stakeWeight // <= FOUND
182:             });
183: 
184:             
185:             _recordTotalStakeUpdate(quorumNumber, stakeDelta); // <= FOUND
186:         }
187: 
188:         return quorumsToRemove; // <= FOUND
189:     }

['192']

192:     function initializeQuorum( // <= FOUND
193:         uint8 quorumNumber, // <= FOUND
194:         uint96 minimumStake, // <= FOUND
195:         StrategyParams[] memory _strategyParams // <= FOUND
196:     ) public virtual onlyRegistryCoordinator { // <= FOUND
197:         require(!_quorumExists(quorumNumber), "StakeRegistry.initializeQuorum: quorum already exists"); // <= FOUND
198:         _addStrategyParams(quorumNumber, _strategyParams); // <= FOUND
199:         _setMinimumStakeForQuorum(quorumNumber, minimumStake); // <= FOUND
200: 
201:         _totalStakeHistory[quorumNumber].push(StakeUpdate({ // <= FOUND
202:             updateBlockNumber: uint32(block.number), // <= FOUND
203:             nextUpdateBlockNumber: 0, // <= FOUND
204:             stake: 0 // <= FOUND
205:         }));
206:     }

['208']

208:     function setMinimumStakeForQuorum( // <= FOUND
209:         uint8 quorumNumber,  // <= FOUND
210:         uint96 minimumStake // <= FOUND
211:     ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber) { // <= FOUND
212:         _setMinimumStakeForQuorum(quorumNumber, minimumStake); // <= FOUND
213:     }

['221']

221:     function addStrategies( // <= FOUND
222:         uint8 quorumNumber,  // <= FOUND
223:         StrategyParams[] memory _strategyParams // <= FOUND
224:     ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber) { // <= FOUND
225:         _addStrategyParams(quorumNumber, _strategyParams); // <= FOUND
226:     }

['233']

233:     function removeStrategies( // <= FOUND
234:         uint8 quorumNumber, // <= FOUND
235:         uint256[] memory indicesToRemove // <= FOUND
236:     ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber) { // <= FOUND
237:         uint256 toRemoveLength = indicesToRemove.length; // <= FOUND
238:         require(toRemoveLength > 0, "StakeRegistry.removeStrategies: no indices to remove provided"); // <= FOUND
239: 
240:         StrategyParams[] storage _strategyParams = strategyParams[quorumNumber]; // <= FOUND
241:         IStrategy[] storage _strategiesPerQuorum = strategiesPerQuorum[quorumNumber]; // <= FOUND
242: 
243:         for (uint256 i = 0; i < toRemoveLength; i++) {
244:             emit StrategyRemovedFromQuorum(quorumNumber, _strategyParams[indicesToRemove[i]].strategy); // <= FOUND
245:             emit StrategyMultiplierUpdated(quorumNumber, _strategyParams[indicesToRemove[i]].strategy, 0); // <= FOUND
246: 
247:             
248:             _strategyParams[indicesToRemove[i]] = _strategyParams[_strategyParams.length - 1]; // <= FOUND
249:             _strategyParams.pop(); // <= FOUND
250:             _strategiesPerQuorum[indicesToRemove[i]] = _strategiesPerQuorum[_strategiesPerQuorum.length - 1]; // <= FOUND
251:             _strategiesPerQuorum.pop(); // <= FOUND
252:         }
253:     }

['261']

261:     function modifyStrategyParams( // <= FOUND
262:         uint8 quorumNumber, // <= FOUND
263:         uint256[] calldata strategyIndices, // <= FOUND
264:         uint96[] calldata newMultipliers // <= FOUND
265:     ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber) { // <= FOUND
266:         uint256 numStrats = strategyIndices.length; // <= FOUND
267:         require(numStrats > 0, "StakeRegistry.modifyStrategyParams: no strategy indices provided"); // <= FOUND
268:         require(newMultipliers.length == numStrats, "StakeRegistry.modifyStrategyParams: input length mismatch"); // <= FOUND
269: 
270:         StrategyParams[] storage _strategyParams = strategyParams[quorumNumber]; // <= FOUND
271: 
272:         for (uint256 i = 0; i < numStrats; i++) { // <= FOUND
273:             
274:             _strategyParams[strategyIndices[i]].multiplier = newMultipliers[i]; // <= FOUND
275:             emit StrategyMultiplierUpdated(quorumNumber, _strategyParams[strategyIndices[i]].strategy, newMultipliers[i]); // <= FOUND
276:         }
277:     }

['506']

506:     function weightOfOperatorForQuorum( // <= FOUND
507:         uint8 quorumNumber,  // <= FOUND
508:         address operator // <= FOUND
509:     ) public virtual view quorumExists(quorumNumber) returns (uint96) { // <= FOUND
510:         (uint96 stake, ) = _weightOfOperatorForQuorum(quorumNumber, operator); // <= FOUND
511:         return stake; // <= FOUND
512:     }

['515']

515:     function strategyParamsLength(uint8 quorumNumber) public view returns (uint256) { // <= FOUND
516:         return strategyParams[quorumNumber].length; // <= FOUND
517:     }

['520']

520:     function strategyParamsByIndex( // <= FOUND
521:         uint8 quorumNumber,  // <= FOUND
522:         uint256 index // <= FOUND
523:     ) public view returns (StrategyParams memory) // <= FOUND
524:     {
525:         return strategyParams[quorumNumber][index]; // <= FOUND
526:     }

['535']

535:     function getStakeHistoryLength( // <= FOUND
536:         bytes32 operatorId, // <= FOUND
537:         uint8 quorumNumber // <= FOUND
538:     ) external view returns (uint256) { // <= FOUND
539:         return operatorStakeHistory[operatorId][quorumNumber].length; // <= FOUND
540:     }

['547']

547:     function getStakeHistory( // <= FOUND
548:         bytes32 operatorId,  // <= FOUND
549:         uint8 quorumNumber // <= FOUND
550:     ) external view returns (StakeUpdate[] memory) { // <= FOUND
551:         return operatorStakeHistory[operatorId][quorumNumber]; // <= FOUND
552:     }

['558']

558:     function getCurrentStake(bytes32 operatorId, uint8 quorumNumber) external view returns (uint96) { // <= FOUND
559:         StakeUpdate memory operatorStakeUpdate = getLatestStakeUpdate(operatorId, quorumNumber); // <= FOUND
560:         return operatorStakeUpdate.stake; // <= FOUND
561:     }

['567']

567:     function getLatestStakeUpdate( // <= FOUND
568:         bytes32 operatorId, // <= FOUND
569:         uint8 quorumNumber // <= FOUND
570:     ) public view returns (StakeUpdate memory) { // <= FOUND
571:         uint256 historyLength = operatorStakeHistory[operatorId][quorumNumber].length; // <= FOUND
572:         StakeUpdate memory operatorStakeUpdate; // <= FOUND
573:         if (historyLength == 0) { // <= FOUND
574:             return operatorStakeUpdate; // <= FOUND
575:         } else { // <= FOUND
576:             operatorStakeUpdate = operatorStakeHistory[operatorId][quorumNumber][historyLength - 1]; // <= FOUND
577:             return operatorStakeUpdate; // <= FOUND
578:         }
579:     }

['588']

588:     function getStakeUpdateAtIndex( // <= FOUND
589:         uint8 quorumNumber, // <= FOUND
590:         bytes32 operatorId, // <= FOUND
591:         uint256 index // <= FOUND
592:     ) external view returns (StakeUpdate memory) { // <= FOUND
593:         return operatorStakeHistory[operatorId][quorumNumber][index]; // <= FOUND
594:     }

['597']

597:     function getStakeAtBlockNumber( // <= FOUND
598:         bytes32 operatorId, // <= FOUND
599:         uint8 quorumNumber, // <= FOUND
600:         uint32 blockNumber // <= FOUND
601:     ) external view returns (uint96) { // <= FOUND
602:         return
603:             operatorStakeHistory[operatorId][quorumNumber][ // <= FOUND
604:                 _getStakeUpdateIndexForOperatorAtBlockNumber(operatorId, quorumNumber, blockNumber) // <= FOUND
605:             ].stake; // <= FOUND
606:     }

['609']

609:     function getStakeUpdateIndexAtBlockNumber( // <= FOUND
610:         bytes32 operatorId, // <= FOUND
611:         uint8 quorumNumber, // <= FOUND
612:         uint32 blockNumber // <= FOUND
613:     ) external view returns (uint32) { // <= FOUND
614:         return _getStakeUpdateIndexForOperatorAtBlockNumber(operatorId, quorumNumber, blockNumber); // <= FOUND
615:     }

['627']

627:     function getStakeAtBlockNumberAndIndex( // <= FOUND
628:         uint8 quorumNumber, // <= FOUND
629:         uint32 blockNumber, // <= FOUND
630:         bytes32 operatorId, // <= FOUND
631:         uint256 index // <= FOUND
632:     ) external view returns (uint96) { // <= FOUND
633:         StakeUpdate memory operatorStakeUpdate = operatorStakeHistory[operatorId][quorumNumber][index]; // <= FOUND
634:         _validateStakeUpdateAtBlockNumber(operatorStakeUpdate, blockNumber); // <= FOUND
635:         return operatorStakeUpdate.stake; // <= FOUND
636:     }

['645']

645:     function getTotalStakeHistoryLength(uint8 quorumNumber) external view returns (uint256) { // <= FOUND
646:         return _totalStakeHistory[quorumNumber].length; // <= FOUND
647:     }

['653']

653:     function getCurrentTotalStake(uint8 quorumNumber) external view returns (uint96) { // <= FOUND
654:         return _totalStakeHistory[quorumNumber][_totalStakeHistory[quorumNumber].length - 1].stake; // <= FOUND
655:     }

['662']

662:     function getTotalStakeUpdateAtIndex( // <= FOUND
663:         uint8 quorumNumber, // <= FOUND
664:         uint256 index // <= FOUND
665:     ) external view returns (StakeUpdate memory) { // <= FOUND
666:         return _totalStakeHistory[quorumNumber][index]; // <= FOUND
667:     }

['677']

677:     function getTotalStakeAtBlockNumberFromIndex( // <= FOUND
678:         uint8 quorumNumber, // <= FOUND
679:         uint32 blockNumber, // <= FOUND
680:         uint256 index // <= FOUND
681:     ) external view returns (uint96) { // <= FOUND
682:         StakeUpdate memory totalStakeUpdate = _totalStakeHistory[quorumNumber][index]; // <= FOUND
683:         _validateStakeUpdateAtBlockNumber(totalStakeUpdate, blockNumber); // <= FOUND
684:         return totalStakeUpdate.stake; // <= FOUND
685:     }

[NonCritical-98] Consider moving duplicated strings to constants

Num of instances: 1

Findings

Click to show findings

['50']

50: require(historyLength != 0, "IndexRegistry.registerOperator: quorum does not exist"); // <= FOUND

[NonCritical-99] Consider providing a ranged getter for array state variables

Resolution

Implementing a ranged getter for array state variables enhances data accessibility in smart contracts. The Solidity compiler auto-generates getters for individual elements of public arrays but lacks direct support for retrieving entire arrays or specific ranges. Adding a custom function to fetch array slices can significantly improve efficiency and flexibility, reducing the need for multiple calls to access several elements. This is particularly useful for contracts without multicall capabilities, streamlining data retrieval and minimizing gas costs associated with separate calls for each element.

Num of instances: 15

Findings

Click to show findings

['289']

289: uint256[49] private __GAP; // <= FOUND

['39']

39: uint256[45] private __GAP; // <= FOUND

['42']

42: uint256[47] private __GAP; // <= FOUND

[]

bytes32 public constant OPERATOR_CHURN_APPROVAL_TYPEHASH =         keccak256("OperatorChurnApproval(address registeringOperator,bytes32 registeringOperatorId,OperatorKickParam[] operatorKickParams,bytes32 salt,uint256 expiry)OperatorKickParam(uint8 quorumNumber,address operator)"); // <= FOUND

['61']

61: address[] public registries; // <= FOUND

['81']

81: uint256[41] private __GAP; // <= FOUND

['153']

153: uint256[50] private __GAP; // <= FOUND

['28']

28:     mapping(uint8 => ApkUpdate[]) public apkHistory; // <= FOUND

['29']

29:     mapping(uint8 => mapping(uint32 => OperatorUpdate[])) internal _operatorIndexHistory; // <= FOUND

['31']

31:     mapping(uint8 => QuorumUpdate[]) internal _operatorCountHistory; // <= FOUND

['52']

52:     mapping(bytes32 => QuorumBitmapUpdate[]) internal _operatorBitmapHistory; // <= FOUND

['35']

35:     mapping(uint8 => StakeUpdate[]) internal _totalStakeHistory; // <= FOUND

['38']

38:     mapping(bytes32 => mapping(uint8 => StakeUpdate[])) internal operatorStakeHistory; // <= FOUND

['44']

44:     mapping(uint8 => StrategyParams[]) public strategyParams; // <= FOUND

['45']

45:     mapping(uint8 => IStrategy[]) public strategiesPerQuorum; // <= FOUND

[NonCritical-100] Consider using named returns

Resolution

Using named returns in Solidity functions enhances code readability and clarity. By explicitly naming return variables in the function signature, developers can document what each return value represents, making the code easier to understand and maintain. This practice can also slightly optimize gas usage by avoiding extra variable declarations.

Num of instances: 75

Findings

Click to show findings

['25']

25:     function orderedBytesArrayToBitmap(bytes memory orderedBytesArray) internal pure returns (uint256)  // <= FOUND

['62']

62:     function orderedBytesArrayToBitmap(bytes memory orderedBytesArray, uint8 bitUpperBound) internal pure returns (uint256)  // <= FOUND

['79']

79:     function isArrayStrictlyAscendingOrdered(bytes calldata bytesArray) internal pure returns (bool)  // <= FOUND

['139']

139:     function countNumOnes(uint256 n) internal pure returns (uint16)  // <= FOUND

['149']

149:     function isSet(uint256 bitmap, uint8 bit) internal pure returns (bool)  // <= FOUND

['159']

159:     function setBit(uint256 bitmap, uint8 bit) internal pure returns (uint256)  // <= FOUND

['166']

166:     function isEmpty(uint256 bitmap) internal pure returns (bool)  // <= FOUND

['173']

173:     function noBitsInCommon(uint256 a, uint256 b) internal pure returns (bool)  // <= FOUND

['180']

180:     function isSubsetOf(uint256 a, uint256 b) internal pure returns (bool)  // <= FOUND

['188']

188:     function plus(uint256 a, uint256 b) internal pure returns (uint256)  // <= FOUND

['196']

196:     function minus(uint256 a, uint256 b) internal pure returns (uint256)  // <= FOUND

['206']

203:     function getApkIndicesAtBlockNumber(
204:         bytes calldata quorumNumbers,
205:         uint256 blockNumber
206:     ) external view returns (uint32[] memory)  // <= FOUND

['234']

234:     function getApkUpdateAtIndex(uint8 quorumNumber, uint256 index) external view returns (ApkUpdate memory)  // <= FOUND

['249']

245:     function getApkHashAtBlockNumberAndIndex(
246:         uint8 quorumNumber,
247:         uint32 blockNumber,
248:         uint256 index
249:     ) external view returns (bytes24)  // <= FOUND

['270']

270:     function getApkHistoryLength(uint8 quorumNumber) external view returns (uint32)  // <= FOUND

['275']

275:     function getOperatorFromPubkeyHash(bytes32 pubkeyHash) public view returns (address)  // <= FOUND

['281']

281:     function getOperatorId(address operator) public view returns (bytes32)  // <= FOUND

['65']

65:     function generatorG2() internal pure returns (G2Point memory)  // <= FOUND

['87']

87:     function negate(G1Point memory p) internal pure returns (G1Point memory)  // <= FOUND

['126']

126:     function scalar_mul_tiny(BN254.G1Point memory p, uint16 s) internal view returns (BN254.G1Point memory)  // <= FOUND

['197']

192:     function pairing(
193:         G1Point memory a1,
194:         G2Point memory a2,
195:         G1Point memory b1,
196:         G2Point memory b2
197:     ) internal view returns (bool)  // <= FOUND

['241']

235:     function safePairing(
236:         G1Point memory a1,
237:         G2Point memory a2,
238:         G1Point memory b1,
239:         G2Point memory b2,
240:         uint256 pairingGas
241:     ) internal view returns (bool, bool)  // <= FOUND

['285']

283:     function hashG2Point(
284:         BN254.G2Point memory pk
285:     ) internal pure returns (bytes32)  // <= FOUND

['318']

318:     function findYFromX(uint256 x) internal view returns (uint256, uint256)  // <= FOUND

['131']

131:     function _increaseOperatorCount(uint8 quorumNumber) internal returns (uint32)  // <= FOUND

['152']

152:     function _decreaseOperatorCount(uint8 quorumNumber) internal returns (uint32)  // <= FOUND

['186']

186:     function _popLastOperator(uint8 quorumNumber, uint32 operatorIndex) internal returns (bytes32)  // <= FOUND

['242']

242:     function _latestOperatorIndexUpdate(uint8 quorumNumber, uint32 operatorIndex) internal view returns (OperatorUpdate storage)  // <= FOUND

['254']

251:     function _operatorCountAtBlockNumber(
252:         uint8 quorumNumber, 
253:         uint32 blockNumber
254:     ) internal view returns (uint32) // <= FOUND

['305']

305:     function getQuorumUpdateAtIndex(uint8 quorumNumber, uint32 quorumIndex) external view returns (QuorumUpdate memory)  // <= FOUND

['317']

317:     function getLatestOperatorUpdate(uint8 quorumNumber, uint32 operatorIndex) external view returns (OperatorUpdate memory)  // <= FOUND

['86']

86:     function totalOperatorsForQuorum(uint8 quorumNumber) external view returns (uint32) // <= FOUND

['505']

502:     function _getOrCreateOperatorId(
503:         address operator,
504:         IBLSApkRegistry.PubkeyRegistrationParams calldata params
505:     ) internal returns (bytes32 operatorId)  // <= FOUND

['632']

632:     function _individualKickThreshold(uint96 operatorStake, OperatorSetParam memory setParams) internal pure returns (uint96)  // <= FOUND

['640']

640:     function _totalKickThreshold(uint96 totalStake, OperatorSetParam memory setParams) internal pure returns (uint96)  // <= FOUND

['732']

732:     function _currentOperatorBitmap(bytes32 operatorId) internal view returns (uint192)  // <= FOUND

['792']

792:     function getOperator(address operator) external view returns (OperatorInfo memory)  // <= FOUND

['797']

797:     function getOperatorId(address operator) external view returns (bytes32)  // <= FOUND

['802']

802:     function getOperatorFromId(bytes32 operatorId) external view returns (address)  // <= FOUND

['807']

807:     function getOperatorStatus(address operator) external view returns (IRegistryCoordinator.OperatorStatus)  // <= FOUND

['837']

833:     function getQuorumBitmapAtBlockNumberByIndex(
834:         bytes32 operatorId, 
835:         uint32 blockNumber, 
836:         uint256 index
837:     ) external view returns (uint192)  // <= FOUND

['866']

866:     function getCurrentQuorumBitmap(bytes32 operatorId) external view returns (uint192)  // <= FOUND

['871']

871:     function getQuorumBitmapHistoryLength(bytes32 operatorId) external view returns (uint256)  // <= FOUND

['876']

876:     function numRegistries() external view returns (uint256)  // <= FOUND

['893']

887:     function calculateOperatorChurnApprovalDigestHash(
888:         address registeringOperator,
889:         bytes32 registeringOperatorId,
890:         OperatorKickParam[] memory operatorKickParams,
891:         bytes32 salt,
892:         uint256 expiry
893:     ) public view returns (bytes32)  // <= FOUND

['915']

911:     function owner()
912:         public
913:         view
914:         override(OwnableUpgradeable, IRegistryCoordinator)
915:         returns (address) // <= FOUND
916:     

['117']

117:     function getOperatorRestakedStrategies(address operator) external view returns (address[] memory)  // <= FOUND

['147']

147:     function avsDirectory() external view override returns (address)  // <= FOUND

['151']

147:     function updateOperatorStake(
148:         address operator, 
149:         bytes32 operatorId, 
150:         bytes calldata quorumNumbers
151:     ) external onlyRegistryCoordinator returns (uint192)  // <= FOUND

['287']

283:     function _getStakeUpdateIndexForOperatorAtBlockNumber(
284:         bytes32 operatorId,
285:         uint8 quorumNumber,
286:         uint32 blockNumber
287:     ) internal view returns (uint32)  // <= FOUND

['316']

312:     function _recordOperatorStakeUpdate(
313:         bytes32 operatorId,
314:         uint8 quorumNumber,
315:         uint96 newStake
316:     ) internal returns (int256)  // <= FOUND

['361']

361:     function _recordTotalStakeUpdate(uint8 quorumNumber, int256 stakeDelta) internal returns (uint96)  // <= FOUND

['433']

433:     function _calculateDelta(uint96 prev, uint96 cur) internal pure returns (int256)  // <= FOUND

['438']

438:     function _applyDelta(uint96 value, int256 delta) internal pure returns (uint96)  // <= FOUND

['472']

472:     function _weightOfOperatorForQuorum(uint8 quorumNumber, address operator) internal virtual view returns (uint96, bool)  // <= FOUND

['494']

494:     function _quorumExists(uint8 quorumNumber) internal view returns (bool)  // <= FOUND

['509']

506:     function weightOfOperatorForQuorum(
507:         uint8 quorumNumber, 
508:         address operator
509:     ) public virtual view quorumExists(quorumNumber) returns (uint96)  // <= FOUND

['515']

515:     function strategyParamsLength(uint8 quorumNumber) public view returns (uint256)  // <= FOUND

['538']

535:     function getStakeHistoryLength(
536:         bytes32 operatorId,
537:         uint8 quorumNumber
538:     ) external view returns (uint256)  // <= FOUND

['558']

558:     function getCurrentStake(bytes32 operatorId, uint8 quorumNumber) external view returns (uint96)  // <= FOUND

['592']

588:     function getStakeUpdateAtIndex(
589:         uint8 quorumNumber,
590:         bytes32 operatorId,
591:         uint256 index
592:     ) external view returns (StakeUpdate memory)  // <= FOUND

['601']

597:     function getStakeAtBlockNumber(
598:         bytes32 operatorId,
599:         uint8 quorumNumber,
600:         uint32 blockNumber
601:     ) external view returns (uint96)  // <= FOUND

['613']

609:     function getStakeUpdateIndexAtBlockNumber(
610:         bytes32 operatorId,
611:         uint8 quorumNumber,
612:         uint32 blockNumber
613:     ) external view returns (uint32)  // <= FOUND

['632']

627:     function getStakeAtBlockNumberAndIndex(
628:         uint8 quorumNumber,
629:         uint32 blockNumber,
630:         bytes32 operatorId,
631:         uint256 index
632:     ) external view returns (uint96)  // <= FOUND

['[645](https://github.c

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment