1

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.

1 Answer 1

0

It sounds that the transaction throws "out of gas" at least is what the Tendery says.

https://dashboard.tenderly.co/tx/sepolia/0xe60515e8719a8cf04ee141797a5757b5ea23a5b5976adb1d8d580114d20dc97e?trace=0.0.0.7.0

have you set enough max gas to perform this transaction on the callback?

[edit] when you call bringUsdcFromAnotherChain how many gas does the contract spends to call this function? I'm asking that, because you are not sending how many gas the CCIP can spend to call this function and by default it's gonna pass "200,000" Try to send this extraArgs gasLimit and set it high to see if it works.

Client.EVM2AnyMessage({
            receiver: abi.encode(_receiver), // ABI-encoded receiver address
            data: abi.encode(_text), // ABI-encoded string
            tokenAmounts: tokenAmounts, // The amount and type of token being transferred
            extraArgs: Client._argsToBytes(
                // Additional arguments, setting gas limit
                Client.EVMExtraArgsV1({gasLimit: 200_000})
            ),
            // Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees
            feeToken: _feeTokenAddress
        });
1
  • Thanks for the answer, Lucas. Yes, it worked when I set the gasLimit in publishMessageToBringUsdcFromAnotherChain as 1_000_000. Commented May 2 at 10:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.