APE Price: $1.15 (+5.77%)

Contract Diff Checker

Contract Name:
TopTrader

Contract Source Code:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.28;

import {IPyth} from "pyth-sdk-solidity/IPyth.sol";
import {PythStructs} from "pyth-sdk-solidity/PythStructs.sol";
import {OwnableRoles} from "solady/auth/OwnableRoles.sol";
import {SignatureCheckerLib} from "solady/utils/SignatureCheckerLib.sol";
import {IRebootTournament} from "./interfaces/IRebootTournament.sol";
import {Direction, Trade, InternalPrice, TournamentConfig} from "./structs/TopTraderStructs.sol";

/**
 * * * * * * * * * * * * * * * * * * * * * * * * *
 * _______  _____   _____                           *
 *    |    |     | |_____]                          *
 *    |    |_____| |                                *
 *                                                  *
 * _______  ______ _______ ______  _______  ______  *
 *    |    |_____/ |_____| |     \ |______ |_____/  *
 *    |    |    \_ |     | |_____/ |______ |    \_  *
 * * * * * * * * * * * * * * * * * * * * * * * * * *
 */
contract TopTrader is OwnableRoles {
    // The price is older than the valid time period.
    error StalePrice();

    // block.timestamp is not within the tournament's start and end time.
    error NotActive();

    // The pair does not match the current active trade.
    error MismatchedPairs();

    // A transfer has reverted.
    error TransferFailed();

    // The amount is 0.
    error InvalidAmount();

    // The user has already entered the tournament.
    error AlreadyEntered();

    // Must provide entry fee in the form of msg.value.
    error InsufficientEntryFee();

    // Prices cannot be finalized while the tournament is active.
    error TournamentActive();

    // The user does not have enough balance to execute the trade.
    error InsufficientBalance(int256 balanceInBips, int256 requiredAmountInBips);

    // The minimum hold duration has not been met.
    error MinimumHoldDurationNotMet();

    // The slippage scaling factor is too high.
    error SlippageTooHigh();

    // The liquidation bounty is too high.
    error LiquidationBountyTooHigh();

    // An address is initialized to the zero address.
    error ZeroAddress();

    // The minimum timestamp has not been met.
    error MinTimestampNotMet();

    // The signature is invalid.
    error InvalidSignature();

    // The nonce has already been used.
    error NonceAlreadyUsed();

    // The caller is not the counterparty signer or the trader.
    error InvalidCaller();

    // The signature has expired.
    error SignatureExpired();

    // Trade function allows price updates of length 1 only.
    error InvalidPriceUpdates();

    // The length of the pairs and prices arrays do not match.
    error ArrayLengthMismatch();

    // The price update publish time is in the future.
    error InvalidTimestamp();

    // Emitted when a new tournament is created.
    event TournamentCreated(
        uint256 indexed tournamentId,
        bytes32[] pairs,
        uint40 startTime,
        uint40 endTime,
        uint128 startBalance,
        uint16 maxLeverage,
        uint80 entryFee,
        uint16 minimumHoldDuration,
        uint64 rebuyLimit,
        uint256 bonusCollateral
    );

    // Emitted when a user is successfully liquidated.
    event Liquidated(uint256 indexed tournamentId, address indexed user);

    // The user does not have a negative (balance + upnl).
    event InvalidLiquidation(uint256 indexed tournamentId, address indexed user, int256 balance, int256 pnl);

    // Emitted when a liquidation bounty is paid.
    event LiquidationBountyPaid(address indexed liquidator, uint256 amount);

    // Emitted when a trade is executed, to provide trade details.
    event TradeExecuted(
        uint256 indexed tournamentId,
        uint256 indexed nonce,
        address indexed user,
        int256 amount,
        int256 executionPrice,
        bytes32 pair
    );

    // Emitted when a trade is executed, to provide new account details.
    event AccountUpdated(
        uint256 indexed tournamentId,
        address indexed user,
        int256 indexed balance,
        int256 amount,
        int256 entryPrice,
        bytes32 pair
    );

    // Emitted when the base slippage scaling factor is updated.
    event BaseSlippageScalingFactorUpdated(uint256 newFactor);

    // Emitted when the slippage scaling factor is updated.
    event SlippageScalingFactorUpdated(uint256 newFactor);

    // Emitted when the liquidation bounty is updated.
    event LiquidationBountyUpdated(uint256 newBounty);

    // Emitted when the counterparty signer is updated.
    event CounterpartySignerUpdated(address newSigner);

    IPyth private immutable _PYTH;
    IRebootTournament private immutable _REBOOT;
    address private immutable _WAPE_CONTRACT_ADDRESS;
    uint64 private constant _FINAL_ENDTIME_GRACE_PERIOD = 5 seconds;
    int256 private constant _BIPS = 10_000;
    int256 private constant _FIRST_SLIPPAGE_BREAKPOINT = 1_000_000;
    int256 private constant _SECOND_SLIPPAGE_BREAKPOINT = 10_000_000;
    uint256 private constant _GLOBAL_CONFIG_MANAGER = _ROLE_42;
    uint256 private constant _TOURNAMENT_MANAGER = _ROLE_69;
    uint256 private constant _PRICE_MANAGER = _ROLE_88;
    uint256 private constant _MAX_BASE_SLIPPAGE_SCALING_FACTOR = 250;
    uint256 private constant _MAX_SLIPPAGE_INCREMENT_SCALING_FACTOR = 10;

    uint256 public constant SIGNATURE_VALIDITY_DURATION = 30 seconds;
    bytes4 private constant _MANUAL_PRICE_PAIR_IDENTIFIER = 0x00420069;

    address public counterpartySigner;
    uint256 public liquidationBounty;
    int256 public baseSlippageScalingFactor;
    int256 public slippageIncrementScalingFactor;

    mapping(uint256 tournamentId => TournamentConfig tournament) public tournaments;
    mapping(bytes32 pair => InternalPrice price) public internalPrices;
    mapping(uint256 nonce => bool isUsed) public usedNonces;

    constructor(
        address pyth,
        address reboot,
        address wape,
        address signer,
        uint256 _liquidationBounty,
        uint256 _baseSlippage,
        uint256 _slippageIncrement
    ) {
        if (pyth == address(0) || reboot == address(0) || wape == address(0)) revert ZeroAddress();

        _initializeOwner(tx.origin);
        _grantRoles(tx.origin, _GLOBAL_CONFIG_MANAGER | _TOURNAMENT_MANAGER);

        _PYTH = IPyth(pyth);
        _REBOOT = IRebootTournament(reboot);
        _WAPE_CONTRACT_ADDRESS = wape;

        liquidationBounty = _liquidationBounty;

        _setCounterpartySigner(signer);
        _setBaseSlippageScalingFactor(_baseSlippage);
        _setSlippageIncrementScalingFactor(_slippageIncrement);
    }

    receive() external payable {}

    /**
     * @notice Creates a new tournament with the given parameters.
     * @param pairs The pairs that are allowed in the tournament (signature enforced).
     * @param startTime The start time of the tournament.
     * @param endTime The end time of the tournament.
     * @param startBalance The starting balance for each user.
     * @param maxLeverage The maximum leverage allowed for each user.
     * @param entryFee The entry fee in $APE for the tournament.
     * @param rebuyLimit The maximum number of rebuys allowed per user, after initial entry.
     * @param bonusCollateral Optional bonus collateral pool.
     */
    function createTournament(
        bytes32[] calldata pairs,
        uint16 minimumHoldDuration,
        uint40 startTime,
        uint40 endTime,
        uint128 startBalance,
        uint16 maxLeverage,
        uint80 entryFee,
        uint64 rebuyLimit,
        uint256 bonusCollateral
    ) external onlyRoles(_TOURNAMENT_MANAGER) returns (uint256 tournamentId) {
        tournamentId = _REBOOT.register(
            IRebootTournament.Config({
                entryFee: entryFee,
                rebuyFee: entryFee,
                isExactFee: true,
                entryLimit: type(uint64).max,
                maxEntriesPerRoom: type(uint64).max,
                rebuyLimit: rebuyLimit,
                startDate: startTime,
                endDate: endTime,
                ticketProfitToTickets: 1 ether,
                creditProfitToTickets: 1 ether,
                creditEntryToTickets: 1 ether,
                entryDuration: endTime - startTime,
                tournamentDuration: 0,
                payoutDuration: 30 days,
                creditRatio: 0,
                creditId: _WAPE_CONTRACT_ADDRESS,
                bonusCollateral: bonusCollateral,
                priceFeedPair: 0x0
            })
        );

        _REBOOT.createRoom(tournamentId, 0, uint64(startTime));

        TournamentConfig storage tournament = tournaments[tournamentId];

        tournament.minimumHoldDuration = minimumHoldDuration;
        tournament.startTime = startTime;
        tournament.endTime = endTime;
        tournament.startBalance = startBalance;
        tournament.maxLeverage = maxLeverage;

        emit TournamentCreated(
            tournamentId,
            pairs,
            startTime,
            endTime,
            startBalance,
            maxLeverage,
            entryFee,
            minimumHoldDuration,
            rebuyLimit,
            bonusCollateral
        );

        return tournamentId;
    }

    /**
     * @notice Enters a user into a tournament.
     * @param tournamentId The ID of the tournament.
     */
    function enterTournament(uint256 tournamentId) external payable {
        _enterTournament(msg.sender, tournamentId);
    }

    /**
     * @notice Enters a user into a tournament
     * @param user The address of the user
     * @param tournamentId The ID of the tournament
     */
    function enterTournamentFor(address user, uint256 tournamentId) external payable {
        _enterTournament(user, tournamentId);
    }

    /**
     * @notice Executes a trade for a user in a tournament.
     * @param tournamentId The ID of the tournament.
     * @param pair The pair to trade. If the pair is not supported by Pyth, it should begin with _MANUAL_PRICE_PAIR_IDENTIFIER.
     * @param priceUpdates The Pyth price update data (empty if pair is not supported by Pyth).
     * @param amountInBips The amount to trade in bips. Positive for long, negative for short.
     * @param trader The address of the trader. If not msg.sender, `signature` must recover to msg.sender.
     * @param minTimestamp The minimum timestamp for the trade.
     * @param nonce The random nonce for the trade.
     * @param signature The signature of the trader, if trader != msg.sender.
     */
    function trade(
        uint256 tournamentId,
        bytes32 pair,
        bytes[] calldata priceUpdates,
        int256 amountInBips,
        address trader,
        uint256 minTimestamp,
        uint256 nonce,
        bytes calldata signature
    ) external {
        uint256 signatureExpirationTimestamp;
        unchecked {
            signatureExpirationTimestamp = minTimestamp + SIGNATURE_VALIDITY_DURATION;
        }

        if (amountInBips == 0) revert InvalidAmount();
        if (block.timestamp < minTimestamp) revert MinTimestampNotMet();
        if (block.timestamp > signatureExpirationTimestamp) revert SignatureExpired();

        TournamentConfig storage tournament = tournaments[tournamentId];
        _assertTournamentOpen(tournament);

        Trade memory currentPosition = tournament.activeTrade[trader];

        bool isReduce = false;

        if (currentPosition.pair != 0x0) {
            if (currentPosition.pair != pair) {
                revert MismatchedPairs();
            } else if (
                // Enforce minimum hold duration if the user has an open position and is reducing or closing it.
                (currentPosition.amountInBips > 0) != (amountInBips > 0)
            ) {
                if (currentPosition.lastUpdatedAt + tournament.minimumHoldDuration > block.timestamp) {
                    revert MinimumHoldDurationNotMet();
                }

                isReduce = true;
            }
        }

        // Prevent signature re-use.
        if (usedNonces[nonce]) revert NonceAlreadyUsed();
        usedNonces[nonce] = true;

        Direction direction = amountInBips > 0 ? Direction.LONG : Direction.SHORT;
        bytes32 hash = SignatureCheckerLib.toEthSignedMessageHash(
            keccak256(abi.encodePacked(trader, tournamentId, pair, direction, minTimestamp, nonce))
        );

        if (msg.sender == trader) {
            // If trader is submitting the trade, ensure counterparty has signed for it.
            if (!SignatureCheckerLib.isValidSignatureNowCalldata(counterpartySigner, hash, signature)) {
                revert InvalidSignature();
            }
        } else if (msg.sender == counterpartySigner) {
            // If counterparty is submitting trade, ensure the trader has signed for it.
            if (!SignatureCheckerLib.isValidSignatureNowCalldata(trader, hash, signature)) {
                revert InvalidSignature();
            }
        } else {
            // In real life this is where a bouncer would break your kneecaps.
            revert InvalidCaller();
        }

        updatePythPriceFeeds(priceUpdates);
        int256 normalizedPrice = _fetchAndNormalizePrice(pair);

        int256 currentUSDNPnlInBips = _currentPnlForPositionAndPrice(currentPosition, normalizedPrice);
        int256 usdnBalance = tournament.userBalance[trader];
        int256 absoluteActivePositionAmountInBips = _abs(currentPosition.amountInBips);

        int256 usdnMaxTradeInBips;
        unchecked {
            // At very large balances, this math will over/underflow and cause unintended side effects.
            // Break the contract. Achieve eternal glory. Bet more.
            int256 usdnTotalAvailableCapital =
                (usdnBalance * _BIPS + currentUSDNPnlInBips) * int256(uint256(tournament.maxLeverage)) / _BIPS;
            int256 absoluteExistingExposure = absoluteActivePositionAmountInBips * normalizedPrice / _BIPS;
            usdnMaxTradeInBips = (
                isReduce
                    ? usdnTotalAvailableCapital + absoluteExistingExposure
                    : usdnTotalAvailableCapital - absoluteExistingExposure
            ) * _BIPS;
        }

        int256 absoluteAmountInBips = _abs(amountInBips);

        // Leave this checked to prevent malicious over/underflow in subsequent calcs.
        int256 tradeSizeInBips = absoluteAmountInBips * normalizedPrice;
        if (tradeSizeInBips > usdnMaxTradeInBips) {
            // Set max trade to 0 if negative, for event clarity.
            if (usdnMaxTradeInBips < 0) usdnMaxTradeInBips = 0;

            revert InsufficientBalance(usdnMaxTradeInBips, tradeSizeInBips);
        }

        int104 openAmount;
        if (isReduce) {
            if (absoluteAmountInBips == absoluteActivePositionAmountInBips) {
                // Position is being closed. Should never be used. Liquidation or glory.
                unchecked {
                    usdnBalance += (currentUSDNPnlInBips / _BIPS);
                }

                // Apply profits and close position.
                tournament.userBalance[trader] = usdnBalance;
                tournament.activeTrade[trader] = Trade(0x0, uint40(block.timestamp), 0, 0);

                emit TradeExecuted(tournamentId, nonce, trader, amountInBips, normalizedPrice, pair);
                emit AccountUpdated(tournamentId, trader, tournament.userBalance[trader], 0, 0, 0x0);
            } else if (absoluteAmountInBips < absoluteActivePositionAmountInBips) {
                // Position is being reduced. For use by cowards only.
                unchecked {
                    openAmount = currentPosition.amountInBips + int104(amountInBips);
                    usdnBalance +=
                        currentUSDNPnlInBips * absoluteAmountInBips / absoluteActivePositionAmountInBips / _BIPS;
                }

                // Apply scaled profits, reduce position, and reset open timstamp.
                tournament.userBalance[trader] = usdnBalance;
                tournament.activeTrade[trader].amountInBips = openAmount;
                tournament.activeTrade[trader].lastUpdatedAt = uint40(block.timestamp);

                emit TradeExecuted(tournamentId, nonce, trader, amountInBips, normalizedPrice, pair);
                emit AccountUpdated(
                    tournamentId, trader, tournament.userBalance[trader], openAmount, currentPosition.entryPrice, pair
                );
            } else {
                // Position is being flipped. For use by the candle chasers.
                // We need to close and apply profits without slippage, then open a new position with slippage.
                unchecked {
                    openAmount = currentPosition.amountInBips + int104(amountInBips);
                    usdnBalance += currentUSDNPnlInBips / _BIPS;
                }

                // Apply profits to user's balance and emit event to represent close with no slippage.
                tournament.userBalance[trader] = usdnBalance;
                emit TradeExecuted(
                    tournamentId, nonce, trader, currentPosition.amountInBips * -1, normalizedPrice, pair
                );

                // Slippage is applied only to the new position.
                int256 newTradeSizeInBips;
                unchecked {
                    newTradeSizeInBips = openAmount * normalizedPrice;
                }
                int256 executionPrice =
                    _calculateEntryPriceWithSlippageApplied(_abs(newTradeSizeInBips), normalizedPrice, direction);
                tournament.activeTrade[trader] =
                    Trade(pair, uint40(block.timestamp), int104(executionPrice), openAmount);

                emit TradeExecuted(tournamentId, nonce, trader, openAmount, executionPrice, pair);
                emit AccountUpdated(
                    tournamentId, trader, tournament.userBalance[trader], openAmount, executionPrice, pair
                );
            }
        } else {
            // Position is being opened or increased. Both are commendable actions.
            int256 executionPrice = _calculateEntryPriceWithSlippageApplied(tradeSizeInBips, normalizedPrice, direction);
            int256 entryPrice;

            unchecked {
                // Scale position size up and calculate new entry price as weighted average of current and new trade.
                openAmount = currentPosition.amountInBips + int104(amountInBips);
                entryPrice = (
                    currentPosition.entryPrice * absoluteActivePositionAmountInBips
                        + executionPrice * absoluteAmountInBips
                ) / (absoluteActivePositionAmountInBips + absoluteAmountInBips);
            }

            tournament.activeTrade[trader] = Trade(pair, uint40(block.timestamp), int104(entryPrice), openAmount);

            emit TradeExecuted(tournamentId, nonce, trader, amountInBips, executionPrice, pair);
            emit AccountUpdated(tournamentId, trader, tournament.userBalance[trader], openAmount, entryPrice, pair);
        }
    }

    /**
     * @notice Liquidates a user in a tournament, as long as their balance + pnl is negative.
     * @param tournamentId The ID of the tournament.
     * @param users The users to liquidate.
     * @param priceUpdates The Pyth price update data.
     */
    function liquidate(uint256 tournamentId, address[] calldata users, bytes[] calldata priceUpdates) external {
        TournamentConfig storage tournament = tournaments[tournamentId];

        _assertTournamentOpen(tournament);
        updatePythPriceFeeds(priceUpdates);

        uint256 liquidationBountyPayout;

        for (uint256 i = 0; i < users.length; i++) {
            int256 currentPnl = _currentPnlFor(tournament, users[i]);

            if (currentPnl + tournament.userBalance[users[i]] < 0) {
                // Get rekt noob.
                tournament.userBalance[users[i]] = 0;
                delete tournament.activeTrade[users[i]];
                unchecked {
                    liquidationBountyPayout += liquidationBounty;
                }

                emit Liquidated(tournamentId, users[i]);
            } else {
                // You're safe for now.
                emit InvalidLiquidation(tournamentId, users[i], tournament.userBalance[users[i]], currentPnl);
            }
        }

        if (liquidationBountyPayout > 0) {
            (bool success,) = msg.sender.call{value: liquidationBountyPayout}("");
            if (!success) revert TransferFailed();

            emit LiquidationBountyPaid(msg.sender, liquidationBountyPayout);
        }
    }

    /**
     * @notice Manual price update.
     * @param priceUpdates The Pyth price update data.
     */
    function updatePythPriceFeeds(bytes[] calldata priceUpdates) public {
        if (priceUpdates.length > 0) {
            uint256 fee = _PYTH.getUpdateFee(priceUpdates);

            _PYTH.updatePriceFeeds{value: fee}(priceUpdates);
        }
    }

    /**
     * @notice Manual price update.
     * @param pairs The pairs to update.
     * @param prices The prices to update.
     */
    function updateManualPriceFeeds(bytes32[] calldata pairs, InternalPrice[] calldata prices) external onlyRoles(_PRICE_MANAGER) {
        if (pairs.length != prices.length) revert ArrayLengthMismatch();

        for (uint256 i = 0; i < pairs.length; i++) {
            InternalPrice memory price = prices[i];
            bytes32 pair = pairs[i];

            if (!_isManualPriceOverride(pair)) revert InvalidPriceUpdates();

            // disallow future timestamps.
            if (price.publishTime > block.timestamp) revert InvalidTimestamp();
            if (price.publishTime <= internalPrices[pair].publishTime) continue;

            internalPrices[pair] = price;
        }
    }

    /**
     * @notice Finalize prices for a tournament.
     * @param tournamentId The ID of the tournament.
     * @param priceUpdates The Pyth price update data.
     * @param pairs The pairs to finalize prices for.
     */
    function finalizePrices(uint256 tournamentId, bytes[] calldata priceUpdates, bytes32[] calldata pairs)
        external
        onlyRoles(_TOURNAMENT_MANAGER)
    {
        TournamentConfig storage tournament = tournaments[tournamentId];

        if (tournament.endTime > block.timestamp) revert TournamentActive();

        uint256 fee = _PYTH.getUpdateFee(priceUpdates);
        PythStructs.PriceFeed[] memory priceFeeds = _PYTH.parsePriceFeedUpdates{value: fee}(
            priceUpdates, pairs, tournament.endTime, tournament.endTime + _FINAL_ENDTIME_GRACE_PERIOD
        );

        for (uint256 i = 0; i < priceFeeds.length; i++) {
            PythStructs.Price memory price = priceFeeds[i].price;

            // convert to InternalPrice to save a storage slot.
            tournament.finalPrices[priceFeeds[i].id] =
                InternalPrice({price: price.price, expo: price.expo, publishTime: uint40(price.publishTime)});
        }
    }

    /**
     * @notice Sets the counterparty signer.
     * @param signer The new counterparty signer.
     */
    function setCounterpartySigner(address signer) external onlyRoles(_GLOBAL_CONFIG_MANAGER) {
        _setCounterpartySigner(signer);
    }

    /**
     * @notice Sets the liquidation bounty.
     * @param bounty The new liquidation bounty.
     */
    function setLiquidationBounty(uint256 bounty) external onlyRoles(_GLOBAL_CONFIG_MANAGER) {
        _setLiquidationBounty(bounty);
    }

    /**
     * @notice Returns the final pyth price for a pair in a tournament.
     * @param tournamentId The ID of the tournament.
     * @param pair The pair to get final pyth price for.
     */
    function finalPrice(uint256 tournamentId, bytes32 pair) external view returns (InternalPrice memory) {
        return tournaments[tournamentId].finalPrices[pair];
    }

    /**
     * @notice Sets the base slippage scaling factor.
     * @param factor The new base slippage scaling factor.
     */
    function setBaseSlippageScalingFactor(uint256 factor) external onlyRoles(_GLOBAL_CONFIG_MANAGER) {
        _setBaseSlippageScalingFactor(factor);
    }

    /**
     * @notice Sets the slippage increment scaling factor.
     * @param factor The new slippage increment scaling factor.
     */
    function setSlippageIncrementScalingFactor(uint256 factor) external onlyRoles(_GLOBAL_CONFIG_MANAGER) {
        _setSlippageIncrementScalingFactor(factor);
    }

    /**
     * @notice Submit results for a tournament.
     * @param tournamentId The ID of the tournament.
     * @param results The results to propose.
     */
    function submitResults(uint256 tournamentId, IRebootTournament.Result[] calldata results)
        external
        onlyRoles(_TOURNAMENT_MANAGER)
    {
        _REBOOT.submitResults(tournamentId, 0, results);
    }

    function cleanup(uint256 tournamentId) external onlyRoles(_TOURNAMENT_MANAGER) {
        _REBOOT.cleanup(tournamentId, 0);
    }

    /**
     * @notice Withdraws the contract balance.
     * @dev If there is a liquidation bounty in place, calling this while a tournament is active will cause liquidations to fail.
     */
    function withdraw() external onlyOwner {
        (bool success,) = msg.sender.call{value: address(this).balance}("");
        if (!success) revert TransferFailed();
    }

    /**
     * @notice Returns the most recent Reboot tournament ID.
     */
    function currentTournamentId() external view returns (uint256) {
        return _REBOOT.tournamentCount();
    }

    /**
     * @notice Returns the active trade for a user in a tournament.
     * @param tournamentId The ID of the tournament.
     * @param user The address of the user.
     */
    function activeTrade(uint256 tournamentId, address user) external view returns (Trade memory) {
        return tournaments[tournamentId].activeTrade[user];
    }

    /**
     * .
     * @notice Returns the balance for a user in a tournament.
     * @param tournamentId The ID of the tournament.
     * @param user The address of the user.
     */
    function userBalance(uint256 tournamentId, address user) external view returns (int256) {
        return tournaments[tournamentId].userBalance[user];
    }

    /**
     * @notice Returns the current UPnL for a user in a tournament.
     * @param tournamentId The ID of the tournament.
     * @param user The address of the user.
     * @dev UPnL does not include the user's balance.
     * @dev Does not perform a price update, so may be stale. Use updatePrices to ensure the most recent prices.
     * @dev accounts for slippage on close.
     */
    function currentPnlFor(uint256 tournamentId, address user) external view returns (int256) {
        TournamentConfig storage tournament = tournaments[tournamentId];

        return _currentPnlFor(tournament, user);
    }

    /**
     * @notice Enters a user into a tournament.
     * @param tournamentId The ID of the tournament.
     */
    function _enterTournament(address user, uint256 tournamentId) internal {
        TournamentConfig storage tournament = tournaments[tournamentId];

        if (tournament.userBalance[user] != 0) {
            revert AlreadyEntered();
        }
        _assertTournamentOpen(tournament);

        // A valid underflow is incredibly impractical.
        uint256 valueToTransfer;
        unchecked {
            valueToTransfer = msg.value - liquidationBounty;
        }
        uint256 entryFee = _REBOOT.getTournament(tournamentId).entryFee;

        // If msg.sender == user, they can use their reboot credits to enter. If not, msg.value must be used for entry fee.
        if (msg.sender != user && valueToTransfer < entryFee) revert InsufficientEntryFee();

        _REBOOT.enter{value: valueToTransfer}(
            IRebootTournament.EntryParams(user, tournamentId, 0, address(0), 0, entryFee)
        );

        tournament.userBalance[user] = int256(int128(tournament.startBalance));
    }

    function _setCounterpartySigner(address signer) internal {
        if (signer == address(0)) revert ZeroAddress();

        counterpartySigner = signer;

        emit CounterpartySignerUpdated(signer);
    }

    function _setBaseSlippageScalingFactor(uint256 factor) internal {
        // revert if slippage scaling factor > _MAX_BASE_SLIPPAGE_SCALING_FACTOR.
        if (factor > _MAX_BASE_SLIPPAGE_SCALING_FACTOR) revert SlippageTooHigh();

        baseSlippageScalingFactor = int256(factor);

        emit BaseSlippageScalingFactorUpdated(factor);
    }

    function _setSlippageIncrementScalingFactor(uint256 factor) internal {
        // revert if slippage scaling factor > _MAX_SLIPPAGE_INCREMENT_SCALING_FACTOR.
        if (factor > _MAX_SLIPPAGE_INCREMENT_SCALING_FACTOR) revert SlippageTooHigh();

        slippageIncrementScalingFactor = int256(factor);

        emit SlippageScalingFactorUpdated(factor);
    }

    function _setLiquidationBounty(uint256 bounty) internal {
        // bounty should typically be ~1 apecoin or less.
        if (bounty > 1_000 ether) revert LiquidationBountyTooHigh();

        liquidationBounty = bounty;

        emit LiquidationBountyUpdated(bounty);
    }

    function _updateAndFetchPythPrice(bytes32 pair, bytes[] calldata priceUpdates)
        internal
        returns (PythStructs.Price memory)
    {
        updatePythPriceFeeds(priceUpdates);
        return _PYTH.getPrice(pair);
    }

    function _fetchAndNormalizePrice(bytes32 pair) internal view returns (int256) {
        if (bytes4(pair) == _MANUAL_PRICE_PAIR_IDENTIFIER) {
            InternalPrice memory price = internalPrices[pair];

            if (price.publishTime < block.timestamp - _PYTH.getValidTimePeriod()) {
                revert StalePrice();
            }

            return _normalizePrice(price.price, price.expo);
        } else {
            PythStructs.Price memory price = _PYTH.getPrice(pair);
            return _normalizePrice(price.price, price.expo);
        }
    }

    function _currentPnlFor(TournamentConfig storage tournament, address user) internal view returns (int256) {
        Trade memory activePosition = tournament.activeTrade[user];
        bytes32 pair = activePosition.pair;
        if (pair == 0x0) {
            return 0;
        }

        int256 normalizedPrice;
        if (tournament.finalPrices[pair].price != 0) {
            InternalPrice memory price = tournament.finalPrices[pair];
            normalizedPrice = _normalizePrice(price.price, price.expo);
        } else {
            normalizedPrice = _fetchAndNormalizePrice(pair);
        }

        return _currentPnlForPositionAndPrice(activePosition, normalizedPrice) / _BIPS;
    }

    function _assertTournamentOpen(TournamentConfig storage tournament) internal view {
        if (block.timestamp < tournament.startTime || block.timestamp > tournament.endTime) {
            revert NotActive();
        }
    }

    // No free rides.
    function _calculateEntryPriceWithSlippageApplied(int256 tradeSizeInBips, int256 rawEntryPrice, Direction direction)
        internal
        view
        returns (int256 executionPrice)
    {
        int256 floorTradeSize;
        unchecked {
            floorTradeSize = tradeSizeInBips / (_BIPS * 1e18);
        }
        int256 directionModifier = direction == Direction.LONG ? int256(1) : int256(-1);

        if (floorTradeSize < _FIRST_SLIPPAGE_BREAKPOINT) {
            // Apply 1x base slippage to raw entry price.
            unchecked {
                int256 slippage = rawEntryPrice * baseSlippageScalingFactor / 1_000_000;
                executionPrice = directionModifier * slippage + rawEntryPrice;
            }
        } else if (floorTradeSize < _SECOND_SLIPPAGE_BREAKPOINT) {
            // Apply 2x base slippage to raw entry price.
            unchecked {
                int256 slippage = rawEntryPrice * baseSlippageScalingFactor / 500_000;
                executionPrice = directionModifier * slippage + rawEntryPrice;
            }
        } else {
            // Apply 2x base slippage + additional slippage for every order of magnitude increase.
            int256 additionalSlippage = 0;
            unchecked {
                int256 baseSlippage = rawEntryPrice * baseSlippageScalingFactor / 500_000;
                int256 slippageIncrement = rawEntryPrice * slippageIncrementScalingFactor / 10_000_000;

                while (floorTradeSize >= _SECOND_SLIPPAGE_BREAKPOINT) {
                    additionalSlippage += slippageIncrement;
                    floorTradeSize = floorTradeSize * 7943 / 10000; // Reduce by 1/10th of a magnitude (20.57%).
                }

                int256 totalSlippage = baseSlippage + additionalSlippage;
                executionPrice = directionModifier * totalSlippage + rawEntryPrice;
            }
        }
    }

    function _normalizePrice(int64 price, int32 expo) internal pure returns (int256) {
        unchecked {
            if (expo < -18) {
                return int256(price) / int256(10 ** uint256(uint32(-(18 + expo))));
            } else {
                return int256(price) * int256(10 ** uint256(uint32(18 + expo)));
            }
        }
    }

    function _currentPnlForPositionAndPrice(Trade memory activePosition, int256 normalizedPrice)
        internal
        pure
        returns (int256)
    {
        unchecked {
            return (normalizedPrice - activePosition.entryPrice) * activePosition.amountInBips;
        }
    }

    function _isManualPriceOverride(bytes32 pair) internal pure returns (bool) {
        return bytes4(pair) == _MANUAL_PRICE_PAIR_IDENTIFIER;
    }

    function _abs(int256 x) internal pure returns (int256) {
        return x >= 0 ? x : -x;
    }
}

/**
 * _     _ _______ _______  _____       _______        _____ _______ _     _ _____ __   _  ______  
 *  |____/  |______ |______ |_____]      |       |        |   |       |____/    |   | \  | |  ____  
 *  |    \_ |______ |______ |            |_____  |_____ __|__ |_____  |    \_ __|__ |  \_| |_____| .
 *
 *   ______         _____   ______ __   __      _______ _  _  _ _______ _____ _______ _______  
 *  |  ____ |      |     | |_____/   \_/        |_____| |  |  | |_____|   |      |    |______  
 *  |_____| |_____ |_____| |    \_    |         |     | |__|__| |     | __|__    |    ______| .
 *
 *  ______  _______ _______      _______  _____   ______ _______  
 *  |_____] |______    |         |  |  | |     | |_____/ |______  
 *  |_____] |______    |         |  |  | |_____| |    \_ |______ .
 */

// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "./PythStructs.sol";
import "./IPythEvents.sol";

/// @title Consume prices from the Pyth Network (https://pyth.network/).
/// @dev Please refer to the guidance at https://docs.pyth.network/consumers/best-practices for how to consume prices safely.
/// @author Pyth Data Association
interface IPyth is IPythEvents {
    /// @notice Returns the period (in seconds) that a price feed is considered valid since its publish time
    function getValidTimePeriod() external view returns (uint validTimePeriod);

    /// @notice Returns the price and confidence interval.
    /// @dev Reverts if the price has not been updated within the last `getValidTimePeriod()` seconds.
    /// @param id The Pyth Price Feed ID of which to fetch the price and confidence interval.
    /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely.
    function getPrice(
        bytes32 id
    ) external view returns (PythStructs.Price memory price);

    /// @notice Returns the exponentially-weighted moving average price and confidence interval.
    /// @dev Reverts if the EMA price is not available.
    /// @param id The Pyth Price Feed ID of which to fetch the EMA price and confidence interval.
    /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely.
    function getEmaPrice(
        bytes32 id
    ) external view returns (PythStructs.Price memory price);

    /// @notice Returns the price of a price feed without any sanity checks.
    /// @dev This function returns the most recent price update in this contract without any recency checks.
    /// This function is unsafe as the returned price update may be arbitrarily far in the past.
    ///
    /// Users of this function should check the `publishTime` in the price to ensure that the returned price is
    /// sufficiently recent for their application. If you are considering using this function, it may be
    /// safer / easier to use either `getPrice` or `getPriceNoOlderThan`.
    /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely.
    function getPriceUnsafe(
        bytes32 id
    ) external view returns (PythStructs.Price memory price);

    /// @notice Returns the price that is no older than `age` seconds of the current time.
    /// @dev This function is a sanity-checked version of `getPriceUnsafe` which is useful in
    /// applications that require a sufficiently-recent price. Reverts if the price wasn't updated sufficiently
    /// recently.
    /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely.
    function getPriceNoOlderThan(
        bytes32 id,
        uint age
    ) external view returns (PythStructs.Price memory price);

    /// @notice Returns the exponentially-weighted moving average price of a price feed without any sanity checks.
    /// @dev This function returns the same price as `getEmaPrice` in the case where the price is available.
    /// However, if the price is not recent this function returns the latest available price.
    ///
    /// The returned price can be from arbitrarily far in the past; this function makes no guarantees that
    /// the returned price is recent or useful for any particular application.
    ///
    /// Users of this function should check the `publishTime` in the price to ensure that the returned price is
    /// sufficiently recent for their application. If you are considering using this function, it may be
    /// safer / easier to use either `getEmaPrice` or `getEmaPriceNoOlderThan`.
    /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely.
    function getEmaPriceUnsafe(
        bytes32 id
    ) external view returns (PythStructs.Price memory price);

    /// @notice Returns the exponentially-weighted moving average price that is no older than `age` seconds
    /// of the current time.
    /// @dev This function is a sanity-checked version of `getEmaPriceUnsafe` which is useful in
    /// applications that require a sufficiently-recent price. Reverts if the price wasn't updated sufficiently
    /// recently.
    /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely.
    function getEmaPriceNoOlderThan(
        bytes32 id,
        uint age
    ) external view returns (PythStructs.Price memory price);

    /// @notice Update price feeds with given update messages.
    /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling
    /// `getUpdateFee` with the length of the `updateData` array.
    /// Prices will be updated if they are more recent than the current stored prices.
    /// The call will succeed even if the update is not the most recent.
    /// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid.
    /// @param updateData Array of price update data.
    function updatePriceFeeds(bytes[] calldata updateData) external payable;

    /// @notice Wrapper around updatePriceFeeds that rejects fast if a price update is not necessary. A price update is
    /// necessary if the current on-chain publishTime is older than the given publishTime. It relies solely on the
    /// given `publishTimes` for the price feeds and does not read the actual price update publish time within `updateData`.
    ///
    /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling
    /// `getUpdateFee` with the length of the `updateData` array.
    ///
    /// `priceIds` and `publishTimes` are two arrays with the same size that correspond to senders known publishTime
    /// of each priceId when calling this method. If all of price feeds within `priceIds` have updated and have
    /// a newer or equal publish time than the given publish time, it will reject the transaction to save gas.
    /// Otherwise, it calls updatePriceFeeds method to update the prices.
    ///
    /// @dev Reverts if update is not needed or the transferred fee is not sufficient or the updateData is invalid.
    /// @param updateData Array of price update data.
    /// @param priceIds Array of price ids.
    /// @param publishTimes Array of publishTimes. `publishTimes[i]` corresponds to known `publishTime` of `priceIds[i]`
    function updatePriceFeedsIfNecessary(
        bytes[] calldata updateData,
        bytes32[] calldata priceIds,
        uint64[] calldata publishTimes
    ) external payable;

    /// @notice Returns the required fee to update an array of price updates.
    /// @param updateData Array of price update data.
    /// @return feeAmount The required fee in Wei.
    function getUpdateFee(
        bytes[] calldata updateData
    ) external view returns (uint feeAmount);

    /// @notice Parse `updateData` and return price feeds of the given `priceIds` if they are all published
    /// within `minPublishTime` and `maxPublishTime`.
    ///
    /// You can use this method if you want to use a Pyth price at a fixed time and not the most recent price;
    /// otherwise, please consider using `updatePriceFeeds`. This method does not store the price updates on-chain.
    ///
    /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling
    /// `getUpdateFee` with the length of the `updateData` array.
    ///
    ///
    /// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid or there is
    /// no update for any of the given `priceIds` within the given time range.
    /// @param updateData Array of price update data.
    /// @param priceIds Array of price ids.
    /// @param minPublishTime minimum acceptable publishTime for the given `priceIds`.
    /// @param maxPublishTime maximum acceptable publishTime for the given `priceIds`.
    /// @return priceFeeds Array of the price feeds corresponding to the given `priceIds` (with the same order).
    function parsePriceFeedUpdates(
        bytes[] calldata updateData,
        bytes32[] calldata priceIds,
        uint64 minPublishTime,
        uint64 maxPublishTime
    ) external payable returns (PythStructs.PriceFeed[] memory priceFeeds);
}

// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

contract PythStructs {
    // A price with a degree of uncertainty, represented as a price +- a confidence interval.
    //
    // The confidence interval roughly corresponds to the standard error of a normal distribution.
    // Both the price and confidence are stored in a fixed-point numeric representation,
    // `x * (10^expo)`, where `expo` is the exponent.
    //
    // Please refer to the documentation at https://docs.pyth.network/consumers/best-practices for how
    // to how this price safely.
    struct Price {
        // Price
        int64 price;
        // Confidence interval around the price
        uint64 conf;
        // Price exponent
        int32 expo;
        // Unix timestamp describing when the price was published
        uint publishTime;
    }

    // PriceFeed represents a current aggregate price from pyth publisher feeds.
    struct PriceFeed {
        // The price ID.
        bytes32 id;
        // Latest available price
        Price price;
        // Latest available exponentially-weighted moving average price
        Price emaPrice;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

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

/// @notice Simple single owner and multiroles authorization mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol)
/// @dev While the ownable portion follows [EIP-173](https://eips.ethereum.org/EIPS/eip-173)
/// for compatibility, the nomenclature for the 2-step ownership handover and roles
/// may be unique to this codebase.
abstract contract OwnableRoles is Ownable {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                           EVENTS                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The `user`'s roles is updated to `roles`.
    /// Each bit of `roles` represents whether the role is set.
    event RolesUpdated(address indexed user, uint256 indexed roles);

    /// @dev `keccak256(bytes("RolesUpdated(address,uint256)"))`.
    uint256 private constant _ROLES_UPDATED_EVENT_SIGNATURE =
        0x715ad5ce61fc9595c7b415289d59cf203f23a94fa06f04af7e489a0a76e1fe26;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                          STORAGE                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The role slot of `user` is given by:
    /// ```
    ///     mstore(0x00, or(shl(96, user), _ROLE_SLOT_SEED))
    ///     let roleSlot := keccak256(0x00, 0x20)
    /// ```
    /// This automatically ignores the upper bits of the `user` in case
    /// they are not clean, as well as keep the `keccak256` under 32-bytes.
    ///
    /// Note: This is equivalent to `uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))`.
    uint256 private constant _ROLE_SLOT_SEED = 0x8b78c6d8;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                     INTERNAL FUNCTIONS                     */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Overwrite the roles directly without authorization guard.
    function _setRoles(address user, uint256 roles) internal virtual {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x0c, _ROLE_SLOT_SEED)
            mstore(0x00, user)
            // Store the new value.
            sstore(keccak256(0x0c, 0x20), roles)
            // Emit the {RolesUpdated} event.
            log3(0, 0, _ROLES_UPDATED_EVENT_SIGNATURE, shr(96, mload(0x0c)), roles)
        }
    }

    /// @dev Updates the roles directly without authorization guard.
    /// If `on` is true, each set bit of `roles` will be turned on,
    /// otherwise, each set bit of `roles` will be turned off.
    function _updateRoles(address user, uint256 roles, bool on) internal virtual {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x0c, _ROLE_SLOT_SEED)
            mstore(0x00, user)
            let roleSlot := keccak256(0x0c, 0x20)
            // Load the current value.
            let current := sload(roleSlot)
            // Compute the updated roles if `on` is true.
            let updated := or(current, roles)
            // Compute the updated roles if `on` is false.
            // Use `and` to compute the intersection of `current` and `roles`,
            // `xor` it with `current` to flip the bits in the intersection.
            if iszero(on) { updated := xor(current, and(current, roles)) }
            // Then, store the new value.
            sstore(roleSlot, updated)
            // Emit the {RolesUpdated} event.
            log3(0, 0, _ROLES_UPDATED_EVENT_SIGNATURE, shr(96, mload(0x0c)), updated)
        }
    }

    /// @dev Grants the roles directly without authorization guard.
    /// Each bit of `roles` represents the role to turn on.
    function _grantRoles(address user, uint256 roles) internal virtual {
        _updateRoles(user, roles, true);
    }

    /// @dev Removes the roles directly without authorization guard.
    /// Each bit of `roles` represents the role to turn off.
    function _removeRoles(address user, uint256 roles) internal virtual {
        _updateRoles(user, roles, false);
    }

    /// @dev Throws if the sender does not have any of the `roles`.
    function _checkRoles(uint256 roles) internal view virtual {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute the role slot.
            mstore(0x0c, _ROLE_SLOT_SEED)
            mstore(0x00, caller())
            // Load the stored value, and if the `and` intersection
            // of the value and `roles` is zero, revert.
            if iszero(and(sload(keccak256(0x0c, 0x20)), roles)) {
                mstore(0x00, 0x82b42900) // `Unauthorized()`.
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Throws if the sender is not the owner,
    /// and does not have any of the `roles`.
    /// Checks for ownership first, then lazily checks for roles.
    function _checkOwnerOrRoles(uint256 roles) internal view virtual {
        /// @solidity memory-safe-assembly
        assembly {
            // If the caller is not the stored owner.
            // Note: `_ROLE_SLOT_SEED` is equal to `_OWNER_SLOT_NOT`.
            if iszero(eq(caller(), sload(not(_ROLE_SLOT_SEED)))) {
                // Compute the role slot.
                mstore(0x0c, _ROLE_SLOT_SEED)
                mstore(0x00, caller())
                // Load the stored value, and if the `and` intersection
                // of the value and `roles` is zero, revert.
                if iszero(and(sload(keccak256(0x0c, 0x20)), roles)) {
                    mstore(0x00, 0x82b42900) // `Unauthorized()`.
                    revert(0x1c, 0x04)
                }
            }
        }
    }

    /// @dev Throws if the sender does not have any of the `roles`,
    /// and is not the owner.
    /// Checks for roles first, then lazily checks for ownership.
    function _checkRolesOrOwner(uint256 roles) internal view virtual {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute the role slot.
            mstore(0x0c, _ROLE_SLOT_SEED)
            mstore(0x00, caller())
            // Load the stored value, and if the `and` intersection
            // of the value and `roles` is zero, revert.
            if iszero(and(sload(keccak256(0x0c, 0x20)), roles)) {
                // If the caller is not the stored owner.
                // Note: `_ROLE_SLOT_SEED` is equal to `_OWNER_SLOT_NOT`.
                if iszero(eq(caller(), sload(not(_ROLE_SLOT_SEED)))) {
                    mstore(0x00, 0x82b42900) // `Unauthorized()`.
                    revert(0x1c, 0x04)
                }
            }
        }
    }

    /// @dev Convenience function to return a `roles` bitmap from an array of `ordinals`.
    /// This is meant for frontends like Etherscan, and is therefore not fully optimized.
    /// Not recommended to be called on-chain.
    /// Made internal to conserve bytecode. Wrap it in a public function if needed.
    function _rolesFromOrdinals(uint8[] memory ordinals) internal pure returns (uint256 roles) {
        /// @solidity memory-safe-assembly
        assembly {
            for { let i := shl(5, mload(ordinals)) } i { i := sub(i, 0x20) } {
                // We don't need to mask the values of `ordinals`, as Solidity
                // cleans dirty upper bits when storing variables into memory.
                roles := or(shl(mload(add(ordinals, i)), 1), roles)
            }
        }
    }

    /// @dev Convenience function to return an array of `ordinals` from the `roles` bitmap.
    /// This is meant for frontends like Etherscan, and is therefore not fully optimized.
    /// Not recommended to be called on-chain.
    /// Made internal to conserve bytecode. Wrap it in a public function if needed.
    function _ordinalsFromRoles(uint256 roles) internal pure returns (uint8[] memory ordinals) {
        /// @solidity memory-safe-assembly
        assembly {
            // Grab the pointer to the free memory.
            ordinals := mload(0x40)
            let ptr := add(ordinals, 0x20)
            let o := 0
            // The absence of lookup tables, De Bruijn, etc., here is intentional for
            // smaller bytecode, as this function is not meant to be called on-chain.
            for { let t := roles } 1 {} {
                mstore(ptr, o)
                // `shr` 5 is equivalent to multiplying by 0x20.
                // Push back into the ordinals array if the bit is set.
                ptr := add(ptr, shl(5, and(t, 1)))
                o := add(o, 1)
                t := shr(o, roles)
                if iszero(t) { break }
            }
            // Store the length of `ordinals`.
            mstore(ordinals, shr(5, sub(ptr, add(ordinals, 0x20))))
            // Allocate the memory.
            mstore(0x40, ptr)
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                  PUBLIC UPDATE FUNCTIONS                   */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Allows the owner to grant `user` `roles`.
    /// If the `user` already has a role, then it will be an no-op for the role.
    function grantRoles(address user, uint256 roles) public payable virtual onlyOwner {
        _grantRoles(user, roles);
    }

    /// @dev Allows the owner to remove `user` `roles`.
    /// If the `user` does not have a role, then it will be an no-op for the role.
    function revokeRoles(address user, uint256 roles) public payable virtual onlyOwner {
        _removeRoles(user, roles);
    }

    /// @dev Allow the caller to remove their own roles.
    /// If the caller does not have a role, then it will be an no-op for the role.
    function renounceRoles(uint256 roles) public payable virtual {
        _removeRoles(msg.sender, roles);
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                   PUBLIC READ FUNCTIONS                    */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns the roles of `user`.
    function rolesOf(address user) public view virtual returns (uint256 roles) {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute the role slot.
            mstore(0x0c, _ROLE_SLOT_SEED)
            mstore(0x00, user)
            // Load the stored value.
            roles := sload(keccak256(0x0c, 0x20))
        }
    }

    /// @dev Returns whether `user` has any of `roles`.
    function hasAnyRole(address user, uint256 roles) public view virtual returns (bool) {
        return rolesOf(user) & roles != 0;
    }

    /// @dev Returns whether `user` has all of `roles`.
    function hasAllRoles(address user, uint256 roles) public view virtual returns (bool) {
        return rolesOf(user) & roles == roles;
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         MODIFIERS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Marks a function as only callable by an account with `roles`.
    modifier onlyRoles(uint256 roles) virtual {
        _checkRoles(roles);
        _;
    }

    /// @dev Marks a function as only callable by the owner or by an account
    /// with `roles`. Checks for ownership first, then lazily checks for roles.
    modifier onlyOwnerOrRoles(uint256 roles) virtual {
        _checkOwnerOrRoles(roles);
        _;
    }

    /// @dev Marks a function as only callable by an account with `roles`
    /// or the owner. Checks for roles first, then lazily checks for ownership.
    modifier onlyRolesOrOwner(uint256 roles) virtual {
        _checkRolesOrOwner(roles);
        _;
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       ROLE CONSTANTS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    // IYKYK

    uint256 internal constant _ROLE_0 = 1 << 0;
    uint256 internal constant _ROLE_1 = 1 << 1;
    uint256 internal constant _ROLE_2 = 1 << 2;
    uint256 internal constant _ROLE_3 = 1 << 3;
    uint256 internal constant _ROLE_4 = 1 << 4;
    uint256 internal constant _ROLE_5 = 1 << 5;
    uint256 internal constant _ROLE_6 = 1 << 6;
    uint256 internal constant _ROLE_7 = 1 << 7;
    uint256 internal constant _ROLE_8 = 1 << 8;
    uint256 internal constant _ROLE_9 = 1 << 9;
    uint256 internal constant _ROLE_10 = 1 << 10;
    uint256 internal constant _ROLE_11 = 1 << 11;
    uint256 internal constant _ROLE_12 = 1 << 12;
    uint256 internal constant _ROLE_13 = 1 << 13;
    uint256 internal constant _ROLE_14 = 1 << 14;
    uint256 internal constant _ROLE_15 = 1 << 15;
    uint256 internal constant _ROLE_16 = 1 << 16;
    uint256 internal constant _ROLE_17 = 1 << 17;
    uint256 internal constant _ROLE_18 = 1 << 18;
    uint256 internal constant _ROLE_19 = 1 << 19;
    uint256 internal constant _ROLE_20 = 1 << 20;
    uint256 internal constant _ROLE_21 = 1 << 21;
    uint256 internal constant _ROLE_22 = 1 << 22;
    uint256 internal constant _ROLE_23 = 1 << 23;
    uint256 internal constant _ROLE_24 = 1 << 24;
    uint256 internal constant _ROLE_25 = 1 << 25;
    uint256 internal constant _ROLE_26 = 1 << 26;
    uint256 internal constant _ROLE_27 = 1 << 27;
    uint256 internal constant _ROLE_28 = 1 << 28;
    uint256 internal constant _ROLE_29 = 1 << 29;
    uint256 internal constant _ROLE_30 = 1 << 30;
    uint256 internal constant _ROLE_31 = 1 << 31;
    uint256 internal constant _ROLE_32 = 1 << 32;
    uint256 internal constant _ROLE_33 = 1 << 33;
    uint256 internal constant _ROLE_34 = 1 << 34;
    uint256 internal constant _ROLE_35 = 1 << 35;
    uint256 internal constant _ROLE_36 = 1 << 36;
    uint256 internal constant _ROLE_37 = 1 << 37;
    uint256 internal constant _ROLE_38 = 1 << 38;
    uint256 internal constant _ROLE_39 = 1 << 39;
    uint256 internal constant _ROLE_40 = 1 << 40;
    uint256 internal constant _ROLE_41 = 1 << 41;
    uint256 internal constant _ROLE_42 = 1 << 42;
    uint256 internal constant _ROLE_43 = 1 << 43;
    uint256 internal constant _ROLE_44 = 1 << 44;
    uint256 internal constant _ROLE_45 = 1 << 45;
    uint256 internal constant _ROLE_46 = 1 << 46;
    uint256 internal constant _ROLE_47 = 1 << 47;
    uint256 internal constant _ROLE_48 = 1 << 48;
    uint256 internal constant _ROLE_49 = 1 << 49;
    uint256 internal constant _ROLE_50 = 1 << 50;
    uint256 internal constant _ROLE_51 = 1 << 51;
    uint256 internal constant _ROLE_52 = 1 << 52;
    uint256 internal constant _ROLE_53 = 1 << 53;
    uint256 internal constant _ROLE_54 = 1 << 54;
    uint256 internal constant _ROLE_55 = 1 << 55;
    uint256 internal constant _ROLE_56 = 1 << 56;
    uint256 internal constant _ROLE_57 = 1 << 57;
    uint256 internal constant _ROLE_58 = 1 << 58;
    uint256 internal constant _ROLE_59 = 1 << 59;
    uint256 internal constant _ROLE_60 = 1 << 60;
    uint256 internal constant _ROLE_61 = 1 << 61;
    uint256 internal constant _ROLE_62 = 1 << 62;
    uint256 internal constant _ROLE_63 = 1 << 63;
    uint256 internal constant _ROLE_64 = 1 << 64;
    uint256 internal constant _ROLE_65 = 1 << 65;
    uint256 internal constant _ROLE_66 = 1 << 66;
    uint256 internal constant _ROLE_67 = 1 << 67;
    uint256 internal constant _ROLE_68 = 1 << 68;
    uint256 internal constant _ROLE_69 = 1 << 69;
    uint256 internal constant _ROLE_70 = 1 << 70;
    uint256 internal constant _ROLE_71 = 1 << 71;
    uint256 internal constant _ROLE_72 = 1 << 72;
    uint256 internal constant _ROLE_73 = 1 << 73;
    uint256 internal constant _ROLE_74 = 1 << 74;
    uint256 internal constant _ROLE_75 = 1 << 75;
    uint256 internal constant _ROLE_76 = 1 << 76;
    uint256 internal constant _ROLE_77 = 1 << 77;
    uint256 internal constant _ROLE_78 = 1 << 78;
    uint256 internal constant _ROLE_79 = 1 << 79;
    uint256 internal constant _ROLE_80 = 1 << 80;
    uint256 internal constant _ROLE_81 = 1 << 81;
    uint256 internal constant _ROLE_82 = 1 << 82;
    uint256 internal constant _ROLE_83 = 1 << 83;
    uint256 internal constant _ROLE_84 = 1 << 84;
    uint256 internal constant _ROLE_85 = 1 << 85;
    uint256 internal constant _ROLE_86 = 1 << 86;
    uint256 internal constant _ROLE_87 = 1 << 87;
    uint256 internal constant _ROLE_88 = 1 << 88;
    uint256 internal constant _ROLE_89 = 1 << 89;
    uint256 internal constant _ROLE_90 = 1 << 90;
    uint256 internal constant _ROLE_91 = 1 << 91;
    uint256 internal constant _ROLE_92 = 1 << 92;
    uint256 internal constant _ROLE_93 = 1 << 93;
    uint256 internal constant _ROLE_94 = 1 << 94;
    uint256 internal constant _ROLE_95 = 1 << 95;
    uint256 internal constant _ROLE_96 = 1 << 96;
    uint256 internal constant _ROLE_97 = 1 << 97;
    uint256 internal constant _ROLE_98 = 1 << 98;
    uint256 internal constant _ROLE_99 = 1 << 99;
    uint256 internal constant _ROLE_100 = 1 << 100;
    uint256 internal constant _ROLE_101 = 1 << 101;
    uint256 internal constant _ROLE_102 = 1 << 102;
    uint256 internal constant _ROLE_103 = 1 << 103;
    uint256 internal constant _ROLE_104 = 1 << 104;
    uint256 internal constant _ROLE_105 = 1 << 105;
    uint256 internal constant _ROLE_106 = 1 << 106;
    uint256 internal constant _ROLE_107 = 1 << 107;
    uint256 internal constant _ROLE_108 = 1 << 108;
    uint256 internal constant _ROLE_109 = 1 << 109;
    uint256 internal constant _ROLE_110 = 1 << 110;
    uint256 internal constant _ROLE_111 = 1 << 111;
    uint256 internal constant _ROLE_112 = 1 << 112;
    uint256 internal constant _ROLE_113 = 1 << 113;
    uint256 internal constant _ROLE_114 = 1 << 114;
    uint256 internal constant _ROLE_115 = 1 << 115;
    uint256 internal constant _ROLE_116 = 1 << 116;
    uint256 internal constant _ROLE_117 = 1 << 117;
    uint256 internal constant _ROLE_118 = 1 << 118;
    uint256 internal constant _ROLE_119 = 1 << 119;
    uint256 internal constant _ROLE_120 = 1 << 120;
    uint256 internal constant _ROLE_121 = 1 << 121;
    uint256 internal constant _ROLE_122 = 1 << 122;
    uint256 internal constant _ROLE_123 = 1 << 123;
    uint256 internal constant _ROLE_124 = 1 << 124;
    uint256 internal constant _ROLE_125 = 1 << 125;
    uint256 internal constant _ROLE_126 = 1 << 126;
    uint256 internal constant _ROLE_127 = 1 << 127;
    uint256 internal constant _ROLE_128 = 1 << 128;
    uint256 internal constant _ROLE_129 = 1 << 129;
    uint256 internal constant _ROLE_130 = 1 << 130;
    uint256 internal constant _ROLE_131 = 1 << 131;
    uint256 internal constant _ROLE_132 = 1 << 132;
    uint256 internal constant _ROLE_133 = 1 << 133;
    uint256 internal constant _ROLE_134 = 1 << 134;
    uint256 internal constant _ROLE_135 = 1 << 135;
    uint256 internal constant _ROLE_136 = 1 << 136;
    uint256 internal constant _ROLE_137 = 1 << 137;
    uint256 internal constant _ROLE_138 = 1 << 138;
    uint256 internal constant _ROLE_139 = 1 << 139;
    uint256 internal constant _ROLE_140 = 1 << 140;
    uint256 internal constant _ROLE_141 = 1 << 141;
    uint256 internal constant _ROLE_142 = 1 << 142;
    uint256 internal constant _ROLE_143 = 1 << 143;
    uint256 internal constant _ROLE_144 = 1 << 144;
    uint256 internal constant _ROLE_145 = 1 << 145;
    uint256 internal constant _ROLE_146 = 1 << 146;
    uint256 internal constant _ROLE_147 = 1 << 147;
    uint256 internal constant _ROLE_148 = 1 << 148;
    uint256 internal constant _ROLE_149 = 1 << 149;
    uint256 internal constant _ROLE_150 = 1 << 150;
    uint256 internal constant _ROLE_151 = 1 << 151;
    uint256 internal constant _ROLE_152 = 1 << 152;
    uint256 internal constant _ROLE_153 = 1 << 153;
    uint256 internal constant _ROLE_154 = 1 << 154;
    uint256 internal constant _ROLE_155 = 1 << 155;
    uint256 internal constant _ROLE_156 = 1 << 156;
    uint256 internal constant _ROLE_157 = 1 << 157;
    uint256 internal constant _ROLE_158 = 1 << 158;
    uint256 internal constant _ROLE_159 = 1 << 159;
    uint256 internal constant _ROLE_160 = 1 << 160;
    uint256 internal constant _ROLE_161 = 1 << 161;
    uint256 internal constant _ROLE_162 = 1 << 162;
    uint256 internal constant _ROLE_163 = 1 << 163;
    uint256 internal constant _ROLE_164 = 1 << 164;
    uint256 internal constant _ROLE_165 = 1 << 165;
    uint256 internal constant _ROLE_166 = 1 << 166;
    uint256 internal constant _ROLE_167 = 1 << 167;
    uint256 internal constant _ROLE_168 = 1 << 168;
    uint256 internal constant _ROLE_169 = 1 << 169;
    uint256 internal constant _ROLE_170 = 1 << 170;
    uint256 internal constant _ROLE_171 = 1 << 171;
    uint256 internal constant _ROLE_172 = 1 << 172;
    uint256 internal constant _ROLE_173 = 1 << 173;
    uint256 internal constant _ROLE_174 = 1 << 174;
    uint256 internal constant _ROLE_175 = 1 << 175;
    uint256 internal constant _ROLE_176 = 1 << 176;
    uint256 internal constant _ROLE_177 = 1 << 177;
    uint256 internal constant _ROLE_178 = 1 << 178;
    uint256 internal constant _ROLE_179 = 1 << 179;
    uint256 internal constant _ROLE_180 = 1 << 180;
    uint256 internal constant _ROLE_181 = 1 << 181;
    uint256 internal constant _ROLE_182 = 1 << 182;
    uint256 internal constant _ROLE_183 = 1 << 183;
    uint256 internal constant _ROLE_184 = 1 << 184;
    uint256 internal constant _ROLE_185 = 1 << 185;
    uint256 internal constant _ROLE_186 = 1 << 186;
    uint256 internal constant _ROLE_187 = 1 << 187;
    uint256 internal constant _ROLE_188 = 1 << 188;
    uint256 internal constant _ROLE_189 = 1 << 189;
    uint256 internal constant _ROLE_190 = 1 << 190;
    uint256 internal constant _ROLE_191 = 1 << 191;
    uint256 internal constant _ROLE_192 = 1 << 192;
    uint256 internal constant _ROLE_193 = 1 << 193;
    uint256 internal constant _ROLE_194 = 1 << 194;
    uint256 internal constant _ROLE_195 = 1 << 195;
    uint256 internal constant _ROLE_196 = 1 << 196;
    uint256 internal constant _ROLE_197 = 1 << 197;
    uint256 internal constant _ROLE_198 = 1 << 198;
    uint256 internal constant _ROLE_199 = 1 << 199;
    uint256 internal constant _ROLE_200 = 1 << 200;
    uint256 internal constant _ROLE_201 = 1 << 201;
    uint256 internal constant _ROLE_202 = 1 << 202;
    uint256 internal constant _ROLE_203 = 1 << 203;
    uint256 internal constant _ROLE_204 = 1 << 204;
    uint256 internal constant _ROLE_205 = 1 << 205;
    uint256 internal constant _ROLE_206 = 1 << 206;
    uint256 internal constant _ROLE_207 = 1 << 207;
    uint256 internal constant _ROLE_208 = 1 << 208;
    uint256 internal constant _ROLE_209 = 1 << 209;
    uint256 internal constant _ROLE_210 = 1 << 210;
    uint256 internal constant _ROLE_211 = 1 << 211;
    uint256 internal constant _ROLE_212 = 1 << 212;
    uint256 internal constant _ROLE_213 = 1 << 213;
    uint256 internal constant _ROLE_214 = 1 << 214;
    uint256 internal constant _ROLE_215 = 1 << 215;
    uint256 internal constant _ROLE_216 = 1 << 216;
    uint256 internal constant _ROLE_217 = 1 << 217;
    uint256 internal constant _ROLE_218 = 1 << 218;
    uint256 internal constant _ROLE_219 = 1 << 219;
    uint256 internal constant _ROLE_220 = 1 << 220;
    uint256 internal constant _ROLE_221 = 1 << 221;
    uint256 internal constant _ROLE_222 = 1 << 222;
    uint256 internal constant _ROLE_223 = 1 << 223;
    uint256 internal constant _ROLE_224 = 1 << 224;
    uint256 internal constant _ROLE_225 = 1 << 225;
    uint256 internal constant _ROLE_226 = 1 << 226;
    uint256 internal constant _ROLE_227 = 1 << 227;
    uint256 internal constant _ROLE_228 = 1 << 228;
    uint256 internal constant _ROLE_229 = 1 << 229;
    uint256 internal constant _ROLE_230 = 1 << 230;
    uint256 internal constant _ROLE_231 = 1 << 231;
    uint256 internal constant _ROLE_232 = 1 << 232;
    uint256 internal constant _ROLE_233 = 1 << 233;
    uint256 internal constant _ROLE_234 = 1 << 234;
    uint256 internal constant _ROLE_235 = 1 << 235;
    uint256 internal constant _ROLE_236 = 1 << 236;
    uint256 internal constant _ROLE_237 = 1 << 237;
    uint256 internal constant _ROLE_238 = 1 << 238;
    uint256 internal constant _ROLE_239 = 1 << 239;
    uint256 internal constant _ROLE_240 = 1 << 240;
    uint256 internal constant _ROLE_241 = 1 << 241;
    uint256 internal constant _ROLE_242 = 1 << 242;
    uint256 internal constant _ROLE_243 = 1 << 243;
    uint256 internal constant _ROLE_244 = 1 << 244;
    uint256 internal constant _ROLE_245 = 1 << 245;
    uint256 internal constant _ROLE_246 = 1 << 246;
    uint256 internal constant _ROLE_247 = 1 << 247;
    uint256 internal constant _ROLE_248 = 1 << 248;
    uint256 internal constant _ROLE_249 = 1 << 249;
    uint256 internal constant _ROLE_250 = 1 << 250;
    uint256 internal constant _ROLE_251 = 1 << 251;
    uint256 internal constant _ROLE_252 = 1 << 252;
    uint256 internal constant _ROLE_253 = 1 << 253;
    uint256 internal constant _ROLE_254 = 1 << 254;
    uint256 internal constant _ROLE_255 = 1 << 255;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Signature verification helper that supports both ECDSA signatures from EOAs
/// and ERC1271 signatures from smart contract wallets like Argent and Gnosis safe.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SignatureCheckerLib.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/SignatureChecker.sol)
///
/// @dev Note:
/// - The signature checking functions use the ecrecover precompile (0x1).
/// - The `bytes memory signature` variants use the identity precompile (0x4)
///   to copy memory internally.
/// - Unlike ECDSA signatures, contract signatures are revocable.
/// - As of Solady version 0.0.134, all `bytes signature` variants accept both
///   regular 65-byte `(r, s, v)` and EIP-2098 `(r, vs)` short form signatures.
///   See: https://eips.ethereum.org/EIPS/eip-2098
///   This is for calldata efficiency on smart accounts prevalent on L2s.
///
/// WARNING! Do NOT use signatures as unique identifiers:
/// - Use a nonce in the digest to prevent replay attacks on the same contract.
/// - Use EIP-712 for the digest to prevent replay attacks across different chains and contracts.
///   EIP-712 also enables readable signing of typed data for better user safety.
/// This implementation does NOT check if a signature is non-malleable.
library SignatureCheckerLib {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*               SIGNATURE CHECKING OPERATIONS                */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns whether `signature` is valid for `signer` and `hash`.
    /// If `signer` is a smart contract, the signature is validated with ERC1271.
    /// Otherwise, the signature is validated with `ECDSA.recover`.
    function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature)
        internal
        view
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            // Clean the upper 96 bits of `signer` in case they are dirty.
            for { signer := shr(96, shl(96, signer)) } signer {} {
                let m := mload(0x40)
                mstore(0x00, hash)
                mstore(0x40, mload(add(signature, 0x20))) // `r`.
                if eq(mload(signature), 64) {
                    let vs := mload(add(signature, 0x40))
                    mstore(0x20, add(shr(255, vs), 27)) // `v`.
                    mstore(0x60, shr(1, shl(1, vs))) // `s`.
                    let t :=
                        staticcall(
                            gas(), // Amount of gas left for the transaction.
                            1, // Address of `ecrecover`.
                            0x00, // Start of input.
                            0x80, // Size of input.
                            0x01, // Start of output.
                            0x20 // Size of output.
                        )
                    // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
                    if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
                        isValid := 1
                        mstore(0x60, 0) // Restore the zero slot.
                        mstore(0x40, m) // Restore the free memory pointer.
                        break
                    }
                }
                if eq(mload(signature), 65) {
                    mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`.
                    mstore(0x60, mload(add(signature, 0x40))) // `s`.
                    let t :=
                        staticcall(
                            gas(), // Amount of gas left for the transaction.
                            1, // Address of `ecrecover`.
                            0x00, // Start of input.
                            0x80, // Size of input.
                            0x01, // Start of output.
                            0x20 // Size of output.
                        )
                    // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
                    if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
                        isValid := 1
                        mstore(0x60, 0) // Restore the zero slot.
                        mstore(0x40, m) // Restore the free memory pointer.
                        break
                    }
                }
                mstore(0x60, 0) // Restore the zero slot.
                mstore(0x40, m) // Restore the free memory pointer.

                let f := shl(224, 0x1626ba7e)
                mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
                mstore(add(m, 0x04), hash)
                let d := add(m, 0x24)
                mstore(d, 0x40) // The offset of the `signature` in the calldata.
                // Copy the `signature` over.
                let n := add(0x20, mload(signature))
                pop(staticcall(gas(), 4, signature, n, add(m, 0x44), n))
                // forgefmt: disable-next-item
                isValid := and(
                    // Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
                    eq(mload(d), f),
                    // Whether the staticcall does not revert.
                    // This must be placed at the end of the `and` clause,
                    // as the arguments are evaluated from right to left.
                    staticcall(
                        gas(), // Remaining gas.
                        signer, // The `signer` address.
                        m, // Offset of calldata in memory.
                        add(returndatasize(), 0x44), // Length of calldata in memory.
                        d, // Offset of returndata.
                        0x20 // Length of returndata to write.
                    )
                )
                break
            }
        }
    }

    /// @dev Returns whether `signature` is valid for `signer` and `hash`.
    /// If `signer` is a smart contract, the signature is validated with ERC1271.
    /// Otherwise, the signature is validated with `ECDSA.recover`.
    function isValidSignatureNowCalldata(address signer, bytes32 hash, bytes calldata signature)
        internal
        view
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            // Clean the upper 96 bits of `signer` in case they are dirty.
            for { signer := shr(96, shl(96, signer)) } signer {} {
                let m := mload(0x40)
                mstore(0x00, hash)
                if eq(signature.length, 64) {
                    let vs := calldataload(add(signature.offset, 0x20))
                    mstore(0x20, add(shr(255, vs), 27)) // `v`.
                    mstore(0x40, calldataload(signature.offset)) // `r`.
                    mstore(0x60, shr(1, shl(1, vs))) // `s`.
                    let t :=
                        staticcall(
                            gas(), // Amount of gas left for the transaction.
                            1, // Address of `ecrecover`.
                            0x00, // Start of input.
                            0x80, // Size of input.
                            0x01, // Start of output.
                            0x20 // Size of output.
                        )
                    // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
                    if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
                        isValid := 1
                        mstore(0x60, 0) // Restore the zero slot.
                        mstore(0x40, m) // Restore the free memory pointer.
                        break
                    }
                }
                if eq(signature.length, 65) {
                    mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`.
                    calldatacopy(0x40, signature.offset, 0x40) // `r`, `s`.
                    let t :=
                        staticcall(
                            gas(), // Amount of gas left for the transaction.
                            1, // Address of `ecrecover`.
                            0x00, // Start of input.
                            0x80, // Size of input.
                            0x01, // Start of output.
                            0x20 // Size of output.
                        )
                    // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
                    if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
                        isValid := 1
                        mstore(0x60, 0) // Restore the zero slot.
                        mstore(0x40, m) // Restore the free memory pointer.
                        break
                    }
                }
                mstore(0x60, 0) // Restore the zero slot.
                mstore(0x40, m) // Restore the free memory pointer.

                let f := shl(224, 0x1626ba7e)
                mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
                mstore(add(m, 0x04), hash)
                let d := add(m, 0x24)
                mstore(d, 0x40) // The offset of the `signature` in the calldata.
                mstore(add(m, 0x44), signature.length)
                // Copy the `signature` over.
                calldatacopy(add(m, 0x64), signature.offset, signature.length)
                // forgefmt: disable-next-item
                isValid := and(
                    // Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
                    eq(mload(d), f),
                    // Whether the staticcall does not revert.
                    // This must be placed at the end of the `and` clause,
                    // as the arguments are evaluated from right to left.
                    staticcall(
                        gas(), // Remaining gas.
                        signer, // The `signer` address.
                        m, // Offset of calldata in memory.
                        add(signature.length, 0x64), // Length of calldata in memory.
                        d, // Offset of returndata.
                        0x20 // Length of returndata to write.
                    )
                )
                break
            }
        }
    }

    /// @dev Returns whether the signature (`r`, `vs`) is valid for `signer` and `hash`.
    /// If `signer` is a smart contract, the signature is validated with ERC1271.
    /// Otherwise, the signature is validated with `ECDSA.recover`.
    function isValidSignatureNow(address signer, bytes32 hash, bytes32 r, bytes32 vs)
        internal
        view
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            // Clean the upper 96 bits of `signer` in case they are dirty.
            for { signer := shr(96, shl(96, signer)) } signer {} {
                let m := mload(0x40)
                mstore(0x00, hash)
                mstore(0x20, add(shr(255, vs), 27)) // `v`.
                mstore(0x40, r) // `r`.
                mstore(0x60, shr(1, shl(1, vs))) // `s`.
                let t :=
                    staticcall(
                        gas(), // Amount of gas left for the transaction.
                        1, // Address of `ecrecover`.
                        0x00, // Start of input.
                        0x80, // Size of input.
                        0x01, // Start of output.
                        0x20 // Size of output.
                    )
                // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
                if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
                    isValid := 1
                    mstore(0x60, 0) // Restore the zero slot.
                    mstore(0x40, m) // Restore the free memory pointer.
                    break
                }

                let f := shl(224, 0x1626ba7e)
                mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
                mstore(add(m, 0x04), hash)
                let d := add(m, 0x24)
                mstore(d, 0x40) // The offset of the `signature` in the calldata.
                mstore(add(m, 0x44), 65) // Length of the signature.
                mstore(add(m, 0x64), r) // `r`.
                mstore(add(m, 0x84), mload(0x60)) // `s`.
                mstore8(add(m, 0xa4), mload(0x20)) // `v`.
                // forgefmt: disable-next-item
                isValid := and(
                    // Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
                    eq(mload(d), f),
                    // Whether the staticcall does not revert.
                    // This must be placed at the end of the `and` clause,
                    // as the arguments are evaluated from right to left.
                    staticcall(
                        gas(), // Remaining gas.
                        signer, // The `signer` address.
                        m, // Offset of calldata in memory.
                        0xa5, // Length of calldata in memory.
                        d, // Offset of returndata.
                        0x20 // Length of returndata to write.
                    )
                )
                mstore(0x60, 0) // Restore the zero slot.
                mstore(0x40, m) // Restore the free memory pointer.
                break
            }
        }
    }

    /// @dev Returns whether the signature (`v`, `r`, `s`) is valid for `signer` and `hash`.
    /// If `signer` is a smart contract, the signature is validated with ERC1271.
    /// Otherwise, the signature is validated with `ECDSA.recover`.
    function isValidSignatureNow(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s)
        internal
        view
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            // Clean the upper 96 bits of `signer` in case they are dirty.
            for { signer := shr(96, shl(96, signer)) } signer {} {
                let m := mload(0x40)
                mstore(0x00, hash)
                mstore(0x20, and(v, 0xff)) // `v`.
                mstore(0x40, r) // `r`.
                mstore(0x60, s) // `s`.
                let t :=
                    staticcall(
                        gas(), // Amount of gas left for the transaction.
                        1, // Address of `ecrecover`.
                        0x00, // Start of input.
                        0x80, // Size of input.
                        0x01, // Start of output.
                        0x20 // Size of output.
                    )
                // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
                if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) {
                    isValid := 1
                    mstore(0x60, 0) // Restore the zero slot.
                    mstore(0x40, m) // Restore the free memory pointer.
                    break
                }

                let f := shl(224, 0x1626ba7e)
                mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
                mstore(add(m, 0x04), hash)
                let d := add(m, 0x24)
                mstore(d, 0x40) // The offset of the `signature` in the calldata.
                mstore(add(m, 0x44), 65) // Length of the signature.
                mstore(add(m, 0x64), r) // `r`.
                mstore(add(m, 0x84), s) // `s`.
                mstore8(add(m, 0xa4), v) // `v`.
                // forgefmt: disable-next-item
                isValid := and(
                    // Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
                    eq(mload(d), f),
                    // Whether the staticcall does not revert.
                    // This must be placed at the end of the `and` clause,
                    // as the arguments are evaluated from right to left.
                    staticcall(
                        gas(), // Remaining gas.
                        signer, // The `signer` address.
                        m, // Offset of calldata in memory.
                        0xa5, // Length of calldata in memory.
                        d, // Offset of returndata.
                        0x20 // Length of returndata to write.
                    )
                )
                mstore(0x60, 0) // Restore the zero slot.
                mstore(0x40, m) // Restore the free memory pointer.
                break
            }
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                     ERC1271 OPERATIONS                     */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    // Note: These ERC1271 operations do NOT have an ECDSA fallback.
    // These functions are intended to be used with the regular `isValidSignatureNow` functions
    // or other signature verification functions (e.g. P256).

    /// @dev Returns whether `signature` is valid for `hash` for an ERC1271 `signer` contract.
    function isValidERC1271SignatureNow(address signer, bytes32 hash, bytes memory signature)
        internal
        view
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            let f := shl(224, 0x1626ba7e)
            mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
            mstore(add(m, 0x04), hash)
            let d := add(m, 0x24)
            mstore(d, 0x40) // The offset of the `signature` in the calldata.
            // Copy the `signature` over.
            let n := add(0x20, mload(signature))
            pop(staticcall(gas(), 4, signature, n, add(m, 0x44), n))
            // forgefmt: disable-next-item
            isValid := and(
                // Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
                eq(mload(d), f),
                // Whether the staticcall does not revert.
                // This must be placed at the end of the `and` clause,
                // as the arguments are evaluated from right to left.
                staticcall(
                    gas(), // Remaining gas.
                    signer, // The `signer` address.
                    m, // Offset of calldata in memory.
                    add(returndatasize(), 0x44), // Length of calldata in memory.
                    d, // Offset of returndata.
                    0x20 // Length of returndata to write.
                )
            )
        }
    }

    /// @dev Returns whether `signature` is valid for `hash` for an ERC1271 `signer` contract.
    function isValidERC1271SignatureNowCalldata(
        address signer,
        bytes32 hash,
        bytes calldata signature
    ) internal view returns (bool isValid) {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            let f := shl(224, 0x1626ba7e)
            mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
            mstore(add(m, 0x04), hash)
            let d := add(m, 0x24)
            mstore(d, 0x40) // The offset of the `signature` in the calldata.
            mstore(add(m, 0x44), signature.length)
            // Copy the `signature` over.
            calldatacopy(add(m, 0x64), signature.offset, signature.length)
            // forgefmt: disable-next-item
            isValid := and(
                // Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
                eq(mload(d), f),
                // Whether the staticcall does not revert.
                // This must be placed at the end of the `and` clause,
                // as the arguments are evaluated from right to left.
                staticcall(
                    gas(), // Remaining gas.
                    signer, // The `signer` address.
                    m, // Offset of calldata in memory.
                    add(signature.length, 0x64), // Length of calldata in memory.
                    d, // Offset of returndata.
                    0x20 // Length of returndata to write.
                )
            )
        }
    }

    /// @dev Returns whether the signature (`r`, `vs`) is valid for `hash`
    /// for an ERC1271 `signer` contract.
    function isValidERC1271SignatureNow(address signer, bytes32 hash, bytes32 r, bytes32 vs)
        internal
        view
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            let f := shl(224, 0x1626ba7e)
            mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
            mstore(add(m, 0x04), hash)
            let d := add(m, 0x24)
            mstore(d, 0x40) // The offset of the `signature` in the calldata.
            mstore(add(m, 0x44), 65) // Length of the signature.
            mstore(add(m, 0x64), r) // `r`.
            mstore(add(m, 0x84), shr(1, shl(1, vs))) // `s`.
            mstore8(add(m, 0xa4), add(shr(255, vs), 27)) // `v`.
            // forgefmt: disable-next-item
            isValid := and(
                // Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
                eq(mload(d), f),
                // Whether the staticcall does not revert.
                // This must be placed at the end of the `and` clause,
                // as the arguments are evaluated from right to left.
                staticcall(
                    gas(), // Remaining gas.
                    signer, // The `signer` address.
                    m, // Offset of calldata in memory.
                    0xa5, // Length of calldata in memory.
                    d, // Offset of returndata.
                    0x20 // Length of returndata to write.
                )
            )
        }
    }

    /// @dev Returns whether the signature (`v`, `r`, `s`) is valid for `hash`
    /// for an ERC1271 `signer` contract.
    function isValidERC1271SignatureNow(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s)
        internal
        view
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            let f := shl(224, 0x1626ba7e)
            mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
            mstore(add(m, 0x04), hash)
            let d := add(m, 0x24)
            mstore(d, 0x40) // The offset of the `signature` in the calldata.
            mstore(add(m, 0x44), 65) // Length of the signature.
            mstore(add(m, 0x64), r) // `r`.
            mstore(add(m, 0x84), s) // `s`.
            mstore8(add(m, 0xa4), v) // `v`.
            // forgefmt: disable-next-item
            isValid := and(
                // Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
                eq(mload(d), f),
                // Whether the staticcall does not revert.
                // This must be placed at the end of the `and` clause,
                // as the arguments are evaluated from right to left.
                staticcall(
                    gas(), // Remaining gas.
                    signer, // The `signer` address.
                    m, // Offset of calldata in memory.
                    0xa5, // Length of calldata in memory.
                    d, // Offset of returndata.
                    0x20 // Length of returndata to write.
                )
            )
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                     ERC6492 OPERATIONS                     */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    // Note: These ERC6492 operations do NOT have an ECDSA fallback.
    // These functions are intended to be used with the regular `isValidSignatureNow` functions
    // or other signature verification functions (e.g. P256).
    // The calldata variants are excluded for brevity.

    /// @dev Returns whether `signature` is valid for `hash`.
    /// If the signature is postfixed with the ERC6492 magic number, it will attempt to
    /// deploy / prepare the `signer` smart account before doing a regular ERC1271 check.
    /// Note: This function is NOT reentrancy safe.
    function isValidERC6492SignatureNowAllowSideEffects(
        address signer,
        bytes32 hash,
        bytes memory signature
    ) internal returns (bool isValid) {
        /// @solidity memory-safe-assembly
        assembly {
            function callIsValidSignature(signer_, hash_, signature_) -> _isValid {
                let m_ := mload(0x40)
                let f_ := shl(224, 0x1626ba7e)
                mstore(m_, f_) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
                mstore(add(m_, 0x04), hash_)
                let d_ := add(m_, 0x24)
                mstore(d_, 0x40) // The offset of the `signature` in the calldata.
                let n_ := add(0x20, mload(signature_))
                pop(staticcall(gas(), 4, signature_, n_, add(m_, 0x44), n_))
                _isValid :=
                    and(
                        eq(mload(d_), f_),
                        staticcall(gas(), signer_, m_, add(returndatasize(), 0x44), d_, 0x20)
                    )
            }
            for { let n := mload(signature) } 1 {} {
                if iszero(eq(mload(add(signature, n)), mul(0x6492, div(not(isValid), 0xffff)))) {
                    isValid := callIsValidSignature(signer, hash, signature)
                    break
                }
                let o := add(signature, 0x20) // Signature bytes.
                let d := add(o, mload(add(o, 0x20))) // Factory calldata.
                if iszero(extcodesize(signer)) {
                    if iszero(call(gas(), mload(o), 0, add(d, 0x20), mload(d), codesize(), 0x00)) {
                        break
                    }
                }
                let s := add(o, mload(add(o, 0x40))) // Inner signature.
                isValid := callIsValidSignature(signer, hash, s)
                if iszero(isValid) {
                    if call(gas(), mload(o), 0, add(d, 0x20), mload(d), codesize(), 0x00) {
                        isValid := callIsValidSignature(signer, hash, s)
                    }
                }
                break
            }
        }
    }

    /// @dev Returns whether `signature` is valid for `hash`.
    /// If the signature is postfixed with the ERC6492 magic number, it will attempt
    /// to use a reverting verifier to deploy / prepare the `signer` smart account
    /// and do a `isValidSignature` check via the reverting verifier.
    /// Note: This function is reentrancy safe.
    /// The reverting verifier must be be deployed.
    /// Otherwise, the function will return false if `signer` is not yet deployed / prepared.
    /// See: https://gist.github.com/Vectorized/846a474c855eee9e441506676800a9ad
    function isValidERC6492SignatureNow(address signer, bytes32 hash, bytes memory signature)
        internal
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            function callIsValidSignature(signer_, hash_, signature_) -> _isValid {
                let m_ := mload(0x40)
                let f_ := shl(224, 0x1626ba7e)
                mstore(m_, f_) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
                mstore(add(m_, 0x04), hash_)
                let d_ := add(m_, 0x24)
                mstore(d_, 0x40) // The offset of the `signature` in the calldata.
                let n_ := add(0x20, mload(signature_))
                pop(staticcall(gas(), 4, signature_, n_, add(m_, 0x44), n_))
                _isValid :=
                    and(
                        eq(mload(d_), f_),
                        staticcall(gas(), signer_, m_, add(returndatasize(), 0x44), d_, 0x20)
                    )
            }
            for { let n := mload(signature) } 1 {} {
                if iszero(eq(mload(add(signature, n)), mul(0x6492, div(not(isValid), 0xffff)))) {
                    isValid := callIsValidSignature(signer, hash, signature)
                    break
                }
                if extcodesize(signer) {
                    let o := add(signature, 0x20) // Signature bytes.
                    isValid := callIsValidSignature(signer, hash, add(o, mload(add(o, 0x40))))
                    if isValid { break }
                }
                let m := mload(0x40)
                mstore(m, signer)
                mstore(add(m, 0x20), hash)
                let willBeZeroIfRevertingVerifierExists :=
                    call(
                        gas(), // Remaining gas.
                        0x00007bd799e4A591FeA53f8A8a3E9f931626Ba7e, // Reverting verifier.
                        0, // Send zero ETH.
                        m, // Start of memory.
                        add(returndatasize(), 0x40), // Length of calldata in memory.
                        staticcall(gas(), 4, add(signature, 0x20), n, add(m, 0x40), n), // 1.
                        0x00 // Length of returndata to write.
                    )
                isValid := gt(returndatasize(), willBeZeroIfRevertingVerifierExists)
                break
            }
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                     HASHING OPERATIONS                     */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns an Ethereum Signed Message, created from a `hash`.
    /// This produces a hash corresponding to the one signed with the
    /// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign)
    /// JSON-RPC method as part of EIP-191.
    function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x20, hash) // Store into scratch space for keccak256.
            mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes.
            result := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`.
        }
    }

    /// @dev Returns an Ethereum Signed Message, created from `s`.
    /// This produces a hash corresponding to the one signed with the
    /// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign)
    /// JSON-RPC method as part of EIP-191.
    /// Note: Supports lengths of `s` up to 999999 bytes.
    function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) {
        /// @solidity memory-safe-assembly
        assembly {
            let sLength := mload(s)
            let o := 0x20
            mstore(o, "\x19Ethereum Signed Message:\n") // 26 bytes, zero-right-padded.
            mstore(0x00, 0x00)
            // Convert the `s.length` to ASCII decimal representation: `base10(s.length)`.
            for { let temp := sLength } 1 {} {
                o := sub(o, 1)
                mstore8(o, add(48, mod(temp, 10)))
                temp := div(temp, 10)
                if iszero(temp) { break }
            }
            let n := sub(0x3a, o) // Header length: `26 + 32 - o`.
            // Throw an out-of-offset error (consumes all gas) if the header exceeds 32 bytes.
            returndatacopy(returndatasize(), returndatasize(), gt(n, 0x20))
            mstore(s, or(mload(0x00), mload(n))) // Temporarily store the header.
            result := keccak256(add(s, sub(0x20, n)), add(n, sLength))
            mstore(s, sLength) // Restore the length.
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                   EMPTY CALLDATA HELPERS                   */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns an empty calldata bytes.
    function emptySignature() internal pure returns (bytes calldata signature) {
        /// @solidity memory-safe-assembly
        assembly {
            signature.length := 0
        }
    }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.28;

interface IRebootTournament {
    struct Entry {
        uint256 ticketBasedCollateral;
        uint256 creditBasedCollateral;
        uint256 totalCollateralPaid;
        uint248 rebuyCount;
        bool payoutReceived;
    }

    struct Result {
        address player;
        uint256 multiplier;
    }

    struct EntryParams {
        address account;
        uint256 tournamentId;
        uint256 roomId;
        address swapFromCredit;
        uint256 swapMinCollateral;
        uint256 entryAmount;
    }

    struct Config {
        uint256 entryFee; // credit fee to enter tournament (new entry for a new score)
        uint256 rebuyFee; // credit fee to rebuy into tournament (re-attempt an existing entry to try to improve score)
        bool isExactFee; // if true, above fees are exact required fees. if false, above fees are minimum fees
        uint64 entryLimit; // max number of entries allowed per player
        uint64 maxEntriesPerRoom; // max number of entries allowed per room
        uint64 rebuyLimit; // max number of rebuys allowed per player per entry
        uint64 startDate; // start date of tournament (block timestamp)
        uint64 endDate; // end date of tournament (block timestamp)
        uint64 ticketProfitToTickets; // % of profit from ticket entry converted to ticket payout (in wei)
        uint64 creditProfitToTickets; // % of profit from credit entry converted to credit payout (in wei)
        uint64 creditEntryToTickets; // % of credit entry converted to ticket payout (in wei)
        uint64 entryDuration; // duration in seconds of entry period for a room once the room is opened
        uint64 tournamentDuration; // duration in seconds of tournament instance (room) once started
        uint64 payoutDuration; // duration in seconds to allow payouts for after tournament ends
        uint96 creditRatio; // ratio of collateral to credits (gets set during register transaction)
        address creditId; // credit id to use for entry fee (the id is the collateral token address)
        uint256 bonusCollateral; // amount of collateral to request each time it's needed
        bytes32 priceFeedPair; // pair to use for price feed (use bytes32(0) for a flat credit fee)
    }

    // Tournament IDs
    function tournamentCount() external view returns (uint256);

    // Allowed to set contract configs and register tournaments
    function ADMIN_ROLE() external view returns (bytes32);

    // Allowed to call `enter`, `rebuy` and `submitResults`
    function RELAYER_ROLE() external view returns (bytes32);

    /**
     * @notice Get the tournament config set by contract admin
     *
     * @param _tournamentId ID of the tournament
     */
    function getTournament(uint256 _tournamentId) external view returns (Config memory);

    /**
     * @notice Opens a new room for an active tournament
     *
     * @param _tournamentId ID of the tournament
     * @param _roomId ID of the tournament room
     * @param _openTimestamp Timestamp to open the room
     */
    function createRoom(uint256 _tournamentId, uint256 _roomId, uint64 _openTimestamp) external;

    /**
     * @notice Enters a player into a tournament room. If the room does not exist, it will be created.
     *
     * @dev A player can only enter a specific room once.
     *
     * @param _params entry params struct
     */
    function enter(EntryParams calldata _params) external payable;

    /**
     * @notice Allows a player to rebuy into a tournament room for a chance to improve their score
     *
     * @param _tournamentId ID of the tournament
     * @param _account Address of player
     * @param _roomId ID of the tournament room
     * @param _swapFromCredit credit token to swap from, use zero address if swap not wanted
     * @param _swapMinCollateral min amount of output collateral to receive from swap
     * @param _entryAmount credit amount to spend to enter - if tournament config has `isExactFee` set to true, must be equivalent to `rebuyFee`. Otherwise, must be >= `rebuyFee`
     */
    function rebuy(
        uint256 _tournamentId,
        address _account,
        uint256 _roomId,
        address _swapFromCredit,
        uint256 _swapMinCollateral,
        uint256 _entryAmount
    ) external payable;

    /**
     * @notice Submits the results of a tournament room. Payouts are calculated and credits are minted to players.
     *
     * @param _tournamentId ID of the tournament
     * @param _roomId ID of the tournament room
     * @param _results Array of results for the room
     */
    function submitResults(uint256 _tournamentId, uint256 _roomId, Result[] memory _results) external;

    function credits() external view returns (address);

    /**
     * @notice Admin function to register a new tournament
     *
     * @param _c Config struct for the tournament
     */
    function register(Config memory _c) external returns (uint256 _tournamentId);

    /**
     * @notice Get the entry for a player in a room
     *
     * @param _tournamentId ID of the tournament
     * @param _player Address of the player
     * @param _roomId ID of the tournament room
     */
    function getEntry(uint256 _tournamentId, address _player, uint256 _roomId) external view returns (Entry memory);

    /**
     * @notice Cleans up a tournament room after the payout duration has ended. Returns any remaining collateral to the game dev or bonus contract.
     *
     * @param _tournamentId ID of the tournament
     * @param _roomId ID of the tournament room
     */
    function cleanup(uint256 _tournamentId, uint256 _roomId) external;
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.28;

enum Direction {
    LONG,
    SHORT
}

struct Trade {
    bytes32 pair;
    uint40 lastUpdatedAt;
    int104 entryPrice;
    int104 amountInBips;
}

struct InternalPrice {
    int64 price;
    int32 expo;
    uint40 publishTime;
}

struct TournamentConfig {
    uint16 minimumHoldDuration;
    uint16 maxLeverage;
    uint40 startTime;
    uint40 endTime;
    uint128 startBalance;
    mapping(bytes32 pair => InternalPrice finalPrice) finalPrices;
    mapping(address user => int256 balance) userBalance;
    mapping(address user => Trade trade) activeTrade;
}

// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

/// @title IPythEvents contains the events that Pyth contract emits.
/// @dev This interface can be used for listening to the updates for off-chain and testing purposes.
interface IPythEvents {
    /// @dev Emitted when the price feed with `id` has received a fresh update.
    /// @param id The Pyth Price Feed ID.
    /// @param publishTime Publish time of the given price update.
    /// @param price Price of the given price update.
    /// @param conf Confidence interval of the given price update.
    event PriceFeedUpdate(
        bytes32 indexed id,
        uint64 publishTime,
        int64 price,
        uint64 conf
    );

    /// @dev Emitted when a batch price update is processed successfully.
    /// @param chainId ID of the source chain that the batch price update comes from.
    /// @param sequenceNumber Sequence number of the batch price update.
    event BatchPriceFeedUpdate(uint16 chainId, uint64 sequenceNumber);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Simple single owner authorization mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol)
///
/// @dev Note:
/// This implementation does NOT auto-initialize the owner to `msg.sender`.
/// You MUST call the `_initializeOwner` in the constructor / initializer.
///
/// While the ownable portion follows
/// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility,
/// the nomenclature for the 2-step ownership handover may be unique to this codebase.
abstract contract Ownable {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The caller is not authorized to call the function.
    error Unauthorized();

    /// @dev The `newOwner` cannot be the zero address.
    error NewOwnerIsZeroAddress();

    /// @dev The `pendingOwner` does not have a valid handover request.
    error NoHandoverRequest();

    /// @dev Cannot double-initialize.
    error AlreadyInitialized();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                           EVENTS                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The ownership is transferred from `oldOwner` to `newOwner`.
    /// This event is intentionally kept the same as OpenZeppelin's Ownable to be
    /// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173),
    /// despite it not being as lightweight as a single argument event.
    event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);

    /// @dev An ownership handover to `pendingOwner` has been requested.
    event OwnershipHandoverRequested(address indexed pendingOwner);

    /// @dev The ownership handover to `pendingOwner` has been canceled.
    event OwnershipHandoverCanceled(address indexed pendingOwner);

    /// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`.
    uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE =
        0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0;

    /// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`.
    uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE =
        0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d;

    /// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`.
    uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE =
        0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                          STORAGE                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The owner slot is given by:
    /// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`.
    /// It is intentionally chosen to be a high value
    /// to avoid collision with lower slots.
    /// The choice of manual storage layout is to enable compatibility
    /// with both regular and upgradeable contracts.
    bytes32 internal constant _OWNER_SLOT =
        0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927;

    /// The ownership handover slot of `newOwner` is given by:
    /// ```
    ///     mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED))
    ///     let handoverSlot := keccak256(0x00, 0x20)
    /// ```
    /// It stores the expiry timestamp of the two-step ownership handover.
    uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                     INTERNAL FUNCTIONS                     */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Override to return true to make `_initializeOwner` prevent double-initialization.
    function _guardInitializeOwner() internal pure virtual returns (bool guard) {}

    /// @dev Initializes the owner directly without authorization guard.
    /// This function must be called upon initialization,
    /// regardless of whether the contract is upgradeable or not.
    /// This is to enable generalization to both regular and upgradeable contracts,
    /// and to save gas in case the initial owner is not the caller.
    /// For performance reasons, this function will not check if there
    /// is an existing owner.
    function _initializeOwner(address newOwner) internal virtual {
        if (_guardInitializeOwner()) {
            /// @solidity memory-safe-assembly
            assembly {
                let ownerSlot := _OWNER_SLOT
                if sload(ownerSlot) {
                    mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`.
                    revert(0x1c, 0x04)
                }
                // Clean the upper 96 bits.
                newOwner := shr(96, shl(96, newOwner))
                // Store the new value.
                sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
                // Emit the {OwnershipTransferred} event.
                log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
            }
        } else {
            /// @solidity memory-safe-assembly
            assembly {
                // Clean the upper 96 bits.
                newOwner := shr(96, shl(96, newOwner))
                // Store the new value.
                sstore(_OWNER_SLOT, newOwner)
                // Emit the {OwnershipTransferred} event.
                log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
            }
        }
    }

    /// @dev Sets the owner directly without authorization guard.
    function _setOwner(address newOwner) internal virtual {
        if (_guardInitializeOwner()) {
            /// @solidity memory-safe-assembly
            assembly {
                let ownerSlot := _OWNER_SLOT
                // Clean the upper 96 bits.
                newOwner := shr(96, shl(96, newOwner))
                // Emit the {OwnershipTransferred} event.
                log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
                // Store the new value.
                sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
            }
        } else {
            /// @solidity memory-safe-assembly
            assembly {
                let ownerSlot := _OWNER_SLOT
                // Clean the upper 96 bits.
                newOwner := shr(96, shl(96, newOwner))
                // Emit the {OwnershipTransferred} event.
                log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
                // Store the new value.
                sstore(ownerSlot, newOwner)
            }
        }
    }

    /// @dev Throws if the sender is not the owner.
    function _checkOwner() internal view virtual {
        /// @solidity memory-safe-assembly
        assembly {
            // If the caller is not the stored owner, revert.
            if iszero(eq(caller(), sload(_OWNER_SLOT))) {
                mstore(0x00, 0x82b42900) // `Unauthorized()`.
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Returns how long a two-step ownership handover is valid for in seconds.
    /// Override to return a different value if needed.
    /// Made internal to conserve bytecode. Wrap it in a public function if needed.
    function _ownershipHandoverValidFor() internal view virtual returns (uint64) {
        return 48 * 3600;
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                  PUBLIC UPDATE FUNCTIONS                   */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Allows the owner to transfer the ownership to `newOwner`.
    function transferOwnership(address newOwner) public payable virtual onlyOwner {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(shl(96, newOwner)) {
                mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`.
                revert(0x1c, 0x04)
            }
        }
        _setOwner(newOwner);
    }

    /// @dev Allows the owner to renounce their ownership.
    function renounceOwnership() public payable virtual onlyOwner {
        _setOwner(address(0));
    }

    /// @dev Request a two-step ownership handover to the caller.
    /// The request will automatically expire in 48 hours (172800 seconds) by default.
    function requestOwnershipHandover() public payable virtual {
        unchecked {
            uint256 expires = block.timestamp + _ownershipHandoverValidFor();
            /// @solidity memory-safe-assembly
            assembly {
                // Compute and set the handover slot to `expires`.
                mstore(0x0c, _HANDOVER_SLOT_SEED)
                mstore(0x00, caller())
                sstore(keccak256(0x0c, 0x20), expires)
                // Emit the {OwnershipHandoverRequested} event.
                log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller())
            }
        }
    }

    /// @dev Cancels the two-step ownership handover to the caller, if any.
    function cancelOwnershipHandover() public payable virtual {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute and set the handover slot to 0.
            mstore(0x0c, _HANDOVER_SLOT_SEED)
            mstore(0x00, caller())
            sstore(keccak256(0x0c, 0x20), 0)
            // Emit the {OwnershipHandoverCanceled} event.
            log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller())
        }
    }

    /// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`.
    /// Reverts if there is no existing ownership handover requested by `pendingOwner`.
    function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute and set the handover slot to 0.
            mstore(0x0c, _HANDOVER_SLOT_SEED)
            mstore(0x00, pendingOwner)
            let handoverSlot := keccak256(0x0c, 0x20)
            // If the handover does not exist, or has expired.
            if gt(timestamp(), sload(handoverSlot)) {
                mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`.
                revert(0x1c, 0x04)
            }
            // Set the handover slot to 0.
            sstore(handoverSlot, 0)
        }
        _setOwner(pendingOwner);
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                   PUBLIC READ FUNCTIONS                    */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns the owner of the contract.
    function owner() public view virtual returns (address result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := sload(_OWNER_SLOT)
        }
    }

    /// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`.
    function ownershipHandoverExpiresAt(address pendingOwner)
        public
        view
        virtual
        returns (uint256 result)
    {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute the handover slot.
            mstore(0x0c, _HANDOVER_SLOT_SEED)
            mstore(0x00, pendingOwner)
            // Load the handover slot.
            result := sload(keccak256(0x0c, 0x20))
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         MODIFIERS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Marks a function as only callable by the owner.
    modifier onlyOwner() virtual {
        _checkOwner();
        _;
    }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):