Blockchain / Global / Address
Contract
0xD8f7DF0aBD834eDcb3e4FC83A11175eB77449f3B
- Balance
- 0 SOMI
- Transactions sent
- 1
- Type
- Contract · 12,543 bytes of code
Booloot Verified
- Compiler
- solc 0.8.30+commit.73712a01
- Language
- Solidity
- Optimization
- No
- EVM version
- —
- License
- —
- Verified
- 2026-10-12
Booloot.sol
and 10 more files
Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
// Chainlink VRF V2.5 Imports (Direct Funding, Native Token Payment)
import {ConfirmedOwner} from "@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol";
import {VRFV2PlusWrapperConsumerBase} from "@chainlink/contracts/src/v0.8/vrf/dev/VRFV2PlusWrapperConsumerBase.sol"; // For Direct Funding
import {VRFV2PlusClient} from "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol";
/**
* @title Booloot Game Contract - Secure VRF Only
* @author Aratta Labs
* @notice A mystery box game utilizing Chainlink VRF for secure, verifiable randomness. Winnings are stored for players to claim manually.
* @dev The contract exclusively uses the secure 'play' function. The 'rng' function remains for potential off-chain testing/simulation but is not used internally for prize selection.
* @custom:version 9
* @custom:emoji 📊
* @custom:security-contact atenyun@gmail.com
*/
contract Booloot is Pausable, ReentrancyGuard, VRFV2PlusWrapperConsumerBase, ConfirmedOwner {
// =============================
// Custom Errors
// =============================
/// @dev Thrown when the payment sent is less than the required game fee plus VRF fee.
error InsufficientPayment(uint256 providedAmount, uint256 requiredFee);
/// @dev Thrown if an internal error occurs when trying to select a prize index. (Legacy error)
error PrizeNotFound(uint256 attempts);
/// @dev Thrown when a VRF request is made while a previous one is still pending fulfillment.
error RequestAlreadyPending();
/// @dev Thrown if the VRF fulfillment callback returns an unexpected Request ID.
error VRFRequestMismatch(uint256 providedRequestId);
/// @dev Thrown if the contract's current ETH balance cannot cover the maximum possible prize plus the game fee.
error InsufficientContractBalance(uint256 required, uint256 current);
// =============================
// Events
// =============================
/// @notice Emitted when a player successfully wins and receives a prize.
/// @param player The address of the player who won.
/// @param prizeAmount The amount of the prize won in wei.
/// @param timestamp The time the event occurred.
event Win(address indexed player, uint256 prizeAmount, uint256 timestamp);
/// @notice Emitted when the owner successfully withdraws funds from the contract.
/// @param recipient The address that received the withdrawn funds (contract owner).
/// @param amount The amount of ETH withdrawn in wei.
/// @param timestamp The time the event occurred.
event Withdrawal(address indexed recipient, uint256 amount, uint256 timestamp);
/// @notice Emitted when the contract owner updates the available prize pool array.
/// @param newPrizes The array of new prize amounts.
/// @param timestamp The time the event occurred.
event PrizesUpdated(uint256[] newPrizes, uint256 timestamp);
// VRF Events
/// @notice Emitted immediately after a VRF request is successfully submitted to the Chainlink network.
/// @param requestId The unique ID assigned to the randomness request.
/// @param requester The address of the player who paid for and initiated the request.
/// @param paidAmount The exact amount of native token (STT) paid to the VRF Wrapper.
event VRFRandomnessRequested(uint256 indexed requestId, address indexed requester, uint256 paidAmount);
/// @notice Emitted when the VRF callback successfully returns the random number and the player is paid.
/// @param requestId The unique ID of the request that was fulfilled.
/// @param prizeAmount The final prize amount awarded to the player.
event VRFRandomnessFulfilled(uint256 indexed requestId, uint256 prizeAmount);
// =============================
// State Variables
// =============================
/// @notice The fixed maximum prize amount (4 ETH), used for ensuring sufficient contract reserve before a game starts.
uint256 public constant MAX_PRIZE_RESERVE_ETH = 4 ether;
/// @notice The fixed fee (in wei) required from the player to play a single round of the game.
uint256 public fee = 1 ether;
/// @notice An array of fixed prizes available in the game, in wei. This pool is infinitely reusable.
uint256[] public prizes = [
0.1 ether,
0.1 ether,
0.1 ether,
0.15 ether,
0.2 ether,
0.2 ether,
0.25 ether,
0.25 ether,
0.3 ether,
0.4 ether,
0.4 ether,
0.5 ether,
0.5 ether,
0.6 ether,
0.7 ether,
0.8 ether,
0.9 ether,
1.1 ether,
1.2 ether,
1.5 ether,
2 ether,
2.5 ether,
3.1 ether,
3.3 ether,
4 ether
];
/// @notice The total number of unique players who have participated in the game.
uint256 public uniquePlayerCount;
/// @notice A mapping from player addresses to their total accumulated winnings (in wei).
mapping(address => uint256) public wins;
/// @notice A mapping from player addresses to the total number of games they have played.
mapping(address => uint256) public players;
/// @notice An array containing the addresses of all unique players who have played.
address[] public allPlayers;
/// @notice A mapping to quickly check if a player's address is already registered in the `allPlayers` array.
mapping(address => bool) public isPlayerRegistered;
/// @notice A mapping from player addresses to the total accumulated winnings (prize + refund) they can claim.
mapping(address => uint256) public unclaimedWinnings;
// Structs
/// @notice Data structure to represent a player's statistics for leaderboard purposes.
struct PlayerData {
/// @notice The player's unique wallet address.
address player;
/// @notice The total accumulated winnings (in wei) of the player.
uint256 wins;
/// @notice The total number of times the player has played the game.
uint256 played;
}
// =============================
// Chainlink VRF V2+
// =============================
/// @notice The maximum gas limit the VRF Wrapper can use when calling `fulfillRandomWords`.
uint32 public constant VRF_CALLBACK_GAS_LIMIT = 2_100_000;
/// @notice The number of block confirmations the oracle network waits for before fulfilling the request.
uint16 public constant VRF_REQUEST_CONFIRMATIONS = 3;
/// @notice The number of random words requested from VRF. One word is used for the prize index.
uint32 public constant VRF_NUM_WORDS = 3;
/// @notice The Request ID of the most recent secure VRF randomness request.
uint256 public latestRequestId;
/// @notice Status flag indicating if the latest randomness request has been fulfilled.
bool public fulfilled = true;
/// @notice Maps a VRF request ID to the address of the player who made the request.
mapping(uint256 => address) public requestToPlayer;
/// @notice Maps a VRF request ID to the overpayment refund amount owed to the player.
mapping(uint256 => uint256) public requestRefunds;
/// @notice Stores the latest random word(s) received from the Chainlink VRF fulfillment callback.
uint256[] public latestRandomWord;
// =============================
// Constructor
// =============================
/**
* @notice Initializes the contract with the VRF V2+ Wrapper address.
* @param wrapper The address of the Protofire Chainlink VRF V2+ Wrapper contract.
*/
constructor(address wrapper) payable ConfirmedOwner(msg.sender) VRFV2PlusWrapperConsumerBase(wrapper) {}
// =============================
// Management
// =============================
/**
* @notice Updates the fixed fee required to play the game.
* @dev Only the contract owner can call this function.
* @param _fee The new fee amount in wei.
*/
function updateFee(uint256 _fee) public onlyOwner {
fee = _fee;
}
/**
* @notice Updates the array of fixed prizes for the game.
* @dev Only the contract owner can call this function.
* @param _prizes An array containing the new prize amounts in wei.
*/
function updatePrizes(uint256[] memory _prizes) public onlyOwner nonReentrant {
prizes = _prizes;
emit PrizesUpdated(_prizes, block.timestamp);
}
// =============================
// Views
// =============================
/**
* @notice Retrieves the current price required to pay the Chainlink VRF oracle.
* @dev This should be used to calculate the total required payment (game fee + VRF fee) for `play()`.
* @return The required VRF price in native token (STT in Somnia).
*/
function getVRFRequestPrice() public view returns (uint256) {
return i_vrfV2PlusWrapper.calculateRequestPriceNative(VRF_CALLBACK_GAS_LIMIT, VRF_NUM_WORDS);
}
/**
* @notice Retrieves the entire array of fixed prizes currently available in the pool.
* @return An array containing all prize amounts in wei.
*/
function getAllPrizes() external view returns (uint256[] memory) {
return prizes;
}
/**
* @notice Retrieves a paginated batch of player addresses and their statistics for leaderboard construction.
* @param startIndex The starting index for the batch.
* @param batchSize The number of players to return in the batch.
* @return A memory array containing the PlayerData struct for the requested batch.
*/
function getPlayersBatch(uint256 startIndex, uint256 batchSize) external view returns (PlayerData[] memory) {
uint256 endIndex = startIndex + batchSize;
if (endIndex > allPlayers.length) endIndex = allPlayers.length;
uint256 resultLength = endIndex - startIndex;
PlayerData[] memory batch = new PlayerData[](resultLength);
for (uint256 i = startIndex; i < endIndex; i++) {
batch[i - startIndex] = PlayerData({player: allPlayers[i], wins: wins[allPlayers[i]], played: players[allPlayers[i]]});
}
return batch;
}
/**
* @notice Generates a predictable number based on block data, sender address, and a nonce.
* @dev CRITICAL VULNERABILITY: This function remains for testing/simulation. It is highly susceptible to miner manipulation and front-running. DO NOT USE INTERNALLY FOR PRIZES.
* @param len The upper bound (non-inclusive) for the random number (e.g., prizes.length).
* @param nonce An additional value to prevent simple replay attacks.
* @return The pseudo-random number, which can be manipulated.
*/
function rng(uint256 len, uint256 nonce) public view returns (uint256) {
return uint256(keccak256(abi.encodePacked(block.timestamp, msg.sender, nonce))) % len;
}
/**
* @notice Returns the modulo of a random word by the prizes length. Used for testing the prize selection logic.
* @param randomWords A simulated random word.
* @return The index into the prize array.
*/
function testTest(uint256 randomWords) public view returns (uint256) {
return randomWords % prizes.length;
}
// =============================
// Game Logic (Secure VRF)
// =============================
/**
* @notice Returns the status of the latest secure VRF request.
* @return requestId The unique ID of the last request.
* @return isPending True if the request has been submitted but not yet fulfilled.
* @return isFulfilled True if the last request has successfully been fulfilled.
*/
function getRequestStatus() external view returns (uint256 requestId, bool isPending, bool isFulfilled) {
return (latestRequestId, latestRequestId != 0 && !fulfilled, fulfilled);
}
/**
* @notice Returns the random word(s) from the most recently fulfilled VRF request.
* @dev Requires that a request has already been successfully fulfilled (`fulfilled == true`).
* @return An array containing the random word(s).
*/
function getLatestRandomWord() external view returns (uint256[] memory) {
require(fulfilled, "No fulfilled request yet");
return latestRandomWord;
}
/**
* @notice Requests a secure, verifiable random number from Chainlink VRF.
* @dev Player must pay the game fee PLUS the current VRF fee. This function initiates an asynchronous process. The prize is awarded in `fulfillRandomWords`.
* @return The request ID for tracking the randomness fulfillment.
*/
function play() external payable nonReentrant whenNotPaused returns (uint256) {
address player = _msgSender();
// Enforce financial reserve check
uint256 requiredReserve = MAX_PRIZE_RESERVE_ETH + fee;
if (address(this).balance < requiredReserve) {
revert InsufficientContractBalance(requiredReserve, address(this).balance);
}
// 1. Block overlapping VRF requests
if (latestRequestId != 0 && !fulfilled) revert RequestAlreadyPending();
// 2. Compute total required fee (Game Fee + VRF Fee)
uint256 vrfPrice = getVRFRequestPrice();
uint256 totalRequiredPayment = fee + vrfPrice;
// 3. Check for sufficient payment
if (msg.value < totalRequiredPayment) {
revert InsufficientPayment(msg.value, totalRequiredPayment);
}
// 4. Calculate overpayment refund amount (if any)
uint256 overpaymentRefund = msg.value - totalRequiredPayment;
// 5. Signal native payment to the wrapper
VRFV2PlusClient.ExtraArgsV1 memory extraArgs = VRFV2PlusClient.ExtraArgsV1({nativePayment: true});
bytes memory args = VRFV2PlusClient._argsToBytes(extraArgs);
// 6. Submit request (uses native STT)
(uint256 requestId, uint256 paid) = requestRandomnessPayInNative(VRF_CALLBACK_GAS_LIMIT, VRF_REQUEST_CONFIRMATIONS, VRF_NUM_WORDS, args);
// 7. Update state for request tracking - REFUND STORAGE RESTORED
latestRequestId = requestId;
fulfilled = false;
requestToPlayer[requestId] = player;
requestRefunds[requestId] = overpaymentRefund; // Store refund to be processed after prize is awarded
players[player] += 1;
if (!isPlayerRegistered[player]) {
allPlayers.push(player);
isPlayerRegistered[player] = true;
uniquePlayerCount++;
}
emit VRFRandomnessRequested(requestId, _msgSender(), paid);
return requestId;
}
/**
* @notice Called by the VRF Wrapper after the random word is generated.
* @dev This is the crucial callback function that verifies the VRF result, selects the prize, pays the player, updates stats, and saves winnings for later claiming.
* @dev Note: https://docs.chain.link/vrf/v2-5/security#fulfillrandomwords-must-not-revert:~:text=If%20your%20fulfillRandomWords,Automation%20Node.
* @param requestId The ID of the VRF request that was fulfilled.
* @param randomWords The array of random numbers (must be length of VRF_NUM_WORDS).
*/
function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override {
// 1. Validation (Cleaned up and precise validation restored)
require(randomWords.length == VRF_NUM_WORDS, "Incorrect word count");
require(requestId == latestRequestId, "VRFRequestMismatch");
address player = requestToPlayer[requestId];
uint256 overpaymentRefund = requestRefunds[requestId];
// 2. Select prize using the random word
uint256 prizeIndex = randomWords[0] % prizes.length;
uint256 prizeAmount = prizes[prizeIndex];
// 3. Instead of immediate transfer, add prize and refund to unclaimed winnings
uint256 totalPayout = prizeAmount + overpaymentRefund;
unclaimedWinnings[player] += totalPayout;
// 4. Update player stats and registration
// Note: 'wins' tracks the gross prize amount won, regardless of when it is claimed.
wins[player] += totalPayout;
// 5. Clean up VRF request state - RESTORED
latestRandomWord = randomWords;
// delete requestToPlayer[requestId];
// delete requestRefunds[requestId];
fulfilled = true;
emit Win(player, prizeAmount, block.timestamp);
emit VRFRandomnessFulfilled(requestId, prizes[prizeIndex]);
}
// =============================
// Claiming
// =============================
/**
* @notice Allows a player to withdraw their accumulated unclaimed winnings and overpayment refunds.
* @dev Uses a reentrancy guard. The player's balance is reset to zero before the transfer to prevent reentrancy attacks.
*/
function claimWinnings() public nonReentrant {
uint256 amount = unclaimedWinnings[msg.sender];
require(amount > 0, "No unclaimed winnings to withdraw");
// Set amount to zero BEFORE sending funds
unclaimedWinnings[msg.sender] = 0;
// Send funds to the player
(bool success, ) = payable(msg.sender).call{value: amount}("");
require(success, "Withdrawal failed");
}
/**
* @notice Transfers the entire contract's ETH balance to the contract owner.
* @dev Only the contract owner (`ConfirmedOwner`) can call this function. Uses a reentrancy guard.
*/
function withdrawAll() public onlyOwner nonReentrant {
uint256 amount = address(this).balance;
require(amount > 0, "No balance");
(bool success, ) = payable(owner()).call{value: amount}("");
require(success, "Failed to withdraw");
emit Withdrawal(owner(), amount, block.timestamp);
}
/**
* @notice Pauses the secure game playing operation (`play`).
* @dev Only the contract owner can call this function.
*/
function pausePlaying() public onlyOwner {
_pause();
}
/**
* @notice Unpauses the secure game playing operation (`play`).
* @dev Only the contract owner can call this function.
*/
function unpausePlaying() public onlyOwner {
_unpause();
}
}
Other files
@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {ConfirmedOwnerWithProposal} from "./ConfirmedOwnerWithProposal.sol"; /// @title The ConfirmedOwner contract /// @notice A contract with helpers for basic contract ownership. contract ConfirmedOwner is ConfirmedOwnerWithProposal { constructor( address newOwner ) ConfirmedOwnerWithProposal(newOwner, address(0)) {} }@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwnerWithProposal.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {IOwnable} from "../interfaces/IOwnable.sol"; /// @title The ConfirmedOwner contract /// @notice A contract with helpers for basic contract ownership. contract ConfirmedOwnerWithProposal is IOwnable { address private s_owner; address private s_pendingOwner; event OwnershipTransferRequested(address indexed from, address indexed to); event OwnershipTransferred(address indexed from, address indexed to); constructor(address newOwner, address pendingOwner) { // solhint-disable-next-line gas-custom-errors require(newOwner != address(0), "Cannot set owner to zero"); s_owner = newOwner; if (pendingOwner != address(0)) { _transferOwnership(pendingOwner); } } /// @notice Allows an owner to begin transferring ownership to a new address. function transferOwnership( address to ) public override onlyOwner { _transferOwnership(to); } /// @notice Allows an ownership transfer to be completed by the recipient. function acceptOwnership() external override { // solhint-disable-next-line gas-custom-errors require(msg.sender == s_pendingOwner, "Must be proposed owner"); address oldOwner = s_owner; s_owner = msg.sender; s_pendingOwner = address(0); emit OwnershipTransferred(oldOwner, msg.sender); } /// @notice Get the current owner function owner() public view override returns (address) { return s_owner; } /// @notice validate, transfer ownership, and emit relevant events function _transferOwnership( address to ) private { // solhint-disable-next-line gas-custom-errors require(to != msg.sender, "Cannot transfer to self"); s_pendingOwner = to; emit OwnershipTransferRequested(s_owner, to); } /// @notice validate access function _validateOwnership() internal view { // solhint-disable-next-line gas-custom-errors require(msg.sender == s_owner, "Only callable by owner"); } /// @notice Reverts if called by anyone other than the contract owner. modifier onlyOwner() { _validateOwnership(); _; } }@chainlink/contracts/src/v0.8/shared/interfaces/IOwnable.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IOwnable { function owner() external returns (address); function transferOwnership( address recipient ) external; function acceptOwnership() external; }@chainlink/contracts/src/v0.8/shared/interfaces/LinkTokenInterface.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // solhint-disable-next-line interface-starts-with-i interface LinkTokenInterface { function allowance(address owner, address spender) external view returns (uint256 remaining); function approve(address spender, uint256 value) external returns (bool success); function balanceOf( address owner ) external view returns (uint256 balance); function decimals() external view returns (uint8 decimalPlaces); function decreaseApproval(address spender, uint256 addedValue) external returns (bool success); function increaseApproval(address spender, uint256 subtractedValue) external; function name() external view returns (string memory tokenName); function symbol() external view returns (string memory tokenSymbol); function totalSupply() external view returns (uint256 totalTokensIssued); function transfer(address to, uint256 value) external returns (bool success); function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool success); function transferFrom(address from, address to, uint256 value) external returns (bool success); }@chainlink/contracts/src/v0.8/vrf/dev/VRFV2PlusWrapperConsumerBase.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {LinkTokenInterface} from "../../shared/interfaces/LinkTokenInterface.sol"; import {IVRFV2PlusWrapper} from "./interfaces/IVRFV2PlusWrapper.sol"; /** * * @notice Interface for contracts using VRF randomness through the VRF V2 wrapper * ******************************************************************************** * @dev PURPOSE * * @dev Create VRF V2+ requests without the need for subscription management. Rather than creating * @dev and funding a VRF V2+ subscription, a user can use this wrapper to create one off requests, * @dev paying up front rather than at fulfillment. * * @dev Since the price is determined using the gas price of the request transaction rather than * @dev the fulfillment transaction, the wrapper charges an additional premium on callback gas * @dev usage, in addition to some extra overhead costs associated with the VRFV2Wrapper contract. * ***************************************************************************** * @dev USAGE * * @dev Calling contracts must inherit from VRFV2PlusWrapperConsumerBase. The consumer must be funded * @dev with enough LINK or ether to make the request, otherwise requests will revert. To request randomness, * @dev call the 'requestRandomWords' function with the desired VRF parameters. This function handles * @dev paying for the request based on the current pricing. * * @dev Consumers must implement the fullfillRandomWords function, which will be called during * @dev fulfillment with the randomness result. */ abstract contract VRFV2PlusWrapperConsumerBase { error OnlyVRFWrapperCanFulfill(address have, address want); LinkTokenInterface internal immutable i_linkToken; IVRFV2PlusWrapper public immutable i_vrfV2PlusWrapper; /** * @param _vrfV2PlusWrapper is the address of the VRFV2Wrapper contract */ constructor( address _vrfV2PlusWrapper ) { IVRFV2PlusWrapper vrfV2PlusWrapper = IVRFV2PlusWrapper(_vrfV2PlusWrapper); i_linkToken = LinkTokenInterface(vrfV2PlusWrapper.link()); i_vrfV2PlusWrapper = vrfV2PlusWrapper; } /** * @dev Requests randomness from the VRF V2+ wrapper. * * @param _callbackGasLimit is the gas limit that should be used when calling the consumer's * fulfillRandomWords function. * @param _requestConfirmations is the number of confirmations to wait before fulfilling the * request. A higher number of confirmations increases security by reducing the likelihood * that a chain re-org changes a published randomness outcome. * @param _numWords is the number of random words to request. * * @return requestId is the VRF V2+ request ID of the newly created randomness request. */ // solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore function requestRandomness( uint32 _callbackGasLimit, uint16 _requestConfirmations, uint32 _numWords, bytes memory extraArgs ) internal returns (uint256 requestId, uint256 reqPrice) { reqPrice = i_vrfV2PlusWrapper.calculateRequestPrice(_callbackGasLimit, _numWords); i_linkToken.transferAndCall( address(i_vrfV2PlusWrapper), reqPrice, abi.encode(_callbackGasLimit, _requestConfirmations, _numWords, extraArgs) ); return (i_vrfV2PlusWrapper.lastRequestId(), reqPrice); } // solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore function requestRandomnessPayInNative( uint32 _callbackGasLimit, uint16 _requestConfirmations, uint32 _numWords, bytes memory extraArgs ) internal returns (uint256 requestId, uint256 requestPrice) { requestPrice = i_vrfV2PlusWrapper.calculateRequestPriceNative(_callbackGasLimit, _numWords); return ( i_vrfV2PlusWrapper.requestRandomWordsInNative{value: requestPrice}( _callbackGasLimit, _requestConfirmations, _numWords, extraArgs ), requestPrice ); } /** * @notice fulfillRandomWords handles the VRF V2 wrapper response. The consuming contract must * @notice implement it. * * @param _requestId is the VRF V2 request ID. * @param _randomWords is the randomness result. */ // solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore function fulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) internal virtual; function rawFulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) external { address vrfWrapperAddr = address(i_vrfV2PlusWrapper); if (msg.sender != vrfWrapperAddr) { revert OnlyVRFWrapperCanFulfill(msg.sender, vrfWrapperAddr); } fulfillRandomWords(_requestId, _randomWords); } /// @notice getBalance returns the native balance of the consumer contract function getBalance() public view returns (uint256) { return address(this).balance; } /// @notice getLinkToken returns the link token contract function getLinkToken() public view returns (LinkTokenInterface) { return i_linkToken; } }@chainlink/contracts/src/v0.8/vrf/dev/interfaces/IVRFV2PlusWrapper.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IVRFV2PlusWrapper { /** * @return the request ID of the most recent VRF V2 request made by this wrapper. This should only * be relied option within the same transaction that the request was made. */ function lastRequestId() external view returns (uint256); /** * @notice Calculates the price of a VRF request with the given callbackGasLimit at the current * @notice block. * * @dev This function relies on the transaction gas price which is not automatically set during * @dev simulation. To estimate the price at a specific gas price, use the estimatePrice function. * * @param _callbackGasLimit is the gas limit used to estimate the price. * @param _numWords is the number of words to request. */ function calculateRequestPrice(uint32 _callbackGasLimit, uint32 _numWords) external view returns (uint256); /** * @notice Calculates the price of a VRF request in native with the given callbackGasLimit at the current * @notice block. * * @dev This function relies on the transaction gas price which is not automatically set during * @dev simulation. To estimate the price at a specific gas price, use the estimatePrice function. * * @param _callbackGasLimit is the gas limit used to estimate the price. * @param _numWords is the number of words to request. */ function calculateRequestPriceNative(uint32 _callbackGasLimit, uint32 _numWords) external view returns (uint256); /** * @notice Estimates the price of a VRF request with a specific gas limit and gas price. * * @dev This is a convenience function that can be called in simulation to better understand * @dev pricing. * * @param _callbackGasLimit is the gas limit used to estimate the price. * @param _numWords is the number of words to request. * @param _requestGasPriceWei is the gas price in wei used for the estimation. */ function estimateRequestPrice( uint32 _callbackGasLimit, uint32 _numWords, uint256 _requestGasPriceWei ) external view returns (uint256); /** * @notice Estimates the price of a VRF request in native with a specific gas limit and gas price. * * @dev This is a convenience function that can be called in simulation to better understand * @dev pricing. * * @param _callbackGasLimit is the gas limit used to estimate the price. * @param _numWords is the number of words to request. * @param _requestGasPriceWei is the gas price in wei used for the estimation. */ function estimateRequestPriceNative( uint32 _callbackGasLimit, uint32 _numWords, uint256 _requestGasPriceWei ) external view returns (uint256); /** * @notice Requests randomness from the VRF V2 wrapper, paying in native token. * * @param _callbackGasLimit is the gas limit for the request. * @param _requestConfirmations number of request confirmations to wait before serving a request. * @param _numWords is the number of words to request. */ function requestRandomWordsInNative( uint32 _callbackGasLimit, uint16 _requestConfirmations, uint32 _numWords, bytes calldata extraArgs ) external payable returns (uint256 requestId); function link() external view returns (address); function linkNativeFeed() external view returns (address); }@chainlink/contracts/src/v0.8/vrf/dev/libraries/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); } }@openzeppelin/contracts/utils/Context.sol
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol) pragma solidity ^0.8.20; /** * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract Context { function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { return msg.data; } function _contextSuffixLength() internal view virtual returns (uint256) { return 0; } }@openzeppelin/contracts/utils/Pausable.sol
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol) pragma solidity ^0.8.20; import {Context} from "../utils/Context.sol"; /** * @dev Contract module which allows children to implement an emergency stop * mechanism that can be triggered by an authorized account. * * This module is used through inheritance. It will make available the * modifiers `whenNotPaused` and `whenPaused`, which can be applied to * the functions of your contract. Note that they will not be pausable by * simply including this module, only once the modifiers are put in place. */ abstract contract Pausable is Context { bool private _paused; /** * @dev Emitted when the pause is triggered by `account`. */ event Paused(address account); /** * @dev Emitted when the pause is lifted by `account`. */ event Unpaused(address account); /** * @dev The operation failed because the contract is paused. */ error EnforcedPause(); /** * @dev The operation failed because the contract is not paused. */ error ExpectedPause(); /** * @dev Modifier to make a function callable only when the contract is not paused. * * Requirements: * * - The contract must not be paused. */ modifier whenNotPaused() { _requireNotPaused(); _; } /** * @dev Modifier to make a function callable only when the contract is paused. * * Requirements: * * - The contract must be paused. */ modifier whenPaused() { _requirePaused(); _; } /** * @dev Returns true if the contract is paused, and false otherwise. */ function paused() public view virtual returns (bool) { return _paused; } /** * @dev Throws if the contract is paused. */ function _requireNotPaused() internal view virtual { if (paused()) { revert EnforcedPause(); } } /** * @dev Throws if the contract is not paused. */ function _requirePaused() internal view virtual { if (!paused()) { revert ExpectedPause(); } } /** * @dev Triggers stopped state. * * Requirements: * * - The contract must not be paused. */ function _pause() internal virtual whenNotPaused { _paused = true; emit Paused(_msgSender()); } /** * @dev Returns to normal state. * * Requirements: * * - The contract must be paused. */ function _unpause() internal virtual whenPaused { _paused = false; emit Unpaused(_msgSender()); } }@openzeppelin/contracts/utils/ReentrancyGuard.sol
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol) pragma solidity ^0.8.20; /** * @dev Contract module that helps prevent reentrant calls to a function. * * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier * available, which can be applied to functions to make sure there are no nested * (reentrant) calls to them. * * Note that because there is a single `nonReentrant` guard, functions marked as * `nonReentrant` may not call one another. This can be worked around by making * those functions `private`, and then adding `external` `nonReentrant` entry * points to them. * * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at, * consider using {ReentrancyGuardTransient} instead. * * TIP: If you would like to learn more about reentrancy and alternative ways * to protect against it, check out our blog post * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. */ abstract contract ReentrancyGuard { // Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled. // The values being non-zero value makes deployment a bit more expensive, // but in exchange the refund on every call to nonReentrant will be lower in // amount. Since refunds are capped to a percentage of the total // transaction's gas, it is best to keep them low in cases like this one, to // increase the likelihood of the full refund coming into effect. uint256 private constant NOT_ENTERED = 1; uint256 private constant ENTERED = 2; uint256 private _status; /** * @dev Unauthorized reentrant call. */ error ReentrancyGuardReentrantCall(); constructor() { _status = NOT_ENTERED; } /** * @dev Prevents a contract from calling itself, directly or indirectly. * Calling a `nonReentrant` function from another `nonReentrant` * function is not supported. It is possible to prevent this from happening * by making the `nonReentrant` function external, and making it call a * `private` function that does the actual work. */ modifier nonReentrant() { _nonReentrantBefore(); _; _nonReentrantAfter(); } function _nonReentrantBefore() private { // On the first call to nonReentrant, _status will be NOT_ENTERED if (_status == ENTERED) { revert ReentrancyGuardReentrantCall(); } // Any calls to nonReentrant after this point will fail _status = ENTERED; } function _nonReentrantAfter() private { // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _status = NOT_ENTERED; } /** * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a * `nonReentrant` function in the call stack. */ function _reentrancyGuardEntered() internal view returns (bool) { return _status == ENTERED; } }
ABI · 58 entries
json
[
{
"type": "constructor",
"inputs": [
{
"name": "wrapper",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "payable"
},
{
"name": "EnforcedPause",
"type": "error",
"inputs": []
},
{
"name": "ExpectedPause",
"type": "error",
"inputs": []
},
{
"name": "InsufficientContractBalance",
"type": "error",
"inputs": [
{
"name": "required",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "current",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"name": "InsufficientPayment",
"type": "error",
"inputs": [
{
"name": "providedAmount",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "requiredFee",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"name": "OnlyVRFWrapperCanFulfill",
"type": "error",
"inputs": [
{
"name": "have",
"type": "address",
"internalType": "address"
},
{
"name": "want",
"type": "address",
"internalType": "address"
}
]
},
{
"name": "PrizeNotFound",
"type": "error",
"inputs": [
{
"name": "attempts",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"name": "ReentrancyGuardReentrantCall",
"type": "error",
"inputs": []
},
{
"name": "RequestAlreadyPending",
"type": "error",
"inputs": []
},
{
"name": "VRFRequestMismatch",
"type": "error",
"inputs": [
{
"name": "providedRequestId",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"name": "OwnershipTransferRequested",
"type": "event",
"inputs": [
{
"name": "from",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "to",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "OwnershipTransferred",
"type": "event",
"inputs": [
{
"name": "from",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "to",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "Paused",
"type": "event",
"inputs": [
{
"name": "account",
"type": "address",
"indexed": false,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "PrizesUpdated",
"type": "event",
"inputs": [
{
"name": "newPrizes",
"type": "uint256[]",
"indexed": false,
"internalType": "uint256[]"
},
{
"name": "timestamp",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "Unpaused",
"type": "event",
"inputs": [
{
"name": "account",
"type": "address",
"indexed": false,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "VRFRandomnessFulfilled",
"type": "event",
"inputs": [
{
"name": "requestId",
"type": "uint256",
"indexed": true,
"internalType": "uint256"
},
{
"name": "prizeAmount",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "VRFRandomnessRequested",
"type": "event",
"inputs": [
{
"name": "requestId",
"type": "uint256",
"indexed": true,
"internalType": "uint256"
},
{
"name": "requester",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "paidAmount",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "Win",
"type": "event",
"inputs": [
{
"name": "player",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "prizeAmount",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "timestamp",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "Withdrawal",
"type": "event",
"inputs": [
{
"name": "recipient",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "amount",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "timestamp",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "MAX_PRIZE_RESERVE_ETH",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "VRF_CALLBACK_GAS_LIMIT",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint32",
"internalType": "uint32"
}
],
"stateMutability": "view"
},
{
"name": "VRF_NUM_WORDS",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint32",
"internalType": "uint32"
}
],
"stateMutability": "view"
},
{
"name": "VRF_REQUEST_CONFIRMATIONS",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint16",
"internalType": "uint16"
}
],
"stateMutability": "view"
},
{
"name": "acceptOwnership",
"type": "function",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "allPlayers",
"type": "function",
"inputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{
"name": "claimWinnings",
"type": "function",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "fee",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "fulfilled",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "view"
},
{
"name": "getAllPrizes",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256[]",
"internalType": "uint256[]"
}
],
"stateMutability": "view"
},
{
"name": "getBalance",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "getLatestRandomWord",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256[]",
"internalType": "uint256[]"
}
],
"stateMutability": "view"
},
{
"name": "getLinkToken",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "contract LinkTokenInterface"
}
],
"stateMutability": "view"
},
{
"name": "getPlayersBatch",
"type": "function",
"inputs": [
{
"name": "startIndex",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "batchSize",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "tuple[]",
"components": [
{
"name": "player",
"type": "address",
"internalType": "address"
},
{
"name": "wins",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "played",
"type": "uint256",
"internalType": "uint256"
}
],
"internalType": "struct Booloot.PlayerData[]"
}
],
"stateMutability": "view"
},
{
"name": "getRequestStatus",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "requestId",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "isPending",
"type": "bool",
"internalType": "bool"
},
{
"name": "isFulfilled",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "view"
},
{
"name": "getVRFRequestPrice",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "i_vrfV2PlusWrapper",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "contract IVRFV2PlusWrapper"
}
],
"stateMutability": "view"
},
{
"name": "isPlayerRegistered",
"type": "function",
"inputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "view"
},
{
"name": "latestRandomWord",
"type": "function",
"inputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "latestRequestId",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "owner",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{
"name": "pausePlaying",
"type": "function",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "paused",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "view"
},
{
"name": "play",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "payable"
},
{
"name": "players",
"type": "function",
"inputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "prizes",
"type": "function",
"inputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "rawFulfillRandomWords",
"type": "function",
"inputs": [
{
"name": "_requestId",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "_randomWords",
"type": "uint256[]",
"internalType": "uint256[]"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "requestRefunds",
"type": "function",
"inputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "requestToPlayer",
"type": "function",
"inputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{
"name": "rng",
"type": "function",
"inputs": [
{
"name": "len",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "nonce",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "testTest",
"type": "function",
"inputs": [
{
"name": "randomWords",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "transferOwnership",
"type": "function",
"inputs": [
{
"name": "to",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "unclaimedWinnings",
"type": "function",
"inputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "uniquePlayerCount",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "unpausePlaying",
"type": "function",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "updateFee",
"type": "function",
"inputs": [
{
"name": "_fee",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "updatePrizes",
"type": "function",
"inputs": [
{
"name": "_prizes",
"type": "uint256[]",
"internalType": "uint256[]"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "wins",
"type": "function",
"inputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "withdrawAll",
"type": "function",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
}
]