无法在智能合约中获取Chainlink请求的数据问题求助
问题背景
我正尝试通过Chainlink外部适配器将数据获取到智能合约中,Chainlink任务已执行完成,但智能合约无法收到请求的数据。
Chainlink任务执行详情
任务步骤详情
fetch bridge
{"data":{"result":{"id":"UCAl9Ld79qaZxp9JzEOwd3aA","statistics":{"subscriberCount":"209000"}},"jobRunId":"0x84aae6f7b7144fbc8196230158918797","statusCode":200}name: yutu
requestData:{"id": $(jobSpec.externalJobID), "data": { "chid": $(decode_cbor.chid)},"key": $(decode_cbor.key)}parse jsonparse
结果:"209000"
path:data,result,statistics,subscriberCount
data:$(fetch)encode_data ethabiencode
结果:"0x0000000000000000000000000000000000000000000000000000000000033068"
abi:(uint256 value)
data:{ "value": $(parse) }encode_tx ethabiencode
结果:"0x4ab0d1902a02a37be17119894c2d9400f9bcf563bd4be47496af9a493a96eb412676916c0000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000002e495ea15b191aff2dea171abba537dc597d6f117642d375000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006571ad180000000000000000000000000000000000000000000000000000000000033068"
abi:fulfillOracleRequest(bytes32 requestId, uint256 payment, address callbackAddress, bytes4 callbackFunctionId, uint256 expiration, bytes32 data)
data:{"requestId": $(decode_log.requestId), "payment": $(decode_log.payment), "callbackAddress": $(decode_log.callbackAddr), "callbackFunctionId": $(decode_log.callbackFunctionId), "expiration": $(decode_log.cancelExpiration), "data": $(encode_data)}submit_tx ethtx
to:0x34b33d85A6a642A6Eaa1fE554f576c6122FF64b6
data:$(encode_tx)
智能合约代码
// SPDX-License-Identifier: MIT pragma solidity ^0.8.7; import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol"; import "@chainlink/contracts/src/v0.8/ConfirmedOwner.sol"; /** * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE. * DO NOT USE THIS CODE IN PRODUCTION. */ contract ConsumerContract is ChainlinkClient, ConfirmedOwner { using Chainlink for Chainlink.Request; uint256 private constant ORACLE_PAYMENT = 1 * LINK_DIVISIBILITY; // 1 * 10**18 uint256 public lastRetrievedSubscriberCount; event RequestForSubscriberCountFulfilled( bytes32 indexed requestId, uint256 indexed subscriberCount ); /** * Goerli * @dev LINK address in Sepholia network: 0x779877A7B0D9E8603169DdbD7836e478b4624789 */ constructor() ConfirmedOwner(msg.sender) { setChainlinkToken(0x779877A7B0D9E8603169DdbD7836e478b4624789); } function requestSubscriberCount( address _oracle, string memory _jobId, string memory channelId, string memory apiKey ) public onlyOwner { Chainlink.Request memory req = buildOperatorRequest( stringToBytes32(_jobId), this.fulfillRequestInfo.selector ); req.add("chid", channelId); req.add("key", apiKey); sendOperatorRequestTo(_oracle, req, ORACLE_PAYMENT); } function fulfillRequestInfo(bytes32 _requestId, string memory _info) public recordChainlinkFulfillment(_requestId) { // Parse the JSON data to extract the subscriberCount (uint256 subscriberCount, ) = abi.decode(bytes(_info), (uint256, uint256)); emit RequestForSubscriberCountFulfilled(_requestId, subscriberCount); lastRetrievedSubscriberCount = subscriberCount; } function getSubscriberCount() public view returns (uint256) { return lastRetrievedSubscriberCount; } /* ========= UTILITY FUNCTIONS ========== */ function contractBalances() public view returns (uint256 eth, uint256 link) { eth = address(this).balance; LinkTokenInterface linkContract = LinkTokenInterface( chainlinkTokenAddress() ); link = linkContract.balanceOf(address(this)); } function getChainlinkToken() public view returns (address) { return chainlinkTokenAddress(); } function withdrawLink() public onlyOwner { LinkTokenInterface link = LinkTokenInterface(chainlinkTokenAddress()); require( link.transfer(msg.sender, link.balanceOf(address(this))), "Unable to transfer Link" ); } function withdrawBalance() public onlyOwner { payable(msg.sender).transfer(address(this).balance); } function cancelRequest( bytes32 _requestId, uint256 _payment, bytes4 _callbackFunctionId, uint256 _expiration ) public onlyOwner { cancelChainlinkRequest( _requestId, _payment, _callbackFunctionId, _expiration ); } function stringToBytes32(string memory source) private pure returns (bytes32 result) { bytes memory tempEmptyStringTest = bytes(source); if (tempEmptyStringTest.length == 0) { return 0x0; } assembly { // solhint-disable-line no-inline-assembly result := mload(add(source, 32)) } } }
问题原因及修复方案
核心问题
智能合约的fulfillRequestInfo函数参数类型与Chainlink任务的输出不匹配:任务中encode_data将订阅者数量编码为uint256对应的bytes32数据,但合约回调函数却期望接收string类型参数,并且错误地尝试将字符串字节解码为两个uint256,直接导致解码失败,无法获取有效数据。
修复步骤
- 修改回调函数参数类型:将
string memory _info改为bytes32 _data,匹配任务输出的编码格式。 - 调整解码逻辑:直接将
bytes32转换为uint256,无需多余的ABI解码操作。
修复后的fulfillRequestInfo函数:
function fulfillRequestInfo(bytes32 _requestId, bytes32 _data) public recordChainlinkFulfillment(_requestId) { uint256 subscriberCount = uint256(_data); emit RequestForSubscriberCountFulfilled(_requestId, subscriberCount); lastRetrievedSubscriberCount = subscriberCount; }
- 验证回调选择器:构建Chainlink请求时,
this.fulfillRequestInfo.selector会自动匹配修改后的函数,无需手动调整。
额外验证点
- 确认任务中的
callbackFunctionId与合约修改后的fulfillRequestInfo函数选择器一致。 - 检查合约部署地址、调用
requestSubscriberCount时传入的预言机地址、Job ID是否正确。 - 确保合约账户中有足够的LINK代币用于支付预言机服务费用。
内容的提问来源于stack exchange,提问作者János Barna

