Band VRF provides deterministic pre-commitments for low entropy inputs, which must resist brute-force pre-image attacks. In addition, the VRF can be used as defense against offline enumeration attacks (such as dictionary attacks) on data stored in hash-based data structures. Therefore, it can be used as a secure source of on-chain randomness. The Band VRF use cases are outlined below.
We separate the use cases into four categories based on the behaviors of the VRF users/consumers.
One-time-use: Consumers only request random data once in their product lifetime, typically when they initiate their contracts.
NFT minting - in the case where a single random seed is used to generate the entire collection.
Batch-use: Consumers request random data multiple times but with a countable number of requests for the entire life of their product.
NFT minting - in the case where every single ID will be minted one by one. Therefore, whenever the end-user tries to mint an NFT, the VRF request is created to resolve the minting. This process will continue until the entire collection is minted.
Interval-use: Consumers set their specific intervals to request random data from our VRF protocol. This process will continue indefinitely since some parts of their products rely on a trusted source of randomness.
Continuous-use: Consumers use randomness as parts of their product with no specific interval. Therefore, they can request random data at any time based on the internal logic of their contracts/system.
Lottery dApps (no predetermined interval, unable to calculate future start-end)
This on-chain lottery dApp is an example of a continuous-use VRF, and it satisfies the requirements below.
Requirements:
Only the owner can set the minimum price and the round's duration.
Only the owner can start a new round.
The owner determined the seed of the started round.
Anyone can buy lotteries during a started round.
Anyone can request to resolve the current round if it has ended.
Only the VRFProvider contract can resolve the resolving round.
Copy
pragmasolidity^0.8.17;interfaceIVRFConsumer{/// @dev The function is called by the VRF provider in order to deliver results to the consumer./// @param seed Any string that used to initialize the randomizer./// @param time Timestamp where the random data was created./// @param result A random bytes for given seed anfd time.functionconsume(stringcalldata seed,uint64 time,bytes32 result
)external;}interfaceIVRFProvider{/// @dev The function for consumers who want random data./// Consumers can simply make requests to get random data back later./// @param seed Any string that used to initialize the randomizer.functionrequestRandomData(stringcalldata seed)externalpayable;}contractSimpleLotteryis IVRFConsumer {eventBuy(address buyer,uint256 buyerIndex,uint256 roundNumber,uint256 buyPrice);eventStartRound(uint256 roundNumber,string seed);eventResolvingRound(uint256 roundNumber,string seed);eventRoundResolved(uint256 roundNumber,string seed,bytes32 result);addresspublic owner;uint256public minLotteryPrice;uint256public roundDuration;uint256public roundCount;boolpublic isResolvingCurrentRound;
IVRFProvider public provider;structRound{uint256 startBlock;uint256 endBlock;string seedOfRound;address[] buyers;}mapping(uint256=> Round)public rounds;constructor(IVRFProvider _provider,uint256 _minLotteryPrice,uint256 _roundDuration){
provider = _provider;
minLotteryPrice = _minLotteryPrice;
roundDuration = _roundDuration;
owner = msg.sender;}functionisCurrentRoundStart()publicviewreturns(bool){return rounds[roundCount].startBlock >0;}functioncurrentRoundBlocksRemaining()publicviewreturns(uint256){
Round memory currentRound = rounds[roundCount];if(block.number > currentRound.endBlock){return0;}return currentRound.endBlock - block.number;}functionsetMinLotteryPrice(uint256 _minLotteryPrice)external{require(msg.sender == owner,"SimpleLottery: not the owner");require(!isCurrentRoundStart(),"SimpleLottery: this round is in progress");
minLotteryPrice = _minLotteryPrice;}functionsetRoundDuration(uint256 _roundDuration)external{require(msg.sender == owner,"SimpleLottery: not the owner");require(!isCurrentRoundStart(),"SimpleLottery: this round is in progress");
roundDuration = _roundDuration;}functionstartANewRound(stringmemory roundSeed)external{require(msg.sender == owner,"SimpleLottery: not the owner");require(!isCurrentRoundStart(),"SimpleLottery: this round is in progress");
Round memory currentRound = rounds[roundCount];
currentRound.seedOfRound = roundSeed;
currentRound.startBlock = block.number;
currentRound.endBlock = currentRound.startBlock + roundDuration;
rounds[roundCount]= currentRound;emitStartRound(roundCount, roundSeed);}functionbuy()externalpayable{require(currentRoundBlocksRemaining()>0,"SimpleLottery: this round is not in progress");require(msg.value >= minLotteryPrice,"SimpleLottery: given price is too low");uint256 currentBuyerIndex = rounds[roundCount].buyers.length;emitBuy(msg.sender, currentBuyerIndex, roundCount, msg.value);
rounds[roundCount].buyers.push(msg.sender);}functionresolveCurrentRound()external{require(isCurrentRoundStart(),"SimpleLottery: this round is not started yet");require(currentRoundBlocksRemaining()==0,"SimpleLottery: this round has not ended yet");require(!isResolvingCurrentRound,"SimpleLottery: round is resolving");
Round memory currentRound = rounds[roundCount];if(currentRound.buyers.length >0){
isResolvingCurrentRound =true;
provider.requestRandomData{value:0}(currentRound.seedOfRound);emitResolvingRound(roundCount, currentRound.seedOfRound);}else{emitRoundResolved(roundCount, currentRound.seedOfRound,bytes32(0));
roundCount +=1;
isResolvingCurrentRound =false;}}functionconsume(stringcalldata seed,uint64 time,bytes32 result)external override {require(msg.sender ==address(provider),"Caller is not the provider");require(isResolvingCurrentRound,"SimpleLottery: round is not resolving");
Round memory currentRound = rounds[roundCount];address winner = currentRound.buyers[uint256(result)% currentRound.buyers.length];emitRoundResolved(roundCount, seed, result);
roundCount +=1;
isResolvingCurrentRound =false;
winner.call{value:address(this).balance}("");}}
This NFT is an example of a batch-use VRF, and it satisfies the requirements below.
Requirements:
The max supply is set once at the time the contract is deployed.
Anyone can call mintWithVRF to start minting an NFT for themself.
An actual minting is done when the VRFprovider resolves the token id for the minter/receiver.
Copy
pragmasolidity^0.8.17;import{ERC721Enumerable}from"@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";/**
* @dev String operations.
*/libraryStrings{bytes16privateconstant _HEX_SYMBOLS ="0123456789abcdef";/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/functiontoString(uint256 value)internalpurereturns(stringmemory){// Inspired by OraclizeAPI's implementation - MIT licence// https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.solif(value ==0){return"0";}uint256 temp = value;uint256 digits;while(temp !=0){
digits++;
temp /=10;}bytesmemory buffer =newbytes(digits);while(value !=0){
digits -=1;
buffer[digits]=bytes1(uint8(48+uint256(value %10)));
value /=10;}returnstring(buffer);}}interfaceIVRFConsumer{/// @dev The function is called by the VRF provider in order to deliver results to the consumer./// @param seed Any string that used to initialize the randomizer./// @param time Timestamp where the random data was created./// @param result A random bytes for given seed anfd time.functionconsume(stringcalldata seed,uint64 time,bytes32 result
)external;}interfaceIVRFProvider{/// @dev The function for consumers who want random data./// Consumers can simply make requests to get random data back later./// @param seed Any string that used to initialize the randomizer.functionrequestRandomData(stringcalldata seed)externalpayable;}contractExampleNFTis ERC721Enumerable, IVRFConsumer {usingStringsforuint256;
IVRFProvider public immutable provider;uint256public immutable maxSupply;uint256public mintRequestCount =0;uint256public mintResolveCount =0;mapping(uint256=>uint256)public tokenMintingLogs;mapping(string=>address)public tokenSeedToMinter;constructor(IVRFProvider _provider,uint256 _maxSupply)ERC721("ExampleNFT","ENFT"){
provider = _provider;
maxSupply = _maxSupply;}functionmintWithVRF()external{require(mintRequestCount < maxSupply,"Reach max supply");stringmemory clientSeed =string(abi.encodePacked("ExampleNFT-", mintRequestCount.toString()));
tokenSeedToMinter[clientSeed]= msg.sender;
mintRequestCount++;
provider.requestRandomData{value:0}(clientSeed);}functionconsume(stringcalldata seed,uint64 time,bytes32 result)external override {require(msg.sender ==address(provider),"Caller is not the provider");address _receiver = tokenSeedToMinter[seed];uint256 index =uint256(result)%(maxSupply - mintResolveCount);uint256 tokenID = tokenMintingLogs[index];if(tokenID ==0){
tokenID = index;}
mintResolveCount++;
tokenMintingLogs[index]= maxSupply - mintResolveCount;_safeMint(_receiver, tokenID);}}