The encoded function call corresponding to bringUsdcFromAnotherChain
when called using publishMessageToBringUsdcFromAnotherChain
is getting reverted with "Ready for manual execution" error on CCIP explorer.
However, the bringUsdcFromAnotherChain
function works fine when called directly (i.e., to send USDC to another chain). But, I want an implementation to bring USDC from another chain.
Additionally, the message is published and received correctly when calling publishMessageToAnotherChain
function.
Here's the contract code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol";
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/shared/interfaces/LinkTokenInterface.sol";
contract TestContract is CCIPReceiver {
error TestContract__NetworkNotSupported();
error TestContract__NotFundedWithEnoughLINK();
error TestContract__InsufficientUSDCBalance();
event MessageSent(bytes32 messageId);
event MessageReceived(bytes data);
address public s_owner;
string public s_chain;
string public s_decodedMessageData;
bytes public s_messageData;
uint256 public s_messageDataLength;
IERC20 immutable i_usdcToken;
LinkTokenInterface immutable i_linkToken;
address immutable i_router;
constructor(address _router) CCIPReceiver(_router) {
s_owner = msg.sender;
if (block.chainid == 11155111) {
i_usdcToken = IERC20(0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238);
i_linkToken = LinkTokenInterface(
0x779877A7B0D9E8603169DdbD7836e478b4624789
);
i_router = _router;
s_chain = "Eth-Sepolia";
} else if (block.chainid == 43113) {
i_usdcToken = IERC20(0x5425890298aed601595a70AB815c96711a31Bc65);
i_linkToken = LinkTokenInterface(
0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846
);
i_router = _router;
s_chain = "Avalanche-Fuji";
} else {
revert TestContract__NetworkNotSupported();
}
}
function _ccipReceive(Client.Any2EVMMessage memory message)
internal
override
{
s_messageData = message.data;
s_messageDataLength = message.data.length;
if (message.data.length > 0) {
(bool success, ) = address(this).call(message.data);
require(success);
emit MessageReceived(message.data);
}
}
struct ChainDetailsHavingUSDC {
address contractAddress;
uint64 chainSelector;
}
function publishMessageToBringUsdcFromAnotherChain(
uint256 amount,
uint64 chainSelectorOfCurrentChain, // where to bring USDC back
ChainDetailsHavingUSDC memory chainDetailsHavingUSDC
) external {
bytes4 selector = this.bringUsdcFromAnotherChain.selector;
bytes memory data = abi.encodeWithSelector(
selector,
address(this),
chainSelectorOfCurrentChain,
amount
);
Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
receiver: abi.encode(chainDetailsHavingUSDC.contractAddress),
data: data,
tokenAmounts: new Client.EVMTokenAmount[](0),
extraArgs: "",
feeToken: address(i_linkToken)
});
uint256 fee = IRouterClient(i_router).getFee(
chainDetailsHavingUSDC.chainSelector,
message
);
if (i_linkToken.balanceOf(address(this)) < fee) {
revert TestContract__NotFundedWithEnoughLINK();
}
bytes32 messageId;
uint256 allowance = i_linkToken.allowance(address(this), i_router);
if (allowance < fee) {
i_linkToken.approve(i_router, fee);
}
messageId = IRouterClient(i_router).ccipSend(
chainDetailsHavingUSDC.chainSelector,
message
);
emit MessageSent(messageId);
}
function bringUsdcFromAnotherChain(
address receiver, // Address of the contract receiving USDC
uint64 chainSelector, // Where to bring USDC back
uint256 amountOfUsdc
) public {
Client.EVMTokenAmount[]
memory tokenAmounts = new Client.EVMTokenAmount[](1);
tokenAmounts[0] = Client.EVMTokenAmount({
token: address(i_usdcToken),
amount: amountOfUsdc
});
Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
receiver: abi.encode(receiver),
data: "",
tokenAmounts: tokenAmounts,
extraArgs: "",
feeToken: address(i_linkToken)
});
if (i_usdcToken.balanceOf(address(this)) < amountOfUsdc) {
revert TestContract__InsufficientUSDCBalance();
}
uint256 usdc_allowance = i_usdcToken.allowance(address(this), i_router);
if (usdc_allowance < amountOfUsdc) {
// approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
i_usdcToken.approve(i_router, amountOfUsdc);
}
uint256 fee = IRouterClient(i_router).getFee(chainSelector, message);
if (i_linkToken.balanceOf(address(this)) < fee) {
revert TestContract__NotFundedWithEnoughLINK();
}
uint256 allowance = i_linkToken.allowance(address(this), i_router);
if (allowance < fee) {
i_linkToken.approve(i_router, fee);
}
bytes32 messageId;
messageId = IRouterClient(i_router).ccipSend(chainSelector, message);
emit MessageSent(messageId);
}
function setMessageData(string memory _messageData) public {
s_decodedMessageData = _messageData;
}
function publishMessageToAnotherChain(
string memory _message,
address receiver,
uint64 destinationChainSelector
) external {
bytes4 selector = this.setMessageData.selector;
bytes memory data = abi.encodeWithSelector(selector, _message);
Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
receiver: abi.encode(receiver),
data: data,
tokenAmounts: new Client.EVMTokenAmount[](0),
extraArgs: "",
feeToken: address(i_linkToken)
});
uint256 fee = IRouterClient(i_router).getFee(
destinationChainSelector,
message
);
if (i_linkToken.balanceOf(address(this)) < fee) {
revert TestContract__NotFundedWithEnoughLINK();
}
bytes32 messageId;
uint256 allowance = i_linkToken.allowance(address(this), i_router);
if (allowance < fee) {
i_linkToken.approve(i_router, fee);
}
messageId = IRouterClient(i_router).ccipSend(
destinationChainSelector,
message
);
emit MessageSent(messageId);
}
}
Any help or suggestions will be greatly appreciated.
Thanks.