Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
"
},
"@openzeppelin/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
}
"
},
"@openzeppelin/contracts/utils/introspection/IERC165.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
"
},
"contracts/abstract/BurnMintTokenPoolAbstract.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity 0.8.24;\r
import {IBurnMintERC20} from "../interface/IBurnMintERC20.sol";\r
import {TokenPool} from "./TokenPool.sol";\r
import {Pool} from "../library/Pool.sol";\r
abstract contract BurnMintTokenPoolAbstract is TokenPool {\r
/// @notice Contains the specific burn call for a pool.\r
/// @dev overriding this method allows us to create pools with different burn signatures\r
/// without duplicating the underlying logic.\r
function _burn(uint256 amount) internal virtual;\r
\r
/// @notice Burn the token in the pool\r
/// @dev The _validateLockOrBurn check is an essential security check\r
function lockOrBurn(\r
Pool.LockOrBurnInV1 calldata lockOrBurnIn\r
) external virtual override returns (Pool.LockOrBurnOutV1 memory) {\r
_validateLockOrBurn(lockOrBurnIn);\r
\r
_burn(lockOrBurnIn.amount);\r
\r
emit Burned(msg.sender, lockOrBurnIn.amount);\r
\r
return\r
Pool.LockOrBurnOutV1({\r
destTokenAddress: getRemoteToken(\r
lockOrBurnIn.remoteChainSelector\r
),\r
destPoolData: _encodeLocalDecimals()\r
});\r
}\r
\r
/// @notice Mint tokens from the pool to the recipient\r
/// @dev The _validateReleaseOrMint check is an essential security check\r
function releaseOrMint(\r
Pool.ReleaseOrMintInV1 calldata releaseOrMintIn\r
) external virtual override returns (Pool.ReleaseOrMintOutV1 memory) {\r
_validateReleaseOrMint(releaseOrMintIn);\r
\r
// Calculate the local amount\r
uint256 localAmount = _calculateLocalAmount(\r
releaseOrMintIn.amount,\r
_parseRemoteDecimals(releaseOrMintIn.sourcePoolData)\r
);\r
\r
// Mint to the receiver\r
IBurnMintERC20(address(i_token)).mint(\r
releaseOrMintIn.receiver,\r
localAmount\r
);\r
\r
emit Minted(msg.sender, releaseOrMintIn.receiver, localAmount);\r
\r
return Pool.ReleaseOrMintOutV1({destinationAmount: localAmount});\r
}\r
}\r
\r
"
},
"contracts/abstract/Ownable2Step.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.4;\r
import {IOwnable} from "../interface/IOwnable.sol";\r
/// @notice A minimal contract that implements 2-step ownership transfer and nothing more. It's made to be minimal\r
/// to reduce the impact of the bytecode size on any contract that inherits from it.\r
contract Ownable2Step is IOwnable {\r
/// @notice The pending owner is the address to which ownership may be transferred.\r
address private s_pendingOwner;\r
/// @notice The owner is the current owner of the contract.\r
/// @dev The owner is the second storage variable so any implementing contract could pack other state with it\r
/// instead of the much less used s_pendingOwner.\r
address private s_owner;\r
\r
error OwnerCannotBeZero();\r
error MustBeProposedOwner();\r
error CannotTransferToSelf();\r
error OnlyCallableByOwner();\r
\r
event OwnershipTransferRequested(address indexed from, address indexed to);\r
event OwnershipTransferred(address indexed from, address indexed to);\r
\r
constructor(address newOwner, address pendingOwner) {\r
if (newOwner == address(0)) {\r
revert OwnerCannotBeZero();\r
}\r
\r
s_owner = newOwner;\r
if (pendingOwner != address(0)) {\r
_transferOwnership(pendingOwner);\r
}\r
}\r
\r
/// @notice Get the current owner\r
function owner() public view override returns (address) {\r
return s_owner;\r
}\r
\r
/// @notice Allows an owner to begin transferring ownership to a new address. The new owner needs to call\r
/// `acceptOwnership` to accept the transfer before any permissions are changed.\r
/// @param to The address to which ownership will be transferred.\r
function transferOwnership(address to) public override onlyOwner {\r
_transferOwnership(to);\r
}\r
\r
/// @notice validate, transfer ownership, and emit relevant events\r
/// @param to The address to which ownership will be transferred.\r
function _transferOwnership(address to) private {\r
if (to == msg.sender) {\r
revert CannotTransferToSelf();\r
}\r
\r
s_pendingOwner = to;\r
\r
emit OwnershipTransferRequested(s_owner, to);\r
}\r
\r
/// @notice Allows an ownership transfer to be completed by the recipient.\r
function acceptOwnership() external override {\r
if (msg.sender != s_pendingOwner) {\r
revert MustBeProposedOwner();\r
}\r
\r
address oldOwner = s_owner;\r
s_owner = msg.sender;\r
s_pendingOwner = address(0);\r
\r
emit OwnershipTransferred(oldOwner, msg.sender);\r
}\r
\r
/// @notice validate access\r
function _validateOwnership() internal view {\r
if (msg.sender != s_owner) {\r
revert OnlyCallableByOwner();\r
}\r
}\r
\r
/// @notice Reverts if called by anyone other than the contract owner.\r
modifier onlyOwner() {\r
_validateOwnership();\r
_;\r
}\r
}"
},
"contracts/abstract/Ownable2StepMsgSender.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.4;\r
import {Ownable2Step} from "./Ownable2Step.sol";\r
/// @notice Sets the msg.sender to be the owner of the contract and does not set a pending owner.\r
contract Ownable2StepMsgSender is Ownable2Step {\r
constructor() Ownable2Step(msg.sender, address(0)) {}\r
}\r
"
},
"contracts/abstract/TokenPool.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity 0.8.24;\r
import { IPoolV1 } from "../interface/IPoolV1.sol";\r
import {EnumerableSet} from "../library/EnumerableSet.sol";\r
import {RateLimiter} from "../library/RateLimiter.sol";\r
import {IRMN} from "../interface/IRMN.sol";\r
import { IPoolV1 } from "../interface/IPoolV1.sol";\r
import { IRouter } from "../interface/IRouter.sol";\r
\r
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";\r
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";\r
\r
// import { IERC20 } from "../interface/IERC20.sol";\r
// import { IERC20Metadata } from "../interface/IERC20Metadata.sol";\r
\r
import {Ownable2StepMsgSender} from "./Ownable2StepMsgSender.sol";\r
import {Pool} from "../library/Pool.sol";\r
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";\r
\r
\r
/// @dev This pool supports different decimals on different chains but using this feature could impact the total number\r
/// of tokens in circulation. Since all of the tokens are locked/burned on the source, and a rounded amount is minted/released on the\r
/// destination, the number of tokens minted/released could be less than the number of tokens burned/locked. This is because the source\r
/// chain does not know about the destination token decimals. This is not a problem if the decimals are the same on both\r
/// chains.\r
///\r
/// Example:\r
/// Assume there is a token with 6 decimals on chain A and 3 decimals on chain B.\r
/// - 1.234567 tokens are burned on chain A.\r
/// - 1.234 tokens are minted on chain B.\r
/// When sending the 1.234 tokens back to chain A, you will receive 1.234000 tokens on chain A, effectively losing\r
/// 0.000567 tokens.\r
/// In the case of a burnMint pool on chain A, these funds are burned in the pool on chain A.\r
/// In the case of a lockRelease pool on chain A, these funds accumulate in the pool on chain A.\r
abstract contract TokenPool is IPoolV1, Ownable2StepMsgSender {\r
using EnumerableSet for EnumerableSet.Bytes32Set;\r
using EnumerableSet for EnumerableSet.AddressSet;\r
using EnumerableSet for EnumerableSet.UintSet;\r
using RateLimiter for RateLimiter.TokenBucket;\r
\r
error CallerIsNotARampOnRouter(address caller);\r
error ZeroAddressNotAllowed();\r
error SenderNotAllowed(address sender);\r
error AllowListNotEnabled();\r
error NonExistentChain(uint64 remoteChainSelector);\r
error ChainNotAllowed(uint64 remoteChainSelector);\r
error CursedByRMN();\r
error ChainAlreadyExists(uint64 chainSelector);\r
error InvalidSourcePoolAddress(bytes sourcePoolAddress);\r
error InvalidToken(address token);\r
error Unauthorized(address caller);\r
error PoolAlreadyAdded(uint64 remoteChainSelector, bytes remotePoolAddress);\r
error InvalidRemotePoolForChain(\r
uint64 remoteChainSelector,\r
bytes remotePoolAddress\r
);\r
error InvalidRemoteChainDecimals(bytes sourcePoolData);\r
error OverflowDetected(\r
uint8 remoteDecimals,\r
uint8 localDecimals,\r
uint256 remoteAmount\r
);\r
error InvalidDecimalArgs(uint8 expected, uint8 actual);\r
\r
event Locked(address indexed sender, uint256 amount);\r
event Burned(address indexed sender, uint256 amount);\r
event Released(\r
address indexed sender,\r
address indexed recipient,\r
uint256 amount\r
);\r
event Minted(\r
address indexed sender,\r
address indexed recipient,\r
uint256 amount\r
);\r
event ChainAdded(\r
uint64 remoteChainSelector,\r
bytes remoteToken,\r
RateLimiter.Config outboundRateLimiterConfig,\r
RateLimiter.Config inboundRateLimiterConfig\r
);\r
event ChainConfigured(\r
uint64 remoteChainSelector,\r
RateLimiter.Config outboundRateLimiterConfig,\r
RateLimiter.Config inboundRateLimiterConfig\r
);\r
event ChainRemoved(uint64 remoteChainSelector);\r
event RemotePoolAdded(\r
uint64 indexed remoteChainSelector,\r
bytes remotePoolAddress\r
);\r
event RemotePoolRemoved(\r
uint64 indexed remoteChainSelector,\r
bytes remotePoolAddress\r
);\r
event AllowListAdd(address sender);\r
event AllowListRemove(address sender);\r
event RouterUpdated(address oldRouter, address newRouter);\r
event RateLimitAdminSet(address rateLimitAdmin);\r
\r
struct ChainUpdate {\r
uint64 remoteChainSelector; // Remote chain selector\r
bytes[] remotePoolAddresses; // Address of the remote pool, ABI encoded in the case of a remote EVM chain.\r
bytes remoteTokenAddress; // Address of the remote token, ABI encoded in the case of a remote EVM chain.\r
RateLimiter.Config outboundRateLimiterConfig; // Outbound rate limited config, meaning the rate limits for all of the onRamps for the given chain\r
RateLimiter.Config inboundRateLimiterConfig; // Inbound rate limited config, meaning the rate limits for all of the offRamps for the given chain\r
}\r
\r
struct RemoteChainConfig {\r
RateLimiter.TokenBucket outboundRateLimiterConfig; // Outbound rate limited config, meaning the rate limits for all of the onRamps for the given chain\r
RateLimiter.TokenBucket inboundRateLimiterConfig; // Inbound rate limited config, meaning the rate limits for all of the offRamps for the given chain\r
bytes remoteTokenAddress; // Address of the remote token, ABI encoded in the case of a remote EVM chain.\r
EnumerableSet.Bytes32Set remotePools; // Set of remote pool hashes, ABI encoded in the case of a remote EVM chain.\r
}\r
\r
/// @dev The bridgeable token that is managed by this pool. Pools could support multiple tokens at the same time if\r
/// required, but this implementation only supports one token.\r
IERC20 internal immutable i_token;\r
/// @dev The number of decimals of the token managed by this pool.\r
uint8 internal immutable i_tokenDecimals;\r
/// @dev The address of the RMN proxy\r
address internal immutable i_rmnProxy;\r
/// @dev The immutable flag that indicates if the pool is access-controlled.\r
bool internal immutable i_allowlistEnabled;\r
/// @dev A set of addresses allowed to trigger lockOrBurn as original senders.\r
/// Only takes effect if i_allowlistEnabled is true.\r
/// This can be used to ensure only token-issuer specified addresses can move tokens.\r
EnumerableSet.AddressSet internal s_allowlist;\r
/// @dev The address of the router\r
IRouter internal s_router;\r
/// @dev A set of allowed chain selectors. We want the allowlist to be enumerable to\r
/// be able to quickly determine (without parsing logs) who can access the pool.\r
/// @dev The chain selectors are in uint256 format because of the EnumerableSet implementation.\r
EnumerableSet.UintSet internal s_remoteChainSelectors;\r
mapping(uint64 remoteChainSelector => RemoteChainConfig)\r
internal s_remoteChainConfigs;\r
/// @notice A mapping of hashed pool addresses to their unhashed form. This is used to be able to find the actually\r
/// configured pools and not just their hashed versions.\r
mapping(bytes32 poolAddressHash => bytes poolAddress)\r
internal s_remotePoolAddresses;\r
/// @notice The address of the rate limiter admin.\r
/// @dev Can be address(0) if none is configured.\r
address internal s_rateLimitAdmin;\r
\r
constructor(\r
IERC20 token,\r
uint8 localTokenDecimals,\r
address[] memory allowlist,\r
address rmnProxy,\r
address router\r
) {\r
if (\r
address(token) == address(0) ||\r
router == address(0) ||\r
rmnProxy == address(0)\r
) revert ZeroAddressNotAllowed();\r
i_token = token;\r
i_rmnProxy = rmnProxy;\r
\r
try IERC20Metadata(address(token)).decimals() returns (\r
uint8 actualTokenDecimals\r
) {\r
if (localTokenDecimals != actualTokenDecimals) {\r
revert InvalidDecimalArgs(\r
localTokenDecimals,\r
actualTokenDecimals\r
);\r
}\r
} catch {\r
// The decimals function doesn't exist, which is possible since it's optional in the ERC20 spec. We skip the check and\r
// assume the supplied token decimals are correct.\r
}\r
i_tokenDecimals = localTokenDecimals;\r
\r
s_router = IRouter(router);\r
\r
// Pool can be set as permissioned or permissionless at deployment time only to save hot-path gas.\r
i_allowlistEnabled = allowlist.length > 0;\r
if (i_allowlistEnabled) {\r
_applyAllowListUpdates(new address[](0), allowlist);\r
}\r
}\r
\r
/// @inheritdoc IPoolV1\r
function isSupportedToken(\r
address token\r
) public view virtual returns (bool) {\r
return token == address(i_token);\r
}\r
\r
/// @notice Gets the IERC20 token that this pool can lock or burn.\r
/// @return token The IERC20 token representation.\r
function getToken() public view returns (IERC20 token) {\r
return i_token;\r
}\r
\r
/// @notice Get RMN proxy address\r
/// @return rmnProxy Address of RMN proxy\r
function getRmnProxy() public view returns (address rmnProxy) {\r
return i_rmnProxy;\r
}\r
\r
/// @notice Gets the pool's Router\r
/// @return router The pool's Router\r
function getRouter() public view returns (address router) {\r
return address(s_router);\r
}\r
\r
/// @notice Sets the pool's Router\r
/// @param newRouter The new Router\r
function setRouter(address newRouter) public onlyOwner {\r
if (newRouter == address(0)) revert ZeroAddressNotAllowed();\r
address oldRouter = address(s_router);\r
s_router = IRouter(newRouter);\r
\r
emit RouterUpdated(oldRouter, newRouter);\r
}\r
\r
/// @notice Signals which version of the pool interface is supported\r
function supportsInterface(\r
bytes4 interfaceId\r
) public pure virtual override returns (bool) {\r
return\r
interfaceId == Pool.CCIP_POOL_V1 ||\r
interfaceId == type(IPoolV1).interfaceId ||\r
interfaceId == type(IERC165).interfaceId;\r
}\r
\r
// ================================================================\r
// │ Validation │\r
// ================================================================\r
\r
/// @notice Validates the lock or burn input for correctness on\r
/// - token to be locked or burned\r
/// - RMN curse status\r
/// - allowlist status\r
/// - if the sender is a valid onRamp\r
/// - rate limit status\r
/// @param lockOrBurnIn The input to validate.\r
/// @dev This function should always be called before executing a lock or burn. Not doing so would allow\r
/// for various exploits.\r
function _validateLockOrBurn(\r
Pool.LockOrBurnInV1 calldata lockOrBurnIn\r
) internal {\r
if (!isSupportedToken(lockOrBurnIn.localToken))\r
revert InvalidToken(lockOrBurnIn.localToken);\r
if (\r
IRMN(i_rmnProxy).isCursed(\r
bytes16(uint128(lockOrBurnIn.remoteChainSelector))\r
)\r
) revert CursedByRMN();\r
_checkAllowList(lockOrBurnIn.originalSender);\r
\r
_onlyOnRamp(lockOrBurnIn.remoteChainSelector);\r
_consumeOutboundRateLimit(\r
lockOrBurnIn.remoteChainSelector,\r
lockOrBurnIn.amount\r
);\r
}\r
\r
/// @notice Validates the release or mint input for correctness on\r
/// - token to be released or minted\r
/// - RMN curse status\r
/// - if the sender is a valid offRamp\r
/// - if the source pool is valid\r
/// - rate limit status\r
/// @param releaseOrMintIn The input to validate.\r
/// @dev This function should always be called before executing a release or mint. Not doing so would allow\r
/// for various exploits.\r
function _validateReleaseOrMint(\r
Pool.ReleaseOrMintInV1 calldata releaseOrMintIn\r
) internal {\r
if (!isSupportedToken(releaseOrMintIn.localToken))\r
revert InvalidToken(releaseOrMintIn.localToken);\r
if (\r
IRMN(i_rmnProxy).isCursed(\r
bytes16(uint128(releaseOrMintIn.remoteChainSelector))\r
)\r
) revert CursedByRMN();\r
_onlyOffRamp(releaseOrMintIn.remoteChainSelector);\r
\r
// Validates that the source pool address is configured on this pool.\r
if (\r
!isRemotePool(\r
releaseOrMintIn.remoteChainSelector,\r
releaseOrMintIn.sourcePoolAddress\r
)\r
) {\r
revert InvalidSourcePoolAddress(releaseOrMintIn.sourcePoolAddress);\r
}\r
\r
_consumeInboundRateLimit(\r
releaseOrMintIn.remoteChainSelector,\r
releaseOrMintIn.amount\r
);\r
}\r
\r
// ================================================================\r
// │ Token decimals │\r
// ================================================================\r
\r
/// @notice Gets the IERC20 token decimals on the local chain.\r
function getTokenDecimals() public view virtual returns (uint8 decimals) {\r
return i_tokenDecimals;\r
}\r
\r
function _encodeLocalDecimals()\r
internal\r
view\r
virtual\r
returns (bytes memory)\r
{\r
return abi.encode(i_tokenDecimals);\r
}\r
\r
function _parseRemoteDecimals(\r
bytes memory sourcePoolData\r
) internal view virtual returns (uint8) {\r
// Fallback to the local token decimals if the source pool data is empty. This allows for backwards compatibility.\r
if (sourcePoolData.length == 0) {\r
return i_tokenDecimals;\r
}\r
if (sourcePoolData.length != 32) {\r
revert InvalidRemoteChainDecimals(sourcePoolData);\r
}\r
uint256 remoteDecimals = abi.decode(sourcePoolData, (uint256));\r
if (remoteDecimals > type(uint8).max) {\r
revert InvalidRemoteChainDecimals(sourcePoolData);\r
}\r
return uint8(remoteDecimals);\r
}\r
\r
/// @notice Calculates the local amount based on the remote amount and decimals.\r
/// @param remoteAmount The amount on the remote chain.\r
/// @param remoteDecimals The decimals of the token on the remote chain.\r
/// @return The local amount.\r
/// @dev This function protects against overflows. If there is a transaction that hits the overflow check, it is\r
/// probably incorrect as that means the amount cannot be represented on this chain. If the local decimals have been\r
/// wrongly configured, the token issuer could redeploy the pool with the correct decimals and manually re-execute the\r
/// CCIP tx to fix the issue.\r
function _calculateLocalAmount(\r
uint256 remoteAmount,\r
uint8 remoteDecimals\r
) internal view virtual returns (uint256) {\r
if (remoteDecimals == i_tokenDecimals) {\r
return remoteAmount;\r
}\r
if (remoteDecimals > i_tokenDecimals) {\r
uint8 decimalsDiff = remoteDecimals - i_tokenDecimals;\r
if (decimalsDiff > 77) {\r
// This is a safety check to prevent overflow in the next calculation.\r
revert OverflowDetected(\r
remoteDecimals,\r
i_tokenDecimals,\r
remoteAmount\r
);\r
}\r
// Solidity rounds down so there is no risk of minting more tokens than the remote chain sent.\r
return remoteAmount / (10 ** decimalsDiff);\r
}\r
\r
// This is a safety check to prevent overflow in the next calculation.\r
// More than 77 would never fit in a uint256 and would cause an overflow. We also check if the resulting amount\r
// would overflow.\r
uint8 diffDecimals = i_tokenDecimals - remoteDecimals;\r
if (\r
diffDecimals > 77 ||\r
remoteAmount > type(uint256).max / (10 ** diffDecimals)\r
) {\r
revert OverflowDetected(\r
remoteDecimals,\r
i_tokenDecimals,\r
remoteAmount\r
);\r
}\r
\r
return remoteAmount * (10 ** diffDecimals);\r
}\r
\r
// ================================================================\r
// │ Chain permissions │\r
// ================================================================\r
\r
/// @notice Gets the pool address on the remote chain.\r
/// @param remoteChainSelector Remote chain selector.\r
/// @dev To support non-evm chains, this value is encoded into bytes\r
function getRemotePools(\r
uint64 remoteChainSelector\r
) public view returns (bytes[] memory) {\r
bytes32[] memory remotePoolHashes = s_remoteChainConfigs[\r
remoteChainSelector\r
].remotePools.values();\r
\r
bytes[] memory remotePools = new bytes[](remotePoolHashes.length);\r
for (uint256 i = 0; i < remotePoolHashes.length; ++i) {\r
remotePools[i] = s_remotePoolAddresses[remotePoolHashes[i]];\r
}\r
\r
return remotePools;\r
}\r
\r
/// @notice Checks if the pool address is configured on the remote chain.\r
/// @param remoteChainSelector Remote chain selector.\r
/// @param remotePoolAddress The address of the remote pool.\r
function isRemotePool(\r
uint64 remoteChainSelector,\r
bytes calldata remotePoolAddress\r
) public view returns (bool) {\r
return\r
s_remoteChainConfigs[remoteChainSelector].remotePools.contains(\r
keccak256(remotePoolAddress)\r
);\r
}\r
\r
/// @notice Gets the token address on the remote chain.\r
/// @param remoteChainSelector Remote chain selector.\r
/// @dev To support non-evm chains, this value is encoded into bytes\r
function getRemoteToken(\r
uint64 remoteChainSelector\r
) public view returns (bytes memory) {\r
return s_remoteChainConfigs[remoteChainSelector].remoteTokenAddress;\r
}\r
\r
/// @notice Adds a remote pool for a given chain selector. This could be due to a pool being upgraded on the remote\r
/// chain. We don't simply want to replace the old pool as there could still be valid inflight messages from the old\r
/// pool. This function allows for multiple pools to be added for a single chain selector.\r
/// @param remoteChainSelector The remote chain selector for which the remote pool address is being added.\r
/// @param remotePoolAddress The address of the new remote pool.\r
function addRemotePool(\r
uint64 remoteChainSelector,\r
bytes calldata remotePoolAddress\r
) external onlyOwner {\r
if (!isSupportedChain(remoteChainSelector))\r
revert NonExistentChain(remoteChainSelector);\r
\r
_setRemotePool(remoteChainSelector, remotePoolAddress);\r
}\r
\r
/// @notice Removes the remote pool address for a given chain selector.\r
/// @dev All inflight txs from the remote pool will be rejected after it is removed. To ensure no loss of funds, there\r
/// should be no inflight txs from the given pool.\r
function removeRemotePool(\r
uint64 remoteChainSelector,\r
bytes calldata remotePoolAddress\r
) external onlyOwner {\r
if (!isSupportedChain(remoteChainSelector))\r
revert NonExistentChain(remoteChainSelector);\r
\r
if (\r
!s_remoteChainConfigs[remoteChainSelector].remotePools.remove(\r
keccak256(remotePoolAddress)\r
)\r
) {\r
revert InvalidRemotePoolForChain(\r
remoteChainSelector,\r
remotePoolAddress\r
);\r
}\r
\r
emit RemotePoolRemoved(remoteChainSelector, remotePoolAddress);\r
}\r
\r
/// @inheritdoc IPoolV1\r
function isSupportedChain(\r
uint64 remoteChainSelector\r
) public view returns (bool) {\r
return s_remoteChainSelectors.contains(remoteChainSelector);\r
}\r
\r
/// @notice Get list of allowed chains\r
/// @return list of chains.\r
function getSupportedChains() public view returns (uint64[] memory) {\r
uint256[] memory uint256ChainSelectors = s_remoteChainSelectors\r
.values();\r
uint64[] memory chainSelectors = new uint64[](\r
uint256ChainSelectors.length\r
);\r
for (uint256 i = 0; i < uint256ChainSelectors.length; ++i) {\r
chainSelectors[i] = uint64(uint256ChainSelectors[i]);\r
}\r
\r
return chainSelectors;\r
}\r
\r
/// @notice Sets the permissions for a list of chains selectors. Actual senders for these chains\r
/// need to be allowed on the Router to interact with this pool.\r
/// @param remoteChainSelectorsToRemove A list of chain selectors to remove.\r
/// @param chainsToAdd A list of chains and their new permission status & rate limits. Rate limits\r
/// are only used when the chain is being added through `allowed` being true.\r
/// @dev Only callable by the owner\r
\r
function applyChainUpdates(\r
uint64[] calldata remoteChainSelectorsToRemove,\r
ChainUpdate[] calldata chainsToAdd\r
) external virtual onlyOwner {\r
for (uint256 i = 0; i < remoteChainSelectorsToRemove.length; ++i) {\r
uint64 remoteChainSelectorToRemove = remoteChainSelectorsToRemove[\r
i\r
];\r
// If the chain doesn't exist, revert\r
if (!s_remoteChainSelectors.remove(remoteChainSelectorToRemove)) {\r
revert NonExistentChain(remoteChainSelectorToRemove);\r
}\r
\r
// Remove all remote pool hashes for the chain\r
bytes32[] memory remotePools = s_remoteChainConfigs[\r
remoteChainSelectorToRemove\r
].remotePools.values();\r
for (uint256 j = 0; j < remotePools.length; ++j) {\r
s_remoteChainConfigs[remoteChainSelectorToRemove]\r
.remotePools\r
.remove(remotePools[j]);\r
}\r
\r
delete s_remoteChainConfigs[remoteChainSelectorToRemove];\r
\r
emit ChainRemoved(remoteChainSelectorToRemove);\r
}\r
\r
for (uint256 i = 0; i < chainsToAdd.length; ++i) {\r
ChainUpdate memory newChain = chainsToAdd[i];\r
RateLimiter._validateTokenBucketConfig(\r
newChain.outboundRateLimiterConfig,\r
false\r
);\r
RateLimiter._validateTokenBucketConfig(\r
newChain.inboundRateLimiterConfig,\r
false\r
);\r
\r
if (newChain.remoteTokenAddress.length == 0) {\r
revert ZeroAddressNotAllowed();\r
}\r
\r
// If the chain already exists, revert\r
if (!s_remoteChainSelectors.add(newChain.remoteChainSelector)) {\r
revert ChainAlreadyExists(newChain.remoteChainSelector);\r
}\r
\r
RemoteChainConfig storage remoteChainConfig = s_remoteChainConfigs[\r
newChain.remoteChainSelector\r
];\r
\r
remoteChainConfig.outboundRateLimiterConfig = RateLimiter\r
.TokenBucket({\r
rate: newChain.outboundRateLimiterConfig.rate,\r
capacity: newChain.outboundRateLimiterConfig.capacity,\r
tokens: newChain.outboundRateLimiterConfig.capacity,\r
lastUpdated: uint32(block.timestamp),\r
isEnabled: newChain.outboundRateLimiterConfig.isEnabled\r
});\r
remoteChainConfig.inboundRateLimiterConfig = RateLimiter\r
.TokenBucket({\r
rate: newChain.inboundRateLimiterConfig.rate,\r
capacity: newChain.inboundRateLimiterConfig.capacity,\r
tokens: newChain.inboundRateLimiterConfig.capacity,\r
lastUpdated: uint32(block.timestamp),\r
isEnabled: newChain.inboundRateLimiterConfig.isEnabled\r
});\r
remoteChainConfig.remoteTokenAddress = newChain.remoteTokenAddress;\r
\r
for (uint256 j = 0; j < newChain.remotePoolAddresses.length; ++j) {\r
_setRemotePool(\r
newChain.remoteChainSelector,\r
newChain.remotePoolAddresses[j]\r
);\r
}\r
\r
emit ChainAdded(\r
newChain.remoteChainSelector,\r
newChain.remoteTokenAddress,\r
newChain.outboundRateLimiterConfig,\r
newChain.inboundRateLimiterConfig\r
);\r
}\r
}\r
\r
/// @notice Adds a pool address to the allowed remote token pools for a particular chain.\r
/// @param remoteChainSelector The remote chain selector for which the remote pool address is being added.\r
/// @param remotePoolAddress The address of the new remote pool.\r
function _setRemotePool(\r
uint64 remoteChainSelector,\r
bytes memory remotePoolAddress\r
) internal {\r
if (remotePoolAddress.length == 0) {\r
revert ZeroAddressNotAllowed();\r
}\r
\r
bytes32 poolHash = keccak256(remotePoolAddress);\r
\r
// Check if the pool already exists.\r
if (\r
!s_remoteChainConfigs[remoteChainSelector].remotePools.add(poolHash)\r
) {\r
revert PoolAlreadyAdded(remoteChainSelector, remotePoolAddress);\r
}\r
\r
// Add the pool to the mapping to be able to un-hash it later.\r
s_remotePoolAddresses[poolHash] = remotePoolAddress;\r
\r
emit RemotePoolAdded(remoteChainSelector, remotePoolAddress);\r
}\r
\r
// ================================================================\r
// │ Rate limiting │\r
// ================================================================\r
\r
/// @dev The inbound rate limits should be slightly higher than the outbound rate limits. This is because many chains\r
/// finalize blocks in batches. CCIP also commits messages in batches: the commit plugin bundles multiple messages in\r
/// a single merkle root.\r
/// Imagine the following scenario.\r
/// - Chain A has an inbound and outbound rate limit of 100 tokens capacity and 1 token per second refill rate.\r
/// - Chain B has an inbound and outbound rate limit of 100 tokens capacity and 1 token per second refill rate.\r
///\r
/// At time 0:\r
/// - Chain A sends 100 tokens to Chain B.\r
/// At time 5:\r
/// - Chain A sends 5 tokens to Chain B.\r
/// At time 6:\r
/// The epoch that contains blocks [0-5] is finalized.\r
/// Both transactions will be included in the same merkle root and become executable at the same time. This means\r
/// the token pool on chain B requires a capacity of 105 to successfully execute both messages at the same time.\r
/// The exact additional capacity required depends on the refill rate and the size of the source chain epochs and the\r
/// CCIP round time. For simplicity, a 5-10% buffer should be sufficient in most cases.\r
\r
/// @notice Sets the rate limiter admin address.\r
/// @dev Only callable by the owner.\r
/// @param rateLimitAdmin The new rate limiter admin address.\r
function setRateLimitAdmin(address rateLimitAdmin) external onlyOwner {\r
s_rateLimitAdmin = rateLimitAdmin;\r
emit RateLimitAdminSet(rateLimitAdmin);\r
}\r
\r
/// @notice Gets the rate limiter admin address.\r
function getRateLimitAdmin() external view returns (address) {\r
return s_rateLimitAdmin;\r
}\r
\r
/// @notice Consumes outbound rate limiting capacity in this pool\r
function _consumeOutboundRateLimit(\r
uint64 remoteChainSelector,\r
uint256 amount\r
) internal {\r
s_remoteChainConfigs[remoteChainSelector]\r
.outboundRateLimiterConfig\r
._consume(amount, address(i_token));\r
}\r
\r
/// @notice Consumes inbound rate limiting capacity in this pool\r
function _consumeInboundRateLimit(\r
uint64 remoteChainSelector,\r
uint256 amount\r
) internal {\r
s_remoteChainConfigs[remoteChainSelector]\r
.inboundRateLimiterConfig\r
._consume(amount, address(i_token));\r
}\r
\r
/// @notice Gets the token bucket with its values for the block it was requested at.\r
/// @return The token bucket.\r
function getCurrentOutboundRateLimiterState(\r
uint64 remoteChainSelector\r
) external view returns (RateLimiter.TokenBucket memory) {\r
return\r
s_remoteChainConfigs[remoteChainSelector]\r
.outboundRateLimiterConfig\r
._currentTokenBucketState();\r
}\r
\r
/// @notice Gets the token bucket with its values for the block it was requested at.\r
/// @return The token bucket.\r
function getCurrentInboundRateLimiterState(\r
uint64 remoteChainSelector\r
) external view returns (RateLimiter.TokenBucket memory) {\r
return\r
s_remoteChainConfigs[remoteChainSelector]\r
.inboundRateLimiterConfig\r
._currentTokenBucketState();\r
}\r
\r
/// @notice Sets the chain rate limiter config.\r
/// @param remoteChainSelector The remote chain selector for which the rate limits apply.\r
/// @param outboundConfig The new outbound rate limiter config, meaning the onRamp rate limits for the given chain.\r
/// @param inboundConfig The new inbound rate limiter config, meaning the offRamp rate limits for the given chain.\r
function setChainRateLimiterConfig(\r
uint64 remoteChainSelector,\r
RateLimiter.Config memory outboundConfig,\r
RateLimiter.Config memory inboundConfig\r
) external {\r
if (msg.sender != s_rateLimitAdmin && msg.sender != owner())\r
revert Unauthorized(msg.sender);\r
\r
_setRateLimitConfig(remoteChainSelector, outboundConfig, inboundConfig);\r
}\r
\r
function _setRateLimitConfig(\r
uint64 remoteChainSelector,\r
RateLimiter.Config memory outboundConfig,\r
RateLimiter.Config memory inboundConfig\r
) internal {\r
if (!isSupportedChain(remoteChainSelector))\r
revert NonExistentChain(remoteChainSelector);\r
RateLimiter._validateTokenBucketConfig(outboundConfig, false);\r
s_remoteChainConfigs[remoteChainSelector]\r
.outboundRateLimiterConfig\r
._setTokenBucketConfig(outboundConfig);\r
RateLimiter._validateTokenBucketConfig(inboundConfig, false);\r
s_remoteChainConfigs[remoteChainSelector]\r
.inboundRateLimiterConfig\r
._setTokenBucketConfig(inboundConfig);\r
emit ChainConfigured(\r
remoteChainSelector,\r
outboundConfig,\r
inboundConfig\r
);\r
}\r
\r
// ================================================================\r
// │ Access │\r
// ================================================================\r
\r
/// @notice Checks whether remote chain selector is configured on this contract, and if the msg.sender\r
/// is a permissioned onRamp for the given chain on the Router.\r
function _onlyOnRamp(uint64 remoteChainSelector) internal view {\r
if (!isSupportedChain(remoteChainSelector))\r
revert ChainNotAllowed(remoteChainSelector);\r
if (!(msg.sender == s_router.getOnRamp(remoteChainSelector)))\r
revert CallerIsNotARampOnRouter(msg.sender);\r
}\r
\r
/// @notice Checks whether remote chain selector is configured on this contract, and if the msg.sender\r
/// is a permissioned offRamp for the given chain on the Router.\r
function _onlyOffRamp(uint64 remoteChainSelector) internal view {\r
if (!isSupportedChain(remoteChainSelector))\r
revert ChainNotAllowed(remoteChainSelector);\r
if (!s_router.isOffRamp(remoteChainSelector, msg.sender))\r
revert CallerIsNotARampOnRouter(msg.sender);\r
}\r
\r
// ================================================================\r
// │ Allowlist │\r
// ================================================================\r
\r
function _checkAllowList(address sender) internal view {\r
if (i_allowlistEnabled) {\r
if (!s_allowlist.contains(sender)) {\r
revert SenderNotAllowed(sender);\r
}\r
}\r
}\r
\r
/// @notice Gets whether the allowlist functionality is enabled.\r
/// @return true is enabled, false if not.\r
function getAllowListEnabled() external view returns (bool) {\r
return i_allowlistEnabled;\r
}\r
\r
/// @notice Gets the allowed addresses.\r
/// @return The allowed addresses.\r
function getAllowList() external view returns (address[] memory) {\r
return s_allowlist.values();\r
}\r
\r
/// @notice Apply updates to the allow list.\r
/// @param removes The addresses to be removed.\r
/// @param adds The addresses to be added.\r
function applyAllowListUpdates(\r
address[] calldata removes,\r
address[] calldata adds\r
) external onlyOwner {\r
_applyAllowListUpdates(removes, adds);\r
}\r
\r
/// @notice Internal version of applyAllowListUpdates to allow for reuse in the constructor.\r
function _applyAllowListUpdates(\r
address[] memory removes,\r
address[] memory adds\r
) internal {\r
if (!i_allowlistEnabled) revert AllowListNotEnabled();\r
\r
for (uint256 i = 0; i < removes.length; ++i) {\r
address toRemove = removes[i];\r
if (s_allowlist.remove(toRemove)) {\r
emit AllowListRemove(toRemove);\r
}\r
}\r
for (uint256 i = 0; i < adds.length; ++i) {\r
address toAdd = adds[i];\r
if (toAdd == address(0)) {\r
continue;\r
}\r
if (s_allowlist.add(toAdd)) {\r
emit AllowListAdd(toAdd);\r
}\r
}\r
}\r
}\r
"
},
"contracts/interface/IBurnMintERC20.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.0;\r
// import {IERC20} from "./IERC20.sol";\r
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";\r
interface IBurnMintERC20 is IERC20 {\r
/// @notice Mints new tokens for a given address.\r
/// @param account The address to mint the new tokens to.\r
/// @param amount The number of tokens to be minted.\r
/// @dev this function increases the total supply.\r
function mint(address account, uint256 amount) external;\r
\r
/// @notice Burns tokens from the sender.\r
/// @param amount The number of tokens to be burned.\r
/// @dev this function decreases the total supply.\r
function burn(uint256 amount) external;\r
\r
/// @notice Burns tokens from a given address..\r
/// @param account The address to burn tokens from.\r
/// @param amount The number of tokens to be burned.\r
/// @dev this function decreases the total supply.\r
function burn(address account, uint256 amount) external;\r
\r
/// @notice Burns tokens from a given address..\r
/// @param account The address to burn tokens from.\r
/// @param amount The number of tokens to be burned.\r
/// @dev this function decreases the total supply.\r
function burnFrom(address account, uint256 amount) external;\r
}"
},
"contracts/interface/IOwnable.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.0;\r
\r
interface IOwnable {\r
function owner() external returns (address);\r
\r
function transferOwnership(address recipient) external;\r
\r
function acceptOwnership() external;\r
}\r
"
},
"contracts/interface/IPoolV1.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.0;\r
\r
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";\r
import {Pool} from "../library/Pool.sol";\r
/// @notice Shared public interface for multiple V1 pool types.\r
/// Each pool type handles a different child token model (lock/unlock, mint/burn.)\r
interface IPoolV1 is IERC165 {\r
/// @notice Lock tokens into the pool or burn the tokens.\r
/// @param lockOrBurnIn Encoded data fields for the processing of tokens on the source chain.\r
/// @return lockOrBurnOut Encoded data fields for the processing of tokens on the destination chain.\r
function lockOrBurn(\r
Pool.LockOrBurnInV1 calldata lockOrBurnIn\r
) external returns (Pool.LockOrBurnOutV1 memory lockOrBurnOut);\r
\r
/// @notice Releases or mints tokens to the receiver address.\r
/// @param releaseOrMintIn All data required to release or mint tokens.\r
/// @return releaseOrMintOut The amount of tokens released or minted on the local chain, denominated\r
/// in the local token's decimals.\r
/// @dev The offramp asserts that the balanceOf of the receiver has been incremented by exactly the number\r
/// of tokens that is returned in ReleaseOrMintOutV1.destinationAmount. If the amounts do not match, the tx reverts.\r
function releaseOrMint(\r
Pool.ReleaseOrMintInV1 calldata releaseOrMintIn\r
) external returns (Pool.ReleaseOrMintOutV1 memory);\r
\r
/// @notice Checks whether a remote chain is supported in the token pool.\r
/// @param remoteChainSelector The selector of the remote chain.\r
/// @return true if the given chain is a permissioned remote chain.\r
function isSupportedChain(\r
uint64 remoteChainSelector\r
) external view returns (bool);\r
\r
/// @notice Returns if the token pool supports the given token.\r
/// @param token The address of the token.\r
/// @return true if the token is supported by the pool.\r
function isSupportedToken(address token) external view returns (bool);\r
}\r
"
},
"contracts/interface/IRMN.sol": {
"content": "\r
// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.0;\r
\r
/// @notice This interface contains the only RMN-related functions that might be used on-chain by other CCIP contracts.\r
interface IRMN {\r
/// @notice A Merkle root tagged with the address of the commit store contract it is destined for.\r
struct TaggedRoot {\r
address commitStore;\r
bytes32 root;\r
}\r
\r
/// @notice Callers MUST NOT cache the return value as a blessed tagged root could become unblessed.\r
function isBlessed(\r
TaggedRoot calldata taggedRoot\r
) external view returns (bool);\r
\r
/// @notice Iff there is an active global or legacy curse, this function returns true.\r
function isCursed() external view returns (bool);\r
\r
/// @notice Iff there is an active global curse, or an active curse for `subject`, this function returns true.\r
/// @param subject To check whether a particular chain is cursed, set to bytes16(uint128(chainSelector)).\r
function isCursed(bytes16 subject) external view returns (bool);\r
}"
},
"contracts/interface/IRouter.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.0;\r
import {Client} from "../library/Client.sol";\r
interface IRouter {\r
error OnlyOffRamp();\r
\r
/// @notice Route the message to its intended receiver contract.\r
/// @param message Client.Any2EVMMessage struct.\r
/// @param gasForCallExactCheck of params for exec\r
/// @param gasLimit set of params for exec\r
/// @param receiver set of params for exec\r
/// @dev if the receiver is a contracts that signals support for CCIP execution through EIP-165.\r
/// the contract is called. If not, only tokens are transferred.\r
/// @return success A boolean value indicating whether the ccip message was received without errors.\r
/// @return retBytes A bytes array containing return data form CCIP receiver.\r
/// @return gasUsed the gas used by the external customer call. Does not include any overhead.\r
function routeMessage(\r
Client.Any2EVMMessage calldata message,\r
uint16 gasForCallExactCheck,\r
uint256 gasLimit,\r
address receiver\r
) external returns (bool success, bytes memory retBytes, uint256 gasUsed);\r
\r
/// @notice Returns the configured onramp for a specific destination chain.\r
/// @param destChainSelector The destination chain Id to get the onRamp for.\r
/// @return onRampAddress The address of the onRamp.\r
function getOnRamp(\r
uint64 destChainSelector\r
) external view returns (address onRampAddress);\r
\r
/// @notice Return true if the given offRamp is a configured offRamp for the given source chain.\r
/// @param sourceChainSelector The source chain selector to check.\r
/// @param offRamp The address of the offRamp to check.\r
function isOffRamp(\r
uint64 sourceChainSelector,\r
address offRamp\r
) external view returns (bool isOffRamp);\r
}"
},
"contracts/interface/ITypeAndVersion.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.0;\r
\r
interface ITypeAndVersion {\r
function typeAndVersion() external pure returns (string memory);\r
}"
},
"contracts/library/Client.sol": {
"content": "\r
// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.0;\r
\r
// End consumer library.\r
library Client {\r
/// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.\r
struct EVMTokenAmount {\r
address token; // token address on the local chain.\r
uint256 amount; // Amount of tokens.\r
}\r
\r
struct Any2EVMMessage {\r
bytes32 messageId; // MessageId corresponding to ccipSend on source.\r
uint64 sourceChainSelector; // Source chain selector.\r
bytes sender; // abi.decode(sender) if coming from an EVM chain.\r
bytes data; // payload sent in original message.\r
EVMTokenAmount[] destTokenAmounts; // Tokens and their amounts in their destination chain representation.\r
}\r
\r
// If extraArgs is empty bytes, the default is 200k gas limit.\r
struct EVM2AnyMessage {\r
bytes receiver; // abi.encode(receiver address) for dest EVM chains\r
bytes data; // Data payload\r
EVMTokenAmount[] tokenAmounts; // Token transfers\r
address feeToken; // Address of feeToken. address(0) means you will send msg.value.\r
bytes extraArgs; // Populate this with _argsToBytes(EVMExtraArgsV2)\r
}\r
\r
// bytes4(keccak256("CCIP EVMExtraArgsV1"));\r
bytes4 public constant EVM_EXTRA_ARGS_V1_TAG = 0x97a657c9;\r
\r
struct EVMExtraArgsV1 {\r
uint256 gasLimit;\r
}\r
\r
function _argsToBytes(\r
EVMExtraArgsV1 memory extraArgs\r
) internal pure returns (bytes memory bts) {\r
return abi.encodeWithSelector(EVM_EXTRA_ARGS_V1_TAG, extraArgs);\r
}\r
\r
// bytes4(keccak256("CCIP EVMExtraArgsV2"));\r
bytes4 public constant EVM_EXTRA_ARGS_V2_TAG = 0x181dcf10;\r
\r
/// @param gasLimit: gas limit for the callback on the destination chain.\r
/// @param allowOutOfOrderExecution: if true, it indicates that the message can be executed in any order relative to other messages from the same sender.\r
/// This value's default varies by chain. On some chains, a particular value is enforced, meaning if the expected value\r
/// is not set, the message request will revert.\r
struct EVMExtraArgsV2 {\r
uint256 gasLimit;\r
bool allowOutOfOrderExecution;\r
}\r
\r
function _argsToBytes(\r
EVMExtraArgsV2 memory extraArgs\r
) internal pure returns (bytes memory bts) {\r
return abi.encodeWithSelector(EVM_EXTRA_ARGS_V2_TAG, extraArgs);\r
}\r
}\r
"
},
"contracts/library/EnumerableSet.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.20;\r
\r
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol)\r
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.\r
\r
/**\r
* @dev Library for managing\r
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive\r
* types.\r
*\r
* Sets have the following properties:\r
*\r
* - Elements are added, removed, and checked for existence in constant time\r
* (O(1)).\r
* - Elements are enumerated in O(n). No guarantees are made on the ordering.\r
*\r
* ```solidity\r
* contract Example {\r
* // Add the library methods\r
* using EnumerableSet for EnumerableSet.AddressSet;\r
*\r
* // Declare a set state variable\r
* EnumerableSet.AddressSet private mySet;\r
* }\r
* ```\r
*\r
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)\r
* and `uint256` (`UintSet`) are supported.\r
*\r
* [WARNING]\r
* ====\r
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure\r
* unusable.\r
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.\r
*\r
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an\r
* array of EnumerableSet.\r
* ====\r
*/\r
library EnumerableSet {\r
// To implement this library for multiple types with as little code\r
// repetition as possible, we write it in terms of a generic Set type with\r
// bytes32 values.\r
// The Set implementation uses private functions, and user-facing\r
// implementations (such as AddressSet) are just wrappers around the\r
// underlying Set.\r
// This means that we can only create new EnumerableSets for types that fit\r
// in bytes32.\r
\r
struct Set {\r
// Storage of set values\r
bytes32[] _values;\r
// Position is the index of the value in the `values` array plus 1.\r
// Position 0 is used to mean a value is not in the set.\r
mapping(bytes32 value => uint256) _positions;\r
}\r
\r
/**\r
* @dev Add a value to a set. O(1).\r
*\r
* Returns true if the value was added to the set, that is if it was not\r
* already present.\r
*/\r
function _add(Set storage set, bytes32 value) private returns (bool) {\r
if (!_contains(set, value)) {\r
set._values.push(value);\r
// The value is stored at length-1, but we add 1 to all indexes\r
// and use 0 as a sentinel value\r
set._positions[value] = set._values.length;\r
return true;\r
} else {\r
return false;\r
}\r
}\r
\r
/**\r
* @dev Removes a value from a set. O(1).\r
*\r
* Returns true if the value was removed from the set, that is if it was\r
* present.\r
*/\r
function _remove(Set storage set, bytes32 value) private returns (bool) {\r
// We cache the value's position to prevent multiple reads from the same storage slot\r
uint256 position = set._positions[value];\r
\r
if (position != 0) {\r
// Equivalent to contains(set, value)\r
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in\r
// the array, and then remove the last element (sometimes called as 'swap and pop').\r
// This modifies the order of the array, as noted in {at}.\r
\r
uint256 valueIndex = position - 1;\r
uint256 lastIndex = set._values.length - 1;\r
\r
if (valueIndex != lastIndex) {\r
bytes32 lastValue = set._values[lastIndex];\r
\r
// Move the lastValue to the index where the value to delete is\r
set._values[valueIndex] = lastValue;\r
// Update the tracked position of the lastValue (that was just moved)\r
set._positions[lastValue] = position;\r
}\r
\r
// Delete the slot where the moved value was stored\r
set._values.pop();\r
\r
// Delete the tracked position for the deleted slot\r
delete set._positions[value];\r
\r
return true;\r
} else {\r
return false;\r
}\r
}\r
\r
/**\r
* @dev Returns true if the value is in the set. O(1).\r
*/\r
function _contains(\r
Set storage set,\r
bytes32 value\r
) private view returns (bool) {\r
return set._positions[value] != 0;\r
}\r
\r
/**\r
* @dev Returns the number of values on the set. O(1).\r
*/\r
function _length(Set storage set) private view returns (uint256) {\r
return set._values.length;\r
}\r
\r
/**\r
* @dev Returns the value stored at position `index` in the set. O(1).\r
*\r
* Note that there are no guarantees on the ordering of values inside the\r
* array, and it may change when more values are added or removed.\r
*\r
* Requirements:\r
*\r
* - `index` must be strictly less than {length}.\r
*/\r
function _at(\r
Set storage set,\r
uint256 index\r
) private view returns (bytes32) {\r
return set._values[index];\r
}\r
\r
/**\r
* @dev Return the entire set in an array\r
*\r
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\r
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\r
* this function has an unbounded cost, and using it as part of a state-changing function may render the function\r
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\r
*/\r
function _values(Set storage set) private view returns (bytes32[] memory) {\r
return set._values;\r
}\r
\r
// Bytes32Set\r
\r
struct Bytes32Set {\r
Set _inner;\r
}\r
\r
/**\r
* @dev Add a value to a set. O(1).\r
*\r
* Returns true if the value was added to the set, that is if it was not\r
* already present.\r
*/\r
function add(\r
Bytes32Set storage set,\r
bytes32 value\r
) internal returns (bool) {\r
return _add(set._inner, value);\r
}\r
\r
/**\r
* @dev Removes a value from a set. O(1).\r
*\r
* Returns true if the value was removed from the set, that is if it was\r
* present.\r
*/\r
function remove(\r
Bytes32Set storage set,\r
bytes32 value\r
) internal returns (bool) {\r
return _remove(set._inner, value);\r
}\r
\r
/**\r
* @dev Returns true if the value is in the set. O(1).\r
*/\r
function contains(\r
Bytes32Set storage set,\r
bytes32 value\r
) internal view returns (bool) {\r
return _contains(set._inner, value);\r
}\r
\r
/**\r
* @dev Returns the number of values in the set. O(1).\r
*/\r
function length(Bytes32Set storage set) internal view returns (uint256) {\r
return _length(set._inner);\r
}\r
\r
/**\r
* @dev Returns the value stored at position `index` in the set. O(1).\r
*\r
* Note that there are no guarantees on the ordering of values inside the\r
* array, and it may change when more values are added or removed.\r
*\r
* Requirements:\r
*\r
* - `index` must be strictly less than {length}.\r
*/\r
function at(\r
Bytes32Set storage set,\r
uint256 index\r
) internal view returns (bytes32) {\r
return _at(set._inner, index);\r
}\r
\r
/**\r
* @dev Return the entire set in an array\r
*\r
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\r
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\r
* this function has an unbounded cost, and using it as part of a state-changing function may render the function\r
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\r
*/\r
function values(\r
Bytes32Set storage set\r
) internal view returns (bytes32[] me
Submitted on: 2025-10-27 13:14:52
Comments
Log in to comment.
No comments yet.