明白基本的VRF服务是怎么样的,有个雏形
1uint private _counter = 0;
function getRandomWithTen() external returns (uint) {
++_counter;
return uint(keccak256(abi.encode(
blockhash(1),
gasleft(),
block.number,
_counter //_counter在这里起到的妙用就是:在一个区块内,以保证用户发送的多笔交易生成的随机数是不同的
))) % 10;
}
对于普通用户来说,这是安全的。但是对于矿工来说,这些信息都是可以看到的,它可以在出块的时候,调控 gasleft() 参数,通过节点计算出最终的要得奖的随机数。
那么针对问题,我们肯定是要保证随机数生成的相关信息是不会别任何人知道的。这里直接引用了文档的介绍
Chainlink VRF(可验证随机函数)是一种可证明公平且可验证的随机数生成器(RNG),它使智能合约能够在不影响安全性或可用性的情况下访问随机值。对于每个请求, Chainlink VRF 生成一个或多个随机值以及如何确定这些值的加密证明。在任何 consumer 应用程序可以使用该证明之前,该证明将在链上发布和验证。此过程确保结果不会被任何单个实体篡改或操纵,包括预言机运营商、矿工、用户或智能合约开发者。
注意
在这个过程中,你所使用的区块链的底层矿工/验证者可以重写链的历史来将你发送的随机性请求合约放到不同的块,这将导致不同的 VRF 输出。请注意,这并不能让矿工提前确定随机值。它只能让他们获得一个新的随机值,这可能对他们有利,也可能不利。打个比方,他们只能重新掷骰子,而不能预先确定或预测骰子会落在哪一边。你必须为你提出的随机性请求选择适当的确认时间,也就是确认区块数。
大佬可以直接看着文档直接写,下面我以介绍实操为主,我是直接按照里面的流程走的,主要是初学者进行熟悉,以后文档都可以这样去看
首先导入两个库合约
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import {VRFConsumerBaseV2Plus} from "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol";
import {VRFV2PlusClient} from "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol";
contract VRFD20 is VRFConsumerBaseV2Plus {
}
VRFConsumerBaseV2Plus.sol 合约内容
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {IVRFCoordinatorV2Plus} from "./interfaces/IVRFCoordinatorV2Plus.sol";
import {IVRFMigratableConsumerV2Plus} from "./interfaces/IVRFMigratableConsumerV2Plus.sol";
import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol";
abstract contract VRFConsumerBaseV2Plus is IVRFMigratableConsumerV2Plus, ConfirmedOwner {
error OnlyCoordinatorCanFulfill(address have, address want);
error OnlyOwnerOrCoordinator(address have, address owner, address coordinator);
error ZeroAddress();
// s_vrfCoordinator should be used by consumers to make requests to vrfCoordinator
// so that coordinator reference is updated after migration
IVRFCoordinatorV2Plus public s_vrfCoordinator;
/**
* @param _vrfCoordinator address of VRFCoordinator contract
*/
constructor(address _vrfCoordinator) ConfirmedOwner(msg.sender) {
if (_vrfCoordinator == address(0)) {
revert ZeroAddress();
}
s_vrfCoordinator = IVRFCoordinatorV2Plus(_vrfCoordinator);
}
/**
* @notice fulfillRandomness handles the VRF response. Your contract must
* @notice implement it. See "SECURITY CONSIDERATIONS" above for important
* @notice principles to keep in mind when implementing your fulfillRandomness
* @notice method.
*
* @dev VRFConsumerBaseV2Plus expects its subcontracts to have a method with this
* @dev signature, and will call it once it has verified the proof
* @dev associated with the randomness. (It is triggered via a call to
* @dev rawFulfillRandomness, below.)
*
* @param requestId The Id initially returned by requestRandomness
* @param randomWords the VRF output expanded to the requested number of words
*/
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) internal virtual;
// rawFulfillRandomness is called by VRFCoordinator when it receives a valid VRF
// proof. rawFulfillRandomness then calls fulfillRandomness, after validating
// the origin of the call
function rawFulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) external {
if (msg.sender != address(s_vrfCoordinator)) {
revert OnlyCoordinatorCanFulfill(msg.sender, address(s_vrfCoordinator));
}
fulfillRandomWords(requestId, randomWords);
}
/**
* @inheritdoc IVRFMigratableConsumerV2Plus
*/
function setCoordinator(address _vrfCoordinator) external override onlyOwnerOrCoordinator {
if (_vrfCoordinator == address(0)) {
revert ZeroAddress();
}
s_vrfCoordinator = IVRFCoordinatorV2Plus(_vrfCoordinator);
emit CoordinatorSet(_vrfCoordinator);
}
modifier onlyOwnerOrCoordinator() {
if (msg.sender != owner() && msg.sender != address(s_vrfCoordinator)) {
revert OnlyOwnerOrCoordinator(msg.sender, owner(), address(s_vrfCoordinator));
}
_;
}
}
这个合约定义了我们之后要使用的一些重要的函数,由我们定义的函数去继承
VRFV2PlusClient.sol 合约内容
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
// End consumer library.
library VRFV2PlusClient {
// extraArgs will evolve to support new features
bytes4 public constant EXTRA_ARGS_V1_TAG = bytes4(keccak256("VRF ExtraArgsV1"));
struct ExtraArgsV1 {
bool nativePayment;
}
struct RandomWordsRequest {
bytes32 keyHash;
uint256 subId;
uint16 requestConfirmations;
uint32 callbackGasLimit;
uint32 numWords;
bytes extraArgs;
}
function _argsToBytes(ExtraArgsV1 memory extraArgs) internal pure returns (bytes memory bts) {
return abi.encodeWithSelector(EXTRA_ARGS_V1_TAG, extraArgs);
}
}
这个合约定义了一些必要的参数(后面会讲),以及配置了是否使用原生代币(eth)功能的函数
补充我们现在的合约,引入一些必要的变量 和 映射
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import {VRFConsumerBaseV2Plus} from "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol";
import {VRFV2PlusClient} from "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol";
contract VRFD20 is VRFConsumerBaseV2Plus {
uint256 s_subscriptionId;
address vrfCoordinator = 0x9DdfaCa8183c41ad55329BdeeD9F6A8d53168B1B;
bytes32 s_keyHash = 0x787d74caea10b2b357790d5b5247c2f63d1d91572a9846f780606e4d953677ae;
uint32 callbackGasLimit = 40000;
uint16 requestConfirmations = 3;
uint32 numWords = 1;
//用来检查请求是否被响应过
mapping(uint256 => address) private s_rollers;
mapping(address => uint256) private s_results;
}
初始化合约 引入事件,发起请求,存储requestId
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import {VRFConsumerBaseV2Plus} from "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol";
import {VRFV2PlusClient} from "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol";
contract VRFD20 is VRFConsumerBaseV2Plus {
uint256 s_subscriptionId;
address vrfCoordinator = 0x9DdfaCa8183c41ad55329BdeeD9F6A8d53168B1B;
bytes32 s_keyHash = 0x787d74caea10b2b357790d5b5247c2f63d1d91572a9846f780606e4d953677ae;
uint32 callbackGasLimit = 40000;
uint16 requestConfirmations = 3;
uint32 numWords = 1;
//用来检查请求是否被响应过
mapping(uint256 => address) private s_rollers;
mapping(address => uint256) private s_results;
// variables
uint256 private constant ROLL_IN_PROGRESS = 42;
// ...
// constructor
constructor(uint256 subscriptionId) VRFConsumerBaseV2Plus(vrfCoordinator) {//向父合约的构造函数传入参数
s_subscriptionId = subscriptionId;
}
// events
event DiceRolled(uint256 indexed requestId, address indexed roller);
// ...
// ...
// { constructor }
// ...
// rollDice function
function rollDice(address roller) public onlyOwner returns (uint256 requestId) {
require(s_results[roller] == 0, "Already rolled");
// Will revert if subscription is not set and funded.
//s_vrfCoordinator 这个参数是在我们的父合约中
requestId = s_vrfCoordinator.requestRandomWords(
VRFV2PlusClient.RandomWordsRequest({
keyHash: s_keyHash,
subId: s_subscriptionId,
requestConfirmations: requestConfirmations,
callbackGasLimit: callbackGasLimit,
numWords: numWords,
// Set nativePayment to true to pay for VRF requests with Sepolia ETH instead of LINK
extraArgs: VRFV2PlusClient._argsToBytes(VRFV2PlusClient.ExtraArgsV1({nativePayment: false}))
})
);
s_rollers[requestId] = roller;
s_results[roller] = ROLL_IN_PROGRESS;
emit DiceRolled(requestId, roller);
}
}
requestId
和 owner地址返回随机数 fulfillRandomWords
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import {VRFConsumerBaseV2Plus} from "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol";
import {VRFV2PlusClient} from "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol";
contract VRFD20 is VRFConsumerBaseV2Plus {
uint256 s_subscriptionId;
address vrfCoordinator = 0x9DdfaCa8183c41ad55329BdeeD9F6A8d53168B1B;
bytes32 s_keyHash = 0x787d74caea10b2b357790d5b5247c2f63d1d91572a9846f780606e4d953677ae;
uint32 callbackGasLimit = 40000;
uint16 requestConfirmations = 3;
uint32 numWords = 1;
//用来检查请求是否被响应过
mapping(uint256 => address) private s_rollers;
mapping(address => uint256) private s_results;
// variables
uint256 private constant ROLL_IN_PROGRESS = 42;
// ...
// constructor
constructor(uint256 subscriptionId) VRFConsumerBaseV2Plus(vrfCoordinator) {//向父合约的构造函数传入参数
s_subscriptionId = subscriptionId;
}
...
// events
event DiceRolled(uint256 indexed requestId, address indexed roller);
event DiceLanded(uint256 indexed requestId, uint256 indexed result);
// ...
// ...
// { constructor }
// ...
// rollDice function
function rollDice(address roller) public onlyOwner returns (uint256 requestId) {
require(s_results[roller] == 0, "Already rolled");
// Will revert if subscription is not set and funded.
//s_vrfCoordinator 这个参数是在我们的父合约中
requestId = s_vrfCoordinator.requestRandomWords(
VRFV2PlusClient.RandomWordsRequest({
keyHash: s_keyHash,
subId: s_subscriptionId,
requestConfirmations: requestConfirmations,
callbackGasLimit: callbackGasLimit,
numWords: numWords,
// Set nativePayment to true to pay for VRF requests with Sepolia ETH instead of LINK
extraArgs: VRFV2PlusClient._argsToBytes(VRFV2PlusClient.ExtraArgsV1({nativePayment: false}))
})
);
s_rollers[requestId] = roller;
s_results[roller] = ROLL_IN_PROGRESS;
emit DiceRolled(requestId, roller);
}
function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) internal override {
// transform the result to a number between 1 and 20 inclusively
uint256 d20Value = (randomWords[0] % 20) + 1;
// assign the transformed value to the address in the s_results mapping variable
s_results[s_rollers[requestId]] = d20Value;
// emitting event to signal that dice landed
emit DiceLanded(requestId, d20Value);
}
}
最后再次注意文档里面说的 该示例适用于 Sepolia 测试网,
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!