Issue | Instances | |
---|---|---|
[M‑01] | The owner is a single point of failure and a centralization risk |
34 |
[M‑02] | Unsafe use of transfer() /transferFrom() with IERC20 |
1 |
Total: 35 instances over 2 issues
Issue | Instances | |
---|---|---|
[L‑01] | Return values of transfer() /transferFrom() not checked |
1 |
[L‑02] | Don't use payable.transfer() /payable.send() |
1 |
[L‑03] | Division by zero not prevented | 1 |
[L‑04] | Missing checks for address(0x0) when assigning values to address state variables |
9 |
[L‑05] | Solidity version 0.8.20 may not work on other chains due to PUSH0 |
23 |
[L‑06] | Unsafe downcast | 19 |
[L‑07] | Loss of precision | 6 |
[L‑08] | Array lengths not checked | 1 |
[L‑09] | Owner can renounce while system is paused | 3 |
[L‑10] | require() should be used instead of assert() |
3 |
[L‑11] | External calls in an un-bounded for- loop may result in a DOS |
6 |
Total: 73 instances over 11 issues
Issue | Instances | |
---|---|---|
[N‑01] | Use OpenZeppelin's MerkleProof rather than rolling your own |
1 |
[N‑02] | Events are missing sender information | 5 |
[N‑03] | Variables need not be initialized to zero | 67 |
[N‑04] | Consider using named mappings | 18 |
[N‑05] | Non-external /public variable and function names should begin with an underscore |
88 |
[N‑06] | Use abi.encodeCall() instead of abi.encodeSignature() /abi.encodeSelector() |
2 |
[N‑07] | Constants in comparisons should appear on the left side | 44 |
[N‑08] | Custom error has no error details | 58 |
[N‑09] | Events may be emitted out of order due to reentrancy | 4 |
[N‑10] | Imports could be organized more systematically | 4 |
[N‑11] | Long functions should be refactored into multiple, smaller, functions | 2 |
[N‑12] | Mixed usage of int /uint with int256 /uint256 |
1 |
[N‑13] | Unsafe conversion from unsigned to signed values | 1 |
[N‑14] | Adding a return statement when the function defines a named return variable, is redundant |
1 |
[N‑15] | public functions not called by the contract should be declared external instead |
11 |
[N‑16] | 2**<n> - 1 should be re-written as type(uint<n>).max |
1 |
[N‑17] | constant s should be defined rather than using magic numbers |
46 |
[N‑18] | Event is not properly indexed |
17 |
[N‑19] | require() /revert() statements should have descriptive reason strings |
1 |
[N‑20] | Non-assembly method available | 1 |
[N‑21] | Missing event and or timelock for critical parameter change | 2 |
[N‑22] | Events that mark critical parameter changes should contain both the old and the new value | 3 |
[N‑23] | Use a more recent version of solidity | 1 |
[N‑24] | Constant redefined elsewhere | 4 |
[N‑25] | Lines are too long | 12 |
[N‑26] | Variable names that consist of all capital letters should be reserved for constant /immutable variables |
3 |
[N‑27] | Non-library/interface files should use fixed compiler versions, not floating ones | 1 |
[N‑28] | Typos | 1 |
[N‑29] | Constructor visibility is ignored | 1 |
[N‑30] | File is missing NatSpec | 5 |
[N‑31] | NatSpec @param is missing |
76 |
[N‑32] | NatSpec @return argument is missing |
29 |
[N‑33] | Function ordering does not follow the Solidity style guide | 43 |
[N‑34] | Contract does not follow the Solidity style guide's suggested layout ordering | 21 |
[N‑35] | Control structures do not follow the Solidity Style Guide | 9 |
[N‑36] | Expressions for constant values such as a call to keccak256() , should use immutable rather than constant |
3 |
[N‑37] | Consider using delete rather than assigning zero/false to clear values |
5 |
[N‑38] | Contracts should have full test coverage | 1 |
[N‑39] | Large or complicated code bases should implement invariant tests | 1 |
Total: 594 instances over 39 issues
Issue | Instances | Total Gas Saved | |
---|---|---|---|
[G‑01] | Reduce gas usage by moving to Solidity 0.8.19 or later | 23 | - |
[G‑02] | State variables only set in the constructor should be declared immutable |
2 | 4194 |
[G‑03] | Structs can be packed into fewer storage slots | 2 | - |
[G‑04] | Using storage instead of memory for structs/arrays saves gas |
19 | 79800 |
[G‑05] | <x> += <y> costs more gas than <x> = <x> + <y> for state variables |
2 | 226 |
[G‑06] | internal functions only called once can be inlined to save gas |
9 | 180 |
[G‑07] | Add unchecked {} for subtractions where the operands cannot underflow because of a previous require() or if -statement |
1 | 85 |
[G‑08] | <array>.length should not be looked up in every loop of a for -loop |
41 | 123 |
[G‑09] | ++i /i++ should be unchecked{++i} /unchecked{i++} when it is not possible for them to overflow, as is the case when used in for - and while -loops |
60 | 3600 |
[G‑10] | Optimize names to save gas | 20 | 440 |
[G‑11] | Using bool s for storage incurs overhead |
3 | 51300 |
[G‑12] | Use a more recent version of solidity | 23 | - |
[G‑13] | ++i costs less gas than i++ , especially when it's used in for -loops (--i /i-- too) |
2 | 10 |
[G‑14] | Usage of uints /ints smaller than 32 bytes (256 bits) incurs overhead |
20 | - |
[G‑15] | Using private rather than public for constants, saves gas |
2 | - |
[G‑16] | Inverting the condition of an if -else -statement wastes gas |
1 | - |
[G‑17] | Division by two should use bit shifting | 2 | 40 |
[G‑18] | Stack variable used as a cheaper cache for a state variable is only used once | 2 | 6 |
[G‑19] | Empty blocks should be removed or emit something | 1 | - |
[G‑20] | Superfluous event fields | 4 | - |
[G‑21] | Functions guaranteed to revert when called by normal users can be marked payable |
44 | 924 |
[G‑22] | Constructors can be marked payable |
15 | 315 |
[G‑23] | Not using the named return variables anywhere in the function is confusing | 24 | - |
Total: 322 instances over 23 issues with 141243 gas saved
Gas totals use lower bounds of ranges and count two iterations of each for
-loop. All values above are runtime, not deployment, values; deployment values are listed in the individual issue descriptions. The table above as well as its gas numbers do not include any of the excluded findings.
Having a single EOA as the only owner of contracts is a large centralization risk and a single point of failure. A single private key may be taken in a hack, or the sole holder of the key may become unable to retrieve the key when necessary. Consider changing to a multi-signature setup, or having a role-based authorization model.
There are 34 instances of this issue:
File: contracts/ARM.sol
256: function ownerResetBlessVotes(IARM.TaggedRoot[] memory taggedRoots) external onlyOwner {
330: function ownerCurse() external onlyOwner {
341: function ownerUnvoteToCurse(UnvoteToCurseRecord[] memory unvoteRecords) external onlyOwner {
386: function setConfig(Config memory config) external onlyOwner {
File: contracts/CommitStore.sol
96: function setMinSeqNr(uint64 minSeqNr) external onlyOwner {
120: function resetUnblessedRoots(bytes32[] calldata rootToReset) external onlyOwner {
226: function pause() external onlyOwner {
233: function unpause() external onlyOwner {
File: contracts/PriceRegistry.sol
167 function applyFeeTokensUpdates(
168 address[] memory feeTokensToAdd,
169 address[] memory feeTokensToRemove
170: ) external onlyOwner {
241 function applyPriceUpdatersUpdates(
242 address[] memory priceUpdatersToAdd,
243 address[] memory priceUpdatersToRemove
244: ) external onlyOwner {
File: contracts/Router.sol
224: function setWrappedNative(address wrappedNative) external onlyOwner {
254 function applyRampUpdates(
255 OnRamp[] calldata onRampUpdates,
256 OffRamp[] calldata offRampRemoves,
257 OffRamp[] calldata offRampAdds
258: ) external onlyOwner {
285: function recoverTokens(address tokenAddress, address to, uint256 amount) external onlyOwner {
File: contracts/ocr/OCR2Base.sol
102 function setOCR2Config(
103 address[] memory signers,
104 address[] memory transmitters,
105 uint8 f,
106 bytes memory onchainConfig,
107 uint64 offchainConfigVersion,
108 bytes memory offchainConfig
109: ) external override checkConfigValid(signers.length, transmitters.length, f) onlyOwner {
File: contracts/ocr/OCR2BaseNoChecks.sol
89 function setOCR2Config(
90 address[] memory signers,
91 address[] memory transmitters,
92 uint8 f,
93 bytes memory onchainConfig,
94 uint64 offchainConfigVersion,
95 bytes memory offchainConfig
96: ) external override checkConfigValid(f) onlyOwner {
File: contracts/offRamp/EVM2EVMOffRamp.sol
510 function applyPoolUpdates(
511 Internal.PoolUpdate[] calldata removes,
512 Internal.PoolUpdate[] calldata adds
513: ) external onlyOwner {
File: contracts/onRamp/EVM2EVMOnRamp.sol
386: function setDynamicConfig(DynamicConfig memory dynamicConfig) external onlyOwner {
431 function applyPoolUpdates(
432 Internal.PoolUpdate[] calldata removes,
433 Internal.PoolUpdate[] calldata adds
434: ) external onlyOwner {
542: function setFeeTokenConfig(FeeTokenConfigArgs[] memory feeTokenConfigs) external onlyOwnerOrAdmin {
568 function setTokenTransferFeeConfig(
569 TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs
570: ) external onlyOwnerOrAdmin {
614: function setNops(NopAndWeight[] calldata nopsAndWeights) external onlyOwner {
651: function payNops() public onlyOwnerOrAdminOrNop {
677: function withdrawNonLinkFees(address feeToken, address to) external onlyOwner {
716: function setAllowListEnabled(bool enabled) external onlyOwner {
736: function applyAllowListUpdates(address[] calldata removes, address[] calldata adds) external onlyOwner {
801: function pause() external onlyOwner {
808: function unpause() external onlyOwner {
File: contracts/pools/ThirdPartyBurnMintTokenPool.sol
34: function applyRampUpdates(RampUpdate[] memory onRamps, RampUpdate[] memory offRamps) public override onlyOwner {
File: contracts/pools/TokenPool.sol
88: function applyRampUpdates(RampUpdate[] memory onRamps, RampUpdate[] memory offRamps) public virtual onlyOwner {
124: function setRateLimiterConfig(RateLimiter.Config memory config) public onlyOwner {
147: function pause() external onlyOwner {
152: function unpause() external onlyOwner {
File: contracts/pools/USDC/USDCTokenPool.sol
138: function setConfig(USDCConfig memory config) external onlyOwner {
155: function setDomains(DomainUpdate[] calldata domains) external onlyOwner {
Some tokens do not implement the ERC20 standard properly but are still accepted by most code that accepts ERC20 tokens. For example Tether (USDT)'s transfer()
and transferFrom()
functions on L1 do not return booleans as the specification requires, and instead have no return value. When these sorts of tokens are cast to IERC20
, their function signatures do not match and therefore the calls made, revert (see this link for a test case). Use OpenZeppelin’s SafeERC20
's safeTransfer()
/safeTransferFrom()
instead
There is one instance of this issue:
File: contracts/Router.sol
290: IERC20(tokenAddress).transfer(to, amount);
Not all IERC20
implementations revert()
when there's a failure in transfer()
/transferFrom()
. The function signature has a boolean
return value and they indicate errors that way instead. By not checking the return value, operations that should have marked as failed, may potentially go through without actually making a payment
There is one instance of this issue:
File: contracts/Router.sol
290: IERC20(tokenAddress).transfer(to, amount);
The use of payable.transfer()
is heavily frowned upon because it can lead to the locking of funds. The transfer()
call requires that the recipient is either an EOA account, or is a contract that has a payable
callback. For the contract case, the transfer()
call only provides 2300 gas for the contract to complete its operations. This means the following cases can cause the transfer to fail:
- The contract does not have a
payable
callback - The contract's
payable
callback spends more than 2300 gas (which is only enough to emit something) - The contract is called through a proxy which itself uses up the 2300 gas
Use OpenZeppelin's Address.sendValue()
instead
There is one instance of this issue:
File: contracts/Router.sol
287: payable(to).transfer(amount);
The divisions below take an input parameter which does not have any zero-value checks, which may lead to the functions reverting when zero is passed.
There is one instance of this issue:
File: contracts/libraries/USDPriceWith18Decimals.sol
43: return (usdValue * 1e18) / tokenPrice;
There are 9 instances of this issue:
File: contracts/ARM.sol
426: blessVoteAddrs[j] = voters[i].blessVoteAddr;
463: curseVoteAddrs[j] = curseVoteAddr;
File: contracts/AggregateRateLimiter.sol
83: s_admin = newAdmin;
File: contracts/Router.sol
59: s_wrappedNative = wrappedNative;
225: s_wrappedNative = wrappedNative;
263: s_onRamps[onRampUpdate.destChainSelector] = onRampUpdate.onRamp;
File: contracts/offRamp/EVM2EVMOffRamp.sol
466: sourceTokens[i] = IERC20(token);
503: destTokens[i] = IERC20(token);
File: contracts/pools/ThirdPartyBurnMintTokenPool.sol
27: s_router = router;
The compiler for Solidity 0.8.20 switches the default target EVM version to Shanghai, which includes the new PUSH0
op code. This op code may not yet be implemented on all L2s, so deployment on these chains will fail. To work around this issue, use an earlier EVM version
There are 23 instances of this issue:
File: contracts/OwnerIsCreator.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IARM.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IAny2EVMMessageReceiver.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IAny2EVMOffRamp.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/ICommitStore.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IERC677.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IERC677Receiver.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IEVM2AnyOnRamp.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IPriceRegistry.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IRouter.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IRouterClient.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/ITypeAndVersion.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IWrappedNative.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/automation/ILinkAvailable.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/pools/IPool.sol
2: pragma solidity ^0.8.0;
File: contracts/libraries/Client.sol
2: pragma solidity ^0.8.0;
File: contracts/libraries/Internal.sol
2: pragma solidity ^0.8.0;
File: contracts/libraries/MerkleMultiProof.sol
2: pragma solidity ^0.8.0;
File: contracts/libraries/RateLimiter.sol
2: pragma solidity ^0.8.0;
File: contracts/libraries/USDPriceWith18Decimals.sol
2: pragma solidity ^0.8.0;
File: contracts/ocr/OCR2Abstract.sol
2: pragma solidity ^0.8.0;
File: contracts/ocr/OCR2Base.sol
2: pragma solidity ^0.8.0;
File: contracts/ocr/OCR2BaseNoChecks.sol
2: pragma solidity ^0.8.0;
When a type is downcast to a smaller type, the higher order bits are truncated, effectively applying a modulo to the original value. Without any other checks, this wrapping will lead to unexpected behavior and bugs
There are 19 instances of this issue:
File: contracts/AggregateRateLimiter.sol
/// @audit uint32
34: lastUpdated: uint32(block.timestamp),
File: contracts/ARM.sol
/// @audit uint32
551: s_versionedConfig.blockNumber = uint32(block.number);
File: contracts/libraries/RateLimiter.sol
/// @audit uint32
57: s_bucket.lastUpdated = uint32(block.timestamp);
/// @audit uint256 tokens -> uint128
70: s_bucket.tokens = uint128(tokens);
/// @audit uint32
83: bucket.lastUpdated = uint32(block.timestamp);
/// @audit uint32
97: s_bucket.lastUpdated = uint32(block.timestamp);
File: contracts/ocr/OCR2BaseNoChecks.sol
/// @audit uint32
127: s_latestConfigBlockNumber = uint32(block.number);
/// @audit uint32
176: emit Transmitted(configDigest, uint32(uint256(reportContext[1]) >> 8));
File: contracts/ocr/OCR2Base.sol
/// @audit uint32
147: s_latestConfigBlockNumber = uint32(block.number);
/// @audit uint32
198: emit Transmitted(configDigest, uint32(uint256(reportContext[1]) >> 8));
File: contracts/offRamp/EVM2EVMOffRamp.sol
/// @audit uint256 senderNonce -> uint64
198: return uint64(senderNonce);
File: contracts/onRamp/EVM2EVMOnRamp.sol
/// @audit uint256 senderNonce -> uint64
257: return uint64(senderNonce);
/// @audit uint256 feeTokenAmount -> uint96
280: s_nopFeesJuels += uint96(feeTokenAmount);
/// @audit uint96
283 s_nopFeesJuels += uint96(
284 IPriceRegistry(s_dynamicConfig.priceRegistry).convertTokenAmount(message.feeToken, feeTokenAmount, i_linkToken)
285: );
/// @audit uint96
664: uint96 amount = uint96((totalFeesToPay * weight) / weightsTotal);
/// @audit int256
696: return int256(IERC20(i_linkToken).balanceOf(address(this))) - int256(uint256(s_nopFeesJuels));
File: contracts/pools/TokenPool.sol
/// @audit uint32
51: lastUpdated: uint32(block.timestamp),
File: contracts/PriceRegistry.sol
/// @audit uint64
209: timestamp: uint64(block.timestamp)
/// @audit uint64
217: timestamp: uint64(block.timestamp)
Division by large numbers may result in the result being zero, due to solidity not supporting fractions. Consider requiring a minimum amount for the numerator to ensure that it is always larger than the denominator
There are 6 instances of this issue:
File: contracts/libraries/RateLimiter.sol
65: revert RateLimitReached(((requestTokens - tokens) + (rate - 1)) / rate);
File: contracts/libraries/USDPriceWith18Decimals.sol
43: return (usdValue * 1e18) / tokenPrice;
File: contracts/onRamp/EVM2EVMOnRamp.sol
475 (((_fromBytes(message.extraArgs).gasLimit + feeTokenConfig.destGasOverhead) * feeTokenConfig.multiplier) /
476: 1 ether) +
514: bpsValue = (tokenValue * transferFeeConfig.ratio) / 1e5;
664: uint96 amount = uint96((totalFeesToPay * weight) / weightsTotal);
File: contracts/PriceRegistry.sol
135: return (fromTokenAmount * _getValidatedTokenPrice(fromToken)) / _getValidatedTokenPrice(toToken);
If the length of the arrays are not required to be of the same length, user operations may not be fully executed due to a mismatch in the number of items iterated over, versus the number of items provided in the second array
There is one instance of this issue:
File: contracts/CommitStore.sol
131 function verify(
132 bytes32[] calldata hashedLeaves,
133 bytes32[] calldata proofs,
134 uint256 proofFlagBits
135: ) external view override whenNotPaused returns (uint256 timestamp) {
The contract owner or single user with a role is not prevented from renouncing the role/ownership while the contract is paused, which would cause any user assets stored in the protocol, to be locked indefinitely
There are 3 instances of this issue:
File: contracts/CommitStore.sol
226 function pause() external onlyOwner {
227 s_paused = true;
228 emit Paused(msg.sender);
229: }
File: contracts/onRamp/EVM2EVMOnRamp.sol
801 function pause() external onlyOwner {
802 s_paused = true;
803 emit Paused(msg.sender);
804: }
File: contracts/pools/TokenPool.sol
147 function pause() external onlyOwner {
148 _pause();
149: }
Prior to solidity version 0.8.0, hitting an assert consumes the remainder of the transaction's available gas rather than returning it, as require()
/revert()
do. assert()
should be avoided even past solidity version 0.8.0 as its documentation states that "The assert function creates an error of type Panic(uint256). ... Properly functioning code should never create a Panic, not even on invalid external input. If this happens, then there is a bug in your contract which you should fix".
There are 3 instances of this issue:
File: contracts/ARM.sol
170: assert(vp.voterBitmap >> (MAX_NUM_VOTERS - 1) >= 1);
176: assert(index < MAX_NUM_VOTERS);
181: assert(index < MAX_NUM_VOTERS);
Consider limiting the number of iterations in for-
loops that make external calls
There are 6 instances of this issue:
File: contracts/offRamp/EVM2EVMOffRamp.sol
/// @audit getSenderNonce()
242: for (uint256 i = 0; i < numMsgs; ++i) {
File: contracts/onRamp/EVM2EVMOnRamp.sol
/// @audit lockOrBurn()
313: for (uint256 i = 0; i < message.tokenAmounts.length; ++i) {
/// @audit getValidatedTokenPrice()
499: for (uint256 i = 0; i < numerOfTokens; ++i) {
/// @audit safeTransfer()
661: for (uint256 i = 0; i < numberOfNops; ++i) {
File: contracts/pools/ThirdPartyBurnMintTokenPool.sol
/// @audit isOffRamp()
44: for (uint256 i = 0; i < offRamps.length; ++i) {
File: contracts/Router.sol
/// @audit getPoolBySourceToken()
121: for (uint256 i = 0; i < message.tokenAmounts.length; ++i) {
Use MerkleProof for handling Merkle tree proofs, since the general advice is "don't roll your own crypto"
There is one instance of this issue:
File: contracts/libraries/MerkleMultiProof.sol
48: bytes32[] memory leaves,
When an action is triggered based on a user's action, not being able to filter based on who triggered the action makes event processing a lot more cumbersome. Including the msg.sender
the events of these types of action will make events much more useful to end users, especially when msg.sender
is not tx.origin
.
There are 5 instances of this issue:
File: contracts/ARM.sol
246: emit TaggedRootBlessed(configVersion, taggedRoot, voteProgress.accumulatedWeight);
283 emit UnvotedToCurse(
284 s_versionedConfig.configVersion,
285 curseVoteAddr,
286 curserRecord.weight,
287 curserRecord.voteCount,
288 cursesHash
289: );
323: emit Cursed(configVersion, block.timestamp);
File: contracts/ocr/OCR2Base.sol
198: emit Transmitted(configDigest, uint32(uint256(reportContext[1]) >> 8));
File: contracts/ocr/OCR2BaseNoChecks.sol
176: emit Transmitted(configDigest, uint32(uint256(reportContext[1]) >> 8));
The default value for variables is zero, so initializing them to zero is superfluous.
There are 67 instances of this issue:
File: contracts/ARM.sol
208: for (uint256 i = 0; i < taggedRoots.length; ++i) {
257: for (uint256 i = 0; i < taggedRoots.length; ++i) {
342: for (uint256 i = 0; i < unvoteRecords.length; ++i) {
423: uint256 j = 0;
424: for (uint256 i = 0; i < voters.length; ++i) {
449: for (uint256 i = 0; i < voters.length; ++i) {
458: uint256 j = 0;
459: for (uint256 i = 0; i < voters.length; ++i) {
481: uint256 totalBlessWeight = 0;
482: uint256 totalCurseWeight = 0;
484: for (uint256 i = 0; i < config.voters.length; ++i) {
500: for (uint256 i = 0; i < allAddrs.length; ++i) {
528: for (uint256 i = 0; i < config.voters.length; ++i) {
536: for (uint8 i = 0; i < config.voters.length; ++i) {
561: for (uint8 i = 0; i < oldConfig.voters.length; ++i) {
File: contracts/AggregateRateLimiter.sol
44: uint256 value = 0;
45: for (uint256 i = 0; i < numberOfTokens; ++i) {
File: contracts/CommitStore.sol
121: for (uint256 i = 0; i < rootToReset.length; ++i) {
File: contracts/PriceRegistry.sol
87: for (uint256 i = 0; i < length; ++i) {
158: for (uint256 i = 0; i < s_feeTokens.length(); ++i) {
179: for (uint256 i = 0; i < feeTokensToAdd.length; ++i) {
184: for (uint256 i = 0; i < feeTokensToRemove.length; ++i) {
205: for (uint256 i = 0; i < priceUpdatesLength; ++i) {
231: for (uint256 i = 0; i < s_priceUpdaters.length(); ++i) {
257: for (uint256 i = 0; i < priceUpdatersToAdd.length; ++i) {
262: for (uint256 i = 0; i < priceUpdatersToRemove.length; ++i) {
File: contracts/Router.sol
121: for (uint256 i = 0; i < message.tokenAmounts.length; ++i) {
238: for (uint256 i = 0; i < offRamps.length; ++i) {
261: for (uint256 i = 0; i < onRampUpdates.length; ++i) {
268: for (uint256 i = 0; i < offRampRemoves.length; ++i) {
273: for (uint256 i = 0; i < offRampAdds.length; ++i) {
File: contracts/libraries/MerkleMultiProof.sol
6: bytes32 internal constant LEAF_DOMAIN_SEPARATOR = 0x0000000000000000000000000000000000000000000000000000000000000000;
67: for (uint256 i = 0; i < totalHashes; ++i) {
File: contracts/ocr/OCR2Base.sol
112: for (uint256 i = 0; i < oldSignerLength; ++i) {
118: for (uint256 i = 0; i < newSignersLength; ++i) {
226: for (uint256 i = 0; i < numberOfSignatures; ++i) {
File: contracts/ocr/OCR2BaseNoChecks.sol
99: for (uint256 i = 0; i < oldTransmitterLength; ++i) {
104: for (uint256 i = 0; i < newTransmitterLength; ++i) {
File: contracts/offRamp/EVM2EVMOffRamp.sol
146: for (uint256 i = 0; i < sourceTokens.length; ++i) {
225: for (uint256 i = 0; i < numMsgs; ++i) {
242: for (uint256 i = 0; i < numMsgs; ++i) {
464: for (uint256 i = 0; i < sourceTokens.length; ++i) {
501: for (uint256 i = 0; i < destTokens.length; ++i) {
514: for (uint256 i = 0; i < removes.length; ++i) {
529: for (uint256 i = 0; i < adds.length; ++i) {
555: for (uint256 i = 0; i < sourceTokenAmounts.length; ++i) {
File: contracts/onRamp/EVM2EVMOnRamp.sol
227: for (uint256 i = 0; i < tokensAndPools.length; ++i) {
313: for (uint256 i = 0; i < message.tokenAmounts.length; ++i) {
417: for (uint256 i = 0; i < sourceTokens.length; ++i) {
435: for (uint256 i = 0; i < removes.length; ++i) {
447: for (uint256 i = 0; i < adds.length; ++i) {
503: uint256 bpsValue = 0;
499: for (uint256 i = 0; i < numerOfTokens; ++i) {
549: for (uint256 i = 0; i < feeTokenConfigs.length; ++i) {
578: for (uint256 i = 0; i < numerOfTokens; ++i) {
604: for (uint256 i = 0; i < length; ++i) {
635: uint32 nopWeightsTotal = 0;
639: for (uint256 i = 0; i < numberOfNops; ++i) {
661: for (uint256 i = 0; i < numberOfNops; ++i) {
726: for (uint256 i = 0; i < s_allowList.length(); ++i) {
743: for (uint256 i = 0; i < removes.length; ++i) {
749: for (uint256 i = 0; i < adds.length; ++i) {
File: contracts/pools/ThirdPartyBurnMintTokenPool.sol
35: for (uint256 i = 0; i < onRamps.length; ++i) {
44: for (uint256 i = 0; i < offRamps.length; ++i) {
File: contracts/pools/TokenPool.sol
89: for (uint256 i = 0; i < onRamps.length; ++i) {
97: for (uint256 i = 0; i < offRamps.length; ++i) {
File: contracts/pools/USDC/USDCTokenPool.sol
156: for (uint256 i = 0; i < domains.length; ++i) {
Consider moving to solidity version 0.8.18 or later, and using named mappings to make it easier to understand the purpose of each mapping
There are 18 instances of this issue:
File: contracts/ARM.sol
63: mapping(address => BlesserRecord) private s_blesserRecords;
77: mapping(bytes32 => BlessVoteProgress) private s_blessVoteProgressByTaggedRootHash;
89: mapping(address => CurserRecord) private s_curserRecords;
95: mapping(address => mapping(bytes32 => bool)) private s_curseVotes;
File: contracts/CommitStore.sol
72: mapping(bytes32 => uint256) private s_roots;
File: contracts/PriceRegistry.sol
38: mapping(uint64 => Internal.TimestampedUint192Value) private s_usdPerUnitGasByDestChainSelector;
45: mapping(address => Internal.TimestampedUint192Value) private s_usdPerToken;
File: contracts/Router.sol
51: mapping(uint256 => address) private s_onRamps;
File: contracts/ocr/OCR2Base.sol
58: mapping(address => Oracle) internal s_oracles;
File: contracts/ocr/OCR2BaseNoChecks.sol
55: mapping(address => Oracle) internal s_oracles;
File: contracts/offRamp/EVM2EVMOffRamp.sol
119: mapping(address => uint64) internal s_senderNonce;
123: mapping(uint64 => uint256) internal s_executionStates;
File: contracts/onRamp/EVM2EVMOnRamp.sol
169: mapping(address => FeeTokenConfig) internal s_feeTokenConfig;
171: mapping(address => TokenTransferFeeConfig) internal s_tokenTransferFeeConfig;
175: mapping(address => uint64) internal s_senderNonce;
File: contracts/pools/LockReleaseTokenPool.sol
23: mapping(address => uint256) internal s_liquidityProviderBalances;
File: contracts/pools/USDC/USDCTokenPool.sol
59: mapping(uint64 => Domain) private s_chainToDomain;
According to the Solidity Style Guide, Non-external
/public
variable and function names should begin with an underscore
There are 88 instances of this issue:
File: contracts/ARM.sol
14: uint256 private constant MAX_NUM_VOTERS = 128;
51: VersionedConfig private s_versionedConfig;
63: mapping(address => BlesserRecord) private s_blesserRecords;
77: mapping(bytes32 => BlessVoteProgress) private s_blessVoteProgressByTaggedRootHash;
89: mapping(address => CurserRecord) private s_curserRecords;
95: mapping(address => mapping(bytes32 => bool)) private s_curseVotes;
107: CurseVoteProgress private s_curseVoteProgress;
File: contracts/AggregateRateLimiter.sol
22: address internal s_admin;
25: RateLimiter.TokenBucket private s_rateLimiter;
File: contracts/CommitStore.sol
56: uint64 internal immutable i_chainSelector;
58: uint64 internal immutable i_sourceChainSelector;
60: address internal immutable i_onRamp;
64: DynamicConfig internal s_dynamicConfig;
68: uint64 private s_minSeqNr = 1;
70: bool private s_paused = false;
72: mapping(bytes32 => uint256) private s_roots;
File: contracts/PriceRegistry.sol
38: mapping(uint64 => Internal.TimestampedUint192Value) private s_usdPerUnitGasByDestChainSelector;
45: mapping(address => Internal.TimestampedUint192Value) private s_usdPerToken;
48: EnumerableSet.AddressSet private s_priceUpdaters;
50: EnumerableSet.AddressSet private s_feeTokens;
52: uint32 private immutable i_stalenessThreshold;
File: contracts/Router.sol
48: address private s_wrappedNative;
51: mapping(uint256 => address) private s_onRamps;
55: EnumerableMap.AddressToUintMap private s_offRamps;
File: contracts/libraries/Internal.sol
70: bytes32 internal constant EVM_2_EVM_MESSAGE_HASH = keccak256("EVM2EVMMessageEvent");
File: contracts/libraries/MerkleMultiProof.sol
6: bytes32 internal constant LEAF_DOMAIN_SEPARATOR = 0x0000000000000000000000000000000000000000000000000000000000000000;
8 bytes32 internal constant INTERNAL_DOMAIN_SEPARATOR =
9: 0x0000000000000000000000000000000000000000000000000000000000000001;
47 function merkleRoot(
48 bytes32[] memory leaves,
49 bytes32[] memory proofs,
50 uint256 proofFlagBits
51: ) internal pure returns (bytes32) {
File: contracts/ocr/OCR2Abstract.sol
8: uint256 internal constant MAX_NUM_ORACLES = 31;
File: contracts/ocr/OCR2Base.sol
49: ConfigInfo internal s_configInfo;
53: uint32 internal s_configCount;
55: uint32 internal s_latestConfigBlockNumber;
58: mapping(address => Oracle) internal s_oracles;
61: address[] internal s_signers;
65: address[] internal s_transmitters;
70 uint16 private constant TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT =
71 4 + // function selector
72 32 *
73 3 + // 3 words containing reportContext
74 32 + // word containing start location of abiencoded report value
75 32 + // word containing location start of abiencoded rs value
76 32 + // word containing start location of abiencoded ss value
77 32 + // rawVs value
78 32 + // word containing length of report
79 32 + // word containing length rs
80: 32; // word containing length of ss
File: contracts/ocr/OCR2BaseNoChecks.sol
46: ConfigInfo internal s_configInfo;
50: uint32 internal s_configCount;
52: uint32 internal s_latestConfigBlockNumber;
55: mapping(address => Oracle) internal s_oracles;
59: address[] internal s_transmitters;
64 uint16 private constant TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT =
65 4 + // function selector
66 32 *
67 3 + // 3 words containing reportContext
68 32 + // word containing start location of abiencoded report value
69 32 + // word containing location start of abiencoded rs value
70 32 + // word containing start location of abiencoded ss value
71 32 + // rawVs value
72 32 + // word containing length of report
73 32 + // word containing length rs
74: 32; // word containing length of ss
File: contracts/offRamp/EVM2EVMOffRamp.sol
96: uint16 private constant GAS_FOR_CALL_EXACT_CHECK = 5_000;
98: address internal immutable i_commitStore;
100: uint64 internal immutable i_sourceChainSelector;
102: uint64 internal immutable i_chainSelector;
104: address internal immutable i_onRamp;
106: bytes32 internal immutable i_metadataHash;
108: address internal immutable i_prevOffRamp;
111: DynamicConfig internal s_dynamicConfig;
113: EnumerableMapAddresses.AddressToAddressMap private s_poolsBySourceToken;
115: EnumerableMapAddresses.AddressToAddressMap private s_poolsByDestToken;
119: mapping(address => uint64) internal s_senderNonce;
123: mapping(uint64 => uint256) internal s_executionStates;
158: uint256 private constant MESSAGE_EXECUTION_STATE_BIT_WIDTH = 2;
160: uint256 private constant MESSAGE_EXECUTION_STATE_MASK = (1 << MESSAGE_EXECUTION_STATE_BIT_WIDTH) - 1;
File: contracts/onRamp/EVM2EVMOnRamp.sol
143: bytes32 internal immutable i_metadataHash;
146: uint64 internal immutable i_defaultTxGasLimit;
148: address internal immutable i_linkToken;
150: uint64 internal immutable i_chainSelector;
152: uint64 internal immutable i_destChainSelector;
154: address internal immutable i_prevOnRamp;
156: uint256 private constant MAX_NUMBER_OF_NOPS = 64;
160: DynamicConfig internal s_dynamicConfig;
162: EnumerableMap.AddressToUintMap internal s_nops;
164: EnumerableMapAddresses.AddressToAddressMap private s_poolsBySourceToken;
167: EnumerableSet.AddressSet private s_allowList;
169: mapping(address => FeeTokenConfig) internal s_feeTokenConfig;
171: mapping(address => TokenTransferFeeConfig) internal s_tokenTransferFeeConfig;
175: mapping(address => uint64) internal s_senderNonce;
177: uint96 internal s_nopFeesJuels;
179: uint32 internal s_nopWeightsTotal;
183: uint64 internal s_sequenceNumber;
185: bool private s_paused = false;
188: bool private s_allowlistEnabled;
File: contracts/pools/LockReleaseTokenPool.sol
23: mapping(address => uint256) internal s_liquidityProviderBalances;
File: contracts/pools/ThirdPartyBurnMintTokenPool.sol
20: address private s_router;
File: contracts/pools/TokenPool.sol
36: IERC20 internal immutable i_token;
38: EnumerableSet.AddressSet internal s_onRamps;
40: EnumerableSet.AddressSet internal s_offRamps;
42: RateLimiter.TokenBucket private s_rateLimiter;
44: constructor(IERC20 token, RateLimiter.Config memory rateLimiterConfig) {
File: contracts/pools/USDC/USDCTokenPool.sol
46: USDCConfig private s_config;
50: bytes4 private constant USDC_INTERFACE_ID = bytes4(keccak256("USDC"));
59: mapping(uint64 => Domain) private s_chainToDomain;
File: contracts/pools/tokens/BurnMintERC677.sol
12: bytes32 private constant MINTER_ROLE = keccak256("MINTER_ROLE");
13: bytes32 private constant BURNER_ROLE = keccak256("BURNER_ROLE");
15: uint8 private immutable i_decimals;
abi.encodeCall()
has compiler type safety, whereas the other two functions do not
There are 2 instances of this issue:
File: contracts/Router.sol
150: abi.encodeWithSelector(IAny2EVMMessageReceiver.ccipReceive.selector, message)
File: contracts/libraries/Client.sol
37: return abi.encodeWithSelector(EVM_EXTRA_ARGS_V1_TAG, extraArgs);
Doing so will prevent typo bugs
There are 44 instances of this issue:
File: contracts/ARM.sol
170: assert(vp.voterBitmap >> (MAX_NUM_VOTERS - 1) >= 1);
177: return bitmap & (uint128(1) << index) != 0;
187: for (; bitmap != 0; ++oneBits) {
280: if (!curserRecord.active || curserRecord.voteCount == 0) return;
305: if (curserRecord.voteCount == 1) {
353: if (!curserRecord.active || curserRecord.voteCount == 0) continue;
473: config.voters.length == 0 ||
475: config.blessWeightThreshold == 0 ||
476: config.curseWeightThreshold == 0
490: (voter.blessWeight == 0 && voter.curseWeight == 0)
522: while (s_versionedConfig.config.voters.length != 0) {
File: contracts/AggregateRateLimiter.sol
49: if (pricePerToken == 0) revert PriceNotFoundForToken(tokenAmounts[i].token);
File: contracts/CommitStore.sol
76: if (staticConfig.onRamp == address(0) || staticConfig.chainSelector == 0 || staticConfig.sourceChainSelector == 0)
138: if (s_roots[root] == 0 || !isBlessed(root)) {
148: if (report.priceUpdates.tokenPriceUpdates.length > 0 || report.priceUpdates.destChainSelector != 0) {
File: contracts/PriceRegistry.sol
63: if (stalenessThreshold == 0) revert InvalidStalenessThreshold();
114: if (gasPrice.timestamp == 0) revert ChainNotSupported(destChainSelector);
144: if (tokenPrice.timestamp == 0 || tokenPrice.value == 0) revert TokenNotSupported(token);
214: if (priceUpdates.destChainSelector != 0) {
File: contracts/libraries/MerkleMultiProof.sol
60: if (totalHashes == 0) {
70: ((proofFlagBits >> i) & uint256(1)) == 1
File: contracts/libraries/RateLimiter.sol
43: if (!s_bucket.isEnabled || requestTokens == 0) {
51: if (timeDiff != 0) {
94: if (timeDiff != 0) {
File: contracts/ocr/OCR2Base.sol
89: if (f == 0) revert InvalidConfig("f must be positive");
File: contracts/ocr/OCR2BaseNoChecks.sol
78: if (f == 0) revert InvalidConfig("f must be positive");
File: contracts/offRamp/EVM2EVMOffRamp.sol
135: if (ICommitStore(staticConfig.commitStore).getExpectedNextSequenceNumber() != 1) revert CommitStoreAlreadyInUse();
194: if (senderNonce == 0 && i_prevOffRamp != address(0)) {
219: if (numMsgs == 0) revert EmptyReport();
239: if (timestampCommitted == 0) revert RootNotCommitted();
271: if (prevNonce == 0 && i_prevOffRamp != address(0)) {
File: contracts/onRamp/EVM2EVMOnRamp.sol
202: staticConfig.chainSelector == 0 ||
203: staticConfig.destChainSelector == 0 ||
204: staticConfig.defaultTxGasLimit == 0
253: if (senderNonce == 0 && i_prevOnRamp != address(0)) {
273: if (message.receiver.length != 32) revert InvalidAddress(message.receiver);
288: if (s_senderNonce[originalSender] == 0 && i_prevOnRamp != address(0)) {
333: if (extraArgs.length == 0) {
495: if (numerOfTokens == 0) {
653: if (weightsTotal == 0) revert NoNopsToPay();
File: contracts/pools/USDC/USDCTokenPool.sol
94: if (domain.domainIdentifier == 0) revert UnknownDomain(destChainSelector);
Consider adding parameters to the error to indicate which user or values caused the failure
There are 58 instances of this issue:
File: contracts/ARM.sol
118: error InvalidConfig();
162: error MustRecoverFromCurse();
File: contracts/CommitStore.sol
14: error PausedError();
16: error InvalidRoot();
17: error InvalidCommitStoreConfig();
18: error BadARMSignal();
File: contracts/PriceRegistry.sol
21: error OnlyCallableByUpdaterOrOwner();
24: error InvalidStalenessThreshold();
File: contracts/interfaces/IRouter.sol
7: error OnlyOffRamp();
File: contracts/interfaces/IRouterClient.sol
8: error InsufficientFeeTokenAmount();
9: error InvalidMsgValue();
File: contracts/libraries/MerkleMultiProof.sol
11: error InvalidProof();
File: contracts/libraries/RateLimiter.sol
14: error BucketOverfilled();
17: error OnlyCallableByAdminOrOwner();
File: contracts/ocr/OCR2Base.sol
14: error WrongNumberOfSignatures();
15: error SignaturesOutOfRegistration();
16: error UnauthorizedTransmitter();
17: error UnauthorizedSigner();
18: error NonUniqueSignatures();
File: contracts/ocr/OCR2BaseNoChecks.sol
16: error UnauthorizedTransmitter();
File: contracts/offRamp/EVM2EVMOffRamp.sol
37: error ZeroAddressNotAllowed();
38: error CommitStoreAlreadyInUse();
43: error UnexpectedTokenData();
45: error ManualExecutionNotYetEnabled();
46: error RootNotCommitted();
49: error CanOnlySelfCall();
50: error ReceiverError();
51: error EmptyReport();
52: error BadARMSignal();
53: error InvalidMessageId();
54: error InvalidTokenPoolConfig();
55: error PoolAlreadyAdded();
56: error PoolDoesNotExist();
57: error TokenPoolMismatch();
File: contracts/onRamp/EVM2EVMOnRamp.sol
37: error PausedError();
38: error InvalidExtraArgsTag();
39: error OnlyCallableByOwnerOrFeeAdmin();
40: error OnlyCallableByOwnerOrFeeAdminOrNop();
43: error NoFeesToPay();
44: error NoNopsToPay();
45: error InsufficientBalance();
46: error TooManyNops();
48: error MessageGasLimitTooHigh();
49: error UnsupportedNumberOfTokens();
51: error MustBeCalledByRouter();
52: error RouterMustSetOriginalSender();
53: error InvalidTokenPoolConfig();
54: error PoolAlreadyAdded();
56: error TokenPoolMismatch();
58: error InvalidConfig();
60: error BadARMSignal();
61: error LinkBalanceNotSettled();
File: contracts/pools/LockReleaseTokenPool.sol
20: error InsufficientLiquidity();
21: error WithdrawalTooHigh();
File: contracts/pools/TokenPool.sol
20: error PermissionsError();
21: error NullAddressNotAllowed();
File: contracts/pools/USDC/USDCTokenPool.sol
21: error UnlockingUSDCFailed();
22: error InvalidConfig();
Ensure that events follow the best practice of check-effects-interaction, and are emitted before external calls
There are 4 instances of this issue:
File: contracts/onRamp/EVM2EVMOnRamp.sol
/// @audit safeTransfer() prior to emission of NopPaid()
667: emit NopPaid(nop, amount);
File: contracts/pools/LockReleaseTokenPool.sol
/// @audit safeTransfer() prior to emission of Released()
54: emit Released(msg.sender, receiver, amount);
/// @audit safeTransferFrom() prior to emission of LiquidityAdded()
69: emit LiquidityAdded(msg.sender, amount);
/// @audit safeTransfer() prior to emission of LiquidityRemoved()
79: emit LiquidityRemoved(msg.sender, amount);
The contract's interface should be imported first, followed by each of the interfaces it uses, followed by all other files. The examples below do not follow this layout.
There are 4 instances of this issue:
File: contracts/Router.sol
17: import {IERC20} from "../vendor/openzeppelin-solidity/v4.8.0/token/ERC20/IERC20.sol";
File: contracts/onRamp/EVM2EVMOnRamp.sol
19: import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.0/token/ERC20/IERC20.sol";
File: contracts/pools/TokenPool.sol
11: import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.0/token/ERC20/IERC20.sol";
12: import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.0/utils/introspection/IERC165.sol";
There are 2 instances of this issue:
File: contracts/ARM.sol
511: function _setConfig(Config memory config) private {
File: contracts/offRamp/EVM2EVMOffRamp.sol
217: function _execute(Internal.ExecutionReport memory report, bool manualExecution) internal whenHealthy {
int256
/uint256
are the preferred type names (they're what are used for function signatures), so they should be used consistently
There is one instance of this issue:
File: contracts/pools/tokens/ERC677.sol
16: function transferAndCall(address to, uint amount, bytes memory data) public returns (bool success) {
Solidity follows two's complement rules for its integers, meaning that the most significant bit for signed integers is used to denote the sign, and converting between the two requires inverting all of the bits and adding one. Because of this, casting an unsigned integer to a signed one may result in a change of the sign and or magnitude of the value. For example, int8(type(uint8).max)
is not equal to type(int8).max
, but is equal to -1
. type(uint8).max
in binary is 11111111
, which if cast to a signed value, means the first binary 1
indicates a negative value, and the binary 1
s, invert to all zeroes, and when one is added, it becomes one, but negative, and therefore the decimal value of binary 11111111
is -1
.
There is one instance of this issue:
File: contracts/onRamp/EVM2EVMOnRamp.sol
/// @audit int256 -> uint256
696: return int256(IERC20(i_linkToken).balanceOf(address(this))) - int256(uint256(s_nopFeesJuels));
Once the return variable has been assigned (or has its default value), there is no need to explicitly return it at the end of the function, since it's returned automatically.
There is one instance of this issue:
File: contracts/onRamp/EVM2EVMOnRamp.sol
530: return feeTokenAmount;
Contracts are allowed to override their parents' functions and change the visibility from external
to public
.
There are 11 instances of this issue:
File: contracts/AggregateRateLimiter.sol
58: function currentRateLimiterState() public view returns (RateLimiter.TokenBucket memory) {
65: function setRateLimiterConfig(RateLimiter.Config memory config) public requireAdminOrOwner {
75: function getTokenLimitAdmin() public view returns (address) {
82: function setAdmin(address newAdmin) public requireAdminOrOwner {
File: contracts/CommitStore.sol
90: function getExpectedNextSequenceNumber() public view returns (uint64) {
File: contracts/offRamp/EVM2EVMOffRamp.sol
191: function getSenderNonce(address sender) public view returns (uint64 nonce) {
462: function getSupportedTokens() public view returns (IERC20[] memory sourceTokens) {
482: function getDestinationToken(IERC20 sourceToken) public view returns (IERC20) {
491: function getPoolByDestToken(IERC20 destToken) public view returns (IPool) {
File: contracts/onRamp/EVM2EVMOnRamp.sol
415: function getSupportedTokens() public view returns (address[] memory) {
File: contracts/pools/USDC/USDCTokenPool.sol
77: function getUSDCInterfaceId() public pure returns (bytes4) {
Earlier versions of solidity can use uint<n>(-1)
instead. Expressions not including the - 1
can often be re-written to accomodate the change (e.g. by using a >
rather than a >=
, which will also save some gas)
There is one instance of this issue:
File: contracts/pools/BurnMintTokenPool.sol
12: token.approve(address(this), 2 ** 256 - 1);
Even assembly can benefit from using readable constants instead of hex/numeric literals
There are 46 instances of this issue:
File: contracts/ARM.sol
/// @audit 3
483: address[] memory allAddrs = new address[](3 * config.voters.length);
/// @audit 3
494: allAddrs[3 * i + 0] = voter.blessVoteAddr;
/// @audit 3
495: allAddrs[3 * i + 1] = voter.curseVoteAddr;
/// @audit 3
496: allAddrs[3 * i + 2] = voter.curseUnvoteAddr;
File: contracts/libraries/MerkleMultiProof.sol
/// @audit 256
63: if (totalHashes > 256) revert InvalidProof();
File: contracts/libraries/USDPriceWith18Decimals.sol
/// @audit 1e18
24: return (tokenPrice * tokenAmount) / 1e18;
/// @audit 1e18
43: return (usdValue * 1e18) / tokenPrice;
File: contracts/ocr/OCR2Abstract.sol
/// @audit 256
/// @audit 16
84: uint256 prefixMask = type(uint256).max << (256 - 16); // 0xFFFF00..00
/// @audit 0x0001
/// @audit 256
/// @audit 16
85: uint256 prefix = 0x0001 << (256 - 16); // 0x000100..00
/// @audit 3
116: bytes32[3] calldata reportContext,
File: contracts/ocr/OCR2BaseNoChecks.sol
/// @audit 3
159: bytes32[3] calldata reportContext,
/// @audit 8
176: emit Transmitted(configDigest, uint32(uint256(reportContext[1]) >> 8));
/// @audit 32
189: 32 + // 32 bytes per entry in _rs
/// @audit 32
191: 32; // 32 bytes per entry in _ss)
File: contracts/ocr/OCR2Base.sol
/// @audit 3
91: if (numSigners <= 3 * f) revert InvalidConfig("faulty-oracle f too high");
/// @audit 3
180: bytes32[3] calldata reportContext,
/// @audit 8
198: emit Transmitted(configDigest, uint32(uint256(reportContext[1]) >> 8));
/// @audit 32
215: 32 + // 32 bytes per entry in _rs
/// @audit 32
217: 32; // 32 bytes per entry in _ss)
/// @audit 27
227: address signer = ecrecover(h, uint8(rawVs[i]) + 27, rs[i], ss[i]);
File: contracts/offRamp/EVM2EVMOffRamp.sol
/// @audit 128
169: (s_executionStates[sequenceNumber / 128] >> ((sequenceNumber % 128) * MESSAGE_EXECUTION_STATE_BIT_WIDTH)) &
/// @audit 128
179: uint256 offset = (sequenceNumber % 128) * MESSAGE_EXECUTION_STATE_BIT_WIDTH;
/// @audit 128
180: uint256 bitmap = s_executionStates[sequenceNumber / 128];
/// @audit 128
187: s_executionStates[sequenceNumber / 128] = bitmap;
/// @audit 16
/// @audit 62
/// @audit 64
395: gasLimit = ((gasleft() - 2 * (16 * message.data.length + GAS_FOR_CALL_EXACT_CHECK)) * 62) / 64;
File: contracts/onRamp/EVM2EVMOnRamp.sol
/// @audit 32
273: if (message.receiver.length != 32) revert InvalidAddress(message.receiver);
/// @audit 4
336: if (bytes4(extraArgs[:4]) != Client.EVM_EXTRA_ARGS_V1_TAG) revert InvalidExtraArgsTag();
/// @audit 4
337: return abi.decode(extraArgs[4:], (Client.EVMExtraArgsV1));
/// @audit 1e5
514: bpsValue = (tokenValue * transferFeeConfig.ratio) / 1e5;
/// @audit 1e16
518: uint256 minFeeValue = uint256(transferFeeConfig.minFee) * 1e16;
/// @audit 1e16
519: uint256 maxFeeValue = uint256(transferFeeConfig.maxFee) * 1e16;
File: contracts/pools/BurnMintTokenPool.sol
/// @audit 256
12: token.approve(address(this), 2 ** 256 - 1);
File: contracts/pools/USDC/USDCTokenPool.sol
/// @audit 32
95: bytes32 receiver = bytes32(destinationReceiver[0:32]);
File: contracts/Router.sol
/// @audit 0
184: revert(0, 0)
/// @audit 64
189: if iszero(gt(sub(g, div(g, 64)), gasAmount)) {
/// @audit 0
190: revert(0, 0)
/// @audit 0
194: revert(0, 0)
/// @audit 0
/// @audit 0x20
198: success := call(gasAmount, target, 0, add(data, 0x20), mload(data), 0, 0)
/// @audit 0x20
/// @audit 0
207: returndatacopy(add(retData, 0x20), 0, toCopy)
Index event fields make the field more quickly accessible to off-chain tools that parse events. This is especially useful when it comes to filtering based on an address. However, note that each index field costs extra gas during emission, so it's not necessarily best to index the maximum allowed per event (three fields). Where applicable, each event
should use three indexed
fields if there are three or more fields, and gas usage is not particularly of concern for the events in question. If there are fewer than three applicable fields, all of the applicable fields should be indexed.
There are 17 instances of this issue:
File: contracts/AggregateRateLimiter.sol
19: event AdminSet(address newAdmin);
File: contracts/ARM.sol
124 event VotedToCurse(
125 uint32 indexed configVersion,
126 address indexed voter,
127 uint8 weight,
128 uint32 voteCount,
129 bytes32 curseId,
130 bytes32 cursesHash,
131 uint16 accumulatedWeight
132: );
File: contracts/CommitStore.sol
20: event Paused(address account);
21: event Unpaused(address account);
File: contracts/offRamp/EVM2EVMOffRamp.sol
60: event PoolAdded(address token, address pool);
61: event PoolRemoved(address token, address pool);
File: contracts/onRamp/EVM2EVMOnRamp.sol
63: event Paused(address account);
64: event Unpaused(address account);
65: event AllowListAdd(address sender);
66: event AllowListRemove(address sender);
74: event PoolAdded(address token, address pool);
75: event PoolRemoved(address token, address pool);
File: contracts/pools/TokenPool.sol
27: event OnRampAllowanceSet(address onRamp, bool allowed);
28: event OffRampAllowanceSet(address onRamp, bool allowed);
File: contracts/Router.sol
26: event OnRampSet(uint64 indexed destChainSelector, address onRamp);
27: event OffRampAdded(uint64 indexed sourceChainSelector, address offRamp);
28: event OffRampRemoved(uint64 indexed sourceChainSelector, address offRamp);
There is one instance of this issue:
File: contracts/offRamp/EVM2EVMOffRamp.sol
579: revert();
assembly{ id := chainid() }
=> uint256 id = block.chainid
, assembly { size := extcodesize() }
=> uint256 size = address().code.length
, assembly { hash := extcodehash() }
=> bytes32 hash = address().codehash
There are some automated tools that will flag a project as having higher complexity if there is inline-assembly, so it's best to avoid using it where it's not necessary
There is one instance of this issue:
File: contracts/Router.sol
193: if iszero(extcodesize(target)) {
Events help non-contract tools to track changes, and events prevent users from being surprised by changes
There are 2 instances of this issue:
File: contracts/CommitStore.sol
96 function setMinSeqNr(uint64 minSeqNr) external onlyOwner {
97 s_minSeqNr = minSeqNr;
98: }
File: contracts/Router.sol
224 function setWrappedNative(address wrappedNative) external onlyOwner {
225 s_wrappedNative = wrappedNative;
226: }
This should especially be done if the new value is not required to be different from the old value
There are 3 instances of this issue:
File: contracts/AggregateRateLimiter.sol
/// @audit setAdmin()
84: emit AdminSet(newAdmin);
File: contracts/onRamp/EVM2EVMOnRamp.sol
/// @audit setAllowListEnabled()
718: emit AllowListEnabledSet(enabled);
File: contracts/pools/USDC/USDCTokenPool.sol
/// @audit setDomains()
163: emit DomainsSet(domains);
Use a solidity version of at least 0.8.4 to get bytes.concat()
instead of abi.encodePacked(<bytes>,<bytes>)
Use a solidity version of at least 0.8.12 to get string.concat()
instead of abi.encodePacked(<str>,<str>)
There is one instance of this issue:
File: contracts/ocr/OCR2Base.sol
2: pragma solidity ^0.8.0;
Consider defining in only one contract so that values cannot become out of sync when only one location is updated. A cheap way to store constants in a single location is to create an internal constant
in a library
. If the variable is a local cache of another contract's value, consider making the cache variable internal or private, which will require external users to query the contract with the source of truth, so that callers don't get out of sync.
There are 4 instances of this issue:
File: contracts/CommitStore.sol
/// @audit seen in contracts/ARM.sol
54: string public constant override typeAndVersion = "CommitStore 1.0.0";
File: contracts/offRamp/EVM2EVMOffRamp.sol
/// @audit seen in contracts/CommitStore.sol
94: string public constant override typeAndVersion = "EVM2EVMOffRamp 1.0.0";
File: contracts/onRamp/EVM2EVMOnRamp.sol
/// @audit seen in contracts/offRamp/EVM2EVMOffRamp.sol
141: string public constant override typeAndVersion = "EVM2EVMOnRamp 1.0.0";
File: contracts/Router.sol
/// @audit seen in contracts/onRamp/EVM2EVMOnRamp.sol
41: string public constant override typeAndVersion = "Router 1.0.0";
Usually lines in source code are limited to 80 characters. Today's screens are much larger so it's reasonable to stretch this in some cases. The solidity style guide recommends a maximumum line length of 120 characters, so the lines below should be split when they reach that length.
There are 12 instances of this issue:
File: contracts/ocr/OCR2Abstract.sol
19: /// @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract
38: /// @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract
110: /// @param rs ith element is the R components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries
111: /// @param ss ith element is the S components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries
File: contracts/ocr/OCR2BaseNoChecks.sol
143: /// Empty by default, please provide an implementation in a child contract if you need additional configuration processing
154: /// @param rs ith element is the R components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries
155: /// @param ss ith element is the S components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries
File: contracts/ocr/OCR2Base.sol
163: /// Empty by default, please provide an implementation in a child contract if you need additional configuration processing
174: /// @param rs ith element is the R components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries
175: /// @param ss ith element is the S components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries
File: contracts/offRamp/EVM2EVMOffRamp.sol
392: // 1/64th. We air on the side of caution and instead of passing ((gasleft() - approx cost of call)*63/64) - approx cost of call)*63/64
File: contracts/PriceRegistry.sol
12: /// @notice The PriceRegistry contract responsibility is to store the current gas price in USD for a given destination chain,
[N‑26] Variable names that consist of all capital letters should be reserved for constant
/immutable
variables
If the variable needs to be different based on which class it comes from, a view
/pure
function should be used instead (e.g. like this).
There are 3 instances of this issue:
File: contracts/CommitStore.sol
36: address ARM; // ARM
File: contracts/offRamp/EVM2EVMOffRamp.sol
87: address ARM; // ---------------┐ ARM address
File: contracts/onRamp/EVM2EVMOnRamp.sol
93: address ARM; // ------------┘ ARM address
There is one instance of this issue:
File: contracts/OwnerIsCreator.sol
2: pragma solidity ^0.8.0;
There is one instance of this issue:
File: contracts/libraries/MerkleMultiProof.sol
/// @audit preiimage
7: /// @notice Internal domain separator, should be used as the first 32 bytes of an internal node's preiimage.
Remove the public
if using solidity at or above 0.7.0
There is one instance of this issue:
File: contracts/onRamp/EVM2EVMOnRamp.sol
190 constructor(
191 StaticConfig memory staticConfig,
192 DynamicConfig memory dynamicConfig,
193 TokenAndPool[] memory tokensAndPools,
194 address[] memory allowlist,
195 RateLimiter.Config memory rateLimiterConfig,
196 FeeTokenConfigArgs[] memory feeTokenConfigs,
197 TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs,
198 NopAndWeight[] memory nopsAndWeights
199: ) AggregateRateLimiter(rateLimiterConfig) {
There are 5 instances of this issue:
File: contracts/interfaces/IERC677Receiver.sol
File: contracts/interfaces/ITypeAndVersion.sol
File: contracts/interfaces/IWrappedNative.sol
File: contracts/libraries/Client.sol
File: contracts/pools/tokens/ERC677.sol
There are 76 instances of this issue:
File: contracts/AggregateRateLimiter.sol
/// @audit Missing: '@param tokenAmounts'
/// @audit Missing: '@param priceRegistry'
39 /// @notice Consumes value from the rate limiter bucket based on the
40 /// token value given. First, calculate the prices
41: function _rateLimitValue(Client.EVMTokenAmount[] memory tokenAmounts, IPriceRegistry priceRegistry) internal {
File: contracts/ARM.sol
/// @audit Missing: '@param taggedRoots'
252 /// @notice Can be called by the owner to remove unintentionally voted or even blessed tagged roots in a recovery
253 /// scenario. The owner must ensure that there are no in-flight transactions by ARM nodes voting for any of the
254 /// taggedRoots before calling this function, as such in-flight transactions could lead to the roots becoming
255 /// re-blessed shortly after the call to this function, contrary to the original intention.
256: function ownerResetBlessVotes(IARM.TaggedRoot[] memory taggedRoots) external onlyOwner {
/// @audit Missing: '@param curseVoteAddr'
/// @audit Missing: '@param cursesHash'
268 /// @notice Can be called by a curser to remove unintentional votes to curse.
269 /// We expect this to be called very rarely, e.g. in case of a bug in the
270 /// offchain code causing false voteToCurse calls.
271 /// @notice Should be called from curser's corresponding curseUnvoteAddr.
272: function unvoteToCurse(address curseVoteAddr, bytes32 cursesHash) external {
/// @audit Missing: '@param curseId'
295 /// @notice A vote to curse is appropriate during unhealthy blockchain conditions
296 /// (eg. finality violations).
297: function voteToCurse(bytes32 curseId) external {
/// @audit Missing: '@param unvoteRecords'
338 /// @notice Enables the owner to remove curse votes. After the curse votes are removed,
339 /// this function will check whether the curse is still valid and restore the uncursed state if possible.
340 /// This function also enables the owner to lift a curse created through ownerCurse.
341: function ownerUnvoteToCurse(UnvoteToCurseRecord[] memory unvoteRecords) external onlyOwner {
/// @audit Missing: '@param config'
381 /// @notice Will revert in case a curse is active. To avoid accidentally invalidating an in-progress curse vote, it
382 /// may be advisable to remove voters one-by-one over time, rather than many at once.
383 /// @dev The gas use of this function varies depending on the number of curse votes that are active. When calling this
384 /// function, be sure to include a gas cushion to account for curse votes that may occur between your transaction
385 /// being sent and mined.
386: function setConfig(Config memory config) external onlyOwner {
/// @audit Missing: '@param taggedRoot'
408 /// @return blessVoteAddrs addresses of voters, will be empty if voting took place with an older config version
409 /// @return accumulatedWeight sum of weights of voters, will be zero if voting took place with an older config version
410 /// @return blessed will be accurate regardless of when voting took place
411 /// @dev This is a helper method for offchain code so efficiency is not really a concern.
412 function getBlessProgress(
413 IARM.TaggedRoot calldata taggedRoot
414: ) external view returns (address[] memory blessVoteAddrs, uint16 accumulatedWeight, bool blessed) {
File: contracts/CommitStore.sol
/// @audit Missing: '@param onchainConfig'
184 /// @notice Sets the dynamic config. This function is called during `setOCR2Config` flow
185: function _beforeSetConfig(bytes memory onchainConfig) internal override {
File: contracts/interfaces/IARM.sol
/// @audit Missing: '@param taggedRoot'
12 /// @notice Callers MUST NOT cache the return value as a blessed tagged root could become unblessed.
13: function isBlessed(TaggedRoot calldata taggedRoot) external view returns (bool);
File: contracts/interfaces/ICommitStore.sol
/// @audit Missing: '@param hashedLeaves'
/// @audit Missing: '@param proofs'
/// @audit Missing: '@param proofFlagBits'
5 /// @notice Returns timestamp of when root was accepted or 0 if verification fails.
6 /// @dev This method uses a merkle tree within a merkle tree, with the hashedLeaves,
7 /// proofs and proofFlagBits being used to get the root of the inner tree.
8 /// This root is then used as the singular leaf of the outer tree.
9 function verify(
10 bytes32[] calldata hashedLeaves,
11 bytes32[] calldata proofs,
12 uint256 proofFlagBits
13: ) external returns (uint256 timestamp);
File: contracts/interfaces/IEVM2AnyOnRamp.sol
/// @audit Missing: '@param feeTokenAmount'
40 /// @notice Send a message to the remote chain
41 /// @dev only callable by the Router
42 /// @dev approve() must have already been called on the token using the this ramp address as the spender.
43 /// @dev if the contract is paused, this function will revert.
44 /// @param message Message struct to send
45 /// @param originalSender The original initiator of the CCIP request
46 function forwardFromRouter(
47 Client.EVM2AnyMessage memory message,
48 uint256 feeTokenAmount,
49 address originalSender
50: ) external returns (bytes32);
File: contracts/libraries/MerkleMultiProof.sol
/// @audit Missing: '@param left'
/// @audit Missing: '@param right'
83 /// @notice Hashes two bytes32 objects in their given order, prepended by the
84 /// INTERNAL_DOMAIN_SEPARATOR.
85: function _hashInternalNode(bytes32 left, bytes32 right) private pure returns (bytes32 hash) {
/// @audit Missing: '@param a'
/// @audit Missing: '@param b'
89 /// @notice Hashes two bytes32 objects. The order is taken into account,
90 /// using the lower value first.
91: function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
File: contracts/libraries/RateLimiter.sol
/// @audit Missing: '@param s_bucket'
36 /// @notice _consume removes the given tokens from the pool, lowering the
37 /// rate tokens allowed to be consumed for subsequent calls.
38 /// @param requestTokens The total tokens to be consumed from the bucket.
39 /// @dev Reverts when requestTokens exceeds bucket capacity or available tokens in the bucket
40 /// @dev emits removal of requestTokens if requestTokens is > 0
41: function _consume(TokenBucket storage s_bucket, uint256 requestTokens) internal {
/// @audit Missing: '@param bucket'
74 /// @notice Gets the token bucket with its values for the block it was requested at.
75 /// @return The token bucket.
76: function _currentTokenBucketState(TokenBucket memory bucket) internal view returns (TokenBucket memory) {
File: contracts/ocr/OCR2Abstract.sol
/// @audit Missing: '@param reportContext'
108 /// @notice transmit is called to post a new report to the contract
109 /// @param report serialized report, which the signatures are signing.
110 /// @param rs ith element is the R components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries
111 /// @param ss ith element is the S components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries
112 /// @param rawVs ith element is the the V component of the ith signature
113 function transmit(
114 // NOTE: If these parameters are changed, expectedMsgDataLength and/or
115 // TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT need to be changed accordingly
116 bytes32[3] calldata reportContext,
117 bytes calldata report,
118 bytes32[] calldata rs,
119 bytes32[] calldata ss,
120: bytes32 rawVs // signatures
File: contracts/ocr/OCR2BaseNoChecks.sol
/// @audit Missing: '@param _onchainConfig'
142 /// @dev Hook that is run from setOCR2Config() right after validating configuration.
143 /// Empty by default, please provide an implementation in a child contract if you need additional configuration processing
144: function _beforeSetConfig(bytes memory _onchainConfig) internal virtual {}
/// @audit Missing: '@param reportContext'
/// @audit Missing: '@param bytes32'
152 /// @notice transmit is called to post a new report to the contract
153 /// @param report serialized report, which the signatures are signing.
154 /// @param rs ith element is the R components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries
155 /// @param ss ith element is the S components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries
156 function transmit(
157 // NOTE: If these parameters are changed, expectedMsgDataLength and/or
158 // TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT need to be changed accordingly
159 bytes32[3] calldata reportContext,
160 bytes calldata report,
161 bytes32[] calldata rs,
162 bytes32[] calldata ss,
163: bytes32 // signatures
File: contracts/ocr/OCR2Base.sol
/// @audit Missing: '@param _onchainConfig'
162 /// @dev Hook that is run from setOCR2Config() right after validating configuration.
163 /// Empty by default, please provide an implementation in a child contract if you need additional configuration processing
164: function _beforeSetConfig(bytes memory _onchainConfig) internal virtual {}
/// @audit Missing: '@param reportContext'
172 /// @notice transmit is called to post a new report to the contract
173 /// @param report serialized report, which the signatures are signing.
174 /// @param rs ith element is the R components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries
175 /// @param ss ith element is the S components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries
176 /// @param rawVs ith element is the the V component of the ith signature
177 function transmit(
178 // NOTE: If these parameters are changed, expectedMsgDataLength and/or
179 // TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT need to be changed accordingly
180 bytes32[3] calldata reportContext,
181 bytes calldata report,
182 bytes32[] calldata rs,
183 bytes32[] calldata ss,
184: bytes32 rawVs // signatures
File: contracts/offRamp/EVM2EVMOffRamp.sol
/// @audit Missing: '@param report'
207 /// @notice Entrypoint for execution, called by the OCR network
208 /// @dev Expects an encoded ExecutionReport
209: function _report(bytes calldata report) internal override {
/// @audit Missing: '@param offchainTokenDataLength'
332 /// @notice Does basic message validation. Should never fail.
333 /// @param message The message to be validated.
334 /// @dev reverts on validation failures.
335: function _isWellFormed(Internal.EVM2EVMMessage memory message, uint256 offchainTokenDataLength) private view {
/// @audit Missing: '@param offchainTokenData'
344 /// @notice Try executing a message.
345 /// @param message Client.Any2EVMMessage memory message.
346 /// @param manualExecution bool to indicate manual instead of DON execution.
347 /// @return the new state of the message, being either SUCCESS or FAILURE.
348 function _trialExecute(
349 Internal.EVM2EVMMessage memory message,
350 bytes[] memory offchainTokenData,
351 bool manualExecution
352: ) internal returns (Internal.MessageExecutionState) {
/// @audit Missing: '@param offchainTokenData'
363 /// @notice Execute a single message.
364 /// @param message The message that will be executed.
365 /// @param manualExecution bool to indicate manual instead of DON execution.
366 /// @dev this can only be called by the contract itself. It is part of
367 /// the Execute call, as we can only try/catch on external calls.
368 function executeSingleMessage(
369 Internal.EVM2EVMMessage memory message,
370 bytes[] memory offchainTokenData,
371: bool manualExecution
/// @audit Missing: '@param prefix'
407 /// @notice creates a unique hash to be used in message hashing.
408: function _metadataHash(bytes32 prefix) internal view returns (bytes32) {
/// @audit Missing: '@param onchainConfig'
435 /// @notice Sets the dynamic config. This function is called during `setOCR2Config` flow
436: function _beforeSetConfig(bytes memory onchainConfig) internal override {
/// @audit Missing: '@param originalSender'
/// @audit Missing: '@param offchainTokenData'
545 /// @notice Uses pools to release or mint a number of different tokens to a receiver address.
546 /// @param sourceTokenAmounts List of tokens and amount values to be released/minted.
547 /// @param receiver The address that will receive the tokens.
548 function _releaseOrMintTokens(
549 Client.EVMTokenAmount[] memory sourceTokenAmounts,
550 bytes memory originalSender,
551 address receiver,
552 bytes[] memory offchainTokenData
553: ) internal returns (Client.EVMTokenAmount[] memory) {
File: contracts/onRamp/EVM2EVMOnRamp.sol
/// @audit Missing: '@param dynamicConfig'
390 /// @notice Internal version of setDynamicConfig to allow for reuse in the constructor.
391: function _setDynamicConfig(DynamicConfig memory dynamicConfig) internal {
/// @audit Missing: '@param feeToken'
/// @audit Missing: '@param feeTokenPrice'
/// @audit Missing: '@param tokenAmounts'
486 /// @notice Returns the fee based on the tokens transferred. Will always be 0 if
487 /// no tokens are transferred. The token fee is calculated based on basis points.
488 function _getTokenTransferFee(
489 address feeToken,
490 uint192 feeTokenPrice,
491 Client.EVMTokenAmount[] calldata tokenAmounts
492: ) internal view returns (uint256 feeTokenAmount) {
/// @audit Missing: '@param token'
559 /// @notice Gets the transfer fee config for a given token.
560 function getTokenTransferFeeConfig(
561 address token
562: ) external view returns (TokenTransferFeeConfig memory tokenTransferFeeConfig) {
/// @audit Missing: '@param tokenTransferFeeConfigArgs'
566 /// @notice Sets the transfer fee config.
567 /// @dev only callable by the owner or admin.
568 function setTokenTransferFeeConfig(
569 TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs
570: ) external onlyOwnerOrAdmin {
/// @audit Missing: '@param tokenTransferFeeConfigArgs'
574 /// @notice internal helper to set the token transfer fee config.
575: function _setTokenTransferFeeConfig(TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs) internal {
/// @audit Missing: '@param removes'
/// @audit Missing: '@param adds'
740 /// @notice Internal version of applyAllowListUpdates to allow for reuse in the constructor.
741 /// @dev allowListing will be removed before public launch
742: function _applyAllowListUpdates(address[] memory removes, address[] memory adds) internal {
File: contracts/pools/BurnMintTokenPool.sol
/// @audit Missing: '@param address'
/// @audit Missing: '@param bytes'
/// @audit Missing: '@param uint64'
15 /// @notice Burn the token in the pool
16 /// @dev Burn is not rate limited at per-pool level. Burn does not contribute to honey pot risk.
17 /// Benefits of rate limiting here does not justify the extra gas cost.
18 /// @param amount Amount to burn
19 function lockOrBurn(
20 address,
21 bytes calldata,
22 uint256 amount,
23 uint64,
24 bytes calldata
25: ) external override whenNotPaused onlyOnRamp {
/// @audit Missing: '@param bytes'
/// @audit Missing: '@param uint64'
30 /// @notice Mint tokens from the pool to the recipient
31 /// @param receiver Recipient address
32 /// @param amount Amount to mint
33 function releaseOrMint(
34 bytes memory,
35 address receiver,
36 uint256 amount,
37 uint64,
38 bytes memory
39: ) external override whenNotPaused onlyOffRamp {
File: contracts/pools/LockReleaseTokenPool.sol
/// @audit Missing: '@param address'
/// @audit Missing: '@param bytes'
/// @audit Missing: '@param uint64'
27 /// @notice Locks the token in the pool
28 /// @dev Locks are not rate limited at per-pool level. Each pool is shared across lanes,
29 /// rate limiting locks does not meaningfully mitigate honeypot risk.
30 /// Benefits of rate limiting here does not justify the extra gas cost.
31 /// @param amount Amount to lock
32 function lockOrBurn(
33 address,
34 bytes calldata,
35 uint256 amount,
36 uint64,
37 bytes calldata
38: ) external override whenNotPaused onlyOnRamp {
/// @audit Missing: '@param bytes'
/// @audit Missing: '@param uint64'
42 /// @notice Release tokens from the pool to the recipient
43 /// @param receiver Recipient address
44 /// @param amount Amount to release
45 function releaseOrMint(
46 bytes memory,
47 address receiver,
48 uint256 amount,
49 uint64,
50 bytes memory
51: ) external override whenNotPaused onlyOffRamp {
File: contracts/pools/TokenPool.sol
/// @audit Missing: '@param onRamp'
72 /// @notice Checks whether something is a permissioned onRamp on this contract.
73 /// @return true if the given address is a permissioned onRamp.
74: function isOnRamp(address onRamp) public view returns (bool) {
/// @audit Missing: '@param offRamp'
78 /// @notice Checks whether something is a permissioned offRamp on this contract.
79 /// @return true is the given address is a permissioned offRamp.
80: function isOffRamp(address offRamp) public view returns (bool) {
/// @audit Missing: '@param amount'
110 /// @notice Consumes rate limiting capacity in this pool
111: function _consumeRateLimit(uint256 amount) internal {
File: contracts/pools/tokens/BurnMintERC677.sol
/// @audit Missing: '@param account'
/// @audit Missing: '@param amount'
43 /// @notice Creates `amount` tokens and assigns them to `account`, increasing
44 /// the total supply.
45 /// @dev Emits a {Transfer} event with `from` set to the zero address.
46 /// @dev `account` cannot be the zero address.
47: function mint(address account, uint256 amount) external override onlyRole(MINTER_ROLE) {
/// @audit Missing: '@param account'
55 /// @notice grants both mint and burn roles to `account`.
56 /// @dev uses the public `grantRole` function internally to manage
57 /// role granting permissions.
58: function grantMintAndBurnRoles(address account) external {
File: contracts/pools/USDC/USDCTokenPool.sol
/// @audit Missing: '@param address'
/// @audit Missing: '@param destinationReceiver'
/// @audit Missing: '@param destChainSelector'
/// @audit Missing: '@param bytes'
81 /// @notice Burn the token in the pool
82 /// @dev Burn is not rate limited at per-pool level. Burn does not contribute to honey pot risk.
83 /// Benefits of rate limiting here does not justify the extra gas cost.
84 /// @param amount Amount to burn
85 /// @dev emits ITokenMessenger.DepositForBurn
86 function lockOrBurn(
87 address,
88 bytes calldata destinationReceiver,
89 uint256 amount,
90 uint64 destChainSelector,
91 bytes calldata
92: ) external override whenNotPaused onlyOnRamp {
/// @audit Missing: '@param bytes'
/// @audit Missing: '@param uint64'
/// @audit Missing: '@param extraData'
107 /// @notice Mint tokens from the pool to the recipient
108 /// @param receiver Recipient address
109 /// @param amount Amount to mint
110 function releaseOrMint(
111 bytes memory,
112 address receiver,
113 uint256 amount,
114 uint64,
115 bytes memory extraData
116: ) external override whenNotPaused onlyOffRamp {
/// @audit Missing: '@param config'
137 /// @notice Sets the config
138: function setConfig(USDCConfig memory config) external onlyOwner {
/// @audit Missing: '@param config'
142 /// @notice Sets the config
143: function _setConfig(USDCConfig memory config) internal {
/// @audit Missing: '@param chainSelector'
149 /// @notice Gets the CCTP domain for a given CCIP chain selector.
150: function getDomain(uint64 chainSelector) external view returns (Domain memory) {
/// @audit Missing: '@param domains'
154 /// @notice Sets the CCTP domain for a CCIP chain selector.
155: function setDomains(DomainUpdate[] calldata domains) external onlyOwner {
File: contracts/Router.sol
/// @audit Missing: '@param offRamp'
245 /// @notice Returns true if the given address is a permissioned offRamp
246 /// and sourceChainSelector if so.
247: function isOffRamp(address offRamp) external view returns (bool, uint64) {
/// @audit Missing: '@param onRampUpdates'
/// @audit Missing: '@param offRampRemoves'
/// @audit Missing: '@param offRampAdds'
252 /// @notice applyRampUpdates applies a set of ramp changes which provides
253 /// the ability to add new chains and upgrade ramps.
254 function applyRampUpdates(
255 OnRamp[] calldata onRampUpdates,
256 OffRamp[] calldata offRampRemoves,
257 OffRamp[] calldata offRampAdds
258: ) external onlyOwner {
/// @audit Missing: '@param amount'
280 /// @notice Provides the ability for the owner to recover any tokens accidentally
281 /// sent to this contract.
282 /// @dev Must be onlyOwner to avoid malicious token contract calls.
283 /// @param tokenAddress ERC20-token to recover
284 /// @param to Destination address to send the tokens to.
285: function recoverTokens(address tokenAddress, address to, uint256 amount) external onlyOwner {
There are 29 instances of this issue:
File: contracts/ARM.sol
/// @audit Missing: '@return'
400 /// @notice Config version might be incremented for many reasons, including
401 /// recovery from a curse and a regular config change.
402: function getConfigDetails() external view returns (uint32 version, uint32 blockNumber, Config memory config) {
/// @audit Missing: '@return'
433 /// @dev This is a helper method for offchain code so efficiency is not really a concern.
434 function getCurseProgress()
435 external
436 view
437 returns (
438 address[] memory curseVoteAddrs,
439 uint32[] memory voteCounts,
440 bytes32[] memory cursesHashes,
441 uint16 accumulatedWeight,
442: bool cursed
File: contracts/CommitStore.sol
/// @audit Missing: '@return'
202 /// @notice Support querying whether health checker is healthy.
203: function isARMHealthy() external view returns (bool) {
/// @audit Missing: '@return'
219 /// @notice Returns true if the contract is paused, and false otherwise.
220: function paused() public view returns (bool) {
File: contracts/interfaces/IARM.sol
/// @audit Missing: '@return'
12 /// @notice Callers MUST NOT cache the return value as a blessed tagged root could become unblessed.
13: function isBlessed(TaggedRoot calldata taggedRoot) external view returns (bool);
/// @audit Missing: '@return'
15 /// @notice When the ARM is "cursed", CCIP pauses until the curse is lifted.
16: function isCursed() external view returns (bool);
File: contracts/interfaces/ICommitStore.sol
/// @audit Missing: '@return'
5 /// @notice Returns timestamp of when root was accepted or 0 if verification fails.
6 /// @dev This method uses a merkle tree within a merkle tree, with the hashedLeaves,
7 /// proofs and proofFlagBits being used to get the root of the inner tree.
8 /// This root is then used as the singular leaf of the outer tree.
9 function verify(
10 bytes32[] calldata hashedLeaves,
11 bytes32[] calldata proofs,
12 uint256 proofFlagBits
13: ) external returns (uint256 timestamp);
/// @audit Missing: '@return'
15 /// @notice Returns the expected next sequence number
16: function getExpectedNextSequenceNumber() external view returns (uint64 sequenceNumber);
File: contracts/interfaces/IEVM2AnyOnRamp.sol
/// @audit Missing: '@return'
40 /// @notice Send a message to the remote chain
41 /// @dev only callable by the Router
42 /// @dev approve() must have already been called on the token using the this ramp address as the spender.
43 /// @dev if the contract is paused, this function will revert.
44 /// @param message Message struct to send
45 /// @param originalSender The original initiator of the CCIP request
46 function forwardFromRouter(
47 Client.EVM2AnyMessage memory message,
48 uint256 feeTokenAmount,
49 address originalSender
50: ) external returns (bytes32);
File: contracts/libraries/MerkleMultiProof.sol
/// @audit Missing: '@return'
45 // that use it, but semantically it is public.
46 // solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
47 function merkleRoot(
48 bytes32[] memory leaves,
49 bytes32[] memory proofs,
50 uint256 proofFlagBits
51: ) internal pure returns (bytes32) {
/// @audit Missing: '@return'
83 /// @notice Hashes two bytes32 objects in their given order, prepended by the
84 /// INTERNAL_DOMAIN_SEPARATOR.
85: function _hashInternalNode(bytes32 left, bytes32 right) private pure returns (bytes32 hash) {
/// @audit Missing: '@return'
89 /// @notice Hashes two bytes32 objects. The order is taken into account,
90 /// using the lower value first.
91: function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
File: contracts/offRamp/EVM2EVMOffRamp.sol
/// @audit Missing: '@return'
407 /// @notice creates a unique hash to be used in message hashing.
408: function _metadataHash(bytes32 prefix) internal view returns (bytes32) {
/// @audit Missing: '@return'
416 /// @notice Returns the static config.
417 /// @dev This function will always return the same struct as the contents is static and can never change.
418: function getStaticConfig() external view returns (StaticConfig memory) {
/// @audit Missing: '@return'
545 /// @notice Uses pools to release or mint a number of different tokens to a receiver address.
546 /// @param sourceTokenAmounts List of tokens and amount values to be released/minted.
547 /// @param receiver The address that will receive the tokens.
548 function _releaseOrMintTokens(
549 Client.EVMTokenAmount[] memory sourceTokenAmounts,
550 bytes memory originalSender,
551 address receiver,
552 bytes[] memory offchainTokenData
553: ) internal returns (Client.EVMTokenAmount[] memory) {
/// @audit Missing: '@return'
582 /// @notice Support querying whether health checker is healthy.
583: function isARMHealthy() external view returns (bool) {
File: contracts/onRamp/EVM2EVMOnRamp.sol
/// @audit Missing: '@return'
486 /// @notice Returns the fee based on the tokens transferred. Will always be 0 if
487 /// no tokens are transferred. The token fee is calculated based on basis points.
488 function _getTokenTransferFee(
489 address feeToken,
490 uint192 feeTokenPrice,
491 Client.EVMTokenAmount[] calldata tokenAmounts
492: ) internal view returns (uint256 feeTokenAmount) {
/// @audit Missing: '@return'
559 /// @notice Gets the transfer fee config for a given token.
560 function getTokenTransferFeeConfig(
561 address token
562: ) external view returns (TokenTransferFeeConfig memory tokenTransferFeeConfig) {
/// @audit Missing: '@return'
699 /// @notice Allow keeper to monitor funds available for paying nops
700: function linkAvailableForPayment() external view returns (int256) {
/// @audit Missing: '@return'
777 /// @notice Support querying whether health checker is healthy.
778: function isARMHealthy() external view returns (bool) {
/// @audit Missing: '@return'
794 /// @notice Returns true if the contract is paused, and false otherwise.
795: function paused() public view returns (bool) {
File: contracts/pools/tokens/BurnMintERC677.sol
/// @audit Missing: '@return'
63 /// @notice Returns the minter role hash
64: function getMinterRole() external pure returns (bytes32) {
/// @audit Missing: '@return'
68 /// @notice Returns the burner role hash
69: function getBurnerRole() external pure returns (bytes32) {
File: contracts/pools/USDC/USDCTokenPool.sol
/// @audit Missing: '@return'
76 /// @notice returns the USDC interface flag used for EIP165 identification.
77: function getUSDCInterfaceId() public pure returns (bytes4) {
/// @audit Missing: '@return'
132 /// @notice Gets the current config
133: function getConfig() external view returns (USDCConfig memory) {
/// @audit Missing: '@return'
149 /// @notice Gets the CCTP domain for a given CCIP chain selector.
150: function getDomain(uint64 chainSelector) external view returns (Domain memory) {
File: contracts/Router.sol
/// @audit Missing: '@return'
166 /// @param target target address
167 /// @param data calldata
168 function _callWithExactGas(
169 uint16 gasForCallExactCheck,
170 uint256 gasAmount,
171 address target,
172 bytes memory data
173: ) internal returns (bool success, bytes memory retData) {
/// @audit Missing: '@return'
235 /// @notice Return a full list of configured offRamps.
236: function getOffRamps() external view returns (OffRamp[] memory) {
/// @audit Missing: '@return'
245 /// @notice Returns true if the given address is a permissioned offRamp
246 /// and sourceChainSelector if so.
247: function isOffRamp(address offRamp) external view returns (bool, uint64) {
According to the Solidity style guide, functions should be laid out in the following order :constructor()
, receive()
, fallback()
, external
, public
, internal
, private
, but the cases below do not follow this pattern
There are 43 instances of this issue:
File: contracts/AggregateRateLimiter.sol
/// @audit _rateLimitValue() came earlier
58: function currentRateLimiterState() public view returns (RateLimiter.TokenBucket memory) {
File: contracts/ARM.sol
/// @audit _taggedRootHash() came earlier
198: function voteToBless(IARM.TaggedRoot[] calldata taggedRoots) external {
/// @audit isCursed() came earlier
402: function getConfigDetails() external view returns (uint32 version, uint32 blockNumber, Config memory config) {
File: contracts/CommitStore.sol
/// @audit getExpectedNextSequenceNumber() came earlier
96: function setMinSeqNr(uint64 minSeqNr) external onlyOwner {
/// @audit isBlessed() came earlier
120: function resetUnblessedRoots(bytes32[] calldata rootToReset) external onlyOwner {
/// @audit _report() came earlier
174: function getStaticConfig() external view returns (StaticConfig memory) {
/// @audit _beforeSetConfig() came earlier
203: function isARMHealthy() external view returns (bool) {
/// @audit paused() came earlier
226: function pause() external onlyOwner {
File: contracts/libraries/RateLimiter.sol
/// @audit _calculateRefill() came earlier
127: function _min(uint256 a, uint256 b) internal pure returns (uint256) {
File: contracts/ocr/OCR2Abstract.sol
/// @audit _configDigestFromConfigData() came earlier
102 function latestConfigDigestAndEpoch()
103 external
104 view
105 virtual
106: returns (bool scanLogs, bytes32 configDigest, uint32 epoch);
File: contracts/ocr/OCR2BaseNoChecks.sol
/// @audit _beforeSetConfig() came earlier
148: function getTransmitters() external view returns (address[] memory) {
File: contracts/ocr/OCR2Base.sol
/// @audit _beforeSetConfig() came earlier
168: function getTransmitters() external view returns (address[] memory) {
File: contracts/offRamp/EVM2EVMOffRamp.sol
/// @audit _setExecutionState() came earlier
191: function getSenderNonce(address sender) public view returns (uint64 nonce) {
/// @audit getSenderNonce() came earlier
203: function manuallyExecute(Internal.ExecutionReport calldata report) external {
/// @audit _isWellFormed() came earlier
348 function _trialExecute(
349 Internal.EVM2EVMMessage memory message,
350 bytes[] memory offchainTokenData,
351 bool manualExecution
352: ) internal returns (Internal.MessageExecutionState) {
/// @audit _trialExecute() came earlier
368 function executeSingleMessage(
369 Internal.EVM2EVMMessage memory message,
370 bytes[] memory offchainTokenData,
371: bool manualExecution
/// @audit _metadataHash() came earlier
418: function getStaticConfig() external view returns (StaticConfig memory) {
/// @audit _beforeSetConfig() came earlier
462: function getSupportedTokens() public view returns (IERC20[] memory sourceTokens) {
/// @audit getPoolByDestToken() came earlier
499: function getDestinationTokens() external view returns (IERC20[] memory destTokens) {
/// @audit _releaseOrMintTokens() came earlier
577: function ccipReceive(Client.Any2EVMMessage calldata) external pure {
File: contracts/onRamp/EVM2EVMOnRamp.sol
/// @audit _validateMessage() came earlier
367: function getStaticConfig() external view returns (StaticConfig memory) {
/// @audit _setDynamicConfig() came earlier
415: function getSupportedTokens() public view returns (address[] memory) {
/// @audit getPoolBySourceToken() came earlier
431 function applyPoolUpdates(
432 Internal.PoolUpdate[] calldata removes,
433 Internal.PoolUpdate[] calldata adds
434: ) external onlyOwner {
/// @audit _getTokenTransferFee() came earlier
536: function getFeeTokenConfig(address token) external view returns (FeeTokenConfig memory feeTokenConfig) {
/// @audit _setFeeTokenConfig() came earlier
560 function getTokenTransferFeeConfig(
561 address token
562: ) external view returns (TokenTransferFeeConfig memory tokenTransferFeeConfig) {
/// @audit _setTokenTransferFeeConfig() came earlier
594: function getNopFeesJuels() external view returns (uint96) {
/// @audit _setNops() came earlier
651: function payNops() public onlyOwnerOrAdminOrNop {
/// @audit payNops() came earlier
677: function withdrawNonLinkFees(address feeToken, address to) external onlyOwner {
/// @audit _linkLeftAfterNopFees() came earlier
700: function linkAvailableForPayment() external view returns (int256) {
/// @audit _applyAllowListUpdates() came earlier
778: function isARMHealthy() external view returns (bool) {
/// @audit paused() came earlier
801: function pause() external onlyOwner {
File: contracts/pools/TokenPool.sol
/// @audit _consumeRateLimit() came earlier
117: function currentRateLimiterState() public view returns (RateLimiter.TokenBucket memory) {
/// @audit setRateLimiterConfig() came earlier
147: function pause() external onlyOwner {
File: contracts/pools/tokens/BurnMintERC677.sol
/// @audit burnFrom() came earlier
47: function mint(address account, uint256 amount) external override onlyRole(MINTER_ROLE) {
File: contracts/pools/tokens/ERC677.sol
/// @audit transferAndCall() came earlier
26: function transferAndCall(address to, uint256 amount) external returns (bool) {
File: contracts/pools/USDC/USDCTokenPool.sol
/// @audit getUSDCInterfaceId() came earlier
86 function lockOrBurn(
87 address,
88 bytes calldata destinationReceiver,
89 uint256 amount,
90 uint64 destChainSelector,
91 bytes calldata
92: ) external override whenNotPaused onlyOnRamp {
/// @audit _setConfig() came earlier
150: function getDomain(uint64 chainSelector) external view returns (Domain memory) {
File: contracts/PriceRegistry.sol
/// @audit getTokenPrice() came earlier
77: function getValidatedTokenPrice(address token) external view override returns (uint192) {
/// @audit _getValidatedTokenPrice() came earlier
156: function getFeeTokens() external view returns (address[] memory feeTokens) {
/// @audit _applyFeeTokensUpdates() came earlier
196: function updatePrices(Internal.PriceUpdates memory priceUpdates) external override requireUpdaterOrOwner {
/// @audit _updatePrices() came earlier
229: function getPriceUpdaters() external view returns (address[] memory priceUpdaters) {
File: contracts/Router.sol
/// @audit isChainSupported() came earlier
94 function ccipSend(
95 uint64 destinationChainSelector,
96 Client.EVM2AnyMessage memory message
97: ) external payable returns (bytes32) {
/// @audit _callWithExactGas() came earlier
218: function getWrappedNative() external view returns (address) {
The style guide says that, within a contract, the ordering should be 1) Type declarations, 2) State variables, 3) Events, 4) Modifiers, and 5) Functions, but the contract(s) below do not follow this ordering
There are 21 instances of this issue:
File: contracts/AggregateRateLimiter.sol
/// @audit event AdminSet came earlier
22: address internal s_admin;
/// @audit function setAdmin came earlier
89 modifier requireAdminOrOwner() {
90 if (msg.sender != owner() && msg.sender != s_admin) revert RateLimiter.OnlyCallableByAdminOrOwner();
91 _;
92: }
File: contracts/CommitStore.sol
/// @audit event RootRemoved came earlier
54: string public constant override typeAndVersion = "CommitStore 1.0.0";
/// @audit function isARMHealthy came earlier
208 modifier whenHealthy() {
209 if (IARM(s_dynamicConfig.ARM).isCursed()) revert BadARMSignal();
210 _;
211: }
File: contracts/libraries/Internal.sol
/// @audit function _toAny2EVMMessage came earlier
70: bytes32 internal constant EVM_2_EVM_MESSAGE_HASH = keccak256("EVM2EVMMessageEvent");
/// @audit function _hash came earlier
98 enum MessageExecutionState {
99 UNTOUCHED,
100 IN_PROGRESS,
101 SUCCESS,
102 FAILURE
103: }
File: contracts/ocr/OCR2Abstract.sol
/// @audit function _configDigestFromConfigData came earlier
92: event Transmitted(bytes32 configDigest, uint32 epoch);
File: contracts/offRamp/EVM2EVMOffRamp.sol
/// @audit event ExecutionStateChanged came earlier
94: string public constant override typeAndVersion = "EVM2EVMOffRamp 1.0.0";
/// @audit function constructor came earlier
158: uint256 private constant MESSAGE_EXECUTION_STATE_BIT_WIDTH = 2;
/// @audit function isARMHealthy came earlier
588 modifier whenHealthy() {
589 if (IARM(s_dynamicConfig.ARM).isCursed()) revert BadARMSignal();
590 _;
591: }
File: contracts/onRamp/EVM2EVMOnRamp.sol
/// @audit event PoolRemoved came earlier
141: string public constant override typeAndVersion = "EVM2EVMOnRamp 1.0.0";
/// @audit function _applyAllowListUpdates came earlier
765 modifier onlyOwnerOrAdminOrNop() {
766 if (msg.sender != owner() && msg.sender != s_admin && !s_nops.contains(msg.sender))
767 revert OnlyCallableByOwnerOrFeeAdminOrNop();
768 _;
769: }
/// @audit function isARMHealthy came earlier
783 modifier whenHealthy() {
784 if (IARM(s_dynamicConfig.ARM).isCursed()) revert BadARMSignal();
785 _;
786: }
File: contracts/pools/LockReleaseTokenPool.sol
/// @audit event LiquidityRemoved came earlier
23: mapping(address => uint256) internal s_liquidityProviderBalances;
File: contracts/pools/TokenPool.sol
/// @audit event OffRampAllowanceSet came earlier
36: IERC20 internal immutable i_token;
/// @audit function setRateLimiterConfig came earlier
134 modifier onlyOnRamp() {
135 if (!isOnRamp(msg.sender)) revert PermissionsError();
136 _;
137: }
File: contracts/pools/USDC/USDCTokenPool.sol
/// @audit event ConfigSet came earlier
46: USDCConfig private s_config;
File: contracts/PriceRegistry.sol
/// @audit event UsdPerTokenUpdated came earlier
38: mapping(uint64 => Internal.TimestampedUint192Value) private s_usdPerUnitGasByDestChainSelector;
/// @audit function _applyPriceUpdatersUpdates came earlier
270 modifier requireUpdaterOrOwner() {
271 if (msg.sender != owner() && !s_priceUpdaters.contains(msg.sender)) revert OnlyCallableByUpdaterOrOwner();
272 _;
273: }
File: contracts/Router.sol
/// @audit event MessageExecuted came earlier
41: string public constant override typeAndVersion = "Router 1.0.0";
/// @audit function recoverTokens came earlier
299 modifier onlyOffRamp(uint64 expectedSourceChainSelector) {
300 (bool exists, uint256 sourceChainSelector) = s_offRamps.tryGet(msg.sender);
301 if (!exists || expectedSourceChainSelector != uint64(sourceChainSelector)) revert OnlyOffRamp();
302 _;
303: }
See the control structures section of the Solidity Style Guide
There are 9 instances of this issue:
File: contracts/ARM.sol
368 if (
369: s_curseVoteProgress.curseActive &&
472 if (
473: config.voters.length == 0 ||
486 if (
487: voter.blessVoteAddr == address(0) ||
File: contracts/offRamp/EVM2EVMOffRamp.sol
248 if (
249: !(originalState == Internal.MessageExecutionState.UNTOUCHED ||
383 if (
384: !message.receiver.isContract() || !message.receiver.supportsInterface(type(IAny2EVMMessageReceiver).interfaceId)
397 if (
398: !IRouter(s_dynamicConfig.router).routeMessage(
File: contracts/onRamp/EVM2EVMOnRamp.sol
200 if (
201: staticConfig.linkToken == address(0) ||
392 if (
393: dynamicConfig.router == address(0) || dynamicConfig.priceRegistry == address(0) || dynamicConfig.ARM == address(0)
File: contracts/pools/USDC/USDCTokenPool.sol
119 if (
120: !IMessageReceiver(s_config.messageTransmitter).receiveMessage(
[N‑36] Expressions for constant values such as a call to keccak256()
, should use immutable
rather than constant
While it doesn't save any gas because the compiler knows that developers often make this mistake, it's still best to use the right tool for the task at hand. There is a difference between constant
variables and immutable
variables, and they should each be used in their appropriate contexts. constants
should be used for literal values written into the code, and immutable
variables should be used for expressions, or values calculated in, or passed into the constructor.
There are 3 instances of this issue:
File: contracts/libraries/Internal.sol
70: bytes32 internal constant EVM_2_EVM_MESSAGE_HASH = keccak256("EVM2EVMMessageEvent");
File: contracts/pools/tokens/BurnMintERC677.sol
12: bytes32 private constant MINTER_ROLE = keccak256("MINTER_ROLE");
13: bytes32 private constant BURNER_ROLE = keccak256("BURNER_ROLE");
The delete
keyword more closely matches the semantics of what is being done, and draws more attention to the changing of state, which may lead to a more thorough audit of its associated logic
There are 5 instances of this issue:
File: contracts/ARM.sol
290: curserRecord.voteCount = 0;
362: curserRecord.voteCount = 0;
363: curserRecord.cursesHash = 0;
372: s_curseVoteProgress.curseActive = false;
525: s_curserRecords[voter.curseVoteAddr].active = false;
While 100% code coverage does not guarantee that there are no bugs, it often will catch easy-to-find bugs, and will ensure that there are fewer regressions when the code invariably has to be modified. Furthermore, in order to get full coverage, code authors will often have to re-organize their code so that it is more modular, so that each component can be tested separately, which reduces interdependencies between modules and layers, and makes for code that is easier to reason about and audit.
There is one instance of this issue:
File: Various Files
Large code bases, or code with lots of inline-assembly, complicated math, or complicated interactions between multiple contracts, should implement invariant fuzzing tests. Invariant fuzzers such as Echidna require the test writer to come up with invariants which should not be violated under any circumstances, and the fuzzer tests various inputs and function calls to ensure that the invariants always hold. Even code with 100% code coverage can still have bugs due to the order of the operations a user performs, and invariant fuzzers, with properly and extensively-written invariants, can close this testing gap significantly.
There is one instance of this issue:
File: Various Files
See this link for the full details
There are 23 instances of this issue:
File: contracts/OwnerIsCreator.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IARM.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IAny2EVMMessageReceiver.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IAny2EVMOffRamp.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/ICommitStore.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IERC677.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IERC677Receiver.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IEVM2AnyOnRamp.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IPriceRegistry.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IRouter.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IRouterClient.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/ITypeAndVersion.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IWrappedNative.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/automation/ILinkAvailable.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/pools/IPool.sol
2: pragma solidity ^0.8.0;
File: contracts/libraries/Client.sol
2: pragma solidity ^0.8.0;
File: contracts/libraries/Internal.sol
2: pragma solidity ^0.8.0;
File: contracts/libraries/MerkleMultiProof.sol
2: pragma solidity ^0.8.0;
File: contracts/libraries/RateLimiter.sol
2: pragma solidity ^0.8.0;
File: contracts/libraries/USDPriceWith18Decimals.sol
2: pragma solidity ^0.8.0;
File: contracts/ocr/OCR2Abstract.sol
2: pragma solidity ^0.8.0;
File: contracts/ocr/OCR2Base.sol
2: pragma solidity ^0.8.0;
File: contracts/ocr/OCR2BaseNoChecks.sol
2: pragma solidity ^0.8.0;
Avoids a Gsset (20000 gas) in the constructor, and replaces the first access in each transaction (Gcoldsload - 2100 gas) and each access thereafter (Gwarmacces - 100 gas) with a PUSH32
(3 gas).
While string
s are not value types, and therefore cannot be immutable
/constant
if not hard-coded outside of the constructor, the same behavior can be achieved by making the current contract abstract
with virtual
functions for the string
accessors, and having a child contract override the functions with the hard-coded implementation-specific values.
There are 2 instances of this issue:
File: contracts/AggregateRateLimiter.sol
/// @audit s_rateLimiter (constructor)
30: s_rateLimiter = RateLimiter.TokenBucket({
File: contracts/pools/ThirdPartyBurnMintTokenPool.sol
/// @audit s_router (constructor)
27: s_router = router;
Each slot saved can avoid an extra Gsset (20000 gas) for the first setting of the struct. Subsequent reads as well as writes have smaller gas savings
There are 2 instances of this issue:
File: contracts/ARM.sol
/// @audit Variable ordering with 2 slots instead of the current 3:
/// bytes32(32):cursesHash, address(20):curseVoteAddr, bool(1):forceUnvote
110 struct UnvoteToCurseRecord {
111 address curseVoteAddr;
112 bytes32 cursesHash;
113 bool forceUnvote;
114: }
File: contracts/libraries/Internal.sol
/// @audit Variable ordering with 8 slots instead of the current 9:
/// uint256(32):feeTokenAmount, uint256(32):gasLimit, bytes(32):data, user-defined[](32):tokenAmounts, bytes32(32):messageId, address(20):sender, uint64(8):sourceChainSelector, bool(1):strict, address(20):receiver, uint64(8):sequenceNumber, address(20):feeToken, uint64(8):nonce
41 struct EVM2EVMMessage {
42 uint64 sourceChainSelector;
43 uint64 sequenceNumber;
44 uint256 feeTokenAmount;
45 address sender;
46 uint64 nonce;
47 uint256 gasLimit;
48 bool strict;
49 // User fields
50 address receiver;
51 bytes data;
52 Client.EVMTokenAmount[] tokenAmounts;
53 address feeToken;
54 bytes32 messageId;
55: }
When fetching data from a storage location, assigning the data to a memory
variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (2100 gas) for each field of the struct/array. If the fields are read from the new memory variable, they incur an additional MLOAD
rather than a cheap stack read. Instead of declearing the variable with the memory
keyword, declaring the variable with the storage
keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incuring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into a memory
variable, is if the full struct/array is being returned by the function, is being passed to a function that requires memory
, or if the array/struct is being read from another memory
array/struct
There are 19 instances of this issue:
File: contracts/ARM.sol
205: BlesserRecord memory blesserRecord = s_blesserRecords[msg.sender];
211: BlessVoteProgress memory voteProgress = s_blessVoteProgressByTaggedRootHash[taggedRootHash];
259: BlessVoteProgress memory voteProgress = s_blessVoteProgressByTaggedRootHash[taggedRootHash];
298: CurserRecord memory curserRecord = s_curserRecords[msg.sender];
344: CurserRecord memory curserRecord = s_curserRecords[unvoteRecord.curseVoteAddr];
416: BlessVoteProgress memory progress = s_blessVoteProgressByTaggedRootHash[taggedRootHash];
422: Voter[] memory voters = s_versionedConfig.config.voters;
448: Voter[] memory voters = s_versionedConfig.config.voters;
450: CurserRecord memory curserRecord = s_curserRecords[voters[i].curseVoteAddr];
461: CurserRecord memory curserRecord = s_curserRecords[curseVoteAddr];
515: Config memory oldConfig = s_versionedConfig.config;
523: Voter memory voter = s_versionedConfig.config.voters[s_versionedConfig.config.voters.length - 1];
565: CurserRecord memory curserRecord = s_curserRecords[curseVoteAddr];
File: contracts/ocr/OCR2BaseNoChecks.sol
180: Oracle memory transmitter = s_oracles[msg.sender];
File: contracts/ocr/OCR2Base.sol
193: ConfigInfo memory configInfo = s_configInfo;
205: Oracle memory transmitter = s_oracles[msg.sender];
228: Oracle memory oracle = s_oracles[signer];
File: contracts/offRamp/EVM2EVMOffRamp.sol
243: Internal.EVM2EVMMessage memory message = decodedMessages[i];
File: contracts/onRamp/EVM2EVMOnRamp.sol
501: TokenTransferFeeConfig memory transferFeeConfig = s_tokenTransferFeeConfig[tokenAmount.token];
Using the addition operator instead of plus-equals saves 113 gas
There are 2 instances of this issue:
File: contracts/onRamp/EVM2EVMOnRamp.sol
280: s_nopFeesJuels += uint96(feeTokenAmount);
283 s_nopFeesJuels += uint96(
284 IPriceRegistry(s_dynamicConfig.priceRegistry).convertTokenAmount(message.feeToken, feeTokenAmount, i_linkToken)
285: );
Not inlining costs 20 to 40 gas because of two extra JUMP
instructions and additional stack operations needed for function calls.
There are 9 instances of this issue:
File: contracts/ARM.sol
180: function _bitmapSet(uint128 bitmap, uint8 index) internal pure returns (uint128) {
185: function _bitmapCount(uint128 bitmap) internal pure returns (uint8 oneBits) {
471: function _validateConfig(Config memory config) internal pure returns (bool) {
File: contracts/offRamp/EVM2EVMOffRamp.sol
348 function _trialExecute(
349 Internal.EVM2EVMMessage memory message,
350 bytes[] memory offchainTokenData,
351 bool manualExecution
352: ) internal returns (Internal.MessageExecutionState) {
408: function _metadataHash(bytes32 prefix) internal view returns (bytes32) {
548 function _releaseOrMintTokens(
549 Client.EVMTokenAmount[] memory sourceTokenAmounts,
550 bytes memory originalSender,
551 address receiver,
552 bytes[] memory offchainTokenData
553: ) internal returns (Client.EVMTokenAmount[] memory) {
File: contracts/onRamp/EVM2EVMOnRamp.sol
345 function _validateMessage(
346 uint256 dataLength,
347 uint256 gasLimit,
348 Client.EVMTokenAmount[] calldata tokenAmounts,
349: address originalSender
488 function _getTokenTransferFee(
489 address feeToken,
490 uint192 feeTokenPrice,
491 Client.EVMTokenAmount[] calldata tokenAmounts
492: ) internal view returns (uint256 feeTokenAmount) {
File: contracts/Router.sol
168 function _callWithExactGas(
169 uint16 gasForCallExactCheck,
170 uint256 gasAmount,
171 address target,
172 bytes memory data
173: ) internal returns (bool success, bytes memory retData) {
[G‑07] Add unchecked {}
for subtractions where the operands cannot underflow because of a previous require()
or if
-statement
require(a <= b); x = b - a
=> require(a <= b); unchecked { x = b - a }
There is one instance of this issue:
File: contracts/libraries/RateLimiter.sol
/// @audit if-condition on line 61
65: revert RateLimitReached(((requestTokens - tokens) + (rate - 1)) / rate);
The overheads outlined below are PER LOOP, excluding the first loop
- storage arrays incur a Gwarmaccess (100 gas)
- memory arrays use
MLOAD
(3 gas) - calldata arrays use
CALLDATALOAD
(3 gas)
Caching the length changes each of these to a DUP<N>
(3 gas), and gets rid of the extra DUP<N>
needed to store the stack offset
There are 41 instances of this issue:
File: contracts/ARM.sol
208: for (uint256 i = 0; i < taggedRoots.length; ++i) {
257: for (uint256 i = 0; i < taggedRoots.length; ++i) {
342: for (uint256 i = 0; i < unvoteRecords.length; ++i) {
424: for (uint256 i = 0; i < voters.length; ++i) {
449: for (uint256 i = 0; i < voters.length; ++i) {
459: for (uint256 i = 0; i < voters.length; ++i) {
484: for (uint256 i = 0; i < config.voters.length; ++i) {
500: for (uint256 i = 0; i < allAddrs.length; ++i) {
501: for (uint256 j = i + 1; j < allAddrs.length; ++j) {
528: for (uint256 i = 0; i < config.voters.length; ++i) {
536: for (uint8 i = 0; i < config.voters.length; ++i) {
561: for (uint8 i = 0; i < oldConfig.voters.length; ++i) {
File: contracts/CommitStore.sol
121: for (uint256 i = 0; i < rootToReset.length; ++i) {
File: contracts/offRamp/EVM2EVMOffRamp.sol
146: for (uint256 i = 0; i < sourceTokens.length; ++i) {
464: for (uint256 i = 0; i < sourceTokens.length; ++i) {
501: for (uint256 i = 0; i < destTokens.length; ++i) {
514: for (uint256 i = 0; i < removes.length; ++i) {
529: for (uint256 i = 0; i < adds.length; ++i) {
555: for (uint256 i = 0; i < sourceTokenAmounts.length; ++i) {
File: contracts/onRamp/EVM2EVMOnRamp.sol
227: for (uint256 i = 0; i < tokensAndPools.length; ++i) {
313: for (uint256 i = 0; i < message.tokenAmounts.length; ++i) {
417: for (uint256 i = 0; i < sourceTokens.length; ++i) {
435: for (uint256 i = 0; i < removes.length; ++i) {
447: for (uint256 i = 0; i < adds.length; ++i) {
549: for (uint256 i = 0; i < feeTokenConfigs.length; ++i) {
743: for (uint256 i = 0; i < removes.length; ++i) {
749: for (uint256 i = 0; i < adds.length; ++i) {
File: contracts/pools/ThirdPartyBurnMintTokenPool.sol
35: for (uint256 i = 0; i < onRamps.length; ++i) {
44: for (uint256 i = 0; i < offRamps.length; ++i) {
File: contracts/pools/TokenPool.sol
89: for (uint256 i = 0; i < onRamps.length; ++i) {
97: for (uint256 i = 0; i < offRamps.length; ++i) {
File: contracts/pools/USDC/USDCTokenPool.sol
156: for (uint256 i = 0; i < domains.length; ++i) {
File: contracts/PriceRegistry.sol
179: for (uint256 i = 0; i < feeTokensToAdd.length; ++i) {
184: for (uint256 i = 0; i < feeTokensToRemove.length; ++i) {
257: for (uint256 i = 0; i < priceUpdatersToAdd.length; ++i) {
262: for (uint256 i = 0; i < priceUpdatersToRemove.length; ++i) {
File: contracts/Router.sol
121: for (uint256 i = 0; i < message.tokenAmounts.length; ++i) {
238: for (uint256 i = 0; i < offRamps.length; ++i) {
261: for (uint256 i = 0; i < onRampUpdates.length; ++i) {
268: for (uint256 i = 0; i < offRampRemoves.length; ++i) {
273: for (uint256 i = 0; i < offRampAdds.length; ++i) {
[G‑09] ++i
/i++
should be unchecked{++i}
/unchecked{i++}
when it is not possible for them to overflow, as is the case when used in for
- and while
-loops
The unchecked
keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas per loop
There are 60 instances of this issue:
File: contracts/AggregateRateLimiter.sol
45: for (uint256 i = 0; i < numberOfTokens; ++i) {
File: contracts/ARM.sol
187: for (; bitmap != 0; ++oneBits) {
208: for (uint256 i = 0; i < taggedRoots.length; ++i) {
257: for (uint256 i = 0; i < taggedRoots.length; ++i) {
342: for (uint256 i = 0; i < unvoteRecords.length; ++i) {
424: for (uint256 i = 0; i < voters.length; ++i) {
449: for (uint256 i = 0; i < voters.length; ++i) {
459: for (uint256 i = 0; i < voters.length; ++i) {
484: for (uint256 i = 0; i < config.voters.length; ++i) {
500: for (uint256 i = 0; i < allAddrs.length; ++i) {
501: for (uint256 j = i + 1; j < allAddrs.length; ++j) {
528: for (uint256 i = 0; i < config.voters.length; ++i) {
536: for (uint8 i = 0; i < config.voters.length; ++i) {
561: for (uint8 i = 0; i < oldConfig.voters.length; ++i) {
File: contracts/CommitStore.sol
121: for (uint256 i = 0; i < rootToReset.length; ++i) {
File: contracts/ocr/OCR2BaseNoChecks.sol
99: for (uint256 i = 0; i < oldTransmitterLength; ++i) {
104: for (uint256 i = 0; i < newTransmitterLength; ++i) {
File: contracts/ocr/OCR2Base.sol
112: for (uint256 i = 0; i < oldSignerLength; ++i) {
118: for (uint256 i = 0; i < newSignersLength; ++i) {
226: for (uint256 i = 0; i < numberOfSignatures; ++i) {
File: contracts/offRamp/EVM2EVMOffRamp.sol
146: for (uint256 i = 0; i < sourceTokens.length; ++i) {
225: for (uint256 i = 0; i < numMsgs; ++i) {
242: for (uint256 i = 0; i < numMsgs; ++i) {
464: for (uint256 i = 0; i < sourceTokens.length; ++i) {
501: for (uint256 i = 0; i < destTokens.length; ++i) {
514: for (uint256 i = 0; i < removes.length; ++i) {
529: for (uint256 i = 0; i < adds.length; ++i) {
555: for (uint256 i = 0; i < sourceTokenAmounts.length; ++i) {
File: contracts/onRamp/EVM2EVMOnRamp.sol
227: for (uint256 i = 0; i < tokensAndPools.length; ++i) {
313: for (uint256 i = 0; i < message.tokenAmounts.length; ++i) {
417: for (uint256 i = 0; i < sourceTokens.length; ++i) {
435: for (uint256 i = 0; i < removes.length; ++i) {
447: for (uint256 i = 0; i < adds.length; ++i) {
499: for (uint256 i = 0; i < numerOfTokens; ++i) {
549: for (uint256 i = 0; i < feeTokenConfigs.length; ++i) {
578: for (uint256 i = 0; i < numerOfTokens; ++i) {
604: for (uint256 i = 0; i < length; ++i) {
639: for (uint256 i = 0; i < numberOfNops; ++i) {
661: for (uint256 i = 0; i < numberOfNops; ++i) {
726: for (uint256 i = 0; i < s_allowList.length(); ++i) {
743: for (uint256 i = 0; i < removes.length; ++i) {
749: for (uint256 i = 0; i < adds.length; ++i) {
File: contracts/pools/ThirdPartyBurnMintTokenPool.sol
35: for (uint256 i = 0; i < onRamps.length; ++i) {
44: for (uint256 i = 0; i < offRamps.length; ++i) {
File: contracts/pools/TokenPool.sol
89: for (uint256 i = 0; i < onRamps.length; ++i) {
97: for (uint256 i = 0; i < offRamps.length; ++i) {
File: contracts/pools/USDC/USDCTokenPool.sol
156: for (uint256 i = 0; i < domains.length; ++i) {
File: contracts/PriceRegistry.sol
87: for (uint256 i = 0; i < length; ++i) {
158: for (uint256 i = 0; i < s_feeTokens.length(); ++i) {
179: for (uint256 i = 0; i < feeTokensToAdd.length; ++i) {
184: for (uint256 i = 0; i < feeTokensToRemove.length; ++i) {
205: for (uint256 i = 0; i < priceUpdatesLength; ++i) {
231: for (uint256 i = 0; i < s_priceUpdaters.length(); ++i) {
257: for (uint256 i = 0; i < priceUpdatersToAdd.length; ++i) {
262: for (uint256 i = 0; i < priceUpdatersToRemove.length; ++i) {
File: contracts/Router.sol
121: for (uint256 i = 0; i < message.tokenAmounts.length; ++i) {
238: for (uint256 i = 0; i < offRamps.length; ++i) {
261: for (uint256 i = 0; i < onRampUpdates.length; ++i) {
268: for (uint256 i = 0; i < offRampRemoves.length; ++i) {
273: for (uint256 i = 0; i < offRampAdds.length; ++i) {
public
/external
function names and public
member variable names can be optimized to save gas. See this link for an example of how it works. Below are the interfaces/abstract contracts that can be optimized so that the most frequently-called functions use the least amount of gas possible during method lookup. Method IDs that have two leading zero bytes can save 128 gas each during deployment, and renaming functions to have lower method IDs will save 22 gas per call, per sorted position shifted
There are 20 instances of this issue:
File: contracts/AggregateRateLimiter.sol
/// @audit currentRateLimiterState(), setRateLimiterConfig(), getTokenLimitAdmin(), setAdmin()
14: contract AggregateRateLimiter is OwnerIsCreator {
File: contracts/ARM.sol
/// @audit voteToBless(), ownerResetBlessVotes(), unvoteToCurse(), voteToCurse(), ownerCurse(), ownerUnvoteToCurse(), setConfig(), getConfigDetails(), getBlessProgress(), getCurseProgress()
9: contract ARM is IARM, OwnerIsCreator, ITypeAndVersion {
File: contracts/CommitStore.sol
/// @audit getExpectedNextSequenceNumber(), setMinSeqNr(), getMerkleRoot(), isBlessed(), resetUnblessedRoots(), getStaticConfig(), getDynamicConfig(), isARMHealthy()
13: contract CommitStore is ICommitStore, ITypeAndVersion, OCR2Base {
File: contracts/interfaces/IARM.sol
/// @audit isBlessed(), isCursed()
5: interface IARM {
File: contracts/interfaces/ICommitStore.sol
/// @audit verify(), getExpectedNextSequenceNumber()
4: interface ICommitStore {
File: contracts/interfaces/IERC677.sol
/// @audit transferAndCall(), transferAndCall()
4: interface IERC677 {
File: contracts/interfaces/IEVM2AnyOnRamp.sol
/// @audit getFee(), getPoolBySourceToken(), getSupportedTokens(), getExpectedNextSequenceNumber(), getSenderNonce(), applyPoolUpdates(), forwardFromRouter()
11: interface IEVM2AnyOnRamp {
File: contracts/interfaces/IPriceRegistry.sol
/// @audit updatePrices(), getTokenPrice(), getValidatedTokenPrice(), getTokenPrices(), getDestinationChainGasPrice(), getFeeTokenAndGasPrices(), convertTokenAmount()
6: interface IPriceRegistry {
File: contracts/interfaces/IRouterClient.sol
/// @audit isChainSupported(), getSupportedTokens(), getFee(), ccipSend()
6: interface IRouterClient {
File: contracts/interfaces/pools/IPool.sol
/// @audit lockOrBurn(), releaseOrMint(), getToken()
8: interface IPool {
File: contracts/ocr/OCR2Abstract.sol
/// @audit setOCR2Config(), latestConfigDetails(), latestConfigDigestAndEpoch(), transmit()
6: abstract contract OCR2Abstract is ITypeAndVersion {
File: contracts/offRamp/EVM2EVMOffRamp.sol
/// @audit getExecutionState(), getSenderNonce(), manuallyExecute(), executeSingleMessage(), getStaticConfig(), getDynamicConfig(), getSupportedTokens(), getPoolBySourceToken(), getDestinationToken(), getPoolByDestToken(), getDestinationTokens(), applyPoolUpdates(), ccipReceive(), isARMHealthy()
30: contract EVM2EVMOffRamp is IAny2EVMOffRamp, AggregateRateLimiter, ITypeAndVersion, OCR2BaseNoChecks {
File: contracts/onRamp/EVM2EVMOnRamp.sol
/// @audit getExpectedNextSequenceNumber(), getSenderNonce(), forwardFromRouter(), getStaticConfig(), getDynamicConfig(), setDynamicConfig(), getSupportedTokens(), getPoolBySourceToken(), applyPoolUpdates(), getFee(), getFeeTokenConfig(), setFeeTokenConfig(), getTokenTransferFeeConfig(), setTokenTransferFeeConfig(), getNopFeesJuels(), getNops(), setNops(), payNops(), withdrawNonLinkFees(), linkAvailableForPayment(), getAllowListEnabled(), setAllowListEnabled(), getAllowList(), applyAllowListUpdates(), isARMHealthy()
30: contract EVM2EVMOnRamp is IEVM2AnyOnRamp, ILinkAvailable, AggregateRateLimiter, ITypeAndVersion {
File: contracts/pools/LockReleaseTokenPool.sol
/// @audit getProvidedLiquidity(), addLiquidity(), removeLiquidity()
14: contract LockReleaseTokenPool is TokenPool {
File: contracts/pools/TokenPool.sol
/// @audit isOnRamp(), isOffRamp(), applyRampUpdates(), currentRateLimiterState(), setRateLimiterConfig()
16: abstract contract TokenPool is IPool, OwnerIsCreator, Pausable, IERC165 {
File: contracts/pools/tokens/BurnMintERC677.sol
/// @audit grantMintAndBurnRoles(), getMinterRole(), getBurnerRole()
11: contract BurnMintERC677 is IBurnMintERC20, ERC677, ERC20Burnable, AccessControlEnumerable {
File: contracts/pools/tokens/ERC677.sol
/// @audit transferAndCall(), transferAndCall()
10: contract ERC677 is IERC677, ERC20 {
File: contracts/pools/USDC/USDCTokenPool.sol
/// @audit getUSDCInterfaceId(), getConfig(), setConfig(), getDomain(), setDomains()
16: contract USDCTokenPool is TokenPool {
File: contracts/PriceRegistry.sol
/// @audit getStalenessThreshold(), getFeeTokens(), applyFeeTokensUpdates(), getPriceUpdaters(), applyPriceUpdatersUpdates()
14: contract PriceRegistry is IPriceRegistry, OwnerIsCreator {
File: contracts/Router.sol
/// @audit getFee(), getSupportedTokens(), isChainSupported(), ccipSend(), getWrappedNative(), setWrappedNative(), getOnRamp(), getOffRamps(), isOffRamp(), applyRampUpdates(), recoverTokens()
22: contract Router is IRouter, IRouterClient, ITypeAndVersion, OwnerIsCreator {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/58f635312aa21f947cae5f8578638a85aa2519f5/contracts/security/ReentrancyGuard.sol#L23-L27
Use uint256(1)
and uint256(2)
for true/false to avoid a Gwarmaccess (100 gas) for the extra SLOAD, and to avoid Gsset (20000 gas) when changing from false
to true
, after having been true
in the past
There are 3 instances of this issue:
File: contracts/CommitStore.sol
70: bool private s_paused = false;
File: contracts/onRamp/EVM2EVMOnRamp.sol
185: bool private s_paused = false;
188: bool private s_allowlistEnabled;
Use a solidity version of at least 0.8.2 to get simple compiler automatic inlining
Use a solidity version of at least 0.8.3 to get better struct packing and cheaper multiple storage reads
Use a solidity version of at least 0.8.4 to get custom errors, which are cheaper at deployment than revert()/require()
strings
Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value
There are 23 instances of this issue:
File: contracts/interfaces/automation/ILinkAvailable.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IAny2EVMMessageReceiver.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IAny2EVMOffRamp.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IARM.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/ICommitStore.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IERC677Receiver.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IERC677.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IEVM2AnyOnRamp.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IPriceRegistry.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IRouterClient.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IRouter.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/ITypeAndVersion.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/IWrappedNative.sol
2: pragma solidity ^0.8.0;
File: contracts/interfaces/pools/IPool.sol
2: pragma solidity ^0.8.0;
File: contracts/libraries/Client.sol
2: pragma solidity ^0.8.0;
File: contracts/libraries/Internal.sol
2: pragma solidity ^0.8.0;
File: contracts/libraries/MerkleMultiProof.sol
2: pragma solidity ^0.8.0;
File: contracts/libraries/RateLimiter.sol
2: pragma solidity ^0.8.0;
File: contracts/libraries/USDPriceWith18Decimals.sol
2: pragma solidity ^0.8.0;
File: contracts/ocr/OCR2Abstract.sol
2: pragma solidity ^0.8.0;
File: contracts/ocr/OCR2BaseNoChecks.sol
2: pragma solidity ^0.8.0;
File: contracts/ocr/OCR2Base.sol
2: pragma solidity ^0.8.0;
File: contracts/OwnerIsCreator.sol
2: pragma solidity ^0.8.0;
Saves 5 gas per loop
There are 2 instances of this issue:
File: contracts/offRamp/EVM2EVMOffRamp.sol
317: s_senderNonce[message.sender]++;
325: s_senderNonce[message.sender]++;
When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.
https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html
Each operation involving a uint8
costs an extra 22-28 gas (depending on whether the other operand is also a variable of type uint8
) as compared to ones involving uint256
, due to the compiler having to clear the higher bits of the memory word before operating on the uint8
, as well as the associated stack operations of doing so. Use a larger size then downcast where needed
There are 20 instances of this issue:
File: contracts/ARM.sol
/// @audit uint128 bitmap
188: bitmap &= bitmap - 1;
/// @audit uint32 version
403: version = s_versionedConfig.configVersion;
/// @audit uint32 blockNumber
404: blockNumber = s_versionedConfig.blockNumber;
/// @audit uint16 accumulatedWeight
419: accumulatedWeight = progress.accumulatedWeight;
/// @audit uint16 accumulatedWeight
445: accumulatedWeight = s_curseVoteProgress.accumulatedWeight;
File: contracts/CommitStore.sol
/// @audit uint64 i_chainSelector
79: i_chainSelector = staticConfig.chainSelector;
/// @audit uint64 i_sourceChainSelector
80: i_sourceChainSelector = staticConfig.sourceChainSelector;
/// @audit uint64 s_minSeqNr
163: s_minSeqNr = report.interval.max + 1;
File: contracts/ocr/OCR2BaseNoChecks.sol
/// @audit uint32 s_latestConfigBlockNumber
127: s_latestConfigBlockNumber = uint32(block.number);
File: contracts/ocr/OCR2Base.sol
/// @audit uint32 s_latestConfigBlockNumber
147: s_latestConfigBlockNumber = uint32(block.number);
File: contracts/offRamp/EVM2EVMOffRamp.sol
/// @audit uint64 i_sourceChainSelector
138: i_sourceChainSelector = staticConfig.sourceChainSelector;
/// @audit uint64 i_chainSelector
139: i_chainSelector = staticConfig.chainSelector;
/// @audit uint64 prevNonce
272: prevNonce = IAny2EVMOffRamp(i_prevOffRamp).getSenderNonce(message.sender);
File: contracts/onRamp/EVM2EVMOnRamp.sol
/// @audit uint64 i_chainSelector
216: i_chainSelector = staticConfig.chainSelector;
/// @audit uint64 i_destChainSelector
217: i_destChainSelector = staticConfig.destChainSelector;
/// @audit uint64 i_defaultTxGasLimit
218: i_defaultTxGasLimit = staticConfig.defaultTxGasLimit;
/// @audit uint96 s_nopFeesJuels
280: s_nopFeesJuels += uint96(feeTokenAmount);
/// @audit uint96 s_nopFeesJuels
283 s_nopFeesJuels += uint96(
284 IPriceRegistry(s_dynamicConfig.priceRegistry).convertTokenAmount(message.feeToken, feeTokenAmount, i_linkToken)
285: );
/// @audit uint192 tokenPrice
508: tokenPrice = IPriceRegistry(s_dynamicConfig.priceRegistry).getValidatedTokenPrice(tokenAmount.token);
/// @audit uint32 nopWeightsTotal
641: nopWeightsTotal += nopsAndWeights[i].weight;
If needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public constants. Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it's used, and not adding another entry to the method ID table
There are 2 instances of this issue:
File: contracts/libraries/Client.sol
30: bytes4 public constant EVM_EXTRA_ARGS_V1_TAG = 0x97a657c9;
File: contracts/Router.sol
45: uint16 public constant MAX_RET_BYTES = 4 + 4 * 32;
Flipping the true
and false
blocks instead saves 3 gas
There is one instance of this issue:
File: contracts/ARM.sol
566 if (!curserRecord.active) {
567 delete s_curserRecords[curseVoteAddr];
568 } else if (curserRecord.active && curserRecord.voteCount > 0) {
569 newCurseVoteProgress.accumulatedWeight += curserRecord.weight;
570 emit ReusedVotesToCurse(
571 configVersion,
572 curseVoteAddr,
573 curserRecord.weight,
574 curserRecord.voteCount,
575 curserRecord.cursesHash,
576 newCurseVoteProgress.accumulatedWeight
577 );
578: }
<x> / 2
is the same as <x> >> 1
. While the compiler uses the SHR
opcode to accomplish both, the version that uses division incurs an overhead of 20 gas due to JUMP
s to and from a compiler utility function that introduces checks which can be avoided by using unchecked {}
around the division by two
There are 2 instances of this issue:
File: contracts/ocr/OCR2Base.sol
200: if (rs.length != (configInfo.n + configInfo.f) / 2 + 1) revert WrongNumberOfSignatures();
File: contracts/offRamp/EVM2EVMOffRamp.sol
395: gasLimit = ((gasleft() - 2 * (16 * message.data.length + GAS_FOR_CALL_EXACT_CHECK)) * 62) / 64;
If the variable is only accessed once, it's cheaper to use the state variable directly that one time, and save the 3 gas the extra stack assignment would spend
There are 2 instances of this issue:
File: contracts/ocr/OCR2BaseNoChecks.sol
126: uint32 previousConfigBlockNumber = s_latestConfigBlockNumber;
File: contracts/ocr/OCR2Base.sol
146: uint32 previousConfigBlockNumber = s_latestConfigBlockNumber;
The code should be refactored such that they no longer exist, or the block should do something useful, such as emitting an event or reverting. If the contract is meant to be extended, the contract should be abstract
and the function signatures be added without any default implementation. If the block is an empty if
-statement block to avoid doing subsequent checks in the else-if/else conditions, the else-if/else conditions should be nested under the negation of the if-statement, because they involve different classes of checks, which may lead to the introduction of errors when the code is later modified (if(x){}else if(y){...}else{...}
=> if(!x){if(y){...}else{...}}
). Empty receive()
/fallback() payable
functions that are not used, can be removed to save deployment gas.
There is one instance of this issue:
File: contracts/offRamp/EVM2EVMOffRamp.sol
353: try this.executeSingleMessage(message, offchainTokenData, manualExecution) {} catch (bytes memory err) {
block.timestamp
and block.number
are added to event information by default so adding them manually wastes gas
There are 4 instances of this issue:
File: contracts/ARM.sol
149: event OwnerCursed(uint256 timestamp);
150: event Cursed(uint32 indexed configVersion, uint256 timestamp);
File: contracts/PriceRegistry.sol
30: event UsdPerUnitGasUpdated(uint64 indexed destChain, uint256 value, uint256 timestamp);
31: event UsdPerTokenUpdated(address indexed token, uint256 value, uint256 timestamp);
If a function modifier such as onlyOwner
is used, the function will revert if a normal user tries to pay the function. Marking the function as payable
will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are
CALLVALUE
(2),DUP1
(3),ISZERO
(3),PUSH2
(3),JUMPI
(10),PUSH1
(3),DUP1
(3),REVERT
(0),JUMPDEST
(1),POP
(2), which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost
There are 44 instances of this issue:
File: contracts/ARM.sol
256: function ownerResetBlessVotes(IARM.TaggedRoot[] memory taggedRoots) external onlyOwner {
330: function ownerCurse() external onlyOwner {
341: function ownerUnvoteToCurse(UnvoteToCurseRecord[] memory unvoteRecords) external onlyOwner {
386: function setConfig(Config memory config) external onlyOwner {
File: contracts/CommitStore.sol
96: function setMinSeqNr(uint64 minSeqNr) external onlyOwner {
120: function resetUnblessedRoots(bytes32[] calldata rootToReset) external onlyOwner {
226: function pause() external onlyOwner {
233: function unpause() external onlyOwner {
File: contracts/ocr/OCR2BaseNoChecks.sol
89 function setOCR2Config(
90 address[] memory signers,
91 address[] memory transmitters,
92 uint8 f,
93 bytes memory onchainConfig,
94 uint64 offchainConfigVersion,
95 bytes memory offchainConfig
96: ) external override checkConfigValid(f) onlyOwner {
File: contracts/ocr/OCR2Base.sol
102 function setOCR2Config(
103 address[] memory signers,
104 address[] memory transmitters,
105 uint8 f,
106 bytes memory onchainConfig,
107 uint64 offchainConfigVersion,
108 bytes memory offchainConfig
109: ) external override checkConfigValid(signers.length, transmitters.length, f) onlyOwner {
File: contracts/offRamp/EVM2EVMOffRamp.sol
510 function applyPoolUpdates(
511 Internal.PoolUpdate[] calldata removes,
512 Internal.PoolUpdate[] calldata adds
513: ) external onlyOwner {
File: contracts/onRamp/EVM2EVMOnRamp.sol
386: function setDynamicConfig(DynamicConfig memory dynamicConfig) external onlyOwner {
431 function applyPoolUpdates(
432 Internal.PoolUpdate[] calldata removes,
433 Internal.PoolUpdate[] calldata adds
434: ) external onlyOwner {
542: function setFeeTokenConfig(FeeTokenConfigArgs[] memory feeTokenConfigs) external onlyOwnerOrAdmin {
568 function setTokenTransferFeeConfig(
569 TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs
570: ) external onlyOwnerOrAdmin {
614: function setNops(NopAndWeight[] calldata nopsAndWeights) external onlyOwner {
651: function payNops() public onlyOwnerOrAdminOrNop {
677: function withdrawNonLinkFees(address feeToken, address to) external onlyOwner {
716: function setAllowListEnabled(bool enabled) external onlyOwner {
736: function applyAllowListUpdates(address[] calldata removes, address[] calldata adds) external onlyOwner {
801: function pause() external onlyOwner {
808: function unpause() external onlyOwner {
File: contracts/pools/BurnMintTokenPool.sol
19 function lockOrBurn(
20 address,
21 bytes calldata,
22 uint256 amount,
23 uint64,
24 bytes calldata
25: ) external override whenNotPaused onlyOnRamp {
33 function releaseOrMint(
34 bytes memory,
35 address receiver,
36 uint256 amount,
37 uint64,
38 bytes memory
39: ) external override whenNotPaused onlyOffRamp {
File: contracts/pools/LockReleaseTokenPool.sol
32 function lockOrBurn(
33 address,
34 bytes calldata,
35 uint256 amount,
36 uint64,
37 bytes calldata
38: ) external override whenNotPaused onlyOnRamp {
45 function releaseOrMint(
46 bytes memory,
47 address receiver,
48 uint256 amount,
49 uint64,
50 bytes memory
51: ) external override whenNotPaused onlyOffRamp {
File: contracts/pools/ThirdPartyBurnMintTokenPool.sol
34: function applyRampUpdates(RampUpdate[] memory onRamps, RampUpdate[] memory offRamps) public override onlyOwner {
File: contracts/pools/TokenPool.sol
88: function applyRampUpdates(RampUpdate[] memory onRamps, RampUpdate[] memory offRamps) public virtual onlyOwner {
124: function setRateLimiterConfig(RateLimiter.Config memory config) public onlyOwner {
147: function pause() external onlyOwner {
152: function unpause() external onlyOwner {
File: contracts/pools/tokens/BurnMintERC677.sol
31: function burn(uint256 amount) public override(IBurnMintERC20, ERC20Burnable) onlyRole(BURNER_ROLE) {
36 function burnFrom(
37 address account,
38 uint256 amount
39: ) public override(IBurnMintERC20, ERC20Burnable) onlyRole(BURNER_ROLE) {
47: function mint(address account, uint256 amount) external override onlyRole(MINTER_ROLE) {
File: contracts/pools/USDC/USDCTokenPool.sol
86 function lockOrBurn(
87 address,
88 bytes calldata destinationReceiver,
89 uint256 amount,
90 uint64 destChainSelector,
91 bytes calldata
92: ) external override whenNotPaused onlyOnRamp {
110 function releaseOrMint(
111 bytes memory,
112 address receiver,
113 uint256 amount,
114 uint64,
115 bytes memory extraData
116: ) external override whenNotPaused onlyOffRamp {
138: function setConfig(USDCConfig memory config) external onlyOwner {
155: function setDomains(DomainUpdate[] calldata domains) external onlyOwner {
File: contracts/PriceRegistry.sol
167 function applyFeeTokensUpdates(
168 address[] memory feeTokensToAdd,
169 address[] memory feeTokensToRemove
170: ) external onlyOwner {
241 function applyPriceUpdatersUpdates(
242 address[] memory priceUpdatersToAdd,
243 address[] memory priceUpdatersToRemove
244: ) external onlyOwner {
File: contracts/Router.sol
138 function routeMessage(
139 Client.Any2EVMMessage calldata message,
140 uint16 gasForCallExactCheck,
141 uint256 gasLimit,
142 address receiver
143: ) external override onlyOffRamp(message.sourceChainSelector) returns (bool) {
224: function setWrappedNative(address wrappedNative) external onlyOwner {
254 function applyRampUpdates(
255 OnRamp[] calldata onRampUpdates,
256 OffRamp[] calldata offRampRemoves,
257 OffRamp[] calldata offRampAdds
258: ) external onlyOwner {
285: function recoverTokens(address tokenAddress, address to, uint256 amount) external onlyOwner {
Payable functions cost less gas to execute, since the compiler does not have to add extra checks to ensure that a payment wasn't provided. A constructor can safely be marked as payable, since only the deployer would be able to pass funds, and the project itself would not pass any funds.
There are 15 instances of this issue:
File: contracts/AggregateRateLimiter.sol
29: constructor(RateLimiter.Config memory config) {
File: contracts/ARM.sol
164: constructor(Config memory config) {
File: contracts/CommitStore.sol
75: constructor(StaticConfig memory staticConfig) OCR2Base() {
File: contracts/offRamp/EVM2EVMOffRamp.sol
125 constructor(
126 StaticConfig memory staticConfig,
127 IERC20[] memory sourceTokens,
128 IPool[] memory pools,
129 RateLimiter.Config memory rateLimiterConfig
130: ) OCR2BaseNoChecks() AggregateRateLimiter(rateLimiterConfig) {
File: contracts/onRamp/EVM2EVMOnRamp.sol
190 constructor(
191 StaticConfig memory staticConfig,
192 DynamicConfig memory dynamicConfig,
193 TokenAndPool[] memory tokensAndPools,
194 address[] memory allowlist,
195 RateLimiter.Config memory rateLimiterConfig,
196 FeeTokenConfigArgs[] memory feeTokenConfigs,
197 TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs,
198 NopAndWeight[] memory nopsAndWeights
199: ) AggregateRateLimiter(rateLimiterConfig) {
File: contracts/OwnerIsCreator.sol
9: constructor() ConfirmedOwner(msg.sender) {}
File: contracts/pools/BurnMintTokenPool.sol
11: constructor(IBurnMintERC20 token, RateLimiter.Config memory rateLimiterConfig) TokenPool(token, rateLimiterConfig) {
File: contracts/pools/LockReleaseTokenPool.sol
25: constructor(IERC20 token, RateLimiter.Config memory rateLimiterConfig) TokenPool(token, rateLimiterConfig) {}
File: contracts/pools/ThirdPartyBurnMintTokenPool.sol
22 constructor(
23 IBurnMintERC20 token,
24 RateLimiter.Config memory rateLimiterConfig,
25 address router
26: ) BurnMintTokenPool(token, rateLimiterConfig) {
File: contracts/pools/TokenPool.sol
44: constructor(IERC20 token, RateLimiter.Config memory rateLimiterConfig) {
File: contracts/pools/tokens/BurnMintERC677.sol
17: constructor(string memory name, string memory symbol, uint8 decimals_) ERC677(name, symbol) {
File: contracts/pools/tokens/ERC677.sol
13: constructor(string memory name, string memory symbol) ERC20(name, symbol) {}
File: contracts/pools/USDC/USDCTokenPool.sol
61 constructor(
62 USDCConfig memory config,
63 IBurnMintERC20 token,
64 RateLimiter.Config memory rateLimiterConfig
65: ) TokenPool(token, rateLimiterConfig) {
File: contracts/PriceRegistry.sol
54 constructor(
55 Internal.PriceUpdates memory priceUpdates,
56 address[] memory priceUpdaters,
57 address[] memory feeTokens,
58: uint32 stalenessThreshold
File: contracts/Router.sol
57: constructor(address wrappedNative) {
Consider changing the variable to be an unnamed one, since the variable is never assigned, nor is it returned by name. If the optimizer is not turned on, leaving the code as it is will also waste gas for the stack variable.
There are 24 instances of this issue:
File: contracts/CommitStore.sol
/// @audit timestamp
131 function verify(
132 bytes32[] calldata hashedLeaves,
133 bytes32[] calldata proofs,
134 uint256 proofFlagBits
135: ) external view override whenNotPaused returns (uint256 timestamp) {
File: contracts/libraries/Client.sol
/// @audit bts
36: function _argsToBytes(EVMExtraArgsV1 memory extraArgs) internal pure returns (bytes memory bts) {
File: contracts/libraries/MerkleMultiProof.sol
/// @audit hash
85: function _hashInternalNode(bytes32 left, bytes32 right) private pure returns (bytes32 hash) {
File: contracts/ocr/OCR2BaseNoChecks.sol
/// @audit configCount
/// @audit blockNumber
/// @audit configDigest
199 function latestConfigDetails()
200 external
201 view
202 override
203: returns (uint32 configCount, uint32 blockNumber, bytes32 configDigest)
/// @audit scanLogs
/// @audit configDigest
/// @audit epoch
209 function latestConfigDigestAndEpoch()
210 external
211 view
212 virtual
213 override
214: returns (bool scanLogs, bytes32 configDigest, uint32 epoch)
File: contracts/ocr/OCR2Base.sol
/// @audit configCount
/// @audit blockNumber
/// @audit configDigest
239 function latestConfigDetails()
240 external
241 view
242 override
243: returns (uint32 configCount, uint32 blockNumber, bytes32 configDigest)
/// @audit scanLogs
/// @audit configDigest
/// @audit epoch
249 function latestConfigDigestAndEpoch()
250 external
251 view
252 virtual
253 override
254: returns (bool scanLogs, bytes32 configDigest, uint32 epoch)
File: contracts/offRamp/EVM2EVMOffRamp.sol
/// @audit nonce
191: function getSenderNonce(address sender) public view returns (uint64 nonce) {
File: contracts/onRamp/EVM2EVMOnRamp.sol
/// @audit dynamicConfig
380: function getDynamicConfig() external view returns (DynamicConfig memory dynamicConfig) {
/// @audit feeTokenConfig
536: function getFeeTokenConfig(address token) external view returns (FeeTokenConfig memory feeTokenConfig) {
/// @audit tokenTransferFeeConfig
560 function getTokenTransferFeeConfig(
561 address token
562: ) external view returns (TokenTransferFeeConfig memory tokenTransferFeeConfig) {
File: contracts/pools/TokenPool.sol
/// @audit token
59: function getToken() public view override returns (IERC20 token) {
File: contracts/pools/tokens/ERC677.sol
/// @audit success
16: function transferAndCall(address to, uint amount, bytes memory data) public returns (bool success) {
File: contracts/PriceRegistry.sol
/// @audit feeTokenPrice
/// @audit gasPriceValue
106 function getFeeTokenAndGasPrices(
107 address feeToken,
108 uint64 destChainSelector
109: ) external view override returns (uint192 feeTokenPrice, uint192 gasPriceValue) {
File: contracts/Router.sol
/// @audit fee
67 function getFee(
68 uint64 destinationChainSelector,
69 Client.EVM2AnyMessage memory message
70: ) external view returns (uint256 fee) {
Judge comments:
| [M‑01] | Direct
supportsInterface()
calls may cause caller to revert | 1 | L || [M‑02] | Contracts are vulnerable to fee-on-transfer accounting-related issues | 1 | L |
| [M‑03] | Excess funds sent via
msg.value
not refunded | 1 | L || [M‑04] | The
owner
is a single point of failure and a centralization risk | 34 | M || [M‑05] | Unsafe use of
transfer()
/transferFrom()
withIERC20
| 1 | M || [M‑06] | Return values of
transfer()
/transferFrom()
not checked | 1 | M || [M‑07] | Don't use
payable.transfer()
/payable.send()
| 1 | L || [L‑01] | Division by zero not prevented | 1 | I |
| [L‑02] | Missing checks for
address(0x0)
when assigning values toaddress
state variables | 9 | L || [L‑03] | Solidity version 0.8.20 may not work on other chains due to
PUSH0
| 23 | I || [L‑04] | Unsafe downcast | 19 | L |
| [L‑05] | Loss of precision | 6 | L |
| [L‑06] | Array lengths not checked | 1 | R |
| [L‑07] | Owner can renounce while system is paused | 3 | L |
| [L‑08] | Missing checks for
address(0x0)
when assigning values toaddress
state variables | 4 | L || [L‑09] |
require()
should be used instead ofassert()
| 3 | L || [L‑10] | External calls in an un-bounded
for-
loop may result in a DOS | 6 | L || [N‑01] | Use OpenZeppelin's
MerkleProof
rather than rolling your own | 1 | I || [N‑02] | Events are missing sender information | 5 | NC |
| [N‑03] | Variables need not be initialized to zero | 67 | R |
| [N‑04] | Consider using named mappings | 18 | NC |
| [N‑05] | Non-
external
/public
variable and function names should begin with an underscore | 88 | R || [N‑06] | Use
abi.encodeCall()
instead ofabi.encodeSignature()
/abi.encodeSelector()
| 2 | L || [N‑07] | Constants in comparisons should appear on the left side | 44 | NC |
| [N‑08] | Variable names don't follow the Solidity style guide | 5 | NC |
| [N‑09] | Contracts containing only utility functions should be made into libraries | 1 | R |
| [N‑10] | Custom error has no error details | 58 | NC |
| [N‑11] | Events may be emitted out of order due to reentrancy | 4 | NC |
| [N‑12] | Imports could be organized more systematically | 4 | NC |
| [N‑13] | Long functions should be refactored into multiple, smaller, functions | 2 | I |
| [N‑14] | Mixed usage of
int
/uint
withint256
/uint256
| 1 | NC || [N‑15] | Unsafe conversion from unsigned to signed values | 1 | I |
| [N‑16] | Adding a
return
statement when the function defines a named return variable, is redundant | 1 | R || [N‑17] |
public
functions not called by the contract should be declaredexternal
instead | 11 | R || [N‑18] |
2**<n> - 1
should be re-written astype(uint<n>).max
| 1 | R || [N‑19] |
constant
s should be defined rather than using magic numbers | 46 | R || [N‑20] | Event is not properly
indexed
| 17 | I || [N‑21] |
require()
/revert()
statements should have descriptive reason strings | 1 | NC || [N‑22] | Non-assembly method available | 1 | NC |
| [N‑23] | Missing event and or timelock for critical parameter change | 2 |
| [N‑24] | Events that mark critical parameter changes should contain both the old and the new value | 3 | NC |
| [N‑25] | Use a more recent version of solidity | 1 |
| [N‑26] | Constant redefined elsewhere | 4 | R |
| [N‑27] | Lines are too long | 12 | NC |
| [N‑28] | Variable names that consist of all capital letters should be reserved for
constant
/immutable
variables | 3 | R || [N‑29] | Non-library/interface files should use fixed compiler versions, not floating ones | 1 | NC |
| [N‑30] | Typos | 1 | NC |
| [N‑31] | Constructor visibility is ignored | 1 |
| [N‑32] | File is missing NatSpec | 5 | NC |
| [N‑33] | NatSpec
@param
is missing | 76 | NC || [N‑34] | NatSpec
@return
argument is missing | 29 | NC || [N‑35] | Function ordering does not follow the Solidity style guide | 43 | NC |
| [N‑36] | Contract does not follow the Solidity style guide's suggested layout ordering | 21 | NC |
| [N‑37] | Control structures do not follow the Solidity Style Guide | 9 | NC |
| [N‑38] | Expressions for constant values such as a call to
keccak256()
, should useimmutable
rather thanconstant
| 3 | I || [N‑39] | Consider using
delete
rather than assigning zero/false to clear values | 5 | I || [N‑40] | Contracts should have full test coverage | 1 | I |
| [N‑41] | Large or complicated code bases should implement invariant tests | 1 | I |
| [G‑01] | Reduce gas usage by moving to Solidity 0.8.19 or later | 23 | - | I |
| [G‑02] | State variables only set in the constructor should be declared
immutable
| 2 | 4194 | L || [G‑03] | Structs can be packed into fewer storage slots | 2 | - | L |
| [G‑04] | Using
storage
instead ofmemory
for structs/arrays saves gas | 19 | 79800 | L || [G‑05] |
<x> += <y>
costs more gas than<x> = <x> + <y>
for state variables | 2 | 226 | NC || [G‑06] |
internal
functions only called once can be inlined to save gas | 9 | 180 | I || [G‑07] | Add
unchecked {}
for subtractions where the operands cannot underflow because of a previousrequire()
orif
-statement | 1 | 85 | NC || [G‑08] |
<array>.length
should not be looked up in every loop of afor
-loop | 41 | 123 | NC || [G‑09] |
++i
/i++
should beunchecked{++i}
/unchecked{i++}
when it is not possible for them to overflow, as is the case when used infor
- andwhile
-loops | 60 | 3600 | NC || [G‑10] | Optimize names to save gas | 20 | 440 | I |
| [G‑11] | Using
bool
s for storage incurs overhead | 3 | 51300 | I || [G‑12] | Use a more recent version of solidity | 23 | - | I |
| [G‑13] |
++i
costs less gas thani++
, especially when it's used infor
-loops (--i
/i--
too) | 2 | 10 | NC || [G‑14] | Usage of
uints
/ints
smaller than 32 bytes (256 bits) incurs overhead | 20 | - | I || [G‑15] | Using
private
rather thanpublic
for constants, saves gas | 2 | - | I || [G‑16] | I |nverting the condition of an
if
-else
-statement wastes gas | 1 | - | I || [G‑17] | Division by two should use bit shifting | 2 | 40 | NC |
| [G‑18] | Stack variable used as a cheaper cache for a state variable is only used once | 2 | 6 | NC |
| [G‑19] | Empty blocks should be removed or emit something | 1 | - | NC |
| [G‑20] | Superfluous event fields | 4 | - | NC |
| [G‑21] | Functions guaranteed to revert when called by normal users can be marked
payable
| 44 | 924 | I || [G‑22] | Constructors can be marked
payable
| 15 | 315 | I || [G‑23] | Not using the named return variables anywhere in the function is confusing | 24 | - | Already awarded |