CCIP跨链NFT铸币失败:目标链执行回退问题排查求助
跨链NFT铸币CCIP目标链执行失败(execution reverted)排查
开发NFT集合跨链桥,尝试从Sepolia链向Mumbai链铸币,源链交易已成功执行,但目标链出现execution reverted错误,无法查看目标链交易内部详情定位问题。以下是相关合约代码及排查分析:
Mumbai链CCIPReceiver合约(DestinationMinterEdited)
// SPDX-License-Identifier: MIT pragma solidity 0.8.19; import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol"; import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; import {EndersComicsMultiTokensTest} from "./EndersComicsTest.sol"; contract DestinationMinterEdited is CCIPReceiver { EndersComicsMultiTokensTest nft; event MintCallSuccessfull(); constructor(address router, address nftAddress) CCIPReceiver(router) { nft = EndersComicsMultiTokensTest(nftAddress); } function _ccipReceive( Client.Any2EVMMessage memory message ) internal override { (bool success, ) = address(nft).call(message.data); require(success, "tx failed"); emit MintCallSuccessfull(); } }
Sepolia链源铸币合约(SourceMinterEdited)
// SPDX-License-Identifier: MIT pragma solidity 0.8.19; import {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/shared/interfaces/LinkTokenInterface.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 {Withdraw} from "./utils/Withdraw.sol"; contract SourceMinterEdited is Withdraw { enum PayFeesIn { Native, LINK } address immutable i_router; address immutable i_link; event MessageSent(bytes32 messageId); constructor(address router, address link) { i_router = router; i_link = link; LinkTokenInterface(i_link).approve(i_router, type(uint256).max); } receive() external payable {} function mint( uint64 destinationChainSelector, address receiver, uint256 id, uint256 amount, PayFeesIn payFeesIn ) external { Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(receiver), data: abi.encodeWithSignature( "mintBatch(address,uint256[],uint256[])", msg.sender, id, amount ), tokenAmounts: new Client.EVMTokenAmount[](0), extraArgs: Client._argsToBytes( // Additional arguments, setting gas limit Client.EVMExtraArgsV1({gasLimit: 2_000_000}) ), feeToken: payFeesIn == PayFeesIn.LINK ? i_link : address(0) }); uint256 fee = IRouterClient(i_router).getFee( destinationChainSelector, message ); bytes32 messageId; if (payFeesIn == PayFeesIn.LINK) { // LinkTokenInterface(i_link).approve(i_router, fee); messageId = IRouterClient(i_router).ccipSend( destinationChainSelector, message ); } else { messageId = IRouterClient(i_router).ccipSend{value: fee}( destinationChainSelector, message ); } emit MessageSent(messageId); } }
EndersComicsMultiTokensTest合约mintBatch函数
function mintBatch( address _receiver, uint256[] calldata ids, uint256[] calldata amounts ) external { _mintBatch(_receiver, ids, amounts, ""); }
已尝试调整gas限制、将参数改为非数组类型,均未解决问题。
核心问题分析与解决方案
ABI编码参数类型不匹配(首要原因)
SourceMinterEdited的mint函数中,调用abi.encodeWithSignature时传递的id和amount是单个uint256值,但目标mintBatch函数要求的是uint256[]数组类型。这种参数类型不匹配会导致编码后的消息数据无法被目标合约正确解析,直接触发执行 revert。错误代码片段:
data: abi.encodeWithSignature( "mintBatch(address,uint256[],uint256[])", msg.sender, id, // 单个uint256,而非uint256[] amount // 单个uint256,而非uint256[] ),修正方式:将单个值包装成数组传递:
data: abi.encodeWithSignature( "mintBatch(address,uint256[],uint256[])", msg.sender, [id], // 包装为数组 [amount] // 包装为数组 ),权限验证(次要排查点)
检查EndersComicsMultiTokensTest合约的mintBatch函数是否存在访问控制逻辑(比如仅允许合约所有者或指定的DestinationMinterEdited地址调用)。如果有访问限制,需确保DestinationMinterEdited合约地址已被授权调用mintBatch。
验证建议
- 本地模拟测试:在Hardhat/Foundry环境中模拟跨链调用流程,编写测试函数直接调用
DestinationMinterEdited的_ccipReceive(需绕过内部函数限制),传入编码后的正确参数,验证mintBatch是否能成功执行。 - 增强错误日志:修改
DestinationMinterEdited的_ccipReceive函数,捕获并返回具体的revert原因,便于定位问题:
重新部署目标链合约后,可通过交易的revert信息获取具体错误详情。function _ccipReceive(Client.Any2EVMMessage memory message) internal override { (bool success, bytes memory returnData) = address(nft).call(message.data); if (!success) { if (returnData.length > 0) { // 解析revert原因 string memory reason = abi.decode(returnData, (string)); revert(string(abi.encodePacked("tx failed: ", reason))); } revert("tx failed"); } emit MintCallSuccessfull(); }
内容的提问来源于stack exchange,提问作者Andrés Contreras
相关产品推荐
相关产品推荐

