{"author":{"address":null,"user":"https://learnblockchain.cn/people/18158"},"content":{"body":"# 前言\r\n\u003e 本文借助Chainlink预言机中的VRF,实现一个链上可验证随机数的合约，以及相关使用场景的介绍；\r\n# VRF\r\n**定义**：一种用于生成随机数的去中心化服务，广泛应用于需要公平、不可预测随机性的场景；\r\n### 场景\r\n#### 1. **游戏**\r\n\r\n-   **随机生成游戏道具、角色属性或战斗结果**：在游戏开发中，VRF可以用于生成随机的游戏道具、角色属性或战斗结果，确保游戏的公平性和不可预测性。\r\n\r\n#### 2. **抽奖和彩票**\r\n\r\n-   **公平地选择中奖者**：VRF可以用于抽奖和彩票系统中，随机选择中奖者，确保抽奖过程的公平性和透明性。\r\n\r\n#### 3. **NFT生成**\r\n\r\n-   **随机分配NFT属性或稀有度**：在NFT（非同质化代币）项目中，VRF可以用于随机分配NFT的属性或稀有度，增加NFT的多样性和不可预测性。\r\n\r\n#### 4. **治理**\r\n\r\n-   **随机选择投票委员会或分配资源**：在去中心化治理系统中，VRF可以用于随机选择投票委员会成员或分配资源，确保治理过程的公平性和透明性。\r\n\r\n#### 5. **随机分配**\r\n\r\n-   **随机分配任务、资源或奖励**：在需要随机分配任务、资源或奖励的场景中，VRF可以提供公平的随机性，确保分配过程的公正性。\r\n\r\n\r\n# 合约开发\r\n#### MockVRFCoordinatorV2合约\r\n```\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.20;\r\n\r\nimport \"@chainlink/contracts/src/v0.8/vrf/interfaces/VRFCoordinatorV2Interface.sol\";\r\nimport \"@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol\";\r\n\r\ncontract MockVRFCoordinatorV2 is VRFCoordinatorV2Interface {\r\n    uint96 public immutable BASE_FEE;\r\n    uint96 public immutable GAS_PRICE_LINK;\r\n    uint16 public immutable MAX_CONSUMERS = 100;\r\n\r\n    error InvalidSubscription();\r\n    error InsufficientBalance();\r\n    error MustBeSubOwner(address owner);\r\n\r\n    mapping(uint256 =\u003e uint64) public s_requestSubscription;\r\n    mapping(uint256 =\u003e uint256[]) public s_requestRandomWords;\r\n    mapping(uint256 =\u003e VRFConsumerBaseV2) public s_requests;\r\n\r\n    uint256 public s_nextRequestId = 1;\r\n    uint256 public s_nextCoordinatorId = 1;\r\n\r\n    constructor(uint96 _baseFee, uint96 _gasPriceLink) {\r\n        BASE_FEE = _baseFee;\r\n        GAS_PRICE_LINK = _gasPriceLink;\r\n    }\r\n\r\n    function requestRandomWords(\r\n        bytes32 keyHash,\r\n        uint64 subId,\r\n        uint16 minimumRequestConfirmations,\r\n        uint32 callbackGasLimit,\r\n        uint32 numWords\r\n    ) external override returns (uint256) {\r\n        uint256 requestId = s_nextRequestId++;\r\n        uint256[] memory randomWords = new uint256[](numWords);\r\n        for(uint256 i = 0; i \u003c numWords; i++) {\r\n            randomWords[i] = uint256(keccak256(abi.encode(requestId, i)));\r\n        }\r\n\r\n        s_requestSubscription[requestId] = subId;\r\n        s_requestRandomWords[requestId] = randomWords;\r\n        s_requests[requestId] = VRFConsumerBaseV2(msg.sender);\r\n\r\n        return requestId;\r\n    }\r\n\r\n    function fulfillRandomWords(uint256 requestId) external {\r\n        require(s_requests[requestId] != VRFConsumerBaseV2(address(0)), \"request not found\");\r\n        s_requests[requestId].rawFulfillRandomWords(\r\n            requestId,\r\n            s_requestRandomWords[requestId]\r\n        );\r\n    }\r\n\r\n    // 以下是必需实现但在测试中未使用的接口函数\r\n    function getRequestConfig() external pure override returns (uint16, uint32, bytes32[] memory) {\r\n        revert(\"not implemented\");\r\n    }\r\n\r\n    function createSubscription() external override returns (uint64) {\r\n        return 1;\r\n    }\r\n\r\n    function getSubscription(uint64) external pure override returns (uint96, uint64, address, address[] memory) {\r\n        revert(\"not implemented\");\r\n    }\r\n\r\n    function requestSubscriptionOwnerTransfer(uint64, address) external pure override {\r\n        revert(\"not implemented\");\r\n    }\r\n\r\n    function acceptSubscriptionOwnerTransfer(uint64) external pure override {\r\n        revert(\"not implemented\");\r\n    }\r\n\r\n    function addConsumer(uint64, address) external pure override {\r\n        revert(\"not implemented\");\r\n    }\r\n\r\n    function removeConsumer(uint64, address) external pure override {\r\n        revert(\"not implemented\");\r\n    }\r\n\r\n    function cancelSubscription(uint64, address) external pure override {\r\n        revert(\"not implemented\");\r\n    }\r\n\r\n    function pendingRequestExists(uint64) external pure override returns (bool) {\r\n        revert(\"not implemented\");\r\n    }\r\n}\r\n# 编译指令\r\n# npx hardhat compile\r\n```\r\n#### VRFv2Consumer合约\r\n```\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.20;\r\n\r\nimport \"@chainlink/contracts/src/v0.8/vrf/interfaces/VRFCoordinatorV2Interface.sol\";\r\nimport \"@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol\";\r\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\r\nimport \"hardhat/console.sol\";\r\ncontract VRFv2Consumer is VRFConsumerBaseV2, Ownable {\r\n    event RequestSent(uint256 requestId, uint32 numWords);\r\n    event RequestFulfilled(uint256 requestId, uint256[] randomWords);\r\n\r\n    struct RequestStatus {\r\n        bool fulfilled; // 是否已完成\r\n        bool exists; // 请求是否存在\r\n        uint256[] randomWords; // 随机数结果\r\n    }\r\n\r\n    mapping(uint256 =\u003e RequestStatus) public s_requests; // requestId =\u003e requestStatus\r\n    uint256[] public requestIds; // 所有请求ID\r\n    uint256 public lastRequestId; // 最后一个请求ID\r\n\r\n    // VRFCoordinatorV2Interface immutable COORDINATOR;\r\n     VRFCoordinatorV2Interface public COORDINATOR;\r\n\r\n    // 订阅ID\r\n    uint64 immutable s_subscriptionId;\r\n\r\n    // Sepolia 测试网配置\r\n    bytes32 immutable s_keyHash = 0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c;\r\n    \r\n    // 请求配置\r\n    uint32 constant CALLBACK_GAS_LIMIT = 100000;\r\n    uint16 constant REQUEST_CONFIRMATIONS = 3;\r\n    uint32 constant NUM_WORDS = 2;\r\n\r\n    constructor(\r\n        uint64 subscriptionId,\r\n        address vrfCoordinator\r\n    ) VRFConsumerBaseV2(vrfCoordinator) Ownable(msg.sender) {\r\n        COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);\r\n        s_subscriptionId = subscriptionId;\r\n    }\r\n\r\n    // 请求随机数\r\n    function requestRandomWords() external onlyOwner returns (uint256 requestId) {\r\n        requestId = COORDINATOR.requestRandomWords(\r\n            s_keyHash,\r\n            s_subscriptionId,\r\n            REQUEST_CONFIRMATIONS,\r\n            CALLBACK_GAS_LIMIT,\r\n            NUM_WORDS\r\n        );\r\n        s_requests[requestId] = RequestStatus({\r\n            fulfilled: false,\r\n            exists: true,\r\n            randomWords: new uint256[](0)\r\n        });\r\n        requestIds.push(requestId);\r\n        lastRequestId = requestId;\r\n        emit RequestSent(requestId, NUM_WORDS);\r\n        return requestId;\r\n    }\r\n\r\n    // VRF回调函数\r\n    function fulfillRandomWords(\r\n        uint256 _requestId,\r\n        uint256[] memory _randomWords\r\n    ) internal override {\r\n        require(s_requests[_requestId].exists, \"request not found\");\r\n        s_requests[_requestId].fulfilled = true;\r\n        s_requests[_requestId].randomWords = _randomWords;\r\n        emit RequestFulfilled(_requestId, _randomWords);\r\n    }\r\n\r\n    // 获取请求状态\r\n    function getRequestStatus(\r\n        uint256 _requestId\r\n    ) external view returns (bool fulfilled, uint256[] memory randomWords) {\r\n        require(s_requests[_requestId].exists, \"request not found\");\r\n        RequestStatus memory request = s_requests[_requestId];\r\n        return (request.fulfilled, request.randomWords);\r\n    }\r\n}\r\n# 编译指令\r\n# npx hardhat compile\r\n```\r\n# 合约测试\r\n**说明**：`注意`：测试合约使用的是EthersV6版本；\r\n```\r\nconst { ethers } = require(\"hardhat\");\r\nconst { expect } = require(\"chai\");\r\nconst { loadFixture } = require(\"@nomicfoundation/hardhat-network-helpers\");\r\n\r\ndescribe(\"VRFv2Consumer\", function () {\r\n    async function deployFixture() {\r\n        const [owner, addr1] = await ethers.getSigners();\r\n\r\n        // 部署模拟VRF协调器\r\n        const MockVRFCoordinatorV2 = await ethers.getContractFactory(\"MockVRFCoordinatorV2\");\r\n        const mockCoordinator = await MockVRFCoordinatorV2.deploy(1, 1);\r\n        const mockCoordinatorAddress = await mockCoordinator.getAddress();\r\n        // 部署VRF消费者合约\r\n        const VRFv2Consumer = await ethers.getContractFactory(\"VRFv2Consumer\");\r\n        const consumer = await VRFv2Consumer.deploy(1, mockCoordinatorAddress);\r\n        return { mockCoordinator, consumer, owner, addr1 };\r\n    }\r\n\r\n    describe(\"部署\", function () {\r\n        it(\"正确的所有者\", async function () {\r\n            const { consumer, owner } = await loadFixture(deployFixture);\r\n            expect(await consumer.owner()).to.equal(owner.address);\r\n        });\r\n\r\n        it(\"正确的协调器进行初始化\", async function () {\r\n            \r\n            const { mockCoordinator, consumer } = await loadFixture(deployFixture);     \r\n            expect(await consumer.COORDINATOR()).to.equal(await mockCoordinator.getAddress());\r\n        });\r\n    });\r\n\r\n    describe(\"随机数请求\", function () {\r\n        it(\"随机数成功\", async function () {\r\n            const { consumer } = await loadFixture(deployFixture);\r\n            await expect(consumer.requestRandomWords())\r\n                .to.emit(consumer, \"RequestSent\")\r\n                .withArgs(1, 2); // requestId = 1, numWords = 2\r\n        });\r\n\r\n        it(\"只允许 owner请求\", async function () {\r\n            const { consumer, addr1 } = await loadFixture(deployFixture);\r\n            \r\n            await expect(consumer.connect(addr1).requestRandomWords())\r\n                .to.be.revertedWithCustomError(consumer, \"OwnableUnauthorizedAccount\");\r\n        });\r\n\r\n        it(\"跟踪请求标识符\", async function () {\r\n            const { consumer } = await loadFixture(deployFixture);\r\n            \r\n            await consumer.requestRandomWords();\r\n            expect(await consumer.lastRequestId()).to.equal(1);\r\n            expect(await consumer.requestIds(0)).to.equal(1);\r\n        });\r\n    });\r\n\r\n    describe(\"随机数实现\", function () {\r\n        it(\"满足随机数请求\", async function () {\r\n            const { mockCoordinator, consumer } = await loadFixture(deployFixture);\r\n            \r\n            const tx = await consumer.requestRandomWords();\r\n            const receipt = await tx.wait();\r\n            \r\n            const requestId = receipt.logs[0].args[0];\r\n            \r\n            await expect(mockCoordinator.fulfillRandomWords(requestId))\r\n                .to.emit(consumer, \"RequestFulfilled\");\r\n        });\r\n\r\n        it(\"正确存储随机数\", async function () {\r\n            const { mockCoordinator, consumer } = await loadFixture(deployFixture);\r\n            \r\n            const tx = await consumer.requestRandomWords();\r\n            const receipt = await tx.wait();\r\n            const requestId = receipt.logs[0].args[0];\r\n            \r\n            await mockCoordinator.fulfillRandomWords(requestId);\r\n            \r\n            const [fulfilled, randomWords] = await consumer.getRequestStatus(requestId);\r\n            expect(fulfilled).to.be.true;\r\n            expect(randomWords.length).to.equal(2);\r\n        });\r\n\r\n        it(\"不存在的随机数回滚\", async function () {\r\n            const { consumer } = await loadFixture(deployFixture);\r\n            \r\n            await expect(consumer.getRequestStatus(999))\r\n                .to.be.revertedWith(\"request not found\");\r\n        });\r\n    });\r\n\r\n    describe(\"多请求\", function () {\r\n        it(\"处理多个请求\", async function () {\r\n            const { mockCoordinator, consumer } = await loadFixture(deployFixture);\r\n            \r\n            // 发送多个请求\r\n            const requestCount = 3;\r\n            const requests = [];\r\n            \r\n            for(let i = 0; i \u003c requestCount; i++) {\r\n                const tx = await consumer.requestRandomWords();\r\n                const receipt = await tx.wait();\r\n                requests.push(receipt.logs[0].args[0]);\r\n            }\r\n\r\n            // 验证请求跟踪\r\n            expect(await consumer.lastRequestId()).to.equal(requestCount);\r\n            \r\n            // 完成所有请求\r\n            for(const requestId of requests) {\r\n                await mockCoordinator.fulfillRandomWords(requestId);\r\n                const [fulfilled, randomWords] = await consumer.getRequestStatus(requestId);\r\n                expect(fulfilled).to.be.true;\r\n                expect(randomWords.length).to.equal(2);\r\n            }\r\n        });\r\n    });\r\n\r\n    describe(\"Gas量\", function () {\r\n        it(\"使用合理的气量\", async function () {\r\n            const { consumer } = await loadFixture(deployFixture);\r\n            \r\n            const tx = await consumer.requestRandomWords();\r\n            const receipt = await tx.wait();\r\n            // console.log(\"gas\",receipt.gasUsed)\r\n            expect(receipt.gasUsed).to.be.lt(300000);\r\n        });\r\n    });\r\n});\r\n# 测试指令\r\n# npx hardhat test ./test/xxx.js\r\n```\r\n# 合约部署\r\n#### MockVRFCoordinatorV2合约\r\n```\r\nmodule.exports = async ({ getNamedAccounts, deployments }) =\u003e {\r\n    const firstAccount=(await getNamedAccounts()).firstAccount;\r\n    const { deploy, log } = deployments\r\n    const MockVRFCoordinatorV2 = await deploy(\"MockVRFCoordinatorV2\", {\r\n        contract: \"MockVRFCoordinatorV2\",\r\n        from: firstAccount,\r\n        log: true,\r\n        args: [1,1],//参数  baseFee, gasPriceLink\r\n    });\r\n    console.log(\"MockVRFCoordinatorV2合约地址\",MockVRFCoordinatorV2.address)\r\n}\r\nmodule.exports.tags = [\"all\", \"MockVRFCoordinatorV2\"]\r\n# 部署指令\r\n# npx hardhat deploy\r\n```\r\n#### VRFv2Consumer合约\r\n```\r\nmodule.exports = async ({ getNamedAccounts, deployments }) =\u003e {\r\n    const firstAccount=(await getNamedAccounts()).firstAccount;\r\n    const { deploy, log } = deployments;\r\n    const MockVRFCoordinatorV2=await deployments.get(\"MockVRFCoordinatorV2\"); \r\n    const MockVRFCoordinatorV2Address = MockVRFCoordinatorV2.address;\r\n    const VRFv2Consumer = await deploy(\"VRFv2Consumer\", {\r\n        contract: \"VRFv2Consumer\",\r\n        from: firstAccount,\r\n        log: true,\r\n        args: [1,MockVRFCoordinatorV2Address],//参数 subscriptionId,vrfCoordinatorAddress\r\n    });\r\n  console.log(\"VRFv2Consumer合约地址\",VRFv2Consumer.address)\r\n}\r\nmodule.exports.tags = [\"all\", \"VRFv2Consumer\"]\r\n\r\n# 部署指令\r\n# npx hardhat deploy\r\n```\r\n# 总结\r\n以上就是Chainlink预言机中VRF链上随机数的合约的开发、测试、部署全部流程以及相关概念和使用场景的介绍，`特别说明注意`ethersV6;","title":"Chainlink预言机中VRF（可验证随机函数）在合约中的使用"},"history":null,"timestamp":1741151194,"version":1}