Accounts
0x08...ee03
0x08...ee03

0x08...ee03

$500
This contract's source code is verified!
Contract Metadata
Compiler
0.8.24+commit.e11b9ed9
Language
Solidity
Contract Source Code
File 1 of 8: AccessControl.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/AccessControl.sol)

pragma solidity ^0.8.20;

import {IAccessControl} from "./IAccessControl.sol";
import {Context} from "../utils/Context.sol";
import {ERC165} from "../utils/introspection/ERC165.sol";

/**
 * @dev Contract module that allows children to implement role-based access
 * control mechanisms. This is a lightweight version that doesn't allow enumerating role
 * members except through off-chain means by accessing the contract event logs. Some
 * applications may benefit from on-chain enumerability, for those cases see
 * {AccessControlEnumerable}.
 *
 * Roles are referred to by their `bytes32` identifier. These should be exposed
 * in the external API and be unique. The best way to achieve this is by
 * using `public constant` hash digests:
 *
 * ```solidity
 * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
 * ```
 *
 * Roles can be used to represent a set of permissions. To restrict access to a
 * function call, use {hasRole}:
 *
 * ```solidity
 * function foo() public {
 *     require(hasRole(MY_ROLE, msg.sender));
 *     ...
 * }
 * ```
 *
 * Roles can be granted and revoked dynamically via the {grantRole} and
 * {revokeRole} functions. Each role has an associated admin role, and only
 * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
 *
 * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
 * that only accounts with this role will be able to grant or revoke other
 * roles. More complex role relationships can be created by using
 * {_setRoleAdmin}.
 *
 * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
 * grant and revoke this role. Extra precautions should be taken to secure
 * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
 * to enforce additional security measures for this role.
 */
abstract contract AccessControl is Context, IAccessControl, ERC165 {
    struct RoleData {
        mapping(address account => bool) hasRole;
        bytes32 adminRole;
    }

    mapping(bytes32 role => RoleData) private _roles;

    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

    /**
     * @dev Modifier that checks that an account has a specific role. Reverts
     * with an {AccessControlUnauthorizedAccount} error including the required role.
     */
    modifier onlyRole(bytes32 role) {
        _checkRole(role);
        _;
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) public view virtual returns (bool) {
        return _roles[role].hasRole[account];
    }

    /**
     * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
     * is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
     */
    function _checkRole(bytes32 role) internal view virtual {
        _checkRole(role, _msgSender());
    }

    /**
     * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
     * is missing `role`.
     */
    function _checkRole(bytes32 role, address account) internal view virtual {
        if (!hasRole(role, account)) {
            revert AccessControlUnauthorizedAccount(account, role);
        }
    }

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
        return _roles[role].adminRole;
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleGranted} event.
     */
    function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
        _grantRole(role, account);
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleRevoked} event.
     */
    function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
        _revokeRole(role, account);
    }

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been revoked `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `callerConfirmation`.
     *
     * May emit a {RoleRevoked} event.
     */
    function renounceRole(bytes32 role, address callerConfirmation) public virtual {
        if (callerConfirmation != _msgSender()) {
            revert AccessControlBadConfirmation();
        }

        _revokeRole(role, callerConfirmation);
    }

    /**
     * @dev Sets `adminRole` as ``role``'s admin role.
     *
     * Emits a {RoleAdminChanged} event.
     */
    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
        bytes32 previousAdminRole = getRoleAdmin(role);
        _roles[role].adminRole = adminRole;
        emit RoleAdminChanged(role, previousAdminRole, adminRole);
    }

    /**
     * @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleGranted} event.
     */
    function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
        if (!hasRole(role, account)) {
            _roles[role].hasRole[account] = true;
            emit RoleGranted(role, account, _msgSender());
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Attempts to revoke `role` to `account` and returns a boolean indicating if `role` was revoked.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleRevoked} event.
     */
    function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
        if (hasRole(role, account)) {
            _roles[role].hasRole[account] = false;
            emit RoleRevoked(role, account, _msgSender());
            return true;
        } else {
            return false;
        }
    }
}
Contract Source Code
File 2 of 8: Bullseye.sol
//  SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {ITreasury} from "./interfaces/ITreasury.sol";
import {IDataStreamsVerifier} from "./interfaces/IDataStreamsVerifier.sol";

contract Bullseye is AccessControl {
    bytes32 public constant GAME_MASTER_ROLE = keccak256("GAME_MASTER_ROLE");
    uint256 constant DENOMINATOR = 10000;
    uint256 public exactRange = 50000;
    uint256 public fee = 1000;
    uint256 public maxPlayers = 100;
    uint256[3][6] public rates = [
        [9000, 1000, 0],
        [10000, 0, 0],
        [7500, 2500, 0],
        [9000, 1000, 0],
        [5000, 3500, 1500],
        [7500, 1500, 1000]
    ];
    event NewTreasury(address newTreasury);
    event NewFee(uint256 newFee);
    event NewExactRange(uint256 newExactRange);
    event BullseyeStart(
        uint256 startTime,
        uint32 stopPredictAt,
        uint32 endTime,
        uint32 depositAmount,
        uint8 feedNumber,
        bytes32 gameId
    );
    event BullseyeNewPlayer(
        address player,
        uint32 assetPrice,
        uint256 depositAmount,
        bytes32 gameId,
        uint256 index
    );
    event BullseyeFinalized(
        address[3] players,
        uint256[3] topIndexes,
        int192 finalPrice,
        bool isExact,
        bytes32 gameId
    );
    event BullseyeCancelled(bytes32 gameId);

    struct GameInfo {
        uint8 feedNumber;
        uint256 startTime;
        uint256 endTime;
        uint256 stopPredictAt;
        uint256 depositAmount;
    }

    struct GuessStruct {
        address player;
        uint256 assetPrice;
        uint256 timestamp;
    }

    uint256[] packedGuessData;

    uint256 packedData;
    bytes32 public currentGameId;
    address public treasury;

    constructor() {
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
    }

    /**
     * Starts bullseye game
     * @param endTime when the game iteration will end
     * @param depositAmount amount to enter the game
     */
    function startGame(
        uint32 endTime,
        uint32 stopPredictAt,
        uint32 depositAmount,
        uint8 feedNumber
    ) public onlyRole(GAME_MASTER_ROLE) {
        require(packedData == 0, "Finish previous game first");
        require(endTime > block.timestamp, "Wrong ending time");
        packedData = (block.timestamp |
            (uint256(stopPredictAt) << 32) |
            (uint256(endTime) << 64) |
            (uint256(feedNumber) << 96) |
            (uint256(depositAmount) << 104));
        currentGameId = keccak256(
            abi.encodePacked(endTime, block.timestamp, address(this))
        );
        emit BullseyeStart(
            block.timestamp,
            stopPredictAt,
            endTime,
            depositAmount,
            feedNumber,
            currentGameId
        );
    }

    /**
     * Participate in bullseye game and deposit funds
     * @param assetPrice player's picked asset price
     */
    function play(uint32 assetPrice) public {
        GameInfo memory game = decodeData();
        require(
            packedGuessData.length + 1 <= maxPlayers,
            "Max player amount reached"
        );
        require(
            game.stopPredictAt >= block.timestamp,
            "Game is closed for new players"
        );
        uint256 packedGuess = uint256(uint160(msg.sender)) |
            (block.timestamp << 160) |
            (uint256(assetPrice) << 192);
        packedGuessData.push(packedGuess);
        ITreasury(treasury).depositAndLock(game.depositAmount, msg.sender);
        emit BullseyeNewPlayer(
            msg.sender,
            assetPrice,
            game.depositAmount,
            currentGameId,
            packedGuessData.length - 1
        );
    }

    /**
     * Participate in bullseye game with deposited funds
     * @param assetPrice player's picked asset price
     */
    function playWithDeposit(uint32 assetPrice) public {
        GameInfo memory game = decodeData();
        require(
            packedGuessData.length + 1 <= maxPlayers,
            "Max player amount reached"
        );
        require(
            game.stopPredictAt >= block.timestamp,
            "Game is closed for new players"
        );
        uint256 packedGuess = uint256(uint160(msg.sender)) |
            (block.timestamp << 160) |
            (uint256(assetPrice) << 192);
        packedGuessData.push(packedGuess);
        ITreasury(treasury).lock(game.depositAmount, msg.sender);
        emit BullseyeNewPlayer(
            msg.sender,
            assetPrice,
            game.depositAmount,
            currentGameId,
            packedGuessData.length - 1
        );
    }

    /**
     * Participate in bullseye game and deposit funds with permit
     * @param assetPrice player's picked asset price
     */
    function playWithPermit(
        uint32 assetPrice,
        ITreasury.PermitData calldata permitData
    ) public {
        GameInfo memory game = decodeData();
        require(
            packedGuessData.length + 1 <= maxPlayers,
            "Max player amount reached"
        );
        require(
            game.stopPredictAt >= block.timestamp,
            "Game is closed for new players"
        );
        uint256 packedGuess = uint256(uint160(msg.sender)) |
            (block.timestamp << 160) |
            (uint256(assetPrice) << 192);
        packedGuessData.push(packedGuess);
        ITreasury(treasury).depositAndLockWithPermit(
            game.depositAmount,
            msg.sender,
            permitData.deadline,
            permitData.v,
            permitData.r,
            permitData.s
        );
        emit BullseyeNewPlayer(
            msg.sender,
            assetPrice,
            game.depositAmount,
            currentGameId,
            packedGuessData.length - 1
        );
    }

    /**
     * Finalizes bullseye game and distributes rewards to players
     * @param unverifiedReport Chainlink DataStreams report
     */
    function finalizeGame(
        bytes memory unverifiedReport
    ) public onlyRole(GAME_MASTER_ROLE) {
        GameInfo memory game = decodeData();
        require(currentGameId != bytes32(0), "Start the game first");
        require(block.timestamp >= game.endTime, "Too early to finish");
        if (packedGuessData.length < 2) {
            if (packedGuessData.length == 1) {
                GuessStruct memory playerGuessData = decodeGuess(0);
                emit BullseyeCancelled(currentGameId);
                ITreasury(treasury).refund(
                    game.depositAmount,
                    playerGuessData.player
                );
                delete packedGuessData;
            }
            packedData = 0;
            currentGameId = bytes32(0);
            return;
        }

        address upkeep = ITreasury(treasury).upkeep();
        (int192 finalPrice, uint32 priceTimestamp) = IDataStreamsVerifier(
            upkeep
        ).verifyReportWithTimestamp(unverifiedReport, game.feedNumber);
        finalPrice /= 1e14;
        require(
            priceTimestamp - game.endTime <= 1 minutes ||
                block.timestamp - priceTimestamp <= 1 minutes,
            "Old chainlink report"
        );
        if (packedGuessData.length == 2) {
            GuessStruct memory playerOneGuessData = decodeGuess(0);

            GuessStruct memory playerTwoGuessData = decodeGuess(1);
            uint256 playerOneDiff = playerOneGuessData.assetPrice >
                uint192(finalPrice)
                ? playerOneGuessData.assetPrice - uint192(finalPrice)
                : uint192(finalPrice) - playerOneGuessData.assetPrice;
            uint256 playerTwoDiff = playerTwoGuessData.assetPrice >
                uint192(finalPrice)
                ? playerTwoGuessData.assetPrice - uint192(finalPrice)
                : uint192(finalPrice) - playerTwoGuessData.assetPrice;
            if (playerOneDiff < playerTwoDiff) {
                // player 1 closer
                if (playerOneDiff > exactRange) {
                    uint256 wonAmountFirst = (2 *
                        game.depositAmount *
                        10 **
                            IERC20(ITreasury(treasury).approvedToken())
                                .decimals() *
                        rates[0][0]) / DENOMINATOR;
                    ITreasury(treasury).distributeBullseye(
                        wonAmountFirst,
                        playerOneGuessData.player,
                        fee
                    );
                    uint256 wonAmountSecond = 2 *
                        game.depositAmount *
                        10 **
                            IERC20(ITreasury(treasury).approvedToken())
                                .decimals() -
                        wonAmountFirst;
                    ITreasury(treasury).distributeBullseye(
                        wonAmountSecond,
                        playerTwoGuessData.player,
                        fee
                    );
                } else {
                    ITreasury(treasury).distributeBullseye(
                        2 *
                            game.depositAmount *
                            10 **
                                IERC20(ITreasury(treasury).approvedToken())
                                    .decimals(),
                        playerOneGuessData.player,
                        fee
                    );
                }
                emit BullseyeFinalized(
                    [
                        playerOneGuessData.player,
                        playerTwoGuessData.player,
                        address(0)
                    ],
                    [uint256(0), uint256(1), uint256(0)],
                    finalPrice,
                    playerOneDiff <= exactRange,
                    currentGameId
                );
            } else {
                // player 2 closer
                if (playerTwoDiff > exactRange) {
                    uint256 wonAmountFirst = (2 *
                        game.depositAmount *
                        10 **
                            IERC20(ITreasury(treasury).approvedToken())
                                .decimals() *
                        rates[0][0]) / DENOMINATOR;
                    ITreasury(treasury).distributeBullseye(
                        wonAmountFirst,
                        playerTwoGuessData.player,
                        fee
                    );
                    uint256 wonAmountSecond = 2 *
                        game.depositAmount *
                        10 **
                            IERC20(ITreasury(treasury).approvedToken())
                                .decimals() -
                        wonAmountFirst;
                    ITreasury(treasury).distributeBullseye(
                        wonAmountSecond,
                        playerOneGuessData.player,
                        fee
                    );
                } else {
                    ITreasury(treasury).distributeBullseye(
                        2 *
                            game.depositAmount *
                            10 **
                                IERC20(ITreasury(treasury).approvedToken())
                                    .decimals(),
                        playerTwoGuessData.player,
                        fee
                    );
                }
                emit BullseyeFinalized(
                    [
                        playerTwoGuessData.player,
                        playerOneGuessData.player,
                        address(0)
                    ],
                    [uint256(1), uint256(0), uint256(0)],
                    finalPrice,
                    playerTwoDiff <= exactRange,
                    currentGameId
                );
            }
        } else {
            uint256[3] memory topIndexes;
            address[3] memory topPlayers;
            uint256[3] memory topTimestamps;
            uint256[3] memory closestDiff = [
                type(uint256).max,
                type(uint256).max,
                type(uint256).max
            ];
            for (uint256 j = 0; j < packedGuessData.length; j++) {
                GuessStruct memory playerGuessData = decodeGuess(j);
                uint256 currentDiff = playerGuessData.assetPrice >
                    uint192(finalPrice)
                    ? playerGuessData.assetPrice - uint192(finalPrice)
                    : uint192(finalPrice) - playerGuessData.assetPrice;
                for (uint256 i = 0; i < 3; i++) {
                    if (currentDiff < closestDiff[i]) {
                        for (uint256 k = 2; k > i; k--) {
                            closestDiff[k] = closestDiff[k - 1];
                            topPlayers[k] = topPlayers[k - 1];
                            topIndexes[k] = topIndexes[k - 1];
                        }
                        closestDiff[i] = currentDiff;
                        topPlayers[i] = playerGuessData.player;
                        topTimestamps[i] = playerGuessData.timestamp;
                        topIndexes[i] = j;
                        break;
                    } else if (
                        //write top timestamps
                        currentDiff == closestDiff[i] &&
                        playerGuessData.timestamp < topTimestamps[i]
                    ) {
                        for (uint256 k = 2; k > i; k--) {
                            closestDiff[k] = closestDiff[k - 1];
                            topPlayers[k] = topPlayers[k - 1];
                            topIndexes[k] = topIndexes[k - 1];
                        }
                        topIndexes[i] = j;
                        topPlayers[i] = playerGuessData.player;
                        break;
                    }
                }
            }
            uint256 totalDeposited = game.depositAmount *
                packedGuessData.length;
            uint256[3] memory wonAmount;
            if (closestDiff[0] <= exactRange) {
                if (packedGuessData.length <= 5) {
                    wonAmount = rates[1];
                } else if (packedGuessData.length <= 10) {
                    wonAmount = rates[3];
                } else {
                    wonAmount = rates[5];
                }
            } else {
                if (packedGuessData.length <= 5) {
                    wonAmount = rates[0];
                } else if (packedGuessData.length <= 10) {
                    wonAmount = rates[2];
                } else {
                    wonAmount = rates[4];
                }
            }
            for (uint256 i = 0; i < 3; i++) {
                if (topPlayers[i] != address(0)) {
                    if (wonAmount[i] != 0) {
                        if (i != 3) {
                            ITreasury(treasury).distributeBullseye(
                                (totalDeposited *
                                    10 **
                                        IERC20(
                                            ITreasury(treasury).approvedToken()
                                        ).decimals() *
                                    wonAmount[i]) / DENOMINATOR,
                                topPlayers[i],
                                fee
                            );
                        } else {
                            ITreasury(treasury).distributeBullseye(
                                totalDeposited *
                                    10 **
                                        IERC20(
                                            ITreasury(treasury).approvedToken()
                                        ).decimals() -
                                    ((totalDeposited *
                                        10 **
                                            IERC20(
                                                ITreasury(treasury)
                                                    .approvedToken()
                                            ).decimals() *
                                        wonAmount[0]) /
                                        DENOMINATOR +
                                        (totalDeposited *
                                            10 **
                                                IERC20(
                                                    ITreasury(treasury)
                                                        .approvedToken()
                                                ).decimals() *
                                            wonAmount[1]) /
                                        DENOMINATOR),
                                topPlayers[i],
                                fee
                            );
                        }
                    }
                }
            }
            emit BullseyeFinalized(
                topPlayers,
                topIndexes,
                finalPrice,
                closestDiff[0] <= exactRange,
                currentGameId
            );
        }
        packedData = 0;
        currentGameId = bytes32(0);
        delete packedGuessData;
    }

    /**
     * Closes game and makes refund
     */
    function closeGame() public onlyRole(GAME_MASTER_ROLE) {
        require(packedData != 0, "Game not started");
        GameInfo memory game = decodeData();
        uint256 deposit = game.depositAmount;
        for (uint i; i < packedGuessData.length; i++) {
            GuessStruct memory playerGuessData = decodeGuess(i);
            ITreasury(treasury).refund(deposit, playerGuessData.player);
        }
        emit BullseyeCancelled(currentGameId);
        packedData = 0;
        currentGameId = bytes32(0);
        delete packedGuessData;
    }

    /**
     * Returns decoded game data
     */
    function decodeData() public view returns (GameInfo memory data) {
        data.startTime = uint256(uint32(packedData));
        data.stopPredictAt = uint256(uint32(packedData >> 32));
        data.endTime = uint256(uint32(packedData >> 64));
        data.feedNumber = uint8(packedData >> 96);
        data.depositAmount = uint256(uint32(packedData >> 104));
    }

    /**
     * Returns decoded guess packed data
     */
    function decodeGuess(
        uint256 index
    ) public view returns (GuessStruct memory data) {
        uint256 guessData = packedGuessData[index];
        data.player = address(uint160(guessData));
        data.timestamp = uint256(uint32(guessData >> 160));
        data.assetPrice = uint256(uint32(guessData >> 192));
    }

    function getTotalPlayers() public view returns (uint256) {
        return packedGuessData.length;
    }

    /**
     * Change maximum players number
     * @param newMax new maximum number
     */
    function setMaxPlayers(uint256 newMax) public onlyRole(DEFAULT_ADMIN_ROLE) {
        maxPlayers = newMax;
    }

    /**
     * Change treasury address
     * @param newTreasury new treasury address
     */
    function setTreasury(
        address newTreasury
    ) public onlyRole(DEFAULT_ADMIN_ROLE) {
        require(newTreasury != address(0), "Zero address");
        treasury = newTreasury;
        emit NewTreasury(newTreasury);
    }

    /**
     * Change exact range
     * @param newRange new exact range
     */
    function setExactRange(
        uint256 newRange
    ) public onlyRole(DEFAULT_ADMIN_ROLE) {
        exactRange = newRange;
        emit NewExactRange(newRange);
    }

    /**
     * Change fee
     * @param newFee new fee in bp
     */
    function setFee(uint256 newFee) public onlyRole(DEFAULT_ADMIN_ROLE) {
        fee = newFee;
        emit NewFee(newFee);
    }

    function getRateIndex(
        uint256 playersCount,
        bool isExact
    ) public pure returns (uint256 index) {
        if (playersCount <= 5) {
            index = isExact ? 1 : 0;
        } else if (playersCount <= 10) {
            index = isExact ? 3 : 2;
        } else {
            index = isExact ? 5 : 4;
        }
    }

    function setRate(
        uint256[3] memory rate,
        uint256 playersCount,
        bool isExact
    ) public onlyRole(DEFAULT_ADMIN_ROLE) {
        rates[getRateIndex(playersCount, isExact)] = rate;
    }
}

interface IERC20 {
    function decimals() external view returns (uint256);
}
Contract Source Code
File 3 of 8: 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;
    }
}
Contract Source Code
File 4 of 8: ERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol)

pragma solidity ^0.8.20;

import {IERC165} from "./IERC165.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}
Contract Source Code
File 5 of 8: IAccessControl.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/IAccessControl.sol)

pragma solidity ^0.8.20;

/**
 * @dev External interface of AccessControl declared to support ERC165 detection.
 */
interface IAccessControl {
    /**
     * @dev The `account` is missing a role.
     */
    error AccessControlUnauthorizedAccount(address account, bytes32 neededRole);

    /**
     * @dev The caller of a function is not the expected one.
     *
     * NOTE: Don't confuse with {AccessControlUnauthorizedAccount}.
     */
    error AccessControlBadConfirmation();

    /**
     * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
     *
     * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
     * {RoleAdminChanged} not being emitted signaling this.
     */
    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);

    /**
     * @dev Emitted when `account` is granted `role`.
     *
     * `sender` is the account that originated the contract call, an admin role
     * bearer except when using {AccessControl-_setupRole}.
     */
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Emitted when `account` is revoked `role`.
     *
     * `sender` is the account that originated the contract call:
     *   - if using `revokeRole`, it is the admin role bearer
     *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
     */
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) external view returns (bool);

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {AccessControl-_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) external view returns (bytes32);

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function grantRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function revokeRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been granted `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `callerConfirmation`.
     */
    function renounceRole(bytes32 role, address callerConfirmation) external;
}
Contract Source Code
File 6 of 8: IDataStreamsVerifier.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface IDataStreamsVerifier {
    function lastRetrievedPrice() external view returns (int192);

    function getPrice() external view returns (int192);

    function verifyReportWithTimestamp(
        bytes memory unverifiedReport,
        uint8 feedNumber
    ) external returns (int192, uint32);
}
Contract Source Code
File 7 of 8: IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
Contract Source Code
File 8 of 8: ITreasury.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface ITreasury {
    struct PermitData {
        uint256 deadline;
        uint8 v;
        bytes32 r;
        bytes32 s;
    }

    function DISTRIBUTOR_ROLE() external view returns (bytes32);

    function grantRole(bytes32 role, address account) external;

    function increaseFee(uint256 amount) external;

    function depositAndLock(uint256 amount, address from) external;

    function depositAndLockWithPermit(
        uint256 amount,
        address from,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    function lock(uint256 amount, address from) external;

    function upkeep() external view returns (address);

    function distribute(uint256 amount, address to, uint256 gameFee) external;

    function distributeBullseye(
        uint256 amount,
        address to,
        uint256 gameFee
    ) external;

    function approvedToken() external returns (address);

    function refund(uint256 amount, address to) external;

    function refundWithFees(
        uint256 amount,
        address to,
        uint256 refundFee
    ) external;

    function distributeWithoutFee(
        uint256 rate,
        address to,
        uint256 usedFee,
        uint256 initialDeposit
    ) external;

    function calculateSetupRate(
        uint256 lostTeamTotal,
        uint256 wonTeamTotal,
        uint256 setupFee,
        address initiator
    ) external returns (uint256, uint256);

    function calculateUpDownRate(
        uint256 lostTeamTotal,
        uint256 wonTeamTotal,
        uint256 updownFee
    ) external returns (uint256 rate);
}
Settings
{
  "compilationTarget": {
    "contracts/Bullseye.sol": "Bullseye"
  },
  "evmVersion": "paris",
  "libraries": {},
  "metadata": {
    "bytecodeHash": "ipfs"
  },
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "remappings": []
}
ABI
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AccessControlBadConfirmation","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"bytes32","name":"neededRole","type":"bytes32"}],"name":"AccessControlUnauthorizedAccount","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"gameId","type":"bytes32"}],"name":"BullseyeCancelled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[3]","name":"players","type":"address[3]"},{"indexed":false,"internalType":"uint256[3]","name":"topIndexes","type":"uint256[3]"},{"indexed":false,"internalType":"int192","name":"finalPrice","type":"int192"},{"indexed":false,"internalType":"bool","name":"isExact","type":"bool"},{"indexed":false,"internalType":"bytes32","name":"gameId","type":"bytes32"}],"name":"BullseyeFinalized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"player","type":"address"},{"indexed":false,"internalType":"uint32","name":"assetPrice","type":"uint32"},{"indexed":false,"internalType":"uint256","name":"depositAmount","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"gameId","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"}],"name":"BullseyeNewPlayer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"startTime","type":"uint256"},{"indexed":false,"internalType":"uint32","name":"stopPredictAt","type":"uint32"},{"indexed":false,"internalType":"uint32","name":"endTime","type":"uint32"},{"indexed":false,"internalType":"uint32","name":"depositAmount","type":"uint32"},{"indexed":false,"internalType":"uint8","name":"feedNumber","type":"uint8"},{"indexed":false,"internalType":"bytes32","name":"gameId","type":"bytes32"}],"name":"BullseyeStart","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newExactRange","type":"uint256"}],"name":"NewExactRange","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newFee","type":"uint256"}],"name":"NewFee","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newTreasury","type":"address"}],"name":"NewTreasury","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GAME_MASTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"closeGame","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"currentGameId","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decodeData","outputs":[{"components":[{"internalType":"uint8","name":"feedNumber","type":"uint8"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"uint256","name":"stopPredictAt","type":"uint256"},{"internalType":"uint256","name":"depositAmount","type":"uint256"}],"internalType":"struct Bullseye.GameInfo","name":"data","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"decodeGuess","outputs":[{"components":[{"internalType":"address","name":"player","type":"address"},{"internalType":"uint256","name":"assetPrice","type":"uint256"},{"internalType":"uint256","name":"timestamp","type":"uint256"}],"internalType":"struct Bullseye.GuessStruct","name":"data","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"exactRange","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"unverifiedReport","type":"bytes"}],"name":"finalizeGame","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"playersCount","type":"uint256"},{"internalType":"bool","name":"isExact","type":"bool"}],"name":"getRateIndex","outputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalPlayers","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxPlayers","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"assetPrice","type":"uint32"}],"name":"play","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"assetPrice","type":"uint32"}],"name":"playWithDeposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"assetPrice","type":"uint32"},{"components":[{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct ITreasury.PermitData","name":"permitData","type":"tuple"}],"name":"playWithPermit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"rates","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"callerConfirmation","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newRange","type":"uint256"}],"name":"setExactRange","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newFee","type":"uint256"}],"name":"setFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newMax","type":"uint256"}],"name":"setMaxPlayers","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[3]","name":"rate","type":"uint256[3]"},{"internalType":"uint256","name":"playersCount","type":"uint256"},{"internalType":"bool","name":"isExact","type":"bool"}],"name":"setRate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newTreasury","type":"address"}],"name":"setTreasury","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"endTime","type":"uint32"},{"internalType":"uint32","name":"stopPredictAt","type":"uint32"},{"internalType":"uint32","name":"depositAmount","type":"uint32"},{"internalType":"uint8","name":"feedNumber","type":"uint8"}],"name":"startGame","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"treasury","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]