账户
0x9a...8c83
0x9a...8c83

0x9a...8c83

$500
此合同的源代码已经过验证!
合同元数据
编译器
0.8.14+commit.80d49f37
语言
Solidity
合同源代码
文件 1 的 16:BoostSlots.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.14;

import "../GameMasterclass/GameMasterclass.sol";
import "../ClaimManager/IClaimManager.sol";
import "../House/IHouse.sol";

/**
    Slots PvH Game
 */
contract Slots is GameMasterclass {

    /** Reel Info */
    uint8[] public reel1;
    uint8[] public reel2;
    uint8[] public reel3;

    /** num to coin in reel */
    mapping ( uint8 => uint8 ) public numToCoinReel1;
    mapping ( uint8 => uint8 ) public numToCoinReel2;
    mapping ( uint8 => uint8 ) public numToCoinReel3;

    // Game Struct
    struct Game {

        /** Player */
        address player;

        /** Amount Bet */
        uint256 betAmount;

        /** Token Being Bet */
        address token;

        /** Total Amount For House */
        uint256 amountForHouse;

        /** Number of spins */
        uint8 numSpins;

        /** Which Boost */
        uint8 whichBoost;

        /** Final Output -- reels */
        uint8[] num0;
        uint8[] num1;
        uint8[] num2;

        /** Payouts */
        uint256 payout;

        /** Whether or not the game has ended and the VRF has called back */
        bool hasEnded;
    }

    /** Boost Structure */
    struct Boost {
        uint8 boostOdds;
        uint16 payoutReduction;
    }
    mapping ( uint8 => Boost ) public boosts;

    // mapping from GameID => Game
    mapping ( uint256 => Game ) public games;

    // request ID => GameID
    mapping ( uint256 => uint256 ) private requestToGame;

    /** Platform Fee Taken Out Of Buy In */
    uint256 public platformFee = 100;
    uint256 public boostFee = 100;

    // boost multiplier
    uint8 public boostGasMultiplier = 180;

    /** Fee Denominator */
    uint256 private constant FEE_DENOM = 10_000;

    /** Maps three random numbers to a payout */
    mapping ( uint8 => mapping ( uint8 => mapping ( uint8 => uint256 ) ) ) public payout;

    /** Payout Denom */
    uint256 public constant PAYOUT_DENOM = 10_000; // 0.01x precision (payout of 100 = 1%, 10,000 = 1x, 1,000,000 = 100x)

    // gas per spin
    uint32 public base_spin_gas = 640_000;
    uint32 public gas_per_spin  = 60_000;

    // buy in gas per spin
    uint256 public minBuyInGas;
    uint256 public buyInGasPerSpin;
    uint8 public boostMultiplier;

    // min spins
    uint256 public MIN_SPINS = 1;
    uint256 public MAX_SPINS = 30;

    /** Locks changes to odds */
    bool public oddsLocked;

    /// @notice emitted after the platform fee has been changed
    event SetPlatformFee(uint256 newFee);

    /// @notice emitted after a random request has been sent out
    event RandomnessRequested(uint256 gameId);

    /// @notice emitted after a game has been started at a specific table
    event GameStarted(address indexed user, uint256 gameId);

    /// @notice Emitted after the VRF comes back with the index of the winning player
    event GameEnded(
        address indexed user, 
        uint256 gameId, 
        uint256 buyIn,
        uint256 payout
    );

    /// @notice Emitted if the fulfilRandomWords function needs to return out for any reason
    event FulfilRandomFailed(uint256 requestId, uint256 gameId, uint256[] randomWords);

    /// @notice Emitted when the reel data is updated, emits the odds associated with the reel data
    event OddsLocked();

    constructor(

        /** Constructor Arguments For Master Class */
        uint256 GAME_ID_,

        /** History Manager */
        address history_,

        /** Game Configs */
        uint8[] memory reel1_,
        uint8[] memory reel2_,
        uint8[] memory reel3_,

        uint256 minBuyInGas_,
        uint256 buyInGasPerSpin_,

        uint8[] memory boostOdds,
        uint16[] memory payoutReductions

    ) GameMasterclass(GAME_ID_, history_) {

        // set reels
        reel1 = reel1_;
        reel2 = reel2_;
        reel3 = reel3_;
        require(
            reel1_.length == reel2_.length &&
            reel2_.length == reel3_.length,
            'Invalid Reel Length'
        );

        // set gas info
        minBuyInGas     = minBuyInGas_;     // 0.03 ether;  // = 0.001 ether;
        buyInGasPerSpin = buyInGasPerSpin_; // 0.001 ether; // = 0.00005 ether;

        // set boosts
        for (uint8 i = 0; i < boostOdds.length;) {
            boosts[i + 1] = Boost(boostOdds[i], payoutReductions[i]);
            unchecked { ++i; }
        }
        // boosts[1] = Boost(25, 700); // 25% odds, 70% payout
        // boosts[2] = Boost(50, 500); // 51% odds, 50% payout
        // boosts[3] = Boost(75, 378); // 75% odds, 37.8% payout
        // boosts[4] = Boost(100, 290); // 100% odds, 29% payout
    }

    // set min spins
    function setMinSpins(uint256 newMin) external onlyOwner {
        MIN_SPINS = newMin;
    }
    function setMaxSpins(uint256 newMax) external onlyOwner {
        MAX_SPINS = newMax;
    }

    function setBoostGasMultiplier(uint8 newMultiplier) external onlyOwner {
        boostGasMultiplier = newMultiplier;
    }

    function setBoost(uint8 boostId, uint8 boostOdds, uint16 payoutReduction) external onlyOwner {
        require(boostId > 0, 'Invalid Boost ID');
        boosts[boostId] = Boost(boostOdds, payoutReduction);
    }

    function setBuyInGasInfo(uint256 newMin, uint256 newGasPerSpin) external onlyOwner {
        minBuyInGas = newMin;
        buyInGasPerSpin = newGasPerSpin;
    }

    function setVRFGasInfo(uint32 newBase, uint32 newGasPerSpin) external onlyOwner {
        base_spin_gas = newBase;
        gas_per_spin = newGasPerSpin;
    }

    function setPlatformFee(uint256 newPlatform) external onlyOwner {
        require(
            newPlatform <= FEE_DENOM / 10,
            'Cannot Exceed 10%'
        );
        platformFee = newPlatform;
        emit SetPlatformFee(newPlatform);
    }

    function setBoostFee(uint256 newBoost) external onlyOwner {
        require(
            newBoost <= FEE_DENOM / 10,
            'Cannot Exceed 10%'
        );
        boostFee = newBoost;
    }

    function lockOdds() external onlyOwner {
        oddsLocked = true;
        emit OddsLocked();
    }

    function setReels(
        uint8[] calldata reel1_,
        uint8[] calldata reel2_,
        uint8[] calldata reel3_,
        bool[] calldata enforce
    ) external onlyOwner {
        require(
            oddsLocked == false,
            'Odds Are Locked'
        );
        require(
            reel1_.length == reel2_.length &&
            reel2_.length == reel3_.length,
            'Invalid Reel Length'
        );
        reel1 = reel1_;
        reel2 = reel2_;
        reel3 = reel3_;

        if (enforce[0]) {
            enforceReel1();
        }
        if (enforce[1]) {
            enforceReel2();
        }
        if (enforce[2]) {
            enforceReel3();
        }
    }

    function enforceReel1() public onlyOwner {
        // set num to coins
        uint8 coin1 = 0;
        uint8 len = reel1[reel1.length - 1];
        for (uint8 i = 0; i < len;) {
            numToCoinReel1[i] = coin1;
            unchecked { ++i; }
            if (i == reel1[coin1]) {
                unchecked { ++coin1; }
            }
        }
    }

    function enforceReel2() public onlyOwner {
        // set num to coins
        uint8 coin2 = 0;
        uint8 len = reel2[reel2.length - 1];
        for (uint8 i = 0; i < len;) {
            numToCoinReel2[i] = coin2;
            unchecked { ++i; }
            if (i == reel2[coin2]) {
                unchecked { ++coin2; }
            }
        }
    }

    function enforceReel3() public onlyOwner {
        // set num to coins
        uint8 coin3 = 0;
        uint8 len = reel3[reel3.length - 1];
        for (uint8 i = 0; i < len;) {
            numToCoinReel3[i] = coin3;
            unchecked { ++i; }
            if (i == reel3[coin3]) {
                unchecked { ++coin3; }
            }
        }
    }

    function batchSetPayouts(
        uint8[] calldata coins0,
        uint8[] calldata coins1,
        uint8[] calldata coins2,
        uint256[] calldata payoutMultiplier
    ) external onlyOwner {
        require(
            oddsLocked == false,
            'Odds are locked'
        );
        uint len = coins0.length;
        require(
            len == coins1.length,
            'Invalid Length 0-1'
        );
        require(
            len == coins2.length,
            'Invalid Length 0-2'
        );
        require(
            len == payoutMultiplier.length,
            'Invalid Length 0-payout'
        );

        for (uint i = 0; i < len;) {
            // set payout
            payout[coins0[i]][coins1[i]][coins2[i]] = payoutMultiplier[i];
            unchecked { ++i; }
        }
    }

    function _playGame(address player, address token, uint256 amount, bytes calldata gameData) internal override {

        // decode input data
        (
            uint8 numSpins,
            uint8 whichBoost,
            uint256 gameId,
            uint256 partnerId,
            address ref
        ) = abi.decode(gameData, (uint8, uint8, uint256, uint256, address));

        // avoid stack-too-deep errors
        address _token = token;

        // validate inputs
        require(
            numSpins >= MIN_SPINS && numSpins <= MAX_SPINS,
            'Invalid Spin Count'
        );
        require(
            isValidGameId(gameId),
            'Game ID Already Used'
        );
        
        // determine gas required to spin
        uint256 gasRequired = getMinBuyInGas(numSpins, whichBoost > 0);
        require(
            msg.value >= gasRequired,
            'Invalid Ether Sent For BuyIn Gas'
        );

        // calculate the bet amount
        uint256 totalBetAmount = _token == address(0) ? msg.value - gasRequired : amount;
        uint256 betAmountPerSpin = totalBetAmount / numSpins;
        if (whichBoost > 0) {
            require(
                boosts[whichBoost].payoutReduction > 0,
                'Invalid Boost'
            );
        }

        // send fee to chainlink
        TransferHelper.safeTransferETH(manager.chainlinkFeeReceiver(), gasRequired);

        // take platform fee out of the buy in
        uint256 platformFeeAmount = whichBoost == 0 ? 
            ( totalBetAmount * platformFee ) / FEE_DENOM :
            ( totalBetAmount * ( platformFee + boostFee ) ) / FEE_DENOM;

        // send to platform receiver
        _processFee(_token, platformFeeAmount, partnerId, ref);

        // save game data
        games[gameId].player = player;
        games[gameId].betAmount = betAmountPerSpin;
        games[gameId].token = _token;
        games[gameId].amountForHouse = totalBetAmount - platformFeeAmount;
        games[gameId].numSpins = numSpins;
        games[gameId].whichBoost = whichBoost;
        games[gameId].num0 = new uint8[](numSpins);
        games[gameId].num1 = new uint8[](numSpins);
        games[gameId].num2 = new uint8[](numSpins);

        // process points
        _registerBet(player, totalBetAmount, _token, partnerId);

        // fetch random number
        _requestRandom(gameId, numSpins, 2_000_000);

        // add to list of used game ids and history
        _registerGameId(player, gameId);
        
        // emit event
        emit GameStarted(player, gameId);
    }

    function _requestRandom(uint256 gameId, uint8 numSpins, uint32 gas) internal {

        // request random words from RNG contract
        uint256 requestId = IRNG(manager.RNG()).requestRandom(
            gas, // callback gas limit is dependent num of random values & gas used in callback
            uint32(numSpins * uint8(3)) // the number of random results to return
        );

        // require that the requestId is unused
        require(
            requestToGame[requestId] == 0,
            'RequestId In Use'
        );

        // map this request ID to the game it belongs to
        requestToGame[requestId] = gameId;

        // set data in house
        IHouse(getHouse(games[gameId].token)).randomRequested();

        // emit event
        emit RandomnessRequested(gameId);
    }

    /**
        Callback to provide us with randomness
     */
    function fulfillRandomWords(
        uint256 requestId,
        uint256[] calldata randomWords
    ) external override onlyRNG {

        // get game ID from requestId
        uint256 gameId = requestToGame[requestId];
        
        // if faulty ID, remove
        if (
            gameId == 0 || 
            games[gameId].player == address(0) || 
            games[gameId].hasEnded == true
        ) {
            emit FulfilRandomFailed(requestId, gameId, randomWords);
            return;
        }

        // set game has ended
        games[gameId].hasEnded = true;

        // clear storage
        delete requestToGame[requestId];

        // resolve request in house
        IHouse(getHouse(games[gameId].token)).randomRequestResolved();

        // get boost
        uint8 boostNo = games[gameId].whichBoost;
        uint8 boostOdds = boosts[boostNo].boostOdds;

        // fetch the bet amount per spin
        uint256 betAmountPerSpin = boostNo > 0 ?
            ( games[gameId].betAmount * boosts[boostNo].payoutReduction ) / 1_000 : 
            games[gameId].betAmount;

        // total to pay out for the house and total to send the house
        uint256 totalToPayout = 0;

        // fetch number of spins
        uint8 numSpins = games[gameId].numSpins;
        
        // loop through spins
        for (uint i = 0; i < numSpins;) {

            // select randoms
            uint256 rand1 = randomWords[i * 3];
            uint256 rand2 = randomWords[(i * 3) + 1];
            uint256 rand3 = randomWords[(i * 3) + 2];

            // fetch index (coin) of reel array
            uint8 index1 = numToCoinReel1[uint8(rand1 % numOptionsReel1())];//getMinIndexReel1(rand1 % _numOptionsReel1);
            uint8 index2 = numToCoinReel2[uint8(rand2 % numOptionsReel2())];//getMinIndexReel2(rand2 % _numOptionsReel2);
            uint8 index3 = numToCoinReel3[uint8(rand3 % numOptionsReel3())];//getMinIndexReel3(rand3 % _numOptionsReel3);

            if (boostNo > 0) {
                // if boost is active, determine if user gets boost
                if (index1 > 0 && ( (uint256(keccak256(abi.encodePacked(rand1))) % 100) < boostOdds )) {
                    // user gets boost
                    unchecked { --index1; }
                }
                if (index2 > 0 && ( (uint256(keccak256(abi.encodePacked(rand2))) % 100) < boostOdds )) {
                    // user gets boost
                    unchecked { --index2; }
                }
                if (index3 > 0 && ( (uint256(keccak256(abi.encodePacked(rand3))) % 100) < boostOdds )) {
                    // user gets boost
                    unchecked { --index3; }
                }
            }

            // save indexes to state
            games[gameId].num0[i] = index1;
            games[gameId].num1[i] = index2;
            games[gameId].num2[i] = index3;

            // if payout exists, user won this spin
            if (payout[index1][index2][index3] > 0) {

                // increment data, add to total payout
                unchecked {
                    totalToPayout += ( payout[index1][index2][index3] * betAmountPerSpin ) / PAYOUT_DENOM;
                }
            }
            unchecked { ++i; }
        }

        // save payout info
        games[gameId].payout = totalToPayout;

        // handle payouts
        _handlePayout(games[gameId].token, games[gameId].player, totalToPayout, games[gameId].amountForHouse);

        // emit game ended event
        emit GameEnded(games[gameId].player, gameId, games[gameId].betAmount * numSpins, totalToPayout);
    }

    function optionsReel1() external view returns (uint8[] memory) {
        return reel1;
    }

    function optionsReel2() external view returns (uint8[] memory) {
        return reel2;
    }

    function optionsReel3() external view returns (uint8[] memory) {
        return reel3;
    }

    function getMinIndexReel1(uint256 random) public view returns (uint8) {
        uint8 len = uint8(reel1.length);
        uint8 index = len;
        for (uint8 i = 0; i < len;) {
            if (random < reel1[i]) {
                index = i;
                break;
            }
            unchecked { ++i; }
        }
        return index;
    }

    function getMinIndexReel2(uint256 random) public view returns (uint8) {
        uint8 len = uint8(reel2.length);
        uint8 index = len;
        for (uint8 i = 0; i < len;) {
            if (random < reel2[i]) {
                index = i;
                break;
            }
            unchecked { ++i; }
        }
        return index;
    }

    function getMinIndexReel3(uint256 random) public view returns (uint8) {
        uint8 len = uint8(reel3.length);
        uint8 index = len;
        for (uint8 i = 0; i < len;) {
            if (random < reel3[i]) {
                index = i;
                break;
            }
            unchecked { ++i; }
        }
        return index;
    }

    function numOptionsReel1() public view returns (uint8) {
        return reel1[reel1.length - 1];
    }

    function numOptionsReel2() public view returns (uint8) {
        return reel2[reel2.length - 1];
    }

    function numOptionsReel3() public view returns (uint8) {
        return reel3[reel3.length - 1];
    }

    function getPayout(uint8 coin1, uint8 coin2, uint8 coin3) public view returns (uint256) {
        return payout[coin1][coin2][coin3];
    }

    function getMinBuyInGas(uint8 numSpins, bool withBoost) public view returns (uint256) {
        uint256 baseGas = minBuyInGas + ( numSpins * buyInGasPerSpin );
        return withBoost ? ( baseGas * boostGasMultiplier ) / 100 : baseGas;
    }

    function quoteValue(uint256 buyIn, uint8 numSpins, bool withBoost) external view returns (uint256) {
        return ( buyIn * numSpins ) + getMinBuyInGas(numSpins, withBoost);
    }

    function getGameInfo(uint256 gameId) external view returns (
        address player,
        uint256 betAmount,
        uint8 numSpins,
        uint8[] memory num0,
        uint8[] memory num1,
        uint8[] memory num2,
        uint256 totalPayout,
        address token,
        bool hasEnded
    ) {
        player = games[gameId].player;
        betAmount = games[gameId].betAmount;
        numSpins = games[gameId].numSpins;
        num0 = games[gameId].num0;
        num1 = games[gameId].num1;
        num2 = games[gameId].num2;
        totalPayout = games[gameId].payout;
        token = games[gameId].token;
        hasEnded = games[gameId].hasEnded;
    }
}
合同源代码
文件 2 的 16:GameMasterclass.sol
//SPDX-License-Identifier: MIT
pragma solidity 0.8.14;

import "../lib/TransferHelper.sol";
import "../GovernanceManager/PVPOwnable.sol";
import "./IGame.sol";
import "../UserTracker/IUserInfoTracker.sol";
import "../House/IHouseManager.sol";
import "../House/IHouse.sol";
import "../House/ITokenHouse.sol";
import "../lib/IERC20.sol";
import "../ClaimManager/IClaimManager.sol";
import "../History/IHistoryManager.sol";


/**
    Game Master Class, any inheriting game must pass the necessary fields into the constructor
 */
contract GameMasterclass is PVPOwnable, IGame {

    // GAME ID
    uint256 public immutable GAME_ID;

    // History Manager
    IHistoryManager public immutable history;

    /** Game Is Either Paused Or Unpaused */
    bool public paused = false;

    /** List of all used game ids */
    uint256[] public usedGameIds;
    mapping ( uint256 => bool ) public isUsedGameId;

    /**
        Builds The Necessary Components Of Any Game
     */
    constructor(
        uint256 GAME_ID_,
        address history_
    )  {

        // set other variables
        GAME_ID = GAME_ID_;
        history = IHistoryManager(history_);
    }

    //////////////////////////////////////
    ///////    OWNER FUNCTIONS    ////////
    //////////////////////////////////////

    function pause() external onlyOwner {
        paused = true;
    }

    function resume() external onlyOwner {
        paused = false;
    }

    function withdrawETH(address to, uint256 amount) external onlyOwner {
        (bool s,) = payable(to).call{value: amount}("");
        require(s);
    }

    function withdrawToken(address token, uint amount) external onlyOwner {
        TransferHelper.safeTransfer(token, msg.sender, amount);
    }

    function _registerBet(address user, uint256 amount, address token, uint256 partnerId) internal {
        IUserInfoTracker(manager.userInfoTracker()).wagered(user, amount, GAME_ID, token, partnerId);
    }

    function _processFee(address token, uint256 feeAmount, uint256 partnerId, address ref) internal {
        if (token == address(0)) {
            IFeeRecipient(manager.feeReceiver()).takeFee{value: feeAmount}(
                token, 0, partnerId, ref
            );
        } else {
            TransferHelper.safeTransfer(token, manager.feeReceiver(), feeAmount);
            IFeeRecipient(manager.feeReceiver()).takeFee{value: 0}(
                token, feeAmount, partnerId, ref
            );
        }
    }

    function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) external virtual {}

    function play(address player, address token, uint256 amount, bytes calldata gameData) external payable override validGameToken(token) validatePlayer(player) {
        require(
            !paused,
            'Paused'
        );

        // transfer in asset
        if (token != address(0)) {

            // Transfer in, noting amount received
            uint256 before = IERC20(token).balanceOf(address(this));
            TransferHelper.safeTransferFrom(token, msg.sender, address(this), amount);
            uint256 After = IERC20(token).balanceOf(address(this));
            require(
                After > before,
                'Invalid Transfer'
            );

            // get amount received
            uint256 received = After - before;

            // play game
            _playGame(player, token, received, gameData);
        } else {

            // play game
            _playGame(player, token, msg.value, gameData);
        }
    }

    // NEW Play type function that allows the user to specify whether or not they want to claim their winnings or have them auto sent
    // function play(address player, address token, uint256 amount, bool autoClaim, bytes calldata gameData) external payable override validGameToken(token) validatePlayer(player) {
    //     require(
    //         !paused,
    //         'Paused'
    //     );

    //     if (autoClaim) {
    //         require(
    //             tx.origin == player,
    //             'Auto Claim Only For Self, No Contracts Allowed'
    //         );
    //         require(
    //             player.code.length == 0,
    //             'Player is smart contract'
    //         );
    //         // NOTE: Check AGAIN if player.code.length == 0 in fulfillRandomWords -> if it is a contract, it would no longer be under construction at this time.
    //         willAutoClaim[gameId] = true; // map to gameId to fetch in fulfillRandomWords
    //     }

    //     // transfer in asset
    //     if (token != address(0)) {

    //         // Transfer in, noting amount received
    //         uint256 before = IERC20(token).balanceOf(address(this));
    //         TransferHelper.safeTransferFrom(token, msg.sender, address(this), amount);
    //         uint256 After = IERC20(token).balanceOf(address(this));
    //         require(
    //             After > before,
    //             'Invalid Transfer'
    //         );

    //         // get amount received
    //         uint256 received = After - before;

    //         // play game
    //         _playGame(player, token, received, gameData);
    //     } else {

    //         // play game
    //         _playGame(player, token, msg.value, gameData);
    //     }
    // }

    function _playGame(address player, address token, uint256 amount, bytes calldata gameData) internal virtual {}

    function _handlePayout(address token, address player, uint256 totalToPayout, uint256 amountForHouse) internal {

        if (totalToPayout > 0) {
            // there is a payout, we won something

            if (totalToPayout >= amountForHouse) {
                // the user has won more than we are giving to the house
                // this means we need to send them (to claim manager) amountForHouse directly
                // and only request that the house `payout` the difference!

                // determine if we're using ETH or tokens
                if (token == address(0)) {

                    // add to user's claim manager the amount for the house
                    IClaimManager(manager.claimManager()).credit{value: amountForHouse}(
                        GAME_ID,
                        player
                    );

                    // get the difference
                    uint256 remaining = totalToPayout - amountForHouse;
                    if (remaining > 0) {
                        // payout the rest from the house
                        IHouse(manager.house()).payout(GAME_ID, player, remaining);
                    }

                } else {

                    // send the house the amount
                    TransferHelper.safeTransfer(token, player, amountForHouse);

                    // credit the user
                    IClaimManager(manager.claimManager()).creditToken(
                        player,
                        token,
                        GAME_ID,
                        amountForHouse
                    );

                    // get the difference
                    uint256 remaining = totalToPayout - amountForHouse;
                    if (remaining > 0) {

                        // determine the house for this token
                        address _house = getHouse(token);

                        // payout the rest from the house
                        ITokenHouse(_house).payout(GAME_ID, player, remaining);
                    }
                }


                
                
            } else {
                // the user has won less than we are giving to the house
                // pay them out directly in full, send whatever is left over to the house

                if (token == address(0)) {

                    // add to user's claim manager the amount for the house
                    IClaimManager(manager.claimManager()).credit{value: totalToPayout}(
                        GAME_ID,
                        player
                    );

                    // calculate remaining left for the house
                    uint256 remaining = amountForHouse - totalToPayout;

                    // send the remaining to the house
                    IHouse(manager.house()).houseProfit{value: remaining }(GAME_ID);

                } else {

                    // send the house the amount
                    TransferHelper.safeTransfer(token, player, totalToPayout);

                    // credit the user
                    IClaimManager(manager.claimManager()).creditToken(
                        player,
                        token,
                        GAME_ID,
                        totalToPayout
                    );

                    // get the difference
                    uint256 remaining = amountForHouse - totalToPayout;

                    // determine the house for this token
                    address _house = getHouse(token);

                    // send the house the amount
                    TransferHelper.safeTransfer(token, _house, remaining);

                    // payout the rest from the house
                    ITokenHouse(_house).houseProfit(GAME_ID, remaining);
                    
                }
            }
        
        } else {

            // we won nothing, send the house everything
            if (token == address(0)) {
                IHouse(manager.house()).houseProfit{value: amountForHouse }(GAME_ID);
            } else {

                // determine the house for this token
                address _house = getHouse(token);

                // send the house the amount
                TransferHelper.safeTransfer(token, _house, amountForHouse);

                // log house profit
                ITokenHouse(_house).houseProfit(GAME_ID, amountForHouse);
            }
            

        }

    }

    /// @dev logs the gameId in the used list and adds game to player's history
    function _registerGameId(address player, uint256 gameId) internal {

        // set history data
        history.addData(player, GAME_ID, gameId);

        // add to list of used game ids
        usedGameIds.push(gameId);
        isUsedGameId[gameId] = true;
    }

    function getHouse(address token) public view returns (address) {
        if (token == address(0)) {
            return manager.house();
        }
        return IHouseManager(IProjectTokensManager(manager.projectTokens()).houseManager()).houseFor(token);
    }

    function isValidGameId(uint256 gameId) public view returns (bool) {
        return isUsedGameId[gameId] == false && gameId > 0;
    }

    function batchCallIsUsedGameId(uint256[] calldata gameIds) external view returns (bool[] memory isUsed) {
        uint len = gameIds.length;
        isUsed = new bool[](len);
        for (uint i = 0; i < len;) {
            isUsed[i] = isUsedGameId[gameIds[i]];
            unchecked { ++i; }
        }
    }

    function paginateUsedGameIDs(uint256 start, uint256 end) external view returns (uint256[] memory) {
        uint count = 0;
        uint256[] memory ids = new uint256[](end - start);
        for (uint i = start; i < end;) {
            ids[count] = usedGameIds[i];
            unchecked { ++i; ++count; }
        }
        return ids;
    }

    function numUsedGameIDs() external view returns (uint256) {
        return usedGameIds.length;
    }
}
合同源代码
文件 3 的 16:IClaimManager.sol
//SPDX-License-Identifier: MIT
pragma solidity 0.8.14;

interface IClaimManager {
    function credit(
        uint256 GAME_ID,
        address user
    ) external payable;
    function creditToken(
        address user,
        address token,
        uint256 GAME_ID,
        uint256 amount
    ) external;
}
合同源代码
文件 4 的 16:IERC20.sol
//SPDX-License-Identifier: MIT
pragma solidity 0.8.14;

interface IERC20 {

    function totalSupply() external view returns (uint256);
    
    function symbol() external view returns(string memory);
    
    function name() external view returns(string memory);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);
    
    /**
     * @dev Returns the number of decimal places
     */
    function decimals() external view returns (uint8);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}
合同源代码
文件 5 的 16:IFeeRecipient.sol
//SPDX-License-Identifier: MIT
pragma solidity 0.8.14;

interface IFeeRecipient {
    function takeFee(address token, uint256 amount, uint256 partner, address ref) external payable;
}
合同源代码
文件 6 的 16:IGame.sol
//SPDX-License-Identifier: MIT
pragma solidity 0.8.14;

interface IGame {
    /**
        Callback to provide us with randomness
     */
    function fulfillRandomWords(
        uint256 requestId,
        uint256[] calldata randomWords
    ) external;

    function play(address user, address token, uint256 amount, bytes calldata gameData) external payable;
}
合同源代码
文件 7 的 16:IGovernanceManager.sol
//SPDX-License-Identifier: MIT
pragma solidity 0.8.14;

interface IGovernanceManager {
    function RNG() external view returns (address);
    function owner() external view returns (address);
    function chainlinkFeeReceiver() external view returns (address);
    function chainlinkSubscriptionOwner() external view returns (address);
    function referralManager() external view returns (address);
    function projectTokens() external view returns (address);
    function feeReceiver() external view returns (address);
    function claimManager() external view returns (address);
    function house() external view returns (address);
    function isGame(address game) external view returns (bool);
    function userInfoTracker() external view returns (address);
}
合同源代码
文件 8 的 16:IHistoryManager.sol
//SPDX-License-Identifier: MIT
pragma solidity 0.8.14;

interface IHistoryManager {
    function addData(address user, uint256 GAME_ID, uint256 gameId) external;
}
合同源代码
文件 9 的 16:IHouse.sol
//SPDX-License-Identifier: MIT
pragma solidity 0.8.14;

interface IHouse {
    
    /**
        House has profited from game, call this to send value into the house and emit the correct event for SubGraphs
     */
    function houseProfit(uint256 GAME_ID) external payable;

    /**
        Function Games Call to tell the house that a user has won the bet
     */
    function payout(uint256 GAME_ID, address user, uint256 value) external;
    
    /**
        Read function to determine the maximum payout allowed by the house at the current time
     */
    function maxPayout() external view returns (uint256);

    /**
        Randomness has been requested, withdrawals are paused until it is resolved by called `randomRequestResolved()`
     */
    function randomRequested() external;

    /**
        Resolves a random request from chainlink, allowing house users to withdraw
     */
    function randomRequestResolved() external;

}
合同源代码
文件 10 的 16:IHouseManager.sol
//SPDX-License-Identifier: MIT
pragma solidity 0.8.14;

interface IHouseManager {

    function isHouse(address house) external view returns (bool);
    function houseFor(address token) external view returns (address);
    function createHouse(address token) external returns (address house);
}
合同源代码
文件 11 的 16:IProjectTokensManager.sol
//SPDX-License-Identifier: MIT
pragma solidity 0.8.14;

interface IProjectTokensManager {
    function isValidPartner(uint256 partnerNonce) external view returns (bool);
    function getFundReceiver(uint256 partner) external view returns (address);
    function isListedToken(address token) external view returns (bool);
    function getWrapper(address token) external view returns (address);
    function getHouse(address token) external view returns (address);
    function getViewer(address token) external view returns (address);
    function isWrappedAsset(address wrapper) external view returns (bool);
    function canPlayForOthers(address addr) external view returns (bool);
    function wrappedAssetManager() external view returns (address);
    function houseManager() external view returns (address);
}
合同源代码
文件 12 的 16:IRNG.sol
//SPDX-License-Identifier: MIT
pragma solidity 0.8.14;

interface IRNG {
    function requestRandom(uint32 gas, uint32 numResults) external returns (uint256 requestId);
}
合同源代码
文件 13 的 16:ITokenHouse.sol
//SPDX-License-Identifier: MIT
pragma solidity 0.8.14;

interface ITokenHouse {
    function __init__(address _token) external;
    function deposit(address to, uint256 amount) external;

    /**
        House has profited from game, call this to send value into the house and emit the correct event for SubGraphs
     */
    function houseProfit(uint256 GAME_ID, uint256 amount) external;

    /**
        Function Games Call to tell the house that a user has won the bet
     */
    function payout(uint256 GAME_ID, address user, uint256 value) external;
    
    /**
        Read function to determine the maximum payout allowed by the house at the current time
     */
    function maxPayout() external view returns (uint256);

    /**
        Randomness has been requested, withdrawals are paused until it is resolved by called `randomRequestResolved()`
     */
    function randomRequested() external;

    /**
        Resolves a random request from chainlink, allowing house users to withdraw
     */
    function randomRequestResolved() external;
}
合同源代码
文件 14 的 16:IUserInfoTracker.sol
//SPDX-License-Identifier: MIT
pragma solidity 0.8.14;

interface IUserInfoTracker {
    function wagered(address user, uint256 amount, uint256 GAME_ID, address token, uint256 partnerId) external;
    function createViewer(address token) external returns (address viewer);
    function listAllUsers() external view returns (address[] memory);
    function totalWageredForPartner(uint256 partnerId) external view returns (uint256);
}
合同源代码
文件 15 的 16:PVPOwnable.sol
//SPDX-License-Identifier: MIT
pragma solidity 0.8.14;

import "./IFeeRecipient.sol";
import "./IGovernanceManager.sol";
import "../ProjectTokens/IProjectTokensManager.sol";
import "../RNGTracker/IRNG.sol";

contract PVPOwnable {

    // Governance Manager
    IGovernanceManager public constant manager = IGovernanceManager(0x22164a57446aD5dE1DBE831784D8029773941678);

    modifier onlyOwner() {
        require(
            msg.sender == manager.owner(),
            'Only Owner'
        );
        _;
    }

    modifier onlyGame() {
        require(
            manager.isGame(msg.sender),
            'UnAuthorized'
        );
        _;
    }

    modifier onlyRNG() {
        require(
            msg.sender == manager.RNG(),
            'Only RNG Contract'
        );
        _;
    }

    modifier onlyValidToken(address token_) {
        require(
            IProjectTokensManager(manager.projectTokens()).isWrappedAsset(token_),
            'Invalid Token'
        );
        _;
    }

    modifier validGameToken(address token_) {
        require(
            token_ == address(0) || IProjectTokensManager(manager.projectTokens()).isWrappedAsset(token_),
            'Invalid Token'
        );
        _;
    }

    modifier validatePlayer(address player) {
        if (player != msg.sender) {
            require(
                IProjectTokensManager(manager.projectTokens()).canPlayForOthers(msg.sender),
                'UnAuthorized To Play For Others'
            );
        }
        _;
    }
}
合同源代码
文件 16 的 16:TransferHelper.sol
//SPDX-License-Identifier: MIT
pragma solidity 0.8.14;

// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false
library TransferHelper {
    function safeApprove(
        address token,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('approve(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::safeApprove: approve failed'
        );
    }

    function safeTransfer(
        address token,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('transfer(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::safeTransfer: transfer failed'
        );
    }

    function safeTransferFrom(
        address token,
        address from,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::transferFrom: transferFrom failed'
        );
    }

    function safeTransferETH(address to, uint256 value) internal {
        (bool success, ) = to.call{value: value}(new bytes(0));
        require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
    }
}
设置
{
  "compilationTarget": {
    "contracts/Slots/BoostSlots.sol": "Slots"
  },
  "evmVersion": "london",
  "libraries": {},
  "metadata": {
    "bytecodeHash": "ipfs"
  },
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "remappings": []
}
ABI
[{"inputs":[{"internalType":"uint256","name":"GAME_ID_","type":"uint256"},{"internalType":"address","name":"history_","type":"address"},{"internalType":"uint8[]","name":"reel1_","type":"uint8[]"},{"internalType":"uint8[]","name":"reel2_","type":"uint8[]"},{"internalType":"uint8[]","name":"reel3_","type":"uint8[]"},{"internalType":"uint256","name":"minBuyInGas_","type":"uint256"},{"internalType":"uint256","name":"buyInGasPerSpin_","type":"uint256"},{"internalType":"uint8[]","name":"boostOdds","type":"uint8[]"},{"internalType":"uint16[]","name":"payoutReductions","type":"uint16[]"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"uint256[]","name":"randomWords","type":"uint256[]"}],"name":"FulfilRandomFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"gameId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"buyIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"payout","type":"uint256"}],"name":"GameEnded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"gameId","type":"uint256"}],"name":"GameStarted","type":"event"},{"anonymous":false,"inputs":[],"name":"OddsLocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"gameId","type":"uint256"}],"name":"RandomnessRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newFee","type":"uint256"}],"name":"SetPlatformFee","type":"event"},{"inputs":[],"name":"GAME_ID","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_SPINS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_SPINS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAYOUT_DENOM","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"base_spin_gas","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"gameIds","type":"uint256[]"}],"name":"batchCallIsUsedGameId","outputs":[{"internalType":"bool[]","name":"isUsed","type":"bool[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8[]","name":"coins0","type":"uint8[]"},{"internalType":"uint8[]","name":"coins1","type":"uint8[]"},{"internalType":"uint8[]","name":"coins2","type":"uint8[]"},{"internalType":"uint256[]","name":"payoutMultiplier","type":"uint256[]"}],"name":"batchSetPayouts","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"boostFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"boostGasMultiplier","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"boostMultiplier","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"","type":"uint8"}],"name":"boosts","outputs":[{"internalType":"uint8","name":"boostOdds","type":"uint8"},{"internalType":"uint16","name":"payoutReduction","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"buyInGasPerSpin","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"enforceReel1","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"enforceReel2","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"enforceReel3","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"requestId","type":"uint256"},{"internalType":"uint256[]","name":"randomWords","type":"uint256[]"}],"name":"fulfillRandomWords","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"games","outputs":[{"internalType":"address","name":"player","type":"address"},{"internalType":"uint256","name":"betAmount","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountForHouse","type":"uint256"},{"internalType":"uint8","name":"numSpins","type":"uint8"},{"internalType":"uint8","name":"whichBoost","type":"uint8"},{"internalType":"uint256","name":"payout","type":"uint256"},{"internalType":"bool","name":"hasEnded","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gas_per_spin","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"}],"name":"getGameInfo","outputs":[{"internalType":"address","name":"player","type":"address"},{"internalType":"uint256","name":"betAmount","type":"uint256"},{"internalType":"uint8","name":"numSpins","type":"uint8"},{"internalType":"uint8[]","name":"num0","type":"uint8[]"},{"internalType":"uint8[]","name":"num1","type":"uint8[]"},{"internalType":"uint8[]","name":"num2","type":"uint8[]"},{"internalType":"uint256","name":"totalPayout","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"bool","name":"hasEnded","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"getHouse","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"numSpins","type":"uint8"},{"internalType":"bool","name":"withBoost","type":"bool"}],"name":"getMinBuyInGas","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"random","type":"uint256"}],"name":"getMinIndexReel1","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"random","type":"uint256"}],"name":"getMinIndexReel2","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"random","type":"uint256"}],"name":"getMinIndexReel3","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"coin1","type":"uint8"},{"internalType":"uint8","name":"coin2","type":"uint8"},{"internalType":"uint8","name":"coin3","type":"uint8"}],"name":"getPayout","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"history","outputs":[{"internalType":"contract IHistoryManager","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"isUsedGameId","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"gameId","type":"uint256"}],"name":"isValidGameId","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lockOdds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"manager","outputs":[{"internalType":"contract IGovernanceManager","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minBuyInGas","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"numOptionsReel1","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"numOptionsReel2","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"numOptionsReel3","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"","type":"uint8"}],"name":"numToCoinReel1","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"","type":"uint8"}],"name":"numToCoinReel2","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"","type":"uint8"}],"name":"numToCoinReel3","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"numUsedGameIDs","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oddsLocked","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"optionsReel1","outputs":[{"internalType":"uint8[]","name":"","type":"uint8[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"optionsReel2","outputs":[{"internalType":"uint8[]","name":"","type":"uint8[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"optionsReel3","outputs":[{"internalType":"uint8[]","name":"","type":"uint8[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"start","type":"uint256"},{"internalType":"uint256","name":"end","type":"uint256"}],"name":"paginateUsedGameIDs","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"","type":"uint8"},{"internalType":"uint8","name":"","type":"uint8"},{"internalType":"uint8","name":"","type":"uint8"}],"name":"payout","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"platformFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"player","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"gameData","type":"bytes"}],"name":"play","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"buyIn","type":"uint256"},{"internalType":"uint8","name":"numSpins","type":"uint8"},{"internalType":"bool","name":"withBoost","type":"bool"}],"name":"quoteValue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"reel1","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"reel2","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"reel3","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"resume","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"boostId","type":"uint8"},{"internalType":"uint8","name":"boostOdds","type":"uint8"},{"internalType":"uint16","name":"payoutReduction","type":"uint16"}],"name":"setBoost","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newBoost","type":"uint256"}],"name":"setBoostFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"newMultiplier","type":"uint8"}],"name":"setBoostGasMultiplier","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newMin","type":"uint256"},{"internalType":"uint256","name":"newGasPerSpin","type":"uint256"}],"name":"setBuyInGasInfo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newMax","type":"uint256"}],"name":"setMaxSpins","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newMin","type":"uint256"}],"name":"setMinSpins","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newPlatform","type":"uint256"}],"name":"setPlatformFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8[]","name":"reel1_","type":"uint8[]"},{"internalType":"uint8[]","name":"reel2_","type":"uint8[]"},{"internalType":"uint8[]","name":"reel3_","type":"uint8[]"},{"internalType":"bool[]","name":"enforce","type":"bool[]"}],"name":"setReels","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"newBase","type":"uint32"},{"internalType":"uint32","name":"newGasPerSpin","type":"uint32"}],"name":"setVRFGasInfo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"usedGameIds","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawToken","outputs":[],"stateMutability":"nonpayable","type":"function"}]