通过智能合约理解 Chainlink Functions

本文介绍了如何使用 Chainlink Functions 将智能合约连接到外部 API,以获取城市的天气数据。通过 WeatherFunctions.sol 代码库,展示了如何创建 Chainlink Function Subscription,以及如何在智能合约中构建和发送请求,并处理返回的数据。文章详细解释了智能合约的关键步骤,例如参数传递,请求初始化等。

通过智能合约理解 Chainlink Functions

Chainlink Functions 解决了区块链和传统 Web 应用之间的互联互通问题。它允许开发者将智能合约连接到 API 接口、物联网设备和企业系统。

要使用 Chainlink Functions,你需要确保你的 metamask 钱包 已连接到与 Chainlink 兼容的网络,拥有足够的 Chainlink 代币 支付 gas 费用,并且创建了 Chainlink 函数订阅

考虑到这些,让我们来看看 Chainlink Function 智能合约。这个合约的逻辑是获取一个城市的当前天气,这要归功于 Chainlink Functions 实现的 API 集成。在本文中,我们使用 WeatherFunctions.sol 代码库,点击这里访问完整代码

首先,我们需要获取一个 Function Subscription ID 传递给构造器部署器(你可以点击这里查看订阅教程):

    constructor(uint64 functionsSubscriptionId) FunctionsClient(router) {
        subscriptionId = functionsSubscriptionId;
    }

在部署时,functionsSubscriptionId 存储在 uint64 public subscriptionId 变量中。部署完成后,我们可以与合约进行交互。首先要理解的交互是 getTemperature() 函数,让我们逐步进行。

首先,getTemperature() 接收一个 _city 参数,并期望返回 bytes32 requestId

    function getTemperature(
        string memory _city
    ) external returns (bytes32 requestId)

_city 参数被分配到 args[0] 中,为链下准备参数,弥合智能合约和外部系统之间的差距:

        string[] memory args = new string[](1);
        args[0] = _city;

new string 的上下文是在内存中定义一个长度为 1 的动态字符串数组。使用特定长度实例化数组使其与 Chainlink Functions 兼容且 gas 高效。

下一步是以一种清晰的方式编码请求,当我们使用:

        FunctionsRequest.Request memory req;

这样,我们可以从 FunctionsRequest.sol 合约中引用 Request 结构体。仅使用 req 简化了实例的使用。为了允许以这种方式使用,需要在合约范围内定义 FunctionsRequest

    using FunctionsRequest for FunctionsRequest.Request;

为了使其正常工作,你需要导入库:

import {FunctionsRequest} from "@chainlink/contracts/src/v0.8/functions/dev/v1_0_0/libraries/FunctionsRequest.sol";

因此,下一步是在 string public source 变量中定义智能合约的 JavaScript 逻辑,请看这里:

    string public source =
        "const city = args[0];"
        "const apiResponse = await Functions.makeHttpRequest({"
        "url: `https://wttr.in/${city}?format=3&m`,"
        "responseType: 'text'"
        "});"
        "if (apiResponse.error) {"
        "throw Error('Request failed');"
        "}"
        "const { data } = apiResponse;"
        "return Functions.encodeString(data);";

string public source 通过参数传递给 req.initializeRequestForInlineJavaScript()

        req.initializeRequestForInlineJavaScript(source);

注意:initializeRequestForInlineJavaScript()FunctionsRequest.sol 合约中的一个函数,用于处理 JavaScript 代码。

然后,它检查 args.length 是否大于 0,将 args 数组作为参数发送给调用函数 req.setArgs()

        if (args.length > 0) req.setArgs(args);

因此,args 被添加到 FunctionsRequest.sol 合约的 Request 结构体中。

下一步是理解 lastRequestId 变量。这个变量接收 _sendRequest() 函数的返回值:

        lastRequestId = _sendRequest(
            req.encodeCBOR(),
            subscriptionId,
            gasLimit,
            donID
        );

_sendRequest() 函数在 FunctionsClient.sol 合约中定义,并期望接收四个参数:bytes memory data, uint64 subscriptionId, uint32 callbackGasLimit, bytes32 donId.

为了使其正常工作,你需要导入 FunctionsClient.sol 合约:

import {FunctionsClient} from "@chainlink/contracts/src/v0.8/functions/dev/v1_0_0/FunctionsClient.sol";

第一个参数是 req.encodeCBOR()。它期望 FunctionsRequest.sol 合约中的 encodeCBOR() 函数的返回值。第二个参数是在构造函数中定义的 subscriotionId 变量。第三个参数是在存储中定义的 gasLimit 变量,值为 300000。然后是 donID 变量,也在存储中定义,值为 0x66756e2d657468657265756d2d7365706f6c69612d3100000000000000000000,参见:

    bytes32 donID =
        0x66756e2d657468657265756d2d7365706f6c69612d3100000000000000000000;

    uint32 gasLimit = 300000;

继续函数逻辑,getTemperature() 函数的 _city 参数存储在 string public lastCity 中,并在 request_city 映射中使用 lastRequestId 定义:

    string public lastCity;
    mapping(bytes32 => string) public request_city;
        lastCity = _city;
        request_city[lastRequestId] = _city;

CityStruct‎ 结构体实例被复制到 CityStruct memory auxCityStruct:

        CityStruct memory auxCityStruct = CityStruct({
            sender: msg.sender,
            timestamp: 0,
            name: _city,
            temperature: ""
        });

为了正常工作,struct CityStructCityStruct[] public cities 必须存在于合约的范围内:

    struct CityStruct {
        address sender;
        uint timestamp;
        string name;
        string temperature;
    }
    CityStruct[] public cities;

当创建一个公共数组(例如 cities)时,Solidity 会在底层生成如下函数:

function cities(uint256 index) external view returns (address sender, uint256 timestamp, string memory name, string memory temperature);

所以,你可以使用 CityStruct({ … }),来设置存储在索引数组中的值,在这种情况下,保存 CityStruct memory auxCityStruct。这种语法是一种类似于构造函数的简写方式来实例化结构体。你显式地命名每个字段并为其赋值,Solidity 将其映射到结构体的布局。

cities.push() 将该实例从内存复制到存储中的 cities 数组。数组增长,并且可以通过 cities[cities.length-1] 访问新元素:

        cities.push(auxCityStruct);
        cityIndex[_city] = cities.length-1;

一个新的 RequestStatus 结构体被添加到映射 requestslastRequestId 键。RequestStatus({…}) 使用命名参数语法创建一个临时的内存结构体,类似于之前的 CityStruct 示例。这个临时的内存结构体从内存复制到存储中的 requests[lastRequestId] 位置:

        requests[lastRequestId] = RequestStatus({
            exists: true,
            fulfilled: false,
            response: "",
            err: ""
        });

这是通过将相关数据分组到单个单元中来完成的,从而更容易管理每个请求的状态。使用 bytes 作为 responseerr 对于处理任意 Chainlink 数据是灵活的。它为跟踪请求是待处理、已完成还是失败提供了一个清晰的结构。

lastRequestId 附加到 requestIds 数组:

        requestIds.push(lastRequestId);

最后,返回 lastRequestId

        return lastRequestId;

getTemperature() 函数的一般操作流程:

getTemperature() 接收一个城市,通过Chainlink Functions发送一个请求来获取天气数据,并且在链上存储请求的细节。存储在 source 变量中的JavaScript在链下运行,并且回调更新结果。提供一种无缝的方式来整合智能合约和外部API,利用 Chainlink 的基础设施来实现可靠性和去中心化。

  1. 用户输入: 用户调用 getTemperature(“London”)
  2. 请求准备: 合约准备一个带有JavaScript的请求来从 wttr.in/London 获取天气。
  3. Chainlink 提交: 将请求发送到 Chainlink 的 DON,获取一个 lastRequestId
  4. 链上存储: 将城市和请求细节存储在 citiesrequestsmappings 中。
  5. 链下执行: Chainlink 运行 JavaScript,获取 “London: 25°C”(示例)。
  6. 回调(未显示): Chainlink 调用 fulfillRequest,更新 requests[lastRequestId].responsecities[cityIndex[“London”] ].temperature

Chainlink Functions 通过 JavaScript 弥合了区块链和外部 API(例如,wttr.in)之间的差距。在这种情况下,它允许合约获取实时天气数据,这是区块链本身无法做到的。

它使用 FunctionsRequest.solFunctionsClient.sol 库进行请求编码和提交。使用可重用和经过测试的 Chainlink 组件简化了开发。

本文基于 Chainlink Fundamentals course 。所有代码由课程提供。

  • 原文链接: coinsbench.com/undestand...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
CoinsBench
CoinsBench
https://coinsbench.com/