Skip to content

Commit

Permalink
it builds
Browse files Browse the repository at this point in the history
  • Loading branch information
GWSzeto committed Sep 18, 2024
1 parent 756a7b0 commit 09603f1
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 94 deletions.
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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/
227 changes: 133 additions & 94 deletions src/module/token/crosschain/chainlink.sol
Original file line number Diff line number Diff line change
@@ -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_) {
Expand All @@ -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
});
}
Expand Down

0 comments on commit 09603f1

Please sign in to comment.