Issue | Instances | |
---|---|---|
GAS-1 | a = a + b is more gas effective than a += b for state variables (excluding arrays and mappings) |
10 |
GAS-2 | Use assembly to check for address(0) |
1 |
GAS-3 | Using bools for storage incurs overhead | 1 |
GAS-4 | Cache array length outside of loop | 41 |
GAS-5 | Use calldata instead of memory for function arguments that do not get mutated | 6 |
GAS-6 | For Operations that will not overflow, you could use unchecked | 434 |
GAS-7 | Avoid contract existence checks by using low level calls | 1 |
GAS-8 | State variables only set in the constructor should be declared immutable |
1 |
GAS-9 | Functions guaranteed to revert when called by normal users can be marked payable |
3 |
GAS-10 | ++i costs less gas compared to i++ or i += 1 (same for --i vs i-- or i -= 1 ) |
1 |
GAS-11 | Using private rather than public for constants, saves gas |
17 |
GAS-12 | Use shift right/left instead of division/multiplication if possible | 5 |
GAS-13 | Superfluous event fields | 2 |
GAS-14 | Use of this instead of marking as public an external function |
1 |
GAS-15 | Increments/decrements can be unchecked in for-loops | 59 |
GAS-16 | Use != 0 instead of > 0 for unsigned integer comparison | 11 |
GAS-17 | internal functions not called by the contract should be removed |
6 |
[GAS-1] a = a + b
is more gas effective than a += b
for state variables (excluding arrays and mappings)
This saves 16 gas per instance.
Instances (10):
File: ./src/ccip/FeeQuoter.sol
665: tokenTransferFeeUSDWei += uint256(destChainConfig.defaultTokenFeeUSDCents) * 1e16;
666: tokenTransferGas += destChainConfig.defaultTokenDestGasOverhead;
667: tokenTransferBytesOverhead += Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES;
687: tokenTransferGas += transferFeeConfig.destGasOverhead;
688: tokenTransferBytesOverhead += transferFeeConfig.destBytesOverhead;
694: tokenTransferFeeUSDWei += minFeeUSDWei;
700: tokenTransferFeeUSDWei += maxFeeUSDWei;
704: tokenTransferFeeUSDWei += bpsFeeUSDWei;
File: ./src/ccip/MultiAggregateRateLimiter.sol
109: value += _getTokenValue(tokenAmounts[i]);
File: ./src/ccip/ocr/MultiOCR3Base.sol
253: expectedDataLength +=
Saves 6 gas per instance
Instances (1):
File: ./src/ccip/ocr/MultiOCR3Base.sol
218: s_oracles[ocrPluginType][oracle] = Oracle(uint8(i), role);
Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas), and to avoid Gsset (20000 gas) when changing from ‘false’ to ‘true’, after having been ‘true’ in the past. See source.
Instances (1):
File: ./src/ccip/rmn/RMNRemote.sol
80: mapping(address signer => bool exists) private s_signers; // for more gas efficient verify.
If not cached, the solidity compiler will always read the length of the array during each iteration. That is, if it is a storage array, this is an extra sload operation (100 additional extra gas for each iteration except for the first) and if it is a memory array, this is an extra mload operation (3 additional gas for each iteration except for the first).
Instances (41):
File: ./src/ccip/FeeQuoter.sol
431: for (uint256 i = 0; i < feeTokensToRemove.length; ++i) {
436: for (uint256 i = 0; i < feeTokensToAdd.length; ++i) {
486: for (uint256 i; i < tokenPriceFeedUpdates.length; ++i) {
508: for (uint256 i = 0; i < feeds.length; ++i) {
617: for (uint256 i = 0; i < premiumMultiplierWeiPerEthArgs.length; ++i) {
798: for (uint256 i = 0; i < tokenTransferFeeConfigArgs.length; ++i) {
802: for (uint256 j = 0; j < tokenTransferFeeConfigArg.tokenTransferFeeConfigs.length; ++j) {
822: for (uint256 i = 0; i < tokensToUseDefaultFeeConfigs.length; ++i) {
966: for (uint256 i = 0; i < onRampTokenTransfers.length; ++i) {
1021: for (uint256 i = 0; i < destChainConfigArgs.length; ++i) {
File: ./src/ccip/MultiAggregateRateLimiter.sol
107: for (uint256 i = 0; i < tokenAmounts.length; ++i) {
165: for (uint256 i = 0; i < rateLimiterUpdates.length; ++i) {
230: for (uint256 i = 0; i < removes.length; ++i) {
239: for (uint256 i = 0; i < adds.length; ++i) {
File: ./src/ccip/NonceManager.sol
132: for (uint256 i = 0; i < previousRampsArgs.length; ++i) {
File: ./src/ccip/capability/CCIPHome.sol
574: for (uint256 i = 0; i < chainSelectorRemoves.length; ++i) {
587: for (uint256 i = 0; i < chainConfigAdds.length; ++i) {
File: ./src/ccip/ocr/MultiOCR3Base.sol
132: for (uint256 i; i < ocrConfigArgs.length; ++i) {
202: for (uint256 i = 0; i < oracleAddresses.length; ++i) {
212: for (uint256 i = 0; i < oracleAddresses.length; ++i) {
File: ./src/ccip/offRamp/OffRamp.sol
305: for (uint256 tokenIndex = 0; tokenIndex < message.tokenAmounts.length; ++tokenIndex) {
349: for (uint256 i = 0; i < reports.length; ++i) {
745: for (uint256 i = 0; i < sourceTokenAmounts.length; ++i) {
810: for (uint256 i = 0; i < commitReport.merkleRoots.length; ++i) {
935: for (uint256 i = 0; i < s_sourceChainSelectors.length(); ++i) {
955: for (uint256 i = 0; i < sourceChainConfigUpdates.length; ++i) {
File: ./src/ccip/onRamp/OnRamp.sol
217: for (uint256 i = 0; i < message.tokenAmounts.length; ++i) {
238: for (uint256 i = 0; i < newMessage.tokenAmounts.length; ++i) {
366: for (uint256 i = 0; i < destChainConfigArgs.length; ++i) {
432: for (uint256 i = 0; i < allowlistConfigArgsItems.length; ++i) {
440: for (uint256 j = 0; j < allowlistConfigArgs.addedAllowlistedSenders.length; ++j) {
454: for (uint256 j = 0; j < allowlistConfigArgs.removedAllowlistedSenders.length; ++j) {
508: for (uint256 i = 0; i < feeTokens.length; ++i) {
File: ./src/ccip/rmn/RMNHome.sol
349: for (uint256 i = 0; i < staticConfig.nodes.length; ++i) {
350: for (uint256 j = i + 1; j < staticConfig.nodes.length; ++j) {
File: ./src/ccip/rmn/RMNRemote.sol
121: for (uint256 i = 0; i < signatures.length; ++i) {
145: for (uint256 i = 1; i < newConfig.signers.length; ++i) {
157: for (uint256 i = s_config.signers.length; i > 0; --i) {
162: for (uint256 i = 0; i < newConfig.signers.length; ++i) {
213: for (uint256 i = 0; i < subjects.length; ++i) {
237: for (uint256 i = 0; i < subjects.length; ++i) {
When a function with a memory
array is called externally, the abi.decode()
step has to use a for-loop to copy each index of the calldata
to the memory
index. Each iteration of this for-loop costs at least 60 gas (i.e. 60 * <mem_array>.length
). Using calldata
directly bypasses this loop.
If the array is passed to an internal
function which passes the array to another internal function where the array is modified and therefore memory
is used in the external
call, it's still more gas-efficient to use calldata
when the external
function uses modifiers, since the modifiers may prevent the internal functions from being called. Structs have the same overhead as an array of length one.
Saves 60 gas per instance
Instances (6):
File: ./src/ccip/interfaces/IFeeQuoter.sol
34: bytes memory extraArgs,
File: ./src/ccip/interfaces/IMessageInterceptor.sol
17: Client.Any2EVMMessage memory message
23: function onOutboundMessage(uint64 destChainSelector, Client.EVM2AnyMessage memory message) external;
File: ./src/ccip/interfaces/IRMNRemote.sol
21: Internal.MerkleRoot[] memory merkleRoots,
22: Signature[] memory signatures
File: ./src/ccip/ocr/MultiOCR3Base.sol
133: _setOCR3Config(ocrConfigArgs[i]);
Instances (434):
File: ./src/ccip/FeeQuoter.sol
4: import {IReceiver} from "../keystone/interfaces/IReceiver.sol";
5: import {ITypeAndVersion} from "../shared/interfaces/ITypeAndVersion.sol";
6: import {IFeeQuoter} from "./interfaces/IFeeQuoter.sol";
7: import {IPriceRegistry} from "./interfaces/IPriceRegistry.sol";
9: import {KeystoneFeedsPermissionHandler} from "../keystone/KeystoneFeedsPermissionHandler.sol";
10: import {KeystoneFeedDefaultMetadataLib} from "../keystone/lib/KeystoneFeedDefaultMetadataLib.sol";
11: import {AuthorizedCallers} from "../shared/access/AuthorizedCallers.sol";
12: import {AggregatorV3Interface} from "./../shared/interfaces/AggregatorV3Interface.sol";
13: import {Client} from "./libraries/Client.sol";
14: import {Internal} from "./libraries/Internal.sol";
15: import {Pool} from "./libraries/Pool.sol";
16: import {USDPriceWith18Decimals} from "./libraries/USDPriceWith18Decimals.sol";
18: import {EnumerableSet} from "../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/structs/EnumerableSet.sol";
62: address dataFeedAddress; // ─╮ Price feed contract. Can be address(0) to indicate no feed is configured.
63: uint8 tokenDecimals; // │ Decimals of the token, used for both keystone and price feed decimal multiplications.
64: bool isEnabled; // ──────────╯ Whether the token is configured to receive keystone and/or price feed updates.
69: address sourceToken; // Source token to update feed for.
70: TokenPriceFeedConfig feedConfig; // Feed config update data.
77: uint96 maxFeeJuelsPerMsg; // ─╮ Maximum fee that can be charged for a message.
78: address linkToken; // ────────╯ LINK token address.
86: address token; // Token address.
87: uint224 price; // ────╮ Price of the token in USD with 18 decimals.
88: uint32 timestamp; // ─╯ Timestamp of the price update.
93: bool isEnabled; // ──────────────────────────╮ Whether this destination chain is enabled.
94: uint16 maxNumberOfTokensPerMsg; // │ Maximum number of distinct ERC20 tokens transferred per message.
95: uint32 maxDataBytes; // │ Maximum data payload size in bytes.
96: uint32 maxPerMsgGasLimit; // │ Maximum gas limit for messages targeting EVMs.
97: uint32 destGasOverhead; // │ Gas charged on top of the gasLimit to cover destination chain costs.
98: uint16 destGasPerPayloadByte; // │ Destination chain gas charged each byte of `data` payload.
99: uint32 destDataAvailabilityOverheadGas; // │ Data availability gas charged for overhead costs e.g. for OCR.
100: uint16 destGasPerDataAvailabilityByte; // │ Gas units charged per byte of message data that needs availability.
101: uint16 destDataAvailabilityMultiplierBps; // │ Multiplier for data availability gas, multiples of bps, or 0.0001.
103: uint16 defaultTokenFeeUSDCents; // │ Default token fee charged per token transfer.
104: uint32 defaultTokenDestGasOverhead; // ──────╯ Default gas charged to execute a token transfer on the destination chain.
105: uint32 defaultTxGasLimit; //─────────────────╮ Default gas limit for a tx.
106: uint64 gasMultiplierWeiPerEth; // │ Multiplier for gas costs, 1e18 based so 11e17 = 10% extra cost.
107: uint32 networkFeeUSDCents; // │ Flat network fee to charge for messages, multiples of 0.01 USD.
108: uint32 gasPriceStalenessThreshold; // │ The amount of time a gas price can be stale before it is considered invalid (0 means disabled).
109: bool enforceOutOfOrder; // │ Whether to enforce the allowOutOfOrderExecution extraArg value to be true.
110: bytes4 chainFamilySelector; // ──────────────╯ Selector that identifies the destination chain's family. Used to determine the correct validations to perform for the dest chain.
117: uint64 destChainSelector; // Destination chain selector.
118: DestChainConfig destChainConfig; // Config to update for the chain selector.
123: uint32 minFeeUSDCents; // ────╮ Minimum fee to charge per token transfer, multiples of 0.01 USD.
124: uint32 maxFeeUSDCents; // │ Maximum fee to charge per token transfer, multiples of 0.01 USD.
125: uint16 deciBps; // │ Basis points charged on token transfers, multiples of 0.1bps, or 1e-5.
126: uint32 destGasOverhead; // │ Gas charged to execute the token transfer on the destination chain.
128: uint32 destBytesOverhead; // │ the destination pool. Must be >= Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES.
129: bool isEnabled; // ───────────╯ Whether this token has custom transfer fees.
135: address token; // Token address.
136: TokenTransferFeeConfig tokenTransferFeeConfig; // Struct to hold the transfer fee configuration for token transfers.
141: uint64 destChainSelector; // Destination chain selector.
142: TokenTransferFeeConfigSingleTokenArgs[] tokenTransferFeeConfigs; // Array of token transfer fee configurations.
148: uint64 destChainSelector; // ─╮ Destination chain selector.
149: address token; // ────────────╯ Token address.
154: address token; // // ──────────────────╮ Token address.
155: uint64 premiumMultiplierWeiPerEth; // ─╯ Multiplier for destination chain specific premiums.
163: string public constant override typeAndVersion = "FeeQuoter 1.6.0-dev";
245: if (block.timestamp - tokenPrice.timestamp < i_tokenPriceStalenessThreshold) {
279: for (uint256 i = 0; i < length; ++i) {
343: return (fromTokenAmount * _getValidatedTokenPrice(fromToken)) / _getValidatedTokenPrice(toToken);
397: uint256 timePassed = block.timestamp - gasPrice.timestamp;
431: for (uint256 i = 0; i < feeTokensToRemove.length; ++i) {
436: for (uint256 i = 0; i < feeTokensToAdd.length; ++i) {
456: for (uint256 i = 0; i < tokenUpdatesLength; ++i) {
465: for (uint256 i = 0; i < gasUpdatesLength; ++i) {
486: for (uint256 i; i < tokenPriceFeedUpdates.length; ++i) {
508: for (uint256 i = 0; i < feeds.length; ++i) {
564: premiumFee = uint256(destChainConfig.networkFeeUSDCents) * 1e16;
593: destChainConfig.destGasOverhead + (message.data.length * destChainConfig.destGasPerPayloadByte) + tokenTransferGas
594: + _parseEVMExtraArgsFromBytes(message.extraArgs, destChainConfig).gasLimit
595: ) * destChainConfig.gasMultiplierWeiPerEth;
600: return ((premiumFee * s_premiumMultiplierWeiPerEth[message.feeToken]) + executionCost + dataAvailabilityCost)
601: / feeTokenPrice;
617: for (uint256 i = 0; i < premiumMultiplierWeiPerEthArgs.length; ++i) {
659: for (uint256 i = 0; i < numberOfTokens; ++i) {
665: tokenTransferFeeUSDWei += uint256(destChainConfig.defaultTokenFeeUSDCents) * 1e16;
666: tokenTransferGas += destChainConfig.defaultTokenDestGasOverhead;
667: tokenTransferBytesOverhead += Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES;
684: bpsFeeUSDWei = (tokenPrice._calcUSDValueFromTokenAmount(tokenAmount.amount) * transferFeeConfig.deciBps) / 1e5;
687: tokenTransferGas += transferFeeConfig.destGasOverhead;
688: tokenTransferBytesOverhead += transferFeeConfig.destBytesOverhead;
692: uint256 minFeeUSDWei = uint256(transferFeeConfig.minFeeUSDCents) * 1e16;
694: tokenTransferFeeUSDWei += minFeeUSDWei;
698: uint256 maxFeeUSDWei = uint256(transferFeeConfig.maxFeeUSDCents) * 1e16;
700: tokenTransferFeeUSDWei += maxFeeUSDWei;
704: tokenTransferFeeUSDWei += bpsFeeUSDWei;
727: uint8 excessDecimals = dataFeedDecimal + tokenDecimal;
731: rebasedVal = feedValue / (10 ** (excessDecimals - FEE_BASE_DECIMALS));
733: rebasedVal = feedValue * (10 ** (FEE_BASE_DECIMALS - excessDecimals));
760: uint256 dataAvailabilityLengthBytes = Internal.MESSAGE_FIXED_BYTES + messageDataLength
761: + (numberOfTokens * Internal.MESSAGE_FIXED_BYTES_PER_TOKEN) + tokenTransferBytesOverhead;
765: uint256 dataAvailabilityGas = (dataAvailabilityLengthBytes * destChainConfig.destGasPerDataAvailabilityByte)
766: + destChainConfig.destDataAvailabilityOverheadGas;
770: return ((dataAvailabilityGas * dataAvailabilityGasPrice) * destChainConfig.destDataAvailabilityMultiplierBps) * 1e14;
798: for (uint256 i = 0; i < tokenTransferFeeConfigArgs.length; ++i) {
802: for (uint256 j = 0; j < tokenTransferFeeConfigArg.tokenTransferFeeConfigs.length; ++j) {
822: for (uint256 i = 0; i < tokensToUseDefaultFeeConfigs.length; ++i) {
966: for (uint256 i = 0; i < onRampTokenTransfers.length; ++i) {
1021: for (uint256 i = 0; i < destChainConfigArgs.length; ++i) {
File: ./src/ccip/MultiAggregateRateLimiter.sol
4: import {ITypeAndVersion} from "../shared/interfaces/ITypeAndVersion.sol";
5: import {IFeeQuoter} from "./interfaces/IFeeQuoter.sol";
6: import {IMessageInterceptor} from "./interfaces/IMessageInterceptor.sol";
8: import {AuthorizedCallers} from "../shared/access/AuthorizedCallers.sol";
9: import {EnumerableMapAddresses} from "./../shared/enumerable/EnumerableMapAddresses.sol";
10: import {Client} from "./libraries/Client.sol";
11: import {RateLimiter} from "./libraries/RateLimiter.sol";
12: import {USDPriceWith18Decimals} from "./libraries/USDPriceWith18Decimals.sol";
34: uint64 remoteChainSelector; // ─╮ Remote chain selector for which to update the rate limit token mapping.
35: address localToken; // ─────────╯ Token on the chain on which the multi-ARL is deployed.
40: LocalRateLimitToken localTokenArgs; // Local token update args scoped to one remote chain.
41: bytes remoteToken; // Token on the remote chain, for OnRamp - dest, or OffRamp - source.
46: uint64 remoteChainSelector; // ─╮ Remote chain selector to set config for.
47: bool isOutboundLane; // ────────╯ If set to true, represents the outbound message lane (OnRamp), and the inbound message lane otherwise (OffRamp)
48: RateLimiter.Config rateLimiterConfig; // Rate limiter config to set
53: RateLimiter.TokenBucket inboundLaneBucket; // Bucket for the inbound lane (remote -> local).
54: RateLimiter.TokenBucket outboundLaneBucket; // Bucket for the outbound lane (local -> remote).
57: string public constant override typeAndVersion = "MultiAggregateRateLimiter 1.6.0-dev";
107: for (uint256 i = 0; i < tokenAmounts.length; ++i) {
109: value += _getTokenValue(tokenAmounts[i]);
165: for (uint256 i = 0; i < rateLimiterUpdates.length; ++i) {
215: for (uint256 i = 0; i < tokenCount; ++i) {
230: for (uint256 i = 0; i < removes.length; ++i) {
239: for (uint256 i = 0; i < adds.length; ++i) {
File: ./src/ccip/NonceManager.sol
4: import {ITypeAndVersion} from "../shared/interfaces/ITypeAndVersion.sol";
5: import {IEVM2AnyOnRamp} from "./interfaces/IEVM2AnyOnRamp.sol";
6: import {INonceManager} from "./interfaces/INonceManager.sol";
8: import {AuthorizedCallers} from "../shared/access/AuthorizedCallers.sol";
20: address prevOnRamp; // Previous onRamp.
21: address prevOffRamp; // Previous offRamp.
27: uint64 remoteChainSelector; // ──╮ Chain selector.
28: bool overrideExistingRamps; // ──╯ Whether to override existing ramps.
29: PreviousRamps prevRamps; // Previous on/off ramps.
32: string public constant override typeAndVersion = "NonceManager 1.6.0-dev";
52: uint64 outboundNonce = _getOutboundNonce(destChainSelector, sender) + 1;
87: uint64 inboundNonce = _getInboundNonce(sourceChainSelector, sender) + 1;
132: for (uint256 i = 0; i < previousRampsArgs.length; ++i) {
File: ./src/ccip/capability/CCIPHome.sol
4: import {ICapabilityConfiguration} from "../../keystone/interfaces/ICapabilityConfiguration.sol";
6: import {INodeInfoProvider} from "../../keystone/interfaces/INodeInfoProvider.sol";
7: import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol";
9: import {Ownable2StepMsgSender} from "../../shared/access/Ownable2StepMsgSender.sol";
10: import {Internal} from "../libraries/Internal.sol";
12: import {IERC165} from "../../vendor/openzeppelin-solidity/v5.0.2/contracts/interfaces/IERC165.sol";
13: import {EnumerableSet} from "../../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/structs/EnumerableSet.sol";
100: bytes32 p2pId; // Peer2Peer connection ID of the oracle.
101: bytes signerKey; // On-chain signer public key.
102: bytes transmitterKey; // On-chain transmitter public key. Can be set to empty bytes to represent that the node is a signer but not a transmitter.
110: Internal.OCRPluginType pluginType; // ─╮ The plugin that the configuration is for.
111: uint64 chainSelector; // │ The (remote) chain that the configuration is for.
112: uint8 FRoleDON; // │ The "big F" parameter for the role DON.
113: uint64 offchainConfigVersion; // ──────╯ The version of the exec offchain configuration.
114: bytes offrampAddress; // The remote chain offramp address.
115: bytes rmnHomeAddress; // The home chain RMN home address.
116: OCR3Node[] nodes; // Keys & IDs of nodes part of the role DON.
117: bytes offchainConfig; // The offchain configuration for the OCR3 plugin. Protobuf encoded.
129: bytes32[] readers; // The P2P IDs of the readers for the chain. These IDs must be registered in the capabilities registry.
130: uint8 fChain; // The fault tolerance parameter of the chain.
131: bytes config; // The chain configuration. This is kept intentionally opaque so as to add fields in the future if needed.
140: string public constant override typeAndVersion = "CCIPHome 1.6.0-dev";
143: uint256 private constant PREFIX = 0x000a << (256 - 16); // 0x000a00..00
149: uint256 private constant PREFIX_MASK = type(uint256).max << (256 - 16); // 0xFFFF00..00
207: bytes32[] calldata, // nodes.
211: uint64, // config count.
247: uint32 /* donId */
294: for (uint256 i = 0; i < MAX_CONCURRENT_CONFIGS; ++i) {
353: uint32 newVersion = ++s_currentVersion;
487: if (numberOfNodes <= 3 * FRoleDON) revert FTooHigh();
491: for (uint256 i = 0; i < numberOfNodes; ++i) {
497: nonZeroTransmitters++;
511: uint256 minTransmittersLength = 3 * fChain + 1;
542: uint256 startIndex = pageIndex * pageSize;
545: return new ChainConfigArgs[](0); // Return an empty array if pageSize is 0 or pageIndex is out of bounds.
548: uint256 endIndex = startIndex + pageSize;
553: ChainConfigArgs[] memory paginatedChainConfigs = new ChainConfigArgs[](endIndex - startIndex);
555: for (uint256 i = startIndex; i < endIndex; ++i) {
557: paginatedChainConfigs[i - startIndex] =
574: for (uint256 i = 0; i < chainSelectorRemoves.length; ++i) {
587: for (uint256 i = 0; i < chainConfigAdds.length; ++i) {
File: ./src/ccip/interfaces/IFeeQuoter.sol
4: import {Client} from "../libraries/Client.sol";
5: import {Internal} from "../libraries/Internal.sol";
6: import {IPriceRegistry} from "./IPriceRegistry.sol";
File: ./src/ccip/interfaces/IMessageInterceptor.sol
4: import {Client} from "../libraries/Client.sol";
File: ./src/ccip/interfaces/IRMNRemote.sol
4: import {Internal} from "../libraries/Internal.sol";
File: ./src/ccip/libraries/Client.sol
8: address token; // token address on the local chain.
9: uint256 amount; // Amount of tokens.
13: bytes32 messageId; // MessageId corresponding to ccipSend on source.
14: uint64 sourceChainSelector; // Source chain selector.
15: bytes sender; // abi.decode(sender) if coming from an EVM chain.
16: bytes data; // payload sent in original message.
17: EVMTokenAmount[] destTokenAmounts; // Tokens and their amounts in their destination chain representation.
22: bytes receiver; // abi.encode(receiver address) for dest EVM chains.
23: bytes data; // Data payload.
24: EVMTokenAmount[] tokenAmounts; // Token transfers.
25: address feeToken; // Address of feeToken. address(0) means you will send msg.value.
26: bytes extraArgs; // Populate this with _argsToBytes(EVMExtraArgsV2).
File: ./src/ccip/libraries/Internal.sol
4: import {MerkleMultiProof} from "../libraries/MerkleMultiProof.sol";
16: uint16 internal constant MAX_RET_BYTES = 4 + 4 * 32;
30: address sourceToken; // Source token.
31: uint224 usdPerToken; // 1e18 USD per 1e18 of the smallest token denomination.
37: uint64 destChainSelector; // Destination chain selector.
38: uint224 usdPerUnitGas; // 1e18 USD per smallest unit (e.g. wei) of destination chain gas.
43: uint224 value; // ──────╮ Value in uint224, packed.
44: uint32 timestamp; // ───╯ Timestamp of the most recent price update.
63: uint32 destGasAmount; // The amount of gas available for the releaseOrMint and balanceOf calls on the offRamp
69: uint64 sourceChainSelector; // Source chain selector for which the report is submitted.
83: uint256 public constant MESSAGE_FIXED_BYTES = 32 * 15;
95: uint256 public constant MESSAGE_FIXED_BYTES_PER_TOKEN = 32 * (4 + (3 + 2));
198: bytes32 messageId; // Unique identifier for the message, generated with the source chain's encoding scheme (i.e. not necessarily abi.encoded).
199: uint64 sourceChainSelector; // ──╮ the chain selector of the source chain, note: not chainId.
200: uint64 destChainSelector; // │ the chain selector of the destination chain, note: not chainId.
201: uint64 sequenceNumber; // │ sequence number, not unique across lanes.
202: uint64 nonce; // ────────────────╯ nonce for this lane for this sender, not unique across senders/lanes.
216: uint256 amount; // Amount of tokens.
226: address destTokenAddress; // ─╮ Address of destination token
227: uint32 destGasAmount; //──────╯ The amount of gas available for the releaseOrMint and transfer calls on the offRamp.
232: uint256 amount; // Amount of tokens.
239: RampMessageHeader header; // Message header.
240: bytes sender; // sender address on the source chain.
241: bytes data; // arbitrary data payload supplied by the message sender.
242: address receiver; // receiver address on the destination chain.
243: uint256 gasLimit; // user supplied maximum gas amount available for dest chain execution.
244: Any2EVMTokenTransfer[] tokenAmounts; // array of tokens and amounts to transfer.
251: RampMessageHeader header; // Message header.
252: address sender; // sender address on the source chain.
253: bytes data; // arbitrary data payload supplied by the message sender.
254: bytes receiver; // receiver address on the destination chain.
255: bytes extraArgs; // destination-chain specific extra args, such as the gasLimit for EVM chains.
256: address feeToken; // fee token.
257: uint256 feeTokenAmount; // fee token amount.
258: uint256 feeValueJuels; // fee amount in Juels.
259: EVM2AnyTokenTransfer[] tokenAmounts; // array of tokens and amounts to transfer.
270: uint64 sourceChainSelector; // Remote source chain selector that the Merkle Root is scoped to
271: bytes onRampAddress; // Generic onramp address, to support arbitrary sources; for EVM, use abi.encode
272: uint64 minSeqNr; // ─────────╮ Minimum sequence number, inclusive
273: uint64 maxSeqNr; // ─────────╯ Maximum sequence number, inclusive
274: bytes32 merkleRoot; // Merkle root covering the interval & source chain messages
File: ./src/ccip/ocr/MultiOCR3Base.sol
4: import {Ownable2StepMsgSender} from "../../shared/access/Ownable2StepMsgSender.sol";
5: import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol";
50: uint8 F; // ─────────────────────────────╮ maximum number of faulty/dishonest oracles the system can tolerate.
51: uint8 n; // │ number of configured signers.
52: bool isSignatureVerificationEnabled; // ─╯ if true, requires signers and verifies signatures on transmission.
69: uint8 index; // ─╮ Index of oracle in s_signers/s_transmitters.
70: Role role; // ───╯ Role of the address which mapped to this struct.
75: ConfigInfo configInfo; // latest OCR config.
77: address[] signers; // addresses oracles use to sign the reports.
78: address[] transmitters; // addresses oracles use to transmit the reports.
83: bytes32 configDigest; // The new config digest.
84: uint8 ocrPluginType; // ─────────────────╮ OCR plugin type to update config for.
85: uint8 F; // │ Maximum number of faulty/dishonest oracles.
86: bool isSignatureVerificationEnabled; // ─╯ If true, requires signers and verifies signatures on transmission.
87: address[] signers; // signing address of each oracle.
88: address[] transmitters; // the address the oracle sends transactions from.
103: uint16 private constant TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT_NO_SIGNATURES = 4 // function selector.
104: + 3 * 32 // 3 words containing reportContext.
105: + 32 // word containing start location of abiencoded report value.
106: + 32; // word containing length of report.
110: uint16 private constant TRANSMIT_MSGDATA_EXTRA_CONSTANT_LENGTH_COMPONENT_FOR_SIGNATURES = 32 // word containing location start of abiencoded rs value.
111: + 32 // word containing start location of abiencoded ss value.
112: + 32 // rawVs value.
113: + 32 // word containing length rs.
114: + 32; // word containing length of ss.
132: for (uint256 i; i < ocrConfigArgs.length; ++i) {
169: if (signers.length <= 3 * ocrConfigArgs.F) revert InvalidConfig(InvalidConfigErrorType.F_TOO_HIGH);
202: for (uint256 i = 0; i < oracleAddresses.length; ++i) {
212: for (uint256 i = 0; i < oracleAddresses.length; ++i) {
249: uint256 expectedDataLength = uint256(TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT_NO_SIGNATURES) + report.length;
253: expectedDataLength +=
254: TRANSMIT_MSGDATA_EXTRA_CONSTANT_LENGTH_COMPONENT_FOR_SIGNATURES + rs.length * 32 + ss.length * 32;
285: if (rs.length != configInfo.F + 1) revert WrongNumberOfSignatures();
313: for (uint256 i; i < numberOfSignatures; ++i) {
315: address signer = ecrecover(hashedReport, uint8(rawVs[i]) + 27, rs[i], ss[i]);
File: ./src/ccip/offRamp/OffRamp.sol
4: import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol";
5: import {IAny2EVMMessageReceiver} from "../interfaces/IAny2EVMMessageReceiver.sol";
6: import {IFeeQuoter} from "../interfaces/IFeeQuoter.sol";
7: import {IMessageInterceptor} from "../interfaces/IMessageInterceptor.sol";
8: import {INonceManager} from "../interfaces/INonceManager.sol";
9: import {IPoolV1} from "../interfaces/IPool.sol";
10: import {IRMNRemote} from "../interfaces/IRMNRemote.sol";
11: import {IRouter} from "../interfaces/IRouter.sol";
12: import {ITokenAdminRegistry} from "../interfaces/ITokenAdminRegistry.sol";
14: import {CallWithExactGas} from "../../shared/call/CallWithExactGas.sol";
15: import {Client} from "../libraries/Client.sol";
16: import {Internal} from "../libraries/Internal.sol";
17: import {MerkleMultiProof} from "../libraries/MerkleMultiProof.sol";
18: import {Pool} from "../libraries/Pool.sol";
19: import {MultiOCR3Base} from "../ocr/MultiOCR3Base.sol";
21: import {IERC20} from "../../vendor/openzeppelin-solidity/v5.0.2/contracts/token/ERC20/IERC20.sol";
22: import {ERC165Checker} from "../../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/introspection/ERC165Checker.sol";
23: import {EnumerableSet} from "../../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/structs/EnumerableSet.sol";
95: uint64 chainSelector; // ────╮ Destination chainSelector
96: IRMNRemote rmnRemote; // ────╯ RMN Verification Contract
97: address tokenAdminRegistry; // Token admin registry address
98: address nonceManager; // Nonce manager address
103: IRouter router; // ───╮ Local router to use for messages coming from this source chain.
104: bool isEnabled; // │ Flag whether the source chain is enabled or not.
105: uint64 minSeqNr; // ──╯ The min sequence number expected for future messages.
106: bytes onRamp; // OnRamp address on the source chain.
112: IRouter router; // ────────────╮ Local router to use for messages coming from this source chain.
113: uint64 sourceChainSelector; // │ Source chain selector of the config to update.
114: bool isEnabled; // ────────────╯ Flag whether the source chain is enabled or not.
115: bytes onRamp; // OnRamp address on the source chain.
121: address feeQuoter; // ─────────────────────────────╮ FeeQuoter address on the local chain.
122: uint32 permissionLessExecutionThresholdSeconds; // │ Waiting time before manual execution is enabled.
123: bool isRMNVerificationDisabled; // ────────────────╯ Flag whether the RMN verification is disabled or not.
124: address messageInterceptor; // Optional, validates incoming messages (zero address = no interceptor).
130: Internal.PriceUpdates priceUpdates; // Collection of gas and price updates to commit.
131: Internal.MerkleRoot[] merkleRoots; // Collection of merkle roots per source chain to commit.
132: IRMNRemote.Signature[] rmnSignatures; // RMN signatures on the merkle roots.
138: uint256 receiverExecutionGasLimit; // Overrides EVM2EVMMessage.gasLimit.
139: uint32[] tokenGasOverrides; // Overrides EVM2EVMMessage.sourceTokenData.destGasAmount, length must be same as tokenAmounts.
143: string public constant override typeAndVersion = "OffRamp 1.6.0-dev";
209: uint256 private constant MESSAGE_EXECUTION_STATE_MASK = (1 << MESSAGE_EXECUTION_STATE_BIT_WIDTH) - 1;
223: >> ((sequenceNumber % 128) * MESSAGE_EXECUTION_STATE_BIT_WIDTH)
238: uint256 offset = (sequenceNumber % 128) * MESSAGE_EXECUTION_STATE_BIT_WIDTH;
246: s_executionStates[sourceChainSelector][sequenceNumber / 128] = bitmap;
257: return s_executionStates[sourceChainSelector][sequenceNumber / 128];
278: for (uint256 reportIndex = 0; reportIndex < numReports; ++reportIndex) {
288: for (uint256 msgIndex = 0; msgIndex < numMsgs; ++msgIndex) {
305: for (uint256 tokenIndex = 0; tokenIndex < message.tokenAmounts.length; ++tokenIndex) {
349: for (uint256 i = 0; i < reports.length; ++i) {
395: for (uint256 i = 0; i < numMsgs; ++i) {
420: for (uint256 i = 0; i < numMsgs; ++i) {
445: (block.timestamp - timestampCommitted) > s_dynamicConfig.permissionLessExecutionThresholdSeconds;
522: gasStart - gasleft()
685: (uint256 balancePost,) = _getBalanceOfReceiver(receiver, localToken, gasLeft - gasUsedReleaseOrMint);
688: if (balancePost < balancePre || balancePost - balancePre != localAmount) {
723: return (abi.decode(returnData, (uint256)), gasLimit - gasUsed);
745: for (uint256 i = 0; i < sourceTokenAmounts.length; ++i) {
810: for (uint256 i = 0; i < commitReport.merkleRoots.length; ++i) {
836: sourceChainConfig.minSeqNr = root.maxSeqNr + 1;
935: for (uint256 i = 0; i < s_sourceChainSelectors.length(); ++i) {
955: for (uint256 i = 0; i < sourceChainConfigUpdates.length; ++i) {
File: ./src/ccip/onRamp/OnRamp.sol
4: import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol";
5: import {IEVM2AnyOnRampClient} from "../interfaces/IEVM2AnyOnRampClient.sol";
6: import {IFeeQuoter} from "../interfaces/IFeeQuoter.sol";
7: import {IMessageInterceptor} from "../interfaces/IMessageInterceptor.sol";
8: import {INonceManager} from "../interfaces/INonceManager.sol";
9: import {IPoolV1} from "../interfaces/IPool.sol";
10: import {IRMNRemote} from "../interfaces/IRMNRemote.sol";
11: import {IRouter} from "../interfaces/IRouter.sol";
12: import {ITokenAdminRegistry} from "../interfaces/ITokenAdminRegistry.sol";
14: import {Ownable2StepMsgSender} from "../../shared/access/Ownable2StepMsgSender.sol";
15: import {Client} from "../libraries/Client.sol";
16: import {Internal} from "../libraries/Internal.sol";
17: import {Pool} from "../libraries/Pool.sol";
18: import {USDPriceWith18Decimals} from "../libraries/USDPriceWith18Decimals.sol";
20: import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol";
21: import {SafeERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol";
22: import {EnumerableSet} from "../../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/structs/EnumerableSet.sol";
62: uint64 chainSelector; // ────╮ Source chain selector.
63: IRMNRemote rmnRemote; // ────╯ RMN remote address.
64: address nonceManager; // Nonce manager address.
65: address tokenAdminRegistry; // Token admin registry address.
71: address feeQuoter; // FeeQuoter address.
72: bool reentrancyGuardEntered; // Reentrancy protection.
73: address messageInterceptor; // Optional message interceptor to validate messages. Zero address = no interceptor.
74: address feeAggregator; // Fee aggregator address.
75: address allowlistAdmin; // authorized admin to add or remove allowed senders.
82: uint64 sequenceNumber; // ──╮ The last used sequence number.
83: bool allowlistEnabled; // │ True if the allowlist is enabled.
84: IRouter router; // ─────────╯ Local router address that is allowed to send messages to the destination chain.
85: EnumerableSet.AddressSet allowedSendersList; // The list of addresses allowed to send messages.
92: uint64 destChainSelector; // ─╮ Destination chain selector.
93: IRouter router; // │ Source router address.
94: bool allowlistEnabled; // ────╯ True if the allowlist is enabled.
99: uint64 destChainSelector; // ──╮ Destination chain selector.
100: bool allowlistEnabled; // ─────╯ True if the allowlist is enabled.
101: address[] addedAllowlistedSenders; // list of senders to be added to the allowedSendersList.
102: address[] removedAllowlistedSenders; // list of senders to be removed from the allowedSendersList.
106: string public constant override typeAndVersion = "OnRamp 1.6.0-dev";
154: return s_destChainConfigs[destChainSelector].sequenceNumber + 1;
199: sequenceNumber: ++destChainConfig.sequenceNumber,
210: feeValueJuels: 0, // calculated later.
217: for (uint256 i = 0; i < message.tokenAmounts.length; ++i) {
238: for (uint256 i = 0; i < newMessage.tokenAmounts.length; ++i) {
298: destExecData: "" // This is set in the processPoolReturnData function.
366: for (uint256 i = 0; i < destChainConfigArgs.length; ++i) {
432: for (uint256 i = 0; i < allowlistConfigArgsItems.length; ++i) {
440: for (uint256 j = 0; j < allowlistConfigArgs.addedAllowlistedSenders.length; ++j) {
454: for (uint256 j = 0; j < allowlistConfigArgs.removedAllowlistedSenders.length; ++j) {
471: function getPoolBySourceToken(uint64, /*destChainSelector*/ IERC20 sourceToken) public view returns (IPoolV1) {
477: uint64 // destChainSelector
508: for (uint256 i = 0; i < feeTokens.length; ++i) {
File: ./src/ccip/rmn/RMNHome.sol
4: import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol";
6: import {Ownable2StepMsgSender} from "../../shared/access/Ownable2StepMsgSender.sol";
78: bytes32 peerId; // Used for p2p communication.
79: bytes32 offchainPublicKey; // Observations are signed with this public key, and are only verified offchain.
83: uint64 chainSelector; // ─╮ The Source chain selector.
84: uint64 f; // ─────────────╯ Maximum number of faulty observers; f+1 observers required to agree on an observation for this source chain.
85: uint256 observerNodesBitmap; // ObserverNodesBitmap & (1<<i) == (1<<i) iff StaticConfig.nodes[i] is an observer for this source chain.
92: bytes offchainConfig; // Offchain configuration for RMN nodes.
98: bytes offchainConfig; // Offchain configuration for RMN nodes.
110: string public constant override typeAndVersion = "RMNHome 1.6.0-dev";
113: uint256 private constant PREFIX = 0x000b << (256 - 16); // 0x000b00..00.
115: uint256 private constant PREFIX_MASK = type(uint256).max << (256 - 16); // 0xFFFF00..00.
166: for (uint256 i = 0; i < MAX_CONCURRENT_CONFIGS; ++i) {
222: uint32 newVersion = ++s_currentVersion;
295: for (uint256 i = 0; i < MAX_CONCURRENT_CONFIGS; ++i) {
349: for (uint256 i = 0; i < staticConfig.nodes.length; ++i) {
350: for (uint256 j = i + 1; j < staticConfig.nodes.length; ++j) {
368: for (uint256 i = 0; i < numberOfSourceChains; ++i) {
371: for (uint256 j = i + 1; j < numberOfSourceChains; ++j) {
380: if (bitmap & (type(uint256).max >> (256 - numberOfNodes)) != bitmap) {
385: for (; bitmap != 0; ++observersCount) {
386: bitmap &= bitmap - 1;
390: if (observersCount < 2 * currentSourceChain.f + 1) {
File: ./src/ccip/rmn/RMNRemote.sol
4: import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol";
5: import {IRMNRemote} from "../interfaces/IRMNRemote.sol";
7: import {Ownable2StepMsgSender} from "../../shared/access/Ownable2StepMsgSender.sol";
8: import {EnumerableSet} from "../../shared/enumerable/EnumerableSetWithBytes16.sol";
9: import {Internal} from "../libraries/Internal.sol";
43: address onchainPublicKey; // ─╮ For signing reports.
44: uint64 nodeIndex; // ─────────╯ Maps to nodes in home chain config, should be strictly increasing.
49: bytes32 rmnHomeContractConfigDigest; // Digest of the RMNHome contract config.
50: Signer[] signers; // List of signers.
51: uint64 f; // Max number of faulty RMN nodes; f+1 signers are required to verify a report, must configure 2f+1 signers in total.
57: uint256 destChainId; // To guard against chain selector misconfiguration.
58: uint64 destChainSelector; // ────────╮ The chain selector of the destination chain.
59: address rmnRemoteContractAddress; // ─╯ The address of this contract.
60: address offrampAddress; // The address of the offramp on the same chain as this contract.
61: bytes32 rmnHomeContractConfigDigest; // The digest of the RMNHome contract config.
62: Internal.MerkleRoot[] merkleRoots; // The dest lane updates.
68: string public constant override typeAndVersion = "RMNRemote 1.6.0-dev";
80: mapping(address signer => bool exists) private s_signers; // for more gas efficient verify.
103: if (signatures.length < s_config.f + 1) revert ThresholdNotMet();
121: for (uint256 i = 0; i < signatures.length; ++i) {
145: for (uint256 i = 1; i < newConfig.signers.length; ++i) {
146: if (!(newConfig.signers[i - 1].nodeIndex < newConfig.signers[i].nodeIndex)) {
152: if (newConfig.signers.length < 2 * newConfig.f + 1) {
157: for (uint256 i = s_config.signers.length; i > 0; --i) {
158: delete s_signers[s_config.signers[i - 1].onchainPublicKey];
162: for (uint256 i = 0; i < newConfig.signers.length; ++i) {
170: uint32 newConfigCount = ++s_configCount;
213: for (uint256 i = 0; i < subjects.length; ++i) {
237: for (uint256 i = 0; i < subjects.length; ++i) {
Prior to 0.8.10 the compiler inserted extra code, including EXTCODESIZE
(100 gas), to check for contract existence for external function calls. In more recent solidity versions, the compiler will not insert these checks if the external call has a return value. Similar behavior can be achieved in earlier versions by using low-level calls, since low level calls never check for contract existence
Instances (1):
File: ./src/ccip/onRamp/OnRamp.sol
510: uint256 feeTokenBalance = feeToken.balanceOf(address(this));
Variables only set in the constructor and never edited afterwards should be marked as immutable, as it would avoid the expensive storage-writing operation in the constructor (around 20 000 gas per variable) and replace the expensive storage-reading operations (around 2100 gas per reading) to a less expensive value reading (3 gas)
Instances (1):
File: ./src/ccip/ocr/MultiOCR3Base.sol
122: /// @notice Sets offchain reporting protocol configuration incl. participating oracles.
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.
Instances (3):
File: ./src/ccip/capability/CCIPHome.sol
520: function _onlySelfCall() internal view {
File: ./src/ccip/rmn/RMNHome.sol
265: function promoteCandidateAndRevokeActive(bytes32 digestToPromote, bytes32 digestToRevoke) external onlyOwner {
294: function setDynamicConfig(DynamicConfig calldata newDynamicConfig, bytes32 currentDigest) external onlyOwner {
Pre-increments and pre-decrements are cheaper.
For a uint256 i
variable, the following is true with the Optimizer enabled at 10k:
Increment:
i += 1
is the most expensive formi++
costs 6 gas less thani += 1
++i
costs 5 gas less thani++
(11 gas less thani += 1
)
Decrement:
i -= 1
is the most expensive formi--
costs 11 gas less thani -= 1
--i
costs 5 gas less thani--
(16 gas less thani -= 1
)
Note that post-increments (or post-decrements) return the old value before incrementing or decrementing, hence the name post-increment:
uint i = 1;
uint j = 2;
require(j == i++, "This will be false as i is incremented after the comparison");
However, pre-increments (or pre-decrements) return the new value:
uint i = 1;
uint j = 2;
require(j == ++i, "This will be true as i is incremented before the comparison");
In the pre-increment case, the compiler has to create a temporary variable (when used) for returning 1
instead of 2
.
Consider using pre-increments and pre-decrements where they are relevant (meaning: not where post-increments/decrements logic are relevant).
Saves 5 gas per instance
Instances (1):
File: ./src/ccip/capability/CCIPHome.sol
497: nonZeroTransmitters++;
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
Instances (17):
File: ./src/ccip/FeeQuoter.sol
159: uint256 public constant FEE_BASE_DECIMALS = 36;
161: uint256 public constant KEYSTONE_PRICE_DECIMALS = 18;
163: string public constant override typeAndVersion = "FeeQuoter 1.6.0-dev";
File: ./src/ccip/MultiAggregateRateLimiter.sol
57: string public constant override typeAndVersion = "MultiAggregateRateLimiter 1.6.0-dev";
File: ./src/ccip/NonceManager.sol
32: string public constant override typeAndVersion = "NonceManager 1.6.0-dev";
File: ./src/ccip/capability/CCIPHome.sol
140: string public constant override typeAndVersion = "CCIPHome 1.6.0-dev";
File: ./src/ccip/libraries/Client.sol
30: bytes4 public constant EVM_EXTRA_ARGS_V1_TAG = 0x97a657c9;
43: bytes4 public constant EVM_EXTRA_ARGS_V2_TAG = 0x181dcf10;
File: ./src/ccip/libraries/Internal.sol
50: uint8 public constant GAS_PRICE_BITS = 112;
83: uint256 public constant MESSAGE_FIXED_BYTES = 32 * 15;
95: uint256 public constant MESSAGE_FIXED_BYTES_PER_TOKEN = 32 * (4 + (3 + 2));
159: uint256 public constant PRECOMPILE_SPACE = 1024;
263: bytes4 public constant CHAIN_FAMILY_SELECTOR_EVM = 0x2812d52c;
File: ./src/ccip/offRamp/OffRamp.sol
143: string public constant override typeAndVersion = "OffRamp 1.6.0-dev";
File: ./src/ccip/onRamp/OnRamp.sol
106: string public constant override typeAndVersion = "OnRamp 1.6.0-dev";
File: ./src/ccip/rmn/RMNHome.sol
110: string public constant override typeAndVersion = "RMNHome 1.6.0-dev";
File: ./src/ccip/rmn/RMNRemote.sol
68: string public constant override typeAndVersion = "RMNRemote 1.6.0-dev";
While the DIV
/ MUL
opcode uses 5 gas, the SHR
/ SHL
opcode only uses 3 gas. Furthermore, beware that Solidity's division operation also includes a division-by-0 prevention which is bypassed using shifting. Eventually, overflow checks are never performed for shift operations as they are done for arithmetic operations. Instead, the result is always truncated, so the calculation can be unchecked in Solidity version 0.8+
- Use
>> 1
instead of/ 2
- Use
>> 2
instead of/ 4
- Use
<< 3
instead of* 8
- ...
- Use
>> 5
instead of/ 2^5 == / 32
- Use
<< 6
instead of* 2^6 == * 64
TL;DR:
- Shifting left by N is like multiplying by 2^N (Each bits to the left is an increased power of 2)
- Shifting right by N is like dividing by 2^N (Each bits to the right is a decreased power of 2)
Saves around 2 gas + 20 for unchecked per instance
Instances (5):
File: ./src/ccip/libraries/Internal.sol
16: uint16 internal constant MAX_RET_BYTES = 4 + 4 * 32;
File: ./src/ccip/ocr/MultiOCR3Base.sol
104: + 3 * 32 // 3 words containing reportContext.
254: TRANSMIT_MSGDATA_EXTRA_CONSTANT_LENGTH_COMPONENT_FOR_SIGNATURES + rs.length * 32 + ss.length * 32;
File: ./src/ccip/offRamp/OffRamp.sol
246: s_executionStates[sourceChainSelector][sequenceNumber / 128] = bitmap;
257: return s_executionStates[sourceChainSelector][sequenceNumber / 128];
block.timestamp
and block.number
are added to event information by default so adding them manually wastes gas
Instances (2):
File: ./src/ccip/FeeQuoter.sol
49: event UsdPerUnitGasUpdated(uint64 indexed destChain, uint256 value, uint256 timestamp);
50: event UsdPerTokenUpdated(address indexed token, uint256 value, uint256 timestamp);
Using this.
is like making an expensive external call. Consider marking the called function as public
Saves around 2000 gas per instance
Instances (1):
File: ./src/ccip/offRamp/OffRamp.sol
537: try this.executeSingleMessage(message, offchainTokenData, tokenGasOverrides) {}
In Solidity 0.8+, there's a default overflow check on unsigned integers. It's possible to uncheck this in for-loops and save some gas at each iteration, but at the cost of some code readability, as this uncheck cannot be made inline.
The change would be:
- for (uint256 i; i < numIterations; i++) {
+ for (uint256 i; i < numIterations;) {
// ...
+ unchecked { ++i; }
}
These save around 25 gas saved per instance.
The same can be applied with decrements (which should use break
when i == 0
).
The risk of overflow is non-existent for uint256
.
Instances (59):
File: ./src/ccip/FeeQuoter.sol
279: for (uint256 i = 0; i < length; ++i) {
431: for (uint256 i = 0; i < feeTokensToRemove.length; ++i) {
436: for (uint256 i = 0; i < feeTokensToAdd.length; ++i) {
456: for (uint256 i = 0; i < tokenUpdatesLength; ++i) {
465: for (uint256 i = 0; i < gasUpdatesLength; ++i) {
486: for (uint256 i; i < tokenPriceFeedUpdates.length; ++i) {
508: for (uint256 i = 0; i < feeds.length; ++i) {
617: for (uint256 i = 0; i < premiumMultiplierWeiPerEthArgs.length; ++i) {
659: for (uint256 i = 0; i < numberOfTokens; ++i) {
798: for (uint256 i = 0; i < tokenTransferFeeConfigArgs.length; ++i) {
802: for (uint256 j = 0; j < tokenTransferFeeConfigArg.tokenTransferFeeConfigs.length; ++j) {
822: for (uint256 i = 0; i < tokensToUseDefaultFeeConfigs.length; ++i) {
966: for (uint256 i = 0; i < onRampTokenTransfers.length; ++i) {
1021: for (uint256 i = 0; i < destChainConfigArgs.length; ++i) {
File: ./src/ccip/MultiAggregateRateLimiter.sol
107: for (uint256 i = 0; i < tokenAmounts.length; ++i) {
165: for (uint256 i = 0; i < rateLimiterUpdates.length; ++i) {
215: for (uint256 i = 0; i < tokenCount; ++i) {
230: for (uint256 i = 0; i < removes.length; ++i) {
239: for (uint256 i = 0; i < adds.length; ++i) {
File: ./src/ccip/NonceManager.sol
132: for (uint256 i = 0; i < previousRampsArgs.length; ++i) {
File: ./src/ccip/capability/CCIPHome.sol
294: for (uint256 i = 0; i < MAX_CONCURRENT_CONFIGS; ++i) {
491: for (uint256 i = 0; i < numberOfNodes; ++i) {
555: for (uint256 i = startIndex; i < endIndex; ++i) {
574: for (uint256 i = 0; i < chainSelectorRemoves.length; ++i) {
587: for (uint256 i = 0; i < chainConfigAdds.length; ++i) {
File: ./src/ccip/ocr/MultiOCR3Base.sol
132: for (uint256 i; i < ocrConfigArgs.length; ++i) {
202: for (uint256 i = 0; i < oracleAddresses.length; ++i) {
212: for (uint256 i = 0; i < oracleAddresses.length; ++i) {
313: for (uint256 i; i < numberOfSignatures; ++i) {
File: ./src/ccip/offRamp/OffRamp.sol
278: for (uint256 reportIndex = 0; reportIndex < numReports; ++reportIndex) {
288: for (uint256 msgIndex = 0; msgIndex < numMsgs; ++msgIndex) {
305: for (uint256 tokenIndex = 0; tokenIndex < message.tokenAmounts.length; ++tokenIndex) {
349: for (uint256 i = 0; i < reports.length; ++i) {
395: for (uint256 i = 0; i < numMsgs; ++i) {
420: for (uint256 i = 0; i < numMsgs; ++i) {
745: for (uint256 i = 0; i < sourceTokenAmounts.length; ++i) {
810: for (uint256 i = 0; i < commitReport.merkleRoots.length; ++i) {
935: for (uint256 i = 0; i < s_sourceChainSelectors.length(); ++i) {
955: for (uint256 i = 0; i < sourceChainConfigUpdates.length; ++i) {
File: ./src/ccip/onRamp/OnRamp.sol
217: for (uint256 i = 0; i < message.tokenAmounts.length; ++i) {
238: for (uint256 i = 0; i < newMessage.tokenAmounts.length; ++i) {
366: for (uint256 i = 0; i < destChainConfigArgs.length; ++i) {
432: for (uint256 i = 0; i < allowlistConfigArgsItems.length; ++i) {
440: for (uint256 j = 0; j < allowlistConfigArgs.addedAllowlistedSenders.length; ++j) {
454: for (uint256 j = 0; j < allowlistConfigArgs.removedAllowlistedSenders.length; ++j) {
508: for (uint256 i = 0; i < feeTokens.length; ++i) {
File: ./src/ccip/rmn/RMNHome.sol
166: for (uint256 i = 0; i < MAX_CONCURRENT_CONFIGS; ++i) {
295: for (uint256 i = 0; i < MAX_CONCURRENT_CONFIGS; ++i) {
349: for (uint256 i = 0; i < staticConfig.nodes.length; ++i) {
350: for (uint256 j = i + 1; j < staticConfig.nodes.length; ++j) {
368: for (uint256 i = 0; i < numberOfSourceChains; ++i) {
371: for (uint256 j = i + 1; j < numberOfSourceChains; ++j) {
385: for (; bitmap != 0; ++observersCount) {
File: ./src/ccip/rmn/RMNRemote.sol
121: for (uint256 i = 0; i < signatures.length; ++i) {
145: for (uint256 i = 1; i < newConfig.signers.length; ++i) {
157: for (uint256 i = s_config.signers.length; i > 0; --i) {
162: for (uint256 i = 0; i < newConfig.signers.length; ++i) {
213: for (uint256 i = 0; i < subjects.length; ++i) {
237: for (uint256 i = 0; i < subjects.length; ++i) {
Instances (11):
File: ./src/ccip/FeeQuoter.sol
559: if (numberOfTokens > 0) {
573: if (destChainConfig.destDataAvailabilityMultiplierBps > 0) {
674: if (transferFeeConfig.deciBps > 0) {
File: ./src/ccip/MultiAggregateRateLimiter.sol
113: if (value > 0) tokenBucket._consume(value, address(0));
File: ./src/ccip/offRamp/OffRamp.sol
561: if (message.tokenAmounts.length > 0) {
787: if (commitReport.merkleRoots.length > 0) {
793: if (commitReport.priceUpdates.tokenPriceUpdates.length > 0 || commitReport.priceUpdates.gasPriceUpdates.length > 0)
File: ./src/ccip/onRamp/OnRamp.sol
438: if (allowlistConfigArgs.addedAllowlistedSenders.length > 0) {
458: if (allowlistConfigArgs.removedAllowlistedSenders.length > 0) {
512: if (feeTokenBalance > 0) {
File: ./src/ccip/rmn/RMNRemote.sol
157: for (uint256 i = s_config.signers.length; i > 0; --i) {
If the functions are required by an interface, the contract should inherit from that interface and use the override
keyword
Instances (6):
File: ./src/ccip/libraries/Client.sol
36: function _argsToBytes(
54: function _argsToBytes(
File: ./src/ccip/libraries/Internal.sol
106: function _hash(Any2EVMRampMessage memory original, bytes32 metadataHash) internal pure returns (bytes32) {
129: function _hash(EVM2AnyRampMessage memory original, bytes32 metadataHash) internal pure returns (bytes32) {
165: function _validateEVMAddress(
File: ./src/ccip/ocr/MultiOCR3Base.sol
231: // NOTE: If these parameters are changed, expectedMsgDataLength and/or TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT
Issue | Instances | |
---|---|---|
NC-1 | Replace abi.encodeWithSignature and abi.encodeWithSelector with abi.encodeCall which keeps the code typo/type safe |
2 |
NC-2 | Array indices should be referenced via enum s rather than via numeric literals |
2 |
NC-3 | Use string.concat() or bytes.concat() instead of abi.encodePacked |
1 |
NC-4 | constant s should be defined rather than using magic numbers |
19 |
NC-5 | Control structures do not follow the Solidity Style Guide | 99 |
NC-6 | Default Visibility for constants | 2 |
NC-7 | Consider disabling renounceOwnership() |
5 |
NC-8 | Event missing indexed field | 1 |
NC-9 | Function ordering does not follow the Solidity style guide | 1 |
NC-10 | Functions should not be longer than 50 lines | 48 |
NC-11 | Change int to int256 | 2 |
NC-12 | Change uint to uint256 | 2 |
NC-13 | Lack of checks in setters | 1 |
NC-14 | Lines are too long | 1 |
NC-15 | Missing Event for critical parameters change | 1 |
NC-16 | Use a modifier instead of a require/if statement for a special msg.sender actor |
6 |
NC-17 | Adding a return statement when the function defines a named return variable, is redundant |
3 |
NC-18 | Take advantage of Custom Error's return value property | 71 |
NC-19 | Contract does not follow the Solidity style guide's suggested layout ordering | 3 |
NC-20 | Use Underscores for Number Literals (add an underscore every 3 digits) | 1 |
NC-21 | Internal and private variables and functions names should begin with an underscore | 2 |
NC-22 | Event is missing indexed fields |
2 |
NC-23 | Variables need not be initialized to zero | 62 |
[NC-1] Replace abi.encodeWithSignature
and abi.encodeWithSelector
with abi.encodeCall
which keeps the code typo/type safe
When using abi.encodeWithSignature
, it is possible to include a typo for the correct function signature.
When using abi.encodeWithSignature
or abi.encodeWithSelector
, it is also possible to provide parameters that are not of the correct type for the function.
To avoid these pitfalls, it would be best to use abi.encodeCall
instead.
Instances (2):
File: ./src/ccip/libraries/Client.sol
39: return abi.encodeWithSelector(EVM_EXTRA_ARGS_V1_TAG, extraArgs);
57: return abi.encodeWithSelector(EVM_EXTRA_ARGS_V2_TAG, extraArgs);
Instances (2):
File: ./src/ccip/ocr/MultiOCR3Base.sol
248: // one byte per entry in _report
297: /// @param ocrPluginType OCR plugin type to transmit report for.
Solidity version 0.8.4 introduces bytes.concat()
(vs abi.encodePacked(<bytes>,<bytes>)
)
Solidity version 0.8.12 introduces string.concat()
(vs abi.encodePacked(<str>,<str>), which catches concatenation errors (in the event of a
bytes data mixed in the concatenation)
)
Instances (1):
File: ./src/ccip/ocr/MultiOCR3Base.sol
289: bytes32 h = keccak256(abi.encodePacked(keccak256(report), reportContext));
Even assembly can benefit from using readable constants instead of hex/numeric literals
Instances (19):
File: ./src/ccip/capability/CCIPHome.sol
487: if (numberOfNodes <= 3 * FRoleDON) revert FTooHigh();
511: uint256 minTransmittersLength = 3 * fChain + 1;
File: ./src/ccip/libraries/Internal.sol
168: if (encodedAddress.length != 32) revert InvalidEVMAddress(encodedAddress);
File: ./src/ccip/ocr/MultiOCR3Base.sol
104: + 3 * 32 // 3 words containing reportContext.
105: + 32 // word containing start location of abiencoded report value.
106: + 32; // word containing length of report.
111: + 32 // word containing start location of abiencoded ss value.
112: + 32 // rawVs value.
113: + 32 // word containing length rs.
114: + 32; // word containing length of ss.
169: if (signers.length <= 3 * ocrConfigArgs.F) revert InvalidConfig(InvalidConfigErrorType.F_TOO_HIGH);
254: TRANSMIT_MSGDATA_EXTRA_CONSTANT_LENGTH_COMPONENT_FOR_SIGNATURES + rs.length * 32 + ss.length * 32;
315: address signer = ecrecover(hashedReport, uint8(rawVs[i]) + 27, rs[i], ss[i]);
File: ./src/ccip/offRamp/OffRamp.sol
223: >> ((sequenceNumber % 128) * MESSAGE_EXECUTION_STATE_BIT_WIDTH)
238: uint256 offset = (sequenceNumber % 128) * MESSAGE_EXECUTION_STATE_BIT_WIDTH;
246: s_executionStates[sourceChainSelector][sequenceNumber / 128] = bitmap;
257: return s_executionStates[sourceChainSelector][sequenceNumber / 128];
File: ./src/ccip/rmn/RMNHome.sol
390: if (observersCount < 2 * currentSourceChain.f + 1) {
File: ./src/ccip/rmn/RMNRemote.sol
152: if (newConfig.signers.length < 2 * newConfig.f + 1) {
See the control structures section of the Solidity Style Guide
Instances (99):
File: ./src/ccip/FeeQuoter.sol
6: import {IFeeQuoter} from "./interfaces/IFeeQuoter.sol";
110: bytes4 chainFamilySelector; // ──────────────╯ Selector that identifies the destination chain's family. Used to determine the correct validations to perform for the dest chain.
155: uint64 premiumMultiplierWeiPerEth; // ─╯ Multiplier for destination chain specific premiums.
216: if (
318: if (!s_destChainConfigs[destChainSelector].isEnabled) revert DestinationChainNotEnabled(destChainSelector);
354: if (tokenPrice.timestamp == 0 || tokenPrice.value == 0) revert TokenNotSupported(token);
542: if (!destChainConfig.isEnabled) revert DestinationChainNotEnabled(destChainSelector);
543: if (!s_feeTokens.contains(message.feeToken)) revert FeeTokenNotSupported(message.feeToken);
855: if (evmExtraArgs.gasLimit > uint256(destChainConfig.maxPerMsgGasLimit)) revert MessageGasLimitTooHigh();
940: if (msgFeeJuels > i_maxFeeJuelsPerMsg) revert MessageFeeTooHigh(msgFeeJuels, i_maxFeeJuelsPerMsg);
1029: if (
File: ./src/ccip/MultiAggregateRateLimiter.sol
5: import {IFeeQuoter} from "./interfaces/IFeeQuoter.sol";
47: bool isOutboundLane; // ────────╯ If set to true, represents the outbound message lane (OnRamp), and the inbound message lane otherwise (OffRamp)
113: if (value > 0) tokenBucket._consume(value, address(0));
141: uint224 pricePerToken = IFeeQuoter(s_feeQuoter).getTokenPrice(tokenAmount.token).value;
142: if (pricePerToken == 0) revert PriceNotFoundForToken(tokenAmount.token);
File: ./src/ccip/capability/CCIPHome.sol
131: bytes config; // The chain configuration. This is kept intentionally opaque so as to add fields in the future if needed.
220: if (
462: if (cfg.chainSelector == 0) revert ChainSelectorNotSet();
472: if (!s_remoteChainSelectors.contains(cfg.chainSelector)) revert ChainSelectorNotFound(cfg.chainSelector);
486: if (numberOfNodes > MAX_NUM_ORACLES) revert TooManySigners();
487: if (numberOfNodes <= 3 * FRoleDON) revert FTooHigh();
545: return new ChainConfigArgs[](0); // Return an empty array if pageSize is 0 or pageIndex is out of bounds.
File: ./src/ccip/interfaces/IRMNRemote.sol
19: function verify(
File: ./src/ccip/libraries/Client.sol
15: bytes sender; // abi.decode(sender) if coming from an EVM chain.
File: ./src/ccip/libraries/Internal.sol
168: if (encodedAddress.length != 32) revert InvalidEVMAddress(encodedAddress);
198: bytes32 messageId; // Unique identifier for the message, generated with the source chain's encoding scheme (i.e. not necessarily abi.encoded).
255: bytes extraArgs; // destination-chain specific extra args, such as the gasLimit for EVM chains.
File: ./src/ccip/ocr/MultiOCR3Base.sol
52: bool isSignatureVerificationEnabled; // ─╯ if true, requires signers and verifies signatures on transmission.
86: bool isSignatureVerificationEnabled; // ─╯ If true, requires signers and verifies signatures on transmission.
142: if (ocrConfigArgs.F == 0) revert InvalidConfig(InvalidConfigErrorType.F_MUST_BE_POSITIVE);
150: configInfo.isSignatureVerificationEnabled = ocrConfigArgs.isSignatureVerificationEnabled;
158: if (transmitters.length > MAX_NUM_ORACLES) revert InvalidConfig(InvalidConfigErrorType.TOO_MANY_TRANSMITTERS);
159: if (transmitters.length == 0) revert InvalidConfig(InvalidConfigErrorType.NO_TRANSMITTERS);
168: if (signers.length > MAX_NUM_ORACLES) revert InvalidConfig(InvalidConfigErrorType.TOO_MANY_SIGNERS);
169: if (signers.length <= 3 * ocrConfigArgs.F) revert InvalidConfig(InvalidConfigErrorType.F_TOO_HIGH);
172: if (signers.length < transmitters.length) revert InvalidConfig(InvalidConfigErrorType.TOO_MANY_TRANSMITTERS);
217: if (oracle == address(0)) revert OracleCannotBeZeroAddress();
257: if (msg.data.length != expectedDataLength) revert WrongMessageLength(expectedDataLength, msg.data.length);
272: if (
285: if (rs.length != configInfo.F + 1) revert WrongNumberOfSignatures();
286: if (rs.length != ss.length) revert SignaturesOutOfRegistration();
290: _verifySignatures(ocrPluginType, h, rs, ss, rawVs);
302: function _verifySignatures(
318: if (oracle.role != Role.Signer) revert UnauthorizedSigner();
319: if (signed & (0x1 << oracle.index) != 0) revert NonUniqueSignatures();
326: if (i_chainID != block.chainid) revert ForkedChain(i_chainID, block.chainid);
File: ./src/ccip/offRamp/OffRamp.sol
6: import {IFeeQuoter} from "../interfaces/IFeeQuoter.sol";
64: error SignatureVerificationRequiredInCommitPlugin();
65: error SignatureVerificationNotAllowedInExecutionPlugin();
96: IRMNRemote rmnRemote; // ────╯ RMN Verification Contract
123: bool isRMNVerificationDisabled; // ────────────────╯ Flag whether the RMN verification is disabled or not.
181: if (
276: if (numReports != gasLimitOverrides.length) revert ManualExecutionGasLimitMismatch();
286: if (numMsgs != msgGasLimitOverrides.length) revert ManualExecutionGasLimitMismatch();
343: if (reports.length == 0) revert EmptyBatch();
376: if (numMsgs == 0) revert EmptyReport(report.sourceChainSelector);
377: if (numMsgs != report.offchainTokenData.length) revert UnexpectedTokenData();
416: uint256 timestampCommitted = _verify(sourceChainSelector, hashedLeaves, report.proofs, report.proofFlagBits);
417: if (timestampCommitted == 0) revert RootNotCommitted(sourceChainSelector);
429: if (
474: if (
559: if (msg.sender != address(this)) revert CanOnlySelfCall();
597: if (
606: if (!success) revert ReceiverError(returnData);
673: if (!success) revert TokenHandlingError(returnData);
714: if (!success) revert TokenHandlingError(returnData);
788: i_rmnRemote.verify(address(this), commitReport.merkleRoots, commitReport.rmnSignatures);
793: if (commitReport.priceUpdates.tokenPriceUpdates.length > 0 || commitReport.priceUpdates.gasPriceUpdates.length > 0)
802: IFeeQuoter(dynamicConfig.feeQuoter).updatePrices(commitReport.priceUpdates);
806: if (commitReport.merkleRoots.length == 0) revert StaleCommitReport();
829: if (merkleRoot == bytes32(0)) revert InvalidRoot();
865: function _verify(
879: bool isSignatureVerificationEnabled = s_ocrConfigs[ocrPluginType].configInfo.isSignatureVerificationEnabled;
884: revert SignatureVerificationRequiredInCommitPlugin();
893: revert SignatureVerificationNotAllowedInExecutionPlugin();
File: ./src/ccip/onRamp/OnRamp.sol
6: import {IFeeQuoter} from "../interfaces/IFeeQuoter.sol";
83: bool allowlistEnabled; // │ True if the allowlist is enabled.
94: bool allowlistEnabled; // ────╯ True if the allowlist is enabled.
100: bool allowlistEnabled; // ─────╯ True if the allowlist is enabled.
128: if (
166: if (s_dynamicConfig.reentrancyGuardEntered) revert ReentrancyGuardReentrantCall();
173: if (originalSender == address(0)) revert RouterMustSetOriginalSender();
182: if (msg.sender != address(destChainConfig.router)) revert MustBeCalledByRouter();
227: (newMessage.feeValueJuels, isOutOfOrderExecution, convertedExtraArgs, destExecDataPerToken) = IFeeQuoter(
272: if (tokenAndAmount.amount == 0) revert CannotSendZeroTokens();
336: if (
495: if (i_rmnRemote.isCursed(bytes16(uint128(destChainSelector)))) revert CursedByRMN(destChainSelector);
497: return IFeeQuoter(s_dynamicConfig.feeQuoter).getValidatedFee(destChainSelector, message);
File: ./src/ccip/rmn/RMNHome.sol
79: bytes32 offchainPublicKey; // Observations are signed with this public key, and are only verified offchain.
85: uint256 observerNodesBitmap; // ObserverNodesBitmap & (1<<i) == (1<<i) iff StaticConfig.nodes[i] is an observer for this source chain.
File: ./src/ccip/rmn/RMNRemote.sol
51: uint64 f; // Max number of faulty RMN nodes; f+1 signers are required to verify a report, must configure 2f+1 signers in total.
80: mapping(address signer => bool exists) private s_signers; // for more gas efficient verify.
86: if (localChainSelector == 0) revert ZeroValueNotAllowed();
95: function verify(
103: if (signatures.length < s_config.f + 1) revert ThresholdNotMet();
123: if (signerAddress == address(0)) revert InvalidSignature();
124: if (prevAddress >= signerAddress) revert OutOfOrderSignatures();
125: if (!s_signers[signerAddress]) revert UnexpectedSigner();
Some constants are using the default visibility. For readability, consider explicitly declaring them as internal
.
Instances (2):
File: ./src/ccip/rmn/RMNRemote.sol
14: bytes16 constant LEGACY_CURSE_SUBJECT = 0x01000000000000000000000000000000;
19: bytes16 constant GLOBAL_CURSE_SUBJECT = 0x01000000000000000000000000000001;
If the plan for your project does not include eventually giving up all ownership control, consider overwriting OpenZeppelin's Ownable
's renounceOwnership()
function in order to disable it.
Instances (5):
File: ./src/ccip/capability/CCIPHome.sol
67: contract CCIPHome is Ownable2StepMsgSender, ITypeAndVersion, ICapabilityConfiguration, IERC165 {
File: ./src/ccip/ocr/MultiOCR3Base.sol
8: abstract contract MultiOCR3Base is ITypeAndVersion, Ownable2StepMsgSender {
File: ./src/ccip/onRamp/OnRamp.sol
27: contract OnRamp is IEVM2AnyOnRampClient, ITypeAndVersion, Ownable2StepMsgSender {
File: ./src/ccip/rmn/RMNHome.sol
59: contract RMNHome is Ownable2StepMsgSender, ITypeAndVersion {
File: ./src/ccip/rmn/RMNRemote.sol
22: contract RMNRemote is Ownable2StepMsgSender, ITypeAndVersion, IRMNRemote {
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.
Instances (1):
File: ./src/ccip/ocr/MultiOCR3Base.sol
18: event ConfigSet(uint8 ocrPluginType, bytes32 configDigest, address[] signers, address[] transmitters, uint8 F);
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
Instances (1):
File: ./src/ccip/ocr/MultiOCR3Base.sol
1:
Current order:
external setOCR3Configs
internal _setOCR3Config
internal _afterOCR3ConfigSet
internal _clearOracleRoles
internal _assignOracleRoles
internal _transmit
internal _verifySignatures
internal _whenChainNotForked
external latestConfigDetails
Suggested order:
external setOCR3Configs
external latestConfigDetails
internal _setOCR3Config
internal _afterOCR3ConfigSet
internal _clearOracleRoles
internal _assignOracleRoles
internal _transmit
internal _verifySignatures
internal _whenChainNotForked
Overly complex code can make understanding functionality more difficult, try to further modularize your code to ensure readability
Instances (48):
File: ./src/ccip/FeeQuoter.sol
411: function getFeeTokens() external view returns (address[] memory) {
430: function _applyFeeTokensUpdates(address[] memory feeTokensToRemove, address[] memory feeTokensToAdd) private {
501: function onReport(bytes calldata metadata, bytes calldata report) external {
838: function _validateDestFamilyAddress(bytes4 chainFamilySelector, bytes memory destAddress) internal pure {
1052: function getStaticConfig() external view returns (StaticConfig memory) {
File: ./src/ccip/MultiAggregateRateLimiter.sol
257: function getFeeQuoter() external view returns (address feeQuoter) {
File: ./src/ccip/NonceManager.sol
62: function getOutboundNonce(uint64 destChainSelector, address sender) external view returns (uint64) {
66: function _getOutboundNonce(uint64 destChainSelector, address sender) private view returns (uint64) {
105: function getInboundNonce(uint64 sourceChainSelector, bytes calldata sender) external view returns (uint64) {
109: function _getInboundNonce(uint64 sourceChainSelector, bytes calldata sender) private view returns (uint64) {
File: ./src/ccip/capability/CCIPHome.sol
192: function getCapabilityRegistry() external view returns (address) {
273: function getActiveDigest(uint32 donId, Internal.OCRPluginType pluginType) public view returns (bytes32) {
279: function getCandidateDigest(uint32 donId, Internal.OCRPluginType pluginType) public view returns (bytes32) {
369: function revokeCandidate(uint32 donId, Internal.OCRPluginType pluginType, bytes32 configDigest) external {
447: function _getActiveIndex(uint32 donId, Internal.OCRPluginType pluginType) private view returns (uint32) {
451: function _getCandidateIndex(uint32 donId, Internal.OCRPluginType pluginType) private view returns (uint32) {
532: function getNumChainConfigurations() external view returns (uint256) {
540: function getAllChainConfigs(uint256 pageIndex, uint256 pageSize) external view returns (ChainConfigArgs[] memory) {
File: ./src/ccip/interfaces/IMessageInterceptor.sol
23: function onOutboundMessage(uint64 destChainSelector, Client.EVM2AnyMessage memory message) external;
File: ./src/ccip/interfaces/INonceManager.sol
10: function getIncrementedOutboundNonce(uint64 destChainSelector, address sender) external returns (uint64);
File: ./src/ccip/interfaces/IRMNRemote.sol
27: function getCursedSubjects() external view returns (bytes16[] memory subjects);
File: ./src/ccip/libraries/Internal.sol
106: function _hash(Any2EVMRampMessage memory original, bytes32 metadataHash) internal pure returns (bytes32) {
129: function _hash(EVM2AnyRampMessage memory original, bytes32 metadataHash) internal pure returns (bytes32) {
File: ./src/ccip/ocr/MultiOCR3Base.sol
201: function _clearOracleRoles(uint8 ocrPluginType, address[] memory oracleAddresses) internal {
211: function _assignOracleRoles(uint8 ocrPluginType, address[] memory oracleAddresses, Role role) internal {
File: ./src/ccip/offRamp/OffRamp.sol
325: function execute(bytes32[3] calldata reportContext, bytes calldata report) external {
847: function getLatestPriceSequenceNumber() external view returns (uint64) {
856: function getMerkleRoot(uint64 sourceChainSelector, bytes32 root) external view returns (uint256) {
906: function getStaticConfig() external view returns (StaticConfig memory) {
917: function getDynamicConfig() external view returns (DynamicConfig memory) {
932: function getAllSourceChainConfigs() external view returns (uint64[] memory, SourceChainConfig[] memory) {
File: ./src/ccip/onRamp/OnRamp.sol
309: function getStaticConfig() external view returns (StaticConfig memory) {
320: function getDynamicConfig() external view returns (DynamicConfig memory dynamicConfig) {
471: function getPoolBySourceToken(uint64, /*destChainSelector*/ IERC20 sourceToken) public view returns (IPoolV1) {
File: ./src/ccip/rmn/RMNHome.sol
144: function getConfigDigests() external view returns (bytes32 activeConfigDigest, bytes32 candidateConfigDigest) {
149: function getActiveDigest() external view returns (bytes32) {
154: function getCandidateDigest() public view returns (bytes32) {
265: function promoteCandidateAndRevokeActive(bytes32 digestToPromote, bytes32 digestToRevoke) external onlyOwner {
294: function setDynamicConfig(DynamicConfig calldata newDynamicConfig, bytes32 currentDigest) external onlyOwner {
313: function _calculateConfigDigest(bytes memory staticConfig, uint32 version) internal view returns (bytes32) {
324: function _getActiveIndex() private view returns (uint32) {
328: function _getCandidateIndex() private view returns (uint32) {
366: function _validateDynamicConfig(DynamicConfig memory dynamicConfig, uint256 numberOfNodes) internal pure {
File: ./src/ccip/rmn/RMNRemote.sol
177: function getVersionedConfig() external view returns (uint32 version, Config memory config) {
183: function getLocalChainSelector() external view returns (uint64 localChainSelector) {
189: function getReportDigestHeader() external pure returns (bytes32 digestHeader) {
246: function getCursedSubjects() external view returns (bytes16[] memory subjects) {
251: function isCursed() external view returns (bool) {
Throughout the code base, some variables are declared as int
. To favor explicitness, consider changing all instances of int
to int256
Instances (2):
File: ./src/ccip/libraries/Internal.sol
63: uint32 destGasAmount; // The amount of gas available for the releaseOrMint and balanceOf calls on the offRamp
227: uint32 destGasAmount; //──────╯ The amount of gas available for the releaseOrMint and transfer calls on the offRamp.
Throughout the code base, some variables are declared as uint
. To favor explicitness, consider changing all instances of uint
to uint256
Instances (2):
File: ./src/ccip/libraries/Internal.sol
169: uint256 encodedAddressUint = abi.decode(encodedAddress, (uint256));
170: if (encodedAddressUint > type(uint160).max || encodedAddressUint < PRECOMPILE_SPACE) {
Be it sanity checks (like checks against 0
-values) or initial setting checks: it's best for Setter functions to have them
Instances (1):
File: ./src/ccip/ocr/MultiOCR3Base.sol
132: for (uint256 i; i < ocrConfigArgs.length; ++i) {
_setOCR3Config(ocrConfigArgs[i]);
}
}
/// @notice Sets offchain reporting protocol configuration incl. participating oracles for a single OCR plugin type.
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. Since the files will most likely reside in GitHub, and GitHub starts using a scroll bar in all cases when the length is over 164 characters, the lines below should be split when they reach that length
Instances (1):
File: ./src/ccip/FeeQuoter.sol
110: bytes4 chainFamilySelector; // ──────────────╯ Selector that identifies the destination chain's family. Used to determine the correct validations to perform for the dest chain.
Events help non-contract tools to track changes, and events prevent users from being surprised by changes.
Instances (1):
File: ./src/ccip/ocr/MultiOCR3Base.sol
132: for (uint256 i; i < ocrConfigArgs.length; ++i) {
_setOCR3Config(ocrConfigArgs[i]);
}
}
/// @notice Sets offchain reporting protocol configuration incl. participating oracles for a single OCR plugin type.
If a function is supposed to be access-controlled, a modifier
should be used instead of a require/if
statement for more readability.
Instances (6):
File: ./src/ccip/capability/CCIPHome.sol
214: if (msg.sender != i_capabilitiesRegistry) {
521: if (msg.sender != address(this)) {
File: ./src/ccip/offRamp/OffRamp.sol
559: if (msg.sender != address(this)) revert CanOnlySelfCall();
File: ./src/ccip/onRamp/OnRamp.sol
182: if (msg.sender != address(destChainConfig.router)) revert MustBeCalledByRouter();
426: if (msg.sender != owner()) {
427: if (msg.sender != s_dynamicConfig.allowlistAdmin) {
Instances (3):
File: ./src/ccip/libraries/Client.sol
36: function _argsToBytes(
EVMExtraArgsV1 memory extraArgs
) internal pure returns (bytes memory bts) {
return abi.encodeWithSelector(EVM_EXTRA_ARGS_V1_TAG, extraArgs);
54: function _argsToBytes(
EVMExtraArgsV2 memory extraArgs
) internal pure returns (bytes memory bts) {
return abi.encodeWithSelector(EVM_EXTRA_ARGS_V2_TAG, extraArgs);
File: ./src/ccip/ocr/MultiOCR3Base.sol
330: /// @param ocrPluginType OCR plugin type to return config details for.
/// @return ocrConfig OCR config for the plugin type.
function latestConfigDetails(
uint8 ocrPluginType
) external view returns (OCRConfig memory ocrConfig) {
return s_ocrConfigs[ocrPluginType];
}
}
An important feature of Custom Error is that values such as address, tokenID, msg.value can be written inside the () sign, this kind of approach provides a serious advantage in debugging and examining the revert details of dapps such as tenderly.
Instances (71):
File: ./src/ccip/FeeQuoter.sol
220: revert InvalidStaticConfig();
376: revert DataFeedValueOutOfUint224Range();
737: revert DataFeedValueOutOfUint224Range();
855: if (evmExtraArgs.gasLimit > uint256(destChainConfig.maxPerMsgGasLimit)) revert MessageGasLimitTooHigh();
859: revert ExtraArgOutOfOrderExecutionMustBeTrue();
889: revert InvalidExtraArgsTag();
File: ./src/ccip/MultiAggregateRateLimiter.sol
171: revert ZeroChainSelectorNotAllowed();
245: revert ZeroAddressNotAllowed();
277: revert ZeroAddressNotAllowed();
File: ./src/ccip/NonceManager.sol
143: revert PreviousRampAlreadySet();
File: ./src/ccip/capability/CCIPHome.sol
181: revert ZeroAddressNotAllowed();
215: revert OnlyCapabilitiesRegistryCanCall();
238: revert(add(retData, 0x20), returndatasize())
373: revert RevokingZeroDigestNotAllowed();
399: revert NoOpStateTransitionNotAllowed();
462: if (cfg.chainSelector == 0) revert ChainSelectorNotSet();
464: revert InvalidPluginType();
467: revert OfframpAddressCannotBeZero();
470: revert RMNHomeAddressCannotBeZero();
486: if (numberOfNodes > MAX_NUM_ORACLES) revert TooManySigners();
487: if (numberOfNodes <= 3 * FRoleDON) revert FTooHigh();
522: revert CanOnlySelfCall();
596: revert FChainMustBePositive();
File: ./src/ccip/ocr/MultiOCR3Base.sol
217: if (oracle == address(0)) revert OracleCannotBeZeroAddress();
278: revert UnauthorizedTransmitter();
285: if (rs.length != configInfo.F + 1) revert WrongNumberOfSignatures();
286: if (rs.length != ss.length) revert SignaturesOutOfRegistration();
318: if (oracle.role != Role.Signer) revert UnauthorizedSigner();
319: if (signed & (0x1 << oracle.index) != 0) revert NonUniqueSignatures();
File: ./src/ccip/offRamp/OffRamp.sol
185: revert ZeroAddressNotAllowed();
189: revert ZeroChainSelectorNotAllowed();
276: if (numReports != gasLimitOverrides.length) revert ManualExecutionGasLimitMismatch();
286: if (numMsgs != msgGasLimitOverrides.length) revert ManualExecutionGasLimitMismatch();
343: if (reports.length == 0) revert EmptyBatch();
377: if (numMsgs != report.offchainTokenData.length) revert UnexpectedTokenData();
559: if (msg.sender != address(this)) revert CanOnlySelfCall();
806: if (commitReport.merkleRoots.length == 0) revert StaleCommitReport();
829: if (merkleRoot == bytes32(0)) revert InvalidRoot();
884: revert SignatureVerificationRequiredInCommitPlugin();
893: revert SignatureVerificationNotAllowedInExecutionPlugin();
960: revert ZeroChainSelectorNotAllowed();
964: revert ZeroAddressNotAllowed();
984: revert ZeroAddressNotAllowed();
1012: revert ZeroAddressNotAllowed();
1043: revert();
File: ./src/ccip/onRamp/OnRamp.sol
132: revert InvalidConfig();
166: if (s_dynamicConfig.reentrancyGuardEntered) revert ReentrancyGuardReentrantCall();
173: if (originalSender == address(0)) revert RouterMustSetOriginalSender();
182: if (msg.sender != address(destChainConfig.router)) revert MustBeCalledByRouter();
272: if (tokenAndAmount.amount == 0) revert CannotSendZeroTokens();
339: ) revert InvalidConfig();
428: revert OnlyCallableByOwnerOrAllowlistAdmin();
479: revert GetSupportedTokensFunctionalityRemovedCheckAdminRegistry();
File: ./src/ccip/rmn/RMNHome.sol
244: revert RevokingZeroDigestNotAllowed();
267: revert NoOpStateTransitionNotAllowed();
345: revert OutOfBoundsNodesLength();
352: revert DuplicatePeerId();
355: revert DuplicateOffchainPublicKey();
373: revert DuplicateSourceChain();
381: revert OutOfBoundsObserverNodeIndex();
391: revert NotEnoughObservers();
File: ./src/ccip/rmn/RMNRemote.sol
86: if (localChainSelector == 0) revert ZeroValueNotAllowed();
101: revert ConfigNotSet();
103: if (signatures.length < s_config.f + 1) revert ThresholdNotMet();
123: if (signerAddress == address(0)) revert InvalidSignature();
124: if (prevAddress >= signerAddress) revert OutOfOrderSignatures();
125: if (!s_signers[signerAddress]) revert UnexpectedSigner();
141: revert ZeroValueNotAllowed();
147: revert InvalidSignerOrder();
153: revert NotEnoughSigners();
164: revert DuplicateOnchainPublicKey();
The style guide says that, within a contract, the ordering should be:
- Type declarations
- State variables
- Events
- Modifiers
- Functions
However, the contract(s) below do not follow this ordering
Instances (3):
File: ./src/ccip/libraries/Client.sol
1:
Current order:
StructDefinition.EVMTokenAmount
StructDefinition.Any2EVMMessage
StructDefinition.EVM2AnyMessage
VariableDeclaration.EVM_EXTRA_ARGS_V1_TAG
StructDefinition.EVMExtraArgsV1
FunctionDefinition._argsToBytes
VariableDeclaration.EVM_EXTRA_ARGS_V2_TAG
StructDefinition.EVMExtraArgsV2
FunctionDefinition._argsToBytes
Suggested order:
VariableDeclaration.EVM_EXTRA_ARGS_V1_TAG
VariableDeclaration.EVM_EXTRA_ARGS_V2_TAG
StructDefinition.EVMTokenAmount
StructDefinition.Any2EVMMessage
StructDefinition.EVM2AnyMessage
StructDefinition.EVMExtraArgsV1
StructDefinition.EVMExtraArgsV2
FunctionDefinition._argsToBytes
FunctionDefinition._argsToBytes
File: ./src/ccip/libraries/Internal.sol
1:
Current order:
ErrorDefinition.InvalidEVMAddress
VariableDeclaration.GAS_FOR_CALL_EXACT_CHECK
VariableDeclaration.MAX_RET_BYTES
VariableDeclaration.MAX_BALANCE_OF_RET_BYTES
StructDefinition.PriceUpdates
StructDefinition.TokenPriceUpdate
StructDefinition.GasPriceUpdate
StructDefinition.TimestampedPackedUint224
VariableDeclaration.GAS_PRICE_BITS
StructDefinition.SourceTokenData
StructDefinition.ExecutionReport
VariableDeclaration.MESSAGE_FIXED_BYTES
VariableDeclaration.MESSAGE_FIXED_BYTES_PER_TOKEN
VariableDeclaration.ANY_2_EVM_MESSAGE_HASH
VariableDeclaration.EVM_2_ANY_MESSAGE_HASH
FunctionDefinition._hash
FunctionDefinition._hash
VariableDeclaration.PRECOMPILE_SPACE
FunctionDefinition._validateEVMAddress
EnumDefinition.MessageExecutionState
EnumDefinition.OCRPluginType
StructDefinition.RampMessageHeader
StructDefinition.EVM2AnyTokenTransfer
StructDefinition.Any2EVMTokenTransfer
StructDefinition.Any2EVMRampMessage
StructDefinition.EVM2AnyRampMessage
VariableDeclaration.CHAIN_FAMILY_SELECTOR_EVM
StructDefinition.MerkleRoot
Suggested order:
VariableDeclaration.GAS_FOR_CALL_EXACT_CHECK
VariableDeclaration.MAX_RET_BYTES
VariableDeclaration.MAX_BALANCE_OF_RET_BYTES
VariableDeclaration.GAS_PRICE_BITS
VariableDeclaration.MESSAGE_FIXED_BYTES
VariableDeclaration.MESSAGE_FIXED_BYTES_PER_TOKEN
VariableDeclaration.ANY_2_EVM_MESSAGE_HASH
VariableDeclaration.EVM_2_ANY_MESSAGE_HASH
VariableDeclaration.PRECOMPILE_SPACE
VariableDeclaration.CHAIN_FAMILY_SELECTOR_EVM
EnumDefinition.MessageExecutionState
EnumDefinition.OCRPluginType
StructDefinition.PriceUpdates
StructDefinition.TokenPriceUpdate
StructDefinition.GasPriceUpdate
StructDefinition.TimestampedPackedUint224
StructDefinition.SourceTokenData
StructDefinition.ExecutionReport
StructDefinition.RampMessageHeader
StructDefinition.EVM2AnyTokenTransfer
StructDefinition.Any2EVMTokenTransfer
StructDefinition.Any2EVMRampMessage
StructDefinition.EVM2AnyRampMessage
StructDefinition.MerkleRoot
ErrorDefinition.InvalidEVMAddress
FunctionDefinition._hash
FunctionDefinition._hash
FunctionDefinition._validateEVMAddress
File: ./src/ccip/ocr/MultiOCR3Base.sol
1:
Current order:
VariableDeclaration.MAX_NUM_ORACLES
EventDefinition.ConfigSet
EventDefinition.Transmitted
EnumDefinition.InvalidConfigErrorType
ErrorDefinition.InvalidConfig
ErrorDefinition.WrongMessageLength
ErrorDefinition.ConfigDigestMismatch
ErrorDefinition.ForkedChain
ErrorDefinition.WrongNumberOfSignatures
ErrorDefinition.SignaturesOutOfRegistration
ErrorDefinition.UnauthorizedTransmitter
ErrorDefinition.UnauthorizedSigner
ErrorDefinition.NonUniqueSignatures
ErrorDefinition.OracleCannotBeZeroAddress
ErrorDefinition.StaticConfigCannotBeChanged
StructDefinition.ConfigInfo
EnumDefinition.Role
StructDefinition.Oracle
StructDefinition.OCRConfig
StructDefinition.OCRConfigArgs
VariableDeclaration.s_ocrConfigs
VariableDeclaration.s_oracles
VariableDeclaration.TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT_NO_SIGNATURES
VariableDeclaration.TRANSMIT_MSGDATA_EXTRA_CONSTANT_LENGTH_COMPONENT_FOR_SIGNATURES
VariableDeclaration.i_chainID
FunctionDefinition.constructor
FunctionDefinition.setOCR3Configs
FunctionDefinition._setOCR3Config
FunctionDefinition._afterOCR3ConfigSet
FunctionDefinition._clearOracleRoles
FunctionDefinition._assignOracleRoles
FunctionDefinition._transmit
FunctionDefinition._verifySignatures
FunctionDefinition._whenChainNotForked
FunctionDefinition.latestConfigDetails
Suggested order:
VariableDeclaration.MAX_NUM_ORACLES
VariableDeclaration.s_ocrConfigs
VariableDeclaration.s_oracles
VariableDeclaration.TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT_NO_SIGNATURES
VariableDeclaration.TRANSMIT_MSGDATA_EXTRA_CONSTANT_LENGTH_COMPONENT_FOR_SIGNATURES
VariableDeclaration.i_chainID
EnumDefinition.InvalidConfigErrorType
EnumDefinition.Role
StructDefinition.ConfigInfo
StructDefinition.Oracle
StructDefinition.OCRConfig
StructDefinition.OCRConfigArgs
ErrorDefinition.InvalidConfig
ErrorDefinition.WrongMessageLength
ErrorDefinition.ConfigDigestMismatch
ErrorDefinition.ForkedChain
ErrorDefinition.WrongNumberOfSignatures
ErrorDefinition.SignaturesOutOfRegistration
ErrorDefinition.UnauthorizedTransmitter
ErrorDefinition.UnauthorizedSigner
ErrorDefinition.NonUniqueSignatures
ErrorDefinition.OracleCannotBeZeroAddress
ErrorDefinition.StaticConfigCannotBeChanged
EventDefinition.ConfigSet
EventDefinition.Transmitted
FunctionDefinition.constructor
FunctionDefinition.setOCR3Configs
FunctionDefinition._setOCR3Config
FunctionDefinition._afterOCR3ConfigSet
FunctionDefinition._clearOracleRoles
FunctionDefinition._assignOracleRoles
FunctionDefinition._transmit
FunctionDefinition._verifySignatures
FunctionDefinition._whenChainNotForked
FunctionDefinition.latestConfigDetails
Instances (1):
File: ./src/ccip/libraries/Internal.sol
159: uint256 public constant PRECOMPILE_SPACE = 1024;
According to the Solidity Style Guide, Non-external
variable and function names should begin with an underscore
Instances (2):
File: ./src/ccip/ocr/MultiOCR3Base.sol
94: /// @notice OCR plugin type => signer OR transmitter address mapping.
97: // Constant-length components of the msg.data sent to transmit.
Index event fields make the field more quickly accessible to off-chain tools that parse events. 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). 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 fields, all of the fields should be indexed.
Instances (2):
File: ./src/ccip/ocr/MultiOCR3Base.sol
18: event ConfigSet(uint8 ocrPluginType, bytes32 configDigest, address[] signers, address[] transmitters, uint8 F);
23: event Transmitted(uint8 indexed ocrPluginType, bytes32 configDigest, uint64 sequenceNumber);
The default value for variables is zero, so initializing them to zero is superfluous.
Instances (62):
File: ./src/ccip/FeeQuoter.sol
279: for (uint256 i = 0; i < length; ++i) {
431: for (uint256 i = 0; i < feeTokensToRemove.length; ++i) {
436: for (uint256 i = 0; i < feeTokensToAdd.length; ++i) {
456: for (uint256 i = 0; i < tokenUpdatesLength; ++i) {
465: for (uint256 i = 0; i < gasUpdatesLength; ++i) {
508: for (uint256 i = 0; i < feeds.length; ++i) {
556: uint256 premiumFee = 0;
557: uint32 tokenTransferGas = 0;
558: uint32 tokenTransferBytesOverhead = 0;
569: uint256 dataAvailabilityCost = 0;
617: for (uint256 i = 0; i < premiumMultiplierWeiPerEthArgs.length; ++i) {
659: for (uint256 i = 0; i < numberOfTokens; ++i) {
671: uint256 bpsFeeUSDWei = 0;
675: uint224 tokenPrice = 0;
798: for (uint256 i = 0; i < tokenTransferFeeConfigArgs.length; ++i) {
802: for (uint256 j = 0; j < tokenTransferFeeConfigArg.tokenTransferFeeConfigs.length; ++j) {
822: for (uint256 i = 0; i < tokensToUseDefaultFeeConfigs.length; ++i) {
966: for (uint256 i = 0; i < onRampTokenTransfers.length; ++i) {
1021: for (uint256 i = 0; i < destChainConfigArgs.length; ++i) {
File: ./src/ccip/MultiAggregateRateLimiter.sol
107: for (uint256 i = 0; i < tokenAmounts.length; ++i) {
165: for (uint256 i = 0; i < rateLimiterUpdates.length; ++i) {
215: for (uint256 i = 0; i < tokenCount; ++i) {
230: for (uint256 i = 0; i < removes.length; ++i) {
239: for (uint256 i = 0; i < adds.length; ++i) {
File: ./src/ccip/NonceManager.sol
132: for (uint256 i = 0; i < previousRampsArgs.length; ++i) {
File: ./src/ccip/capability/CCIPHome.sol
171: uint32 private s_currentVersion = 0;
294: for (uint256 i = 0; i < MAX_CONCURRENT_CONFIGS; ++i) {
489: uint256 nonZeroTransmitters = 0;
491: for (uint256 i = 0; i < numberOfNodes; ++i) {
574: for (uint256 i = 0; i < chainSelectorRemoves.length; ++i) {
587: for (uint256 i = 0; i < chainConfigAdds.length; ++i) {
File: ./src/ccip/ocr/MultiOCR3Base.sol
202: for (uint256 i = 0; i < oracleAddresses.length; ++i) {
212: for (uint256 i = 0; i < oracleAddresses.length; ++i) {
310: uint256 signed = 0;
File: ./src/ccip/offRamp/OffRamp.sol
278: for (uint256 reportIndex = 0; reportIndex < numReports; ++reportIndex) {
288: for (uint256 msgIndex = 0; msgIndex < numMsgs; ++msgIndex) {
305: for (uint256 tokenIndex = 0; tokenIndex < message.tokenAmounts.length; ++tokenIndex) {
349: for (uint256 i = 0; i < reports.length; ++i) {
395: for (uint256 i = 0; i < numMsgs; ++i) {
420: for (uint256 i = 0; i < numMsgs; ++i) {
745: for (uint256 i = 0; i < sourceTokenAmounts.length; ++i) {
810: for (uint256 i = 0; i < commitReport.merkleRoots.length; ++i) {
935: for (uint256 i = 0; i < s_sourceChainSelectors.length(); ++i) {
955: for (uint256 i = 0; i < sourceChainConfigUpdates.length; ++i) {
File: ./src/ccip/onRamp/OnRamp.sol
217: for (uint256 i = 0; i < message.tokenAmounts.length; ++i) {
238: for (uint256 i = 0; i < newMessage.tokenAmounts.length; ++i) {
366: for (uint256 i = 0; i < destChainConfigArgs.length; ++i) {
432: for (uint256 i = 0; i < allowlistConfigArgsItems.length; ++i) {
440: for (uint256 j = 0; j < allowlistConfigArgs.addedAllowlistedSenders.length; ++j) {
454: for (uint256 j = 0; j < allowlistConfigArgs.removedAllowlistedSenders.length; ++j) {
508: for (uint256 i = 0; i < feeTokens.length; ++i) {
File: ./src/ccip/rmn/RMNHome.sol
128: uint32 private s_currentVersion = 0;
134: uint32 private s_activeConfigIndex = 0;
166: for (uint256 i = 0; i < MAX_CONCURRENT_CONFIGS; ++i) {
295: for (uint256 i = 0; i < MAX_CONCURRENT_CONFIGS; ++i) {
349: for (uint256 i = 0; i < staticConfig.nodes.length; ++i) {
368: for (uint256 i = 0; i < numberOfSourceChains; ++i) {
384: uint256 observersCount = 0;
File: ./src/ccip/rmn/RMNRemote.sol
121: for (uint256 i = 0; i < signatures.length; ++i) {
162: for (uint256 i = 0; i < newConfig.signers.length; ++i) {
213: for (uint256 i = 0; i < subjects.length; ++i) {
237: for (uint256 i = 0; i < subjects.length; ++i) {
Issue | Instances | |
---|---|---|
L-1 | Use of ecrecover is susceptible to signature malleability |
2 |
L-2 | abi.encodePacked() should not be used with dynamic types when passing the result to a hash function such as keccak256() |
1 |
L-3 | decimals() is not a part of the ERC-20 standard |
1 |
L-4 | Division by zero not prevented | 3 |
L-5 | domainSeparator() isn't protected against replay attacks in case of a future chain split |
2 |
L-6 | Loss of precision | 1 |
L-7 | Solidity version 0.8.20+ may not work on other chains due to PUSH0 |
2 |
L-8 | File allows a version of solidity that is susceptible to an assembly optimizer bug | 2 |
L-9 | Consider using OpenZeppelin's SafeCast library to prevent unexpected overflows when downcasting | 1 |
L-10 | Unsafe solidity low-level call can cause gas grief attack | 1 |
L-11 | Use of ecrecover is susceptible to signature malleability | 2 |
The built-in EVM precompile ecrecover
is susceptible to signature malleability, which could lead to replay attacks.
References: https://swcregistry.io/docs/SWC-117, https://swcregistry.io/docs/SWC-121, and https://medium.com/cryptronics/signature-replay-vulnerabilities-in-smart-contracts-3b6f7596df57.
While this is not immediately exploitable, this may become a vulnerability if used elsewhere.
Instances (2):
File: ./src/ccip/ocr/MultiOCR3Base.sol
315: address signer = ecrecover(hashedReport, uint8(rawVs[i]) + 27, rs[i], ss[i]);
File: ./src/ccip/rmn/RMNRemote.sol
122: signerAddress = ecrecover(digest, ECDSA_RECOVERY_V, signatures[i].r, signatures[i].s);
[L-2] abi.encodePacked()
should not be used with dynamic types when passing the result to a hash function such as keccak256()
Use abi.encode()
instead which will pad items to 32 bytes, which will prevent hash collisions (e.g. abi.encodePacked(0x123,0x456)
=> 0x123456
=> abi.encodePacked(0x1,0x23456)
, but abi.encode(0x123,0x456)
=> 0x0...1230...456
). "Unless there is a compelling reason, abi.encode
should be preferred". If there is only one argument to abi.encodePacked()
it can often be cast to bytes()
or bytes32()
instead.
If all arguments are strings and or bytes, bytes.concat()
should be used instead
Instances (1):
File: ./src/ccip/ocr/MultiOCR3Base.sol
293: emit Transmitted(ocrPluginType, configDigest, uint64(uint256(reportContext[1])));
The decimals()
function is not a part of the ERC-20 standard, and was added later as an optional extension. As such, some valid ERC20 tokens do not support this interface, so it is unsafe to blindly cast all tokens to this interface, and then call this function.
Instances (1):
File: ./src/ccip/FeeQuoter.sol
379: _calculateRebasedValue(dataFeedContract.decimals(), priceFeedConfig.tokenDecimals, uint256(dataFeedAnswer));
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.
Instances (3):
File: ./src/ccip/FeeQuoter.sol
343: return (fromTokenAmount * _getValidatedTokenPrice(fromToken)) / _getValidatedTokenPrice(toToken);
601: / feeTokenPrice;
731: rebasedVal = feedValue / (10 ** (excessDecimals - FEE_BASE_DECIMALS));
Severity: Low.
Description: See https://eips.ethereum.org/EIPS/eip-2612#security-considerations.
Remediation: Consider using the implementation from OpenZeppelin, which recalculates the domain separator if the current block.chainid
is not the cached chain ID.
Past occurrences of this issue:
Instances (2):
File: ./src/ccip/libraries/Internal.sol
111: MerkleMultiProof.LEAF_DOMAIN_SEPARATOR,
134: MerkleMultiProof.LEAF_DOMAIN_SEPARATOR,
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
Instances (1):
File: ./src/ccip/FeeQuoter.sol
731: rebasedVal = feedValue / (10 ** (excessDecimals - FEE_BASE_DECIMALS));
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. While the project itself may or may not compile with 0.8.20, other projects with which it integrates, or which extend this project may, and those projects will have problems deploying these contracts/libraries.
Instances (2):
File: ./src/ccip/libraries/Client.sol
2: pragma solidity ^0.8.0;
File: ./src/ccip/libraries/Internal.sol
2: pragma solidity ^0.8.4;
In solidity versions 0.8.13 and 0.8.14, there is an optimizer bug where, if the use of a variable is in a separate assembly
block from the block in which it was stored, the mstore
operation is optimized out, leading to uninitialized memory. The code currently does not have such a pattern of execution, but it does use mstore
s in assembly
blocks, so it is a risk for future changes. The affected solidity versions should be avoided if at all possible.
Instances (2):
File: ./src/ccip/libraries/Client.sol
2: pragma solidity ^0.8.0;
File: ./src/ccip/libraries/Internal.sol
2: pragma solidity ^0.8.4;
[L-9] Consider using OpenZeppelin's SafeCast library to prevent unexpected overflows when downcasting
Downcasting from uint256
/int256
in Solidity does not revert on overflow. This can result in undesired exploitation or bugs, since developers usually assume that overflows raise errors. OpenZeppelin's SafeCast library restores this intuition by reverting the transaction when such an operation overflows. Using this library eliminates an entire class of bugs, so it's recommended to use it always. Some exceptions are acceptable like with the classic uint256(uint160(address(variable)))
Instances (1):
File: ./src/ccip/libraries/Internal.sol
173: return address(uint160(encodedAddressUint));
Using the low-level calls of a solidity address can leave the contract open to gas grief attacks. These attacks occur when the called contract returns a large amount of data.
So when calling an external contract, it is necessary to check the length of the return data before reading/copying it (using returndatasize()
).
Instances (1):
File: ./src/ccip/capability/CCIPHome.sol
234: (bool success, bytes memory retData) = address(this).call(update);
The built-in EVM precompile ecrecover is susceptible to signature malleability, which could lead to replay attacks.Consider using OpenZeppelin’s ECDSA library instead of the built-in function.
Instances (2):
File: ./src/ccip/ocr/MultiOCR3Base.sol
315: address signer = ecrecover(hashedReport, uint8(rawVs[i]) + 27, rs[i], ss[i]);
File: ./src/ccip/rmn/RMNRemote.sol
122: signerAddress = ecrecover(digest, ECDSA_RECOVERY_V, signatures[i].r, signatures[i].s);
Issue | Instances | |
---|---|---|
M-1 | Centralization Risk for trusted owners | 22 |
M-2 | Lack of EIP-712 compliance: using keccak256() directly on an array or struct variable |
3 |
M-3 | Direct supportsInterface() calls may cause caller to revert |
3 |
Contracts have owners with privileged rights to perform admin tasks and need to be trusted to not perform malicious updates or drain funds.
Instances (22):
File: ./src/ccip/FeeQuoter.sol
422: ) external onlyOwner {
477: ) external onlyOwner {
608: ) external onlyOwner {
789: ) external onlyOwner {
1013: ) external onlyOwner {
File: ./src/ccip/MultiAggregateRateLimiter.sol
164: ) external onlyOwner {
229: ) external onlyOwner {
266: ) external onlyOwner {
File: ./src/ccip/NonceManager.sol
131: ) external onlyOwner {
File: ./src/ccip/capability/CCIPHome.sol
572: ) external onlyOwner {
File: ./src/ccip/ocr/MultiOCR3Base.sol
131: ) external onlyOwner {
File: ./src/ccip/offRamp/OffRamp.sol
946: ) external onlyOwner {
1002: ) external onlyOwner {
File: ./src/ccip/onRamp/OnRamp.sol
328: ) external onlyOwner {
358: ) external onlyOwner {
File: ./src/ccip/rmn/RMNHome.sol
208: ) external onlyOwner returns (bytes32 newConfigDigest) {
242: ) external onlyOwner {
265: function promoteCandidateAndRevokeActive(bytes32 digestToPromote, bytes32 digestToRevoke) external onlyOwner {
294: function setDynamicConfig(DynamicConfig calldata newDynamicConfig, bytes32 currentDigest) external onlyOwner {
File: ./src/ccip/rmn/RMNRemote.sol
139: ) external onlyOwner {
212: ) public onlyOwner {
236: ) public onlyOwner {
Directly using the actual variable instead of encoding the array values goes against the EIP-712 specification https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md#definition-of-encodedata. Note: OpenSea's Seaport's example with offerHashes and considerationHashes can be used as a reference to understand how array of structs should be encoded.
Instances (3):
File: ./src/ccip/libraries/Internal.sol
124: keccak256(abi.encode(original.tokenAmounts))
147: keccak256(abi.encode(original.tokenAmounts)),
File: ./src/ccip/ocr/MultiOCR3Base.sol
293: emit Transmitted(ocrPluginType, configDigest, uint64(uint256(reportContext[1])));
Calling supportsInterface()
on a contract that doesn't implement the ERC-165 standard will result in the call reverting. Even if the caller does support the function, the contract may be malicious and consume all of the transaction's available gas. Call it via a low-level staticcall(), with a fixed amount of gas, and check the return code, or use OpenZeppelin's ERC165Checker.supportsInterface()
.
Instances (3):
File: ./src/ccip/offRamp/OffRamp.sol
599: || !message.receiver.supportsInterface(type(IAny2EVMMessageReceiver).interfaceId)
640: if (localPoolAddress == address(0) || !localPoolAddress.supportsInterface(Pool.CCIP_POOL_V1)) {
File: ./src/ccip/onRamp/OnRamp.sol
278: if (address(sourcePool) == address(0) || !sourcePool.supportsInterface(Pool.CCIP_POOL_V1)) {