前言本文借助Chainlink预言机中的VRF,实现一个链上可验证随机数的合约,以及相关使用场景的介绍;VRF定义:一种用于生成随机数的去中心化服务,广泛应用于需要公平、不可预测随机性的场景;场景1.游戏随机生成游戏道具、角色属性或战斗结果:在游戏开发中,VRF可以用于生成随机的
本文借助Chainlink预言机中的VRF,实现一个链上可验证随机数的合约,以及相关使用场景的介绍;
VRF
定义:一种用于生成随机数的去中心化服务,广泛应用于需要公平、不可预测随机性的场景;
场景
1. 游戏
// 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
// 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
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
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;
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!