Contract Name:
MultiLockMintERC721Pool
Contract Source Code:
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {TokenPool} from "src/pools/TokenPool.sol";
import {MultiTokenPool} from "src/pools/MultiTokenPool.sol";
import {LockMintERC721Pool} from "src/pools/erc721/LockMintERC721Pool.sol";
import {IMultiLockMintERC721Pool} from "src/interfaces/pools/erc721/IMultiLockMintERC721Pool.sol";
import {Pool} from "src/libraries/Pool.sol";
contract MultiLockMintERC721Pool is MultiTokenPool, LockMintERC721Pool, IMultiLockMintERC721Pool {
string public constant override typeAndVersion = "MultiLockMintERC721Pool 1.0.0";
constructor() {
_disableInitializers();
}
function initialize(address admin, address router, uint64 currentChainSelector) external initializer {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
__PausableExtended_init(admin);
__RateLimitConsumer_init(admin);
__SharedStorageConsumer_init(admin);
__TokenPool_init(admin, router, currentChainSelector);
}
/**
* @inheritdoc IMultiLockMintERC721Pool
*/
function withdrawLiquidity(address[] calldata localTokens, address[] calldata tos, uint256[] calldata ids)
external
onlyRole(DEFAULT_ADMIN_ROLE)
{
uint256 tokenCount = localTokens.length;
_requireEqualLength(tokenCount, ids.length);
_requireEqualLength(tokenCount, tos.length);
_requireNonZero(tokenCount);
for (uint256 i; i < tokenCount; ++i) {
_requireLocalToken(localTokens[i]);
_requireNonZero(tos[i]);
IERC721(localTokens[i]).transferFrom(address(this), tos[i], ids[i]);
}
}
/**
* @inheritdoc IMultiLockMintERC721Pool
*/
function crossTransfer(
address localToken,
uint64 remoteChainSelector,
address to,
uint256 id,
address feeToken,
uint256 gasLimit
) external payable returns (bytes32 messageId) {
return _crossBatchTransfer(localToken, remoteChainSelector, to, _toSingletonArray(id), feeToken, gasLimit);
}
/**
* @inheritdoc IMultiLockMintERC721Pool
*/
function crossBatchTransfer(
address localToken,
uint64 remoteChainSelector,
address to,
uint256[] calldata ids,
address feeToken,
uint256 gasLimit
) external payable returns (bytes32 messageId) {
return _crossBatchTransfer(localToken, remoteChainSelector, to, ids, feeToken, gasLimit);
}
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(MultiTokenPool, LockMintERC721Pool)
returns (bool)
{
return interfaceId == type(IMultiLockMintERC721Pool).interfaceId
|| LockMintERC721Pool.supportsInterface(interfaceId) || MultiTokenPool.supportsInterface(interfaceId);
}
function _releaseOrMint(Pool.ReleaseOrMint memory releaseOrMint)
internal
virtual
override(TokenPool, LockMintERC721Pool)
{
// Prevent the case when localToken is mapped to many remote chains
// and the message is sent to from the remote chain that is unmapped with the localToken
_requireNonZero(getRemoteToken(releaseOrMint.localToken, releaseOrMint.remoteChainSelector));
super._releaseOrMint(releaseOrMint);
}
function _crossBatchTransfer(
address localToken,
uint64 remoteChainSelector,
address to,
uint256[] memory ids,
address feeToken,
uint256 gasLimit
) internal nonZero(to) returns (bytes32 messageId) {
bytes memory data = abi.encode(ids);
_lockOrBurn(
Pool.LockOrBurn({remoteChainSelector: remoteChainSelector, localToken: localToken, extraData: data})
);
address remoteToken = getRemoteToken(localToken, remoteChainSelector);
_requireNonZero(remoteToken);
messageId = _sendDataPayFeeToken({
remoteChainSelector: remoteChainSelector,
receiver: getRemotePool(remoteChainSelector),
data: abi.encode(
Pool.ReleaseOrMint({
originalSender: msg.sender,
remoteChainSelector: getCurrentChainSelector(),
receiver: to,
localToken: remoteToken,
remotePoolAddress: address(this),
remotePoolData: data
})
),
gasLimit: gasLimit,
allowOutOfOrderExecution: true,
feeToken: feeToken
});
_storeMessageId(getCurrentChainSelector(), messageId);
}
function _toSingletonArray(uint256 id) internal pure returns (uint256[] memory ids) {
ids = new uint256[](1);
ids[0] = id;
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
* {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the address zero.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import {ITypeAndVersion} from "@chainlink/contracts-ccip/src/v0.8/shared/interfaces/ITypeAndVersion.sol";
import {AccessControlEnumerableUpgradeable} from
"@openzeppelin/contracts-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol";
import {CCIPSenderReceiverUpgradeable} from "src/extensions/CCIPSenderReceiverUpgradeable.sol";
import {ITokenPool} from "src/interfaces/pools/ITokenPool.sol";
import {Pool} from "src/libraries/Pool.sol";
abstract contract TokenPool is AccessControlEnumerableUpgradeable, CCIPSenderReceiverUpgradeable, ITokenPool {
bytes32 public constant TOKEN_POOL_OWNER_ROLE = keccak256("TOKEN_POOL_OWNER_ROLE");
/// @dev Gap for future upgrades
uint256[50] private __gap1;
/// @dev Mapping of chain selector to array of message IDs
mapping(uint64 chainSelector => bytes32[] messageIds) private s_messageIds;
/// @dev Mapping of chain selector to exists mapping of message IDs
mapping(uint64 chainSelector => mapping(bytes32 messageId => bool exists)) private s_messageIdExists;
/// @dev Gap for future upgrades
uint256[50] private __gap2;
modifier onlyLocalToken(address localToken) {
_requireLocalToken(localToken);
_;
}
function __TokenPool_init(address owner, address router, uint64 currentChainSelector) internal onlyInitializing {
__TokenPool_init_unchained(owner);
__CCIPSenderReceiver_init(router, currentChainSelector);
}
function __TokenPool_init_unchained(address owner) internal onlyInitializing {
_grantRole(TOKEN_POOL_OWNER_ROLE, owner);
}
/**
* @inheritdoc ITokenPool
*/
function addRemotePool(uint64 remoteChainSelector, address remotePool) external onlyRole(TOKEN_POOL_OWNER_ROLE) {
_addRemoteChain(remoteChainSelector, remotePool);
emit RemotePoolAdded(msg.sender, remoteChainSelector, remotePool);
}
/**
* @inheritdoc ITokenPool
*/
function removeRemotePool(uint64 remoteChainSelector) external onlyRole(TOKEN_POOL_OWNER_ROLE) {
_removeRemoteChain(remoteChainSelector);
emit RemotePoolRemoved(msg.sender, remoteChainSelector);
}
/**
* @inheritdoc ITokenPool
*/
function mapRemoteToken(address localToken, uint64 remoteChainSelector, address remoteToken)
external
onlyRole(TOKEN_POOL_OWNER_ROLE)
nonZero(localToken)
onlyEnabledChain(remoteChainSelector)
{
_requireNonZero(remoteToken);
_mapRemoteToken(localToken, remoteChainSelector, remoteToken);
emit RemoteTokenMapped(msg.sender, remoteChainSelector, localToken, remoteToken);
}
/**
* @inheritdoc ITokenPool
*/
function unmapRemoteToken(address localToken, uint64 remoteChainSelector)
external
onlyRole(TOKEN_POOL_OWNER_ROLE)
onlyEnabledChain(remoteChainSelector)
{
_unmapRemoteToken(localToken, remoteChainSelector);
emit RemoteTokenUnmapped(msg.sender, remoteChainSelector, localToken);
}
/**
* @inheritdoc ITokenPool
*/
function getRemotePool(uint64 remoteChainSelector) public view returns (address remotePool) {
return _getRemoteSender(remoteChainSelector);
}
/**
* @inheritdoc ITokenPool
*/
function getRemotePools()
external
view
returns (uint64[] memory remoteChainSelectors, address[] memory remotePools)
{
remoteChainSelectors = getSupportedChains();
uint256 length = remoteChainSelectors.length;
remotePools = new address[](length);
for (uint256 i; i < length; ++i) {
remotePools[i] = getRemotePool(remoteChainSelectors[i]);
}
}
/**
* @inheritdoc ITokenPool
*/
function getMessageIds(uint64 chainSelector, uint256 offset, uint256 limit)
external
view
returns (uint256 length, bytes32[] memory messageIds)
{
length = s_messageIds[chainSelector].length;
if (offset >= length) return (length, new bytes32[](0));
if (offset + limit > length) limit = length - offset;
messageIds = new bytes32[](limit);
for (uint256 i; i < limit; ++i) {
messageIds[i] = s_messageIds[chainSelector][offset + i];
}
}
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(AccessControlEnumerableUpgradeable, CCIPSenderReceiverUpgradeable)
returns (bool)
{
return interfaceId == type(ITypeAndVersion).interfaceId || interfaceId == type(ITokenPool).interfaceId
|| super.supportsInterface(interfaceId);
}
function _storeMessageId(uint64 chainSelector, bytes32 messageId) internal {
if (s_messageIdExists[chainSelector][messageId]) revert MessageIdAlreadyExists(chainSelector, messageId);
s_messageIds[chainSelector].push(messageId);
s_messageIdExists[chainSelector][messageId] = true;
}
/**
* @dev Virtual function to be implemented by derived contracts to handle the lock or burn operation.
*/
function _lockOrBurn(Pool.LockOrBurn memory lockOrBurn) internal virtual;
/**
* @dev Virtual function to be implemented by derived contracts to handle the release or mint operation.
*/
function _releaseOrMint(Pool.ReleaseOrMint memory releaseOrMint) internal virtual;
/**
* @dev Virtual function to be implemented by derived contracts to handle the mapping of remote tokens.
*/
function _mapRemoteToken(address localToken, uint64 remoteChainSelector, address remoteToken) internal virtual;
/**
* @dev Virtual function to be implemented by derived contracts to handle the unmapping of remote tokens.
*/
function _unmapRemoteToken(address localToken, uint64 remoteChainSelector) internal virtual;
/**
* @dev Internal function to require that the local token is supported by the pool.
*/
function _requireLocalToken(address localToken) internal view virtual;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {TokenPool} from "src/pools/TokenPool.sol";
import {ITokenPool} from "src/interfaces/pools/ITokenPool.sol";
import {IMultiTokenPool} from "src/interfaces/pools/IMultiTokenPool.sol";
abstract contract MultiTokenPool is TokenPool, IMultiTokenPool {
using EnumerableSet for EnumerableSet.AddressSet;
/// @dev Gap for future upgrades
uint256[50] private __gap1;
/// @dev The set of local tokens.
EnumerableSet.AddressSet private s_localTokens;
/// @dev The mapping of local token address => remote chain selector => remote token address.
mapping(address local => mapping(uint64 remoteChainSelector => address)) private s_remoteTokens;
/// @dev Gap for future upgrades
uint256[50] private __gap2;
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IMultiTokenPool).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @inheritdoc IMultiTokenPool
*/
function getSupportedTokensForChain(uint64 remoteChainSelector)
public
view
returns (address[] memory localTokens, address[] memory remoteTokens)
{
if (!isSupportedChain(remoteChainSelector)) return (new address[](0), new address[](0));
uint256 tokenCount = s_localTokens.length();
localTokens = new address[](tokenCount);
remoteTokens = new address[](tokenCount);
uint256 count;
for (uint256 i; i < tokenCount; ++i) {
address localToken = s_localTokens.at(i);
address remoteToken = s_remoteTokens[localToken][remoteChainSelector];
if (remoteToken != address(0)) {
localTokens[count] = localToken;
remoteTokens[count] = remoteToken;
++count;
}
}
assembly ("memory-safe") {
mstore(localTokens, count)
mstore(remoteTokens, count)
}
}
/**
* @inheritdoc IMultiTokenPool
*/
function getTokens() public view returns (address[] memory localTokens) {
address[] memory tokenSet = s_localTokens.values();
uint256 tokenCount = tokenSet.length;
localTokens = new address[](tokenCount);
uint256 count;
for (uint256 i; i < tokenCount; ++i) {
if (isSupportedToken(tokenSet[i])) {
localTokens[count++] = tokenSet[i];
}
}
assembly ("memory-safe") {
mstore(localTokens, count)
}
}
/**
* @inheritdoc ITokenPool
*/
function isSupportedToken(address localToken) public view virtual override returns (bool yes) {
// Short circuit if the token is not in the set
if (!s_localTokens.contains(localToken)) return false;
// Check if the token has a mapping on any enabled chain
uint64[] memory supportedChains = getSupportedChains();
uint256 chainCount = supportedChains.length;
for (uint256 i; i < chainCount; ++i) {
if (getRemoteToken(localToken, supportedChains[i]) != address(0)) {
return true;
}
}
return false;
}
/**
* @inheritdoc IMultiTokenPool
*/
function getRemoteToken(address localToken, uint64 remoteChainSelector) public view returns (address remoteToken) {
if (!isSupportedChain(remoteChainSelector)) return address(0);
return s_remoteTokens[localToken][remoteChainSelector];
}
/**
* @dev See {ITokenPool-mapRemoteToken}.
*/
function _mapRemoteToken(address localToken, uint64 remoteChainSelector, address remoteToken)
internal
virtual
override
{
// Check if the remote token is already mapped to the local token
(address[] memory existingLocalTokens, address[] memory existingRemoteTokens) =
getSupportedTokensForChain(remoteChainSelector);
if (existingRemoteTokens.length > 0) {
for (uint256 i; i < existingRemoteTokens.length; ++i) {
if (existingRemoteTokens[i] == remoteToken) {
revert TokenAlreadyMapped(existingLocalTokens[i], remoteToken);
}
}
}
s_localTokens.add(localToken);
s_remoteTokens[localToken][remoteChainSelector] = remoteToken;
}
/**
* @dev See {ITokenPool-unmapRemoteToken}.
*/
function _unmapRemoteToken(address localToken, uint64 remoteChainSelector) internal virtual override {
address remoteToken = s_remoteTokens[localToken][remoteChainSelector];
if (remoteToken == address(0)) revert TokenNotMapped(localToken, remoteChainSelector);
delete s_remoteTokens[localToken][remoteChainSelector];
// If the token is not mapped to any other chain, remove it from the set
if (!isSupportedToken(localToken)) s_localTokens.remove(localToken);
}
/**
* @dev See {TokenPool-requireLocalToken}.
*/
function _requireLocalToken(address localToken) internal view virtual override {
if (!isSupportedToken(localToken)) revert OnlyLocalToken();
}
/**
* @dev Checks if the two arrays have the same length.
*/
function _requireEqualLength(uint256 a, uint256 b) internal pure {
if (a != b) revert LengthMismatch(a, b);
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import {AccessControlEnumerableUpgradeable} from
"@openzeppelin/contracts-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC721Mintable} from "src/interfaces/external/IERC721Mintable.sol";
import {PausableExtendedUpgradeable} from "src/extensions/PausableExtendedUpgradeable.sol";
import {RateLimitConsumerUpgradeable} from "src/extensions/RateLimitConsumerUpgradeable.sol";
import {SharedStorageConsumerUpgradeable} from "src/extensions/SharedStorageConsumerUpgradeable.sol";
import {TokenPool} from "src/pools/TokenPool.sol";
import {ILockMintERC721Pool} from "src/interfaces/pools/erc721/ILockMintERC721Pool.sol";
import {Pool} from "src/libraries/Pool.sol";
abstract contract LockMintERC721Pool is
PausableExtendedUpgradeable,
RateLimitConsumerUpgradeable,
SharedStorageConsumerUpgradeable,
TokenPool,
ILockMintERC721Pool
{
/**
* @inheritdoc ILockMintERC721Pool
*/
function estimateFee(address feeToken, uint64 remoteChainSelector, uint256 tokenCount, uint256 gasLimit)
external
view
returns (uint256 fee)
{
Pool.ReleaseOrMint memory empty;
empty.remotePoolData = abi.encode(new uint256[](tokenCount));
(fee,) = _getSendDataFee({
remoteChainSelector: remoteChainSelector,
receiver: getRemotePool(remoteChainSelector),
data: abi.encode(empty),
gasLimit: gasLimit,
allowOutOfOrderExecution: true,
feeToken: feeToken
});
}
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(PausableExtendedUpgradeable, RateLimitConsumerUpgradeable, SharedStorageConsumerUpgradeable, TokenPool)
returns (bool)
{
return interfaceId == type(ILockMintERC721Pool).interfaceId || TokenPool.supportsInterface(interfaceId)
|| SharedStorageConsumerUpgradeable.supportsInterface(interfaceId)
|| RateLimitConsumerUpgradeable.supportsInterface(interfaceId)
|| PausableExtendedUpgradeable.supportsInterface(interfaceId);
}
function _ccipReceive(Client.Any2EVMMessage calldata message) internal virtual override {
Pool.ReleaseOrMint memory releaseOrMint = abi.decode(message.data, (Pool.ReleaseOrMint));
// Validate the message.sourceChainSelector against the releaseOrMint.remoteChainSelector
if (message.sourceChainSelector != releaseOrMint.remoteChainSelector) {
revert RemoteChainSelectorNotMatch(message.sourceChainSelector, releaseOrMint.remoteChainSelector);
}
// Validate the message.sender against the releaseOrMint.remotePoolAddress
address remoteSender = abi.decode(message.sender, (address));
if (remoteSender != releaseOrMint.remotePoolAddress) {
revert RemotePoolNotMatch(remoteSender, releaseOrMint.remotePoolAddress);
}
_requireNonZero(releaseOrMint.originalSender);
_storeMessageId(releaseOrMint.remoteChainSelector, message.messageId);
_releaseOrMint(releaseOrMint);
emit CrossTransfer(
releaseOrMint.originalSender,
releaseOrMint.receiver,
message.messageId,
abi.decode(releaseOrMint.remotePoolData, (uint256[])),
message.sourceChainSelector,
getCurrentChainSelector()
);
}
function _releaseOrMint(Pool.ReleaseOrMint memory releaseOrMint) internal virtual override whenNotPaused {
uint256[] memory ids = abi.decode(releaseOrMint.remotePoolData, (uint256[]));
uint256 tokenCount = ids.length;
_requireNonZero(tokenCount);
_requireNonZero(releaseOrMint.receiver);
_requireLocalToken(releaseOrMint.localToken);
for (uint256 i; i < tokenCount; ++i) {
uint256 id = ids[i];
address owned = _tryGetOwnerOf(releaseOrMint.localToken, id);
if (owned == address(this) || isSharedStorage(owned)) {
IERC721(releaseOrMint.localToken).transferFrom(owned, releaseOrMint.receiver, id);
} else {
IERC721Mintable(releaseOrMint.localToken).mint(releaseOrMint.receiver, id);
}
}
_requireDelivered(releaseOrMint.localToken, releaseOrMint.receiver, ids);
_consumeInboundRateLimit(releaseOrMint.remoteChainSelector, releaseOrMint.localToken, tokenCount);
}
function _lockOrBurn(Pool.LockOrBurn memory lockOrBurn)
internal
virtual
override
whenNotPaused
onlyLocalToken(lockOrBurn.localToken)
onlyEnabledChain(lockOrBurn.remoteChainSelector)
{
uint256[] memory ids = abi.decode(lockOrBurn.extraData, (uint256[]));
uint256 tokenCount = ids.length;
_requireNonZero(tokenCount);
for (uint256 i; i < tokenCount; ++i) {
IERC721(lockOrBurn.localToken).transferFrom(msg.sender, address(this), ids[i]);
}
_requireDelivered(lockOrBurn.localToken, address(this), ids);
_consumeOutboundRateLimit(lockOrBurn.remoteChainSelector, lockOrBurn.localToken, tokenCount);
}
function _requireDelivered(address token, address recipient, uint256[] memory ids) internal view {
uint256 tokenCount = ids.length;
for (uint256 i; i < tokenCount; ++i) {
if (_tryGetOwnerOf(token, ids[i]) != recipient) revert ERC721TransferFailed(recipient, ids[i]);
}
}
function _tryGetOwnerOf(address token, uint256 id) internal view returns (address ownedBy) {
try IERC721(token).ownerOf(id) returns (address by) {
return by;
} catch {
// Handle the case where the token does not exist or is not an ERC721
return address(0);
}
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
interface IMultiLockMintERC721Pool {
/**
* @dev Withdraw the liquidity of the tokens in the pool.
* Requirements:
* - The caller must have `DEFAULT_ADMIN_ROLE`.
* - The `localTokens` must be the same length as `tos` and `ids`.
* - The `tos` must not be zero.
* - `localTokens` must be supported by the pool.
*/
function withdrawLiquidity(address[] calldata localTokens, address[] calldata tos, uint256[] calldata ids)
external;
/**
* @dev Cross transfer a single token to the remote chain.
* Requirements:
* - `to` must not be zero.
* - `id` must be approved for transfer.
* - token fee should be query via `estimateFee` before calling this function.
* - if `feeToken` is native (address(0)), the caller must send enough native tokens to cover the fee.
* - if `feeToken` is ERC20, the caller must approve the pool to spend the fee token.
* @return messageId The message ID of the cross transfer.
*/
function crossTransfer(
address localToken,
uint64 remoteChainSelector,
address to,
uint256 id,
address feeToken,
uint256 gasLimit
) external payable returns (bytes32 messageId);
/**
* @dev Cross transfer multiple tokens to the remote chain.
* Requirements:
* - `to` must not be zero.
* - `ids` must be approved for transfer.
* - token fee should be query via `estimateFee` before calling this function.
* - if `feeToken` is native (address(0)), the caller must send enough native tokens to cover the fee.
* - if `feeToken` is ERC20, the caller must approve the pool to spend the fee token.
* @return messageId The message ID of the cross transfer.
*/
function crossBatchTransfer(
address localToken,
uint64 remoteChainSelector,
address to,
uint256[] calldata ids,
address feeToken,
uint256 gasLimit
) external payable returns (bytes32 messageId);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
library Pool {
struct LockOrBurn {
uint64 remoteChainSelector;
address localToken;
bytes extraData;
}
struct ReleaseOrMint {
address originalSender;
uint64 remoteChainSelector;
address receiver;
address localToken;
address remotePoolAddress;
bytes remotePoolData;
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @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);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface ITypeAndVersion {
function typeAndVersion() external pure returns (string memory);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/extensions/AccessControlEnumerable.sol)
pragma solidity ^0.8.20;
import {IAccessControlEnumerable} from "@openzeppelin/contracts/access/extensions/IAccessControlEnumerable.sol";
import {AccessControlUpgradeable} from "../AccessControlUpgradeable.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";
/**
* @dev Extension of {AccessControl} that allows enumerating the members of each role.
*/
abstract contract AccessControlEnumerableUpgradeable is Initializable, IAccessControlEnumerable, AccessControlUpgradeable {
using EnumerableSet for EnumerableSet.AddressSet;
/// @custom:storage-location erc7201:openzeppelin.storage.AccessControlEnumerable
struct AccessControlEnumerableStorage {
mapping(bytes32 role => EnumerableSet.AddressSet) _roleMembers;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.AccessControlEnumerable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant AccessControlEnumerableStorageLocation = 0xc1f6fe24621ce81ec5827caf0253cadb74709b061630e6b55e82371705932000;
function _getAccessControlEnumerableStorage() private pure returns (AccessControlEnumerableStorage storage $) {
assembly {
$.slot := AccessControlEnumerableStorageLocation
}
}
function __AccessControlEnumerable_init() internal onlyInitializing {
}
function __AccessControlEnumerable_init_unchained() internal onlyInitializing {
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) public view virtual returns (address) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
return $._roleMembers[role].at(index);
}
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) public view virtual returns (uint256) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
return $._roleMembers[role].length();
}
/**
* @dev Return all accounts that have `role`
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function getRoleMembers(bytes32 role) public view virtual returns (address[] memory) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
return $._roleMembers[role].values();
}
/**
* @dev Overload {AccessControl-_grantRole} to track enumerable memberships
*/
function _grantRole(bytes32 role, address account) internal virtual override returns (bool) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
bool granted = super._grantRole(role, account);
if (granted) {
$._roleMembers[role].add(account);
}
return granted;
}
/**
* @dev Overload {AccessControl-_revokeRole} to track enumerable memberships
*/
function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
bool revoked = super._revokeRole(role, account);
if (revoked) {
$._roleMembers[role].remove(account);
}
return revoked;
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import {IRMN} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRMN.sol";
import {IWrappedNative} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IWrappedNative.sol";
import {IAny2EVMMessageReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IAny2EVMMessageReceiver.sol";
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {IRouterClientExtended} from "src/interfaces/external/IRouterClientExtended.sol";
import {IEVM2EVMOnRamp} from "src/interfaces/external/IEVM2EVMOnRamp.sol";
import {ICCIPSenderReceiver} from "src/interfaces/extensions/ICCIPSenderReceiver.sol";
import {Error} from "src/libraries/Error.sol";
abstract contract CCIPSenderReceiverUpgradeable is Initializable, ICCIPSenderReceiver {
using SafeERC20 for IERC20;
using EnumerableSet for EnumerableSet.UintSet;
/// @dev Gap for future storage
uint256[50] private __gap1;
/// @dev Current chain selector
uint64 private s_currentChainSelector;
/// @dev CCIP Risk Management Network Proxy
IRMN private s_rmnProxy;
/// @dev CCIP Router
IRouterClientExtended private s_router;
/// @dev Remote chain selectors set
EnumerableSet.UintSet private s_remoteChainSelectors;
/// @dev Mapping of remote chain selector to enabled sender. This is the address that is allowed to send messages from the remote chain.
mapping(uint64 remoteChainSelector => address) private s_remoteSenders;
/// @dev Gap for future storage
uint256[50] private __gap2;
modifier onlyEnabledChain(uint64 remoteChainSelector) {
_requireEnabledChain(remoteChainSelector);
_;
}
modifier onlyLocalChain(uint64 currentChainSelector) {
_requireLocalChain(currentChainSelector);
_;
}
modifier nonZero(address addr) {
_requireNonZero(addr);
_;
}
modifier notCursed(uint64 remoteChainSelector) {
_requireNotCursed(remoteChainSelector);
_;
}
function __CCIPSenderReceiver_init(address router, uint64 currentChainSelector) internal onlyInitializing {
__CCIPSenderReceiver_init_unchained(router, currentChainSelector);
}
function __CCIPSenderReceiver_init_unchained(address router, uint64 currentChainSelector)
internal
onlyInitializing
{
_requireNonZero(currentChainSelector);
s_router = IRouterClientExtended(router);
s_rmnProxy = IRMN(s_router.getArmProxy());
s_currentChainSelector = currentChainSelector;
}
/**
* @inheritdoc IAny2EVMMessageReceiver
*/
function ccipReceive(Client.Any2EVMMessage calldata message)
external
override
notCursed(message.sourceChainSelector)
{
address sender = abi.decode(message.sender, (address));
_requireEnabledSender(message.sourceChainSelector, sender);
_requireRouter();
_ccipReceive(message);
emit MessageReceived(sender, message.messageId);
}
/**
* @inheritdoc ICCIPSenderReceiver
*/
function isFeeTokenSupported(uint64 remoteChainSelector, address feeToken) external view returns (bool yes) {
address[] memory feeTokens = getFeeTokens(remoteChainSelector);
uint256 feeTokenCount = feeTokens.length;
if (feeTokenCount == 0) return false;
for (uint256 i; i < feeTokenCount; ++i) {
if (feeTokens[i] == feeToken) return true;
}
return false;
}
/**
* @inheritdoc ICCIPSenderReceiver
*/
function getFeeTokens(uint64 remoteChainSelector)
public
view
onlyEnabledChain(remoteChainSelector)
returns (address[] memory feeTokens)
{
return IEVM2EVMOnRamp(s_router.getOnRamp(remoteChainSelector)).getDynamicConfig().priceRegistry.getFeeTokens();
}
/**
* @inheritdoc ICCIPSenderReceiver
*/
function isLocalChain(uint64 currentChainSelector) public view returns (bool yes) {
return currentChainSelector == s_currentChainSelector;
}
/**
* @inheritdoc ICCIPSenderReceiver
*/
function isSupportedChain(uint64 remoteChainSelector) public view returns (bool yes) {
return s_remoteChainSelectors.contains(remoteChainSelector) && s_router.isChainSupported(remoteChainSelector);
}
/**
* @inheritdoc ICCIPSenderReceiver
*/
function isSenderEnabled(uint64 remoteChainSelector, address sender) public view returns (bool yes) {
if (!isSupportedChain(remoteChainSelector)) return false;
return s_remoteSenders[remoteChainSelector] == sender;
}
/**
* @inheritdoc ICCIPSenderReceiver
*/
function getSupportedChains() public view returns (uint64[] memory remoteChainSelectors) {
uint256[] memory values = s_remoteChainSelectors.values();
uint256 length = values.length;
remoteChainSelectors = new uint64[](length);
uint256 count;
for (uint256 i; i < length; ++i) {
uint64 chain = uint64(values[i]);
if (s_router.isChainSupported(chain)) {
remoteChainSelectors[count++] = chain;
}
}
assembly ("memory-safe") {
// Resize the array to the actual number of supported chains
mstore(remoteChainSelectors, count)
}
}
/**
* @inheritdoc ICCIPSenderReceiver
*/
function getCurrentChainSelector() public view returns (uint64 currentChainSelector) {
return s_currentChainSelector;
}
/**
* @inheritdoc ICCIPSenderReceiver
*/
function getRouter() public view returns (IRouterClientExtended router) {
return s_router;
}
/**
* @inheritdoc ICCIPSenderReceiver
*/
function getRmnProxy() external view returns (IRMN rmnProxy) {
return s_rmnProxy;
}
/**
* @notice IERC165 supports an interfaceId
* @param interfaceId The interfaceId to check
* @return true if the interfaceId is supported
* @dev Should indicate whether the contract implements IAny2EVMMessageReceiver
* e.g. return interfaceId == type(IAny2EVMMessageReceiver).interfaceId || interfaceId == type(IERC165).interfaceId
* This allows CCIP to check if ccipReceive is available before calling it.
* If this returns false or reverts, only tokens are transferred to the receiver.
* If this returns true, tokens are transferred and ccipReceive is called atomically.
* Additionally, if the receiver address does not have code associated with
* it at the time of execution (EXTCODESIZE returns 0), only tokens will be transferred.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(ICCIPSenderReceiver).interfaceId
|| interfaceId == type(IAny2EVMMessageReceiver).interfaceId || interfaceId == type(IERC165).interfaceId;
}
/**
* @notice Override this function in your implementation.
* @param message Any2EVMMessage
*/
function _ccipReceive(Client.Any2EVMMessage calldata message) internal virtual;
/**
* @dev Enable a remote chain
* @param remoteChainSelector The chain selector of the remote chain
* @param remoteSender The address of the sender on the remote chain
*/
function _addRemoteChain(uint64 remoteChainSelector, address remoteSender) internal {
_requireNonZero(remoteSender);
if (remoteChainSelector == s_currentChainSelector) revert OnlyRemoteChain(remoteChainSelector);
if (!s_router.isChainSupported(remoteChainSelector)) revert ChainNotSupported(remoteChainSelector);
if (!s_remoteChainSelectors.add(remoteChainSelector)) revert ChainAlreadyEnabled(remoteChainSelector);
s_remoteSenders[remoteChainSelector] = remoteSender;
emit RemoteChainEnabled(msg.sender, remoteChainSelector, remoteSender);
}
/**
* @dev Disable a remote chain
* @param remoteChainSelector The chain selector of the remote chain
*/
function _removeRemoteChain(uint64 remoteChainSelector) internal {
if (!s_remoteChainSelectors.remove(remoteChainSelector)) revert NonExistentChain(remoteChainSelector);
delete s_remoteSenders[remoteChainSelector];
emit RemoteChainDisabled(msg.sender, remoteChainSelector);
}
/**
* @notice Send data to a remote chain and pay fee by ERC20 token
* If the feeToken is address(0), the fee will be wrapped into WrappedNative token.
* Revert if the feeToken is not supported by the remote chain.
*/
function _sendDataPayFeeToken(
uint64 remoteChainSelector,
address receiver,
bytes memory data,
uint256 gasLimit,
bool allowOutOfOrderExecution,
address feeToken
) internal notCursed(remoteChainSelector) returns (bytes32 messageId) {
_requireNonZero(receiver);
_requireNonZero(gasLimit);
_requireNonZero(data.length);
(uint256 fee, Client.EVM2AnyMessage memory message) =
_getSendDataFee(remoteChainSelector, receiver, data, gasLimit, allowOutOfOrderExecution, feeToken);
uint256 overspent;
if (feeToken == address(0)) {
if (msg.value < fee) revert InsufficientAllowance(fee, msg.value);
feeToken = s_router.getWrappedNative();
// Overwrite feeToken to wrapped native token
message.feeToken = feeToken;
IWrappedNative(feeToken).deposit{value: fee}();
overspent = msg.value - fee;
} else {
if (msg.value != 0) revert MsgValueNotAllowed(msg.value);
IERC20(feeToken).safeTransferFrom(msg.sender, address(this), fee);
}
IERC20(feeToken).approve(address(s_router), fee);
messageId = s_router.ccipSend(remoteChainSelector, message);
if (overspent > 0) {
// solhint-disable-next-line avoid-low-level-calls
(bool success,) = msg.sender.call{value: overspent}("");
if (!success) revert RefundFailed(msg.sender, overspent);
emit Refunded(msg.sender, overspent);
}
emit MessageSent(msg.sender, messageId);
}
/**
* @notice Estimate the fee for sending data to a remote chain
*/
function _getSendDataFee(
uint64 remoteChainSelector,
address receiver,
bytes memory data,
uint256 gasLimit,
bool allowOutOfOrderExecution,
address feeToken
) internal view onlyEnabledChain(remoteChainSelector) returns (uint256 fee, Client.EVM2AnyMessage memory message) {
message = Client.EVM2AnyMessage({
receiver: abi.encode(receiver),
data: data,
tokenAmounts: new Client.EVMTokenAmount[](0),
extraArgs: Client._argsToBytes(
Client.EVMExtraArgsV2({gasLimit: gasLimit, allowOutOfOrderExecution: allowOutOfOrderExecution})
),
feeToken: feeToken
});
fee = s_router.getFee(remoteChainSelector, message);
}
function _getRemoteSender(uint64 remoteChainSelector) internal view returns (address remoteAddress) {
if (!isSupportedChain(remoteChainSelector)) return address(0);
return s_remoteSenders[remoteChainSelector];
}
function _requireLocalChain(uint64 currentChainSelector) internal view {
if (currentChainSelector != s_currentChainSelector) revert OnlyLocalChain(currentChainSelector);
}
function _requireRouter() internal view {
if (msg.sender != address(s_router)) revert InvalidRouter(address(s_router), msg.sender);
}
function _requireNonZero(address addr) internal pure {
if (addr == address(0)) revert Error.ZeroAddressNotAllowed();
}
function _requireNonZero(uint256 val) internal pure {
if (val == 0) revert ZeroValueNotAllowed();
}
function _requireNotCursed(uint64 remoteChainSelector) internal view {
if (s_rmnProxy.isCursed(bytes16(uint128(remoteChainSelector)))) revert CursedByRMN();
}
function _requireEnabledChain(uint64 remoteChainSelector) internal view {
if (!isSupportedChain(remoteChainSelector)) revert NonExistentChain(remoteChainSelector);
}
function _requireEnabledSender(uint64 remoteChainSelector, address sender) internal view {
if (!isSenderEnabled(remoteChainSelector, sender)) revert SenderNotEnabled(remoteChainSelector, sender);
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import {ITypeAndVersion} from "@chainlink/contracts-ccip/src/v0.8/shared/interfaces/ITypeAndVersion.sol";
interface ITokenPool is ITypeAndVersion {
/// @dev Throw if the messageId already exists.
error MessageIdAlreadyExists(uint64 chainSelector, bytes32 messageId);
/// @dev emit when gas config for cross-chain message is set.
event GasLimitConfigured(address indexed by, uint32 fixedGas, uint32 dynamicGas);
/// @dev emit when remote pool is added.
event RemotePoolAdded(address indexed by, uint64 indexed remoteChainSelector, address indexed remotePool);
/// @dev emit when remote pool is removed.
event RemotePoolRemoved(address indexed by, uint64 indexed remoteChainSelector);
/// @dev emit when remote token is mapped.
event RemoteTokenMapped(
address indexed by, uint64 indexed remoteChainSelector, address indexed localToken, address remoteToken
);
/// @dev emit when remote token is unmapped.
event RemoteTokenUnmapped(address indexed by, uint64 indexed remoteChainSelector, address indexed localToken);
/**
* @dev Token pool owner role.
* Value is equal to keccak256("TOKEN_POOL_OWNER_ROLE").
*/
function TOKEN_POOL_OWNER_ROLE() external view returns (bytes32);
/**
* @dev Enabled `remotePool` to interact with this pool.
* Requirements:
* - `remotePool` is not null.
* - `remoteChainSelector` is not null.
* - `remoteChainSelector` != `currentChainSelector`.
* - Caller must have `TOKEN_POOL_OWNER_ROLE`.
* - `remoteChainSelector` must be supported by CCIP router.
*/
function addRemotePool(uint64 remoteChainSelector, address remotePool) external;
/**
* @dev Disable `remotePool` to interact with this pool.
* Requirements:
* - `remoteChainSelector` is not null.
* - `remoteChainSelector` != `currentChainSelector`.
* - Caller must have `TOKEN_POOL_OWNER_ROLE`.
* - Remote pool must be added before.
*
* Removing a remote pool does not delete the token mappings for the specified remote chain.
* However, all tokens mapped to the removed remote pool will be deactivated.
*/
function removeRemotePool(uint64 remoteChainSelector) external;
/**
* @dev Map local token to remote token for a given remote chain.
* Requirements:
* - `localToken` is not null.
* - `remoteChainSelector` must be enabled.
* - `remoteToken` is not null.
* - Caller must have `TOKEN_POOL_OWNER_ROLE`.
*/
function mapRemoteToken(address localToken, uint64 remoteChainSelector, address remoteToken) external;
/**
* @dev Unmap local token to remote token for a given remote chain.
* Requirements:
* - `localToken` must be added before.
* - `remoteChainSelector` must be enabled.
* - Caller must have `TOKEN_POOL_OWNER_ROLE`.
* - If `remoteChainSelector` is not enabled in this pool or no remote token is mapped to `localToken`, `localToken` will be removed from the set.
*/
function unmapRemoteToken(address localToken, uint64 remoteChainSelector) external;
/**
* @dev Get all supported chain selectors along with their remote pools pair for this pool/
* @return remoteChainSelectors The list of remote chain selectors.
* @return remotePools The list of enabled sender pools for the given remote chain.
*/
function getRemotePools()
external
view
returns (uint64[] memory remoteChainSelectors, address[] memory remotePools);
/**
* @dev Get remote token address for a given local token and remote chain.
* Returns zero address if the token is not mapped or `remoteChainSelector` is disabled.
*/
function getRemotePool(uint64 remoteChainSelector) external view returns (address remotePool);
/**
* @dev Returns whether the token is supported by the pool.
*/
function isSupportedToken(address token) external view returns (bool yes);
/**
* @dev Returns the array of message IDs for the given chain selector.
*/
function getMessageIds(uint64 chainSelector, uint256 offset, uint256 limit)
external
view
returns (uint256 length, bytes32[] memory messageIds);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.20;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position is the index of the value in the `values` array plus 1.
// Position 0 is used to mean a value is not in the set.
mapping(bytes32 value => uint256) _positions;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._positions[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slot
uint256 position = set._positions[value];
if (position != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 valueIndex = position - 1;
uint256 lastIndex = set._values.length - 1;
if (valueIndex != lastIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the lastValue to the index where the value to delete is
set._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
set._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the tracked position for the deleted slot
delete set._positions[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._positions[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
interface IMultiTokenPool {
/// @dev Revert if the given token is not supported by the pool.
error OnlyLocalToken();
/// @dev Revert if array lengths do not match.
error LengthMismatch(uint256 expected, uint256 actual);
/// @dev Revert if the local token does not have remote token mapped given the remote chain.
error TokenNotMapped(address localToken, uint64 remoteChainSelector);
/// @dev Revert if the local token already has remote token mapped given the remote chain.
error TokenAlreadyMapped(address localToken, address remoteToken);
/**
* @dev Get all supported token pair for a given chain.
* Requirements:
* - The remote chain is be enabled by the pool.
* @param remoteChainSelector The selector of the remote chain.
* @return localTokens The list of local tokens supported by the pool.
* @return remoteTokens The list of remote tokens supported by the pool.
*/
function getSupportedTokensForChain(uint64 remoteChainSelector)
external
view
returns (address[] memory localTokens, address[] memory remoteTokens);
/**
* @dev Get all supported local tokens.
* @return localTokens The list of local tokens supported by the pool.
*/
function getTokens() external view returns (address[] memory localTokens);
/**
* @dev Get remote token address for a given local token and remote chain.
* Requirements:
* - The local token must be supported by the pool.
* - The remote chain must be enabled by the pool.
*/
function getRemoteToken(address localToken, uint64 remoteChainSelector)
external
view
returns (address remoteToken);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// End consumer library.
library Client {
/// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
struct EVMTokenAmount {
address token; // token address on the local chain.
uint256 amount; // Amount of tokens.
}
struct Any2EVMMessage {
bytes32 messageId; // MessageId corresponding to ccipSend on source.
uint64 sourceChainSelector; // Source chain selector.
bytes sender; // abi.decode(sender) if coming from an EVM chain.
bytes data; // payload sent in original message.
EVMTokenAmount[] destTokenAmounts; // Tokens and their amounts in their destination chain representation.
}
// If extraArgs is empty bytes, the default is 200k gas limit.
struct EVM2AnyMessage {
bytes receiver; // abi.encode(receiver address) for dest EVM chains
bytes data; // Data payload
EVMTokenAmount[] tokenAmounts; // Token transfers
address feeToken; // Address of feeToken. address(0) means you will send msg.value.
bytes extraArgs; // Populate this with _argsToBytes(EVMExtraArgsV2)
}
// bytes4(keccak256("CCIP EVMExtraArgsV1"));
bytes4 public constant EVM_EXTRA_ARGS_V1_TAG = 0x97a657c9;
struct EVMExtraArgsV1 {
uint256 gasLimit;
}
function _argsToBytes(
EVMExtraArgsV1 memory extraArgs
) internal pure returns (bytes memory bts) {
return abi.encodeWithSelector(EVM_EXTRA_ARGS_V1_TAG, extraArgs);
}
// bytes4(keccak256("CCIP EVMExtraArgsV2"));
bytes4 public constant EVM_EXTRA_ARGS_V2_TAG = 0x181dcf10;
/// @param gasLimit: gas limit for the callback on the destination chain.
/// @param allowOutOfOrderExecution: if true, it indicates that the message can be executed in any order relative to other messages from the same sender.
/// This value's default varies by chain. On some chains, a particular value is enforced, meaning if the expected value
/// is not set, the message request will revert.
struct EVMExtraArgsV2 {
uint256 gasLimit;
bool allowOutOfOrderExecution;
}
function _argsToBytes(
EVMExtraArgsV2 memory extraArgs
) internal pure returns (bytes memory bts) {
return abi.encodeWithSelector(EVM_EXTRA_ARGS_V2_TAG, extraArgs);
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
interface IERC721Mintable is IERC721 {
function mint(address to, uint256 id) external;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import {AccessControlEnumerableUpgradeable} from
"@openzeppelin/contracts-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol";
import {IPausable} from "src/interfaces/external/IPausable.sol";
import {IPausableExtended} from "src/interfaces/extensions/IPausableExtended.sol";
abstract contract PausableExtendedUpgradeable is
PausableUpgradeable,
AccessControlEnumerableUpgradeable,
IPausableExtended
{
/// @inheritdoc IPausableExtended
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
/// @dev Gap for future storage
uint256[50] private __gap1;
address private s_globalPauser;
/// @dev Gap for future storage
uint256[50] private __gap2;
function __PausableExtended_init(address globalPauser) internal onlyInitializing {
__PausableExtended_init_unchained(globalPauser);
}
function __PausableExtended_init_unchained(address globalPauser) internal onlyInitializing {
s_globalPauser = globalPauser;
_grantRole(PAUSER_ROLE, globalPauser);
}
/**
* @inheritdoc IPausableExtended
*/
function pause() external onlyRole(PAUSER_ROLE) {
_pause();
}
/**
* @inheritdoc IPausableExtended
*/
function unpause() external onlyRole(PAUSER_ROLE) {
_unpause();
}
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IPausableExtended).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @inheritdoc IPausableExtended
*/
function getGlobalPauser() external view returns (address globalPauser) {
return s_globalPauser;
}
/**
* @inheritdoc IPausableExtended
*/
function setGlobalPauser(address globalPauser) external onlyRole(DEFAULT_ADMIN_ROLE) {
s_globalPauser = globalPauser;
if (globalPauser == address(0)) {
_revokeRole(PAUSER_ROLE, s_globalPauser);
return;
}
_grantRole(PAUSER_ROLE, globalPauser);
}
function paused() public view virtual override returns (bool) {
address globalPauser = s_globalPauser;
return (hasRole(PAUSER_ROLE, globalPauser) && _hasCode(globalPauser) && IPausable(globalPauser).paused())
|| PausableUpgradeable.paused();
}
function _hasCode(address target) private view returns (bool) {
return target.code.length > 0;
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import {RateLimiter} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/RateLimiter.sol";
import {AccessControlEnumerableUpgradeable} from
"@openzeppelin/contracts-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol";
import {IRateLimitConsumer} from "src/interfaces/extensions/IRateLimitConsumer.sol";
abstract contract RateLimitConsumerUpgradeable is AccessControlEnumerableUpgradeable, IRateLimitConsumer {
using RateLimiter for RateLimiter.Config;
using RateLimiter for RateLimiter.TokenBucket;
bytes32 public constant RATE_LIMITER_ROLE = keccak256("RATE_LIMITER_ROLE");
uint256[50] private __gap1;
/// @dev The outbound rate limit configuration for the given chain selector.
mapping(uint64 remoteChainSelector => RateLimiter.TokenBucket) private s_outboundConfig;
/// @dev The inbound rate limit configuration for the given chain selector.
mapping(uint64 remoteChainSelector => RateLimiter.TokenBucket) private s_inboundConfig;
uint256[50] private __gap2;
function __RateLimitConsumer_init(address rateLimiter) internal onlyInitializing {
__RateLimitConsumer_init_unchained(rateLimiter);
}
function __RateLimitConsumer_init_unchained(address rateLimiter) internal onlyInitializing {
_grantRole(RATE_LIMITER_ROLE, rateLimiter);
}
/**
* @inheritdoc IRateLimitConsumer
*/
function setChainRateLimiterConfig(
uint64 remoteChainSelector,
RateLimiter.Config calldata outboundConfig,
RateLimiter.Config calldata inboundConfig
) external onlyRole(RATE_LIMITER_ROLE) {
_setRateLimitConfig(remoteChainSelector, outboundConfig, inboundConfig);
}
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IRateLimitConsumer).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @inheritdoc IRateLimitConsumer
*/
function getCurrentOutboundRateLimiterState(uint64 remoteChainSelector)
external
view
returns (RateLimiter.TokenBucket memory state)
{
return s_outboundConfig[remoteChainSelector]._currentTokenBucketState();
}
/**
* @inheritdoc IRateLimitConsumer
*/
function getCurrentInboundRateLimiterState(uint64 remoteChainSelector)
external
view
returns (RateLimiter.TokenBucket memory state)
{
return s_inboundConfig[remoteChainSelector]._currentTokenBucketState();
}
/**
* @notice Consumes outbound rate limiting capacity in this pool
*/
function _consumeOutboundRateLimit(uint64 remoteChainSelector, address token, uint256 amount) internal {
s_outboundConfig[remoteChainSelector]._consume(amount, token);
}
/**
* @notice Consumes inbound rate limiting capacity in this pool
*/
function _consumeInboundRateLimit(uint64 remoteChainSelector, address token, uint256 amount) internal {
s_inboundConfig[remoteChainSelector]._consume(amount, token);
}
function _setRateLimitConfig(
uint64 remoteChainSelector,
RateLimiter.Config memory outboundConfig,
RateLimiter.Config memory inboundConfig
) internal {
outboundConfig._validateTokenBucketConfig({mustBeDisabled: false});
s_outboundConfig[remoteChainSelector]._setTokenBucketConfig(outboundConfig);
inboundConfig._validateTokenBucketConfig({mustBeDisabled: false});
s_inboundConfig[remoteChainSelector]._setTokenBucketConfig(inboundConfig);
emit RateLimitConfigured(msg.sender, remoteChainSelector, outboundConfig, inboundConfig);
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import {AccessControlEnumerableUpgradeable} from
"@openzeppelin/contracts-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol";
import {ISharedStorageConsumer} from "src/interfaces/extensions/ISharedStorageConsumer.sol";
import {Error} from "src/libraries/Error.sol";
abstract contract SharedStorageConsumerUpgradeable is AccessControlEnumerableUpgradeable, ISharedStorageConsumer {
bytes32 public constant SHARED_STORAGE_SETTER_ROLE = keccak256("SHARED_STORAGE_SETTER_ROLE");
/// @dev Gap for future upgrades
uint256[50] private __gap1;
/// @dev Mapping of shared storage addresses to their allowed status
mapping(address sharedStorage => bool allowed) private s_sharedStorage;
/// @dev Gap for future upgrades
uint256[50] private __gap2;
function __SharedStorageConsumer_init(address sharedStorageSetter) internal onlyInitializing {
__SharedStorageConsumer_init_unchained(sharedStorageSetter);
}
function __SharedStorageConsumer_init_unchained(address sharedStorageSetter) internal onlyInitializing {
_grantRole(SHARED_STORAGE_SETTER_ROLE, sharedStorageSetter);
}
/**
* @inheritdoc ISharedStorageConsumer
*/
function setSharedStorage(address sharedStorage, bool shouldAdd) external onlyRole(SHARED_STORAGE_SETTER_ROLE) {
if (sharedStorage == address(0)) revert Error.ZeroAddressNotAllowed();
s_sharedStorage[sharedStorage] = shouldAdd;
emit SharedStorageUpdated(msg.sender, sharedStorage, shouldAdd);
}
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(ISharedStorageConsumer).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @inheritdoc ISharedStorageConsumer
*/
function isSharedStorage(address sharedStorage) public view returns (bool yes) {
return s_sharedStorage[sharedStorage];
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
interface ILockMintERC721Pool {
/// @dev Throws when ccip's message source chain selector does not match with decoded input.
error RemoteChainSelectorNotMatch(uint64 expected, uint64 actual);
/// @dev Throws when ccip's message original sender does not match with current pool config.
error RemotePoolNotMatch(address expected, address actual);
/// @dev Throws when failed to mint token.
error MintFailed(address minter, address to, uint256 id);
/// @dev Throws when failed to transfer token.
error ERC721TransferFailed(address recipient, uint256 id);
/// @dev Emit when successfully bridged nfts for user.
event CrossTransfer(
address indexed srcFrom,
address indexed dstTo,
bytes32 indexed messageId,
uint256[] ids,
uint64 srcChainSelector,
uint64 dstChainSelector
);
/**
* @dev Estimate the fee to pay for CCIP protocol to bridge the tokens.
* @param feeToken The token to pay the fee.
* @param remoteChainSelector The chain selector of the destination chain.
* @param tokenCount The number of tokens to bridge.
* @param gasLimit The gas limit for the transaction on the remote chain.
* @return fee The estimated fee in the feeToken.
*/
function estimateFee(address feeToken, uint64 remoteChainSelector, uint256 tokenCount, uint256 gasLimit)
external
view
returns (uint256 fee);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/extensions/IAccessControlEnumerable.sol)
pragma solidity ^0.8.20;
import {IAccessControl} from "../IAccessControl.sol";
/**
* @dev External interface of AccessControlEnumerable declared to support ERC165 detection.
*/
interface IAccessControlEnumerable is IAccessControl {
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) external view returns (address);
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) external view returns (uint256);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (access/AccessControl.sol)
pragma solidity ^0.8.20;
import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol";
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {ERC165Upgradeable} from "../utils/introspection/ERC165Upgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```solidity
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```solidity
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
* to enforce additional security measures for this role.
*/
abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControl, ERC165Upgradeable {
struct RoleData {
mapping(address account => bool) hasRole;
bytes32 adminRole;
}
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/// @custom:storage-location erc7201:openzeppelin.storage.AccessControl
struct AccessControlStorage {
mapping(bytes32 role => RoleData) _roles;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.AccessControl")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant AccessControlStorageLocation = 0x02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800;
function _getAccessControlStorage() private pure returns (AccessControlStorage storage $) {
assembly {
$.slot := AccessControlStorageLocation
}
}
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with an {AccessControlUnauthorizedAccount} error including the required role.
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
function __AccessControl_init() internal onlyInitializing {
}
function __AccessControl_init_unchained() internal onlyInitializing {
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view virtual returns (bool) {
AccessControlStorage storage $ = _getAccessControlStorage();
return $._roles[role].hasRole[account];
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
* is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
* is missing `role`.
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert AccessControlUnauthorizedAccount(account, role);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
AccessControlStorage storage $ = _getAccessControlStorage();
return $._roles[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleGranted} event.
*/
function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleRevoked} event.
*/
function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been revoked `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `callerConfirmation`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address callerConfirmation) public virtual {
if (callerConfirmation != _msgSender()) {
revert AccessControlBadConfirmation();
}
_revokeRole(role, callerConfirmation);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
AccessControlStorage storage $ = _getAccessControlStorage();
bytes32 previousAdminRole = getRoleAdmin(role);
$._roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
AccessControlStorage storage $ = _getAccessControlStorage();
if (!hasRole(role, account)) {
$._roles[role].hasRole[account] = true;
emit RoleGranted(role, account, _msgSender());
return true;
} else {
return false;
}
}
/**
* @dev Attempts to revoke `role` from `account` and returns a boolean indicating if `role` was revoked.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
AccessControlStorage storage $ = _getAccessControlStorage();
if (hasRole(role, account)) {
$._roles[role].hasRole[account] = false;
emit RoleRevoked(role, account, _msgSender());
return true;
} else {
return false;
}
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.20;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Storage of the initializable contract.
*
* It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
* when using with upgradeable contracts.
*
* @custom:storage-location erc7201:openzeppelin.storage.Initializable
*/
struct InitializableStorage {
/**
* @dev Indicates that the contract has been initialized.
*/
uint64 _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool _initializing;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
/**
* @dev The contract is already initialized.
*/
error InvalidInitialization();
/**
* @dev The contract is not initializing.
*/
error NotInitializing();
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint64 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
* number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
* production.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
// Cache values to avoid duplicated sloads
bool isTopLevelCall = !$._initializing;
uint64 initialized = $._initialized;
// Allowed calls:
// - initialSetup: the contract is not in the initializing state and no previous version was
// initialized
// - construction: the contract is initialized at version 1 (no reinitialization) and the
// current contract is just being deployed
bool initialSetup = initialized == 0 && isTopLevelCall;
bool construction = initialized == 1 && address(this).code.length == 0;
if (!initialSetup && !construction) {
revert InvalidInitialization();
}
$._initialized = 1;
if (isTopLevelCall) {
$._initializing = true;
}
_;
if (isTopLevelCall) {
$._initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint64 version) {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing || $._initialized >= version) {
revert InvalidInitialization();
}
$._initialized = version;
$._initializing = true;
_;
$._initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
_checkInitializing();
_;
}
/**
* @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
*/
function _checkInitializing() internal view virtual {
if (!_isInitializing()) {
revert NotInitializing();
}
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing) {
revert InvalidInitialization();
}
if ($._initialized != type(uint64).max) {
$._initialized = type(uint64).max;
emit Initialized(type(uint64).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint64) {
return _getInitializableStorage()._initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _getInitializableStorage()._initializing;
}
/**
* @dev Pointer to storage slot. Allows integrators to override it with a custom storage location.
*
* NOTE: Consider following the ERC-7201 formula to derive storage locations.
*/
function _initializableStorageSlot() internal pure virtual returns (bytes32) {
return INITIALIZABLE_STORAGE;
}
/**
* @dev Returns a pointer to the storage namespace.
*/
// solhint-disable-next-line var-name-mixedcase
function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
bytes32 slot = _initializableStorageSlot();
assembly {
$.slot := slot
}
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @notice This interface contains the only RMN-related functions that might be used on-chain by other CCIP contracts.
interface IRMN {
/// @notice A Merkle root tagged with the address of the commit store contract it is destined for.
struct TaggedRoot {
address commitStore;
bytes32 root;
}
/// @notice Callers MUST NOT cache the return value as a blessed tagged root could become unblessed.
function isBlessed(
TaggedRoot calldata taggedRoot
) external view returns (bool);
/// @notice Iff there is an active global or legacy curse, this function returns true.
function isCursed() external view returns (bool);
/// @notice Iff there is an active global curse, or an active curse for `subject`, this function returns true.
/// @param subject To check whether a particular chain is cursed, set to bytes16(uint128(chainSelector)).
function isCursed(
bytes16 subject
) external view returns (bool);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol";
interface IWrappedNative is IERC20 {
function deposit() external payable;
function withdraw(
uint256 wad
) external;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Client} from "../libraries/Client.sol";
/// @notice Application contracts that intend to receive messages from
/// the router should implement this interface.
interface IAny2EVMMessageReceiver {
/// @notice Called by the Router to deliver a message.
/// If this reverts, any token transfers also revert. The message
/// will move to a FAILED state and become available for manual execution.
/// @param message CCIP Message
/// @dev Note ensure you check the msg.sender is the OffRampRouter
function ccipReceive(
Client.Any2EVMMessage calldata message
) external;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @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 value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` 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 value) external returns (bool);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev An operation with an ERC20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data);
if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {IRouter} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouter.sol";
interface IRouterClientExtended is IRouter, IRouterClient {
function getArmProxy() external view returns (address);
function getWrappedNative() external view returns (address);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import {IPriceRegistry} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IPriceRegistry.sol";
interface IEVM2EVMOnRamp {
/// @dev Struct to contains the dynamic configuration
struct DynamicConfig {
address router; // ──────────────────────────╮ Router address
uint16 maxNumberOfTokensPerMsg; // │ Maximum number of distinct ERC20 token transferred per message
uint32 destGasOverhead; // │ Gas charged on top of the gasLimit to cover destination chain costs
uint16 destGasPerPayloadByte; // │ Destination chain gas charged for passing each byte of `data` payload to receiver
uint32 destDataAvailabilityOverheadGas; // ──╯ Extra data availability gas charged on top of the message, e.g. for OCR
uint16 destGasPerDataAvailabilityByte; // ───╮ Amount of gas to charge per byte of message data that needs availability
uint16 destDataAvailabilityMultiplierBps; // │ Multiplier for data availability gas, multiples of bps, or 0.0001
IPriceRegistry priceRegistry; // │ Price registry address
uint32 maxDataBytes; // │ Maximum payload data size in bytes
uint32 maxPerMsgGasLimit; // ────────────────╯ Maximum gas limit for messages targeting EVMs
// │
// The following three properties are defaults, they can be overridden by setting the TokenTransferFeeConfig for a token
uint16 defaultTokenFeeUSDCents; // ──────────╮ Default token fee charged per token transfer
uint32 defaultTokenDestGasOverhead; // │ Default gas charged to execute the token transfer on the destination chain
bool enforceOutOfOrder; // ──────────────────╯ Whether to enforce the allowOutOfOrderExecution extraArg value to be true.
}
function getDynamicConfig() external view returns (DynamicConfig memory dynamicConfig);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import {IRMN} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRMN.sol";
import {IAny2EVMMessageReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IAny2EVMMessageReceiver.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {IRouterClientExtended} from "src/interfaces/external/IRouterClientExtended.sol";
interface ICCIPSenderReceiver is IAny2EVMMessageReceiver, IERC165 {
/// @dev Revert if the remote chain is cursed by RMN.
error CursedByRMN();
/// @dev Revert if the sender is not enabled for the remote chain.
error SenderNotEnabled(uint64 chainSelector, address sender);
/// @dev Revert if the provided chain selector is current chain selector.
error OnlyRemoteChain(uint64 chainSelector);
/// @dev Revert if the provided chain selector is not current chain selector.
error OnlyLocalChain(uint64 chainSelector);
/// @dev Revert if the provided value is zero.
error ZeroValueNotAllowed();
/// @dev Revert if the provided address is not a valid router.
error InvalidRouter(address expected, address actual);
/// @dev Revert if the provided `chainSelector` is not enabled.
error NonExistentChain(uint64 chainSelector);
/// @dev Revert if the provided `chainSelector` is already enabled.
error ChainAlreadyEnabled(uint64 chainSelector);
/// @dev Revert if the provided `chainSelector` is not supported by CCIP router.
error ChainNotSupported(uint64 chainSelector);
/// @dev Revert if not allow receiving msg.value.
error MsgValueNotAllowed(uint256 value);
/// @dev Revert if failed to refund to the sender.
error RefundFailed(address to, uint256 value);
/// @dev Revert if provided fee is insufficient.
error InsufficientAllowance(uint256 expected, uint256 actual);
/// @dev Emit when refunded to the sender.
event Refunded(address indexed to, uint256 value);
/// @dev Emit when the message is sent to CCIP.
event MessageSent(address indexed by, bytes32 indexed messageId);
/// @dev Emit when the message is received from CCIP.
event MessageReceived(address by, bytes32 indexed messageId);
/// @dev Emit when the given `chainSelector` is disabled.
event RemoteChainDisabled(address indexed by, uint64 indexed chainSelector);
/// @dev Emit when the given `chainSelector` is enabled.
event RemoteChainEnabled(address indexed by, uint64 indexed chainSelector, address indexed remoteSender);
/*
* @dev Check whether the given `feeToken` is supported for the given `remoteChainSelector`.
*/
function isFeeTokenSupported(uint64 remoteChainSelector, address feeToken) external view returns (bool yes);
/*
* @dev Get all supported fee tokens for the given `remoteChainSelector`.
*/
function getFeeTokens(uint64 remoteChainSelector) external view returns (address[] memory feeTokens);
/*
* @dev Check whether given `chainSelector` is current chain selector.
*/
function isLocalChain(uint64 currentChainSelector) external view returns (bool yes);
/*
* @dev Check whether given `chainSelector` is supported by this contract.
*/
function isSupportedChain(uint64 remoteChainSelector) external view returns (bool yes);
/*
* @dev Get all supported chains for this contract.
*/
function getSupportedChains() external view returns (uint64[] memory remoteChainSelectors);
/*
* @dev Check whether the given `sender` is enabled for the given `remoteChainSelector`.
*/
function isSenderEnabled(uint64 remoteChainSelector, address sender) external view returns (bool yes);
/*
* @dev Get chain selector of the current chain.
*/
function getCurrentChainSelector() external view returns (uint64 currentChainSelector);
/*
* @dev Get CCIP router address.
*/
function getRouter() external view returns (IRouterClientExtended router);
/*
* @dev Get RMN proxy address.
*/
function getRmnProxy() external view returns (IRMN rmnProxy);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
library Error {
error ZeroAddressNotAllowed();
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
/// @custom:storage-location erc7201:openzeppelin.storage.Pausable
struct PausableStorage {
bool _paused;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Pausable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant PausableStorageLocation = 0xcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300;
function _getPausableStorage() private pure returns (PausableStorage storage $) {
assembly {
$.slot := PausableStorageLocation
}
}
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
function __Pausable_init() internal onlyInitializing {
}
function __Pausable_init_unchained() internal onlyInitializing {
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
PausableStorage storage $ = _getPausableStorage();
return $._paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
PausableStorage storage $ = _getPausableStorage();
$._paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
PausableStorage storage $ = _getPausableStorage();
$._paused = false;
emit Unpaused(_msgSender());
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
interface IPausable {
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
function paused() external view returns (bool);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
interface IPausableExtended {
/**
* @dev Pauser role.
* Value is equal to keccak256("PAUSER_ROLE").
*/
function PAUSER_ROLE() external view returns (bytes32);
/**
* @dev Get global pauser address.
*/
function getGlobalPauser() external view returns (address globalPauser);
/**
* @dev Set global pauser address.
* Requirements:
* - The caller must have the DEFAULT_ADMIN_ROLE.
* @param globalPauser The address of the global pauser.
*/
function setGlobalPauser(address globalPauser) external;
/**
* @dev Pause the contract.
* Requirements:
* - The caller must have the PAUSER_ROLE.
*/
function pause() external;
/**
* @dev Unpause the contract.
* Requirements:
* - The caller must have the PAUSER_ROLE.
*/
function unpause() external;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.4;
/// @notice Implements Token Bucket rate limiting.
/// @dev uint128 is safe for rate limiter state.
/// For USD value rate limiting, it can adequately store USD value in 18 decimals.
/// For ERC20 token amount rate limiting, all tokens that will be listed will have at most
/// a supply of uint128.max tokens, and it will therefore not overflow the bucket.
/// In exceptional scenarios where tokens consumed may be larger than uint128,
/// e.g. compromised issuer, an enabled RateLimiter will check and revert.
library RateLimiter {
error BucketOverfilled();
error OnlyCallableByAdminOrOwner();
error TokenMaxCapacityExceeded(uint256 capacity, uint256 requested, address tokenAddress);
error TokenRateLimitReached(uint256 minWaitInSeconds, uint256 available, address tokenAddress);
error AggregateValueMaxCapacityExceeded(uint256 capacity, uint256 requested);
error AggregateValueRateLimitReached(uint256 minWaitInSeconds, uint256 available);
error InvalidRateLimitRate(Config rateLimiterConfig);
error DisabledNonZeroRateLimit(Config config);
error RateLimitMustBeDisabled();
event TokensConsumed(uint256 tokens);
event ConfigChanged(Config config);
struct TokenBucket {
uint128 tokens; // ──────╮ Current number of tokens that are in the bucket.
uint32 lastUpdated; // │ Timestamp in seconds of the last token refill, good for 100+ years.
bool isEnabled; // ──────╯ Indication whether the rate limiting is enabled or not
uint128 capacity; // ────╮ Maximum number of tokens that can be in the bucket.
uint128 rate; // ────────╯ Number of tokens per second that the bucket is refilled.
}
struct Config {
bool isEnabled; // Indication whether the rate limiting should be enabled
uint128 capacity; // ────╮ Specifies the capacity of the rate limiter
uint128 rate; // ───────╯ Specifies the rate of the rate limiter
}
/// @notice _consume removes the given tokens from the pool, lowering the
/// rate tokens allowed to be consumed for subsequent calls.
/// @param requestTokens The total tokens to be consumed from the bucket.
/// @param tokenAddress The token to consume capacity for, use 0x0 to indicate aggregate value capacity.
/// @dev Reverts when requestTokens exceeds bucket capacity or available tokens in the bucket
/// @dev emits removal of requestTokens if requestTokens is > 0
function _consume(TokenBucket storage s_bucket, uint256 requestTokens, address tokenAddress) internal {
// If there is no value to remove or rate limiting is turned off, skip this step to reduce gas usage
if (!s_bucket.isEnabled || requestTokens == 0) {
return;
}
uint256 tokens = s_bucket.tokens;
uint256 capacity = s_bucket.capacity;
uint256 timeDiff = block.timestamp - s_bucket.lastUpdated;
if (timeDiff != 0) {
if (tokens > capacity) revert BucketOverfilled();
// Refill tokens when arriving at a new block time
tokens = _calculateRefill(capacity, tokens, timeDiff, s_bucket.rate);
s_bucket.lastUpdated = uint32(block.timestamp);
}
if (capacity < requestTokens) {
// Token address 0 indicates consuming aggregate value rate limit capacity.
if (tokenAddress == address(0)) revert AggregateValueMaxCapacityExceeded(capacity, requestTokens);
revert TokenMaxCapacityExceeded(capacity, requestTokens, tokenAddress);
}
if (tokens < requestTokens) {
uint256 rate = s_bucket.rate;
// Wait required until the bucket is refilled enough to accept this value, round up to next higher second
// Consume is not guaranteed to succeed after wait time passes if there is competing traffic.
// This acts as a lower bound of wait time.
uint256 minWaitInSeconds = ((requestTokens - tokens) + (rate - 1)) / rate;
if (tokenAddress == address(0)) revert AggregateValueRateLimitReached(minWaitInSeconds, tokens);
revert TokenRateLimitReached(minWaitInSeconds, tokens, tokenAddress);
}
tokens -= requestTokens;
// Downcast is safe here, as tokens is not larger than capacity
s_bucket.tokens = uint128(tokens);
emit TokensConsumed(requestTokens);
}
/// @notice Gets the token bucket with its values for the block it was requested at.
/// @return The token bucket.
function _currentTokenBucketState(
TokenBucket memory bucket
) internal view returns (TokenBucket memory) {
// We update the bucket to reflect the status at the exact time of the
// call. This means we might need to refill a part of the bucket based
// on the time that has passed since the last update.
bucket.tokens =
uint128(_calculateRefill(bucket.capacity, bucket.tokens, block.timestamp - bucket.lastUpdated, bucket.rate));
bucket.lastUpdated = uint32(block.timestamp);
return bucket;
}
/// @notice Sets the rate limited config.
/// @param s_bucket The token bucket
/// @param config The new config
function _setTokenBucketConfig(TokenBucket storage s_bucket, Config memory config) internal {
// First update the bucket to make sure the proper rate is used for all the time
// up until the config change.
uint256 timeDiff = block.timestamp - s_bucket.lastUpdated;
if (timeDiff != 0) {
s_bucket.tokens = uint128(_calculateRefill(s_bucket.capacity, s_bucket.tokens, timeDiff, s_bucket.rate));
s_bucket.lastUpdated = uint32(block.timestamp);
}
s_bucket.tokens = uint128(_min(config.capacity, s_bucket.tokens));
s_bucket.isEnabled = config.isEnabled;
s_bucket.capacity = config.capacity;
s_bucket.rate = config.rate;
emit ConfigChanged(config);
}
/// @notice Validates the token bucket config
function _validateTokenBucketConfig(Config memory config, bool mustBeDisabled) internal pure {
if (config.isEnabled) {
if (config.rate >= config.capacity || config.rate == 0) {
revert InvalidRateLimitRate(config);
}
if (mustBeDisabled) {
revert RateLimitMustBeDisabled();
}
} else {
if (config.rate != 0 || config.capacity != 0) {
revert DisabledNonZeroRateLimit(config);
}
}
}
/// @notice Calculate refilled tokens
/// @param capacity bucket capacity
/// @param tokens current bucket tokens
/// @param timeDiff block time difference since last refill
/// @param rate bucket refill rate
/// @return the value of tokens after refill
function _calculateRefill(
uint256 capacity,
uint256 tokens,
uint256 timeDiff,
uint256 rate
) private pure returns (uint256) {
return _min(capacity, tokens + timeDiff * rate);
}
/// @notice Return the smallest of two integers
/// @param a first int
/// @param b second int
/// @return smallest
function _min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import {RateLimiter} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/RateLimiter.sol";
interface IRateLimitConsumer {
/// @dev Emit when the rate limit is configured.
event RateLimitConfigured(
address indexed by,
uint64 indexed chainSelector,
RateLimiter.Config outboundConfig,
RateLimiter.Config inboundConfig
);
function RATE_LIMITER_ROLE() external view returns (bytes32);
/**
* @notice Sets the chain rate limiter config.
* @param remoteChainSelector The remote chain selector for which the rate limits apply.
* @param outboundConfig The new outbound rate limiter config, meaning the onRamp rate limits for the given chain.
* @param inboundConfig The new inbound rate limiter config, meaning the offRamp rate limits for the given chain.
*/
function setChainRateLimiterConfig(
uint64 remoteChainSelector,
RateLimiter.Config memory outboundConfig,
RateLimiter.Config memory inboundConfig
) external;
/**
* @notice Gets the token bucket with its values for the block it was requested at.
* @return state The token bucket.
*/
function getCurrentOutboundRateLimiterState(uint64 remoteChainSelector)
external
view
returns (RateLimiter.TokenBucket memory state);
/**
* @notice Gets the token bucket with its values for the block it was requested at.
* @return state The token bucket.
*/
function getCurrentInboundRateLimiterState(uint64 remoteChainSelector)
external
view
returns (RateLimiter.TokenBucket memory state);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
interface ISharedStorageConsumer {
/// @dev emit when shared storage is set.
event SharedStorageUpdated(address indexed sender, address indexed sharedStorage, bool shouldAdd);
/**
* @dev Sets the shared storage address. Used for pool to transferFrom
* @param sharedStorage The address of the shared storage contract.
* @param shouldAdd A boolean indicating whether to add or remove the shared storage.
*/
function setSharedStorage(address sharedStorage, bool shouldAdd) external;
/**
* @dev Shared storage setter role.
* Value is equal to keccak256("SHARED_STORAGE_SETTER_ROLE").
*/
function SHARED_STORAGE_SETTER_ROLE() external view returns (bytes32);
/**
* @dev Checks if the given address is a shared storage.
* @param sharedStorage The address to check.
* @return yes A boolean indicating whether the address is a shared storage.
*/
function isSharedStorage(address sharedStorage) external view returns (bool);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/IAccessControl.sol)
pragma solidity ^0.8.20;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControl {
/**
* @dev The `account` is missing a role.
*/
error AccessControlUnauthorizedAccount(address account, bytes32 neededRole);
/**
* @dev The caller of a function is not the expected one.
*
* NOTE: Don't confuse with {AccessControlUnauthorizedAccount}.
*/
error AccessControlBadConfirmation();
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `callerConfirmation`.
*/
function renounceRole(bytes32 role, address callerConfirmation) external;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC-165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*/
abstract contract ERC165Upgradeable is Initializable, IERC165 {
function __ERC165_init() internal onlyInitializing {
}
function __ERC165_init_unchained() internal onlyInitializing {
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// 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);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error AddressInsufficientBalance(address account);
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedInnerCall();
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert AddressInsufficientBalance(address(this));
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert FailedInnerCall();
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {FailedInnerCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert AddressInsufficientBalance(address(this));
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
* unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {FailedInnerCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
*/
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert FailedInnerCall();
}
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {Client} from "../libraries/Client.sol";
interface IRouterClient {
error UnsupportedDestinationChain(uint64 destChainSelector);
error InsufficientFeeTokenAmount();
error InvalidMsgValue();
/// @notice Checks if the given chain ID is supported for sending/receiving.
/// @param destChainSelector The chain to check.
/// @return supported is true if it is supported, false if not.
function isChainSupported(
uint64 destChainSelector
) external view returns (bool supported);
/// @param destinationChainSelector The destination chainSelector
/// @param message The cross-chain CCIP message including data and/or tokens
/// @return fee returns execution fee for the message
/// delivery to destination chain, denominated in the feeToken specified in the message.
/// @dev Reverts with appropriate reason upon invalid message.
function getFee(
uint64 destinationChainSelector,
Client.EVM2AnyMessage memory message
) external view returns (uint256 fee);
/// @notice Request a message to be sent to the destination chain
/// @param destinationChainSelector The destination chain ID
/// @param message The cross-chain CCIP message including data and/or tokens
/// @return messageId The message ID
/// @dev Note if msg.value is larger than the required fee (from getFee) we accept
/// the overpayment with no refund.
/// @dev Reverts with appropriate reason upon invalid message.
function ccipSend(
uint64 destinationChainSelector,
Client.EVM2AnyMessage calldata message
) external payable returns (bytes32);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Client} from "../libraries/Client.sol";
interface IRouter {
error OnlyOffRamp();
/// @notice Route the message to its intended receiver contract.
/// @param message Client.Any2EVMMessage struct.
/// @param gasForCallExactCheck of params for exec
/// @param gasLimit set of params for exec
/// @param receiver set of params for exec
/// @dev if the receiver is a contracts that signals support for CCIP execution through EIP-165.
/// the contract is called. If not, only tokens are transferred.
/// @return success A boolean value indicating whether the ccip message was received without errors.
/// @return retBytes A bytes array containing return data form CCIP receiver.
/// @return gasUsed the gas used by the external customer call. Does not include any overhead.
function routeMessage(
Client.Any2EVMMessage calldata message,
uint16 gasForCallExactCheck,
uint256 gasLimit,
address receiver
) external returns (bool success, bytes memory retBytes, uint256 gasUsed);
/// @notice Returns the configured onramp for a specific destination chain.
/// @param destChainSelector The destination chain Id to get the onRamp for.
/// @return onRampAddress The address of the onRamp.
function getOnRamp(
uint64 destChainSelector
) external view returns (address onRampAddress);
/// @notice Return true if the given offRamp is a configured offRamp for the given source chain.
/// @param sourceChainSelector The source chain selector to check.
/// @param offRamp The address of the offRamp to check.
function isOffRamp(uint64 sourceChainSelector, address offRamp) external view returns (bool isOffRamp);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {Internal} from "../libraries/Internal.sol";
interface IPriceRegistry {
/// @notice Update the price for given tokens and gas prices for given chains.
/// @param priceUpdates The price updates to apply.
function updatePrices(
Internal.PriceUpdates memory priceUpdates
) external;
/// @notice Get the `tokenPrice` for a given token.
/// @param token The token to get the price for.
/// @return tokenPrice The tokenPrice for the given token.
function getTokenPrice(
address token
) external view returns (Internal.TimestampedPackedUint224 memory);
/// @notice Get the `tokenPrice` for a given token, checks if the price is valid.
/// @param token The token to get the price for.
/// @return tokenPrice The tokenPrice for the given token if it exists and is valid.
function getValidatedTokenPrice(
address token
) external view returns (uint224);
/// @notice Get the `tokenPrice` for an array of tokens.
/// @param tokens The tokens to get prices for.
/// @return tokenPrices The tokenPrices for the given tokens.
function getTokenPrices(
address[] calldata tokens
) external view returns (Internal.TimestampedPackedUint224[] memory);
/// @notice Get an encoded `gasPrice` for a given destination chain ID.
/// The 224-bit result encodes necessary gas price components.
/// On L1 chains like Ethereum or Avax, the only component is the gas price.
/// On Optimistic Rollups, there are two components - the L2 gas price, and L1 base fee for data availability.
/// On future chains, there could be more or differing price components.
/// PriceRegistry does not contain chain-specific logic to parse destination chain price components.
/// @param destChainSelector The destination chain to get the price for.
/// @return gasPrice The encoded gasPrice for the given destination chain ID.
function getDestinationChainGasPrice(
uint64 destChainSelector
) external view returns (Internal.TimestampedPackedUint224 memory);
/// @notice Gets the fee token price and the gas price, both denominated in dollars.
/// @param token The source token to get the price for.
/// @param destChainSelector The destination chain to get the gas price for.
/// @return tokenPrice The price of the feeToken in 1e18 dollars per base unit.
/// @return gasPrice The price of gas in 1e18 dollars per base unit.
function getTokenAndGasPrices(
address token,
uint64 destChainSelector
) external view returns (uint224 tokenPrice, uint224 gasPrice);
/// @notice Convert a given token amount to target token amount.
/// @param fromToken The given token address.
/// @param fromTokenAmount The given token amount.
/// @param toToken The target token address.
/// @return toTokenAmount The target token amount.
function convertTokenAmount(
address fromToken,
uint256 fromTokenAmount,
address toToken
) external view returns (uint256 toTokenAmount);
/// @notice Get the list of fee tokens.
/// @return feeTokens The tokens set as fee tokens.
function getFeeTokens() external view returns (address[] memory);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {MerkleMultiProof} from "../libraries/MerkleMultiProof.sol";
import {Client} from "./Client.sol";
// Library for CCIP internal definitions common to multiple contracts.
library Internal {
error InvalidEVMAddress(bytes encodedAddress);
/// @dev The minimum amount of gas to perform the call with exact gas.
/// We include this in the offramp so that we can redeploy to adjust it
/// should a hardfork change the gas costs of relevant opcodes in callWithExactGas.
uint16 internal constant GAS_FOR_CALL_EXACT_CHECK = 5_000;
// @dev We limit return data to a selector plus 4 words. This is to avoid
// malicious contracts from returning large amounts of data and causing
// repeated out-of-gas scenarios.
uint16 internal constant MAX_RET_BYTES = 4 + 4 * 32;
/// @dev The expected number of bytes returned by the balanceOf function.
uint256 internal constant MAX_BALANCE_OF_RET_BYTES = 32;
/// @notice A collection of token price and gas price updates.
/// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
struct PriceUpdates {
TokenPriceUpdate[] tokenPriceUpdates;
GasPriceUpdate[] gasPriceUpdates;
}
/// @notice Token price in USD.
/// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
struct TokenPriceUpdate {
address sourceToken; // Source token
uint224 usdPerToken; // 1e18 USD per 1e18 of the smallest token denomination.
}
/// @notice Gas price for a given chain in USD, its value may contain tightly packed fields.
/// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
struct GasPriceUpdate {
uint64 destChainSelector; // Destination chain selector
uint224 usdPerUnitGas; // 1e18 USD per smallest unit (e.g. wei) of destination chain gas
}
/// @notice A timestamped uint224 value that can contain several tightly packed fields.
struct TimestampedPackedUint224 {
uint224 value; // ───────╮ Value in uint224, packed.
uint32 timestamp; // ────╯ Timestamp of the most recent price update.
}
/// @dev Gas price is stored in 112-bit unsigned int. uint224 can pack 2 prices.
/// When packing L1 and L2 gas prices, L1 gas price is left-shifted to the higher-order bits.
/// Using uint8 type, which cannot be higher than other bit shift operands, to avoid shift operand type warning.
uint8 public constant GAS_PRICE_BITS = 112;
struct PoolUpdate {
address token; // The IERC20 token address
address pool; // The token pool address
}
struct SourceTokenData {
// The source pool address, abi encoded. This value is trusted as it was obtained through the onRamp. It can be
// relied upon by the destination pool to validate the source pool.
bytes sourcePoolAddress;
// The address of the destination token, abi encoded in the case of EVM chains
// This value is UNTRUSTED as any pool owner can return whatever value they want.
bytes destTokenAddress;
// Optional pool data to be transferred to the destination chain. Be default this is capped at
// CCIP_LOCK_OR_BURN_V1_RET_BYTES bytes. If more data is required, the TokenTransferFeeConfig.destBytesOverhead
// has to be set for the specific token.
bytes extraData;
uint32 destGasAmount; // The amount of gas available for the releaseOrMint and balanceOf calls on the offRamp
}
/// @notice Report that is submitted by the execution DON at the execution phase. (including chain selector data)
/// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
struct ExecutionReportSingleChain {
uint64 sourceChainSelector; // Source chain selector for which the report is submitted
Any2EVMRampMessage[] messages;
// Contains a bytes array for each message, each inner bytes array contains bytes per transferred token
bytes[][] offchainTokenData;
bytes32[] proofs;
uint256 proofFlagBits;
}
/// @notice Report that is submitted by the execution DON at the execution phase.
/// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
struct ExecutionReport {
EVM2EVMMessage[] messages;
// Contains a bytes array for each message, each inner bytes array contains bytes per transferred token
bytes[][] offchainTokenData;
bytes32[] proofs;
uint256 proofFlagBits;
}
/// @notice The cross chain message that gets committed to EVM chains.
/// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
struct EVM2EVMMessage {
uint64 sourceChainSelector; // ────────╮ the chain selector of the source chain, note: not chainId
address sender; // ────────────────────╯ sender address on the source chain
address receiver; // ──────────────────╮ receiver address on the destination chain
uint64 sequenceNumber; // ─────────────╯ sequence number, not unique across lanes
uint256 gasLimit; // user supplied maximum gas amount available for dest chain execution
bool strict; // ───────────────────────╮ DEPRECATED
uint64 nonce; // │ nonce for this lane for this sender, not unique across senders/lanes
address feeToken; // ──────────────────╯ fee token
uint256 feeTokenAmount; // fee token amount
bytes data; // arbitrary data payload supplied by the message sender
Client.EVMTokenAmount[] tokenAmounts; // array of tokens and amounts to transfer
bytes[] sourceTokenData; // array of token data, one per token
bytes32 messageId; // a hash of the message data
}
/// @dev EVM2EVMMessage struct has 13 fields, including 3 variable arrays.
/// Each variable array takes 1 more slot to store its length.
/// When abi encoded, excluding array contents,
/// EVM2EVMMessage takes up a fixed number of 16 lots, 32 bytes each.
/// For structs that contain arrays, 1 more slot is added to the front, reaching a total of 17.
uint256 public constant MESSAGE_FIXED_BYTES = 32 * 17;
/// @dev Each token transfer adds 1 EVMTokenAmount and 3 bytes at 3 slots each and one slot for the destGasAmount.
/// When abi encoded, each EVMTokenAmount takes 2 slots, each bytes takes 1 slot for length, one slot of data and one
/// slot for the offset. This results in effectively 3*3 slots per SourceTokenData.
/// 0x20
/// destGasAmount
/// sourcePoolAddress_offset
/// destTokenAddress_offset
/// extraData_offset
/// sourcePoolAddress_length
/// sourcePoolAddress_content // assume 1 slot
/// destTokenAddress_length
/// destTokenAddress_content // assume 1 slot
/// extraData_length // contents billed separately
uint256 public constant MESSAGE_FIXED_BYTES_PER_TOKEN = 32 * ((1 + 3 * 3) + 2);
/// @dev Any2EVMRampMessage struct has 10 fields, including 3 variable unnested arrays (data, receiver and tokenAmounts).
/// Each variable array takes 1 more slot to store its length.
/// When abi encoded, excluding array contents,
/// Any2EVMMessage takes up a fixed number of 13 slots, 32 bytes each.
/// For structs that contain arrays, 1 more slot is added to the front, reaching a total of 14.
/// The fixed bytes does not cover struct data (this is represented by ANY_2_EVM_MESSAGE_FIXED_BYTES_PER_TOKEN)
uint256 public constant ANY_2_EVM_MESSAGE_FIXED_BYTES = 32 * 14;
/// @dev Each token transfer adds 1 RampTokenAmount
/// RampTokenAmount has 5 fields, 2 of which are bytes type, 1 Address, 1 uint256 and 1 uint32.
/// Each bytes type takes 1 slot for length, 1 slot for data and 1 slot for the offset.
/// address
/// uint256 amount takes 1 slot.
/// uint32 destGasAmount takes 1 slot.
uint256 public constant ANY_2_EVM_MESSAGE_FIXED_BYTES_PER_TOKEN = 32 * ((2 * 3) + 3);
bytes32 internal constant EVM_2_EVM_MESSAGE_HASH = keccak256("EVM2EVMMessageHashV2");
/// @dev Used to hash messages for single-lane ramps.
/// OnRamp hash(EVM2EVMMessage) = OffRamp hash(EVM2EVMMessage)
/// The EVM2EVMMessage's messageId is expected to be the output of this hash function
/// @param original Message to hash
/// @param metadataHash Immutable metadata hash representing a lane with a fixed OnRamp
/// @return hashedMessage hashed message as a keccak256
function _hash(EVM2EVMMessage memory original, bytes32 metadataHash) internal pure returns (bytes32) {
// Fixed-size message fields are included in nested hash to reduce stack pressure.
// This hashing scheme is also used by RMN. If changing it, please notify the RMN maintainers.
return keccak256(
abi.encode(
MerkleMultiProof.LEAF_DOMAIN_SEPARATOR,
metadataHash,
keccak256(
abi.encode(
original.sender,
original.receiver,
original.sequenceNumber,
original.gasLimit,
original.strict,
original.nonce,
original.feeToken,
original.feeTokenAmount
)
),
keccak256(original.data),
keccak256(abi.encode(original.tokenAmounts)),
keccak256(abi.encode(original.sourceTokenData))
)
);
}
bytes32 internal constant ANY_2_EVM_MESSAGE_HASH = keccak256("Any2EVMMessageHashV1");
bytes32 internal constant EVM_2_ANY_MESSAGE_HASH = keccak256("EVM2AnyMessageHashV1");
/// @dev Used to hash messages for multi-lane family-agnostic OffRamps.
/// OnRamp hash(EVM2AnyMessage) != Any2EVMRampMessage.messageId
/// OnRamp hash(EVM2AnyMessage) != OffRamp hash(Any2EVMRampMessage)
/// @param original OffRamp message to hash
/// @param metadataHash Hash preimage to ensure global uniqueness
/// @return hashedMessage hashed message as a keccak256
function _hash(Any2EVMRampMessage memory original, bytes32 metadataHash) internal pure returns (bytes32) {
// Fixed-size message fields are included in nested hash to reduce stack pressure.
// This hashing scheme is also used by RMN. If changing it, please notify the RMN maintainers.
return keccak256(
abi.encode(
MerkleMultiProof.LEAF_DOMAIN_SEPARATOR,
metadataHash,
keccak256(
abi.encode(
original.header.messageId,
original.receiver,
original.header.sequenceNumber,
original.gasLimit,
original.header.nonce
)
),
keccak256(original.sender),
keccak256(original.data),
keccak256(abi.encode(original.tokenAmounts))
)
);
}
function _hash(EVM2AnyRampMessage memory original, bytes32 metadataHash) internal pure returns (bytes32) {
// Fixed-size message fields are included in nested hash to reduce stack pressure.
// This hashing scheme is also used by RMN. If changing it, please notify the RMN maintainers.
return keccak256(
abi.encode(
MerkleMultiProof.LEAF_DOMAIN_SEPARATOR,
metadataHash,
keccak256(
abi.encode(
original.sender,
original.header.sequenceNumber,
original.header.nonce,
original.feeToken,
original.feeTokenAmount
)
),
keccak256(original.receiver),
keccak256(original.data),
keccak256(abi.encode(original.tokenAmounts)),
keccak256(original.extraArgs)
)
);
}
/// @dev We disallow the first 1024 addresses to avoid calling into a range known for hosting precompiles. Calling
/// into precompiles probably won't cause any issues, but to be safe we can disallow this range. It is extremely
/// unlikely that anyone would ever be able to generate an address in this range. There is no official range of
/// precompiles, but EIP-7587 proposes to reserve the range 0x100 to 0x1ff. Our range is more conservative, even
/// though it might not be exhaustive for all chains, which is OK. We also disallow the zero address, which is a
/// common practice.
uint256 public constant PRECOMPILE_SPACE = 1024;
/// @notice This methods provides validation for parsing abi encoded addresses by ensuring the
/// address is within the EVM address space. If it isn't it will revert with an InvalidEVMAddress error, which
/// we can catch and handle more gracefully than a revert from abi.decode.
/// @return The address if it is valid, the function will revert otherwise.
function _validateEVMAddress(
bytes memory encodedAddress
) internal pure returns (address) {
if (encodedAddress.length != 32) revert InvalidEVMAddress(encodedAddress);
uint256 encodedAddressUint = abi.decode(encodedAddress, (uint256));
if (encodedAddressUint > type(uint160).max || encodedAddressUint < PRECOMPILE_SPACE) {
revert InvalidEVMAddress(encodedAddress);
}
return address(uint160(encodedAddressUint));
}
/// @notice Enum listing the possible message execution states within
/// the offRamp contract.
/// UNTOUCHED never executed
/// IN_PROGRESS currently being executed, used a replay protection
/// SUCCESS successfully executed. End state
/// FAILURE unsuccessfully executed, manual execution is now enabled.
/// @dev RMN depends on this enum, if changing, please notify the RMN maintainers.
enum MessageExecutionState {
UNTOUCHED,
IN_PROGRESS,
SUCCESS,
FAILURE
}
/// @notice CCIP OCR plugin type, used to separate execution & commit transmissions and configs
enum OCRPluginType {
Commit,
Execution
}
/// @notice Family-agnostic header for OnRamp & OffRamp messages.
/// The messageId is not expected to match hash(message), since it may originate from another ramp family
struct RampMessageHeader {
bytes32 messageId; // Unique identifier for the message, generated with the source chain's encoding scheme (i.e. not necessarily abi.encoded)
uint64 sourceChainSelector; // ──╮ the chain selector of the source chain, note: not chainId
uint64 destChainSelector; // | the chain selector of the destination chain, note: not chainId
uint64 sequenceNumber; // │ sequence number, not unique across lanes
uint64 nonce; // ────────────────╯ nonce for this lane for this sender, not unique across senders/lanes
}
struct EVM2AnyTokenTransfer {
// The source pool EVM address. This value is trusted as it was obtained through the onRamp. It can be
// relied upon by the destination pool to validate the source pool.
address sourcePoolAddress;
// The EVM address of the destination token
// This value is UNTRUSTED as any pool owner can return whatever value they want.
bytes destTokenAddress;
// Optional pool data to be transferred to the destination chain. Be default this is capped at
// CCIP_LOCK_OR_BURN_V1_RET_BYTES bytes. If more data is required, the TokenTransferFeeConfig.destBytesOverhead
// has to be set for the specific token.
bytes extraData;
uint256 amount; // Amount of tokens.
// Destination chain specific execution data encoded in bytes
// for an EVM destination, it consists of the amount of gas available for the releaseOrMint
// and transfer calls made by the offRamp
bytes destExecData;
}
struct Any2EVMTokenTransfer {
// The source pool EVM address encoded to bytes. This value is trusted as it is obtained through the onRamp. It can be
// relied upon by the destination pool to validate the source pool.
bytes sourcePoolAddress;
address destTokenAddress; // ───╮ Address of destination token
uint32 destGasAmount; //────────╯ The amount of gas available for the releaseOrMint and transfer calls on the offRamp.
// Optional pool data to be transferred to the destination chain. Be default this is capped at
// CCIP_LOCK_OR_BURN_V1_RET_BYTES bytes. If more data is required, the TokenTransferFeeConfig.destBytesOverhead
// has to be set for the specific token.
bytes extraData;
uint256 amount; // Amount of tokens.
}
/// @notice Family-agnostic message routed to an OffRamp
/// Note: hash(Any2EVMRampMessage) != hash(EVM2AnyRampMessage), hash(Any2EVMRampMessage) != messageId
/// due to encoding & parameter differences
struct Any2EVMRampMessage {
RampMessageHeader header; // Message header
bytes sender; // sender address on the source chain
bytes data; // arbitrary data payload supplied by the message sender
address receiver; // receiver address on the destination chain
uint256 gasLimit; // user supplied maximum gas amount available for dest chain execution
Any2EVMTokenTransfer[] tokenAmounts; // array of tokens and amounts to transfer
}
/// @notice Family-agnostic message emitted from the OnRamp
/// Note: hash(Any2EVMRampMessage) != hash(EVM2AnyRampMessage) due to encoding & parameter differences
/// messageId = hash(EVM2AnyRampMessage) using the source EVM chain's encoding format
struct EVM2AnyRampMessage {
RampMessageHeader header; // Message header
address sender; // sender address on the source chain
bytes data; // arbitrary data payload supplied by the message sender
bytes receiver; // receiver address on the destination chain
bytes extraArgs; // destination-chain specific extra args, such as the gasLimit for EVM chains
address feeToken; // fee token
uint256 feeTokenAmount; // fee token amount
uint256 feeValueJuels; // fee amount in Juels
EVM2AnyTokenTransfer[] tokenAmounts; // array of tokens and amounts to transfer
}
// bytes4(keccak256("CCIP ChainFamilySelector EVM"))
bytes4 public constant CHAIN_FAMILY_SELECTOR_EVM = 0x2812d52c;
/// @dev Struct to hold a merkle root and an interval for a source chain so that an array of these can be passed in the CommitReport.
/// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
/// @dev ineffiecient struct packing intentionally chosen to maintain order of specificity. Not a storage struct so impact is minimal.
// solhint-disable-next-line gas-struct-packing
struct MerkleRoot {
uint64 sourceChainSelector; // Remote source chain selector that the Merkle Root is scoped to
bytes onRampAddress; // Generic onramp address, to support arbitrary sources; for EVM, use abi.encode
uint64 minSeqNr; // ─────────────╮ Minimum sequence number, inclusive
uint64 maxSeqNr; // ─────────────╯ Maximum sequence number, inclusive
bytes32 merkleRoot; // Merkle root covering the interval & source chain messages
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.4;
library MerkleMultiProof {
/// @notice Leaf domain separator, should be used as the first 32 bytes of a leaf's preimage.
bytes32 internal constant LEAF_DOMAIN_SEPARATOR = 0x0000000000000000000000000000000000000000000000000000000000000000;
/// @notice Internal domain separator, should be used as the first 32 bytes of an internal node's preiimage.
bytes32 internal constant INTERNAL_DOMAIN_SEPARATOR =
0x0000000000000000000000000000000000000000000000000000000000000001;
uint256 internal constant MAX_NUM_HASHES = 256;
error InvalidProof();
error LeavesCannotBeEmpty();
/// @notice Computes the root based on provided pre-hashed leaf nodes in
/// leaves, internal nodes in proofs, and using proofFlagBits' i-th bit to
/// determine if an element of proofs or one of the previously computed leafs
/// or internal nodes will be used for the i-th hash.
/// @param leaves Should be pre-hashed and the first 32 bytes of a leaf's
/// preimage should match LEAF_DOMAIN_SEPARATOR.
/// @param proofs The hashes to be used instead of a leaf hash when the proofFlagBits
/// indicates a proof should be used.
/// @param proofFlagBits A single uint256 of which each bit indicates whether a leaf or
/// a proof needs to be used in a hash operation.
/// @dev the maximum number of hash operations it set to 256. Any input that would require
/// more than 256 hashes to get to a root will revert.
/// @dev For given input `leaves` = [a,b,c] `proofs` = [D] and `proofFlagBits` = 5
/// totalHashes = 3 + 1 - 1 = 3
/// ** round 1 **
/// proofFlagBits = (5 >> 0) & 1 = true
/// hashes[0] = hashPair(a, b)
/// (leafPos, hashPos, proofPos) = (2, 0, 0);
///
/// ** round 2 **
/// proofFlagBits = (5 >> 1) & 1 = false
/// hashes[1] = hashPair(D, c)
/// (leafPos, hashPos, proofPos) = (3, 0, 1);
///
/// ** round 3 **
/// proofFlagBits = (5 >> 2) & 1 = true
/// hashes[2] = hashPair(hashes[0], hashes[1])
/// (leafPos, hashPos, proofPos) = (3, 2, 1);
///
/// i = 3 and no longer < totalHashes. The algorithm is done
/// return hashes[totalHashes - 1] = hashes[2]; the last hash we computed.
// We mark this function as internal to force it to be inlined in contracts
// that use it, but semantically it is public.
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
function merkleRoot(
bytes32[] memory leaves,
bytes32[] memory proofs,
uint256 proofFlagBits
) internal pure returns (bytes32) {
unchecked {
uint256 leavesLen = leaves.length;
uint256 proofsLen = proofs.length;
if (leavesLen == 0) revert LeavesCannotBeEmpty();
if (!(leavesLen <= MAX_NUM_HASHES + 1 && proofsLen <= MAX_NUM_HASHES + 1)) revert InvalidProof();
uint256 totalHashes = leavesLen + proofsLen - 1;
if (!(totalHashes <= MAX_NUM_HASHES)) revert InvalidProof();
if (totalHashes == 0) {
return leaves[0];
}
bytes32[] memory hashes = new bytes32[](totalHashes);
(uint256 leafPos, uint256 hashPos, uint256 proofPos) = (0, 0, 0);
for (uint256 i = 0; i < totalHashes; ++i) {
// Checks if the bit flag signals the use of a supplied proof or a leaf/previous hash.
bytes32 a;
if (proofFlagBits & (1 << i) == (1 << i)) {
// Use a leaf or a previously computed hash.
if (leafPos < leavesLen) {
a = leaves[leafPos++];
} else {
a = hashes[hashPos++];
}
} else {
// Use a supplied proof.
a = proofs[proofPos++];
}
// The second part of the hashed pair is never a proof as hashing two proofs would result in a
// hash that can already be computed offchain.
bytes32 b;
if (leafPos < leavesLen) {
b = leaves[leafPos++];
} else {
b = hashes[hashPos++];
}
if (!(hashPos <= i)) revert InvalidProof();
hashes[i] = _hashPair(a, b);
}
if (!(hashPos == totalHashes - 1 && leafPos == leavesLen && proofPos == proofsLen)) revert InvalidProof();
// Return the last hash.
return hashes[totalHashes - 1];
}
}
/// @notice Hashes two bytes32 objects in their given order, prepended by the
/// INTERNAL_DOMAIN_SEPARATOR.
function _hashInternalNode(bytes32 left, bytes32 right) private pure returns (bytes32 hash) {
return keccak256(abi.encode(INTERNAL_DOMAIN_SEPARATOR, left, right));
}
/// @notice Hashes two bytes32 objects. The order is taken into account,
/// using the lower value first.
function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
return a < b ? _hashInternalNode(a, b) : _hashInternalNode(b, a);
}
}