在 Solidity 智能合约中调用 Chainlink 预言机获取外部数据及生成随机数

在我们做去中心化应用开发时,其中有两个头疼的问题,一是想要获取去中心化的一些数据,如代币价格等,还有一个就是安全的创建随机数,这篇文章带大家了解一下这两块的实际应用。

由于智能合约无法调用外部 API 这一特性,所以诞生了预言机这个机制用来帮助智能合约获取外部数据,除了应用最广泛的价格数据以外,还包括一些天气数据,体育比赛数据,股票市场数据,交通数据,甚至包括总统选结果等数据。

除了提供数据,预言机广义上的功能也包括提供随机数和作为触发器实现智能合约执行,它们都算是链下的工具来和链上的合约进行交互。

Chainlink 是目前预言机数据的最大提供平台。

ts 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

/**
 * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED
 * VALUES FOR CLARITY.
 * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
 * DO NOT USE THIS CODE IN PRODUCTION.
 */

/**
 * If you are reading data feeds on L2 networks, you must
 * check the latest answer from the L2 Sequencer Uptime
 * Feed to ensure that the data is accurate in the event
 * of an L2 sequencer outage. See the
 * https://docs.chain.link/data-feeds/l2-sequencer-feeds
 * page for details.
 */

contract DataConsumerV3 {
    AggregatorV3Interface internal dataFeed;

    /**
     * Network: Sepolia
     * Aggregator: BTC/USD
     * Address: 0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43
     */
    constructor() {
        dataFeed = AggregatorV3Interface(
            0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43
        );
    }

    /**
     * Returns the latest answer.
     */
    function getPrice() public view returns (int) {
        // prettier-ignore
        (
            /* uint80 roundID */,
            int answer,
            /*uint startedAt*/,
            /*uint timeStamp*/,
            /*uint80 answeredInRound*/
        ) = dataFeed.latestRoundData();
        return answer / (10 ** 8);
    }
}

上面是一个获取BTC/USD价格的智能合约示例。

  • 首先 import名为AggregatorV3Interface的接口,这个接口由chainlink官方的包提供。
  • constructor构造函数中,初始化一个名为dataFeedAggregatorV3Interface接口对象,在初始化的接口函数中传入Chainklink部署的对应BTC/USD价格的合约地址,这里因为是用的Sepolia测试网,具体每个网络可以获取哪些价格数据的合约地址,点此查看
  • 最后通过一个getPrice函数调用dataFeedlatestRoundData方法来获取价格,返回的answer参数就是具体的价格,但每个交易对的金额精度不一样,一般要除以金额精度才是真正的USD单位,如这里的精度是8,最终answer需要除以10 ** 8才是USD

3. 通过 Remix 部署合约测试

我们的智能合约需要调用Sepolia其他智能合约,所以没办法直接在本地测试,需要部署到Sepolia测试网上,建议通过Remix来部署

首先在MetaMask钱包中添加Sepolia网络,然后在Remix部署界面选择使用MetaMask钱包部署,最后点击Deploy按钮部署。

部署成功后就可以看到我们已经部署的智能合约,执行getPrice方法来获取价格。

更详细的获取外部数据文档,点此查看官方文档

1. Solidity 直接生成随机数

ts 复制代码
/** 
* 链上伪随机数生成
* 利用keccak256()打包一些链上的全局变量/自定义变量
* 返回时转换成uint256类型
*/
function getRandomOnchain() public view returns(uint256){
    // remix运行blockhash会报错
    bytes32 randomBytes = keccak256(abi.encodePacked(block.number, msg.sender, blockhash(block.timestamp-1)));
    return uint256(randomBytes);
}

主要是利用keccak256函数根据区块高度当前调用人时间戳等生成随机数,但是这种方法并不安全,因为结果对于矿工是可预测的,如我们做一款Web3彩票项目,矿工可以选择根据这个生成结果是否中奖选择是否上报这个区块。

ts 复制代码
// SPDX-License-Identifier: MIT
// An example of a consumer contract that directly pays for each request.
pragma solidity ^0.8.7;

import "@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol";
import "@chainlink/contracts/src/v0.8/vrf/VRFV2WrapperConsumerBase.sol";

/**
 * Request testnet LINK and ETH here: https://faucets.chain.link/
 * Find information on LINK Token Contracts and get the latest ETH and LINK faucets here: https://docs.chain.link/docs/link-token-contracts/
 */

/**
 * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
 * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
 * DO NOT USE THIS CODE IN PRODUCTION.
 */

contract VRFv2DirectFundingConsumer is
    VRFV2WrapperConsumerBase,
    ConfirmedOwner
{
    event RequestSent(uint256 requestId, uint32 numWords);
    event RequestFulfilled(
        uint256 requestId,
        uint256[] randomWords,
        uint256 payment
    );

    struct RequestStatus {
        uint256 paid; // amount paid in link
        bool fulfilled; // whether the request has been successfully fulfilled
        uint256[] randomWords;
    }
    mapping(uint256 => RequestStatus)
        public s_requests; /* requestId --> requestStatus */

    // past requests Id.
    uint256[] public requestIds;
    uint256 public lastRequestId;

    // Depends on the number of requested values that you want sent to the
    // fulfillRandomWords() function. Test and adjust
    // this limit based on the network that you select, the size of the request,
    // and the processing of the callback request in the fulfillRandomWords()
    // function.
    uint32 callbackGasLimit = 100000;

    // The default is 3, but you can set this higher.
    uint16 requestConfirmations = 3;

    // For this example, retrieve 2 random values in one request.
    // Cannot exceed VRFV2Wrapper.getConfig().maxNumWords.
    uint32 numWords = 2;

    // Address LINK - hardcoded for Sepolia
    address linkAddress = 0x779877A7B0D9E8603169DdbD7836e478b4624789;

    // address WRAPPER - hardcoded for Sepolia
    address wrapperAddress = 0xab18414CD93297B0d12ac29E63Ca20f515b3DB46;

    constructor()
        ConfirmedOwner(msg.sender)
        VRFV2WrapperConsumerBase(linkAddress, wrapperAddress)
    {}

    function requestRandomWords()
        external
        onlyOwner
        returns (uint256 requestId)
    {
        requestId = requestRandomness(
            callbackGasLimit,
            requestConfirmations,
            numWords
        );
        s_requests[requestId] = RequestStatus({
            paid: VRF_V2_WRAPPER.calculateRequestPrice(callbackGasLimit),
            randomWords: new uint256[](0),
            fulfilled: false
        });
        requestIds.push(requestId);
        lastRequestId = requestId;
        emit RequestSent(requestId, numWords);
        return requestId;
    }

    function fulfillRandomWords(
        uint256 _requestId,
        uint256[] memory _randomWords
    ) internal override {
        require(s_requests[_requestId].paid > 0, "request not found");
        s_requests[_requestId].fulfilled = true;
        s_requests[_requestId].randomWords = _randomWords;
        emit RequestFulfilled(
            _requestId,
            _randomWords,
            s_requests[_requestId].paid
        );
    }

    function getRequestStatus(
        uint256 _requestId
    )
        external
        view
        returns (uint256 paid, bool fulfilled, uint256[] memory randomWords)
    {
        require(s_requests[_requestId].paid > 0, "request not found");
        RequestStatus memory request = s_requests[_requestId];
        return (request.paid, request.fulfilled, request.randomWords);
    }

    /**
     * Allow withdraw of Link tokens from the contract
     */
    function withdrawLink() public onlyOwner {
        LinkTokenInterface link = LinkTokenInterface(linkAddress);
        require(
            link.transfer(msg.sender, link.balanceOf(address(this))),
            "Unable to transfer"
        );
    }
}

这是一个Sepolia测试网的示例,每个网络下linkAddresswrapperAddress地址不同, 每个网络具体可以 点此查看

3. 部署及测试

部署的时候同样选择MetaMask连接到Sepolia网络,部署好了后,我们还需要给部署好的合约转入LINK代币(很重要的)。

复制这个合约地址,用MetaMask钱包给这个地址转入LINK代币2-3个左右(测试用)。

转入成功后就可以调用requestRandomWords方法,它会生成一个requestId,这个requestId可以通过调用lastRequestId方法查看。

最后通过给getRequestStatus方法传入requestId参数并调用就可以查看Chainlink响应给我们的随机字符串了。

具体的获取随机数官方文档,点此查看

这样我们就最终实现了在Solidity 智能合约中调用 Chainklink 获取外部数据及生成随机数,如果各位小伙伴有什么问题,也欢迎留言哦!

相关推荐
dingzd953 小时前
走进 Web3:探索分布式网络的未来
web3·去中心化·区块链·互联网
Roun34 小时前
元宇宙中的去中心化应用:Web3的未来角色
web3·去中心化·区块链
清 晨4 小时前
Web3 生态全景:创新与发展之路
人工智能·web3·去中心化·智能合约
TianXuan_Chain14 小时前
web3跨链预言机协议-BandProtocol
web3·区块链·预言机
电报号dapp11914 小时前
比特币市场震荡:回调背后的机遇与挑战
人工智能·去中心化·区块链·智能合约
用户74921347159719 小时前
solidity(基础特性)—学习总结
solidity
2301_776045232 天前
加密货币地址的基本概念
区块链
CESS_Cloud4 天前
CESS 出席华盛顿区块链政策峰会:参与国家安全与数据隐私保护专题讨论
安全·阿里云·web3·去中心化·区块链
TianXuan_Chain5 天前
web3跨链桥协议-Nomad
web3·区块链·智能合约·跨链桥
CertiK5 天前
Web3.0安全开发实践:探索比特币DeFi生态中的PSBT
区块链