Skip to content

Instantly share code, notes, and snippets.

@ChaseTheLight01
Created February 28, 2024 19:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • 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[