Skip to content

Commit

Permalink
feat: cross chain support
Browse files Browse the repository at this point in the history
  • Loading branch information
Delioos committed Oct 1, 2024
1 parent 2ec10e9 commit 5c84c71
Show file tree
Hide file tree
Showing 2 changed files with 216 additions and 0 deletions.
180 changes: 180 additions & 0 deletions src/CrossChainBurnAndMintERC1155.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {ERC1155Core, ERC1155} from "./ERC1155Core.sol";
import {Client} from "@chainlink/contracts/src/v0.8/ccip/libraries/Client.sol";
import {IRouterClient} from "@chainlink/contracts/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {IAny2EVMMessageReceiver} from "@chainlink/contracts/src/v0.8/ccip/interfaces/IAny2EVMMessageReceiver.sol"; // note à moi meme : contract kit hold tout tout les contrats donc je peux nettoyer la lib local et eviter les multi import auto degeulasse de foundry
import {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/shared/interfaces/LinkTokenInterface.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {Withdraw} from "./utils/Withdraw.sol";

/**
* THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/
contract CrossChainBurnAndMintERC1155 is ERC1155Core, IAny2EVMMessageReceiver, ReentrancyGuard, Withdraw {
enum PayFeesIn {
Native,
LINK
}

error InvalidRouter(address router);
error NotEnoughBalanceForFees(uint256 currentBalance, uint256 calculatedFees);
error ChainNotEnabled(uint64 chainSelector);
error SenderNotEnabled(address sender);
error OperationNotAllowedOnCurrentChain(uint64 chainSelector);

struct XNftDetails {
address xNftAddress;
bytes ccipExtraArgsBytes;
}

IRouterClient internal immutable i_ccipRouter;
LinkTokenInterface internal immutable i_linkToken;
uint64 private immutable i_currentChainSelector;

mapping(uint64 destChainSelector => XNftDetails xNftDetailsPerChain) public s_chains;

event ChainEnabled(uint64 chainSelector, address xNftAddress, bytes ccipExtraArgs);
event ChainDisabled(uint64 chainSelector);
event CrossChainSent(
address from,
address to,
uint256 id,
uint256 amount,
bytes data,
uint64 sourceChainSelector,
uint64 destinationChainSelector
);
event CrossChainReceived(
address from,
address to,
uint256 id,
uint256 amount,
bytes data,
uint64 sourceChainSelector,
uint64 destinationChainSelector
);

modifier onlyRouter() {
if (msg.sender != address(i_ccipRouter)) {
revert InvalidRouter(msg.sender);
}
_;
}

modifier onlyEnabledChain(uint64 _chainSelector) {
if (s_chains[_chainSelector].xNftAddress == address(0)) {
revert ChainNotEnabled(_chainSelector);
}
_;
}

modifier onlyEnabledSender(uint64 _chainSelector, address _sender) {
if (s_chains[_chainSelector].xNftAddress != _sender) {
revert SenderNotEnabled(_sender);
}
_;
}

modifier onlyOtherChains(uint64 _chainSelector) {
if (_chainSelector == i_currentChainSelector) {
revert OperationNotAllowedOnCurrentChain(_chainSelector);
}
_;
}

constructor(string memory uri_, address ccipRouterAddress, address linkTokenAddress, uint64 currentChainSelector)
ERC1155Core(uri_)
{
i_ccipRouter = IRouterClient(ccipRouterAddress);
i_linkToken = LinkTokenInterface(linkTokenAddress);
i_currentChainSelector = currentChainSelector;
}

function enableChain(uint64 chainSelector, address xNftAddress, bytes memory ccipExtraArgs)
external
onlyOwner
onlyOtherChains(chainSelector)
{
s_chains[chainSelector] = XNftDetails({xNftAddress: xNftAddress, ccipExtraArgsBytes: ccipExtraArgs});

emit ChainEnabled(chainSelector, xNftAddress, ccipExtraArgs);
}

function disableChain(uint64 chainSelector) external onlyOwner onlyOtherChains(chainSelector) {
delete s_chains[chainSelector];

emit ChainDisabled(chainSelector);
}

function crossChainTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data,
uint64 destinationChainSelector,
PayFeesIn payFeesIn
) external nonReentrant onlyEnabledChain(destinationChainSelector) returns (bytes32 messageId) {
string memory tokenUri = uri(id);
burn(from, id, amount);

Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
receiver: abi.encode(s_chains[destinationChainSelector].xNftAddress),
data: abi.encode(from, to, id, amount, data, tokenUri),
tokenAmounts: new Client.EVMTokenAmount[](0),
extraArgs: s_chains[destinationChainSelector].ccipExtraArgsBytes,
feeToken: payFeesIn == PayFeesIn.LINK ? address(i_linkToken) : address(0)
});

// Get the fee required to send the CCIP message
uint256 fees = i_ccipRouter.getFee(destinationChainSelector, message);

if (payFeesIn == PayFeesIn.LINK) {
if (fees > i_linkToken.balanceOf(address(this))) {
revert NotEnoughBalanceForFees(i_linkToken.balanceOf(address(this)), fees);
}

// Approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK
i_linkToken.approve(address(i_ccipRouter), fees);

// Send the message through the router and store the returned message ID
messageId = i_ccipRouter.ccipSend(destinationChainSelector, message);
} else {
if (fees > address(this).balance) {
revert NotEnoughBalanceForFees(address(this).balance, fees);
}

// Send the message through the router and store the returned message ID
messageId = i_ccipRouter.ccipSend{value: fees}(destinationChainSelector, message);
}

emit CrossChainSent(from, to, id, amount, data, i_currentChainSelector, destinationChainSelector);
}

function supportsInterface(bytes4 interfaceId) public view override(ERC1155) returns (bool) {
return interfaceId == type(IAny2EVMMessageReceiver).interfaceId || super.supportsInterface(interfaceId);
}

/// @inheritdoc IAny2EVMMessageReceiver
function ccipReceive(Client.Any2EVMMessage calldata message)
external
virtual
override
onlyRouter
nonReentrant
onlyEnabledChain(message.sourceChainSelector)
onlyEnabledSender(message.sourceChainSelector, abi.decode(message.sender, (address)))
{
uint64 sourceChainSelector = message.sourceChainSelector;
(address from, address to, uint256 id, uint256 amount, bytes memory data, string memory tokenUri) =
abi.decode(message.data, (address, address, uint256, uint256, bytes, string));

mint(to, id, amount, data, tokenUri);

emit CrossChainReceived(from, to, id, amount, data, sourceChainSelector, i_currentChainSelector);
}
}
36 changes: 36 additions & 0 deletions src/utils/Withdraw.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {OwnerIsCreator} from "@chainlink/contracts/src/v0.8/shared/access/OwnerIsCreator.sol";

/**
* THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/
contract Withdraw is OwnerIsCreator {
using SafeERC20 for IERC20;

error NothingToWithdraw();
error FailedToWithdrawEth(address owner, address target, uint256 value);

function withdraw(address _beneficiary) public onlyOwner {
uint256 amount = address(this).balance;

if (amount == 0) revert NothingToWithdraw();

(bool sent,) = _beneficiary.call{value: amount}("");

if (!sent) revert FailedToWithdrawEth(msg.sender, _beneficiary, amount);
}

function withdrawToken(address _beneficiary, address _token) public onlyOwner {
uint256 amount = IERC20(_token).balanceOf(address(this));

if (amount == 0) revert NothingToWithdraw();

IERC20(_token).safeTransfer(_beneficiary, amount);
}
}

0 comments on commit 5c84c71

Please sign in to comment.