Chainlink预言机中VRF(可验证随机函数)在合约中的使用

  • 木西
  • 发布于 2025-03-05 13:06
  • 阅读 166

前言本文借助Chainlink预言机中的VRF,实现一个链上可验证随机数的合约,以及相关使用场景的介绍;VRF定义:一种用于生成随机数的去中心化服务,广泛应用于需要公平、不可预测随机性的场景;场景1.游戏随机生成游戏道具、角色属性或战斗结果:在游戏开发中,VRF可以用于生成随机的

前言

本文借助Chainlink预言机中的VRF,实现一个链上可验证随机数的合约,以及相关使用场景的介绍;

VRF

定义:一种用于生成随机数的去中心化服务,广泛应用于需要公平、不可预测随机性的场景;

场景

1. 游戏

  • 随机生成游戏道具、角色属性或战斗结果:在游戏开发中,VRF可以用于生成随机的游戏道具、角色属性或战斗结果,确保游戏的公平性和不可预测性。

2. 抽奖和彩票

  • 公平地选择中奖者:VRF可以用于抽奖和彩票系统中,随机选择中奖者,确保抽奖过程的公平性和透明性。

3. NFT生成

  • 随机分配NFT属性或稀有度:在NFT(非同质化代币)项目中,VRF可以用于随机分配NFT的属性或稀有度,增加NFT的多样性和不可预测性。

4. 治理

  • 随机选择投票委员会或分配资源:在去中心化治理系统中,VRF可以用于随机选择投票委员会成员或分配资源,确保治理过程的公平性和透明性。

5. 随机分配

  • 随机分配任务、资源或奖励:在需要随机分配任务、资源或奖励的场景中,VRF可以提供公平的随机性,确保分配过程的公正性。

合约开发

MockVRFCoordinatorV2合约

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@chainlink/contracts/src/v0.8/vrf/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol";

contract MockVRFCoordinatorV2 is VRFCoordinatorV2Interface {
    uint96 public immutable BASE_FEE;
    uint96 public immutable GAS_PRICE_LINK;
    uint16 public immutable MAX_CONSUMERS = 100;

    error InvalidSubscription();
    error InsufficientBalance();
    error MustBeSubOwner(address owner);

    mapping(uint256 => uint64) public s_requestSubscription;
    mapping(uint256 => uint256[]) public s_requestRandomWords;
    mapping(uint256 => VRFConsumerBaseV2) public s_requests;

    uint256 public s_nextRequestId = 1;
    uint256 public s_nextCoordinatorId = 1;

    constructor(uint96 _baseFee, uint96 _gasPriceLink) {
        BASE_FEE = _baseFee;
        GAS_PRICE_LINK = _gasPriceLink;
    }

    function requestRandomWords(
        bytes32 keyHash,
        uint64 subId,
        uint16 minimumRequestConfirmations,
        uint32 callbackGasLimit,
        uint32 numWords
    ) external override returns (uint256) {
        uint256 requestId = s_nextRequestId++;
        uint256[] memory randomWords = new uint256[](numWords);
        for(uint256 i = 0; i < numWords; i++) {
            randomWords[i] = uint256(keccak256(abi.encode(requestId, i)));
        }

        s_requestSubscription[requestId] = subId;
        s_requestRandomWords[requestId] = randomWords;
        s_requests[requestId] = VRFConsumerBaseV2(msg.sender);

        return requestId;
    }

    function fulfillRandomWords(uint256 requestId) external {
        require(s_requests[requestId] != VRFConsumerBaseV2(address(0)), "request not found");
        s_requests[requestId].rawFulfillRandomWords(
            requestId,
            s_requestRandomWords[requestId]
        );
    }

    // 以下是必需实现但在测试中未使用的接口函数
    function getRequestConfig() external pure override returns (uint16, uint32, bytes32[] memory) {
        revert("not implemented");
    }

    function createSubscription() external override returns (uint64) {
        return 1;
    }

    function getSubscription(uint64) external pure override returns (uint96, uint64, address, address[] memory) {
        revert("not implemented");
    }

    function requestSubscriptionOwnerTransfer(uint64, address) external pure override {
        revert("not implemented");
    }

    function acceptSubscriptionOwnerTransfer(uint64) external pure override {
        revert("not implemented");
    }

    function addConsumer(uint64, address) external pure override {
        revert("not implemented");
    }

    function removeConsumer(uint64, address) external pure override {
        revert("not implemented");
    }

    function cancelSubscription(uint64, address) external pure override {
        revert("not implemented");
    }

    function pendingRequestExists(uint64) external pure override returns (bool) {
        revert("not implemented");
    }
}
# 编译指令
# npx hardhat compile

VRFv2Consumer合约

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@chainlink/contracts/src/v0.8/vrf/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "hardhat/console.sol";
contract VRFv2Consumer is VRFConsumerBaseV2, Ownable {
    event RequestSent(uint256 requestId, uint32 numWords);
    event RequestFulfilled(uint256 requestId, uint256[] randomWords);

    struct RequestStatus {
        bool fulfilled; // 是否已完成
        bool exists; // 请求是否存在
        uint256[] randomWords; // 随机数结果
    }

    mapping(uint256 => RequestStatus) public s_requests; // requestId => requestStatus
    uint256[] public requestIds; // 所有请求ID
    uint256 public lastRequestId; // 最后一个请求ID

    // VRFCoordinatorV2Interface immutable COORDINATOR;
     VRFCoordinatorV2Interface public COORDINATOR;

    // 订阅ID
    uint64 immutable s_subscriptionId;

    // Sepolia 测试网配置
    bytes32 immutable s_keyHash = 0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c;

    // 请求配置
    uint32 constant CALLBACK_GAS_LIMIT = 100000;
    uint16 constant REQUEST_CONFIRMATIONS = 3;
    uint32 constant NUM_WORDS = 2;

    constructor(
        uint64 subscriptionId,
        address vrfCoordinator
    ) VRFConsumerBaseV2(vrfCoordinator) Ownable(msg.sender) {
        COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
        s_subscriptionId = subscriptionId;
    }

    // 请求随机数
    function requestRandomWords() external onlyOwner returns (uint256 requestId) {
        requestId = COORDINATOR.requestRandomWords(
            s_keyHash,
            s_subscriptionId,
            REQUEST_CONFIRMATIONS,
            CALLBACK_GAS_LIMIT,
            NUM_WORDS
        );
        s_requests[requestId] = RequestStatus({
            fulfilled: false,
            exists: true,
            randomWords: new uint256[](0)
        });
        requestIds.push(requestId);
        lastRequestId = requestId;
        emit RequestSent(requestId, NUM_WORDS);
        return requestId;
    }

    // VRF回调函数
    function fulfillRandomWords(
        uint256 _requestId,
        uint256[] memory _randomWords
    ) internal override {
        require(s_requests[_requestId].exists, "request not found");
        s_requests[_requestId].fulfilled = true;
        s_requests[_requestId].randomWords = _randomWords;
        emit RequestFulfilled(_requestId, _randomWords);
    }

    // 获取请求状态
    function getRequestStatus(
        uint256 _requestId
    ) external view returns (bool fulfilled, uint256[] memory randomWords) {
        require(s_requests[_requestId].exists, "request not found");
        RequestStatus memory request = s_requests[_requestId];
        return (request.fulfilled, request.randomWords);
    }
}
# 编译指令
# npx hardhat compile

合约测试

说明注意:测试合约使用的是EthersV6版本;

const { ethers } = require("hardhat");
const { expect } = require("chai");
const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers");

describe("VRFv2Consumer", function () {
    async function deployFixture() {
        const [owner, addr1] = await ethers.getSigners();

        // 部署模拟VRF协调器
        const MockVRFCoordinatorV2 = await ethers.getContractFactory("MockVRFCoordinatorV2");
        const mockCoordinator = await MockVRFCoordinatorV2.deploy(1, 1);
        const mockCoordinatorAddress = await mockCoordinator.getAddress();
        // 部署VRF消费者合约
        const VRFv2Consumer = await ethers.getContractFactory("VRFv2Consumer");
        const consumer = await VRFv2Consumer.deploy(1, mockCoordinatorAddress);
        return { mockCoordinator, consumer, owner, addr1 };
    }

    describe("部署", function () {
        it("正确的所有者", async function () {
            const { consumer, owner } = await loadFixture(deployFixture);
            expect(await consumer.owner()).to.equal(owner.address);
        });

        it("正确的协调器进行初始化", async function () {

            const { mockCoordinator, consumer } = await loadFixture(deployFixture);     
            expect(await consumer.COORDINATOR()).to.equal(await mockCoordinator.getAddress());
        });
    });

    describe("随机数请求", function () {
        it("随机数成功", async function () {
            const { consumer } = await loadFixture(deployFixture);
            await expect(consumer.requestRandomWords())
                .to.emit(consumer, "RequestSent")
                .withArgs(1, 2); // requestId = 1, numWords = 2
        });

        it("只允许 owner请求", async function () {
            const { consumer, addr1 } = await loadFixture(deployFixture);

            await expect(consumer.connect(addr1).requestRandomWords())
                .to.be.revertedWithCustomError(consumer, "OwnableUnauthorizedAccount");
        });

        it("跟踪请求标识符", async function () {
            const { consumer } = await loadFixture(deployFixture);

            await consumer.requestRandomWords();
            expect(await consumer.lastRequestId()).to.equal(1);
            expect(await consumer.requestIds(0)).to.equal(1);
        });
    });

    describe("随机数实现", function () {
        it("满足随机数请求", async function () {
            const { mockCoordinator, consumer } = await loadFixture(deployFixture);

            const tx = await consumer.requestRandomWords();
            const receipt = await tx.wait();

            const requestId = receipt.logs[0].args[0];

            await expect(mockCoordinator.fulfillRandomWords(requestId))
                .to.emit(consumer, "RequestFulfilled");
        });

        it("正确存储随机数", async function () {
            const { mockCoordinator, consumer } = await loadFixture(deployFixture);

            const tx = await consumer.requestRandomWords();
            const receipt = await tx.wait();
            const requestId = receipt.logs[0].args[0];

            await mockCoordinator.fulfillRandomWords(requestId);

            const [fulfilled, randomWords] = await consumer.getRequestStatus(requestId);
            expect(fulfilled).to.be.true;
            expect(randomWords.length).to.equal(2);
        });

        it("不存在的随机数回滚", async function () {
            const { consumer } = await loadFixture(deployFixture);

            await expect(consumer.getRequestStatus(999))
                .to.be.revertedWith("request not found");
        });
    });

    describe("多请求", function () {
        it("处理多个请求", async function () {
            const { mockCoordinator, consumer } = await loadFixture(deployFixture);

            // 发送多个请求
            const requestCount = 3;
            const requests = [];

            for(let i = 0; i < requestCount; i++) {
                const tx = await consumer.requestRandomWords();
                const receipt = await tx.wait();
                requests.push(receipt.logs[0].args[0]);
            }

            // 验证请求跟踪
            expect(await consumer.lastRequestId()).to.equal(requestCount);

            // 完成所有请求
            for(const requestId of requests) {
                await mockCoordinator.fulfillRandomWords(requestId);
                const [fulfilled, randomWords] = await consumer.getRequestStatus(requestId);
                expect(fulfilled).to.be.true;
                expect(randomWords.length).to.equal(2);
            }
        });
    });

    describe("Gas量", function () {
        it("使用合理的气量", async function () {
            const { consumer } = await loadFixture(deployFixture);

            const tx = await consumer.requestRandomWords();
            const receipt = await tx.wait();
            // console.log("gas",receipt.gasUsed)
            expect(receipt.gasUsed).to.be.lt(300000);
        });
    });
});
# 测试指令
# npx hardhat test ./test/xxx.js

合约部署

MockVRFCoordinatorV2合约

module.exports = async ({ getNamedAccounts, deployments }) => {
    const firstAccount=(await getNamedAccounts()).firstAccount;
    const { deploy, log } = deployments
    const MockVRFCoordinatorV2 = await deploy("MockVRFCoordinatorV2", {
        contract: "MockVRFCoordinatorV2",
        from: firstAccount,
        log: true,
        args: [1,1],//参数  baseFee, gasPriceLink
    });
    console.log("MockVRFCoordinatorV2合约地址",MockVRFCoordinatorV2.address)
}
module.exports.tags = ["all", "MockVRFCoordinatorV2"]
# 部署指令
# npx hardhat deploy

VRFv2Consumer合约

module.exports = async ({ getNamedAccounts, deployments }) => {
    const firstAccount=(await getNamedAccounts()).firstAccount;
    const { deploy, log } = deployments;
    const MockVRFCoordinatorV2=await deployments.get("MockVRFCoordinatorV2"); 
    const MockVRFCoordinatorV2Address = MockVRFCoordinatorV2.address;
    const VRFv2Consumer = await deploy("VRFv2Consumer", {
        contract: "VRFv2Consumer",
        from: firstAccount,
        log: true,
        args: [1,MockVRFCoordinatorV2Address],//参数 subscriptionId,vrfCoordinatorAddress
    });
  console.log("VRFv2Consumer合约地址",VRFv2Consumer.address)
}
module.exports.tags = ["all", "VRFv2Consumer"]

# 部署指令
# npx hardhat deploy

总结

以上就是Chainlink预言机中VRF链上随机数的合约的开发、测试、部署全部流程以及相关概念和使用场景的介绍,特别说明注意ethersV6;

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
木西
木西
江湖只有他的大名,没有他的介绍。