Skip to content

与随机数预编译交互

概览

Moonbeam使用可验证随机函数(Verifiable Random Functions,VRF)生成可以在链上验证的随机数。VRF是一种利用一些输入值并产生随机数的加密函数,并证明这些数值是由提交者生成。此证明可以由任何人验证,以确保准确计算生成的随机数计算。

目前有两种提供随机输入的可用随机数来源,分别基于区块生产者的VRF私钥以及过去的随机数结果:本地VRFBABE Epoch随机数。本地VRF在Moonbeam中使用区块的VRF私钥以及最新区块的VRF输出值决定。而BABE Epoch随机数基于所有由中继链验证人在完整Epoch期间生产的VRF。

关于更多两种随机数来源的信息,如何请求和完成工作流程以及安全考虑,请查看Moonbeam上的随机数页面。

Moonbeam提供一个随机数预编译,其为一个允许智能合约开发者使用以太坊API通过本地VRF或BABE Epoch随机数来生成随机数。Moonbeam同样提供一个随机数消费者Solidity合约,您的合约必须继承此Solidity合约才能实现已完成的随机数请求。

此教程将会包含如何使用随机数预编译以及随机数消费者合约创建一个随机选取赢家的彩票。同时,您将学习如何直接与随机数预编译交互以执行操作,例如清除过期的随机数请求。

此预编译合约位于以下地址:

0x0000000000000000000000000000000000000809
0x0000000000000000000000000000000000000809
0x0000000000000000000000000000000000000809

注意事项

在Moonbeam使用预编译合约时,可能会出现一些意想不到的后果。 请参阅安全注意事项 页面了解更多信息。

随机数Solidity接口

Randomness.sol为一个允许开发者与预编译方法交互的Solidity接口。

Randomness.sol
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.3;

/// @dev The Randomness contract's address.
address constant RANDOMNESS_ADDRESS = 0x0000000000000000000000000000000000000809;

/// @dev The Randomness contract's instance.
Randomness constant RANDOMNESS_CONTRACT = Randomness(RANDOMNESS_ADDRESS);

/// @dev Maximum number of random words being requested
uint32 constant MAX_RANDOM_WORDS = 100;
/// @dev Minimum number of blocks before a request can be fulfilled for Local VRF Request
uint32 constant MIN_VRF_BLOCKS_DELAY = 2;
/// @dev Maximum number of blocks before a request can be fulfilled for Local VRF Request
uint32 constant MAX_VRF_BLOCKS_DELAY = 2000;
/// @dev The deposit amount needed to request random words. There is 1 deposit per request
uint256 constant REQUEST_DEPOSIT_AMOUNT = 1000000000000000000;

/// @author The Moonbeam Team
/// @title Pallet Randomness Interface
/// @dev The interface through which solidity contracts will interact with Randomness
/// @custom:address 0x0000000000000000000000000000000000000809
interface Randomness {
    /// @notice Event emitted when the request has been successfully executed
    event FulFillmentSucceeded();
    /// @notice Event emitted when the request has failed to execute fulfillment
    event FulFillmentFailed();

    /// @notice The status of the request
    /// @param DoesNotExist The request doesn't exist
    /// @param Pending The request cannot be fulfilled yet
    /// @param Ready The request is ready to be fulfilled
    /// @param Expired The request has expired
    enum RequestStatus {
        DoesNotExist,
        Pending,
        Ready,
        Expired
    }

    /// @notice The type of randomness source
    /// @param LocalVRF Randomness VRF using the parachain material as seed
    /// @param RelayBabeEpoch Randomness VRF using relay material from previous epoch
    enum RandomnessSource {
        LocalVRF,
        RelayBabeEpoch
    }

    /// @notice The request details
    /// @param id The id of the request (is always < 2**64)
    /// @param refundAddress The address receiving the left-over fees after the fulfillment
    /// @param contractAddress The address of the contract being called back during fulfillment
    /// @param fee The amount to set aside to pay for the fulfillment
    /// @param gasLimit The gas limit to use for the fulfillment
    /// @param salt A string being mixed with the randomness seed to obtain different random words. This should be as unique as possible; using the same salt will lead to same randomness result.
    /// @param numWords The number of random words requested (from 1 to MAX_RANDOM_WORDS)
    /// @param randomnessSource The type of randomness source used to generate the random words
    /// @param fulfillmentBlock The parachain block number at which the request can be fulfilled (for LocalVRF only)
    /// @param fulfillmentEpochIndex The relay epoch index at which the request can be fulfilled (for RelayBabeEpoch)
    /// @param expirationBlock The parachain block number at which the request expires (for LocalVRF only)
    /// @param expirationEpochIndex The relay epoch index at which the request expires (for RelayBabeEpoch)
    /// @param status The current status of the request
    struct Request {
        uint256 id;
        address refundAddress;
        address contractAddress;
        uint256 fee;
        uint256 gasLimit;
        bytes32 salt;
        uint32 numWords;
        RandomnessSource randomnessSource;
        uint32 fulfillmentBlock;
        uint64 fulfillmentEpochIndex;
        uint32 expirationBlock;
        uint64 expirationEpochIndex;
        RequestStatus status;
    }

    /// Return the current relay epoch index
    /// @dev An epoch represents real time and not a block number
    /// @dev Currently, time between epoch changes cannot be longer than:
    /// @dev  - Kusama/Westend/Rococo: 600 relay blocks (1 hour)
    /// @dev  - Polkadot: 2400 relay blocks (4 hours)
    /// @custom:selector 81797566
    function relayEpochIndex() external view returns (uint64);

    /// Return the deposit required to perform a request
    /// @dev Each request will need a deposit.
    /// @custom:selector fb7cfdd7
    function requiredDeposit() external view returns (uint256);

    /// @notice Returns the request status
    /// @param requestId The id of the request to check (must be < 2**64)
    /// @return status Status of the request
    /// @custom:selector d8a4676f
    function getRequestStatus(uint256 requestId)
        external
        view
        returns (RequestStatus status);

    /// @notice Returns the request or revert
    /// @param requestId The id of the request to check (must be < 2**64)
    /// @return request The request
    /// @custom:selector c58343ef
    function getRequest(uint256 requestId)
        external
        view
        returns (Request memory request);

    /// @notice Request random words generated from the parachain VRF
    /// @dev This is using pseudo-random VRF executed by the collator at the fulfillment
    /// @dev Warning:
    /// @dev The collator in charge of producing the block at fulfillment can decide to skip
    /// @dev producing the block in order to have a different random word generated by the next
    /// @dev collator, at the cost of a block reward. It is therefore economically viable to use
    /// @dev this randomness source only if the financial reward at stake is lower than the block
    /// @dev reward.
    /// @dev In order to reduce the risk of a collator being able to predict the random words
    /// @dev when the request is performed, it is possible to increase the delay to multiple blocks
    /// @dev The higher the delay is, the less likely the collator will be able to know which
    /// @dev collator will be in charge of fulfilling the request.
    /// @dev Fulfillment is manual and can be executed by anyone (for free) after the given delay
    /// @param refundAddress The address receiving the left-over fees after the fulfillment
    /// @param fee The amount to set aside to pay for the fulfillment
    /// @param gasLimit The gas limit to use for the fulfillment
    /// @param salt A string being mixed with the randomness seed to obtain different random words
    /// @param numWords The number of random words requested (from 1 to MAX_RANDOM_WORDS)
    /// @param delay The number of blocks until the request can be fulfilled (between MIN_DELAY_BLOCKS and MAX_DELAY_BLOCKS)
    /// @return requestId The id of the request requestLocalVRFRandomWords
    /// @custom:selector 9478430c
    function requestLocalVRFRandomWords(
        address refundAddress,
        uint256 fee,
        uint64 gasLimit,
        bytes32 salt,
        uint8 numWords,
        uint64 delay
    ) external returns (uint256);

    /// @notice Request random words generated from the relaychain Babe consensus
    /// @dev The random words are generated from the hash of the all the VRF provided by the
    /// @dev relaychain validator during 1 epoch.
    /// @dev It requires a delay of at least 1 epoch after the current epoch to be unpredictable
    /// @dev at the time the request is performed.
    /// @dev Warning:
    /// @dev The validator (on the relaychain) of the last block of an epoch can decide to skip
    /// @dev producing the block in order to choose the previous generated epoch random number
    /// @dev at the cost of a relaychain block rewards. It is therefore economically viable to use
    /// @dev this randomness source only if the financial reward at stake is lower than the relaychain
    /// @dev block reward.
    /// @dev (see https://crates.parity.io/pallet_babe/struct.RandomnessFromOneEpochAgo.html)
    /// @dev Fulfillment is manual and can be executed by anyone (for free) at
    /// @dev the beginning of the 2nd relay epoch following the current one
    /// @param refundAddress The address receiving the left-over fees after the fulfillment
    /// @param fee Amount to set aside to pay for the fulfillment. Those fees are taken from the contract
    /// @param gasLimit Gas limit for the fulfillment
    /// @param salt Salt to be mixed with raw randomness to get output
    /// @param numWords Number of random words to be returned (limited to MAX_RANDOM_WORDS)
    /// @return requestId The id of the request
    /// @custom:selector 33c14a63
    function requestRelayBabeEpochRandomWords(
        address refundAddress,
        uint256 fee,
        uint64 gasLimit,
        bytes32 salt,
        uint8 numWords
    ) external returns (uint256);

    /// @dev fulFill the request which will call the contract method "fulfillRandomWords"
    /// @dev Fees of the caller are refunded if the request is fulfillable
    /// @param requestId Request to be fulfilled (must be < 2**64)
    /// @custom:selector 9a91eb0d
    function fulfillRequest(uint256 requestId) external;

    /// @param requestId Request receiving the additional fees (must be < 2**64)
    /// @param feeIncrease Amount to increase
    /// @custom:selector d0408a7f
    function increaseRequestFee(uint256 requestId, uint256 feeIncrease)
        external;

    /// @param requestId Request to be purged (must be < 2**64)
    /// @custom:selector 1d26cbab
    function purgeExpiredRequest(uint256 requestId) external;
}

此接口包含函数、常量、事件以及枚举,如下列部分所包含。

函数

此接口包含以下函数:

  • relayEpochIndex() — 返回当前的中继Epoch索引,Epoch代表当前实际时间而非区块编号
  • requiredDeposit() — 返回使用随机数请求要求的保证金
  • getRequestStatus(uint256 requestId) — 根据给定的随机数请求返回请求状态
  • getRequest(uint256 requestId) — 根据给定的随机数请求返回请求细节
  • requestLocalVRFRandomWords(address refundAddress, uint256 fee, uint64 gasLimit, bytes32 salt, uint8 numWords, uint64 delay) — 请求由平行链VRF生成的随机词
  • requestRelayBabeEpochRandomWords(address refundAddress, uint256 fee, uint64 gasLimit, bytes32 salt, uint8 numWords) — 请求由中继链BABE共识生成的随机词
  • fulfillRequest(uint256 requestId) — 完成调用消费者合约方法fulfillRandomWords的请求。调用者的费用将会在请求可完成后退还
  • increaseRequestFee(uint256 requestId, uint256 feeIncrease) — 根据给定的随机数请求提高相关费用。此适用于在请求被完成前Gas价格突然升高的情况
  • purgeExpiredRequest(uint256 requestId) — 从存储库中移除给定的过期请求,并将请求费用和保证金转移给调用者

以下为需要被定义的输入值:

  • requestId - 随机数请求的ID
  • refundAddress - 过程完成后收取剩余费用的地址
  • fee - 设定为支付完成费用的数值
  • gasLimit - 用于完成请求设置的Gas限制
  • salt - 包含随机数种子以获得不同随机词的字符串
  • numWords - 请求的随机词数量,最大至随机词的生成最大数值
  • delay - 在请求被完成前所需要经过的区块数量。此数值将需要在本地VRF请求能被完成的最大和最小区块数值之间
  • feeIncrease - 需要提高费用的数值

常量

此接口包含以下常量:

  • MAX_RANDOM_WORDS - 被请求的随机词的最大值
  • MIN_VRF_BLOCKS_DELAY - 在请求能被本地VRF请求完成前的最小区块数量
  • MAX_VRF_BLOCKS_DELAY - 在请求能被本地VRF请求完成前的最大区块数量
  • REQUEST_DEPOSIT_AMOUNT - 请求随机词所需的保证金。每个请求需要一笔保证金
变量
MAX_RANDOM_WORDS 100 words
MIN_VRF_BLOCKS_DELAY 2 blocks
MAX_VRF_BLOCKS_DELAY 2000 blocks
REQUEST_DEPOSIT_AMOUNT 100 GLMR
变量
MAX_RANDOM_WORDS 100 words
MIN_VRF_BLOCKS_DELAY 2 blocks
MAX_VRF_BLOCKS_DELAY 2000 blocks
REQUEST_DEPOSIT_AMOUNT 1 MOVR
变量
MAX_RANDOM_WORDS 100 words
MIN_VRF_BLOCKS_DELAY 2 blocks
MAX_VRF_BLOCKS_DELAY 2000 blocks
REQUEST_DEPOSIT_AMOUNT 1 DEV

事件

此接口包含以下事件:

  • FulfillmentSucceeded() - 在请求被成功执行后发起
  • FulfillmentFailed() - 在请求执行失败后发起

枚举

以下接口包含下列枚举:

  • RequestStatus - 请求的状态,分别能为DoesNotExist (0)、Pending (1)、Ready (2)或Expired (3)
  • RandomnessSource - 随机数资源的类型,分别能为LocalVRF (0)或RelayBabeEpoch (1)

随机数消费者Solidity接口

RandomnessConsumer.sol Solidity接口使智能合约能够更简单地与随机数预编译交互。使用随机数消费者能确保完成来自随机数预编译。

RandomnessConsumer.sol
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.3;

/// @dev The Randomness contract's address.
address constant RANDOMNESS_ADDRESS = 0x0000000000000000000000000000000000000809;

/// @dev The Randomness contract's instance.
Randomness constant RANDOMNESS_CONTRACT = Randomness(RANDOMNESS_ADDRESS);

/// @dev Maximum number of random words being requested
uint32 constant MAX_RANDOM_WORDS = 100;
/// @dev Minimum number of blocks before a request can be fulfilled for Local VRF Request
uint32 constant MIN_VRF_BLOCKS_DELAY = 2;
/// @dev Maximum number of blocks before a request can be fulfilled for Local VRF Request
uint32 constant MAX_VRF_BLOCKS_DELAY = 2000;
/// @dev The deposit amount needed to request random words. There is 1 deposit per request
uint256 constant REQUEST_DEPOSIT_AMOUNT = 1000000000000000000;

/// @author The Moonbeam Team
/// @title Pallet Randomness Interface
/// @dev The interface through which solidity contracts will interact with Randomness
/// @custom:address 0x0000000000000000000000000000000000000809
interface Randomness {
    /// @notice Event emitted when the request has been successfully executed
    event FulFillmentSucceeded();
    /// @notice Event emitted when the request has failed to execute fulfillment
    event FulFillmentFailed();

    /// @notice The status of the request
    /// @param DoesNotExist The request doesn't exist
    /// @param Pending The request cannot be fulfilled yet
    /// @param Ready The request is ready to be fulfilled
    /// @param Expired The request has expired
    enum RequestStatus {
        DoesNotExist,
        Pending,
        Ready,
        Expired
    }

    /// @notice The type of randomness source
    /// @param LocalVRF Randomness VRF using the parachain material as seed
    /// @param RelayBabeEpoch Randomness VRF using relay material from previous epoch
    enum RandomnessSource {
        LocalVRF,
        RelayBabeEpoch
    }

    /// @notice The request details
    /// @param id The id of the request (is always < 2**64)
    /// @param refundAddress The address receiving the left-over fees after the fulfillment
    /// @param contractAddress The address of the contract being called back during fulfillment
    /// @param fee The amount to set aside to pay for the fulfillment
    /// @param gasLimit The gas limit to use for the fulfillment
    /// @param salt A string being mixed with the randomness seed to obtain different random words. This should be as unique as possible; using the same salt will lead to same randomness result.
    /// @param numWords The number of random words requested (from 1 to MAX_RANDOM_WORDS)
    /// @param randomnessSource The type of randomness source used to generate the random words
    /// @param fulfillmentBlock The parachain block number at which the request can be fulfilled (for LocalVRF only)
    /// @param fulfillmentEpochIndex The relay epoch index at which the request can be fulfilled (for RelayBabeEpoch)
    /// @param expirationBlock The parachain block number at which the request expires (for LocalVRF only)
    /// @param expirationEpochIndex The relay epoch index at which the request expires (for RelayBabeEpoch)
    /// @param status The current status of the request
    struct Request {
        uint256 id;
        address refundAddress;
        address contractAddress;
        uint256 fee;
        uint256 gasLimit;
        bytes32 salt;
        uint32 numWords;
        RandomnessSource randomnessSource;
        uint32 fulfillmentBlock;
        uint64 fulfillmentEpochIndex;
        uint32 expirationBlock;
        uint64 expirationEpochIndex;
        RequestStatus status;
    }

    /// Return the current relay epoch index
    /// @dev An epoch represents real time and not a block number
    /// @dev Currently, time between epoch changes cannot be longer than:
    /// @dev  - Kusama/Westend/Rococo: 600 relay blocks (1 hour)
    /// @dev  - Polkadot: 2400 relay blocks (4 hours)
    /// @custom:selector 81797566
    function relayEpochIndex() external view returns (uint64);

    /// Return the deposit required to perform a request
    /// @dev Each request will need a deposit.
    /// @custom:selector fb7cfdd7
    function requiredDeposit() external view returns (uint256);

    /// @notice Returns the request status
    /// @param requestId The id of the request to check (must be < 2**64)
    /// @return status Status of the request
    /// @custom:selector d8a4676f
    function getRequestStatus(uint256 requestId)
        external
        view
        returns (RequestStatus status);

    /// @notice Returns the request or revert
    /// @param requestId The id of the request to check (must be < 2**64)
    /// @return request The request
    /// @custom:selector c58343ef
    function getRequest(uint256 requestId)
        external
        view
        returns (Request memory request);

    /// @notice Request random words generated from the parachain VRF
    /// @dev This is using pseudo-random VRF executed by the collator at the fulfillment
    /// @dev Warning:
    /// @dev The collator in charge of producing the block at fulfillment can decide to skip
    /// @dev producing the block in order to have a different random word generated by the next
    /// @dev collator, at the cost of a block reward. It is therefore economically viable to use
    /// @dev this randomness source only if the financial reward at stake is lower than the block
    /// @dev reward.
    /// @dev In order to reduce the risk of a collator being able to predict the random words
    /// @dev when the request is performed, it is possible to increase the delay to multiple blocks
    /// @dev The higher the delay is, the less likely the collator will be able to know which
    /// @dev collator will be in charge of fulfilling the request.
    /// @dev Fulfillment is manual and can be executed by anyone (for free) after the given delay
    /// @param refundAddress The address receiving the left-over fees after the fulfillment
    /// @param fee The amount to set aside to pay for the fulfillment
    /// @param gasLimit The gas limit to use for the fulfillment
    /// @param salt A string being mixed with the randomness seed to obtain different random words
    /// @param numWords The number of random words requested (from 1 to MAX_RANDOM_WORDS)
    /// @param delay The number of blocks until the request can be fulfilled (between MIN_DELAY_BLOCKS and MAX_DELAY_BLOCKS)
    /// @return requestId The id of the request requestLocalVRFRandomWords
    /// @custom:selector 9478430c
    function requestLocalVRFRandomWords(
        address refundAddress,
        uint256 fee,
        uint64 gasLimit,
        bytes32 salt,
        uint8 numWords,
        uint64 delay
    ) external returns (uint256);

    /// @notice Request random words generated from the relaychain Babe consensus
    /// @dev The random words are generated from the hash of the all the VRF provided by the
    /// @dev relaychain validator during 1 epoch.
    /// @dev It requires a delay of at least 1 epoch after the current epoch to be unpredictable
    /// @dev at the time the request is performed.
    /// @dev Warning:
    /// @dev The validator (on the relaychain) of the last block of an epoch can decide to skip
    /// @dev producing the block in order to choose the previous generated epoch random number
    /// @dev at the cost of a relaychain block rewards. It is therefore economically viable to use
    /// @dev this randomness source only if the financial reward at stake is lower than the relaychain
    /// @dev block reward.
    /// @dev (see https://crates.parity.io/pallet_babe/struct.RandomnessFromOneEpochAgo.html)
    /// @dev Fulfillment is manual and can be executed by anyone (for free) at
    /// @dev the beginning of the 2nd relay epoch following the current one
    /// @param refundAddress The address receiving the left-over fees after the fulfillment
    /// @param fee Amount to set aside to pay for the fulfillment. Those fees are taken from the contract
    /// @param gasLimit Gas limit for the fulfillment
    /// @param salt Salt to be mixed with raw randomness to get output
    /// @param numWords Number of random words to be returned (limited to MAX_RANDOM_WORDS)
    /// @return requestId The id of the request
    /// @custom:selector 33c14a63
    function requestRelayBabeEpochRandomWords(
        address refundAddress,
        uint256 fee,
        uint64 gasLimit,
        bytes32 salt,
        uint8 numWords
    ) external returns (uint256);

    /// @dev fulFill the request which will call the contract method "fulfillRandomWords"
    /// @dev Fees of the caller are refunded if the request is fulfillable
    /// @param requestId Request to be fulfilled (must be < 2**64)
    /// @custom:selector 9a91eb0d
    function fulfillRequest(uint256 requestId) external;

    /// @param requestId Request receiving the additional fees (must be < 2**64)
    /// @param feeIncrease Amount to increase
    /// @custom:selector d0408a7f
    function increaseRequestFee(uint256 requestId, uint256 feeIncrease)
        external;

    /// @param requestId Request to be purged (must be < 2**64)
    /// @custom:selector 1d26cbab
    function purgeExpiredRequest(uint256 requestId) external;
}

消费者接口包含以下函数:

  • fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) - 根据给定的请求处理VRF回应,此函数将会通过rawFulfillRandomWords的调用而启动
  • rawFulfillRandomWords(uint256 requestId, uint256[] memory randomWords) - 在随机数预编译的fulfillRequest函数被调用时启动。调用的源头将被验证,确保随机数预编译为调用的源头以及fulfillRandomWords方法确实被调用

请求和完成过程

要启用随机性,您必须有一个执行以下操作的合约:

通过预编译的requestLocalVRFRandomWordsrequestRelayBabeEpochRandomWords方法请求随机数时,将会设置一个用于支付请求完成过程的费用。当使用本地VRF时,为提高不可预测性,制定的延迟时间段(以区块计算)将需要在请求被完成时经过。在最后,延迟的时间段必须大于一个区块。至于BABE Epoch随机数,您不需要制定一个延迟时间段,而可以在当前Epoch的第二个Epoch完成要求。

延迟时间段过后,请求的完成可以由任何人使用fulfillRequest方法以及先前在提交要求时设置的费用完成请求。

在通过预编译的fulfillRequest方法完成随机数请求时,RandomnessConsumer.sol合约中的 rawFulfillRandomWords函数将会被调用,其将会验证传送者为随机预编译。自其, fulfillRandomWords将被调用而请求的随机词数量将由当前区块的随机数结果以及给定的salt计算并得出。如果整个过程是成功的,FulfillmentSucceeded事件将被触发,否则FulfillmentFailed事件将被触发。

至于已完成的请求,执行的费用将会从请求费用退还给fulfillRequest调用者。任何剩余的费用以及要求的保证金将被转移回指定的退还地址。

您合约的fulfillRandomWords回调将负责处理整个完成过程。举例来说,在彩票合约中,回调将会使用随机词选取赢家并支付奖品。

如果一个请求已经过期,它可以通过预编译的purgeExpiredRequest函数删除。当此函数被调用且请求费用已经被支付给调用者,保证金将被退还给原先的请求者。

随机数请求的过程如下所示:

Randomness request happy path diagram

使用随机预编译生成随机数

在接下来的教程中,您将学习如何使用随机数预编译和随机数消费者创建生成随机数的智能合约。如果您只想探索随机数预编译的一些功能,可以跳到使用Remix直接与随机数预编译交互部分。

查看先决条件

跟随本指南,您需要准备以下内容:

创建随机数生成器合约

这一部分创建的合约将包含请求随机数和消费履行随机数请求的结果所需的基本函数。

此合约仅用于演示目的,不可用于生产环境。

此合约将包含以下函数:

  • 构造函数,接受请求随机数所需的保证金
  • 提交随机数请求的函数。在本示例中,随机数来源为本地VRF,但是可以轻松修改合约以使用BABE epoch随机数
  • 通过调用随机数预编译的fulfillRequest函数履行请求的函数。此函数将是payable,因为需要在随机数请求时提交履行费用
  • 消费履行请求结果的函数。此函数的签名必须与随机数消费者合约的fulfillRandomWords函数的签名相匹配

合约如下所示:

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;

import "https://github.com/moonbeam-foundation/moonbeam/blob/master/precompiles/randomness/Randomness.sol";
import {RandomnessConsumer} from "https://github.com/moonbeam-foundation/moonbeam/blob/master/precompiles/randomness/RandomnessConsumer.sol";

contract RandomNumber is RandomnessConsumer {
    // The Randomness Precompile Interface
    Randomness public randomness =
        Randomness(0x0000000000000000000000000000000000000809);

    // Variables required for randomness requests
    uint256 public requiredDeposit = randomness.requiredDeposit();
    uint64 public FULFILLMENT_GAS_LIMIT = 100000;
    // The fee can be set to any value as long as it is enough to cover
    // the fulfillment costs. Any leftover fees will be refunded to the
    // refund address specified in the requestRandomness function below.
    // 150 Gwei should be sufficient for all networks.
    // For Moonbase Alpha and Moonriver, you can specify 5 Gwei 
    uint256 public MIN_FEE = FULFILLMENT_GAS_LIMIT * 150 gwei; 
    uint32 public VRF_BLOCKS_DELAY = MIN_VRF_BLOCKS_DELAY;
    bytes32 public SALT_PREFIX = "INSERT_ANY_STRING_FOR_SALT";

    // Storage variables for the current request
    uint256 public requestId;
    uint256[] public random;

    constructor() payable RandomnessConsumer() {
        // Because this contract can only perform 1 random request at a time,
        // We only need to have 1 required deposit
        require(msg.value >= requiredDeposit);
    }

    function requestRandomness() public payable {
        // Make sure that the value sent is enough
        require(msg.value >= MIN_FEE);
        // Request local VRF randomness
        requestId = randomness.requestLocalVRFRandomWords(
            msg.sender, // Refund address
            msg.value, // Fulfillment fee
            FULFILLMENT_GAS_LIMIT, // Gas limit for the fulfillment
            SALT_PREFIX ^ bytes32(requestId++), // A salt to generate unique results
            1, // Number of random words
            VRF_BLOCKS_DELAY // Delay before request can be fulfilled
        );
    }

    function fulfillRequest() public {
        randomness.fulfillRequest(requestId);
    }

    function fulfillRandomWords(
        uint256, // requestId
        uint256[] memory randomWords
    ) internal override {
        // Save the randomness results
        random = randomWords;
    }
}

如您所见,合约中还有一些常量可以根据需要进行调整,尤其是可用于生成独特结果的SALT_PREFIX

在以下部分中,您将使用Remix部署合约并与其交互。

Remix设置

要添加合约至Remix并遵循本教程操作,您需要在Remix中创建一个名为RandomnessNumber.sol的新文件并将RandomNumber合约粘贴至该文件。

Add the random number generator contract to Remix.

编译部署随机数生成器合约

要在Remix中编译RandomNumber.sol合约,请执行以下步骤:

  1. 点击Compile标签(从上到下第二个)
  2. 点击Compile RandomNumber.sol按钮

成功编译合约后,您将在Compile标签旁边看到一个绿色的勾号。

Compile the random number generator contract in Remix.

现在您可以开始执行以下步骤部署合约:

  1. 点击位于Compile标签正下方的Deploy and Run标签
  2. 确保在ENVIRONMENT下拉菜单中已选择Injected Provider - Metamask。选择Injected Provider - Metamask后,MetaMask将提示您链接账户至Remix
  3. 确保ACCOUNT下方显示是正确的账户
  4. VALUE字段中输入保证金金额,即1000000000000000000 Wei(1 Ether)
  5. 确保在CONTRACT下拉菜单中已选择RandomNumber - RandomNumber.sol
  6. 点击Deploy
  7. 在MetaMask跳出的弹窗中,点击Confirm确认交易

Deploy the random number generator contract in Remix.

RANDOMNUMBER合约将出现在Deployed Contracts列表中。

提交生成随机数的请求

要请求随机数,您需要使用合约的requestRandomness函数,这将要求您按照随机数预编译中的定义提交保证金。您可以通过以下步骤提交随机数请求并支付保证金:

  1. VALUE字段中输入数量用于支付履行费用,该数值需等于或高于RandomNumber合约中指定的最低费用,即15000000 Gwei
  2. 展开RANDOMNUMBER合约
  3. 点击requestRandomness按钮
  4. 在MetaMask中确认交易

Request a random number using the random number generator contract in Remix.

提交交易后,requestId将更新为请求的ID。您可以使用随机数合约的requestId调用来获取请求ID,并使用随机数预编译的getRequestStatus函数来检查此请求ID的状态。

履行请求并保存随机数

提交随机数请求后,您需要等待延迟时间段才能完成请求。对于RandomNumber.sol合约,延迟时间段设置为随机数预编译中定义的最小区块延迟,即2个区块。您必须在延迟时间段结束之前完成请求。对于本地VRF,请求在10000个区块后过期,对于BABE epoch随机数,请求在10000个epoch后过期。

假设您已等待最小区块数(如果您使用的是BABE epoch随机数,则为epoch)通过并且请求尚未过期,您可以通过以下步骤来完成请求:

  1. 点击fulfillRequest按钮
  2. 在MetaMask中确认交易

Fulfill the randomness request using the random number generator contract in Remix.

履行请求后,您可以查看生成的随机数:

  1. 展开random函数
  2. 由于合约只请求一个随机词,您可以通过访问random数组的0索引来获取随机数
  3. 点击call
  4. 随机数将出现在call按钮下方

Retrieve the random number that was generated by the random number contract in Remix.

成功完成后,多余的费用和保证金将发送到指定的退款地址。

如果请求刚好在完成之前过期,您可以直接与随机数预编译交互以清除请求并解锁保证金和费用。有关如何执行此操作的说明,请参考以下部分。

使用Remix直接与随机数预编译交互

除了通过智能合约与随机预编译进行交互外,您还可以在Remix中直接与其交互以执行创建随机请求、检查请求状态和清除过期请求等操作。请记住,您需要有一个从消费者合约继承的合约才能满足请求,因此如果您直接使用预编译来完成请求,则无法使用结果。

Remix设置

要将接口添加至Remix并跟随以下教程步骤,您将需要:

  1. 复制Randomness.sol
  2. 在Remix文件中粘贴文件内容并命名为Randomness.sol

Add precompile to Remix

编译和访问随机数预编译

接着,您将需要在Remix中编译Randomness.sol文件。要开始操作,请确认您已打开Randomness.sol文件并跟随以下步骤:

  1. 点击从上至下的第二个Compile标签
  2. 点击Compile Randomness.sol编译合约

如果合约被成功编译,您将在Compile标签旁看见绿色打勾标志。

您将会根据给定的预编译合约地址访问接口,而非部署随机数预编译:

  1. 在Remix中Compile标签下方点击Deploy and Run标签,请注意预编译合约已部署
  2. 确保在ENVIRONMENT下拉菜单中Injected Provider - MetaMask已选取。当选取时,MetaMask将跳出弹窗要求您将账户连接至Remix
  3. 确保正确的账户在ACCOUNT下方显示
  4. 确保CONTRACT下拉菜单中Randomness - Randomness.sol已被选取。由于此为预编译合约,因此并不需要部署任何代码,我们反而将会在At Address一栏中提供预编译地址
  5. 提供批量预编译的地址0x0000000000000000000000000000000000000809并点击At Address

Access the address

RANDOMNESS预编译将会出现在Deployed Contracts列表中,您将会用其完成后续教程中彩票合约的随机数请求。

获得请求状态和删除过期请求

任何人都可以清除过期的请求。您不需要成为请求随机数者才能够清除它。当您清除过期的请求时,要求费用将转给您,要求的保证金将退还给请求发起者。

要清除请求,首先您必须确保请求已过期。为此,您可以使用预编译的getRequestStatus函数验证请求的状态。此调用返回的数字对应于RequestStatus枚举中值的索引。因此,您需要验证返回的数字是否为3表示Expired

当您验证请求已过期,您可以调用purgeExpiredRequest函数清除此请求。

要验证和清除请求,您可以跟随以下步骤:

  1. 展开RANDOMNESS合约
  2. 输入您希望验证其是否过期的请求ID并点击getRequestStatus
  3. 回应将会出现在函数下方,验证您是否获得3
  4. 展开purgeExpiredRequest函数并输入要求ID
  5. 点击transact
  6. MetaMask将会跳出弹窗,请确认交易

Purge an exired request

交易完成后,您可以通过使用相同的请求ID再次调用getRequestStatus函数来验证请求已被清除。您应该收到0DoesNotExist的状态。您还可以预期请求费用的金额将转入您的帐户。

Last update: January 25, 2024
| Created: September 5, 2022