From b5f8c367bdfcc11fe17216307d5da47cd9d516da Mon Sep 17 00:00:00 2001 From: Stanley Date: Mon, 16 Sep 2024 15:16:40 -0400 Subject: [PATCH 1/5] initial implementation of chainlink crosschain module --- src/module/token/crosschain/chainlink.sol | 158 ++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 src/module/token/crosschain/chainlink.sol diff --git a/src/module/token/crosschain/chainlink.sol b/src/module/token/crosschain/chainlink.sol new file mode 100644 index 00000000..0196577e --- /dev/null +++ b/src/module/token/crosschain/chainlink.sol @@ -0,0 +1,158 @@ + +library ChainlinkCrossChainStorage { + + /// @custom:storage-location erc7201:token.minting.chainlinkcrosschain + bytes32 public constant CHAINLINKCROSSCHAIN_STORAGE_POSITION = + keccak256(abi.encode(uint256(keccak256("token.minting.chainlinkcrosschain.erc721")) - 1)) & ~bytes32(uint256(0xff)); + + struct Data { + address router; + address s_linkToken; + } + + function data() internal pure returns (Data storage data_) { + bytes32 position = CHAINLINKCROSSCHAIN_STORAGE_POSITION; + assembly { + data_.slot := position + } + } + +} + + +contract ChainlinkCrossChain is CCIPReceiver, OwnerIsCreator { + + address immutable s_linkToken; + address immutable router; + + constructor(address _router, address _link) { + s_linkToken = _link; + router = _router; + } + + function bridgeWithToken( + address _destinationChain, + address _recipient, + bytes memory _data, + address _token, + uint256 _amount, + bytes memory _extraArgs, + ) external { + (uint256 _feeTokenAddress, ccipMessageExtraArgs) = abi.decode(_extraArgs, (uint256, bytes)); + + Client.EVM2AnyMessage memory evm2AnyMessage = + _buildCCIPMessage(_receiver, _text, _token, _amount, _feeTokenAddress, ccipMessageExtraArgs); + + // Initialize a router client instance to interact with cross-chain router + IRouterClient router = IRouterClient(_chainLinkCrossChainStorage().router); + + // Get the fee required to send the CCIP message + uint256 fees = router.getFee(_destinationChainSelector, evm2AnyMessage); + + if (fees > s_linkToken.balanceOf(address(this))) { + revert NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees); + } + + // approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK + s_linkToken.approve(address(router), fees); + + // approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token + IERC20(_token).approve(address(router), _amount); + + // Send the message through the router and store the returned message ID + messageId = router.ccipSend(_destinationChainSelector, evm2AnyMessage); + + // Emit an event with message details + emit MessageSent( + messageId, _destinationChainSelector, _receiver, _text, _token, _amount, address(s_linkToken), fees + ); + + // Return the message ID + return messageId; + } + + /// @notice Sends data and transfer tokens to receiver on the destination chain. + /// @notice Pay for fees in native gas. + /// @dev Assumes your contract has sufficient native gas like ETH on Ethereum or POL on Polygon. + /// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain. + /// @param _receiver The address of the recipient on the destination blockchain. + /// @param _text The string data to be sent. + /// @param _token token address. + /// @param _amount token amount. + /// @return messageId The ID of the CCIP message that was sent. + function sendMessagePayNative( + uint64 _destinationChainSelector, + address _receiver, + string calldata _text, + address _token, + uint256 _amount + ) + external + onlyOwner + onlyAllowlistedDestinationChain(_destinationChainSelector) + validateReceiver(_receiver) + returns (bytes32 messageId) + { + // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message + // address(0) means fees are paid in native gas + Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(_receiver, _text, _token, _amount, address(0)); + + // Initialize a router client instance to interact with cross-chain router + IRouterClient router = IRouterClient(this.getRouter()); + + // Get the fee required to send the CCIP message + uint256 fees = router.getFee(_destinationChainSelector, evm2AnyMessage); + + if (fees > address(this).balance) { + revert NotEnoughBalance(address(this).balance, fees); + } + + // approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token + IERC20(_token).approve(address(router), _amount); + + // Send the message through the router and store the returned message ID + messageId = router.ccipSend{value: fees}(_destinationChainSelector, evm2AnyMessage); + + // Emit an event with message details + emit MessageSent(messageId, _destinationChainSelector, _receiver, _text, _token, _amount, address(0), fees); + + // Return the message ID + return messageId; + } + + + /// @notice Construct a CCIP message. + /// @dev This function will create an EVM2AnyMessage struct with all the necessary information for programmable tokens transfer. + /// @param _receiver The address of the receiver. + /// @param _text The string data to be sent. + /// @param _token The token to be transferred. + /// @param _amount The amount of the token to be transferred. + /// @param _feeTokenAddress The address of the token used for fees. Set address(0) for native gas. + /// @return Client.EVM2AnyMessage Returns an EVM2AnyMessage struct which contains information for sending a CCIP message. + function _buildCCIPMessage( + address _recipient + bytes calldata _data, + address _token, + uint256 _amount, + address _feeTokenAddress, + bytes calldata _extraArgs + ) private pure returns (Client.EVM2AnyMessage memory) { + // Set the token amounts + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); + tokenAmounts[0] = Client.EVMTokenAmount({token: _token, amount: _amount}); + // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message + return Client.EVM2AnyMessage({ + receiver: abi.encode(_recipient), // ABI-encoded receiver address + data: _data, + tokenAmounts: tokenAmounts, // The amount and type of token being transferred + extraArgs: _extraArgs, + // Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees + feeToken: _feeTokenAddress + }); + } + + function _chainlinkCrossChainStorage() internal pure returns (ChainlinkCrossChainStorage.Data storage) { + return ChainlinkCrossChainStorage.data(); + } + +} From 52e0d2205df133cb6bc8ec8d4c5e4933003a3041 Mon Sep 17 00:00:00 2001 From: Stanley Date: Mon, 16 Sep 2024 15:24:25 -0400 Subject: [PATCH 2/5] forge install: chainlink.git develop --- .gitmodules | 3 +++ lib/chainlink.git | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/chainlink.git diff --git a/.gitmodules b/.gitmodules index 86d572d1..77f395f7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "lib/PermitC"] path = lib/PermitC url = https://github.com/limitbreakinc/PermitC +[submodule "lib/chainlink.git"] + path = lib/chainlink.git + url = https://github.com/smartcontractkit/chainlink.git diff --git a/lib/chainlink.git b/lib/chainlink.git new file mode 160000 index 00000000..0187f18b --- /dev/null +++ b/lib/chainlink.git @@ -0,0 +1 @@ +Subproject commit 0187f18ba62b44d4c8ff20f07ef8dfd6e0d7b451 From 84c3d252a08893577079636df4f640d461b401fd Mon Sep 17 00:00:00 2001 From: Stanley Date: Mon, 16 Sep 2024 16:06:47 -0400 Subject: [PATCH 3/5] it builds --- remappings.txt | 1 + src/module/token/crosschain/chainlink.sol | 227 +++++++++++++--------- 2 files changed, 134 insertions(+), 94 deletions(-) diff --git a/remappings.txt b/remappings.txt index 413cecc2..fc11654b 100644 --- a/remappings.txt +++ b/remappings.txt @@ -5,3 +5,4 @@ forge-std/=lib/forge-std/src/ @erc721a-upgradeable/=lib/ERC721A-Upgradeable/contracts/ @limitbreak/creator-token-standards/=lib/creator-token-standards/src/ @limitbreak/permit-c/=lib/PermitC/src/ +@chainlink/=lib/chainlink.git/contracts/src/v0.8/ diff --git a/src/module/token/crosschain/chainlink.sol b/src/module/token/crosschain/chainlink.sol index 0196577e..f9780157 100644 --- a/src/module/token/crosschain/chainlink.sol +++ b/src/module/token/crosschain/chainlink.sol @@ -1,13 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +import {Module} from "../../../Module.sol"; + +import {Role} from "../../../Role.sol"; + +import {IERC20} from "../../../interface/IERC20.sol"; +import {IInstallationCallback} from "../../../interface/IInstallationCallback.sol"; +import {OwnableRoles} from "@solady/auth/OwnableRoles.sol"; + +import {IRouterClient} from "@chainlink/ccip/interfaces/IRouterClient.sol"; +import {Client} from "@chainlink/ccip/libraries/Client.sol"; library ChainlinkCrossChainStorage { /// @custom:storage-location erc7201:token.minting.chainlinkcrosschain - bytes32 public constant CHAINLINKCROSSCHAIN_STORAGE_POSITION = - keccak256(abi.encode(uint256(keccak256("token.minting.chainlinkcrosschain.erc721")) - 1)) & ~bytes32(uint256(0xff)); + bytes32 public constant CHAINLINKCROSSCHAIN_STORAGE_POSITION = keccak256( + abi.encode(uint256(keccak256("token.minting.chainlinkcrosschain.erc721")) - 1) + ) & ~bytes32(uint256(0xff)); struct Data { address router; - address s_linkToken; + address linkToken; } function data() internal pure returns (Data storage data_) { @@ -19,134 +33,159 @@ library ChainlinkCrossChainStorage { } +contract ChainlinkCrossChain is Module { + + error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); + + /*////////////////////////////////////////////////////////////// + MODULE CONFIG + //////////////////////////////////////////////////////////////*/ -contract ChainlinkCrossChain is CCIPReceiver, OwnerIsCreator { + /// @notice Returns all implemented callback and fallback functions. + function getModuleConfig() external pure override returns (ModuleConfig memory config) { + config.fallbackFunctions = new FallbackFunction[](5); - address immutable s_linkToken; - address immutable router; + config.fallbackFunctions[0] = FallbackFunction({selector: this.getRouter.selector, permissionBits: 0}); + config.fallbackFunctions[1] = FallbackFunction({selector: this.getLinkToken.selector, permissionBits: 0}); + config.fallbackFunctions[2] = + FallbackFunction({selector: this.setRouter.selector, permissionBits: Role._MANAGER_ROLE}); + config.fallbackFunctions[3] = + FallbackFunction({selector: this.setLinkToken.selector, permissionBits: Role._MANAGER_ROLE}); + config.fallbackFunctions[4] = + FallbackFunction({selector: this.sendCrossChainTransaction.selector, permissionBits: 0}); - constructor(address _router, address _link) { - s_linkToken = _link; - router = _router; + config.registerInstallationCallback = true; } - function bridgeWithToken( - address _destinationChain, - address _recipient, - bytes memory _data, - address _token, - uint256 _amount, - bytes memory _extraArgs, - ) external { - (uint256 _feeTokenAddress, ccipMessageExtraArgs) = abi.decode(_extraArgs, (uint256, bytes)); + /*////////////////////////////////////////////////////////////// + INSTALL / UNINSTALL FUNCTIONS + //////////////////////////////////////////////////////////////*/ - Client.EVM2AnyMessage memory evm2AnyMessage = - _buildCCIPMessage(_receiver, _text, _token, _amount, _feeTokenAddress, ccipMessageExtraArgs); + /// @dev Called by a Core into an Module during the installation of the Module. + function onInstall(bytes calldata data) external { + (address router, address linkToken) = abi.decode(data, (address, address)); + _chainlinkCrossChainStorage().router = router; + _chainlinkCrossChainStorage().linkToken = linkToken; + } - // Initialize a router client instance to interact with cross-chain router - IRouterClient router = IRouterClient(_chainLinkCrossChainStorage().router); + /// @dev Called by a Core into an Module during the uninstallation of the Module. + function onUninstall(bytes calldata data) external {} - // Get the fee required to send the CCIP message - uint256 fees = router.getFee(_destinationChainSelector, evm2AnyMessage); + /// @dev Returns bytes encoded install params, to be sent to `onInstall` function + function encodeBytesOnInstall(address router, address linkToken) external pure returns (bytes memory) { + return abi.encode(router, linkToken); + } - if (fees > s_linkToken.balanceOf(address(this))) { - revert NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees); - } + /// @dev Returns bytes encoded uninstall params, to be sent to `onUninstall` function + function encodeBytesOnUninstall() external pure returns (bytes memory) { + return ""; + } - // approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK - s_linkToken.approve(address(router), fees); + /*////////////////////////////////////////////////////////////// + FALLBACK FUNCTIONS + //////////////////////////////////////////////////////////////*/ - // approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token - IERC20(_token).approve(address(router), _amount); + function getRouter() external view returns (address) { + return _chainlinkCrossChainStorage().router; + } + + function getLinkToken() external view returns (address) { + return _chainlinkCrossChainStorage().linkToken; + } - // Send the message through the router and store the returned message ID - messageId = router.ccipSend(_destinationChainSelector, evm2AnyMessage); + function setRouter(address router) external { + _chainlinkCrossChainStorage().router = router; + } - // Emit an event with message details - emit MessageSent( - messageId, _destinationChainSelector, _receiver, _text, _token, _amount, address(s_linkToken), fees - ); + function setLinkToken(address linkToken) external { + _chainlinkCrossChainStorage().linkToken = linkToken; + } - // Return the message ID - return messageId; + function sendCrossChainTransaction( + uint64 _destinationChain, + address _recipient, + bytes calldata _data, + address _token, + uint256 _amount, + address _callAddress, + bytes memory _extraArgs + ) external { + (address _feeTokenAddress, bytes memory ccipMessageExtraArgs) = abi.decode(_extraArgs, (address, bytes)); + + if (_feeTokenAddress == address(0)) { + _sendMessagePayNative(_destinationChain, _recipient, _data, _token, _amount, ccipMessageExtraArgs); + } else { + _sendMessagePayToken( + _destinationChain, _recipient, _data, _token, _amount, _feeTokenAddress, ccipMessageExtraArgs + ); + } } - /// @notice Sends data and transfer tokens to receiver on the destination chain. - /// @notice Pay for fees in native gas. - /// @dev Assumes your contract has sufficient native gas like ETH on Ethereum or POL on Polygon. - /// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain. - /// @param _receiver The address of the recipient on the destination blockchain. - /// @param _text The string data to be sent. - /// @param _token token address. - /// @param _amount token amount. - /// @return messageId The ID of the CCIP message that was sent. - function sendMessagePayNative( - uint64 _destinationChainSelector, - address _receiver, - string calldata _text, + /*////////////////////////////////////////////////////////////// + INTERNAL FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + function _sendMessagePayToken( + uint64 _destinationChain, + address _recipient, + bytes calldata _data, address _token, - uint256 _amount - ) - external - onlyOwner - onlyAllowlistedDestinationChain(_destinationChainSelector) - validateReceiver(_receiver) - returns (bytes32 messageId) - { - // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message - // address(0) means fees are paid in native gas - Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(_receiver, _text, _token, _amount, address(0)); - - // Initialize a router client instance to interact with cross-chain router - IRouterClient router = IRouterClient(this.getRouter()); - - // Get the fee required to send the CCIP message - uint256 fees = router.getFee(_destinationChainSelector, evm2AnyMessage); + uint256 _amount, + address _feeTokenAddress, + bytes memory _extraArgs + ) internal { + Client.EVM2AnyMessage memory evm2AnyMessage = + _buildCCIPMessage(_recipient, _data, _token, _amount, _feeTokenAddress, _extraArgs); + IRouterClient router = IRouterClient(_chainlinkCrossChainStorage().router); + uint256 fees = router.getFee(_destinationChain, evm2AnyMessage); + IERC20 linkToken = IERC20(_chainlinkCrossChainStorage().linkToken); - if (fees > address(this).balance) { - revert NotEnoughBalance(address(this).balance, fees); + if (fees > linkToken.balanceOf(address(this))) { + revert NotEnoughBalance(linkToken.balanceOf(address(this)), fees); } - // approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token + IERC20(linkToken).approve(address(router), fees); IERC20(_token).approve(address(router), _amount); + router.ccipSend(_destinationChain, evm2AnyMessage); + } - // Send the message through the router and store the returned message ID - messageId = router.ccipSend{value: fees}(_destinationChainSelector, evm2AnyMessage); + function _sendMessagePayNative( + uint64 _destinationChain, + address _recipient, + bytes calldata _data, + address _token, + uint256 _amount, + bytes memory _extraArgs + ) internal { + Client.EVM2AnyMessage memory evm2AnyMessage = + _buildCCIPMessage(_recipient, _data, _token, _amount, address(0), _extraArgs); + IRouterClient router = IRouterClient(_chainlinkCrossChainStorage().router); + uint256 fees = router.getFee(_destinationChain, evm2AnyMessage); - // Emit an event with message details - emit MessageSent(messageId, _destinationChainSelector, _receiver, _text, _token, _amount, address(0), fees); + if (fees > address(this).balance) { + revert NotEnoughBalance(address(this).balance, fees); + } - // Return the message ID - return messageId; + IERC20(_token).approve(address(router), _amount); + router.ccipSend{value: fees}(_destinationChain, evm2AnyMessage); } - - /// @notice Construct a CCIP message. - /// @dev This function will create an EVM2AnyMessage struct with all the necessary information for programmable tokens transfer. - /// @param _receiver The address of the receiver. - /// @param _text The string data to be sent. - /// @param _token The token to be transferred. - /// @param _amount The amount of the token to be transferred. - /// @param _feeTokenAddress The address of the token used for fees. Set address(0) for native gas. - /// @return Client.EVM2AnyMessage Returns an EVM2AnyMessage struct which contains information for sending a CCIP message. function _buildCCIPMessage( - address _recipient + address _recipient, bytes calldata _data, address _token, uint256 _amount, address _feeTokenAddress, - bytes calldata _extraArgs + bytes memory _extraArgs ) private pure returns (Client.EVM2AnyMessage memory) { - // Set the token amounts Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); tokenAmounts[0] = Client.EVMTokenAmount({token: _token, amount: _amount}); - // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message + return Client.EVM2AnyMessage({ - receiver: abi.encode(_recipient), // ABI-encoded receiver address + receiver: abi.encode(_recipient), data: _data, - tokenAmounts: tokenAmounts, // The amount and type of token being transferred + tokenAmounts: tokenAmounts, extraArgs: _extraArgs, - // Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees feeToken: _feeTokenAddress }); } From 346468ab7eac1829b241e8d4871f9015b6ad399e Mon Sep 17 00:00:00 2001 From: Stanley Date: Mon, 16 Sep 2024 16:59:22 -0400 Subject: [PATCH 4/5] reordered parameters in function --- src/module/token/crosschain/chainlink.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/module/token/crosschain/chainlink.sol b/src/module/token/crosschain/chainlink.sol index f9780157..cf190f5b 100644 --- a/src/module/token/crosschain/chainlink.sol +++ b/src/module/token/crosschain/chainlink.sol @@ -103,12 +103,12 @@ contract ChainlinkCrossChain is Module { function sendCrossChainTransaction( uint64 _destinationChain, + address _callAddress, address _recipient, - bytes calldata _data, address _token, uint256 _amount, - address _callAddress, - bytes memory _extraArgs + bytes calldata _data, + bytes calldata _extraArgs ) external { (address _feeTokenAddress, bytes memory ccipMessageExtraArgs) = abi.decode(_extraArgs, (address, bytes)); From dd23301d5e23c4504070ffe1400b9b6ba5471dfc Mon Sep 17 00:00:00 2001 From: Stanley Date: Wed, 18 Sep 2024 16:43:03 -0400 Subject: [PATCH 5/5] Updated CCIP chainlink to use the unified interface --- src/interface/ICrosschain.sol | 52 ----------------------- src/module/token/crosschain/chainlink.sol | 49 +++++++++++++++++---- 2 files changed, 41 insertions(+), 60 deletions(-) delete mode 100644 src/interface/ICrosschain.sol diff --git a/src/interface/ICrosschain.sol b/src/interface/ICrosschain.sol deleted file mode 100644 index b8de6212..00000000 --- a/src/interface/ICrosschain.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.20; - -interface ICrosschain { - - /** - * @notice Sends a cross-chain transaction. - * @param _destinationChain The destination chain ID. - * @param _callAddress The address of the contract on the destination chain. - * @param _payload The payload to send to the destination chain. - * @param _extraArgs The extra arguments to pass - * @dev extraArgs may contain items such as token, amount, feeTokenAddress, receipient, gasLimit, etc - */ - function sendCrossChainTransaction( - uint64 _destinationChain, - address _callAddress, - bytes calldata _payload, - bytes calldata _extraArgs - ) external payable; - - /** - * @notice callback function for when a cross-chain transaction is sent. - * @param _destinationChain The destination chain ID. - * @param _callAddress The address of the contract on the destination chain. - * @param _payload The payload sent to the destination chain. - * @param _extraArgs The extra arguments sent to the callAddress on the destination chain. - */ - function onCrossChainTransactionSent( - uint64 _destinationChain, - address _callAddress, - bytes calldata _payload, - bytes calldata _extraArgs - ) internal; - - /** - * @notice callback function for when a cross-chain transaction is received. - * @param _sourceChain The source chain ID. - * @param _sourceAddress The address of the contract on the source chain. - * @param _payload The payload sent to the destination chain. - * @param _extraArgs The extra arguments sent to the callAddress on the destination chain. - */ - function onCrossChainTransactionReceived( - uint64 _sourceChain, - address _sourceAddress, - bytes calldata _payload, - bytes calldata _extraArgs - ) internal; - - function setRouter(address _router) external; - function getRouter() external view returns (address); - -} diff --git a/src/module/token/crosschain/chainlink.sol b/src/module/token/crosschain/chainlink.sol index cf190f5b..c1f7fee5 100644 --- a/src/module/token/crosschain/chainlink.sol +++ b/src/module/token/crosschain/chainlink.sol @@ -7,8 +7,10 @@ import {Role} from "../../../Role.sol"; import {IERC20} from "../../../interface/IERC20.sol"; import {IInstallationCallback} from "../../../interface/IInstallationCallback.sol"; +import {CrossChain} from "./CrossChain.sol"; import {OwnableRoles} from "@solady/auth/OwnableRoles.sol"; +import {CCIPReceiver} from "@chainlink/ccip/applications/CCIPReceiver.sol"; import {IRouterClient} from "@chainlink/ccip/interfaces/IRouterClient.sol"; import {Client} from "@chainlink/ccip/libraries/Client.sol"; @@ -33,10 +35,12 @@ library ChainlinkCrossChainStorage { } -contract ChainlinkCrossChain is Module { +contract ChainlinkCrossChain is Module, CrossChain, CCIPReceiver { error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); + constructor(address _router, address _link) CCIPReceiver(_router) {} + /*////////////////////////////////////////////////////////////// MODULE CONFIG //////////////////////////////////////////////////////////////*/ @@ -85,7 +89,7 @@ contract ChainlinkCrossChain is Module { FALLBACK FUNCTIONS //////////////////////////////////////////////////////////////*/ - function getRouter() external view returns (address) { + function getRouter() public view override(CrossChain, CCIPReceiver) returns (address) { return _chainlinkCrossChainStorage().router; } @@ -93,7 +97,7 @@ contract ChainlinkCrossChain is Module { return _chainlinkCrossChainStorage().linkToken; } - function setRouter(address router) external { + function setRouter(address router) external override { _chainlinkCrossChainStorage().router = router; } @@ -104,13 +108,16 @@ contract ChainlinkCrossChain is Module { function sendCrossChainTransaction( uint64 _destinationChain, address _callAddress, - address _recipient, - address _token, - uint256 _amount, bytes calldata _data, bytes calldata _extraArgs - ) external { - (address _feeTokenAddress, bytes memory ccipMessageExtraArgs) = abi.decode(_extraArgs, (address, bytes)); + ) external payable override { + ( + address _recipient, + address _token, + uint256 _amount, + address _feeTokenAddress, + bytes memory ccipMessageExtraArgs + ) = abi.decode(_extraArgs, (address, address, uint256, address, bytes)); if (_feeTokenAddress == address(0)) { _sendMessagePayNative(_destinationChain, _recipient, _data, _token, _amount, ccipMessageExtraArgs); @@ -119,12 +126,32 @@ contract ChainlinkCrossChain is Module { _destinationChain, _recipient, _data, _token, _amount, _feeTokenAddress, ccipMessageExtraArgs ); } + + onCrossChainTransactionSent(_destinationChain, _callAddress, _data, _extraArgs); } /*////////////////////////////////////////////////////////////// INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////*/ + function onCrossChainTransactionSent( + uint64 _destinationChain, + address _callAddress, + bytes calldata _payload, + bytes calldata _extraArgs + ) internal override { + /// post cross chain transaction sent logic goes here + } + + function onCrossChainTransactionReceived( + uint64 _sourceChain, + address _sourceAddress, + bytes memory _payload, + bytes memory _extraArgs + ) internal override { + /// post cross chain transaction received logic goes here + } + function _sendMessagePayToken( uint64 _destinationChain, address _recipient, @@ -190,6 +217,12 @@ contract ChainlinkCrossChain is Module { }); } + function _ccipReceive(Client.Any2EVMMessage memory message) internal override { + address sender = abi.decode(message.sender, (address)); + bytes memory payload = ""; + onCrossChainTransactionReceived(message.sourceChainSelector, sender, message.data, payload); + } + function _chainlinkCrossChainStorage() internal pure returns (ChainlinkCrossChainStorage.Data storage) { return ChainlinkCrossChainStorage.data(); }