ETH Price: $3,178.64 (-2.76%)

Contract

0x8473d4aFc3f43A4f9B5e105820fa67E66FB0C7B4
 

Overview

ETH Balance

0.063522157353164663 ETH

Eth Value

$201.91 (@ $3,178.64/ETH)

Token Holdings

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Update Root216443652025-01-17 12:58:4744 hrs ago1737118727IN
0x8473d4aF...66FB0C7B4
0 ETH0.000212737.7057926
Update Root216443632025-01-17 12:58:2344 hrs ago1737118703IN
0x8473d4aF...66FB0C7B4
0 ETH0.000213137.00945323
Update Root216367342025-01-16 11:25:472 days ago1737026747IN
0x8473d4aF...66FB0C7B4
0 ETH0.000161135.29917585
Update Root216334762025-01-16 0:30:233 days ago1736987423IN
0x8473d4aF...66FB0C7B4
0 ETH0.000164655.41499408
Update Root216317222025-01-15 18:37:473 days ago1736966267IN
0x8473d4aF...66FB0C7B4
0 ETH0.0004510314.83340493
Update Root216248492025-01-14 19:34:474 days ago1736883287IN
0x8473d4aF...66FB0C7B4
0 ETH0.0002096.87355965
Update Root216241382025-01-14 17:11:354 days ago1736874695IN
0x8473d4aF...66FB0C7B4
0 ETH0.0004020713.22323013
Cancel Challenge215917512025-01-10 4:40:119 days ago1736484011IN
0x8473d4aF...66FB0C7B4
0 ETH0.000284413.0195225
Resolve Group Ch...215912302025-01-10 2:55:119 days ago1736477711IN
0x8473d4aF...66FB0C7B4
0 ETH0.000690064.8407639
Update Root215886142025-01-09 18:09:359 days ago1736446175IN
0x8473d4aF...66FB0C7B4
0 ETH0.0003545911.66165869
Join Challenge215878502025-01-09 15:35:359 days ago1736436935IN
0x8473d4aF...66FB0C7B4
0.00329498 ETH0.0034380714.75120469
Create Challenge215877842025-01-09 15:22:239 days ago1736436143IN
0x8473d4aF...66FB0C7B4
0.0033 ETH0.003846311.51821726
Join Challenge215877122025-01-09 15:07:599 days ago1736435279IN
0x8473d4aF...66FB0C7B4
0.0032 ETH0.0026022711.16631184
Create Challenge215876982025-01-09 15:05:119 days ago1736435111IN
0x8473d4aF...66FB0C7B4
0.0032 ETH0.0038372111.49182114
Update Root215875222025-01-09 14:29:239 days ago1736432963IN
0x8473d4aF...66FB0C7B4
0 ETH0.0004278315.50414171
Update Root215875142025-01-09 14:27:479 days ago1736432867IN
0x8473d4aF...66FB0C7B4
0 ETH0.0004401415.95029431
Update Root215873762025-01-09 13:59:599 days ago1736431199IN
0x8473d4aF...66FB0C7B4
0 ETH0.0004492916.28165045
Update Root215873722025-01-09 13:59:119 days ago1736431151IN
0x8473d4aF...66FB0C7B4
0 ETH0.000411214.90142568
Update Root215872502025-01-09 13:34:479 days ago1736429687IN
0x8473d4aF...66FB0C7B4
0 ETH0.000194157.0357512
Update Root215872452025-01-09 13:33:479 days ago1736429627IN
0x8473d4aF...66FB0C7B4
0 ETH0.000199827.24128257
Update Root215864522025-01-09 10:53:359 days ago1736420015IN
0x8473d4aF...66FB0C7B4
0 ETH0.000131824.77706763
Update Root215864412025-01-09 10:51:239 days ago1736419883IN
0x8473d4aF...66FB0C7B4
0 ETH0.000141455.12624822
Update Root215802052025-01-08 13:58:3510 days ago1736344715IN
0x8473d4aF...66FB0C7B4
0 ETH0.000256948.45336958
Update Root215733322025-01-07 14:56:5911 days ago1736261819IN
0x8473d4aF...66FB0C7B4
0 ETH0.000258738.50921999
Withdraw215733082025-01-07 14:52:1111 days ago1736261531IN
0x8473d4aF...66FB0C7B4
0 ETH0.0006217911.4549897
View all transactions

Latest 8 internal transactions

Advanced mode:
Parent Transaction Hash Block
From
To
214444182024-12-20 14:46:3529 days ago1734705995
0x8473d4aF...66FB0C7B4
0.00938943 ETH
214354162024-12-19 8:36:4731 days ago1734597407
0x8473d4aF...66FB0C7B4
0.00528365 ETH
214137472024-12-16 8:00:5934 days ago1734336059
0x8473d4aF...66FB0C7B4
0.00527054 ETH
214070662024-12-15 9:36:4734 days ago1734255407
0x8473d4aF...66FB0C7B4
0.00849552 ETH
213428532024-12-06 10:27:2343 days ago1733480843
0x8473d4aF...66FB0C7B4
0.01173115 ETH
213378372024-12-05 17:38:4744 days ago1733420327
0x8473d4aF...66FB0C7B4
0.00579 ETH
212800432024-11-27 15:40:3552 days ago1732722035
0x8473d4aF...66FB0C7B4
0.04978134 ETH
212752382024-11-26 23:33:4753 days ago1732664027
0x8473d4aF...66FB0C7B4
0.00382474 ETH
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
P2PSports

Compiler Version
v0.8.19+commit.7dd6d404

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion
File 1 of 22 : P2PSports.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

import "@solarity/solidity-lib/libs/arrays/SetHelper.sol";
import "@solarity/solidity-lib/libs/arrays/ArrayHelper.sol";
import "@solarity/solidity-lib/libs/utils/TypeCaster.sol";
import "@solarity/solidity-lib/libs/decimals/DecimalsConverter.sol";

import "./interfaces/IP2PSports.sol";

import "@solarity/solidity-lib/utils/Globals.sol";

/// @title P2PSports: A Peer-to-Peer Sports Betting Smart Contract
/** @notice This contract allows users to create and join sports betting challenges, bet on outcomes,
 * and withdraw winnings in a decentralized manner. It supports betting with STMX token and other ERC20 tokens, along with ETH
 * and uses Chainlink for price feeds to calculate admin shares.
 * @dev The contract uses OpenZeppelin's Ownable and ReentrancyGuard for access control and reentrancy protection,
 * and utilizes libraries from solidity-lib for array and decimal manipulations.
 *
 * ERROR CODES: In order to reduce the size of the Smart Contract, we have defined the short codes instead of the complete
 * error messages in the revert/require statements. The messages corresponding to the error codes can be seen in the following document.
 * https://duelnow.notion.site/Smart-Contract-Error-Codes-ca7427520ce04ca293d3e21fb1e21583
 */
contract P2PSports is IP2PSports, Ownable2Step, ReentrancyGuard {
    using SafeERC20 for IERC20;
    using EnumerableSet for EnumerableSet.AddressSet;
    using EnumerableSet for EnumerableSet.UintSet;
    using SetHelper for EnumerableSet.AddressSet;
    using DecimalsConverter for *;
    using ArrayHelper for uint256[];
    using TypeCaster for *;

    /// @notice merkle tree root node
    bytes32 private merkleRoot;

    /// @notice Backend server address to resolve, cancel challenges or some additional control.
    address public backend;
    /// @notice Token address for the STMX token, used as one of the betting currencies

    // Configuration / Validation parameters for different betting logics
    uint256 public maxChallengersEachSide;
    uint256 public maxChallengersForPickem;
    uint256 public minUSDBetAmount;
    bool public applyMembershipValues;

    /// @notice ChallengeId of the last challenge created
    uint256 public latestChallengeId;

    /// @notice Flag to allow or restrict creations or joining challenges
    bool public bettingAllowed;

    uint256 public constant maxAdminShareInUsd = 1000 * 10 ** 8;
    uint256 public constant maxAdminShareSTMX = 100000 * 10 ** 18;
    uint256 public constant maxForMinUSDBetAmount = 100 * 10 ** 8;
    uint256 public constant maxChallengesToResolve = 10;
    uint256 public constant maxWinnersGroupChallenge = 10;
    uint256 public constant awaitingTimeForPublicCancel = 172800; //48 hours
    uint256 public constant defaultOracleDecimals = 8;
    uint256 public constant maxAdminShareThresholds = 20;
    uint256 public constant priceFeedErrorMargin = 5;

    // Internal storage for tokens, price feeds, challenges, and bets
    EnumerableSet.AddressSet internal _allTokens;
    EnumerableSet.AddressSet internal _oraclessTokens;
    EnumerableSet.AddressSet internal _allowedTokens;

    mapping(address => AggregatorV3Interface) internal _priceFeeds;
    mapping(uint256 => Challenge) internal _challenges;
    mapping(address => mapping(uint256 => UserBet)) internal _userChallengeBets;
    mapping(address => mapping(address => uint256)) internal _withdrawables;
    mapping(address => AdminShareRule) internal _adminShareRules;
    mapping(address => uint256) internal _oraclessTokensMinBetAmount;
    /// @dev Ensures the function is called only by the backend address
    modifier onlyBackend() {
        _onlyBackend();
        _;
    }

    /// @dev Ensures the function is called only by the backend address or owner address
    modifier onlyBackendOrOwner() {
        _onlyBackendOrOwner();
        _;
    }

    /// @notice Initializes the contract with provided addresses and tokens
    /** @dev Sets initial configuration for the contract and allows specified tokens.
     * @param backend_ Address of the backend server for challenge resolution and control
     */
    constructor(address backend_) {
        require(backend_ != address(0), "1");

        // Contract setup and initial token allowance
        backend = backend_;
        maxChallengersEachSide = 50;
        maxChallengersForPickem = 50;
        bettingAllowed = true;
        minUSDBetAmount = 10 * 10 ** 8;
    }

    receive() external payable {}

    /// @notice Creates a new challenge for betting
    /** @dev Emits a `ChallengeCreated` event and calls `joinChallenge` for the challenge creator.
     * @param token Address of the token used for betting (zero address for native currency)
     * @param amountFromWallet Amount to be bet from the creator's wallet
     * @param amountFromWithdrawables Amount to be bet from the creator's withdrawable balance
     * @param decision The side of the bet the creator is taking
     * @param challengeType The type of challenge (Individual or Group)
     * @param startTime Start time of the challenge
     * @param endTime End time of the challenge
     * @param membershipLevel user membership level
     * @param feePercentage percentage amount reduced from admin share
     * @param referrer referrer address
     * @param referralCommision referral will get the comission from admin share
     * @param proof leaf nood proof
     */

    function createChallenge(
        address token,
        uint256 amountFromWallet,
        uint256 amountFromWithdrawables,
        uint8 decision,
        ChallengeType challengeType,
        uint256 startTime,
        uint256 endTime,
        uint8 membershipLevel,
        uint256 feePercentage,
        address referrer,
        uint256 referralCommision,
        bytes32[] memory proof
    ) external payable {
        uint256 challengeId = ++latestChallengeId;

        require(startTime <= block.timestamp && endTime > block.timestamp, "2");
        require(_allowedTokens.contains(token), "3");

        Challenge storage _challenge = _challenges[challengeId];

        _challenge.token = token;
        _challenge.status = ChallengeStatus.Betting;
        _challenge.challengeType = challengeType;
        _challenge.startTime = startTime;
        _challenge.endTime = endTime;

        emit ChallengeCreated(
            challengeId,
            token,
            msg.sender,
            amountFromWallet + amountFromWithdrawables
        );

        joinChallenge(
            challengeId,
            amountFromWallet,
            amountFromWithdrawables,
            decision,
            membershipLevel,
            feePercentage,
            referrer,
            referralCommision,
            proof
        );
    }

    /// @notice Allows users to join an existing challenge with their bet
    /** @dev Emits a `ChallengeJoined` event if the join is successful.
     * @param challengeId ID of the challenge to join
     * @param amountFromWallet Amount to be bet from the user's wallet
     * @param amountFromWithdrawables Amount to be bet from the user's withdrawable balance
     * @param decision The side of the bet the user is taking
     * @param membershipLevel user membership level
     * @param feePercentage percentage amount reduced from admin share
     * @param referrer referrer address
     * @param referralCommision referral will get the comission from admin share
     * @param proof leaf nood proof
     */
    function joinChallenge(
        uint256 challengeId,
        uint256 amountFromWallet,
        uint256 amountFromWithdrawables,
        uint8 decision,
        uint8 membershipLevel,
        uint256 feePercentage,
        address referrer,
        uint256 referralCommision,
        bytes32[] memory proof
    ) public payable {
        Challenge memory challengeDetails = _challenges[challengeId];

        _assertChallengeExistence(challengeId);
        if (challengeDetails.challengeType == ChallengeType.Group) {
            require(decision == 1, "4");
        } else {
            require(decision == 1 || decision == 2, "5");
        }
        require(_userChallengeBets[msg.sender][challengeId].decision == 0, "6");

        _joinChallenge(
            challengeId,
            amountFromWallet,
            amountFromWithdrawables,
            decision,
            membershipLevel,
            feePercentage,
            referrer,
            referralCommision,
            proof
        );
    }

    /// @notice Allows users to increase the bet amount
    /** @dev Emits a `BetAmountIncreased` event if the join is successful.
     * @param challengeId ID of the challenge for which user wants to increase the bet amount
     * @param amountFromWallet Amount to be bet from the user's wallet
     * @param amountFromWithdrawables Amount to be bet from the user's withdrawable balance
     * @param membershipLevel user membership level
     * @param feePercentage percentage amount reduced from admin share
     * @param referrer referrer address
     * @param referralCommision referral will get the comission from admin share
     * @param proof leaf nood proof
     */
    function increaseBetAmount(
        uint256 challengeId,
        uint256 amountFromWallet,
        uint256 amountFromWithdrawables,
        uint8 membershipLevel,
        uint256 feePercentage,
        address referrer,
        uint256 referralCommision,
        bytes32[] memory proof
    ) public payable {
        _assertChallengeExistence(challengeId);
        UserBet storage betDetails = _userChallengeBets[msg.sender][challengeId];
        require(betDetails.decision != 0, "7");
        Challenge storage challengeDetails = _challenges[challengeId];

        (
            uint256 amount,
            uint256 adminShare,
            uint256 referralCommisionAmount
        ) = _calculateChallengeAmounts(
                challengeDetails.token,
                amountFromWallet,
                amountFromWithdrawables,
                challengeId,
                membershipLevel,
                feePercentage,
                referrer,
                referralCommision,
                proof,
                false
            );

        betDetails.amount += amount;
        betDetails.adminShare += adminShare;
        betDetails.referralCommision += referralCommisionAmount;

        if (betDetails.decision == 1) {
            challengeDetails.amountFor += amount;
        } else {
            challengeDetails.amountAgainst += amount;
        }

        emit BetAmountIncreased(
            challengeId,
            betDetails.amount,
            amount,
            msg.sender,
            challengeDetails.token
        );
    }

    /// @notice Checks if a challenge with the given ID exists
    /** @dev A challenge is considered to exist if its ID is greater than 0 and less than or equal to the latest challenge ID.
     * @param challengeId The ID of the challenge to check.
     * @return bool Returns true if the challenge exists, false otherwise.
     */
    function challengeExists(uint256 challengeId) public view returns (bool) {
        return challengeId > 0 && challengeId <= latestChallengeId;
    }

    /// @notice Owner can update the root node of merkle
    /** @dev This function will allow the owner to update the root node of merkle tree
     * @param _root root node of merkle tree
     */
    function updateRoot(bytes32 _root) public onlyBackend {
        merkleRoot = _root;
        emit MerkleRootUpdated(_root, msg.sender);
    }

    /// @notice Withdraws available tokens for the sender
    /** @dev This function allows users to withdraw their available tokens from the contract. It uses the
     * nonReentrant modifier from OpenZeppelin to prevent reentrancy attacks. A `UserWithdrawn` event is
     * emitted upon a successful withdrawal.
     * @param token The address of the token to be withdrawn. Use the zero address for the native currency.
     *
     * Requirements:
     * - The sender must have a positive withdrawable balance for the specified token.
     * Emits a {UserWithdrawn} event indicating the token, amount, and the user who performed the withdrawal.
     */
    function withdraw(address token) external nonReentrant {
        uint256 amount = _withdrawables[msg.sender][token];
        require(amount > 0, "8");
        delete _withdrawables[msg.sender][token];
        _withdraw(token, msg.sender, amount);

        emit UserWithdrawn(token, amount, msg.sender);
    }

    /// @notice Resolves multiple challenges with their final outcomes
    /** @dev This function is called by the backend to resolve challenges that have reached their end time
     * and are in the awaiting status. It updates the status of each challenge based on its final outcome.
     * Only challenges of type `Individual` can be resolved using this function. A `ChallengeResolved` event is
     * emitted for each challenge that is resolved. This function uses the `onlyBackend` modifier to ensure
     * that only authorized backend addresses can call it, and `nonReentrant` to prevent reentrancy attacks.
     * @param challengeIds Array of IDs of the challenges to be resolved.
     * @param finalOutcomes Array of final outcomes for each challenge, where outcomes are defined as follows:
     * - 1: Side A wins,
     * - 2: Side B wins,
     * - 3: Draw.
     *
     * Requirements:
     * - The lengths of `challengeIds` and `finalOutcomes` must be the same and not exceed `maxChallengesToResolve`.
     * - Each challenge must exist, be in the `Awaiting` status, and be of type `Individual`.
     * - Each `finalOutcome` must be within the range [1,3].
     */
    function resolveChallenge(
        uint256[] memory challengeIds,
        uint8[] memory finalOutcomes
    ) external onlyBackend nonReentrant {
        uint256 challengeIdsLength = challengeIds.length;
        require(
            challengeIdsLength <= maxChallengesToResolve &&
                challengeIdsLength == finalOutcomes.length,
            "9"
        );
        for (uint256 i = 0; i < challengeIdsLength; ++i) {
            uint256 challengeId = challengeIds[i];
            Challenge storage challengeDetails = _challenges[challengeId];
            require(challengeDetails.challengeType == ChallengeType.Individual, "10");
            uint8 finalOutcome = finalOutcomes[i];
            require(finalOutcome > 0 && finalOutcome < 4, "11");
            _assertChallengeExistence(challengeId);
            _assertResolveableStatus(challengeId);

            challengeDetails.status = ChallengeStatus(finalOutcome + 4);

            emit ChallengeResolved(challengeId, finalOutcome);

            if (finalOutcome == 3) {
                _cancelBets(challengeId, 0);
            } else {
                _calculateChallenge(challengeId, finalOutcome);
            }
        }
    }

    /// @notice Cancels a user's participation in a challenge
    /** @dev This function allows the backend to cancel a user's participation in a challenge, refunding their bet.
     * It can only be called by the backend and is protected against reentrancy attacks. The function checks if the
     * challenge exists and ensures that the challenge is either in the `Awaiting` or `Betting` status, implying that
     * it has not been resolved yet. Additionally, it verifies that the user has indeed placed a bet on the challenge.
     * After these checks, it calls an internal function `_cancelParticipation` to handle the logic for cancelling the
     * user's participation and processing the refund.
     * @param user The address of the user whose participation is to be cancelled.
     * @param challengeId The ID of the challenge from which the user's participation is to be cancelled.
     *
     * Requirements:
     * - The challenge must exist and be in a state where participation can be cancelled (`Awaiting` or `Betting`).
     * - The user must have participated in the challenge.
     * Uses the `onlyBackend` modifier to ensure only the backend can invoke this function, and `nonReentrant` for security.
     */
    function cancelParticipation(
        address user,
        uint256 challengeId,
        uint8 cancelType
    ) external onlyBackend nonReentrant {
        _assertChallengeExistence(challengeId);
        _assertCancelableStatus(challengeId);
        require(_userChallengeBets[user][challengeId].decision != 0, "12");

        _cancelParticipation(user, challengeId, cancelType);
    }

    /// @notice Resolves a group challenge by determining winners and distributing profits
    /** @dev This function is used for resolving group challenges specifically, where multiple participants can win.
     * It can only be executed by the backend and is protected against reentrancy. The function ensures that the
     * challenge exists, is currently awaiting resolution, and is of the `Group` challenge type. It then validates
     * that the lengths of the winners and profits arrays match and do not exceed the maximum number of winners allowed.
     * Each winner's address must have participated in the challenge, and winners must be unique. The total of the profits
     * percentages must equal 100. Once validated, the challenge status is updated, and profits are calculated and
     * distributed to the winners based on the provided profits percentages.
     * @param challengeId The ID of the group challenge to resolve.
     * @param winners An array of addresses of the winners of the challenge.
     * @param profits An array of profit percentages corresponding to each winner, summing to 100.
     *
     * Requirements:
     * - The challenge must exist, be in the `Awaiting` status, and be of the `Group` type.
     * - The `winners` and `profits` arrays must have the same length and comply with the maximum winners limit.
     * - The sum of the `profits` percentages must equal 100.
     * Emits a {ChallengeResolved} event with the challenge ID and a hardcoded outcome of `5`, indicating group resolution.
     */
    function resolveGroupChallenge(
        uint256 challengeId,
        address[] calldata winners,
        uint256[] calldata profits
    ) external onlyBackend nonReentrant {
        _assertChallengeExistence(challengeId);
        _assertResolveableStatus(challengeId);
        Challenge storage challengeDetails = _challenges[challengeId];
        require(challengeDetails.challengeType == ChallengeType.Group, "13");
        uint256 winnersLength = winners.length;
        require(
            winnersLength == profits.length && winnersLength <= maxWinnersGroupChallenge,
            "14"
        );

        uint256 totalProfit = 0;
        for (uint256 i = 0; i < winnersLength; ++i) {
            totalProfit += profits[i];
            if (i > 0) {
                require(winners[i] > winners[i - 1], "16");
            }
            require(_userChallengeBets[winners[i]][challengeId].decision != 0, "15");
        }

        require(totalProfit == (100 * DECIMAL), "17");

        challengeDetails.status = ChallengeStatus.ResolvedFor;

        emit ChallengeResolved(challengeId, 5);

        _calculateGroupChallenge(challengeId, winners, profits);
    }

    /// @notice Cancels a challenge and refunds all participants
    /** @dev This function allows the backend to cancel a challenge if it's either awaiting resolution or still open for betting.
     * It ensures that the challenge exists and is in a cancelable state (either `Awaiting` or `Betting`). Upon cancellation,
     * the challenge's status is updated to `Canceled`, and all bets placed on the challenge are refunded to the participants.
     * This function is protected by the `onlyBackend` modifier to restrict access to the backend address, and `nonReentrant`
     * to prevent reentrancy attacks.
     * @param challengeId The ID of the challenge to be cancelled.
     * @param cancelType 0-Return bet amount without admin shares 1-Return bet amount with admin shares.
     *
     * Requirements:
     * - The challenge must exist and be in a state that allows cancellation (`Awaiting` or `Betting`).
     * Emits a {ChallengeCanceled} event upon successful cancellation, indicating which challenge was cancelled.
     */
    function cancelChallenge(
        uint256 challengeId,
        uint8 cancelType
    ) external onlyBackendOrOwner nonReentrant {
        _assertChallengeExistence(challengeId);
        _assertCancelableStatus(challengeId);
        Challenge storage challengeDetails = _challenges[challengeId];
        if (msg.sender == owner()) {
            require(
                (challengeDetails.endTime + awaitingTimeForPublicCancel) < block.timestamp,
                "18"
            );
        }

        challengeDetails.status = ChallengeStatus.Canceled;

        emit ChallengeCanceled(challengeId);

        _cancelBets(challengeId, cancelType);
    }

    /// @notice Toggles the ability for users to place bets on challenges
    /** @dev This function allows the contract owner to enable or disable betting across the platform.
     * It's a straightforward toggle that sets the `bettingAllowed` state variable based on the input.
     * Access to this function is restricted to the contract owner through the `onlyOwner` modifier from
     * OpenZeppelin's Ownable contract, ensuring that only the owner can change the betting policy.
     * @param value_ A boolean indicating whether betting should be allowed (`true`) or not (`false`).
     */
    function allowBetting(bool value_) external onlyOwner {
        bettingAllowed = value_;
        emit BettingAllowed(value_, msg.sender);
    }

    /// @notice Toggles the ability for membership discount and referral comisions
    /** @dev This function will allow the owner to toggle the apply membership values
     * @param value_ true to apply membership values and false for disable membership values
     */
    function updateApplyMembershipValues(bool value_) external onlyOwner {
        applyMembershipValues = value_;
        emit MembershipApplied(value_, msg.sender);
    }

    /// @notice Updates the minimum USD betting amount.
    /// @dev Can only be called by the contract owner.
    /// @param value_ The new minimum betting amount in USD.
    function changeMinUSDBettingAmount(uint256 value_) external onlyOwner {
        require(value_ >= minUSDBetAmount && value_ <= maxForMinUSDBetAmount, "28");
        minUSDBetAmount = value_;
        emit MinUSDBettingAmountUpdated(value_, msg.sender);
    }

    /// @notice Updates the address of the backend responsible for challenge resolutions and administrative actions
    /** @dev This function allows the contract owner to change the backend address to a new one.
     * Ensures the new backend address is not the zero address to prevent rendering the contract unusable.
     * The function is protected by the `onlyOwner` modifier, ensuring that only the contract owner has the authority
     * to update the backend address. This is crucial for maintaining the integrity and security of the contract's
     * administrative functions.
     * @param backend_ The new address to be set as the backend. It must be a non-zero address.
     *
     * Requirements:
     * - The new backend address cannot be the zero address, ensuring that the function call has meaningful intent.
     */
    function changeBackend(address backend_) external onlyOwner {
        require(backend_ != address(0), "1");
        backend = backend_;
        emit BackendChanged(backend_, msg.sender);
    }

    /// @notice Allows a batch of tokens to be used for betting, with optional price feeds for valuation
    /** @dev This function permits the contract owner to add tokens to the list of those allowed for betting.
     * It also associates Chainlink price feeds with tokens, enabling the conversion of bets to a common value basis for calculations.
     * Tokens without a specified price feed (address(0)) are considered to have fixed or known values and are added to a separate list.
     * The function ensures that each token in the input array has a corresponding price feed address (which can be the zero address).
     * The `onlyOwner` modifier restricts this function's execution to the contract's owner, safeguarding against unauthorized token addition.
     * @param tokens An array of token addresses to be allowed for betting.
     * @param priceFeeds An array of Chainlink price feed addresses corresponding to the tokens. Use address(0) for tokens without a need for price feeds.
     * @param minBetAmounts An array of amount corresponding to every token being allowed, the value for oracless tokens will be considers only in this method.
     * Requirements:
     * - The lengths of the `tokens` and `priceFeeds` arrays must match to ensure each token has a corresponding price feed address.
     */
    function allowTokens(
        address[] memory tokens,
        address[] memory priceFeeds,
        uint256[] memory minBetAmounts
    ) public onlyOwner {
        uint256 tokensLength = tokens.length;
        uint256 priceFeedLength = priceFeeds.length;
        uint256 minBetAmountsLength = minBetAmounts.length;
        require(tokensLength == priceFeedLength && tokensLength == minBetAmountsLength, "9");

        for (uint256 i = 0; i < tokensLength; ++i) {
            require(!_allowedTokens.contains(tokens[i]), "46");

            if (priceFeeds[i] == address(0)) {
                require(minBetAmounts[i] > 0, "20");
                _oraclessTokensMinBetAmount[tokens[i]] = minBetAmounts[i];
                _oraclessTokens.add(tokens[i]);
            } else {
                isValidPriceFeed(priceFeeds[i], priceFeedErrorMargin);
                _priceFeeds[tokens[i]] = AggregatorV3Interface(priceFeeds[i]);
            }
        }
        _allowedTokens.add(tokens);
        _allTokens.add(tokens);

        emit TokenAllowed(tokens, priceFeeds, minBetAmounts, msg.sender);
    }

    /// @notice Removes a batch of tokens from being allowed for betting and deletes associated price feeds
    /** @dev This function enables the contract owner to restrict certain tokens from being used in betting activities.
     * It involves removing tokens from the list of allowed tokens, potentially removing them from the list of tokens
     * without a Chainlink price feed (oracless tokens), and deleting their associated price feeds if any were set.
     * This is a crucial administrative function for managing the tokens that can be used on the platform, allowing
     * for adjustments based on compliance, liquidity, or other operational considerations.
     * Execution is restricted to the contract's owner through the `onlyOwner` modifier, ensuring that token restrictions
     * can only be imposed by authorized parties.
     * @param tokens An array of token addresses that are to be restricted from use in betting.
     */
    function restrictTokens(address[] memory tokens) external onlyOwner {
        uint256 tokensLength = tokens.length;
        for (uint256 i = 0; i < tokensLength; ++i) {
            require(_allowedTokens.contains(tokens[i]), "47");
            delete _priceFeeds[tokens[i]];
            delete _oraclessTokensMinBetAmount[tokens[i]];
        }

        _allowedTokens.remove(tokens);
        _oraclessTokens.remove(tokens);

        emit TokenRestricted(tokens, msg.sender);
    }

    /// @notice Sets the rules for administrative shares on betting winnings based on thresholds
    /** @dev Allows the contract owner to define how administrative shares (a portion of betting winnings) are calculated.
     * This can be configured differently for the STMX token versus other tokens, as indicated by the `isSTMX` flag.
     * Each entry in the `thresholds` and `sharesInUSD` arrays defines a tier: if the winnings fall into a certain threshold,
     * the corresponding percentage is applied as the administrative share. The function enforces ascending order for thresholds
     * and ensures that the share in USD do not exceed a maximum limit. This setup allows for flexible configuration
     * of administrative fees based on the amount won.
     * Access is restricted to the contract owner through the `onlyOwner` modifier, ensuring that only they can set these rules.
     * @param thresholds An array of threshold values, each representing the lower bound of a winnings bracket.
     * @param sharesInUSD An array of sharesInUSD corresponding to each threshold, defining the admin share for that bracket.
     * @param token Token address.
     * @param isSTMX A boolean flag indicating whether these rules apply to the STMX token (`true`) or other tokens (`false`).
     *
     * Requirements:
     * - The `thresholds` and `sharesInUSD` arrays must be of equal length and not empty, ensuring each threshold has a corresponding percentage.
     * - Thresholds must be in ascending order, and all sharesInUSD must not exceed the predefined maximum admin share percentage.
     */
    function setAdminShareRules(
        uint256[] memory thresholds,
        uint256[] memory sharesInUSD,
        address token,
        bool isSTMX
    ) external onlyOwner {
        require(_allTokens.contains(token), "48");
        uint256 thresholdsLength = thresholds.length;
        uint256 sharesInUSDLength = sharesInUSD.length;
        require(
            thresholdsLength > 0 &&
                thresholdsLength == sharesInUSDLength &&
                thresholdsLength <= maxAdminShareThresholds,
            "9"
        );

        uint256 maxAdminShare = maxAdminShareInUsd;

        for (uint256 i = 0; i < thresholdsLength - 1; ++i) {
            require(thresholds[i] <= thresholds[i + 1], "21");
            if (isSTMX) {
                maxAdminShare = maxAdminShareSTMX;
            }
            require(sharesInUSD[i] <= maxAdminShare, "22");
        }

        if (isSTMX) {
            maxAdminShare = maxAdminShareSTMX;
        }

        require(sharesInUSD[sharesInUSDLength - 1] <= maxAdminShare, "22");

        _adminShareRules[token] = AdminShareRule({
            sharesInUSD: sharesInUSD,
            thresholds: thresholds,
            isSTMX: isSTMX
        });

        emit AdminShareRulesUpdated(_adminShareRules[token], msg.sender);
    }

    /// @notice Update the maximum challenger limits
    /**
     * Access is restricted to the contract owner through the `onlyOwner` modifier, ensuring that only they can set these rules.
     * @param _maxChallengersEachSide maximun limit of challengers can join in each side.
     * @param _maxChallengersForPickem maximun limit of challengers can join for pickem.
     */
    function updateMaxChallengers(
        uint256 _maxChallengersEachSide,
        uint256 _maxChallengersForPickem
    ) external onlyOwner {
        require(
            _maxChallengersForPickem > 0 &&
                _maxChallengersForPickem <= 50 &&
                _maxChallengersEachSide > 0 &&
                _maxChallengersEachSide <= 50,
            "23"
        );
        maxChallengersForPickem = _maxChallengersForPickem;
        maxChallengersEachSide = _maxChallengersEachSide;

        emit MaxChallengersUpdated(_maxChallengersEachSide, _maxChallengersForPickem, msg.sender);
    }

    /// @notice Owner is able to deposit tokens in SC under the owner's withdrawbales, to use the owner withdrawables in user's bets
    /**
     * Access is restricted to the contract owner through the `onlyOwner` modifier, ensuring that only owner can deposit amount to SC.
     * @param _amount amount of tokens.
     * @param _token token address.
     */
    function debitInSC(uint256 _amount, address _token) external payable onlyOwner {
        require(_allowedTokens.contains(_token), "3");
        require(_amount > 0, "28");
        if (_token == address(0)) {
            require(msg.value == _amount, "29");
        } else {
            require(msg.value == 0, "34");
            IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount);
        }
        _withdrawables[msg.sender][_token] += _amount;
        emit DebitedInSC(_withdrawables[msg.sender][_token], msg.sender);
    }

    /// @notice Retrieves the administrative share rules for either the STMX token or other tokens
    /** @dev This function provides external access to the administrative share rules that have been set up for either
     * the STMX token (if `isSTMX` is true) or for other tokens (if `isSTMX` is false). These rules define the thresholds
     * and corresponding percentages that determine how administrative shares are calculated from betting winnings.
     * The function returns two arrays: one for the thresholds and one for the percentages, which together outline the
     * structure of admin shares based on the amount of winnings.
     * @param token A boolean flag indicating whether to retrieve the rules for the STMX token (`true`) or other tokens (`false`).
     * @return thresholds An array of uint256 representing the winnings thresholds for admin shares calculation.
     * @return sharesInUSD An array of uint256 representing the admin share in USD for each corresponding threshold.
     */
    function getAdminShareRules(
        address token
    )
        external
        view
        returns (uint256[] memory thresholds, uint256[] memory sharesInUSD, bool isSTMX)
    {
        AdminShareRule storage rule = _adminShareRules[token];
        return (rule.thresholds, rule.sharesInUSD, rule.isSTMX);
    }

    /// @notice Retrieves the list of tokens currently allowed for betting
    /** @dev This function provides external visibility into which tokens are currently permitted for use in betting within the platform.
     * It leverages the EnumerableSet library from OpenZeppelin to handle the dynamic array of addresses representing the allowed tokens.
     * This is particularly useful for interfaces or external contracts that need to verify or display the tokens users can bet with.
     * @return An array of addresses, each representing a token that is allowed for betting.
     */
    function getAllowedTokens() external view returns (address[] memory) {
        return _allowedTokens.values();
    }

    /// @notice Fetches detailed information about a specific challenge by its ID
    /** @dev This function provides access to the details of a given challenge, including its current status, which is
     * dynamically determined based on the challenge's timing and resolution state. It's essential for external callers
     * to be able to retrieve comprehensive data on a challenge, such as its participants, status, and betting amounts,
     * to properly interact with or display information about the challenge. The function checks that the requested
     * challenge exists before attempting to access its details.
     * @param challengeId The unique identifier of the challenge for which details are requested.
     * @return challengeDetails A `Challenge` struct containing all relevant data about the challenge, including an updated status.
     *
     * Requirements:
     * - The challenge must exist, as indicated by its ID being within the range of created challenges.
     */
    function getChallengeDetails(
        uint256 challengeId
    ) external view returns (Challenge memory challengeDetails) {
        _assertChallengeExistence(challengeId);
        challengeDetails = _challenges[challengeId];

        challengeDetails.status = _challengeStatus(challengeId);
    }

    /// @notice Retrieves the bet details placed by a specific user on a particular challenge
    /** @dev This function allows anyone to view the details of a bet made by a user on a specific challenge,
     * including the amount bet and the side the user has chosen. It's crucial for enabling users or interfaces
     * to confirm the details of participation in challenges and to understand the stakes involved. This function
     * directly accesses the mapping of user bets based on the user address and challenge ID, returning the
     * corresponding `UserBet` struct.
     * @param challengeId The ID of the challenge for which the bet details are being queried.
     * @param user The address of the user whose bet details are requested.
     * @return A `UserBet` struct containing the amount of the bet and the decision (side chosen) by the user for the specified challenge.
     */
    function getUserBet(uint256 challengeId, address user) external view returns (UserBet memory) {
        return _userChallengeBets[user][challengeId];
    }

    /// @notice Provides a list of tokens and corresponding amounts available for withdrawal by a specific user
    /** @dev This function compiles a comprehensive view of all tokens that a user has available to withdraw,
     * including winnings, refunds, or other credits due to the user. It iterates over the entire list of tokens
     * recognized by the contract (not just those currently allowed for betting) to ensure that users can access
     * any funds owed to them, regardless of whether a token's betting status has changed. This is essential for
     * maintaining transparency and access to funds for users within the platform.
     * @param user The address of the user for whom withdrawable balances are being queried.
     * @return tokens An array of token addresses, representing each token that the user has a balance of.
     * @return amounts An array of uint256 values, each corresponding to the balance of the token at the same index in the `tokens` array.
     */
    function getUserWithdrawables(
        address user
    ) external view returns (address[] memory tokens, uint256[] memory amounts) {
        uint256 allTokensLength = _allTokens.length();

        tokens = new address[](allTokensLength);
        amounts = new uint256[](allTokensLength);

        for (uint256 i = 0; i < allTokensLength; ++i) {
            tokens[i] = _allTokens.at(i);
            amounts[i] = _withdrawables[user][tokens[i]];
        }
    }

    /// @notice verify the membership proof from merkle tree
    /** @dev If merkle proof got verified it will return true otherwise false
     * @param proof leaf nood proof
     * @param membershipLevel user membership level
     * @param feePercentage percentage amount reduced from admin share
     * @param referrer referrer address
     * @param referralCommision referral will get the comission from admin share
     * @return bool Returns true if proof got verified, false otherwise.
     */
    function verifyMembership(
        bytes32[] memory proof,
        uint256 membershipLevel,
        uint256 feePercentage,
        address referrer,
        uint256 referralCommision
    ) internal view returns (bool) {
        bytes32 leaf = keccak256(
            bytes.concat(
                keccak256(
                    abi.encode(
                        msg.sender,
                        membershipLevel,
                        feePercentage,
                        referrer,
                        referralCommision
                    )
                )
            )
        );
        require(MerkleProof.verify(proof, merkleRoot, leaf), "45");
        return true;
    }

    /**
     * @dev Allows a user to join a challenge, handling the financial transactions involved, including admin fees.
     * This internal function processes a user's bet on a challenge, taking into account amounts from the user's wallet and
     * withdrawable balance. It calculates and deducts an admin share based on the total bet amount and updates the challenge
     * and user's records accordingly.
     *
     * @param challengeId The unique identifier of the challenge the user wishes to join.
     * @param amountFromWallet The portion of the user's bet that will be taken from their wallet.
     * @param amountFromWithdrawables The portion of the user's bet that will be taken from their withdrawable balance.
     * @param decision Indicates whether the user is betting for (1) or against (2) in the challenge; for group challenges, this is ignored.
     * @param membershipLevel user membership level
     * @param feePercentage percentage amount reduced from admin share
     * @param referrer referrer address
     * @param referralCommision referral will get the comission from admin share
     * @param proof leaf nood proof
     *
     * The function enforces several checks and conditions:
     * - The total bet amount must exceed the admin share calculated for the transaction.
     * - The user must have sufficient withdrawable balance if opting to use it.
     * - Transfers the required amount from the user's wallet if applicable.
     * - Updates the admin's withdrawable balance with the admin share.
     * - Adds the user to the challenge participants and updates the challenge's total amount for or against based on the user's decision.
     * - Ensures the number of participants does not exceed the maximum allowed.
     * - Records the user's bet details.
     *
     * Emits a `ChallengeJoined` event upon successful joining of the challenge.
     * Emits an `AdminShareCalculated` event to indicate the admin share calculated from the user's bet.
     *
     * Requirements:
     * - The sum of `amountFromWallet` and `amountFromWithdrawables` must be greater than the admin share.
     * - If using withdrawables, the user must have enough balance.
     * - The challenge token must be transferred successfully from the user's wallet if necessary.
     * - The challenge's participants count for either side must not exceed `maxChallengersEachSide`.
     *
     * Notes:
     * - This function uses the nonReentrant modifier to prevent reentry attacks.
     * - It supports participation in both individual and group challenges.
     * - Admin shares are calculated and deducted from the user's total bet amount to ensure fair administration fees.
     */
    function _joinChallenge(
        uint256 challengeId,
        uint256 amountFromWallet,
        uint256 amountFromWithdrawables,
        uint8 decision,
        uint8 membershipLevel,
        uint256 feePercentage,
        address referrer,
        uint256 referralCommision,
        bytes32[] memory proof
    ) internal nonReentrant {
        Challenge storage _challenge = _challenges[challengeId];
        (
            uint256 amount,
            uint256 adminShare,
            uint256 referralCommisionAmount
        ) = _calculateChallengeAmounts(
                _challenge.token,
                amountFromWallet,
                amountFromWithdrawables,
                challengeId,
                membershipLevel,
                feePercentage,
                referrer,
                referralCommision,
                proof,
                true
            );

        uint256 participants;

        // Depending on the decision, update challenge state and user bet details
        if (decision == 1 || _challenge.challengeType == ChallengeType.Group) {
            _challenge.usersFor.push(msg.sender);
            participants = _challenge.usersFor.length;
            _challenge.amountFor += amount;
        } else {
            _challenge.usersAgainst.push(msg.sender);
            participants = _challenge.usersAgainst.length;
            _challenge.amountAgainst += amount;
        }

        // Ensure the number of participants does not exceed the maximum allowed per side
        if (_challenge.challengeType == ChallengeType.Group) {
            require(participants <= maxChallengersForPickem, "30");
        } else {
            require(participants <= maxChallengersEachSide, "44");
        }

        // Record user's bet details for the challenge
        if (_challenge.challengeType == ChallengeType.Group) {
            decision = 1;
        }
        _userChallengeBets[msg.sender][challengeId] = UserBet({
            amount: amount,
            decision: decision,
            adminShare: adminShare,
            referrer: referrer,
            referralCommision: referralCommisionAmount
        });

        // Emit events for challenge joined and admin received shares
        emit ChallengeJoined(
            challengeId,
            amount,
            msg.sender,
            _challenge.token,
            amountFromWallet + amountFromWithdrawables
        );
        emit AdminShareCalculated(challengeId, _challenge.token, adminShare);
    }

    /// @notice calculate the challenge bet amount
    /**
     * @param challengeToken token in which user tried to bet
     * @param amountFromWallet amount which will be deducted from the users wallet
     * @param amountFromWithdrawables amount which will be deducted from the users withdrawables
     * @param membershipLevel user membership level
     * @param feePercentage percentage amount reduced from admin share 
     * @param referrer referrer address
     * @param referralCommision referral will get the comission from admin share
     * @param proof leaf nood proof
     * @return amount of the bet
     * @return admin share on bet amount
     
     */
    function _calculateChallengeAmounts(
        address challengeToken,
        uint256 amountFromWallet,
        uint256 amountFromWithdrawables,
        uint256 challengeId,
        uint8 membershipLevel,
        uint256 feePercentage,
        address referrer,
        uint256 referralCommision,
        bytes32[] memory proof,
        bool checkMinUsdAmounts
    ) internal returns (uint256, uint256, uint256) {
        require(bettingAllowed, "31");
        require(_challengeStatus(challengeId) == ChallengeStatus.Betting, "32");

        if (challengeToken == address(0)) {
            require(amountFromWallet == msg.value, "33");
        } else {
            require(msg.value == 0, "34");
        }
        uint256 amount = amountFromWallet + amountFromWithdrawables;
        uint256 adminShare = _calculateAdminShare(challengeToken, amount);
        uint256 referralCommisionAmount;
        if (applyMembershipValues) {
            verifyMembership(proof, membershipLevel, feePercentage, referrer, referralCommision);
            adminShare = (adminShare * ((100 * 10 ** 20) - feePercentage)) / (100 * 10 ** 20);

            if (referrer != address(0)) {
                referralCommisionAmount = adminShare; // admin share amount with refferral commision amount

                // deduct the referral commission from admin share
                adminShare =
                    (adminShare * ((100 * 10 ** 20) - referralCommision)) /
                    (100 * 10 ** 20);
                referralCommisionAmount -= adminShare;
            }
        }
        // Ensure that the total amount is greater than the admin share per challenge
        require(amount > adminShare, "35");
        uint256 valueAmount = (_getValue(challengeToken) * amount) /
            10 ** (challengeToken == address(0) ? 18 : challengeToken.decimals());

        if (_oraclessTokens.contains(challengeToken)) {
            require(valueAmount >= _oraclessTokensMinBetAmount[challengeToken], "28");
        } else {
            require(!checkMinUsdAmounts || valueAmount >= minUSDBetAmount, "28");
        }

        // Deduct the amount from the withdrawables if bet amount is from withdrawables
        if (amountFromWithdrawables > 0) {
            require(_withdrawables[msg.sender][challengeToken] >= amountFromWithdrawables, "36");
            _withdrawables[msg.sender][challengeToken] -= amountFromWithdrawables;
        }

        // Transfer the amount from the user's wallet to the contract
        if (challengeToken != address(0)) {
            IERC20(challengeToken).safeTransferFrom(msg.sender, address(this), amountFromWallet);
        }

        amount -= (adminShare + referralCommisionAmount);
        return (amount, adminShare, referralCommisionAmount);
    }

    /**
     * @dev Calculates the results of a challenge based on the final outcome and updates the participants' balances accordingly.
     * This internal function takes the final outcome of a challenge and determines the winners and losers, redistributing the
     * pooled amounts between participants based on their initial bets. It ensures that the winnings are proportionally distributed
     * to the winners from the total amount bet by the losers.
     *
     * @param challengeId The unique identifier of the challenge to calculate results for.
     * @param finalOutcome The final outcome of the challenge represented as a uint8 value. A value of `1` indicates
     * that the original "for" side wins, while `2` indicates that the "against" side wins.
     *
     * The function performs the following steps:
     * - Identifies the winning and losing sides based on `finalOutcome`.
     * - Calculates the total winning amount for each winning participant based on their bet proportion.
     * - Updates the `_withdrawables` mapping to reflect the winnings for each winning participant.
     * - Prepares data for the losing participants but does not adjust their balances as their amounts are considered lost.
     *
     * Emits a `ChallengeFundsMoved` event indicating the redistribution of funds following the challenge's conclusion.
     * This event provides detailed arrays of winning and losing users, alongside the amounts won or lost.
     *
     * Requirements:
     * - The challenge identified by `challengeId` must exist within the `_challenges` mapping.
     * - The `finalOutcome` must correctly reflect the challenge's outcome, with `1` for a win by the original "for" side
     *   and `2` for a win by the "against" side.
     *
     * Notes:
     * - This function is critical for ensuring fair payout to the winners based on the total amount bet by the losers.
     * - It assumes that the `finalOutcome` has been determined by an external process or oracle that is not part of this function.
     */
    function _calculateChallenge(uint256 challengeId, uint8 finalOutcome) internal {
        Challenge storage _challenge = _challenges[challengeId];
        address challengeToken = _challenge.token;
        uint256 adminShare;

        // Determine the arrays of winning and losing users, and their respective amounts
        address[] storage usersWin = _challenge.usersFor;
        address[] storage usersLose = _challenge.usersAgainst;
        uint256 winAmount = _challenge.amountFor;
        uint256 loseAmount = _challenge.amountAgainst;

        if (finalOutcome == 2) {
            // If final outcome is lose, swap win and lose arrays
            (usersWin, usersLose) = (usersLose, usersWin);
            (winAmount, loseAmount) = (loseAmount, winAmount);
        }

        uint256 usersWinLength = usersWin.length;
        uint256 usersLoseLength = usersLose.length;

        uint256[] memory winAmounts = new uint256[](usersWinLength);
        address[] memory referrers = new address[](usersWinLength + usersLoseLength);
        uint256[] memory referrelCommissions = new uint256[](usersWinLength + usersLoseLength);

        uint256 j = 0;
        // Distribute winnings to winning users
        for (uint256 i = 0; i < usersWinLength; ++i) {
            address user = usersWin[i];
            UserBet storage bet = _userChallengeBets[user][challengeId];

            uint256 userWinAmount = bet.amount + ((loseAmount * bet.amount) / winAmount);

            winAmounts[i] = userWinAmount;
            referrers[j] = bet.referrer;
            referrelCommissions[j] = bet.referralCommision;
            _withdrawables[user][challengeToken] += userWinAmount;
            _withdrawables[bet.referrer][challengeToken] += bet.referralCommision;
            adminShare += bet.adminShare;
            ++j;
        }

        uint256[] memory loseAmounts = new uint256[](usersLoseLength);

        // Record losing amounts
        for (uint256 i = 0; i < usersLoseLength; ++i) {
            UserBet storage bet = _userChallengeBets[usersLose[i]][challengeId];
            loseAmounts[i] = bet.amount;
            referrers[j] = bet.referrer;
            referrelCommissions[j] = bet.referralCommision;
            _withdrawables[bet.referrer][challengeToken] += bet.referralCommision;
            adminShare += bet.adminShare;
            ++j;
        }

        _withdrawables[owner()][challengeToken] += adminShare;

        // Emit event for funds distribution
        emit ReferralsEarned(challengeId, challengeToken, referrers, referrelCommissions);
        emit AdminReceived(challengeId, challengeToken, adminShare);
        emit ChallengeFundsMoved(
            challengeId,
            usersWin,
            winAmounts,
            usersLose,
            loseAmounts,
            MethodType.ResolveChallenge,
            challengeToken
        );
    }

    /**
     * @dev Cancels a user's participation in a given challenge, refunding their bet and updating the challenge's state.
     * This internal function handles the cancellation process for both individual and group challenges.
     * It adjusts the challenge's total bet amount and participant list based on the user's decision (for or against).
     * Additionally, it increments the user's withdrawable balance by the amount of their canceled bet.
     *
     * @param user The address of the user whose participation is being canceled.
     * @param challengeId The unique identifier of the challenge from which the user is withdrawing.
     *
     * The function performs the following operations:
     * - Identifies whether the user was betting for or against the challenge, or if it's a group challenge.
     * - Removes the user from the appropriate participant list (`usersFor` or `usersAgainst`) and adjusts the challenge's
     *   total amount for or against accordingly.
     * - Increases the user's withdrawable balance by the amount of their bet.
     * - Emits a `CancelParticipation` event signaling the user's cancellation from the challenge.
     * - Emits a `ChallengeFundsMoved` event to indicate the movement of funds due to the cancellation, for consistency and tracking.
     *
     * Notes:
     * - This function is designed to work with both individual and group challenges, modifying the challenge's state
     *   to reflect the user's cancellation and ensuring the integrity of the challenge's betting totals.
     * - It utilizes the `contains` function to find the user's position in the participant lists and handles their removal efficiently.
     * - The adjustment of the challenge's betting totals and participant lists is crucial for maintaining accurate and fair
     *   challenge outcomes and balances.
     */
    function _cancelParticipation(address user, uint256 challengeId, uint8 cancelType) internal {
        Challenge storage _challenge = _challenges[challengeId];
        address challengeToken = _challenge.token;

        uint256 usersForLength = _challenge.usersFor.length;
        uint256 usersAgainstLength = _challenge.usersAgainst.length;

        address[] memory users = new address[](1);
        uint256[] memory amounts = new uint256[](1);
        address[] memory referrers = new address[](1);
        uint256[] memory referrelCommissions = new uint256[](1);

        uint256 amount = _userChallengeBets[user][challengeId].amount;
        uint256 adminShare = _userChallengeBets[user][challengeId].adminShare;
        uint256 referralCommision = _userChallengeBets[user][challengeId].referralCommision;
        address referrer = _userChallengeBets[user][challengeId].referrer;

        if (
            (_challenge.challengeType == ChallengeType.Individual &&
                _userChallengeBets[user][challengeId].decision == 1) ||
            _challenge.challengeType == ChallengeType.Group
        ) {
            // If user is for the challenge or it's a group challenge, handle accordingly
            uint256 i = contains(_challenge.usersFor, user);
            if (cancelType == 1) {
                amount += adminShare + referralCommision;
            } else {
                _withdrawables[owner()][challengeToken] += adminShare;
                _withdrawables[referrer][challengeToken] += referralCommision;
            }
            _withdrawables[user][challengeToken] += amount;
            _challenge.amountFor -= amount;
            _challenge.usersFor[i] = _challenge.usersFor[usersForLength - 1];
            _challenge.usersFor.pop();
        } else {
            // If user is against the challenge, handle accordingly
            uint256 i = contains(_challenge.usersAgainst, user);
            if (cancelType == 1) {
                amount += adminShare + referralCommision;
                referralCommision = 0;
                adminShare = 0;
            } else {
                _withdrawables[owner()][challengeToken] += adminShare;
                _withdrawables[referrer][challengeToken] += referralCommision;
            }
            _withdrawables[user][challengeToken] += amount;
            _challenge.amountAgainst -= amount;
            _challenge.usersAgainst[i] = _challenge.usersAgainst[usersAgainstLength - 1];
            _challenge.usersAgainst.pop();
        }

        // Prepare data for event emission
        users[0] = user;
        amounts[0] = amount;
        referrers[0] = referrer;
        referrelCommissions[0] = referralCommision;

        // Clear user's bet for the challenge
        delete _userChallengeBets[user][challengeId];

        // Emit events for cancellation of participation and fund movement
        emit CancelParticipation(user, challengeId);
        emit ReferralsEarned(challengeId, _challenge.token, referrers, referrelCommissions);
        emit AdminReceived(challengeId, _challenge.token, adminShare);

        emit ChallengeFundsMoved(
            challengeId,
            users,
            amounts,
            new address[](0),
            new uint256[](0),
            MethodType.CancelParticipation,
            _challenge.token
        );
    }

    /**
     * @dev Calculates and allocates winnings and losses for a group challenge.
     * This internal function determines the amounts won by each winning user and the amounts lost by each losing user
     * within a challenge. It updates the `_withdrawables` mapping to reflect the winnings for each winning user based
     * on their share of the profits. Losing users' bet amounts are noted but not immediately acted upon in this function.
     *
     * @param challengeId The unique identifier of the challenge being calculated.
     * @param usersWin An array of addresses for users who won in the challenge.
     * @param profits An array of profit percentages corresponding to each winning user.
     *
     * Requirements:
     * - `usersWin` and `profits` arrays must be of the same length, with each entry in `profits` representing
     *   the percentage of the total winnings that the corresponding user in `usersWin` should receive.
     * - This function does not directly handle the transfer of funds but updates the `_withdrawables` mapping to
     *   reflect the amounts that winning users are able to withdraw.
     * - Losing users' details are aggregated but are used primarily for event emission.
     *
     * Emits a `ChallengeFundsMoved` event indicating the challenge ID, winning users and their win amounts,
     * and losing users with the amounts they bet and lost. This helps in tracking the outcome and settlements
     * of group challenges.
     *
     * Note:
     * - The actual transfer of funds from losing to winning users is not performed in this function. Instead, it calculates
     *   and updates balances that users can later withdraw.
     */
    function _calculateGroupChallenge(
        uint256 challengeId,
        address[] calldata usersWin,
        uint256[] calldata profits
    ) internal {
        Challenge storage _challenge = _challenges[challengeId];
        address challengeToken = _challenge.token;
        uint256 userWinLength = usersWin.length;
        address[] storage usersFor = _challenge.usersFor;
        uint256 challengeUserForLength = usersFor.length;
        uint256[] memory winAmounts = new uint256[](userWinLength);
        uint256[] memory loseAmounts = new uint256[](challengeUserForLength - userWinLength);
        address[] memory usersLose = new address[](challengeUserForLength - userWinLength);
        address[] memory referrers = new address[](challengeUserForLength);
        uint256[] memory referrelCommissions = new uint256[](challengeUserForLength);

        uint256 j = 0;
        uint256 adminShare;
        for (uint256 i = 0; i < challengeUserForLength; ++i) {
            uint256 index = contains(usersWin, _challenge.usersFor[i]);
            UserBet storage bet = _userChallengeBets[usersFor[i]][challengeId];
            if (index == userWinLength) {
                usersLose[j] = usersFor[i];
                loseAmounts[j] = bet.amount;
                j++;
            } else {
                uint256 winAmount = (_challenge.amountFor * profits[index]) / (100 * DECIMAL);
                _withdrawables[usersWin[index]][challengeToken] += winAmount;
                winAmounts[index] = winAmount;
            }
            adminShare += bet.adminShare;
            _withdrawables[bet.referrer][_challenge.token] += bet.referralCommision;
            referrers[i] = bet.referrer;
            referrelCommissions[i] = bet.referralCommision;
        }
        _withdrawables[owner()][_challenge.token] += adminShare;

        // Emit event for fund movement in the group challenge
        emit ReferralsEarned(challengeId, challengeToken, referrers, referrelCommissions);
        emit AdminReceived(challengeId, challengeToken, adminShare);

        emit ChallengeFundsMoved(
            challengeId,
            usersWin,
            winAmounts,
            usersLose,
            loseAmounts,
            MethodType.ResolveGroupChallenge,
            challengeToken
        );
    }

    /**
     * @dev Searches for an element in an address array and returns its index if found.
     * This internal pure function iterates through an array of addresses to find a specified element.
     * It's designed to check the presence of an address in a given array and identify its position.
     *
     * @param array The array of addresses to search through.
     * @param element The address to search for within the array.
     * @return The index of the element within the array if found; otherwise, returns the length of the array.
     * This means that if the return value is equal to the array's length, the element is not present in the array.
     */
    function contains(address[] memory array, address element) internal pure returns (uint256) {
        uint256 arrayLength = array.length;
        for (uint256 i = 0; i < arrayLength; ++i) {
            if (array[i] == element) {
                return i;
            }
        }
        return arrayLength;
    }

    /**
     * @dev Cancels all bets placed on a challenge, refunding the bet amounts to the bettors.
     * This internal function handles the process of cancelling bets for both "for" and "against" positions in a given challenge.
     * It aggregates users and their respective bet amounts from both positions, updates their withdrawable balances,
     * and emits an event indicating the movement of funds due to the challenge's cancellation.
     *
     * The function iterates through all bets placed "for" and "against" the challenge, compiles lists of users and their bet amounts,
     * and credits the bet amounts back to the users' withdrawable balances in the form of the challenge's token.
     *
     * @param challengeId The unique identifier of the challenge whose bets are to be cancelled.
     *
     * Emits a `ChallengeFundsMoved` event with details about the challengeId, users involved, their refunded amounts,
     * and empty arrays for new users and new amounts as no new bets are created during the cancellation process.
     *
     * Requirements:
     * - The function is internal and expected to be called in scenarios where a challenge needs to be cancelled, such as
     *   when a challenge is deemed invalid or when conditions for the challenge's execution are not met.
     * - It assumes that `_challenges` maps `challengeId` to a valid `Challenge` struct containing arrays of users who have bet "for" and "against".
     * - The function updates `_withdrawables`, a mapping of user addresses to another mapping of token addresses and their withdrawable amounts, ensuring users can withdraw their bet amounts after the bets are cancelled.
     */
    function _cancelBets(uint256 challengeId, uint8 cancelType) internal {
        Challenge storage _challenge = _challenges[challengeId];
        address challengeToken = _challenge.token;

        uint256 usersForLength = _challenge.usersFor.length;
        uint256 usersAgainstLength = _challenge.usersAgainst.length;

        address[] memory users = new address[](usersForLength + usersAgainstLength);
        uint256[] memory amounts = new uint256[](usersForLength + usersAgainstLength);
        address[] memory referrers = new address[](usersForLength + usersAgainstLength);
        uint256[] memory referrelCommissions = new uint256[](usersForLength + usersAgainstLength);

        uint256 j = 0;
        uint256 totalAdminShare = 0;
        for (uint256 i = 0; i < usersForLength; ++i) {
            address user = _challenge.usersFor[i];

            users[i] = user;
            uint256 returnAmount = _userChallengeBets[user][challengeId].amount;
            uint256 adminShare = _userChallengeBets[user][challengeId].adminShare;
            uint256 referralCommision = _userChallengeBets[user][challengeId].referralCommision;
            address referrer = _userChallengeBets[user][challengeId].referrer;

            if (cancelType == 1) {
                returnAmount += adminShare + referralCommision;
                adminShare = 0;
                referralCommision = 0;
            } else {
                _withdrawables[owner()][challengeToken] += adminShare;
                _withdrawables[referrer][challengeToken] += referralCommision;
            }
            amounts[i] = returnAmount;
            referrers[j] = referrer;
            referrelCommissions[j] = referralCommision;
            _withdrawables[user][challengeToken] += amounts[i];
            totalAdminShare += adminShare;
            ++j;
        }

        for (uint256 i = 0; i < usersAgainstLength; ++i) {
            address user = _challenge.usersAgainst[i];
            uint256 index = i + usersForLength;

            users[index] = user;
            uint256 returnAmount = _userChallengeBets[user][challengeId].amount;
            uint256 adminShare = _userChallengeBets[user][challengeId].adminShare;
            uint256 referralCommision = _userChallengeBets[user][challengeId].referralCommision;
            address referrer = _userChallengeBets[user][challengeId].referrer;

            if (cancelType == 1) {
                returnAmount += adminShare + referralCommision;
                adminShare = 0;
                referralCommision = 0;
            } else {
                _withdrawables[owner()][challengeToken] += adminShare;
                _withdrawables[referrer][challengeToken] += referralCommision;
            }
            amounts[index] = returnAmount;
            referrers[j] = referrer;
            referrelCommissions[j] = referralCommision;
            _withdrawables[user][challengeToken] += amounts[index];
            totalAdminShare += adminShare;
            ++j;
        }

        emit ReferralsEarned(challengeId, challengeToken, referrers, referrelCommissions);
        emit AdminReceived(challengeId, challengeToken, totalAdminShare);

        emit ChallengeFundsMoved(
            challengeId,
            users,
            amounts,
            new address[](0),
            new uint256[](0),
            MethodType.CancelChallenge,
            challengeToken
        );
    }

    /**
     * @dev Withdraws an amount of native cryptocurrency (e.g., ETH) or an ERC-20 token and sends it to a specified address.
     * This internal function handles the transfer of both native cryptocurrency and ERC-20 tokens based on the token address provided.
     * If the `token` parameter is the zero address, it treats the transfer as a native cryptocurrency transaction.
     * Otherwise, it performs a safe transfer of an ERC-20 token.
     *
     * @param token The address of the token to withdraw. If the address is `0x0`, the withdrawal is processed as a native cryptocurrency transaction.
     * @param to The recipient address to which the currency or tokens are sent.
     * @param amount The amount of currency or tokens to send. The function ensures that this amount is securely transferred to the `to` address.
     *
     * Requirements:
     * - For native cryptocurrency transfers:
     *   - The transaction must succeed. If it fails, the function reverts with "Failed to send ETH".
     * - For ERC-20 token transfers:
     *   - The function uses `safeTransfer` from the IERC20 interface to prevent issues related to double spending or errors in transfer.
     *   - The ERC-20 token contract must implement `safeTransfer` correctly according to the ERC-20 standard.
     */
    function _withdraw(address token, address to, uint256 amount) internal {
        if (token == address(0)) {
            // Native cryptocurrency transfer
            (bool ok, ) = to.call{value: amount}("");
            require(ok, "37");
        } else {
            // ERC-20 token transfer
            IERC20(token).safeTransfer(to, amount);
        }
    }

    /**
     * @dev Calculates the administrator's share of a challenge based on the challenge's token and the amount.
     * This internal view function determines the admin's share by first converting the `amount` of the challenge's
     * token into a standardized value (using `_getValue` function to get the token's value in a common denomination).
     * It then uses this value to find the applicable admin share percentage from a predefined set of rules (`_adminShareRules`).
     *
     * @param token The token of the challenge to calculate the value amount.
     * @param amount The amount involved in the challenge for which the admin's share is to be calculated.
     * @return The calculated admin share as a uint256, based on the challenge's conditions and predefined rules.
     *
     * Logic:
     * - Determines the value of the `amount` of tokens by fetching the token's current value and adjusting for decimal places.
     * - Uses the calculated value to find the corresponding admin share percentage from `_adminShareRules`.
     * - The share is computed based on thresholds which determine the percentage rate applicable to the value amount.
     * - If the value amount does not meet the minimum threshold, the function returns 0, indicating no admin share.
     * - If applicable, the admin share is calculated by multiplying the `amount` by the determined percentage
     *   and dividing by `PERCENTAGE_100` to ensure the result is in the correct scale.
     *
     * Requirements:
     * - The function dynamically adjusts for the token's decimals, using 18 decimals for the native currency (e.g., ETH) or
     *   querying the token contract for ERC-20 tokens.
     * - It handles special cases, such as when the token is the platform's specific token (e.g. STMX),
     *   by applying predefined rules for calculating the admin share.
     */
    function _calculateAdminShare(address token, uint256 amount) internal view returns (uint256) {
        require(_allTokens.contains(token), "48");

        uint256 valueAmount = (_getValue(token) * amount) /
            10 ** (token == address(0) ? 18 : token.decimals());

        AdminShareRule storage rule = _adminShareRules[token];

        uint256 index = rule.thresholds.upperBound(valueAmount);

        if (index == 0) {
            return 0;
        }
        // Get the admin share in USD for the corresponding threshold
        uint256 shareInUSD = rule.sharesInUSD[index - 1];

        if (rule.isSTMX) {
            return shareInUSD;
        }

        // Convert the USD share back into the equivalent token amount
        uint256 adminShareInTokens = (shareInUSD *
            10 ** (token == address(0) ? 18 : token.decimals())) / _getValue(token);

        return adminShareInTokens;
    }

    /**
     * @dev Retrieves the current value of a given token, based on oracle data.
     * This internal view function queries the value of the specified token from a price feed oracle.
     * If the token is recognized by a preset list of oracles (_oraclessTokens), it returns a default value.
     * Otherwise, it fetches the latest round data from the token's associated price feed.
     * The function requires that the oracle's reported value be positive and updated within the last day,
     * indicating no oracle malfunction.
     * It adjusts the oracle's value based on a default decimal precision, to ensure consistency across different oracles.
     *
     * @param token The address of the token for which the value is being queried.
     * @return The current value of the token as a uint256, adjusted for default decimal precision.
     * The value is adjusted to match the `defaultOracleDecimals` precision if necessary.
     *
     * Requirements:
     * - The oracle's latest value for the token must be positive and updated within the last 24 hours.
     * - If the token is not recognized by the _oraclessTokens set, but has a price feed, the function normalizes the
     *   value to a standard decimal precision (defaultOracleDecimals) for consistency.
     * - Throws "Oracle malfunction" if the oracle's latest data does not meet the requirements.
     */
    function _getValue(address token) internal view returns (uint256) {
        int256 value;
        uint256 updatedAt;

        if (_oraclessTokens.contains(token)) {
            value = int256(10 ** defaultOracleDecimals);
        } else {
            (, value, , updatedAt, ) = _priceFeeds[token].latestRoundData();
            require(value > 0 && updatedAt >= block.timestamp - 1 days, "43");
            uint256 oracleDecimals = _priceFeeds[token].decimals();
            if (oracleDecimals > defaultOracleDecimals) {
                value = value / int256(10 ** (oracleDecimals - defaultOracleDecimals));
            } else if (oracleDecimals < defaultOracleDecimals) {
                value = value * int256(10 ** (defaultOracleDecimals - oracleDecimals));
            }
        }

        return uint256(value);
    }

    /**
     * @dev Determines the current status of a specific challenge by its ID.
     * This internal view function assesses the challenge's status based on its current state and timing.
     * It checks if the challenge is in a final state (Canceled, ResolvedFor, ResolvedAgainst, or ResolvedDraw).
     * If not, it then checks whether the challenge's end time has passed to determine if it's in the Awaiting state.
     * Otherwise, it defaults to the Betting state, implying that the challenge is still active and accepting bets.
     *
     * @param challengeId The unique identifier for the challenge whose status is being queried.
     * @return ChallengeStatus The current status of the challenge. This can be one of the following:
     * - Canceled: The challenge has been canceled.
     * - ResolvedFor: The challenge has been resolved in favor of the proposer.
     * - ResolvedAgainst: The challenge has been resolved against the proposer.
     * - ResolvedDraw: The challenge has been resolved as a draw.
     * - Awaiting: The challenge is awaiting resolution, but betting is closed due to the end time having passed.
     * - Betting: The challenge is open for bets.
     */
    function _challengeStatus(uint256 challengeId) internal view returns (ChallengeStatus) {
        ChallengeStatus status = _challenges[challengeId].status;
        uint256 endTime = _challenges[challengeId].endTime;

        if (
            status == ChallengeStatus.Canceled ||
            status == ChallengeStatus.ResolvedFor ||
            status == ChallengeStatus.ResolvedAgainst ||
            status == ChallengeStatus.ResolvedDraw
        ) {
            return status;
        }

        if (block.timestamp > endTime) {
            return ChallengeStatus.Awaiting;
        }

        return ChallengeStatus.Betting;
    }

    /**
     * @dev Checks if the challenge id is valid or not.
     *
     * @param challengeId The unique identifier for the challenge.
     */
    function _assertChallengeExistence(uint256 challengeId) internal view {
        require(challengeId > 0 && challengeId <= latestChallengeId, "38");
    }

    /**
     * @dev Checks if the challenge is resolved or not.
     *
     * @param challengeId The unique identifier for the challenge.
     */
    function _assertResolveableStatus(uint256 challengeId) internal view {
        require(_challengeStatus(challengeId) == ChallengeStatus.Awaiting, "39");
    }

    /**
     * @dev Checks if the challenge is canceled or not.
     *
     * @param challengeId The unique identifier for the challenge.
     */
    function _assertCancelableStatus(uint256 challengeId) internal view {
        ChallengeStatus status = _challengeStatus(challengeId);
        require(status == ChallengeStatus.Awaiting || status == ChallengeStatus.Betting, "40");
    }

    /**
     * @dev Ensures that the function is only callable by the designated backend address.
     * This internal view function checks if the `msg.sender` is the same as the stored `backend` address.
     * It should be used as a modifier in functions that are meant to be accessible only by the backend.
     * Reverts with a "Not a backend" error message if the `msg.sender` is not the backend address.
     */
    function _onlyBackend() internal view {
        require(msg.sender == backend, "41");
    }

    /**
     * @dev Ensures that the function is only callable by the designated backend address or owner address.
     * This internal view function checks if the `msg.sender` is the same as the stored `backend` address or owner address.
     * It should be used as a modifier in functions that are meant to be accessible by the backend and owner.
     * Reverts with a "Not a backend or owner" error message if the `msg.sender` is neither the backend address, nor the owner address.
     */
    function _onlyBackendOrOwner() internal view {
        require(msg.sender == backend || msg.sender == owner(), "42");
    }

    /**
     * @dev Overrides the renounceOwnership function to disable the ability to renounce ownership.
     * This ensures that the contract always has an owner.
     */
    function renounceOwnership() public view override onlyOwner {
        revert("Renouncing ownership is disabled");
    }

    /**
     * @dev Checks if the price feed from a given address is valid within a specified error margin.
     * @param priceFeedAddress The address of the price feed.
     * @param errorMarginPercent The acceptable error margin in percentage.
     * @return bool Returns true if the price feed is valid, otherwise reverts.
     */
    function isValidPriceFeed(
        address priceFeedAddress,
        uint256 errorMarginPercent
    ) internal view returns (bool) {
        AggregatorV3Interface priceFeed = AggregatorV3Interface(priceFeedAddress);
        (, int256 price, , uint256 updatedAt, ) = priceFeed.latestRoundData();

        require(price > 0, "19");
        require(block.timestamp - updatedAt <= 1 days, "25");

        int256 expectedPrice = getExpectedPrice(priceFeedAddress);
        int256 lowerBound = expectedPrice - ((expectedPrice * int256(errorMarginPercent)) / 100);
        int256 upperBound = expectedPrice + ((expectedPrice * int256(errorMarginPercent)) / 100);

        require(price >= lowerBound && price <= upperBound, "27");

        return true;
    }

    /**
     * @dev Computes the expected price from the historical price feed data.
     * @param priceFeedAddress The address of the price feed.
     * @return int256 The average price calculated from the last few rounds.
     */
    function getExpectedPrice(address priceFeedAddress) internal view returns (int256) {
        AggregatorV3Interface priceFeed = AggregatorV3Interface(priceFeedAddress);

        // Fetch the latest round data
        (uint80 roundID, , , , ) = priceFeed.latestRoundData();

        // Calculate the average price over the last few rounds
        int256 sum = 0;
        uint256 count = 0;
        uint80 currentRoundId = roundID;

        // Assuming we want to average the last 5 rounds
        uint256 roundsToAverage = 5;

        for (uint256 i = 0; i < roundsToAverage; ++i) {
            (, int256 historicalPrice, , , ) = priceFeed.getRoundData(currentRoundId);
            sum += historicalPrice;
            count += 1;

            if (currentRoundId > 0) {
                currentRoundId -= 1;
            } else {
                break;
            }
        }

        int256 averagePrice = sum / int256(count);

        return averagePrice;
    }
}

File 2 of 22 : AggregatorV3Interface.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface AggregatorV3Interface {
  function decimals() external view returns (uint8);

  function description() external view returns (string memory);

  function version() external view returns (uint256);

  function getRoundData(
    uint80 _roundId
  ) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);

  function latestRoundData()
    external
    view
    returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}

File 3 of 22 : Ownable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

File 4 of 22 : Ownable2Step.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol)

pragma solidity ^0.8.0;

import "./Ownable.sol";

/**
 * @dev Contract module which provides access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership} and {acceptOwnership}.
 *
 * This module is used through inheritance. It will make available all functions
 * from parent (Ownable).
 */
abstract contract Ownable2Step is Ownable {
    address private _pendingOwner;

    event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Returns the address of the pending owner.
     */
    function pendingOwner() public view virtual returns (address) {
        return _pendingOwner;
    }

    /**
     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual override onlyOwner {
        _pendingOwner = newOwner;
        emit OwnershipTransferStarted(owner(), newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual override {
        delete _pendingOwner;
        super._transferOwnership(newOwner);
    }

    /**
     * @dev The new owner accepts the ownership transfer.
     */
    function acceptOwnership() public virtual {
        address sender = _msgSender();
        require(pendingOwner() == sender, "Ownable2Step: caller is not the new owner");
        _transferOwnership(sender);
    }
}

File 5 of 22 : ReentrancyGuard.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)

pragma solidity ^0.8.0;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == _ENTERED;
    }
}

File 6 of 22 : ERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.0;

import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * The default value of {decimals} is 18. To change this, you should override
 * this function so it returns a different value.
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC20
 * applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20 is Context, IERC20, IERC20Metadata {
    mapping(address => uint256) private _balances;

    mapping(address => mapping(address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the default value returned by this function, unless
     * it's overridden.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual override returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `amount`.
     */
    function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, allowance(owner, spender) + addedValue);
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        address owner = _msgSender();
        uint256 currentAllowance = allowance(owner, spender);
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        unchecked {
            _approve(owner, spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    /**
     * @dev Moves `amount` of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     */
    function _transfer(address from, address to, uint256 amount) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
            // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
            // decrementing then incrementing.
            _balances[to] += amount;
        }

        emit Transfer(from, to, amount);

        _afterTokenTransfer(from, to, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        unchecked {
            // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
            _balances[account] += amount;
        }
        emit Transfer(address(0), account, amount);

        _afterTokenTransfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        unchecked {
            _balances[account] = accountBalance - amount;
            // Overflow not possible: amount <= accountBalance <= totalSupply.
            _totalSupply -= amount;
        }

        emit Transfer(account, address(0), amount);

        _afterTokenTransfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @dev Updates `owner` s allowance for `spender` based on spent `amount`.
     *
     * Does not update the allowance amount in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Might emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "ERC20: insufficient allowance");
            unchecked {
                _approve(owner, spender, currentAllowance - amount);
            }
        }
    }

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}

    /**
     * @dev Hook that is called after any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * has been transferred to `to`.
     * - when `from` is zero, `amount` tokens have been minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
}

File 7 of 22 : IERC20Metadata.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

File 8 of 22 : IERC20Permit.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

File 9 of 22 : IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

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

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

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

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

File 10 of 22 : SafeERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(IERC20 token, address spender, uint256 value) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
     * Revert on invalid signature.
     */
    function safePermit(
        IERC20Permit token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return
            success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
    }
}

File 11 of 22 : Address.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     *
     * Furthermore, `isContract` will also return true if the target contract within
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
     * which only has an effect at the end of a transaction.
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    function _revert(bytes memory returndata, string memory errorMessage) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }
}

File 12 of 22 : Context.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

File 13 of 22 : MerkleProof.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.2) (utils/cryptography/MerkleProof.sol)

pragma solidity ^0.8.0;

/**
 * @dev These functions deal with verification of Merkle Tree proofs.
 *
 * The tree and the proofs can be generated using our
 * https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
 * You will find a quickstart guide in the readme.
 *
 * WARNING: You should avoid using leaf values that are 64 bytes long prior to
 * hashing, or use a hash function other than keccak256 for hashing leaves.
 * This is because the concatenation of a sorted pair of internal nodes in
 * the merkle tree could be reinterpreted as a leaf value.
 * OpenZeppelin's JavaScript library generates merkle trees that are safe
 * against this attack out of the box.
 */
library MerkleProof {
    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     */
    function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        return processProof(proof, leaf) == root;
    }

    /**
     * @dev Calldata version of {verify}
     *
     * _Available since v4.7._
     */
    function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        return processProofCalldata(proof, leaf) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leafs & pre-images are assumed to be sorted.
     *
     * _Available since v4.4._
     */
    function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = _hashPair(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Calldata version of {processProof}
     *
     * _Available since v4.7._
     */
    function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = _hashPair(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a merkle tree defined by
     * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
     *
     * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * _Available since v4.7._
     */
    function multiProofVerify(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProof(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Calldata version of {multiProofVerify}
     *
     * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * _Available since v4.7._
     */
    function multiProofVerifyCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProofCalldata(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
     * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
     * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
     * respectively.
     *
     * CAUTION: Not all merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
     * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
     * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
     *
     * _Available since v4.7._
     */
    function processMultiProof(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32[] memory leaves
    ) internal pure returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofLen = proof.length;
        uint256 totalHashes = proofFlags.length;

        // Check proof validity.
        require(leavesLen + proofLen - 1 == totalHashes, "MerkleProof: invalid multiproof");

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](totalHashes);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < totalHashes; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = _hashPair(a, b);
        }

        if (totalHashes > 0) {
            require(proofPos == proofLen, "MerkleProof: invalid multiproof");
            unchecked {
                return hashes[totalHashes - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    /**
     * @dev Calldata version of {processMultiProof}.
     *
     * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * _Available since v4.7._
     */
    function processMultiProofCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32[] memory leaves
    ) internal pure returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofLen = proof.length;
        uint256 totalHashes = proofFlags.length;

        // Check proof validity.
        require(leavesLen + proofLen - 1 == totalHashes, "MerkleProof: invalid multiproof");

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](totalHashes);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < totalHashes; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = _hashPair(a, b);
        }

        if (totalHashes > 0) {
            require(proofPos == proofLen, "MerkleProof: invalid multiproof");
            unchecked {
                return hashes[totalHashes - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
        return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
    }

    function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, a)
            mstore(0x20, b)
            value := keccak256(0x00, 0x40)
        }
    }
}

File 14 of 22 : Math.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
     * with further edits by Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1, "Math: mulDiv overflow");

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
            // See https://cs.stackexchange.com/q/138556/92363.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
            // in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
        }
    }
}

File 15 of 22 : EnumerableSet.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.

pragma solidity ^0.8.0;

/**
 * @dev Library for managing
 * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
 * types.
 *
 * Sets have the following properties:
 *
 * - Elements are added, removed, and checked for existence in constant time
 * (O(1)).
 * - Elements are enumerated in O(n). No guarantees are made on the ordering.
 *
 * ```solidity
 * contract Example {
 *     // Add the library methods
 *     using EnumerableSet for EnumerableSet.AddressSet;
 *
 *     // Declare a set state variable
 *     EnumerableSet.AddressSet private mySet;
 * }
 * ```
 *
 * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
 * and `uint256` (`UintSet`) are supported.
 *
 * [WARNING]
 * ====
 * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
 * unusable.
 * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
 *
 * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
 * array of EnumerableSet.
 * ====
 */
library EnumerableSet {
    // To implement this library for multiple types with as little code
    // repetition as possible, we write it in terms of a generic Set type with
    // bytes32 values.
    // The Set implementation uses private functions, and user-facing
    // implementations (such as AddressSet) are just wrappers around the
    // underlying Set.
    // This means that we can only create new EnumerableSets for types that fit
    // in bytes32.

    struct Set {
        // Storage of set values
        bytes32[] _values;
        // Position of the value in the `values` array, plus 1 because index 0
        // means a value is not in the set.
        mapping(bytes32 => uint256) _indexes;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
            set._values.push(value);
            // The value is stored at length-1, but we add 1 to all indexes
            // and use 0 as a sentinel value
            set._indexes[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function _remove(Set storage set, bytes32 value) private returns (bool) {
        // We read and store the value's index to prevent multiple reads from the same storage slot
        uint256 valueIndex = set._indexes[value];

        if (valueIndex != 0) {
            // Equivalent to contains(set, value)
            // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
            // the array, and then remove the last element (sometimes called as 'swap and pop').
            // This modifies the order of the array, as noted in {at}.

            uint256 toDeleteIndex = valueIndex - 1;
            uint256 lastIndex = set._values.length - 1;

            if (lastIndex != toDeleteIndex) {
                bytes32 lastValue = set._values[lastIndex];

                // Move the last value to the index where the value to delete is
                set._values[toDeleteIndex] = lastValue;
                // Update the index for the moved value
                set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
            }

            // Delete the slot where the moved value was stored
            set._values.pop();

            // Delete the index for the deleted slot
            delete set._indexes[value];

            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
        return set._indexes[value] != 0;
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function _length(Set storage set) private view returns (uint256) {
        return set._values.length;
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function _at(Set storage set, uint256 index) private view returns (bytes32) {
        return set._values[index];
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function _values(Set storage set) private view returns (bytes32[] memory) {
        return set._values;
    }

    // Bytes32Set

    struct Bytes32Set {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _add(set._inner, value);
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _remove(set._inner, value);
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
        return _contains(set._inner, value);
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(Bytes32Set storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
        return _at(set._inner, index);
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
        bytes32[] memory store = _values(set._inner);
        bytes32[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // AddressSet

    struct AddressSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(AddressSet storage set, address value) internal returns (bool) {
        return _add(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(AddressSet storage set, address value) internal returns (bool) {
        return _remove(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(AddressSet storage set, address value) internal view returns (bool) {
        return _contains(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(AddressSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(AddressSet storage set, uint256 index) internal view returns (address) {
        return address(uint160(uint256(_at(set._inner, index))));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(AddressSet storage set) internal view returns (address[] memory) {
        bytes32[] memory store = _values(set._inner);
        address[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // UintSet

    struct UintSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(UintSet storage set, uint256 value) internal returns (bool) {
        return _remove(set._inner, bytes32(value));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
        return _contains(set._inner, bytes32(value));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(UintSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
        return uint256(_at(set._inner, index));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(UintSet storage set) internal view returns (uint256[] memory) {
        bytes32[] memory store = _values(set._inner);
        uint256[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }
}

File 16 of 22 : ArrayHelper.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";

/**
 * @notice A simple library to work with arrays
 */
library ArrayHelper {
    /**
     * @notice The function that searches for the index of the first occurring element, which is
     * greater than or equal to the `element_`. The time complexity is O(log n)
     * @param array the array to search in
     * @param element_ the element
     * @return index_ the index of the found element or the length of the `array` if no such element
     */
    function lowerBound(
        uint256[] storage array,
        uint256 element_
    ) internal view returns (uint256 index_) {
        (uint256 low_, uint256 high_) = (0, array.length);

        while (low_ < high_) {
            uint256 mid_ = Math.average(low_, high_);

            if (array[mid_] >= element_) {
                high_ = mid_;
            } else {
                low_ = mid_ + 1;
            }
        }

        return high_;
    }

    /**
     * @notice The function that searches for the index of the first occurring element, which is
     * greater than the `element_`. The time complexity is O(log n)
     * @param array the array to search in
     * @param element_ the element
     * @return index_ the index of the found element or the length of the `array` if no such element
     */
    function upperBound(
        uint256[] storage array,
        uint256 element_
    ) internal view returns (uint256 index_) {
        (uint256 low_, uint256 high_) = (0, array.length);

        while (low_ < high_) {
            uint256 mid_ = Math.average(low_, high_);

            if (array[mid_] > element_) {
                high_ = mid_;
            } else {
                low_ = mid_ + 1;
            }
        }

        return high_;
    }

    /**
     * @notice The function that calculates the sum of all array elements from `beginIndex_` to
     * `endIndex_` inclusive using its prefix sum array
     * @param beginIndex_ the index of the first range element
     * @param endIndex_ the index of the last range element
     * @return the sum of all elements of the range
     */
    function getRangeSum(
        uint256[] storage prefixes,
        uint256 beginIndex_,
        uint256 endIndex_
    ) internal view returns (uint256) {
        require(beginIndex_ <= endIndex_, "ArrayHelper: wrong range");

        if (beginIndex_ == 0) {
            return prefixes[endIndex_];
        }

        return prefixes[endIndex_] - prefixes[beginIndex_ - 1];
    }

    /**
     * @notice The function to compute the prefix sum array
     * @param arr_ the initial array to be turned into the prefix sum array
     * @return prefixes_ the prefix sum array
     */
    function countPrefixes(
        uint256[] memory arr_
    ) internal pure returns (uint256[] memory prefixes_) {
        if (arr_.length == 0) {
            return prefixes_;
        }

        prefixes_ = new uint256[](arr_.length);
        prefixes_[0] = arr_[0];

        for (uint256 i = 1; i < prefixes_.length; i++) {
            prefixes_[i] = prefixes_[i - 1] + arr_[i];
        }
    }

    /**
     * @notice The function to reverse an array
     * @param arr_ the array to reverse
     * @return reversed_ the reversed array
     */
    function reverse(uint256[] memory arr_) internal pure returns (uint256[] memory reversed_) {
        reversed_ = new uint256[](arr_.length);
        uint256 i = arr_.length;

        while (i > 0) {
            i--;
            reversed_[arr_.length - 1 - i] = arr_[i];
        }
    }

    function reverse(address[] memory arr_) internal pure returns (address[] memory reversed_) {
        reversed_ = new address[](arr_.length);
        uint256 i = arr_.length;

        while (i > 0) {
            i--;
            reversed_[arr_.length - 1 - i] = arr_[i];
        }
    }

    function reverse(string[] memory arr_) internal pure returns (string[] memory reversed_) {
        reversed_ = new string[](arr_.length);
        uint256 i = arr_.length;

        while (i > 0) {
            i--;
            reversed_[arr_.length - 1 - i] = arr_[i];
        }
    }

    function reverse(bytes32[] memory arr_) internal pure returns (bytes32[] memory reversed_) {
        reversed_ = new bytes32[](arr_.length);
        uint256 i = arr_.length;

        while (i > 0) {
            i--;
            reversed_[arr_.length - 1 - i] = arr_[i];
        }
    }

    /**
     * @notice The function to insert an array into the other array
     * @param to_ the array to insert into
     * @param index_ the insertion starting index
     * @param what_ the array to be inserted
     * @return the index to start the next insertion from
     */
    function insert(
        uint256[] memory to_,
        uint256 index_,
        uint256[] memory what_
    ) internal pure returns (uint256) {
        for (uint256 i = 0; i < what_.length; i++) {
            to_[index_ + i] = what_[i];
        }

        return index_ + what_.length;
    }

    function insert(
        address[] memory to_,
        uint256 index_,
        address[] memory what_
    ) internal pure returns (uint256) {
        for (uint256 i = 0; i < what_.length; i++) {
            to_[index_ + i] = what_[i];
        }

        return index_ + what_.length;
    }

    function insert(
        string[] memory to_,
        uint256 index_,
        string[] memory what_
    ) internal pure returns (uint256) {
        for (uint256 i = 0; i < what_.length; i++) {
            to_[index_ + i] = what_[i];
        }

        return index_ + what_.length;
    }

    function insert(
        bytes32[] memory to_,
        uint256 index_,
        bytes32[] memory what_
    ) internal pure returns (uint256) {
        for (uint256 i = 0; i < what_.length; i++) {
            to_[index_ + i] = what_[i];
        }

        return index_ + what_.length;
    }

    /**
     * @notice The function that free memory that was allocated for array
     * @param array_ the array to crop
     * @param newLength_ the new length of the array
     * @return ref to cropped array
     */
    function crop(
        uint256[] memory array_,
        uint256 newLength_
    ) internal pure returns (uint256[] memory) {
        if (newLength_ < array_.length) {
            assembly {
                mstore(array_, newLength_)
            }
        }

        return array_;
    }

    function crop(
        address[] memory array_,
        uint256 newLength_
    ) internal pure returns (address[] memory) {
        if (newLength_ < array_.length) {
            assembly {
                mstore(array_, newLength_)
            }
        }

        return array_;
    }

    function crop(bool[] memory array_, uint256 newLength_) internal pure returns (bool[] memory) {
        if (newLength_ < array_.length) {
            assembly {
                mstore(array_, newLength_)
            }
        }

        return array_;
    }

    function crop(
        string[] memory array_,
        uint256 newLength_
    ) internal pure returns (string[] memory) {
        if (newLength_ < array_.length) {
            assembly {
                mstore(array_, newLength_)
            }
        }

        return array_;
    }

    function crop(
        bytes32[] memory array_,
        uint256 newLength_
    ) internal pure returns (bytes32[] memory) {
        if (newLength_ < array_.length) {
            assembly {
                mstore(array_, newLength_)
            }
        }

        return array_;
    }
}

File 17 of 22 : SetHelper.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

import {StringSet} from "../data-structures/StringSet.sol";

/**
 * @notice A simple library to work with sets
 */
library SetHelper {
    using EnumerableSet for EnumerableSet.UintSet;
    using EnumerableSet for EnumerableSet.AddressSet;
    using StringSet for StringSet.Set;

    /**
     * @notice The function to insert an array of elements into the set
     * @param set the set to insert the elements into
     * @param array_ the elements to be inserted
     */
    function add(EnumerableSet.AddressSet storage set, address[] memory array_) internal {
        for (uint256 i = 0; i < array_.length; i++) {
            set.add(array_[i]);
        }
    }

    function add(EnumerableSet.UintSet storage set, uint256[] memory array_) internal {
        for (uint256 i = 0; i < array_.length; i++) {
            set.add(array_[i]);
        }
    }

    function add(StringSet.Set storage set, string[] memory array_) internal {
        for (uint256 i = 0; i < array_.length; i++) {
            set.add(array_[i]);
        }
    }

    /**
     * @notice The function to remove an array of elements from the set
     * @param set the set to remove the elements from
     * @param array_ the elements to be removed
     */
    function remove(EnumerableSet.AddressSet storage set, address[] memory array_) internal {
        for (uint256 i = 0; i < array_.length; i++) {
            set.remove(array_[i]);
        }
    }

    function remove(EnumerableSet.UintSet storage set, uint256[] memory array_) internal {
        for (uint256 i = 0; i < array_.length; i++) {
            set.remove(array_[i]);
        }
    }

    function remove(StringSet.Set storage set, string[] memory array_) internal {
        for (uint256 i = 0; i < array_.length; i++) {
            set.remove(array_[i]);
        }
    }
}

File 18 of 22 : StringSet.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/**
 * @notice ## Usage example:
 *
 * ```
 * using StringSet for StringSet.Set;
 *
 * StringSet.Set internal set;
 * ```
 */
library StringSet {
    struct Set {
        string[] _values;
        mapping(string => uint256) _indexes;
    }

    /**
     * @notice The function add value to set
     * @param set the set object
     * @param value_ the value to add
     */
    function add(Set storage set, string memory value_) internal returns (bool) {
        if (!contains(set, value_)) {
            set._values.push(value_);
            set._indexes[value_] = set._values.length;

            return true;
        } else {
            return false;
        }
    }

    /**
     * @notice The function remove value to set
     * @param set the set object
     * @param value_ the value to remove
     */
    function remove(Set storage set, string memory value_) internal returns (bool) {
        uint256 valueIndex_ = set._indexes[value_];

        if (valueIndex_ != 0) {
            uint256 toDeleteIndex_ = valueIndex_ - 1;
            uint256 lastIndex_ = set._values.length - 1;

            if (lastIndex_ != toDeleteIndex_) {
                string memory lastValue_ = set._values[lastIndex_];

                set._values[toDeleteIndex_] = lastValue_;
                set._indexes[lastValue_] = valueIndex_;
            }

            set._values.pop();

            delete set._indexes[value_];

            return true;
        } else {
            return false;
        }
    }

    /**
     * @notice The function returns true if value in the set
     * @param set the set object
     * @param value_ the value to search in set
     * @return true if value is in the set, false otherwise
     */
    function contains(Set storage set, string memory value_) internal view returns (bool) {
        return set._indexes[value_] != 0;
    }

    /**
     * @notice The function returns length of set
     * @param set the set object
     * @return the the number of elements in the set
     */
    function length(Set storage set) internal view returns (uint256) {
        return set._values.length;
    }

    /**
     * @notice The function returns value from set by index
     * @param set the set object
     * @param index_ the index of slot in set
     * @return the value at index
     */
    function at(Set storage set, uint256 index_) internal view returns (string memory) {
        return set._values[index_];
    }

    /**
     * @notice The function that returns values the set stores, can be very expensive to call
     * @param set the set object
     * @return the memory array of values
     */
    function values(Set storage set) internal view returns (string[] memory) {
        return set._values;
    }
}

File 19 of 22 : DecimalsConverter.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import {ERC20, IERC20, IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

/**
 * @notice This library is used to convert numbers that use token's N decimals to M decimals.
 * Comes extremely handy with standardizing the business logic that is intended to work with many different ERC20 tokens
 * that have different precision (decimals). One can perform calculations with 18 decimals only and resort to convertion
 * only when the payouts (or interactions) with the actual tokes have to be made.
 *
 * The best usage scenario involves accepting and calculating values with 18 decimals throughout the project, despite the tokens decimals.
 *
 * Also it is recommended to call `round18()` function on the first execution line in order to get rid of the
 * trailing numbers if the destination decimals are less than 18
 *
 * ## Usage example:
 *
 * ```
 * contract Taker {
 *     ERC20 public USDC;
 *     uint256 public paid;
 *
 *     . . .
 *
 *     function pay(uint256 amount) external {
 *         uint256 decimals = USDC.decimals();
 *         amount = amount.round18(decimals);
 *
 *         paid += amount;
 *         USDC.transferFrom(msg.sender, address(this), amount.from18(decimals));
 *     }
 * }
 * ```
 */
library DecimalsConverter {
    /**
     * @notice The function to get the decimals of ERC20 token. Needed for bytecode optimization
     * @param token_ the ERC20 token
     * @return the decimals of provided token
     */
    function decimals(address token_) internal view returns (uint8) {
        return ERC20(token_).decimals();
    }

    /**
     * @notice The function to bring the number to 18 decimals of precision
     * @param amount_ the number to convert
     * @param token_ the token, whose decimals will be precised to 18
     * @return the number brought to 18 decimals of precision
     */
    function to18(uint256 amount_, address token_) internal view returns (uint256) {
        return to18(amount_, decimals(token_));
    }

    /**
     * @notice The function to bring the number to 18 decimals of precision
     * @param amount_ the number to convert
     * @param baseDecimals_ the current precision of the number
     * @return the number brought to 18 decimals of precision
     */
    function to18(uint256 amount_, uint256 baseDecimals_) internal pure returns (uint256) {
        return convert(amount_, baseDecimals_, 18);
    }

    /**
     * @notice The function to bring the number to 18 decimals of precision. Reverts if output is zero
     * @param amount_ the number to convert
     * @param token_ the token, whose decimals will be precised to 18
     * @return the number brought to 18 decimals of precision
     */
    function to18Safe(uint256 amount_, address token_) internal view returns (uint256) {
        return to18Safe(amount_, decimals(token_));
    }

    /**
     * @notice The function to bring the number to 18 decimals of precision. Reverts if output is zero
     * @param amount_ the number to convert
     * @param baseDecimals_ the current precision of the number
     * @return the number brought to 18 decimals of precision
     */
    function to18Safe(uint256 amount_, uint256 baseDecimals_) internal pure returns (uint256) {
        return _convertSafe(amount_, baseDecimals_, _to18);
    }

    /**
     * @notice The function to bring the number from 18 decimals to the desired decimals of precision
     * @param amount_ the number to covert
     * @param token_ the token, whose decimals will be used as desired decimals of precision
     * @return the number brought from 18 to desired decimals of precision
     */
    function from18(uint256 amount_, address token_) internal view returns (uint256) {
        return from18(amount_, decimals(token_));
    }

    /**
     * @notice The function to bring the number from 18 decimals to the desired decimals of precision
     * @param amount_ the number to covert
     * @param destDecimals_ the desired precision decimals
     * @return the number brought from 18 to desired decimals of precision
     */
    function from18(uint256 amount_, uint256 destDecimals_) internal pure returns (uint256) {
        return convert(amount_, 18, destDecimals_);
    }

    /**
     * @notice The function to bring the number from 18 decimals to the desired decimals of precision.
     * Reverts if output is zero
     * @param amount_ the number to covert
     * @param token_ the token, whose decimals will be used as desired decimals of precision
     * @return the number brought from 18 to desired decimals of precision
     */
    function from18Safe(uint256 amount_, address token_) internal view returns (uint256) {
        return from18Safe(amount_, decimals(token_));
    }

    /**
     * @notice The function to bring the number from 18 decimals to the desired decimals of precision.
     * Reverts if output is zero
     * @param amount_ the number to covert
     * @param destDecimals_ the desired precision decimals
     * @return the number brought from 18 to desired decimals of precision
     */
    function from18Safe(uint256 amount_, uint256 destDecimals_) internal pure returns (uint256) {
        return _convertSafe(amount_, destDecimals_, _from18);
    }

    /**
     * @notice The function to substitute the trailing digits of a number with zeros
     * @param amount_ the number to round. Should be with 18 precision decimals
     * @param decimals_ the required number precision
     * @return the rounded number. Comes with 18 precision decimals
     */
    function round18(uint256 amount_, uint256 decimals_) internal pure returns (uint256) {
        return to18(from18(amount_, decimals_), decimals_);
    }

    /**
     * @notice The function to substitute the trailing digits of a number with zeros. Reverts if output is zero
     * @param amount_ the number to round. Should be with 18 precision decimals
     * @param decimals_ the required number precision
     * @return the rounded number. Comes with 18 precision decimals
     */
    function round18Safe(uint256 amount_, uint256 decimals_) internal pure returns (uint256) {
        return _convertSafe(amount_, decimals_, round18);
    }

    /**
     * @notice The function to do the token precision convertion
     * @param amount_ the amount to convert
     * @param baseToken_ current token
     * @param destToken_ desired token
     * @return the converted number
     */
    function convert(
        uint256 amount_,
        address baseToken_,
        address destToken_
    ) internal view returns (uint256) {
        return convert(amount_, uint256(decimals(baseToken_)), uint256(decimals(destToken_)));
    }

    /**
     * @notice The function to do the precision convertion
     * @param amount_ the amount to covert
     * @param baseDecimals_ current number precision
     * @param destDecimals_ desired number precision
     * @return the converted number
     */
    function convert(
        uint256 amount_,
        uint256 baseDecimals_,
        uint256 destDecimals_
    ) internal pure returns (uint256) {
        if (baseDecimals_ > destDecimals_) {
            amount_ = amount_ / 10 ** (baseDecimals_ - destDecimals_);
        } else if (baseDecimals_ < destDecimals_) {
            amount_ = amount_ * 10 ** (destDecimals_ - baseDecimals_);
        }

        return amount_;
    }

    /**
     * @notice The function to do the token precision convertion. Reverts if output is zero
     * @param amount_ the amount to convert
     * @param baseToken_ current token
     * @param destToken_ desired token
     * @return the converted number
     */
    function convertTokensSafe(
        uint256 amount_,
        address baseToken_,
        address destToken_
    ) internal view returns (uint256) {
        return _convertTokensSafe(amount_, baseToken_, destToken_, _convertTokens);
    }

    /**
     * @notice The function to bring the number to 18 decimals of precision
     * @param amount_ the number to convert
     * @param baseDecimals_ the current precision of the number
     * @return the number brought to 18 decimals of precision
     */
    function _to18(uint256 amount_, uint256 baseDecimals_) private pure returns (uint256) {
        return convert(amount_, baseDecimals_, 18);
    }

    /**
     * @notice The function to bring the number from 18 decimals to the desired decimals of precision
     * @param amount_ the number to covert
     * @param destDecimals_ the desired precision decimals
     * @return the number brought from 18 to desired decimals of precision
     */
    function _from18(uint256 amount_, uint256 destDecimals_) private pure returns (uint256) {
        return convert(amount_, 18, destDecimals_);
    }

    /**
     * @notice The function to do the token precision convertion
     * @param amount_ the amount to convert
     * @param baseToken_ current token
     * @param destToken_ desired token
     * @return the converted number
     */
    function _convertTokens(
        uint256 amount_,
        address baseToken_,
        address destToken_
    ) private view returns (uint256) {
        return convert(amount_, uint256(decimals(baseToken_)), uint256(decimals(destToken_)));
    }

    /**
     * @notice The function wrapper to do the safe precision convertion. Reverts if output is zero
     * @param amount_ the amount to covert
     * @param decimals_ the precision decimals
     * @param _convertFunc the internal function pointer to "from", "to", or "round" functions
     * @return conversionResult_ the convertion result
     */
    function _convertSafe(
        uint256 amount_,
        uint256 decimals_,
        function(uint256, uint256) internal pure returns (uint256) _convertFunc
    ) private pure returns (uint256 conversionResult_) {
        conversionResult_ = _convertFunc(amount_, decimals_);

        require(conversionResult_ > 0, "DecimalsConverter: conversion failed");
    }

    /**
     * @notice The function wrapper to do the safe precision convertion for ERC20 tokens. Reverts if output is zero
     * @param amount_ the amount to covert
     * @param baseToken_ current token
     * @param destToken_ desired token
     * @param _convertFunc the internal function pointer to "from", "to", or "round" functions
     * @return conversionResult_ the convertion result
     */
    function _convertTokensSafe(
        uint256 amount_,
        address baseToken_,
        address destToken_,
        function(uint256, address, address) internal view returns (uint256) _convertFunc
    ) private view returns (uint256 conversionResult_) {
        conversionResult_ = _convertFunc(amount_, baseToken_, destToken_);

        require(conversionResult_ > 0, "DecimalsConverter: conversion failed");
    }
}

File 20 of 22 : TypeCaster.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/**
 * @notice This library simplifies non-obvious type castings
 */
library TypeCaster {
    /**
     * @notice The function that casts the list of `X`-type elements to the list of uint256
     * @param from_ the list of `X`-type elements
     * @return array_ the list of uint256
     */
    function asUint256Array(
        bytes32[] memory from_
    ) internal pure returns (uint256[] memory array_) {
        assembly {
            array_ := from_
        }
    }

    function asUint256Array(
        address[] memory from_
    ) internal pure returns (uint256[] memory array_) {
        assembly {
            array_ := from_
        }
    }

    /**
     * @notice The function that casts the list of `X`-type elements to the list of addresses
     * @param from_ the list of `X`-type elements
     * @return array_ the list of addresses
     */
    function asAddressArray(
        bytes32[] memory from_
    ) internal pure returns (address[] memory array_) {
        assembly {
            array_ := from_
        }
    }

    function asAddressArray(
        uint256[] memory from_
    ) internal pure returns (address[] memory array_) {
        assembly {
            array_ := from_
        }
    }

    /**
     * @notice The function that casts the list of `X`-type elements to the list of bytes32
     * @param from_ the list of `X`-type elements
     * @return array_ the list of bytes32
     */
    function asBytes32Array(
        uint256[] memory from_
    ) internal pure returns (bytes32[] memory array_) {
        assembly {
            array_ := from_
        }
    }

    function asBytes32Array(
        address[] memory from_
    ) internal pure returns (bytes32[] memory array_) {
        assembly {
            array_ := from_
        }
    }

    /**
     * @notice The function to transform an element into an array
     * @param from_ the element
     * @return array_ the element as an array
     */
    function asSingletonArray(uint256 from_) internal pure returns (uint256[] memory array_) {
        array_ = new uint256[](1);
        array_[0] = from_;
    }

    function asSingletonArray(address from_) internal pure returns (address[] memory array_) {
        array_ = new address[](1);
        array_[0] = from_;
    }

    function asSingletonArray(bool from_) internal pure returns (bool[] memory array_) {
        array_ = new bool[](1);
        array_[0] = from_;
    }

    function asSingletonArray(string memory from_) internal pure returns (string[] memory array_) {
        array_ = new string[](1);
        array_[0] = from_;
    }

    function asSingletonArray(bytes32 from_) internal pure returns (bytes32[] memory array_) {
        array_ = new bytes32[](1);
        array_[0] = from_;
    }

    /**
     * @notice The function to convert static array to dynamic
     * @param static_ the static array to convert
     * @return dynamic_ the converted dynamic array
     */
    function asDynamic(
        uint256[1] memory static_
    ) internal pure returns (uint256[] memory dynamic_) {
        return asSingletonArray(static_[0]);
    }

    function asDynamic(
        uint256[2] memory static_
    ) internal pure returns (uint256[] memory dynamic_) {
        dynamic_ = new uint256[](2);

        uint256 pointerS_;
        uint256 pointerD_;

        assembly {
            pointerS_ := static_
            pointerD_ := dynamic_
        }

        _copy(pointerS_, pointerD_, 2);
    }

    function asDynamic(
        uint256[3] memory static_
    ) internal pure returns (uint256[] memory dynamic_) {
        dynamic_ = new uint256[](3);

        uint256 pointerS_;
        uint256 pointerD_;

        assembly {
            pointerS_ := static_
            pointerD_ := dynamic_
        }

        _copy(pointerS_, pointerD_, 3);
    }

    function asDynamic(
        uint256[4] memory static_
    ) internal pure returns (uint256[] memory dynamic_) {
        dynamic_ = new uint256[](4);

        uint256 pointerS_;
        uint256 pointerD_;

        assembly {
            pointerS_ := static_
            pointerD_ := dynamic_
        }

        _copy(pointerS_, pointerD_, 4);
    }

    function asDynamic(
        uint256[5] memory static_
    ) internal pure returns (uint256[] memory dynamic_) {
        dynamic_ = new uint256[](5);

        uint256 pointerS_;
        uint256 pointerD_;

        assembly {
            pointerS_ := static_
            pointerD_ := dynamic_
        }

        _copy(pointerS_, pointerD_, 5);
    }

    function asDynamic(
        address[1] memory static_
    ) internal pure returns (address[] memory dynamic_) {
        return asSingletonArray(static_[0]);
    }

    function asDynamic(
        address[2] memory static_
    ) internal pure returns (address[] memory dynamic_) {
        dynamic_ = new address[](2);

        uint256 pointerS_;
        uint256 pointerD_;

        assembly {
            pointerS_ := static_
            pointerD_ := dynamic_
        }

        _copy(pointerS_, pointerD_, 2);
    }

    function asDynamic(
        address[3] memory static_
    ) internal pure returns (address[] memory dynamic_) {
        dynamic_ = new address[](3);

        uint256 pointerS_;
        uint256 pointerD_;

        assembly {
            pointerS_ := static_
            pointerD_ := dynamic_
        }

        _copy(pointerS_, pointerD_, 3);
    }

    function asDynamic(
        address[4] memory static_
    ) internal pure returns (address[] memory dynamic_) {
        dynamic_ = new address[](4);

        uint256 pointerS_;
        uint256 pointerD_;

        assembly {
            pointerS_ := static_
            pointerD_ := dynamic_
        }

        _copy(pointerS_, pointerD_, 4);
    }

    function asDynamic(
        address[5] memory static_
    ) internal pure returns (address[] memory dynamic_) {
        dynamic_ = new address[](5);

        uint256 pointerS_;
        uint256 pointerD_;

        assembly {
            pointerS_ := static_
            pointerD_ := dynamic_
        }

        _copy(pointerS_, pointerD_, 5);
    }

    function asDynamic(bool[1] memory static_) internal pure returns (bool[] memory dynamic_) {
        return asSingletonArray(static_[0]);
    }

    function asDynamic(bool[2] memory static_) internal pure returns (bool[] memory dynamic_) {
        dynamic_ = new bool[](2);

        uint256 pointerS_;
        uint256 pointerD_;

        assembly {
            pointerS_ := static_
            pointerD_ := dynamic_
        }

        _copy(pointerS_, pointerD_, 2);
    }

    function asDynamic(bool[3] memory static_) internal pure returns (bool[] memory dynamic_) {
        dynamic_ = new bool[](3);

        uint256 pointerS_;
        uint256 pointerD_;

        assembly {
            pointerS_ := static_
            pointerD_ := dynamic_
        }

        _copy(pointerS_, pointerD_, 3);
    }

    function asDynamic(bool[4] memory static_) internal pure returns (bool[] memory dynamic_) {
        dynamic_ = new bool[](4);

        uint256 pointerS_;
        uint256 pointerD_;

        assembly {
            pointerS_ := static_
            pointerD_ := dynamic_
        }

        _copy(pointerS_, pointerD_, 4);
    }

    function asDynamic(bool[5] memory static_) internal pure returns (bool[] memory dynamic_) {
        dynamic_ = new bool[](5);

        uint256 pointerS_;
        uint256 pointerD_;

        assembly {
            pointerS_ := static_
            pointerD_ := dynamic_
        }

        _copy(pointerS_, pointerD_, 5);
    }

    function asDynamic(string[1] memory static_) internal pure returns (string[] memory dynamic_) {
        return asSingletonArray(static_[0]);
    }

    function asDynamic(string[2] memory static_) internal pure returns (string[] memory dynamic_) {
        dynamic_ = new string[](2);

        uint256 pointerS_;
        uint256 pointerD_;

        assembly {
            pointerS_ := static_
            pointerD_ := dynamic_
        }

        _copy(pointerS_, pointerD_, 2);
    }

    function asDynamic(string[3] memory static_) internal pure returns (string[] memory dynamic_) {
        dynamic_ = new string[](3);

        uint256 pointerS_;
        uint256 pointerD_;

        assembly {
            pointerS_ := static_
            pointerD_ := dynamic_
        }

        _copy(pointerS_, pointerD_, 3);
    }

    function asDynamic(string[4] memory static_) internal pure returns (string[] memory dynamic_) {
        dynamic_ = new string[](4);

        uint256 pointerS_;
        uint256 pointerD_;

        assembly {
            pointerS_ := static_
            pointerD_ := dynamic_
        }

        _copy(pointerS_, pointerD_, 4);
    }

    function asDynamic(string[5] memory static_) internal pure returns (string[] memory dynamic_) {
        dynamic_ = new string[](5);

        uint256 pointerS_;
        uint256 pointerD_;

        assembly {
            pointerS_ := static_
            pointerD_ := dynamic_
        }

        _copy(pointerS_, pointerD_, 5);
    }

    function asDynamic(
        bytes32[1] memory static_
    ) internal pure returns (bytes32[] memory dynamic_) {
        return asSingletonArray(static_[0]);
    }

    function asDynamic(
        bytes32[2] memory static_
    ) internal pure returns (bytes32[] memory dynamic_) {
        dynamic_ = new bytes32[](2);

        uint256 pointerS_;
        uint256 pointerD_;

        assembly {
            pointerS_ := static_
            pointerD_ := dynamic_
        }

        _copy(pointerS_, pointerD_, 2);
    }

    function asDynamic(
        bytes32[3] memory static_
    ) internal pure returns (bytes32[] memory dynamic_) {
        dynamic_ = new bytes32[](3);

        uint256 pointerS_;
        uint256 pointerD_;

        assembly {
            pointerS_ := static_
            pointerD_ := dynamic_
        }

        _copy(pointerS_, pointerD_, 3);
    }

    function asDynamic(
        bytes32[4] memory static_
    ) internal pure returns (bytes32[] memory dynamic_) {
        dynamic_ = new bytes32[](4);

        uint256 pointerS_;
        uint256 pointerD_;

        assembly {
            pointerS_ := static_
            pointerD_ := dynamic_
        }

        _copy(pointerS_, pointerD_, 4);
    }

    function asDynamic(
        bytes32[5] memory static_
    ) internal pure returns (bytes32[] memory dynamic_) {
        dynamic_ = new bytes32[](5);

        uint256 pointerS_;
        uint256 pointerD_;

        assembly {
            pointerS_ := static_
            pointerD_ := dynamic_
        }

        _copy(pointerS_, pointerD_, 5);
    }

    function _copy(uint256 locationS_, uint256 locationD_, uint256 length_) private pure {
        assembly {
            for {
                let i := 0
            } lt(i, length_) {
                i := add(i, 1)
            } {
                locationD_ := add(locationD_, 0x20)

                mstore(locationD_, mload(locationS_))

                locationS_ := add(locationS_, 0x20)
            }
        }
    }
}

File 21 of 22 : Globals.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

uint256 constant PRECISION = 10 ** 25;
uint256 constant DECIMAL = 10 ** 18;
uint256 constant PERCENTAGE_100 = 10 ** 27;

File 22 of 22 : IP2PSports.sol
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

/**
 * @title IP2PSports
 * @dev Interface for a peer-to-peer sports betting platform.
 * This interface defines the basic events, enums, and structs required for creating, joining, resolving,
 * and canceling challenges, as well as managing and withdrawing bets and admin shares in a decentralized sports betting platform.
 */
interface IP2PSports {
    /**
     * @dev Emitted when the backend address changes.
     * @param backend The new backend address.
     * @param by The address of the user who changed the backend address.
     */
    event BackendChanged(address backend, address by);

    /**
     * @dev Emitted when the merkle root is updated.
     * @param root The new merkle root.
     * @param by The address of the user who updated the merkle root.
     */
    event MerkleRootUpdated(bytes32 root, address by);

    /**
     * @dev Emitted when the maximum number of challengers is updated.
     * @param maxChallengersEachSide The maximum number of challengers on each side.
     * @param maxChallengersForPickem The maximum number of challengers for pickem.
     * @param by The address of the user who updated the maximum number of challengers.
     */
    event MaxChallengersUpdated(
        uint256 maxChallengersEachSide,
        uint256 maxChallengersForPickem,
        address by
    );

    /**
     * @dev Emitted when the minimum USD bet amount is updated.
     * @param amount The new minimum USD bet amount.
     * @param by The address of the user who updated the minimum USD bet amount.
     */
    event MinUSDBettingAmountUpdated(uint256 amount, address by);

    /**
     * @dev Emitted when the membership application status is updated.
     * @param value The new membership application status (true or false).
     * @param by The address of the user who updated the membership application status.
     */
    event MembershipApplied(bool value, address by);

    /**
     * @dev Emitted when betting is enabled or disabled.
     * @param value The new betting status (true or false).
     * @param by The address of the user who updated the betting status.
     */
    event BettingAllowed(bool value, address by);

    /**
     * @dev Emitted when the admin share rules are updated.
     * @param adminShareRules The new admin share rules.
     * @param by The address of the user who updated the admin share rules.
     */
    event AdminShareRulesUpdated(AdminShareRule adminShareRules, address by);

    /**
     * @dev Emitted when an amount is debited in SC.
     * @param amount The amount to be debited.
     * @param by The address of the user who initiated the debit.
     */
    event DebitedInSC(uint256 amount, address by);

    /**
     * @dev Emitted when a token is allowed.
     * @param tokens The addresses of the allowed tokens.
     * @param priceFeeds The addresses of the price feeds for the tokens.
     * @param minBetAmounts The minimum bet amounts for the tokens.
     * @param by The address of the user who allowed the tokens.
     */
    event TokenAllowed(
        address[] tokens,
        address[] priceFeeds,
        uint256[] minBetAmounts,
        address by
    );

    /**
     * @dev Emitted when tokens are restricted.
     * @param tokens The addresses of the restricted tokens.
     * @param by The address of the user who restricted the tokens.
     */
    event TokenRestricted(address[] tokens, address by);

    /**
     * @dev Emitted when a new challenge is created.
     * @param challengeId Unique identifier for the challenge.
     * @param token Address of the token used for betting.
     * @param by Address of the user who created the challenge.
     * @param inputStakedQty The original amount input by user, without any deductions
     */
    event ChallengeCreated(uint256 challengeId, address token, address by, uint256 inputStakedQty);

    /**
     * @dev Emitted when a user joins an existing challenge.
     * @param challengeId Unique identifier for the challenge.
     * @param amount Amount of the token bet by the user.
     * @param by Address of the user who joined the challenge.
     * @param inputStakedQty The original amount input by user, without any deductions
     * @param token Address of the token used for betting.
     */
    event ChallengeJoined(
        uint256 challengeId,
        uint256 amount,
        address by,
        address token,
        uint256 inputStakedQty
    );

    /**
     * @dev Emitted when a user increases amount for an already joined challenge.
     * @param challengeId Unique identifier for the challenge.
     * @param increasedAmount Amount that is added to the previous amount of the user for the specified bet.
     * @param newTotalAmount The new total amount of the user's participation in the bet.
     * @param by Address of the user who increased the challenge amount.
     * @param token Address of the token used for betting.
     */
    event BetAmountIncreased(
        uint256 challengeId,
        uint256 increasedAmount,
        uint256 newTotalAmount,
        address by,
        address token
    );

    /**
     * @dev Emitted when a challenge is resolved.
     * @param challengeId Unique identifier for the challenge.
     * @param finalOutcome Final outcome of the challenge (1 for win, 2 for loss, etc.).
     */
    event ChallengeResolved(uint256 challengeId, uint8 finalOutcome);

    /**
     * @dev Emitted when a challenge is canceled.
     * @param challengeId Unique identifier for the canceled challenge.
     */
    event ChallengeCanceled(uint256 challengeId);

    /**
     * @dev Emitted when a user cancels their participation in a challenge.
     * @param user Address of the user canceling their participation.
     * @param challengeId Unique identifier for the challenge.
     */
    event CancelParticipation(address user, uint256 challengeId);

    /**
     * @dev Emitted after the resolution of a challenge, detailing the redistribution of funds.
     * @param challengeId Unique identifier for the challenge.
     * @param winners Array of addresses of the winning users.
     * @param winnersProfit Array of profits earned by each winning user.
     * @param losers Array of addresses of the losing users.
     * @param losersLoss Array of amounts lost by each losing user.
     */
    event ChallengeFundsMoved(
        uint256 challengeId,
        address[] winners,
        uint256[] winnersProfit,
        address[] losers,
        uint256[] losersLoss,
        MethodType mothodType,
        address token
    );

    /**
     * @dev Emitted when a user withdraws their winnings or funds.
     * @param token Address of the token being withdrawn.
     * @param amount Amount of the token being withdrawn.
     * @param by Address of the user performing the withdrawal.
     */
    event UserWithdrawn(address token, uint256 amount, address by);

    /**
     * @dev Emitted when the admin shares is calculated from challenge participation fees.
     * @param challengeId Unique identifier for the challenge from which the fees were taken.
     * @param token Address of the token in which the fees were paid.
     * @param amount Amount of the fees received.
     */
    event AdminShareCalculated(uint256 challengeId, address token, uint256 amount);

    /**
     * @dev Emitted when the admin receives a share from challenge participation fees.
     * @param challengeId Unique identifier for the challenge from which the fees were taken.
     * @param token Address of the token in which the fees were paid.
     * @param amount Amount of the fees received.
     */
    event AdminReceived(uint256 challengeId, address token, uint256 amount);

    /**
     * @dev Emitted when the referrel commission is earned by the referrer from challenge participation fees.
     * @param challengeId Unique identifier for the challenge from which the fees were taken.
     * @param token Address of the token in which the fees were paid.
     * @param referrers addresses of the referrers.
     * @param referrelCommissions Amount of the fees received.
     */
    event ReferralsEarned(
        uint256 challengeId,
        address token,
        address[] referrers,
        uint256[] referrelCommissions
    );

    /**
     * @dev Emitted when the admin withdraws their accumulated shares.
     * @param token Address of the token being withdrawn.
     * @param amount Amount of the token being withdrawn.
     */
    event AdminWithdrawn(address token, uint256 amount);

    /**
     * @dev Enum for tracking the status of a challenge.
     */
    enum ChallengeStatus {
        None,
        CanBeCreated,
        Betting,
        Awaiting,
        Canceled,
        ResolvedFor,
        ResolvedAgainst,
        ResolvedDraw
    }

    /**
     * @dev Enum for distinguishing between individual and group challenges.
     */
    enum ChallengeType {
        Individual,
        Group
    }

    /**
     * @dev Enum for the functions in which the fund are being distributed.
     */
    enum MethodType {
        ResolveChallenge,
        ResolveGroupChallenge,
        CancelChallenge,
        CancelParticipation
    }

    /**
     * @dev Struct for storing details about a challenge.
     */
    struct Challenge {
        address token; // Token used for betting.
        address[] usersFor; // Users betting for the outcome.
        address[] usersAgainst; // Users betting against the outcome.
        uint256 amountFor; // Total amount bet for the outcome.
        uint256 amountAgainst; // Total amount bet against the outcome.
        ChallengeStatus status; // Current status of the challenge.
        ChallengeType challengeType; // Type of challenge (individual or group).
        uint256 startTime; // Start time of the challenge.
        uint256 endTime; // End time of the challenge.
    }

    /**
     * @dev Struct for storing a user's bet on a challenge.
     */
    struct UserBet {
        uint256 amount; // Amount of the bet.
        uint8 decision; // User's decision (for or against).
        uint256 adminShare; //Admin's share calculated for this bet amount
        address referrer;
        uint256 referralCommision;
    }

    /**
     * @dev Struct for defining admin share rules based on bet thresholds.
     */
    struct AdminShareRule {
        uint256[] thresholds; // Bet amount thresholds for different share percentages.
        uint256[] sharesInUSD; // Admin share in USD for corresponding thresholds.
        bool isSTMX; //To define if this is an STMX or some other
    }

    /**
     *  External Methods
     */

    /** @dev Emits a `ChallengeCreated` event and calls `joinChallenge` for the challenge creator.
     * @param token Address of the token used for betting (zero address for native currency)
     * @param amountFromWallet Amount to be bet from the creator's wallet
     * @param amountFromWithdrawables Amount to be bet from the creator's withdrawable balance
     * @param decision The side of the bet the creator is taking
     * @param challengeType The type of challenge (Individual or Group)
     * @param startTime Start time of the challenge
     * @param endTime End time of the challenge
     * @param membershipLevel user membership level
     * @param feePercentage percentage amount reduced from admin share
     * @param referrer referrer address
     * @param referralCommision referral will get the comission from admin share
     * @param proof leaf nood proof
     */
    function createChallenge(
        address token,
        uint256 amountFromWallet,
        uint256 amountFromWithdrawables,
        uint8 decision,
        ChallengeType challengeType,
        uint256 startTime,
        uint256 endTime,
        uint8 membershipLevel,
        uint256 feePercentage,
        address referrer,
        uint256 referralCommision,
        bytes32[] memory proof
    ) external payable;

    /** @dev This function allows users to withdraw their available tokens from the contract. It uses the
     * nonReentrant modifier from OpenZeppelin to prevent reentrancy attacks. A `UserWithdrawn` event is
     * emitted upon a successful withdrawal.
     * @param token The address of the token to be withdrawn. Use the zero address for the native currency.
     */
    function withdraw(address token) external;

    /** @dev This function is called by the backend to resolve challenges that have reached their end time
     * and are in the awaiting status. It updates the status of each challenge based on its final outcome.
     * Only challenges of type `Individual` can be resolved using this function. A `ChallengeResolved` event is
     * emitted for each challenge that is resolved. This function uses the `onlyBackend` modifier to ensure
     * that only authorized backend addresses can call it, and `nonReentrant` to prevent reentrancy attacks.
     * @param challengeIds Array of IDs of the challenges to be resolved.
     * @param finalOutcomes Array of final outcomes for each challenge, where outcomes are defined as follows:
     * - 1: Side A wins,
     * - 2: Side B wins,
     * - 3: Draw.
     */
    function resolveChallenge(
        uint256[] memory challengeIds,
        uint8[] memory finalOutcomes
    ) external;

    /** @dev This function allows the backend to cancel a user's participation in a challenge, refunding their bet.
     * It can only be called by the backend and is protected against reentrancy attacks. The function checks if the
     * challenge exists and ensures that the challenge is either in the `Awaiting` or `Betting` status, implying that
     * it has not been resolved yet. Additionally, it verifies that the user has indeed placed a bet on the challenge.
     * After these checks, it calls an internal function `_cancelParticipation` to handle the logic for cancelling the
     * user's participation and processing the refund.
     * @param user The address of the user whose participation is to be cancelled.
     * @param challengeId The ID of the challenge from which the user's participation is to be cancelled.
     */
    function cancelParticipation(address user, uint256 challengeId, uint8 cancelType) external;

    /** @dev This function is used for resolving group challenges specifically, where multiple participants can win.
     * It can only be executed by the backend and is protected against reentrancy. The function ensures that the
     * challenge exists, is currently awaiting resolution, and is of the `Group` challenge type. It then validates
     * that the lengths of the winners and profits arrays match and do not exceed the maximum number of winners allowed.
     * Each winner's address must have participated in the challenge, and winners must be unique. The total of the profits
     * percentages must equal 100. Once validated, the challenge status is updated, and profits are calculated and
     * distributed to the winners based on the provided profits percentages.
     * @param challengeId The ID of the group challenge to resolve.
     * @param winners An array of addresses of the winners of the challenge.
     * @param profits An array of profit percentages corresponding to each winner, summing to 100.
     */
    function resolveGroupChallenge(
        uint256 challengeId,
        address[] calldata winners,
        uint256[] calldata profits
    ) external;

    /** @dev This function allows the backend to cancel a challenge if it's either awaiting resolution or still open for betting.
     * It ensures that the challenge exists and is in a cancelable state (either `Awaiting` or `Betting`). Upon cancellation,
     * the challenge's status is updated to `Canceled`, and all bets placed on the challenge are refunded to the participants.
     * This function is protected by the `onlyBackend` modifier to restrict access to the backend address, and `nonReentrant`
     * to prevent reentrancy attacks.
     * @param challengeId The ID of the challenge to be cancelled.
     * @param cancelType 0-Return bet amount without admin shares 1-Return bet amount with admin shares.
     */
    function cancelChallenge(uint256 challengeId, uint8 cancelType) external;

    /** @dev This function allows the contract owner to enable or disable betting across the platform.
     * It's a straightforward toggle that sets the `bettingAllowed` state variable based on the input.
     * Access to this function is restricted to the contract owner through the `onlyOwner` modifier from
     * OpenZeppelin's Ownable contract, ensuring that only the owner can change the betting policy.
     * @param value_ A boolean indicating whether betting should be allowed (`true`) or not (`false`).
     */
    function allowBetting(bool value_) external;

    /** @dev This function will allow the owner to toggle the apply membership values
     * @param value_ true to apply membership values and false for disable membership values
     */
    function updateApplyMembershipValues(bool value_) external;

    /** @dev Can only be called by the contract owner.
     * @param value_ The new minimum betting amount in USD.
     */
    function changeMinUSDBettingAmount(uint256 value_) external;

    /** @dev This function allows the contract owner to change the backend address to a new one.
     * Ensures the new backend address is not the zero address to prevent rendering the contract unusable.
     * The function is protected by the `onlyOwner` modifier, ensuring that only the contract owner has the authority
     * to update the backend address. This is crucial for maintaining the integrity and security of the contract's
     * administrative functions.
     * @param backend_ The new address to be set as the backend. It must be a non-zero address.
     */
    function changeBackend(address backend_) external;

    /** @dev This function is designed to adjust the timing of a challenge, allowing the backend to
     * modify the start and end times as necessary. It's particularly useful for correcting mistakes
     * or accommodating changes in event schedules. The function checks for the existence of the challenge
     * and validates that the new end time is indeed after the new start time to maintain logical consistency.
     * Access is restricted to the backend through the `onlyBackend` modifier to ensure that only authorized
     * personnel can make such adjustments.
     * @param challengeId The ID of the challenge whose timings are to be changed.
     * @param startTime The new start time for the challenge.
     * @param endTime The new end time for the challenge.
     */
    // function changeChallengeTime(
    //     uint256 challengeId,
    //     uint256 startTime,
    //     uint256 endTime
    // ) external;

    /** @dev This function enables the contract owner to restrict certain tokens from being used in betting activities.
     * It involves removing tokens from the list of allowed tokens, potentially removing them from the list of tokens
     * without a Chainlink price feed (oracless tokens), and deleting their associated price feeds if any were set.
     * This is a crucial administrative function for managing the tokens that can be used on the platform, allowing
     * for adjustments based on compliance, liquidity, or other operational considerations.
     * Execution is restricted to the contract's owner through the `onlyOwner` modifier, ensuring that token restrictions
     * can only be imposed by authorized parties.
     * @param tokens An array of token addresses that are to be restricted from use in betting.
     */
    function restrictTokens(address[] memory tokens) external;

    /** @dev Allows the contract owner to define how administrative shares (a portion of betting winnings) are calculated.
     * This can be configured differently for the STMX token versus other tokens, as indicated by the `isSTMX` flag.
     * Each entry in the `thresholds` and `percentages` arrays defines a tier: if the winnings fall into a certain threshold,
     * the corresponding percentage is applied as the administrative share. The function enforces ascending order for thresholds
     * and ensures that the share percentages do not exceed a maximum limit. This setup allows for flexible configuration
     * of administrative fees based on the amount won.
     * Access is restricted to the contract owner through the `onlyOwner` modifier, ensuring that only they can set these rules.
     * @param thresholds An array of threshold values, each representing the lower bound of a winnings bracket.
     * @param percentages An array of percentages corresponding to each threshold, defining the admin share for that bracket.
     * @param token Token address.
     * @param isSTMX A boolean flag indicating whether these rules apply to the STMX token (`true`) or other tokens (`false`).
     */
    function setAdminShareRules(
        uint256[] memory thresholds,
        uint256[] memory percentages,
        address token,
        bool isSTMX
    ) external;

    /**
     * Access is restricted to the contract owner through the `onlyOwner` modifier, ensuring that only they can set these rules.
     * @param _maxChallengersEachSide maximun limit of challengers can join in each side.
     * @param _maxChallengersForPickem maximun limit of challengers can join for pickem.
     */
    function updateMaxChallengers(
        uint256 _maxChallengersEachSide,
        uint256 _maxChallengersForPickem
    ) external;

    /**
     * Access is restricted to the contract owner through the `onlyOwner` modifier, ensuring that only owner can deposit amount to SC.
     * @param _amount amount of tokens.
     * @param _token token address.
     */
    function debitInSC(uint256 _amount, address _token) external payable;

    /** @dev This function provides external access to the administrative share rules that have been set up for either
     * the STMX token (if `isSTMX` is true) or for other tokens (if `isSTMX` is false). These rules define the thresholds
     * and corresponding percentages that determine how administrative shares are calculated from betting winnings.
     * The function returns two arrays: one for the thresholds and one for the percentages, which together outline the
     * structure of admin shares based on the amount of winnings.
     * @param token A boolean flag indicating whether to retrieve the rules for the STMX token (`true`) or other tokens (`false`).
     * @return thresholds An array of uint256 representing the winnings thresholds for admin shares calculation.
     * @return percentages An array of uint256 representing the admin share percentages for each corresponding threshold.
     */
    function getAdminShareRules(
        address token
    )
        external
        view
        returns (uint256[] memory thresholds, uint256[] memory percentages, bool isSTMX);

    /** @dev This function provides external visibility into which tokens are currently permitted for use in betting within the platform.
     * It leverages the EnumerableSet library from OpenZeppelin to handle the dynamic array of addresses representing the allowed tokens.
     * This is particularly useful for interfaces or external contracts that need to verify or display the tokens users can bet with.
     * @return An array of addresses, each representing a token that is allowed for betting.
     */
    function getAllowedTokens() external view returns (address[] memory);

    /** @dev This function provides access to the details of a given challenge, including its current status, which is
     * dynamically determined based on the challenge's timing and resolution state. It's essential for external callers
     * to be able to retrieve comprehensive data on a challenge, such as its participants, status, and betting amounts,
     * to properly interact with or display information about the challenge. The function checks that the requested
     * challenge exists before attempting to access its details.
     * @param challengeId The unique identifier of the challenge for which details are requested.
     * @return challengeDetails A `Challenge` struct containing all relevant data about the challenge, including an updated status.
     *
     * Requirements:
     * - The challenge must exist, as indicated by its ID being within the range of created challenges.
     */
    function getChallengeDetails(
        uint256 challengeId
    ) external view returns (Challenge memory challengeDetails);

    /** @dev This function allows anyone to view the details of a bet made by a user on a specific challenge,
     * including the amount bet and the side the user has chosen. It's crucial for enabling users or interfaces
     * to confirm the details of participation in challenges and to understand the stakes involved. This function
     * directly accesses the mapping of user bets based on the user address and challenge ID, returning the
     * corresponding `UserBet` struct.
     * @param challengeId The ID of the challenge for which the bet details are being queried.
     * @param user The address of the user whose bet details are requested.
     * @return A `UserBet` struct containing the amount of the bet and the decision (side chosen) by the user for the specified challenge.
     */
    function getUserBet(uint256 challengeId, address user) external view returns (UserBet memory);

    /** @dev This function compiles a comprehensive view of all tokens that a user has available to withdraw,
     * including winnings, refunds, or other credits due to the user. It iterates over the entire list of tokens
     * recognized by the contract (not just those currently allowed for betting) to ensure that users can access
     * any funds owed to them, regardless of whether a token's betting status has changed. This is essential for
     * maintaining transparency and access to funds for users within the platform.
     * @param user The address of the user for whom withdrawable balances are being queried.
     * @return tokens An array of token addresses, representing each token that the user has a balance of.
     * @return amounts An array of uint256 values, each corresponding to the balance of the token at the same index in the `tokens` array.
     */
    function getUserWithdrawables(
        address user
    ) external view returns (address[] memory tokens, uint256[] memory amounts);

    /** @dev Emits a `ChallengeJoined` event if the join is successful.
     * @param challengeId ID of the challenge to join
     * @param amountFromWallet Amount to be bet from the user's wallet
     * @param amountFromWithdrawables Amount to be bet from the user's withdrawable balance
     * @param decision The side of the bet the user is taking
     * @param membershipLevel user membership level
     * @param feePercentage percentage amount reduced from admin share
     * @param referrer referrer address
     * @param referralCommision referral will get the comission from admin share
     * @param proof leaf nood proof
     */
    function joinChallenge(
        uint256 challengeId,
        uint256 amountFromWallet,
        uint256 amountFromWithdrawables,
        uint8 decision,
        uint8 membershipLevel,
        uint256 feePercentage,
        address referrer,
        uint256 referralCommision,
        bytes32[] memory proof
    ) external payable;

    /** @dev Emits a `BetAmountIncreased` event if the join is successful.
     * @param challengeId ID of the challenge for which user wants to increase the bet amount
     * @param amountFromWallet Amount to be bet from the user's wallet
     * @param amountFromWithdrawables Amount to be bet from the user's withdrawable balance
     * @param membershipLevel user membership level
     * @param feePercentage percentage amount reduced from admin share
     * @param referrer referrer address
     * @param referralCommision referral will get the comission from admin share
     * @param proof leaf nood proof
     */
    function increaseBetAmount(
        uint256 challengeId,
        uint256 amountFromWallet,
        uint256 amountFromWithdrawables,
        uint8 membershipLevel,
        uint256 feePercentage,
        address referrer,
        uint256 referralCommision,
        bytes32[] memory proof
    ) external payable;

    /** @dev A challenge is considered to exist if its ID is greater than 0 and less than or equal to the latest challenge ID.
     * @param challengeId The ID of the challenge to check.
     * @return bool Returns true if the challenge exists, false otherwise.
     */
    function challengeExists(uint256 challengeId) external view returns (bool);

    /** @dev This function will allow the owner to update the root node of merkle tree
     * @param _root root node of merkle tree
     */
    function updateRoot(bytes32 _root) external;

    /** @dev This function permits the contract owner to add tokens to the list of those allowed for betting.
     * It also associates Chainlink price feeds with tokens, enabling the conversion of bets to a common value basis for calculations.
     * Tokens without a specified price feed (address(0)) are considered to have fixed or known values and are added to a separate list.
     * The function ensures that each token in the input array has a corresponding price feed address (which can be the zero address).
     * The `onlyOwner` modifier restricts this function's execution to the contract's owner, safeguarding against unauthorized token addition.
     * @param tokens An array of token addresses to be allowed for betting.
     * @param priceFeeds An array of Chainlink price feed addresses corresponding to the tokens. Use address(0) for tokens without a need for price feeds.
     * @param minBetAmounts An array of amount corresponding to every token being allowed, the value for oracless tokens will be considers only in this method.
     * Requirements:
     * - The lengths of the `tokens` and `priceFeeds` arrays must match to ensure each token has a corresponding price feed address.
     */
    function allowTokens(
        address[] memory tokens,
        address[] memory priceFeeds,
        uint256[] memory minBetAmounts
    ) external;
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 200,
    "details": {
      "yul": true
    }
  },
  "viaIR": true,
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {}
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"address","name":"backend_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"challengeId","type":"uint256"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"AdminReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"challengeId","type":"uint256"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"AdminShareCalculated","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"uint256[]","name":"thresholds","type":"uint256[]"},{"internalType":"uint256[]","name":"sharesInUSD","type":"uint256[]"},{"internalType":"bool","name":"isSTMX","type":"bool"}],"indexed":false,"internalType":"struct IP2PSports.AdminShareRule","name":"adminShareRules","type":"tuple"},{"indexed":false,"internalType":"address","name":"by","type":"address"}],"name":"AdminShareRulesUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"AdminWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"backend","type":"address"},{"indexed":false,"internalType":"address","name":"by","type":"address"}],"name":"BackendChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"challengeId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"increasedAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newTotalAmount","type":"uint256"},{"indexed":false,"internalType":"address","name":"by","type":"address"},{"indexed":false,"internalType":"address","name":"token","type":"address"}],"name":"BetAmountIncreased","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"value","type":"bool"},{"indexed":false,"internalType":"address","name":"by","type":"address"}],"name":"BettingAllowed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"challengeId","type":"uint256"}],"name":"CancelParticipation","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"challengeId","type":"uint256"}],"name":"ChallengeCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"challengeId","type":"uint256"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"address","name":"by","type":"address"},{"indexed":false,"internalType":"uint256","name":"inputStakedQty","type":"uint256"}],"name":"ChallengeCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"challengeId","type":"uint256"},{"indexed":false,"internalType":"address[]","name":"winners","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"winnersProfit","type":"uint256[]"},{"indexed":false,"internalType":"address[]","name":"losers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"losersLoss","type":"uint256[]"},{"indexed":false,"internalType":"enum IP2PSports.MethodType","name":"mothodType","type":"uint8"},{"indexed":false,"internalType":"address","name":"token","type":"address"}],"name":"ChallengeFundsMoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"challengeId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"address","name":"by","type":"address"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"inputStakedQty","type":"uint256"}],"name":"ChallengeJoined","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"challengeId","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"finalOutcome","type":"uint8"}],"name":"ChallengeResolved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"address","name":"by","type":"address"}],"name":"DebitedInSC","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"maxChallengersEachSide","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"maxChallengersForPickem","type":"uint256"},{"indexed":false,"internalType":"address","name":"by","type":"address"}],"name":"MaxChallengersUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"value","type":"bool"},{"indexed":false,"internalType":"address","name":"by","type":"address"}],"name":"MembershipApplied","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"root","type":"bytes32"},{"indexed":false,"internalType":"address","name":"by","type":"address"}],"name":"MerkleRootUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"address","name":"by","type":"address"}],"name":"MinUSDBettingAmountUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"challengeId","type":"uint256"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"address[]","name":"referrers","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"referrelCommissions","type":"uint256[]"}],"name":"ReferralsEarned","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[]","name":"tokens","type":"address[]"},{"indexed":false,"internalType":"address[]","name":"priceFeeds","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"minBetAmounts","type":"uint256[]"},{"indexed":false,"internalType":"address","name":"by","type":"address"}],"name":"TokenAllowed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[]","name":"tokens","type":"address[]"},{"indexed":false,"internalType":"address","name":"by","type":"address"}],"name":"TokenRestricted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"address","name":"by","type":"address"}],"name":"UserWithdrawn","type":"event"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"value_","type":"bool"}],"name":"allowBetting","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"tokens","type":"address[]"},{"internalType":"address[]","name":"priceFeeds","type":"address[]"},{"internalType":"uint256[]","name":"minBetAmounts","type":"uint256[]"}],"name":"allowTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"applyMembershipValues","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"awaitingTimeForPublicCancel","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"backend","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bettingAllowed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"challengeId","type":"uint256"},{"internalType":"uint8","name":"cancelType","type":"uint8"}],"name":"cancelChallenge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"challengeId","type":"uint256"},{"internalType":"uint8","name":"cancelType","type":"uint8"}],"name":"cancelParticipation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"challengeId","type":"uint256"}],"name":"challengeExists","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"backend_","type":"address"}],"name":"changeBackend","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"value_","type":"uint256"}],"name":"changeMinUSDBettingAmount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountFromWallet","type":"uint256"},{"internalType":"uint256","name":"amountFromWithdrawables","type":"uint256"},{"internalType":"uint8","name":"decision","type":"uint8"},{"internalType":"enum IP2PSports.ChallengeType","name":"challengeType","type":"uint8"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"uint8","name":"membershipLevel","type":"uint8"},{"internalType":"uint256","name":"feePercentage","type":"uint256"},{"internalType":"address","name":"referrer","type":"address"},{"internalType":"uint256","name":"referralCommision","type":"uint256"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"}],"name":"createChallenge","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_token","type":"address"}],"name":"debitInSC","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"defaultOracleDecimals","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"getAdminShareRules","outputs":[{"internalType":"uint256[]","name":"thresholds","type":"uint256[]"},{"internalType":"uint256[]","name":"sharesInUSD","type":"uint256[]"},{"internalType":"bool","name":"isSTMX","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllowedTokens","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"challengeId","type":"uint256"}],"name":"getChallengeDetails","outputs":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address[]","name":"usersFor","type":"address[]"},{"internalType":"address[]","name":"usersAgainst","type":"address[]"},{"internalType":"uint256","name":"amountFor","type":"uint256"},{"internalType":"uint256","name":"amountAgainst","type":"uint256"},{"internalType":"enum IP2PSports.ChallengeStatus","name":"status","type":"uint8"},{"internalType":"enum IP2PSports.ChallengeType","name":"challengeType","type":"uint8"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"}],"internalType":"struct IP2PSports.Challenge","name":"challengeDetails","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"challengeId","type":"uint256"},{"internalType":"address","name":"user","type":"address"}],"name":"getUserBet","outputs":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint8","name":"decision","type":"uint8"},{"internalType":"uint256","name":"adminShare","type":"uint256"},{"internalType":"address","name":"referrer","type":"address"},{"internalType":"uint256","name":"referralCommision","type":"uint256"}],"internalType":"struct IP2PSports.UserBet","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getUserWithdrawables","outputs":[{"internalType":"address[]","name":"tokens","type":"address[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"challengeId","type":"uint256"},{"internalType":"uint256","name":"amountFromWallet","type":"uint256"},{"internalType":"uint256","name":"amountFromWithdrawables","type":"uint256"},{"internalType":"uint8","name":"membershipLevel","type":"uint8"},{"internalType":"uint256","name":"feePercentage","type":"uint256"},{"internalType":"address","name":"referrer","type":"address"},{"internalType":"uint256","name":"referralCommision","type":"uint256"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"}],"name":"increaseBetAmount","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"challengeId","type":"uint256"},{"internalType":"uint256","name":"amountFromWallet","type":"uint256"},{"internalType":"uint256","name":"amountFromWithdrawables","type":"uint256"},{"internalType":"uint8","name":"decision","type":"uint8"},{"internalType":"uint8","name":"membershipLevel","type":"uint8"},{"internalType":"uint256","name":"feePercentage","type":"uint256"},{"internalType":"address","name":"referrer","type":"address"},{"internalType":"uint256","name":"referralCommision","type":"uint256"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"}],"name":"joinChallenge","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"latestChallengeId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxAdminShareInUsd","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxAdminShareSTMX","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxAdminShareThresholds","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxChallengersEachSide","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxChallengersForPickem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxChallengesToResolve","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxForMinUSDBetAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxWinnersGroupChallenge","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minUSDBetAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"priceFeedErrorMargin","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"challengeIds","type":"uint256[]"},{"internalType":"uint8[]","name":"finalOutcomes","type":"uint8[]"}],"name":"resolveChallenge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"challengeId","type":"uint256"},{"internalType":"address[]","name":"winners","type":"address[]"},{"internalType":"uint256[]","name":"profits","type":"uint256[]"}],"name":"resolveGroupChallenge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"tokens","type":"address[]"}],"name":"restrictTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"thresholds","type":"uint256[]"},{"internalType":"uint256[]","name":"sharesInUSD","type":"uint256[]"},{"internalType":"address","name":"token","type":"address"},{"internalType":"bool","name":"isSTMX","type":"bool"}],"name":"setAdminShareRules","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"value_","type":"bool"}],"name":"updateApplyMembershipValues","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxChallengersEachSide","type":"uint256"},{"internalType":"uint256","name":"_maxChallengersForPickem","type":"uint256"}],"name":"updateMaxChallengers","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_root","type":"bytes32"}],"name":"updateRoot","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]

6080346200010957601f620059d438819003918201601f19168301916001600160401b038311848410176200010e578084926020946040528339810103126200010957516001600160a01b0380821691829003620001095760018060a01b03199081600154166001556000543383821617600055604051913391167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a360016002558215620000e35750600454161760045560326005556032600655600160ff19600a541617600a55633b9aca006007556040516158af9081620001258239f35b62461bcd60e51b81526020600482015260016024820152603160f81b6044820152606490fd5b600080fd5b634e487b7160e01b600052604160045260246000fdfe61016080604052600436101561001e575b50361561001c57600080fd5b005b60003560e01c908163024ece8914613c5f575080630306c19a1461363d57806307c9f4661461361d578063099e4133146135f4578063130894e0146135865780631411b438146135685780631f2a8dd6146135455780631fb05b33146135275780632179e5fe14612fd357806321ff997014612f825780633d40a54f14612dda5780634655471914612da257806346d1cdc014612cdf5780634c9f166d14612cbc57806351cff8d914612b46578063615b2c7814612793578063715018a61461273657806375b1510c146115bf57806378a29c09146126c45780637946b7811461262457806379ba5097146125615780638288c80e146124e25780638770912f1461235b5780638bbed8ef1461233d5780638c2bd69214611cef5780638da5cb5b14611cc65780638edfe78714611caa5780639474aef614611c8c57806395e737c014611c1e578063a8037e0e14611b34578063a8f3f408146115e9578063ab04663b146115c4578063ab535f8f146115bf578063b9f433a614610bbe578063bf26931f14610ba0578063c03fb87c14610acc578063ca44bb5314610ab0578063d0339fee14610922578063d89b6dc014610906578063def08d26146107ed578063e30c3978146107c4578063eaecfca714610727578063ef2104a2146102aa578063f030f0131461028a5763f2fde38b1461021a5738610010565b3461028557602036600319011261028557610233613d74565b61023b613f96565b60018060a01b0380911690816001600160601b0360a01b6001541617600155600054167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700600080a3005b600080fd5b3461028557600036600319011261028557602060405164174876e8008152f35b346102855760403660031901126102855760243560ff81168103610285576004546001600160a01b031633148015610713575b156106e9576102ea614802565b6102f56004356156d6565b610300600435615764565b6004356000526012602052604060002060018060a01b03600054163314610691575b600501600460ff198254161790557ff0fbc916c90170d09d494b840e0bb03ab2e82065a8e1990d3673211b45182a8e60206040516004358152a1600435600052601260205260406000209060018060a01b0382541690600183015491600284015491610396610391848661402d565b614fd3565b926103a4610391828761402d565b946103b2610391838361402d565b916103c0610391828461402d565b976000926000958a89898c6000935b86851061063e5750505050506000925b84841061049d575060008051602061585a8339815191528a8a6104938b8f6104608d60008051602061581a83398151915260008051602061583a833981519152938f61043460405192839289600435856150f4565b0390a16040805160043581526001600160a01b0386166020820152908101919091529081906060820190565b0390a160405161046f81613e21565b600081526040519061048082613e21565b60008252604051958695600435876151b5565b0390a16001600255005b9091929394966105bb6105b5886105c1938f8f8f8f918f8b908e958e956104d46104cb8f60028b91016149a0565b9890549961402d565b956104ed8760018060a01b038b8b60031b1c16926148a9565b526001600160a01b03600388811b8a901c82166000908152601360209081526040808320600480358552925290912080546002820154928201549190930154909316969193909160ff166001036105cc575061057a9796959492610558610574959361055e9361402d565b9061402d565b936105748360009d6000975b6105748b8b6148a9565b526148a9565b519160018060a01b039160031b1c1660005260146020528d6040600020906000526020526105ae604060002091825461402d565b905561402d565b98613fee565b94613fee565b9291909493946103df565b85829d610574969497956105749361057a9c9b9a9560018060a01b036000541660005260149081602052604060002083600052602052610612604060002091825461402d565b90558660005260205260406000209060005260205260406000206106378a825461402d565b905561056a565b9a6105bb936105b593868d948d9f99829d9e8d9b610665839e9f9c60016106819e016149a0565b969054976104ed8760018060a01b038b8b60031b1c16926148a9565b89898c8e989798969594966103cf565b60078101546202a30081018091116106d35742116103225760405162461bcd60e51b8152602060048201526002602482015261062760f31b6044820152606490fd5b634e487b7160e01b600052601160045260246000fd5b60405162461bcd60e51b81526020600482015260026024820152611a1960f11b6044820152606490fd5b506000546001600160a01b031633146102dd565b3461028557602036600319011261028557610740613d74565b610748613f96565b6001600160a01b0316801561079b57600480546001600160a01b03191682179055604080519182523360208301527fea8ca364cc9c0e44ae5ceab245cececcc710724873a9ecfc89a0559983e4473991a1005b60405162461bcd60e51b81526020600482015260016024820152603160f81b6044820152606490fd5b34610285576000366003190112610285576001546040516001600160a01b039091168152602090f35b604036600319011261028557600435610804613d8a565b61080c613f96565b60018060a01b03169081600052602090601082526108306040600020541515613ffd565b61083b811515614901565b826108eb578034036108c1575b3360005260148252604060002083600052825261086b604060002091825461402d565b9055336000818152601483526040808220948252939092529082902054825190815260208101919091527fc6eb93583634faca0ac6216e0725f08457b08e39adbbf4b9484973740f23327791819081015b0390a1005b60405162461bcd60e51b8152600481018390526002602482015261323960f01b6044820152606490fd5b6108f53415614d51565b61090181303386614d82565b610848565b3461028557600036600319011261028557602060405160058152f35b61018036600319011261028557610937613d74565b604435602435610945613d64565b6084359060028210156102855760a4359360c4359160e4359160ff831683036102855761012435946001600160a01b03808716870361028557610164356001600160401b0381116102855761099e903690600401613e74565b986109aa600954613fee565b96876009554282111580610aa7575b15610a7e5760077fd2f7931a802085b3d0234d4c320ce7ee0041da96678ce2bf5c93e8d3d7e65f529460809461001c9e1693610a0a610a05866000526010602052604060002054151590565b613ffd565b8a6000526012602052604060002091856001600160601b0360a01b8454161783556002600584019161ff0083549160081b169061ffff19161717905560068201550155610a57848a61402d565b6040519188835260208301523360408301526060820152a1610144359561010435946140a0565b60405162461bcd60e51b81526020600482015260016024820152601960f91b6044820152606490fd5b504281116109b9565b3461028557600036600319011261028557602060405160088152f35b346102855760403660031901126102855760a0610ae7613d8a565b60006080604051610af781613dd0565b8281528260208201528260408201528260608201520152600180831b038091166000526013602052604060002060043560005260205260406000209060405190610b4082613dd0565b82549283835260ff6001820154166020840190815260ff6002830154916040860192835260806004866003870154169560608901968752015496019586526040519687525116602086015251604085015251166060830152516080820152f35b34610285576000366003190112610285576020600954604051908152f35b34610285576040366003190112610285576001600160401b0360043581811161028557610bef903690600401613e74565b6024359182116102855736602383011215610285578160040135610c1281613e5d565b92610c206040519485613e3c565b8184526024602085019260051b8201019036821161028557602401915b8183106115a557505050610c4f6157bf565b610c57614802565b805190600a8211158061159b575b610c729093929193614856565b6000925b818410610c84576001600255005b610c8e84826148a9565b5193846000526012602052600560406000200180549060ff916008928082851c166002811015611527576115715780610cc7868a6148a9565b51169384151580611567575b1561153d57610ce18a6156d6565b610cea8a61571f565b60048501908282116106d357828216101561152757169060ff19161790557fb6171b34500ba874839b220ee75cbfbeb25bba762c6a57c158e19a3c27af89ae60408051888152836020820152a1600381036111aa57509091929380600052601260205260406000209060018060a01b03825416600183015492600281015490610d76610391838761402d565b94610d84610391848361402d565b92610d92610391828461402d565b90610da0610391828561402d565b9260009160009560005b83811061101857506000925b828410610e7b5750505050509260008051602061583a833981519152610e3b610e6b9460008051602061581a833981519152610e739b9a989560008051602061585a8339815191529a98610e116040519283928a8d856150f4565b0390a1604080518881526001600160a01b0387166020820152908101919091529081906060820190565b0390a1604051610e4a81613e21565b6000815260405191610e5b83613e21565b60008352604051968796876151b5565b0390a1613fee565b929190610c76565b90919293968a8c8b876002870190610e92916149a0565b905460039190911b1c6001600160a01b03169182610eb0878b61402d565b9182610ebb916148a9565b52826000526013602052604060002084600052602052604060002054938360005260136020526040600020816000526020528d8d6040600020600201549686600052601360205260406000208460005260205260406000206004015493876000526013602052604060002090600052602052600160a01b600190036040600020600301541692600160a01b60019003600054166000526014968760205260406000208160005260205260406000208a815490610f769161402d565b9055846000528760205260406000209060005260205285604060002086815490610f9f9161402d565b9055610faa916148a9565b52610fb5908d6148a9565b52610fc08d8d6148a9565b52610fcb908d6148a9565b519160005260205260406000208c600052602052604060002090815490610ff19161402d565b9055610ffc9161402d565b9661100690613fee565b9361101090613fee565b929190610db6565b9396919290918b8561102d81600187016149a0565b905460039190911b1c6001600160a01b031691829161104b916148a9565b5280600052601360205260406000208c6000526020526040600020549080600052601360205260406000208d6000526020528b8d8c6040600020600201549484600052601360205260406000208360005260205260406000206004015492856000526013602052604060002090600052602052600160a01b600190036040600020600301541691600160a01b6001900360005416600052601494856020526040600020816000526020526040600020888154906111079161402d565b905583600052856020526040600020906000526020528b6040600020858154906111309161402d565b905561113b916148a9565b526111468d8c6148a9565b526111518c8c6148a9565b5261115c888d6148a9565b519160005260205260406000208c6000526020526040600020908154906111829161402d565b905561118d9161402d565b9661119790613fee565b936111a190613fee565b92919092610daa565b85600052601260205260406000209560018060a01b038754166000600189019360028a019182600460038d01549c0154926002849114611519575b5050855483549b92908c906111f981614fd3565b9461121461039161120d610391868661402d565b948461402d565b936000926000925b81841061141857505050506112388e9798999a9b9c9d9e614fd3565b966000905b80821061135757505050936113266113429460008051602061583a8339815191526112f98b999660008051602061581a833981519152610e739f9e9c60008051602061585a8339815191529e6112cf6113349a839e60018060a01b0360005416600052601460205260406000208560005260205260406000206112c188825461402d565b9055604051948594856150f4565b0390a1604080518c81526001600160a01b038c166020820152908101919091529081906060820190565b0390a1611318604051998a998a5260e060208b015260e08a019061512c565b9088820360408a0152613eee565b90868203606088015261512c565b908482036080860152613eee565b90600060a084015260c08301520390a1613fee565b90919461140c6114066114129260028c8f8f8c918f8b611376916149a0565b60018060a01b0391549060031b1c1660005260136020526040600020906000526020526040600020926113ab8b8554926148a9565b5260038301546001600160a01b0316806113c5848e6148a9565b528c6113d760048601549485926148a9565b5260005260146020526040600020906000526020526113fc604060002091825461402d565b905501549061402d565b96613fee565b92613fee565b9061123d565b90919293966105bb6105b5611511928f8f8f8b908f8f8f948b958f938f61148761148f916114488860029d6149a0565b60018060a01b0391549060031b1c169586600052601360205260406000209060005260205261055860406000209b6114828d5480946148ee565b61504d565b9586926148a9565b5260038701956114a98460018060a01b03895416926148a9565b526114ba60048801938454926148a9565b52600052601491826020526040600020846000526020526114e1604060002091825461402d565b9055549260018060a01b039054166000526020526040600020906000526020526113fc604060002091825461402d565b92919061121c565b9b9096935091508b806111e5565b634e487b7160e01b600052602160045260246000fd5b60405162461bcd60e51b8152602060048201526002602482015261313160f01b6044820152606490fd5b5060048510610cd3565b60405162461bcd60e51b8152602060048201526002602482015261031360f41b6044820152606490fd5b5082518214610c65565b823560ff8116810361028557815260209283019201610c3d565b613ed2565b3461028557600036600319011261028557602060405169152d02c7e14af68000008152f35b34610285576060366003190112610285576001600160401b036004358181116102855761161a903690600401613f78565b9060243581811161028557611633903690600401613f78565b906044359081116102855761164c903690600401613e74565b90611655613f96565b825161166e825184519083149081611b2a575b50614856565b60005b81811061174c57505060005b83518110156116b1576116ac906116a66001600160a01b0361169f83886148a9565b5116614a50565b50613fee565b61167d565b509060005b83518110156116e4576116df906116a66001600160a01b036116d883886148a9565b5116614ab4565b6116b6565b7fee34c4fff4314ef30863a798e2773a9aa5251ff332d9de421fdddcabfc423778611725856117418561173388604051958695608087526080870190613ce8565b908582036020870152613ce8565b908382036040850152613eee565b3360608301520390a1005b6117766001600160a01b0361176183886148a9565b51166000526010602052604060002054151590565b611b00576001600160a01b0361178c82856148a9565b511661181f5761179c81856148a9565b51156117f557806117b06117f092866148a9565b516001600160a01b036117c383896148a9565b51166000908152601660205260409020556116a66001600160a01b036117e983896148a9565b51166149b8565b611671565b60405162461bcd60e51b8152602060048201526002602482015261032360f41b6044820152606490fd5b6001600160a01b0361183182856148a9565b511660405190633fabe5a360e21b9081835260a083600481845afa8015611a5357600093600091611ad8575b506000841315611aae576118756201518091426148e1565b11611a845760405191825260a082600481845afa918215611a5357600092611a5f575b506000908190815b60058110611987575b50506118b592506153fd565b90606491826118c3826153e7565b059060008282039212818312811690828413901516176106d35780846118eb6118f2936153e7565b05906157fd565b90821215918261197c575b50501561195557506117f0906001600160a01b0361191b82866148a9565b51166001600160a01b0361192f83896148a9565b511660005260116020526040600020906001600160601b0360a01b825416179055613fee565b60405162461bcd60e51b8152602060048201526002602482015261323760f01b6044820152fd5b1315905087806118fd565b909160405190639a6fc8f560e01b82526001600160501b038616600483015260a082602481875afa908115611a53576119c892600092611a1d575b506157fd565b92600181018091116106d357936001600160501b03811615611a14576001600160501b036000199116016001600160501b0381116106d357611a0a9091613fee565b93929190936118a0565b849392506118a9565b611a4091925060a03d60a011611a4c575b611a388183613e3c565b8101906153a7565b5050509050908d6119c2565b503d611a2e565b6040513d6000823e3d90fd5b611a7991925060a03d60a011611a4c57611a388183613e3c565b505050509088611898565b60405162461bcd60e51b8152602060048201526002602482015261323560f01b6044820152606490fd5b60405162461bcd60e51b8152602060048201526002602482015261313960f01b6044820152606490fd5b9050611af491935060a03d60a011611a4c57611a388183613e3c565b5094925050928961185d565b60405162461bcd60e51b81526020600482015260026024820152611a1b60f11b6044820152606490fd5b9050821486611668565b346102855760208060031936011261028557611b4e613d74565b90600b8054611b5c81614fd3565b93611b6682614fd3565b926001600160a01b039182169060005b848110611bab57611b9a88611ba78989604051948594604086526040860190613ce8565b9184830390850152613eee565b0390f35b611c19908260005284817f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9015416611be3828b6148a9565b528360005260148852604060002085611bfc838c6148a9565b51166000528852604060002054611c1382896148a9565b52613fee565b611b76565b34610285576020366003190112610285577f39d65726005dded9e32c1c37c38c433a478814ed5df9a080f9788a8861cc383a6108bc611c5b613d55565b611c63613f96565b600a805460ff191660ff9215159283161790556040805191825233602083015290918291820190565b34610285576000366003190112610285576020600754604051908152f35b3461028557600036600319011261028557602060405160148152f35b34610285576000366003190112610285576000546040516001600160a01b039091168152602090f35b3461028557606036600319011261028557611d08613d74565b6044359060ff8216820361028557611d1e6157bf565b611d26614802565b611d316024356156d6565b611d3c602435615764565b60018060a01b0381166000526013602052604060002060243560005260205260ff600160406000200154161561231357602435600052601260205260406000209060018060a01b0382541691600181015491600282015460405193611da085613e06565b600185526020368187013760405191611db883613e06565b600183526020368185013760405194611dd086613e06565b600186526020368188013760405192611de884613e06565b60018452602036818601376001600160a01b038681166000908152601360209081526040808320602435845290915290208054600280830154600484015460039094015460058801549499951697909693909160089190911c60ff169081101561152757801590816122de575b81156122d3575b50156121395750600160ff611e7b8b611e7684870161403a565b6151f6565b9e16036120db57611e9090610558888761402d565b9a5b60018060a01b03891660005260146020526040600020906000526020526040600020611ebf8c825461402d565b905560038101611ed08c82546148e1565b9055826000198101116106d3577f679f73fac18389f339b511a489b811473ff4d0f3d4937002ed9951c7636cce3a6120236120c49960008051602061583a8339815191529860008051602061585a8339815191529f986113189f8f8f9091611fb88f611fbe946120709f611f9660008051602061581a8339815191529f8e926001611f64611f7993600019018287016149a0565b828060a01b0391549060031b1c1694016149a0565b819391549060031b9160018060a01b03809116831b921b19161790565b9055611fa460018d01615172565b6001600160a01b03891690611fb890614886565b52614886565b52611fc886614886565b526001600160a01b031660008181526013602090815260408083206024358085529083528184208481556001810185905560028101859055600381018590556004019390935580519384529083019190915290918291820190565b0390a160018060a01b03905416976120446040519283928b602435856150f4565b0390a16040805160243581526001600160a01b0388166020820152908101919091529081906060820190565b0390a161133460405161208281613e21565b600081526120b66040519361209685613e21565b600085526040519889986024358a5260e060208b015260e08a0190613ce8565b908682036060880152613ce8565b90600360a084015260c08301520390a16001600255005b9a60018060a01b03600054166000526014602052604060002081600052602052604060002061210b86825461402d565b9055856000526014602052604060002081600052602052604060002061213288825461402d565b9055611e92565b9350919a93600160ff6121548b611e766002879c970161403a565b9e160361226f5761216992916105589161402d565b986000946000935b60018060a01b0389166000526014602052604060002090600052602052604060002061219e8c825461402d565b9055600481016121af8c82546148e1565b9055826000198101116106d3577f679f73fac18389f339b511a489b811473ff4d0f3d4937002ed9951c7636cce3a6120236120c49960008051602061583a8339815191529860008051602061585a8339815191529f986113189f8f8f9091611fb88f611fbe946120709f61225c60008051602061581a8339815191529f8e926002612243611f7993600019018287016149a0565b905460039190911b1c6001600160a01b031694016149a0565b905561226a60028d01615172565b611fa4565b9360018060a09d949d9893981b0360005416600052601460205260406000208160005260205260406000206122a586825461402d565b905585600052601460205260406000208160005260205260406000206122cc88825461402d565b9055612171565b60019150148f611e5c565b905060018060a01b038b1660005260136020526040600020602435600052602052600160ff8160406000200154161490611e55565b60405162461bcd60e51b8152602060048201526002602482015261189960f11b6044820152606490fd5b34610285576000366003190112610285576020600654604051908152f35b3461028557602080600319360112610285576004356001600160401b0381116102855761238c903690600401613f78565b90612395613f96565b815160005b81811061244b57505060005b82518110156123d4576123cf906116a66001600160a01b036123c883876148a9565b5116614b1f565b6123a6565b5060005b825181101561240657612401906116a66001600160a01b036123fa83876148a9565b5116614c09565b6123d8565b7fe315bdee05329e2e69a5253f25814537fb86d11c4c808142d2ea502822516e7f6124408484604051928392604084526040840190613ce8565b9033908301520390a1005b6001600160a01b036124618161176184886148a9565b156124b85790816124b39261247683886148a9565b51166000526011855260406000206001600160601b0360a01b815416905561249e82876148a9565b51166000526016845260006040812055613fee565b61239a565b60405162461bcd60e51b8152600481018590526002602482015261343760f01b6044820152606490fd5b34610285576020366003190112610285577f1952e249d74df2e2b0c9a7de47fdd506d63de48981dfbffad2b925a1152928f96108bc600435612522613f96565b60075481101580612552575b61253790614901565b60078190556040805191825233602083015290918291820190565b506402540be40081111561252e565b34610285576000366003190112610285576001546001600160a01b0333818316036125cd576001600160601b0360a01b8092166001556000549133908316176000553391167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a3005b60405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b6064820152608490fd5b34610285576020366003190112610285576001600160a01b03612645613d74565b1660005260156020526126aa604060002060ff6002820154166126b860016126966040519461267f866126788184614d14565b0387613e3c565b61268f6040518094819301614d14565b0382613e3c565b604051948594606086526060860190613eee565b908482036020860152613eee565b90151560408301520390f35b610120366003190112610285576126d9613d64565b6084359060ff821682036102855760c435916001600160a01b03831683036102855761010435926001600160401b0384116102855761271f61001c943690600401613e74565b9260e4359260a435916044356024356004356140a0565b346102855760003660031901126102855761274f613f96565b606460405162461bcd60e51b815260206004820152602060248201527f52656e6f756e63696e67206f776e6572736869702069732064697361626c65646044820152fd5b34610285576080366003190112610285576004356001600160401b038111610285576127c3903690600401613e74565b6024356001600160401b038111610285576127e2903690600401613e74565b906044356001600160a01b0381168103610285576064351515606435036102855761280b613f96565b6001600160a01b0381166000908152600c602052604090205461282f901515614cb2565b81519183519183151580612b3d575b80612b32575b61284d90614856565b64174876e8009260005b856000198101116106d35760001986018110156128fc5761287881856148a9565b51600182018083116106d35761288e90866148a9565b51106128d2576064356128c0575b806128b6866128ae6128bb948b6148a9565b511115614ce3565b613fee565b612857565b69152d02c7e14af6800000945061289c565b60405162461bcd60e51b8152602060048201526002602482015261323160f01b6044820152606490fd5b5091859284606435612b21575b60001982019182116106d3576128ae61292292866148a9565b60405190606082018281106001600160401b03821117612ace5760405281526020810192835260408101906064351515825260018060a01b03831660005260156020526040600020905180516001600160401b038111612ace57600160401b91828211612ace578354828555808310612af8575b5060200183600052602060002060005b838110612ae4575050505060019485830190518051926001600160401b038411612ace578311612ace578154838355808410612aa3575b5060200190600052602060002060005b838110612a90577fbf48b0820ca81715d6b1acb25649f7ba1d18bd574712fcc4788a22a3f9e49229612a7a89898960028a019051151560ff8019835416911617905560018060a01b03166000526015602052604060002060ff60026040519485946040865260606040870152612a6660a0870186614d14565b868103603f19016060880152908501614d14565b92015416151560808301523360208301520390a1005b87906020845194019381840155016129ed565b826000528784602060002092830192015b828110612ac25750506129dd565b60008155018890612ab4565b634e487b7160e01b600052604160045260246000fd5b6001906020845194019381840155016129a6565b8460005282602060002091820191015b818110612b155750612996565b60008155600101612b08565b5069152d02c7e14af6800000612909565b506014841115612844565b5082841461283e565b346102855760208060031936011261028557612b60613d74565b612b68614802565b3360009081526014835260408082206001600160a01b039093168083529284529020548015612c935733600052601483526040600020826000528352600060408120558115600014612c3357600080808084335af1612bc5614ec0565b5015612c0957906060917f30f0f2f1c6b25d25ce05404ece9a5feb949e2e6f459f939cc1f01c6512428066935b604051928352820152336040820152a16001600255005b60405162461bcd60e51b8152600481018490526002602482015261333760f01b6044820152606490fd5b60405163a9059cbb60e01b84820152336024820152604480820183905281527f30f0f2f1c6b25d25ce05404ece9a5feb949e2e6f459f939cc1f01c6512428066936060939291612c8e90612c88606482613e3c565b84614dc4565b612bf2565b60405162461bcd60e51b81526004810184905260016024820152600760fb1b6044820152606490fd5b3461028557600036600319011261028557602060ff600a54166040519015158152f35b3461028557604036600319011261028557600435602435612cfe613f96565b80151580612d97575b80612d8e575b80612d83575b15612d59577f42a3e0b2ac4ef58d5c467d7199e859b2b40ee8a57507885e2d4539a9ae0a05da9181606092600655816005556040519182526020820152336040820152a1005b60405162461bcd60e51b8152602060048201526002602482015261323360f01b6044820152606490fd5b506032821115612d13565b50811515612d0d565b506032811115612d07565b346102855760203660031901126102855760206004358015159081612dcd575b506040519015158152f35b9050600954101582612dc2565b34610285576020806003193601126102855760043590604051612dfc81613db4565b60008152606082820152606060408201526000606082015260006080820152600060a0820152600060c0820152600060e0820152600061010080920152612e42836156d6565b826000526012825260406000209160405193612e5d85613db4565b83546001600160a01b03908116865292612e796001860161403a565b93838701948552612e8c6002870161403a565b93604088019485526003870154956060890196875260048801549060808a0191825260058901549260ff60a08c0194612ec782821687614094565b60081c169460c08c0195600281101561152757865260068b01549a60e08d019b8c526007015497878d01988952612efd90615639565b612f079086614094565b6040519b828d525116908b01525195610120968760408c01526101408b01612f2e91613ce8565b90518a8203601f190160608c0152612f469190613ce8565b965160808a01525160a08901525160088110156115275760c08801525194600286101561152757869560e0870152519085015251908301520390f35b34610285576020366003190112610285577fe2c8b1f4bcb8086dc9805a9868ee616914948de41a09786213a2284a8ffba27b6040600435612fc16157bf565b806003558151908152336020820152a1005b61010036600319011261028557612fe8613d64565b60a435906001600160a01b03821682036102855760e4356001600160401b0381116102855761301b903690600401613e74565b916130276004356156d6565b3360005260136020526040600020600435600052602052604060002060ff600182015416156134fe57600435600052601260205260406000209260018060a01b038454169460ff600a5416156134d457613082600435615639565b6008811015611527576002036134aa578561349b573460243503613471575b6130af60443560243561402d565b906130ba828861523d565b9460009360ff6008541661330e575b505050838111156132e4576130fe6130e9826130e489615418565b6148ee565b876132d3576130f8601261506d565b9061504d565b6000878152600e6020526040902054156132cd576131289060166020526040600020541115614901565b60443561321d575b9061317360a095949392877f0b17a6125c8c06bcd40d6a58de2c6f4e8815d9f52c8f8b6999ca7493c0fea31a98613206575b5061316d838661402d565b906148e1565b9261317f84845461402d565b83556131906002840191825461402d565b90556131a16004830191825461402d565b905560018181015460ff16036131f057600383016131c083825461402d565b90555b5491600180851b0390541690604051926004358452602084015260408301523360608301526080820152a1005b600483016131ff83825461402d565b90556131c3565b613217906024359030903390614d82565b88613162565b93929190336000526014602052604060002086600052602052604435604060002054106132a35761317360a0957f0b17a6125c8c06bcd40d6a58de2c6f4e8815d9f52c8f8b6999ca7493c0fea31a97336000526014602052604060002081600052602052604060002061329360443582546148e1565b9055975091929394955050613130565b60405162461bcd60e51b8152602060048201526002602482015261199b60f11b6044820152606490fd5b50613128565b6130f86132df89615097565b61506d565b60405162461bcd60e51b8152602060048201526002602482015261333560f01b6044820152606490fd5b60ff6040999693999895929794985191336020840152166040820152608435606082015260018060a01b038716608082015260c43560a082015260a0815261335581613deb565b60208151910120604051602081019182526020815261337381613e06565b5190209660035497966000975b8a518910156133cb57613393898c6148a9565b5190818110156133b7576000526020526133b1604060002098613fee565b97613380565b906000526020526133b160406000206105b5565b919497509295985096909396036134475769021e19e0c9bab2400000946084358603908682116106d3578691613400916148ee565b049485916001600160a01b0316613418575b806130c9565b909491925060c4358103918183116106d35761343f92613437916148ee565b0480946148e1565b908680613412565b60405162461bcd60e51b8152602060048201526002602482015261343560f01b6044820152606490fd5b60405162461bcd60e51b8152602060048201526002602482015261333360f01b6044820152606490fd5b6134a53415614d51565b6130a1565b60405162461bcd60e51b8152602060048201526002602482015261199960f11b6044820152606490fd5b60405162461bcd60e51b8152602060048201526002602482015261333160f01b6044820152606490fd5b60405162461bcd60e51b81526020600482015260016024820152603760f81b6044820152606490fd5b346102855760003660031901126102855760206040516202a3008152f35b3461028557600036600319011261028557602060ff600854166040519015158152f35b34610285576000366003190112610285576020600554604051908152f35b34610285576020366003190112610285577f46eb189395b90ab0cfd42a309b25f46e015815a01e7e676f6f804ef62466127d6108bc6135c3613d55565b6135cb613f96565b6008805460ff191660ff9215159283161790556040805191825233602083015290918291820190565b34610285576000366003190112610285576004546040516001600160a01b039091168152602090f35b346102855760003660031901126102855760206040516402540be4008152f35b34610285576060366003190112610285576001600160401b036024358181116102855761366e903690600401613d25565b909160443590811161028557613688903690600401613d25565b6136906157bf565b613698614802565b6136a36004356156d6565b6136ae60043561571f565b60043560005260126020526005604060002001805460ff8160081c16600281101561152757600103613c355782851480613c2a575b15613c00576000805b868210613b065768056bc75e2d63100000915003613adc5760059060ff19161790557fb6171b34500ba874839b220ee75cbfbeb25bba762c6a57c158e19a3c27af89ae60408051600435815260056020820152a1600435600052601260205260406000209160018060a01b0383541660c05260018301549161376d85614fd3565b9161377b61039187866148e1565b948761378a61039189886148e1565b9561379481614fd3565b9361379e82614fd3565b60a052600095600096898c6000965b8688106139125750505050505050509160008051602061581a8339815191526138346138629360008051602061583a8339815191529560018060a01b0360005416600052601460205260406000209060018060a01b03905416600052602052604060002061381c85825461402d565b905560405191829160a0519060c051600435856150f4565b0390a16040805160c05160043582526001600160a01b03166020820152908101919091529081906060820190565b0390a1604051938060e08601600435875260e06020880152526101008501959060005b8181106138ec5760008051602061585a83398151915287806138d3896138c58a6138b78f8c8782036040890152613eee565b908582036060870152613ce8565b908382036080850152613eee565b600160a083015260c05160c08301520390a16001600255005b909196602080600192838060a01b036139048c613da0565b168152019801929101613885565b879a969791899161394661392985600186016149a0565b905460039190911b1c6001600160a01b0316611e76368585613f22565b9061395485600186016149a0565b60018060a01b0391549060031b1c16600052601360205260406000206004356000526020526040600020608052828214600014613a5657505050926139e1926139c2826139aa6139d2956001613a4c99016149a0565b905460039190911b1c6001600160a01b0316926148a9565b528d611c138260805154926148a9565b945b600260805101549061402d565b9760805160048101906003825491019060018060a01b038254166000526014602052604060002060018060a01b038b5416600052602052613a28604060002091825461402d565b9055546001600160a01b0316613a3e838b6148a9565b5254611c138260a0516148a9565b9392898c8e6137ad565b6139e19550613a4c96999450613aa7613aa28368056bc75e2d63100000613a928d613a8b848f6003613ad69c9d0154936148bd565b35906148ee565b04966001600160a01b03946148bd565b6148cd565b166000526014602052604060002060c0516000526020526040600020613ace84825461402d565b90558d6148a9565b526139d4565b60405162461bcd60e51b8152602060048201526002602482015261313760f01b6044820152606490fd5b613b1c90613b158387896148bd565b359061402d565b9080613b9a575b6001600160a01b03613b39613aa2838a8c6148bd565b166000526013602052604060002060043560005260205260ff6001604060002001541615613b7057613b6a90613fee565b906136ec565b60405162461bcd60e51b8152602060048201526002602482015261313560f01b6044820152606490fd5b613ba8613aa282898b6148bd565b60001982018281116106d3576001600160a01b03908190613bce90613aa2908c8e6148bd565b16911611613b235760405162461bcd60e51b8152602060048201526002602482015261189b60f11b6044820152606490fd5b60405162461bcd60e51b81526020600482015260026024820152610c4d60f21b6044820152606490fd5b50600a8511156136e3565b60405162461bcd60e51b8152602060048201526002602482015261313360f01b6044820152606490fd5b3461028557600036600319011261028557600f5480825260209082828101600f6000527f8d1108e10bcb7c27dddfc02ed9d693a074039d026cf4ea4240b40f7d581ac8029260005b85828210613cd257505050613cbe92500383613e3c565b611ba7604051928284938452830190613ce8565b8554845260019586019588955093019201613ca7565b90815180825260208080930193019160005b828110613d08575050505090565b83516001600160a01b031685529381019392810192600101613cfa565b9181601f84011215610285578235916001600160401b038311610285576020808501948460051b01011161028557565b60043590811515820361028557565b6064359060ff8216820361028557565b600435906001600160a01b038216820361028557565b602435906001600160a01b038216820361028557565b35906001600160a01b038216820361028557565b61012081019081106001600160401b03821117612ace57604052565b60a081019081106001600160401b03821117612ace57604052565b60c081019081106001600160401b03821117612ace57604052565b604081019081106001600160401b03821117612ace57604052565b602081019081106001600160401b03821117612ace57604052565b90601f801991011681019081106001600160401b03821117612ace57604052565b6001600160401b038111612ace5760051b60200190565b81601f8201121561028557803591613e8b83613e5d565b92613e996040519485613e3c565b808452602092838086019260051b820101928311610285578301905b828210613ec3575050505090565b81358152908301908301613eb5565b34610285576000366003190112610285576020604051600a8152f35b90815180825260208080930193019160005b828110613f0e575050505090565b835185529381019392810192600101613f00565b9291613f2d82613e5d565b91613f3b6040519384613e3c565b829481845260208094019160051b810192831161028557905b828210613f615750505050565b838091613f6d84613da0565b815201910190613f54565b9080601f8301121561028557816020613f9393359101613f22565b90565b6000546001600160a01b03163303613faa57565b606460405162461bcd60e51b815260206004820152602060248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152fd5b60001981146106d35760010190565b1561400457565b60405162461bcd60e51b81526020600482015260016024820152603360f81b6044820152606490fd5b919082018092116106d357565b9060405191828154918282526020928383019160005283600020936000905b8282106140715750505061406f92500383613e3c565b565b85546001600160a01b031684526001958601958895509381019390910190614059565b60088210156115275752565b959492909693919760e05285600052601260205260406000206040516140c581613db4565b81546001600160a01b031681526140de6001830161403a565b60208201526140ef6002830161403a565b60408201526003820154606082015260048201546080820152600582015461411d60ff821660a08401614094565b600260ff8260081c1610156115275760c09260ff60079260081c1684840152600681015460e08401520154610100820152614157886156d6565b01516002811015611527576001036147b957600160ff821603614790575b33600052601360205260406000208660005260205260ff60016040600020015416614767576141a2614802565b80928660005260126020526040600020806101405260018060a01b039054169060ff600a5416156134d4576141d688615639565b6008811015611527576002036134aa578161475857348903613471575b6141fd8a8a61402d565b90614208828461523d565b9660009560ff600854166145d1575b505050858111156132e457614241614232826130e485615418565b836145c5576130f8601261506d565b6000838152600e6020526040902054156145b25761426f908360005260166020526040600020541115614901565b89614556575b61429160ff91838b600195614542575b505061316d868961402d565b9216148015614522575b156144f757600161014051016142b13382615005565b54600361014051016142c483825461402d565b90555b60ff600561014051015460081c16906002821015611527576001820361446f5760065410614445577f010fe3fa2b3358b8306447cc0a08515aa6c99e255d3d63b181fee6b377b6682398614432967ff6d42021a4549bed2c5e686f3a0e138917db52e05ad24e28d9239f7caef635ca9560a09560016143ec955b1461443c575b6040519260049261435785613dd0565b87855260ff602086019116815260408501908b825260608601926001808c1b03168352608086019384523360005260136020528d60406000209060005260205260406000209551865560ff6001870191511660ff1982541617905551600285015560038401906001808a1b039051166001600160601b03891b82541617905551910155600180851b036101405154169861402d565b6040519187835260208301523360408301528760608301526080820152a1604080519384526001600160a01b039094166020840152928201929092529081906060820190565b0390a16001600255565b60019150614347565b60405162461bcd60e51b8152602060048201526002602482015261033360f41b6044820152606490fd5b600554106144cd577f010fe3fa2b3358b8306447cc0a08515aa6c99e255d3d63b181fee6b377b6682398614432967ff6d42021a4549bed2c5e686f3a0e138917db52e05ad24e28d9239f7caef635ca9560a09560016143ec95614341565b60405162461bcd60e51b81526020600482015260026024820152610d0d60f21b6044820152606490fd5b600261014051016145083382615005565b546004610140510161451b83825461402d565b90556142c7565b5060ff600561014051015460081c1660028110156115275760011461429b565b61454f9130903390614d82565b388b614285565b33600052601460205260406000208260005260205289604060002054106132a35761429160ff916001933360005260146020526040600020816000526020528c6145a660406000209182546148e1565b90559350915050614275565b6145c0906007541115614901565b61426f565b6130f86132df85615097565b60409c9a989c9b99979593919694929b5160ff60208201923384521660408201528c606082015260018060a01b03891660808201528760a082015260a0815261461981613deb565b519020604051602081019182526020815261463381613e06565b5190209b6003549c610120526000610100525b60e05180519061010051918210156146aa5790614662916148a9565b51610120518181101561469957506101205160005260205260406000205b6101205261469061010051613fee565b61010052614646565b906000526020526040600020614680565b505091939597999b9a90929496989a61012051036134475769021e19e0c9bab2400000039069021e19e0c9bab240000082116106d35769021e19e0c9bab2400000916146f5916148ee565b049586906001600160a01b03891661470e575b80614217565b909691945069021e19e0c9bab24000000369021e19e0c9bab240000081116106d35761474869021e19e0c9bab240000091614750936148ee565b0480966148e1565b923880614708565b6147623415614d51565b6141f3565b60405162461bcd60e51b81526020600482015260016024820152601b60f91b6044820152606490fd5b60405162461bcd60e51b81526020600482015260016024820152600d60fa1b6044820152606490fd5b60ff8116600181149081156147f7575b506141755760405162461bcd60e51b81526020600482015260016024820152603560f81b6044820152606490fd5b6002915014386147c9565b60028054146148115760028055565b60405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606490fd5b1561485d57565b60405162461bcd60e51b81526020600482015260016024820152603960f81b6044820152606490fd5b8051156148935760200190565b634e487b7160e01b600052603260045260246000fd5b80518210156148935760209160051b010190565b91908110156148935760051b0190565b356001600160a01b03811681036102855790565b919082039182116106d357565b818102929181159184041417156106d357565b1561490857565b60405162461bcd60e51b8152602060048201526002602482015261064760f31b6044820152606490fd5b600f5481101561489357600f6000527f8d1108e10bcb7c27dddfc02ed9d693a074039d026cf4ea4240b40f7d581ac8020190600090565b600d5481101561489357600d6000527fd7b6990105719101dabeb77144f2a3385c8033acd3af97e9423a695e81ad1eb50190600090565b80548210156148935760005260206000200190600090565b6000818152600e6020526040812054614a4b57600d54600160401b811015614a37576001810180600d55811015614a235790826040927fd7b6990105719101dabeb77144f2a3385c8033acd3af97e9423a695e81ad1eb50155600d54928152600e6020522055600190565b634e487b7160e01b82526032600452602482fd5b634e487b7160e01b82526041600452602482fd5b905090565b600081815260106020526040812054614a4b57600f54600160401b811015614a37579082614aa0614a8984600160409601600f55614932565b819391549060031b91821b91600019901b19161790565b9055600f5492815260106020522055600190565b6000818152600c6020526040812054614a4b57600b54600160401b811015614a37576001810180600b55811015614a235790826040927f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db90155600b54928152600c6020522055600190565b6000818152601060205260408120549091908015614c045760001990808201818111614bf057600f5490838201918211614bdc57808203614ba8575b505050600f548015614b9457810190614b7382614932565b909182549160031b1b19169055600f55815260106020526040812055600190565b634e487b7160e01b84526031600452602484fd5b614bc6614bb7614a8993614932565b90549060031b1c928392614932565b9055845260106020526040842055388080614b5b565b634e487b7160e01b86526011600452602486fd5b634e487b7160e01b85526011600452602485fd5b505090565b6000818152600e60205260408120549091908015614c045760001990808201818111614bf057600d5490838201918211614bdc57808203614c7e575b505050600d548015614b9457810190614c5d82614969565b909182549160031b1b19169055600d558152600e6020526040812055600190565b614c9c614c8d614a8993614969565b90549060031b1c928392614969565b90558452600e6020526040842055388080614c45565b15614cb957565b60405162461bcd60e51b8152602060048201526002602482015261068760f31b6044820152606490fd5b15614cea57565b60405162461bcd60e51b8152602060048201526002602482015261191960f11b6044820152606490fd5b90815480825260208092019260005281600020916000905b828210614d3a575050505090565b835485529384019360019384019390910190614d2c565b15614d5857565b60405162461bcd60e51b81526020600482015260026024820152610ccd60f21b6044820152606490fd5b6040516323b872dd60e01b60208201526001600160a01b03928316602482015292909116604483015260648083019390935291815261406f91614dc482613dd0565b60018060a01b031690614e23604051614ddc81613e06565b6020938482527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564858301526000808587829751910182855af1614e1d614ec0565b91614eff565b805191821591848315614e95575b505050905015614e3e5750565b6084906040519062461bcd60e51b82526004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152fd5b919381809450010312614ebc57820151908115158203614eb9575080388084614e31565b80fd5b5080fd5b3d15614efa573d906001600160401b038211612ace5760405191614eee601f8201601f191660200184613e3c565b82523d6000602084013e565b606090565b91929015614f615750815115614f13575090565b3b15614f1c5790565b60405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606490fd5b825190915015614f745750805190602001fd5b6040519062461bcd60e51b82528160208060048301528251908160248401526000935b828510614fba575050604492506000838284010152601f80199101168101030190fd5b8481018201518686016044015293810193859350614f97565b90614fdd82613e5d565b614fea6040519182613e3c565b8281528092614ffb601f1991613e5d565b0190602036910137565b805490600160401b821015612ace5781615027916001615049940181556149a0565b815491936001600160a01b0360039290921b82811b19909316911690911b1790565b9055565b8115615057570490565b634e487b7160e01b600052601260045260246000fd5b60ff16604d81116106d357600a0a90565b90816020910312610285575160ff811681036102855790565b60405163313ce56760e01b815290602090829060049082906001600160a01b03165afa908115611a53576000916150cc575090565b613f93915060203d81116150ed575b6150e58183613e3c565b81019061507e565b503d6150db565b92613f93949261511e92855260018060a01b03166020850152608060408501526080840190613ce8565b916060818403910152613eee565b90815480825260208092019260005281600020916000905b828210615152575050505090565b83546001600160a01b031685529384019360019384019390910190615144565b8054801561519f57600019019061518982826149a0565b81549060018060a01b039060031b1b1916905555565b634e487b7160e01b600052603160045260246000fd5b9491936120b66151df9461131860c097611334959b9a9b8a5260e060208b015260e08a0190613ce8565b600260a08401526001600160a01b03909416910152565b9081519060005b82811061520b575050905090565b6001600160a01b038061521e83876148a9565b5116908316146152365761523190613fee565b6151fd565b9250505090565b60018060a01b0381169161526d600091848352600c60205261526460408420541515614cb2565b6130e484615418565b9061528684159283600014615387576130f8601261506d565b9381526015602052604081209384548290915b82821061532c57505080156153245760001981019081116153105760026152c560ff92600188016149a0565b90549060031b1c9501541661530a57613f93936130f8926152f892506000146152fe576152f2601261506d565b906148ee565b91615418565b6152f26132df85615097565b50505090565b634e487b7160e01b82526011600452602482fd5b509250505090565b9091600190615341818518831c82861661402d565b918361534d848b6149a0565b90549060031b1c11600014615366575050915b90615299565b90935081018091111561536057634e487b7160e01b84526011600452602484fd5b6130f86132df86615097565b51906001600160501b038216820361028557565b908160a0910312610285576153bb81615393565b91602082015191604081015191613f93608060608401519301615393565b604d81116106d357600a0a90565b906005820291808305600514901517156106d357565b811561505757600160ff1b81146000198314166106d3570590565b6001600160a01b039081166000818152600e602090815260408083205491949293911561544c5750505050506305f5e10090565b60a09460119384825280838720541693835197888096633fabe5a360e21b825260049788915afa97881561562f578798615606575b5086976000881390816155da575b50156155b257918091859360005286825284600020541684519384809263313ce56760e01b82525afa9283156155a857509060ff929160009261558b575b50501692600884111561550e575060071983019283116154fc575050906154f6613f93926153d9565b906153fd565b634e487b7160e01b6000525260246000fd5b926008811061551f575b5050505090565b90919293506008036008811161557857615538906153d9565b838102939060008212600160ff1b8214166155645781850514901517156154fc57505038808080615518565b5050634e487b7160e01b6000525260246000fd5b50634e487b7160e01b6000525260246000fd5b6155a19250803d106150ed576150e58183613e3c565b38806154cd565b513d6000823e3d90fd5b835162461bcd60e51b81528086018490526002602482015261343360f01b6044820152606490fd5b426201517f198101925082116155f25710153861548f565b8787634e487b7160e01b6000525260246000fd5b90975061562291965060a03d8111611a4c57611a388183613e3c565b5097925050959638615481565b84513d89823e3d90fd5b6000908152601260205260408120600760ff60058301541691015491600882101590816156a457600483149182156156c9575b82156156b8575b8215615691575b5050614a4b5750421161568c57600290565b600390565b9091506156a4575060078114388061567a565b634e487b7160e01b81526021600452602490fd5b8092506156a4576006831491615673565b506005831491508061566c565b8015159081615712575b50156156e857565b60405162461bcd60e51b8152602060048201526002602482015261066760f31b6044820152606490fd5b90506009541015386156e0565b61572890615639565b60088110156115275760030361573a57565b60405162461bcd60e51b8152602060048201526002602482015261333960f01b6044820152606490fd5b61576d90615639565b600881101561152757600381149081156157b4575b501561578a57565b60405162461bcd60e51b8152602060048201526002602482015261034360f41b6044820152606490fd5b600291501438615782565b6004546001600160a01b031633036157d357565b60405162461bcd60e51b8152602060048201526002602482015261343160f01b6044820152606490fd5b919091600083820193841291129080158216911516176106d35756fe7620380c554abbc2f2c8551155ba9b12b3fcd40700f9a718a43678a914753048a933c5f2ff3ab65b8004e747a48707dab64e66636a6ff5247f0431bc16881774eb0cc7968044d5a32d1b2d53427cbc63a081ddf73f5afb1637a4dfbdea2f7db9a26469706673582212205cb023200ab77eb6105b029109f1d79a2294df8d75da4112a2520090bd92d02964736f6c63430008130033000000000000000000000000cd854bd769287bee0b9ac77712c32b12b27cb8e4

Deployed Bytecode

0x61016080604052600436101561001e575b50361561001c57600080fd5b005b60003560e01c908163024ece8914613c5f575080630306c19a1461363d57806307c9f4661461361d578063099e4133146135f4578063130894e0146135865780631411b438146135685780631f2a8dd6146135455780631fb05b33146135275780632179e5fe14612fd357806321ff997014612f825780633d40a54f14612dda5780634655471914612da257806346d1cdc014612cdf5780634c9f166d14612cbc57806351cff8d914612b46578063615b2c7814612793578063715018a61461273657806375b1510c146115bf57806378a29c09146126c45780637946b7811461262457806379ba5097146125615780638288c80e146124e25780638770912f1461235b5780638bbed8ef1461233d5780638c2bd69214611cef5780638da5cb5b14611cc65780638edfe78714611caa5780639474aef614611c8c57806395e737c014611c1e578063a8037e0e14611b34578063a8f3f408146115e9578063ab04663b146115c4578063ab535f8f146115bf578063b9f433a614610bbe578063bf26931f14610ba0578063c03fb87c14610acc578063ca44bb5314610ab0578063d0339fee14610922578063d89b6dc014610906578063def08d26146107ed578063e30c3978146107c4578063eaecfca714610727578063ef2104a2146102aa578063f030f0131461028a5763f2fde38b1461021a5738610010565b3461028557602036600319011261028557610233613d74565b61023b613f96565b60018060a01b0380911690816001600160601b0360a01b6001541617600155600054167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700600080a3005b600080fd5b3461028557600036600319011261028557602060405164174876e8008152f35b346102855760403660031901126102855760243560ff81168103610285576004546001600160a01b031633148015610713575b156106e9576102ea614802565b6102f56004356156d6565b610300600435615764565b6004356000526012602052604060002060018060a01b03600054163314610691575b600501600460ff198254161790557ff0fbc916c90170d09d494b840e0bb03ab2e82065a8e1990d3673211b45182a8e60206040516004358152a1600435600052601260205260406000209060018060a01b0382541690600183015491600284015491610396610391848661402d565b614fd3565b926103a4610391828761402d565b946103b2610391838361402d565b916103c0610391828461402d565b976000926000958a89898c6000935b86851061063e5750505050506000925b84841061049d575060008051602061585a8339815191528a8a6104938b8f6104608d60008051602061581a83398151915260008051602061583a833981519152938f61043460405192839289600435856150f4565b0390a16040805160043581526001600160a01b0386166020820152908101919091529081906060820190565b0390a160405161046f81613e21565b600081526040519061048082613e21565b60008252604051958695600435876151b5565b0390a16001600255005b9091929394966105bb6105b5886105c1938f8f8f8f918f8b908e958e956104d46104cb8f60028b91016149a0565b9890549961402d565b956104ed8760018060a01b038b8b60031b1c16926148a9565b526001600160a01b03600388811b8a901c82166000908152601360209081526040808320600480358552925290912080546002820154928201549190930154909316969193909160ff166001036105cc575061057a9796959492610558610574959361055e9361402d565b9061402d565b936105748360009d6000975b6105748b8b6148a9565b526148a9565b519160018060a01b039160031b1c1660005260146020528d6040600020906000526020526105ae604060002091825461402d565b905561402d565b98613fee565b94613fee565b9291909493946103df565b85829d610574969497956105749361057a9c9b9a9560018060a01b036000541660005260149081602052604060002083600052602052610612604060002091825461402d565b90558660005260205260406000209060005260205260406000206106378a825461402d565b905561056a565b9a6105bb936105b593868d948d9f99829d9e8d9b610665839e9f9c60016106819e016149a0565b969054976104ed8760018060a01b038b8b60031b1c16926148a9565b89898c8e989798969594966103cf565b60078101546202a30081018091116106d35742116103225760405162461bcd60e51b8152602060048201526002602482015261062760f31b6044820152606490fd5b634e487b7160e01b600052601160045260246000fd5b60405162461bcd60e51b81526020600482015260026024820152611a1960f11b6044820152606490fd5b506000546001600160a01b031633146102dd565b3461028557602036600319011261028557610740613d74565b610748613f96565b6001600160a01b0316801561079b57600480546001600160a01b03191682179055604080519182523360208301527fea8ca364cc9c0e44ae5ceab245cececcc710724873a9ecfc89a0559983e4473991a1005b60405162461bcd60e51b81526020600482015260016024820152603160f81b6044820152606490fd5b34610285576000366003190112610285576001546040516001600160a01b039091168152602090f35b604036600319011261028557600435610804613d8a565b61080c613f96565b60018060a01b03169081600052602090601082526108306040600020541515613ffd565b61083b811515614901565b826108eb578034036108c1575b3360005260148252604060002083600052825261086b604060002091825461402d565b9055336000818152601483526040808220948252939092529082902054825190815260208101919091527fc6eb93583634faca0ac6216e0725f08457b08e39adbbf4b9484973740f23327791819081015b0390a1005b60405162461bcd60e51b8152600481018390526002602482015261323960f01b6044820152606490fd5b6108f53415614d51565b61090181303386614d82565b610848565b3461028557600036600319011261028557602060405160058152f35b61018036600319011261028557610937613d74565b604435602435610945613d64565b6084359060028210156102855760a4359360c4359160e4359160ff831683036102855761012435946001600160a01b03808716870361028557610164356001600160401b0381116102855761099e903690600401613e74565b986109aa600954613fee565b96876009554282111580610aa7575b15610a7e5760077fd2f7931a802085b3d0234d4c320ce7ee0041da96678ce2bf5c93e8d3d7e65f529460809461001c9e1693610a0a610a05866000526010602052604060002054151590565b613ffd565b8a6000526012602052604060002091856001600160601b0360a01b8454161783556002600584019161ff0083549160081b169061ffff19161717905560068201550155610a57848a61402d565b6040519188835260208301523360408301526060820152a1610144359561010435946140a0565b60405162461bcd60e51b81526020600482015260016024820152601960f91b6044820152606490fd5b504281116109b9565b3461028557600036600319011261028557602060405160088152f35b346102855760403660031901126102855760a0610ae7613d8a565b60006080604051610af781613dd0565b8281528260208201528260408201528260608201520152600180831b038091166000526013602052604060002060043560005260205260406000209060405190610b4082613dd0565b82549283835260ff6001820154166020840190815260ff6002830154916040860192835260806004866003870154169560608901968752015496019586526040519687525116602086015251604085015251166060830152516080820152f35b34610285576000366003190112610285576020600954604051908152f35b34610285576040366003190112610285576001600160401b0360043581811161028557610bef903690600401613e74565b6024359182116102855736602383011215610285578160040135610c1281613e5d565b92610c206040519485613e3c565b8184526024602085019260051b8201019036821161028557602401915b8183106115a557505050610c4f6157bf565b610c57614802565b805190600a8211158061159b575b610c729093929193614856565b6000925b818410610c84576001600255005b610c8e84826148a9565b5193846000526012602052600560406000200180549060ff916008928082851c166002811015611527576115715780610cc7868a6148a9565b51169384151580611567575b1561153d57610ce18a6156d6565b610cea8a61571f565b60048501908282116106d357828216101561152757169060ff19161790557fb6171b34500ba874839b220ee75cbfbeb25bba762c6a57c158e19a3c27af89ae60408051888152836020820152a1600381036111aa57509091929380600052601260205260406000209060018060a01b03825416600183015492600281015490610d76610391838761402d565b94610d84610391848361402d565b92610d92610391828461402d565b90610da0610391828561402d565b9260009160009560005b83811061101857506000925b828410610e7b5750505050509260008051602061583a833981519152610e3b610e6b9460008051602061581a833981519152610e739b9a989560008051602061585a8339815191529a98610e116040519283928a8d856150f4565b0390a1604080518881526001600160a01b0387166020820152908101919091529081906060820190565b0390a1604051610e4a81613e21565b6000815260405191610e5b83613e21565b60008352604051968796876151b5565b0390a1613fee565b929190610c76565b90919293968a8c8b876002870190610e92916149a0565b905460039190911b1c6001600160a01b03169182610eb0878b61402d565b9182610ebb916148a9565b52826000526013602052604060002084600052602052604060002054938360005260136020526040600020816000526020528d8d6040600020600201549686600052601360205260406000208460005260205260406000206004015493876000526013602052604060002090600052602052600160a01b600190036040600020600301541692600160a01b60019003600054166000526014968760205260406000208160005260205260406000208a815490610f769161402d565b9055846000528760205260406000209060005260205285604060002086815490610f9f9161402d565b9055610faa916148a9565b52610fb5908d6148a9565b52610fc08d8d6148a9565b52610fcb908d6148a9565b519160005260205260406000208c600052602052604060002090815490610ff19161402d565b9055610ffc9161402d565b9661100690613fee565b9361101090613fee565b929190610db6565b9396919290918b8561102d81600187016149a0565b905460039190911b1c6001600160a01b031691829161104b916148a9565b5280600052601360205260406000208c6000526020526040600020549080600052601360205260406000208d6000526020528b8d8c6040600020600201549484600052601360205260406000208360005260205260406000206004015492856000526013602052604060002090600052602052600160a01b600190036040600020600301541691600160a01b6001900360005416600052601494856020526040600020816000526020526040600020888154906111079161402d565b905583600052856020526040600020906000526020528b6040600020858154906111309161402d565b905561113b916148a9565b526111468d8c6148a9565b526111518c8c6148a9565b5261115c888d6148a9565b519160005260205260406000208c6000526020526040600020908154906111829161402d565b905561118d9161402d565b9661119790613fee565b936111a190613fee565b92919092610daa565b85600052601260205260406000209560018060a01b038754166000600189019360028a019182600460038d01549c0154926002849114611519575b5050855483549b92908c906111f981614fd3565b9461121461039161120d610391868661402d565b948461402d565b936000926000925b81841061141857505050506112388e9798999a9b9c9d9e614fd3565b966000905b80821061135757505050936113266113429460008051602061583a8339815191526112f98b999660008051602061581a833981519152610e739f9e9c60008051602061585a8339815191529e6112cf6113349a839e60018060a01b0360005416600052601460205260406000208560005260205260406000206112c188825461402d565b9055604051948594856150f4565b0390a1604080518c81526001600160a01b038c166020820152908101919091529081906060820190565b0390a1611318604051998a998a5260e060208b015260e08a019061512c565b9088820360408a0152613eee565b90868203606088015261512c565b908482036080860152613eee565b90600060a084015260c08301520390a1613fee565b90919461140c6114066114129260028c8f8f8c918f8b611376916149a0565b60018060a01b0391549060031b1c1660005260136020526040600020906000526020526040600020926113ab8b8554926148a9565b5260038301546001600160a01b0316806113c5848e6148a9565b528c6113d760048601549485926148a9565b5260005260146020526040600020906000526020526113fc604060002091825461402d565b905501549061402d565b96613fee565b92613fee565b9061123d565b90919293966105bb6105b5611511928f8f8f8b908f8f8f948b958f938f61148761148f916114488860029d6149a0565b60018060a01b0391549060031b1c169586600052601360205260406000209060005260205261055860406000209b6114828d5480946148ee565b61504d565b9586926148a9565b5260038701956114a98460018060a01b03895416926148a9565b526114ba60048801938454926148a9565b52600052601491826020526040600020846000526020526114e1604060002091825461402d565b9055549260018060a01b039054166000526020526040600020906000526020526113fc604060002091825461402d565b92919061121c565b9b9096935091508b806111e5565b634e487b7160e01b600052602160045260246000fd5b60405162461bcd60e51b8152602060048201526002602482015261313160f01b6044820152606490fd5b5060048510610cd3565b60405162461bcd60e51b8152602060048201526002602482015261031360f41b6044820152606490fd5b5082518214610c65565b823560ff8116810361028557815260209283019201610c3d565b613ed2565b3461028557600036600319011261028557602060405169152d02c7e14af68000008152f35b34610285576060366003190112610285576001600160401b036004358181116102855761161a903690600401613f78565b9060243581811161028557611633903690600401613f78565b906044359081116102855761164c903690600401613e74565b90611655613f96565b825161166e825184519083149081611b2a575b50614856565b60005b81811061174c57505060005b83518110156116b1576116ac906116a66001600160a01b0361169f83886148a9565b5116614a50565b50613fee565b61167d565b509060005b83518110156116e4576116df906116a66001600160a01b036116d883886148a9565b5116614ab4565b6116b6565b7fee34c4fff4314ef30863a798e2773a9aa5251ff332d9de421fdddcabfc423778611725856117418561173388604051958695608087526080870190613ce8565b908582036020870152613ce8565b908382036040850152613eee565b3360608301520390a1005b6117766001600160a01b0361176183886148a9565b51166000526010602052604060002054151590565b611b00576001600160a01b0361178c82856148a9565b511661181f5761179c81856148a9565b51156117f557806117b06117f092866148a9565b516001600160a01b036117c383896148a9565b51166000908152601660205260409020556116a66001600160a01b036117e983896148a9565b51166149b8565b611671565b60405162461bcd60e51b8152602060048201526002602482015261032360f41b6044820152606490fd5b6001600160a01b0361183182856148a9565b511660405190633fabe5a360e21b9081835260a083600481845afa8015611a5357600093600091611ad8575b506000841315611aae576118756201518091426148e1565b11611a845760405191825260a082600481845afa918215611a5357600092611a5f575b506000908190815b60058110611987575b50506118b592506153fd565b90606491826118c3826153e7565b059060008282039212818312811690828413901516176106d35780846118eb6118f2936153e7565b05906157fd565b90821215918261197c575b50501561195557506117f0906001600160a01b0361191b82866148a9565b51166001600160a01b0361192f83896148a9565b511660005260116020526040600020906001600160601b0360a01b825416179055613fee565b60405162461bcd60e51b8152602060048201526002602482015261323760f01b6044820152fd5b1315905087806118fd565b909160405190639a6fc8f560e01b82526001600160501b038616600483015260a082602481875afa908115611a53576119c892600092611a1d575b506157fd565b92600181018091116106d357936001600160501b03811615611a14576001600160501b036000199116016001600160501b0381116106d357611a0a9091613fee565b93929190936118a0565b849392506118a9565b611a4091925060a03d60a011611a4c575b611a388183613e3c565b8101906153a7565b5050509050908d6119c2565b503d611a2e565b6040513d6000823e3d90fd5b611a7991925060a03d60a011611a4c57611a388183613e3c565b505050509088611898565b60405162461bcd60e51b8152602060048201526002602482015261323560f01b6044820152606490fd5b60405162461bcd60e51b8152602060048201526002602482015261313960f01b6044820152606490fd5b9050611af491935060a03d60a011611a4c57611a388183613e3c565b5094925050928961185d565b60405162461bcd60e51b81526020600482015260026024820152611a1b60f11b6044820152606490fd5b9050821486611668565b346102855760208060031936011261028557611b4e613d74565b90600b8054611b5c81614fd3565b93611b6682614fd3565b926001600160a01b039182169060005b848110611bab57611b9a88611ba78989604051948594604086526040860190613ce8565b9184830390850152613eee565b0390f35b611c19908260005284817f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9015416611be3828b6148a9565b528360005260148852604060002085611bfc838c6148a9565b51166000528852604060002054611c1382896148a9565b52613fee565b611b76565b34610285576020366003190112610285577f39d65726005dded9e32c1c37c38c433a478814ed5df9a080f9788a8861cc383a6108bc611c5b613d55565b611c63613f96565b600a805460ff191660ff9215159283161790556040805191825233602083015290918291820190565b34610285576000366003190112610285576020600754604051908152f35b3461028557600036600319011261028557602060405160148152f35b34610285576000366003190112610285576000546040516001600160a01b039091168152602090f35b3461028557606036600319011261028557611d08613d74565b6044359060ff8216820361028557611d1e6157bf565b611d26614802565b611d316024356156d6565b611d3c602435615764565b60018060a01b0381166000526013602052604060002060243560005260205260ff600160406000200154161561231357602435600052601260205260406000209060018060a01b0382541691600181015491600282015460405193611da085613e06565b600185526020368187013760405191611db883613e06565b600183526020368185013760405194611dd086613e06565b600186526020368188013760405192611de884613e06565b60018452602036818601376001600160a01b038681166000908152601360209081526040808320602435845290915290208054600280830154600484015460039094015460058801549499951697909693909160089190911c60ff169081101561152757801590816122de575b81156122d3575b50156121395750600160ff611e7b8b611e7684870161403a565b6151f6565b9e16036120db57611e9090610558888761402d565b9a5b60018060a01b03891660005260146020526040600020906000526020526040600020611ebf8c825461402d565b905560038101611ed08c82546148e1565b9055826000198101116106d3577f679f73fac18389f339b511a489b811473ff4d0f3d4937002ed9951c7636cce3a6120236120c49960008051602061583a8339815191529860008051602061585a8339815191529f986113189f8f8f9091611fb88f611fbe946120709f611f9660008051602061581a8339815191529f8e926001611f64611f7993600019018287016149a0565b828060a01b0391549060031b1c1694016149a0565b819391549060031b9160018060a01b03809116831b921b19161790565b9055611fa460018d01615172565b6001600160a01b03891690611fb890614886565b52614886565b52611fc886614886565b526001600160a01b031660008181526013602090815260408083206024358085529083528184208481556001810185905560028101859055600381018590556004019390935580519384529083019190915290918291820190565b0390a160018060a01b03905416976120446040519283928b602435856150f4565b0390a16040805160243581526001600160a01b0388166020820152908101919091529081906060820190565b0390a161133460405161208281613e21565b600081526120b66040519361209685613e21565b600085526040519889986024358a5260e060208b015260e08a0190613ce8565b908682036060880152613ce8565b90600360a084015260c08301520390a16001600255005b9a60018060a01b03600054166000526014602052604060002081600052602052604060002061210b86825461402d565b9055856000526014602052604060002081600052602052604060002061213288825461402d565b9055611e92565b9350919a93600160ff6121548b611e766002879c970161403a565b9e160361226f5761216992916105589161402d565b986000946000935b60018060a01b0389166000526014602052604060002090600052602052604060002061219e8c825461402d565b9055600481016121af8c82546148e1565b9055826000198101116106d3577f679f73fac18389f339b511a489b811473ff4d0f3d4937002ed9951c7636cce3a6120236120c49960008051602061583a8339815191529860008051602061585a8339815191529f986113189f8f8f9091611fb88f611fbe946120709f61225c60008051602061581a8339815191529f8e926002612243611f7993600019018287016149a0565b905460039190911b1c6001600160a01b031694016149a0565b905561226a60028d01615172565b611fa4565b9360018060a09d949d9893981b0360005416600052601460205260406000208160005260205260406000206122a586825461402d565b905585600052601460205260406000208160005260205260406000206122cc88825461402d565b9055612171565b60019150148f611e5c565b905060018060a01b038b1660005260136020526040600020602435600052602052600160ff8160406000200154161490611e55565b60405162461bcd60e51b8152602060048201526002602482015261189960f11b6044820152606490fd5b34610285576000366003190112610285576020600654604051908152f35b3461028557602080600319360112610285576004356001600160401b0381116102855761238c903690600401613f78565b90612395613f96565b815160005b81811061244b57505060005b82518110156123d4576123cf906116a66001600160a01b036123c883876148a9565b5116614b1f565b6123a6565b5060005b825181101561240657612401906116a66001600160a01b036123fa83876148a9565b5116614c09565b6123d8565b7fe315bdee05329e2e69a5253f25814537fb86d11c4c808142d2ea502822516e7f6124408484604051928392604084526040840190613ce8565b9033908301520390a1005b6001600160a01b036124618161176184886148a9565b156124b85790816124b39261247683886148a9565b51166000526011855260406000206001600160601b0360a01b815416905561249e82876148a9565b51166000526016845260006040812055613fee565b61239a565b60405162461bcd60e51b8152600481018590526002602482015261343760f01b6044820152606490fd5b34610285576020366003190112610285577f1952e249d74df2e2b0c9a7de47fdd506d63de48981dfbffad2b925a1152928f96108bc600435612522613f96565b60075481101580612552575b61253790614901565b60078190556040805191825233602083015290918291820190565b506402540be40081111561252e565b34610285576000366003190112610285576001546001600160a01b0333818316036125cd576001600160601b0360a01b8092166001556000549133908316176000553391167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a3005b60405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b6064820152608490fd5b34610285576020366003190112610285576001600160a01b03612645613d74565b1660005260156020526126aa604060002060ff6002820154166126b860016126966040519461267f866126788184614d14565b0387613e3c565b61268f6040518094819301614d14565b0382613e3c565b604051948594606086526060860190613eee565b908482036020860152613eee565b90151560408301520390f35b610120366003190112610285576126d9613d64565b6084359060ff821682036102855760c435916001600160a01b03831683036102855761010435926001600160401b0384116102855761271f61001c943690600401613e74565b9260e4359260a435916044356024356004356140a0565b346102855760003660031901126102855761274f613f96565b606460405162461bcd60e51b815260206004820152602060248201527f52656e6f756e63696e67206f776e6572736869702069732064697361626c65646044820152fd5b34610285576080366003190112610285576004356001600160401b038111610285576127c3903690600401613e74565b6024356001600160401b038111610285576127e2903690600401613e74565b906044356001600160a01b0381168103610285576064351515606435036102855761280b613f96565b6001600160a01b0381166000908152600c602052604090205461282f901515614cb2565b81519183519183151580612b3d575b80612b32575b61284d90614856565b64174876e8009260005b856000198101116106d35760001986018110156128fc5761287881856148a9565b51600182018083116106d35761288e90866148a9565b51106128d2576064356128c0575b806128b6866128ae6128bb948b6148a9565b511115614ce3565b613fee565b612857565b69152d02c7e14af6800000945061289c565b60405162461bcd60e51b8152602060048201526002602482015261323160f01b6044820152606490fd5b5091859284606435612b21575b60001982019182116106d3576128ae61292292866148a9565b60405190606082018281106001600160401b03821117612ace5760405281526020810192835260408101906064351515825260018060a01b03831660005260156020526040600020905180516001600160401b038111612ace57600160401b91828211612ace578354828555808310612af8575b5060200183600052602060002060005b838110612ae4575050505060019485830190518051926001600160401b038411612ace578311612ace578154838355808410612aa3575b5060200190600052602060002060005b838110612a90577fbf48b0820ca81715d6b1acb25649f7ba1d18bd574712fcc4788a22a3f9e49229612a7a89898960028a019051151560ff8019835416911617905560018060a01b03166000526015602052604060002060ff60026040519485946040865260606040870152612a6660a0870186614d14565b868103603f19016060880152908501614d14565b92015416151560808301523360208301520390a1005b87906020845194019381840155016129ed565b826000528784602060002092830192015b828110612ac25750506129dd565b60008155018890612ab4565b634e487b7160e01b600052604160045260246000fd5b6001906020845194019381840155016129a6565b8460005282602060002091820191015b818110612b155750612996565b60008155600101612b08565b5069152d02c7e14af6800000612909565b506014841115612844565b5082841461283e565b346102855760208060031936011261028557612b60613d74565b612b68614802565b3360009081526014835260408082206001600160a01b039093168083529284529020548015612c935733600052601483526040600020826000528352600060408120558115600014612c3357600080808084335af1612bc5614ec0565b5015612c0957906060917f30f0f2f1c6b25d25ce05404ece9a5feb949e2e6f459f939cc1f01c6512428066935b604051928352820152336040820152a16001600255005b60405162461bcd60e51b8152600481018490526002602482015261333760f01b6044820152606490fd5b60405163a9059cbb60e01b84820152336024820152604480820183905281527f30f0f2f1c6b25d25ce05404ece9a5feb949e2e6f459f939cc1f01c6512428066936060939291612c8e90612c88606482613e3c565b84614dc4565b612bf2565b60405162461bcd60e51b81526004810184905260016024820152600760fb1b6044820152606490fd5b3461028557600036600319011261028557602060ff600a54166040519015158152f35b3461028557604036600319011261028557600435602435612cfe613f96565b80151580612d97575b80612d8e575b80612d83575b15612d59577f42a3e0b2ac4ef58d5c467d7199e859b2b40ee8a57507885e2d4539a9ae0a05da9181606092600655816005556040519182526020820152336040820152a1005b60405162461bcd60e51b8152602060048201526002602482015261323360f01b6044820152606490fd5b506032821115612d13565b50811515612d0d565b506032811115612d07565b346102855760203660031901126102855760206004358015159081612dcd575b506040519015158152f35b9050600954101582612dc2565b34610285576020806003193601126102855760043590604051612dfc81613db4565b60008152606082820152606060408201526000606082015260006080820152600060a0820152600060c0820152600060e0820152600061010080920152612e42836156d6565b826000526012825260406000209160405193612e5d85613db4565b83546001600160a01b03908116865292612e796001860161403a565b93838701948552612e8c6002870161403a565b93604088019485526003870154956060890196875260048801549060808a0191825260058901549260ff60a08c0194612ec782821687614094565b60081c169460c08c0195600281101561152757865260068b01549a60e08d019b8c526007015497878d01988952612efd90615639565b612f079086614094565b6040519b828d525116908b01525195610120968760408c01526101408b01612f2e91613ce8565b90518a8203601f190160608c0152612f469190613ce8565b965160808a01525160a08901525160088110156115275760c08801525194600286101561152757869560e0870152519085015251908301520390f35b34610285576020366003190112610285577fe2c8b1f4bcb8086dc9805a9868ee616914948de41a09786213a2284a8ffba27b6040600435612fc16157bf565b806003558151908152336020820152a1005b61010036600319011261028557612fe8613d64565b60a435906001600160a01b03821682036102855760e4356001600160401b0381116102855761301b903690600401613e74565b916130276004356156d6565b3360005260136020526040600020600435600052602052604060002060ff600182015416156134fe57600435600052601260205260406000209260018060a01b038454169460ff600a5416156134d457613082600435615639565b6008811015611527576002036134aa578561349b573460243503613471575b6130af60443560243561402d565b906130ba828861523d565b9460009360ff6008541661330e575b505050838111156132e4576130fe6130e9826130e489615418565b6148ee565b876132d3576130f8601261506d565b9061504d565b6000878152600e6020526040902054156132cd576131289060166020526040600020541115614901565b60443561321d575b9061317360a095949392877f0b17a6125c8c06bcd40d6a58de2c6f4e8815d9f52c8f8b6999ca7493c0fea31a98613206575b5061316d838661402d565b906148e1565b9261317f84845461402d565b83556131906002840191825461402d565b90556131a16004830191825461402d565b905560018181015460ff16036131f057600383016131c083825461402d565b90555b5491600180851b0390541690604051926004358452602084015260408301523360608301526080820152a1005b600483016131ff83825461402d565b90556131c3565b613217906024359030903390614d82565b88613162565b93929190336000526014602052604060002086600052602052604435604060002054106132a35761317360a0957f0b17a6125c8c06bcd40d6a58de2c6f4e8815d9f52c8f8b6999ca7493c0fea31a97336000526014602052604060002081600052602052604060002061329360443582546148e1565b9055975091929394955050613130565b60405162461bcd60e51b8152602060048201526002602482015261199b60f11b6044820152606490fd5b50613128565b6130f86132df89615097565b61506d565b60405162461bcd60e51b8152602060048201526002602482015261333560f01b6044820152606490fd5b60ff6040999693999895929794985191336020840152166040820152608435606082015260018060a01b038716608082015260c43560a082015260a0815261335581613deb565b60208151910120604051602081019182526020815261337381613e06565b5190209660035497966000975b8a518910156133cb57613393898c6148a9565b5190818110156133b7576000526020526133b1604060002098613fee565b97613380565b906000526020526133b160406000206105b5565b919497509295985096909396036134475769021e19e0c9bab2400000946084358603908682116106d3578691613400916148ee565b049485916001600160a01b0316613418575b806130c9565b909491925060c4358103918183116106d35761343f92613437916148ee565b0480946148e1565b908680613412565b60405162461bcd60e51b8152602060048201526002602482015261343560f01b6044820152606490fd5b60405162461bcd60e51b8152602060048201526002602482015261333360f01b6044820152606490fd5b6134a53415614d51565b6130a1565b60405162461bcd60e51b8152602060048201526002602482015261199960f11b6044820152606490fd5b60405162461bcd60e51b8152602060048201526002602482015261333160f01b6044820152606490fd5b60405162461bcd60e51b81526020600482015260016024820152603760f81b6044820152606490fd5b346102855760003660031901126102855760206040516202a3008152f35b3461028557600036600319011261028557602060ff600854166040519015158152f35b34610285576000366003190112610285576020600554604051908152f35b34610285576020366003190112610285577f46eb189395b90ab0cfd42a309b25f46e015815a01e7e676f6f804ef62466127d6108bc6135c3613d55565b6135cb613f96565b6008805460ff191660ff9215159283161790556040805191825233602083015290918291820190565b34610285576000366003190112610285576004546040516001600160a01b039091168152602090f35b346102855760003660031901126102855760206040516402540be4008152f35b34610285576060366003190112610285576001600160401b036024358181116102855761366e903690600401613d25565b909160443590811161028557613688903690600401613d25565b6136906157bf565b613698614802565b6136a36004356156d6565b6136ae60043561571f565b60043560005260126020526005604060002001805460ff8160081c16600281101561152757600103613c355782851480613c2a575b15613c00576000805b868210613b065768056bc75e2d63100000915003613adc5760059060ff19161790557fb6171b34500ba874839b220ee75cbfbeb25bba762c6a57c158e19a3c27af89ae60408051600435815260056020820152a1600435600052601260205260406000209160018060a01b0383541660c05260018301549161376d85614fd3565b9161377b61039187866148e1565b948761378a61039189886148e1565b9561379481614fd3565b9361379e82614fd3565b60a052600095600096898c6000965b8688106139125750505050505050509160008051602061581a8339815191526138346138629360008051602061583a8339815191529560018060a01b0360005416600052601460205260406000209060018060a01b03905416600052602052604060002061381c85825461402d565b905560405191829160a0519060c051600435856150f4565b0390a16040805160c05160043582526001600160a01b03166020820152908101919091529081906060820190565b0390a1604051938060e08601600435875260e06020880152526101008501959060005b8181106138ec5760008051602061585a83398151915287806138d3896138c58a6138b78f8c8782036040890152613eee565b908582036060870152613ce8565b908382036080850152613eee565b600160a083015260c05160c08301520390a16001600255005b909196602080600192838060a01b036139048c613da0565b168152019801929101613885565b879a969791899161394661392985600186016149a0565b905460039190911b1c6001600160a01b0316611e76368585613f22565b9061395485600186016149a0565b60018060a01b0391549060031b1c16600052601360205260406000206004356000526020526040600020608052828214600014613a5657505050926139e1926139c2826139aa6139d2956001613a4c99016149a0565b905460039190911b1c6001600160a01b0316926148a9565b528d611c138260805154926148a9565b945b600260805101549061402d565b9760805160048101906003825491019060018060a01b038254166000526014602052604060002060018060a01b038b5416600052602052613a28604060002091825461402d565b9055546001600160a01b0316613a3e838b6148a9565b5254611c138260a0516148a9565b9392898c8e6137ad565b6139e19550613a4c96999450613aa7613aa28368056bc75e2d63100000613a928d613a8b848f6003613ad69c9d0154936148bd565b35906148ee565b04966001600160a01b03946148bd565b6148cd565b166000526014602052604060002060c0516000526020526040600020613ace84825461402d565b90558d6148a9565b526139d4565b60405162461bcd60e51b8152602060048201526002602482015261313760f01b6044820152606490fd5b613b1c90613b158387896148bd565b359061402d565b9080613b9a575b6001600160a01b03613b39613aa2838a8c6148bd565b166000526013602052604060002060043560005260205260ff6001604060002001541615613b7057613b6a90613fee565b906136ec565b60405162461bcd60e51b8152602060048201526002602482015261313560f01b6044820152606490fd5b613ba8613aa282898b6148bd565b60001982018281116106d3576001600160a01b03908190613bce90613aa2908c8e6148bd565b16911611613b235760405162461bcd60e51b8152602060048201526002602482015261189b60f11b6044820152606490fd5b60405162461bcd60e51b81526020600482015260026024820152610c4d60f21b6044820152606490fd5b50600a8511156136e3565b60405162461bcd60e51b8152602060048201526002602482015261313360f01b6044820152606490fd5b3461028557600036600319011261028557600f5480825260209082828101600f6000527f8d1108e10bcb7c27dddfc02ed9d693a074039d026cf4ea4240b40f7d581ac8029260005b85828210613cd257505050613cbe92500383613e3c565b611ba7604051928284938452830190613ce8565b8554845260019586019588955093019201613ca7565b90815180825260208080930193019160005b828110613d08575050505090565b83516001600160a01b031685529381019392810192600101613cfa565b9181601f84011215610285578235916001600160401b038311610285576020808501948460051b01011161028557565b60043590811515820361028557565b6064359060ff8216820361028557565b600435906001600160a01b038216820361028557565b602435906001600160a01b038216820361028557565b35906001600160a01b038216820361028557565b61012081019081106001600160401b03821117612ace57604052565b60a081019081106001600160401b03821117612ace57604052565b60c081019081106001600160401b03821117612ace57604052565b604081019081106001600160401b03821117612ace57604052565b602081019081106001600160401b03821117612ace57604052565b90601f801991011681019081106001600160401b03821117612ace57604052565b6001600160401b038111612ace5760051b60200190565b81601f8201121561028557803591613e8b83613e5d565b92613e996040519485613e3c565b808452602092838086019260051b820101928311610285578301905b828210613ec3575050505090565b81358152908301908301613eb5565b34610285576000366003190112610285576020604051600a8152f35b90815180825260208080930193019160005b828110613f0e575050505090565b835185529381019392810192600101613f00565b9291613f2d82613e5d565b91613f3b6040519384613e3c565b829481845260208094019160051b810192831161028557905b828210613f615750505050565b838091613f6d84613da0565b815201910190613f54565b9080601f8301121561028557816020613f9393359101613f22565b90565b6000546001600160a01b03163303613faa57565b606460405162461bcd60e51b815260206004820152602060248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152fd5b60001981146106d35760010190565b1561400457565b60405162461bcd60e51b81526020600482015260016024820152603360f81b6044820152606490fd5b919082018092116106d357565b9060405191828154918282526020928383019160005283600020936000905b8282106140715750505061406f92500383613e3c565b565b85546001600160a01b031684526001958601958895509381019390910190614059565b60088210156115275752565b959492909693919760e05285600052601260205260406000206040516140c581613db4565b81546001600160a01b031681526140de6001830161403a565b60208201526140ef6002830161403a565b60408201526003820154606082015260048201546080820152600582015461411d60ff821660a08401614094565b600260ff8260081c1610156115275760c09260ff60079260081c1684840152600681015460e08401520154610100820152614157886156d6565b01516002811015611527576001036147b957600160ff821603614790575b33600052601360205260406000208660005260205260ff60016040600020015416614767576141a2614802565b80928660005260126020526040600020806101405260018060a01b039054169060ff600a5416156134d4576141d688615639565b6008811015611527576002036134aa578161475857348903613471575b6141fd8a8a61402d565b90614208828461523d565b9660009560ff600854166145d1575b505050858111156132e457614241614232826130e485615418565b836145c5576130f8601261506d565b6000838152600e6020526040902054156145b25761426f908360005260166020526040600020541115614901565b89614556575b61429160ff91838b600195614542575b505061316d868961402d565b9216148015614522575b156144f757600161014051016142b13382615005565b54600361014051016142c483825461402d565b90555b60ff600561014051015460081c16906002821015611527576001820361446f5760065410614445577f010fe3fa2b3358b8306447cc0a08515aa6c99e255d3d63b181fee6b377b6682398614432967ff6d42021a4549bed2c5e686f3a0e138917db52e05ad24e28d9239f7caef635ca9560a09560016143ec955b1461443c575b6040519260049261435785613dd0565b87855260ff602086019116815260408501908b825260608601926001808c1b03168352608086019384523360005260136020528d60406000209060005260205260406000209551865560ff6001870191511660ff1982541617905551600285015560038401906001808a1b039051166001600160601b03891b82541617905551910155600180851b036101405154169861402d565b6040519187835260208301523360408301528760608301526080820152a1604080519384526001600160a01b039094166020840152928201929092529081906060820190565b0390a16001600255565b60019150614347565b60405162461bcd60e51b8152602060048201526002602482015261033360f41b6044820152606490fd5b600554106144cd577f010fe3fa2b3358b8306447cc0a08515aa6c99e255d3d63b181fee6b377b6682398614432967ff6d42021a4549bed2c5e686f3a0e138917db52e05ad24e28d9239f7caef635ca9560a09560016143ec95614341565b60405162461bcd60e51b81526020600482015260026024820152610d0d60f21b6044820152606490fd5b600261014051016145083382615005565b546004610140510161451b83825461402d565b90556142c7565b5060ff600561014051015460081c1660028110156115275760011461429b565b61454f9130903390614d82565b388b614285565b33600052601460205260406000208260005260205289604060002054106132a35761429160ff916001933360005260146020526040600020816000526020528c6145a660406000209182546148e1565b90559350915050614275565b6145c0906007541115614901565b61426f565b6130f86132df85615097565b60409c9a989c9b99979593919694929b5160ff60208201923384521660408201528c606082015260018060a01b03891660808201528760a082015260a0815261461981613deb565b519020604051602081019182526020815261463381613e06565b5190209b6003549c610120526000610100525b60e05180519061010051918210156146aa5790614662916148a9565b51610120518181101561469957506101205160005260205260406000205b6101205261469061010051613fee565b61010052614646565b906000526020526040600020614680565b505091939597999b9a90929496989a61012051036134475769021e19e0c9bab2400000039069021e19e0c9bab240000082116106d35769021e19e0c9bab2400000916146f5916148ee565b049586906001600160a01b03891661470e575b80614217565b909691945069021e19e0c9bab24000000369021e19e0c9bab240000081116106d35761474869021e19e0c9bab240000091614750936148ee565b0480966148e1565b923880614708565b6147623415614d51565b6141f3565b60405162461bcd60e51b81526020600482015260016024820152601b60f91b6044820152606490fd5b60405162461bcd60e51b81526020600482015260016024820152600d60fa1b6044820152606490fd5b60ff8116600181149081156147f7575b506141755760405162461bcd60e51b81526020600482015260016024820152603560f81b6044820152606490fd5b6002915014386147c9565b60028054146148115760028055565b60405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606490fd5b1561485d57565b60405162461bcd60e51b81526020600482015260016024820152603960f81b6044820152606490fd5b8051156148935760200190565b634e487b7160e01b600052603260045260246000fd5b80518210156148935760209160051b010190565b91908110156148935760051b0190565b356001600160a01b03811681036102855790565b919082039182116106d357565b818102929181159184041417156106d357565b1561490857565b60405162461bcd60e51b8152602060048201526002602482015261064760f31b6044820152606490fd5b600f5481101561489357600f6000527f8d1108e10bcb7c27dddfc02ed9d693a074039d026cf4ea4240b40f7d581ac8020190600090565b600d5481101561489357600d6000527fd7b6990105719101dabeb77144f2a3385c8033acd3af97e9423a695e81ad1eb50190600090565b80548210156148935760005260206000200190600090565b6000818152600e6020526040812054614a4b57600d54600160401b811015614a37576001810180600d55811015614a235790826040927fd7b6990105719101dabeb77144f2a3385c8033acd3af97e9423a695e81ad1eb50155600d54928152600e6020522055600190565b634e487b7160e01b82526032600452602482fd5b634e487b7160e01b82526041600452602482fd5b905090565b600081815260106020526040812054614a4b57600f54600160401b811015614a37579082614aa0614a8984600160409601600f55614932565b819391549060031b91821b91600019901b19161790565b9055600f5492815260106020522055600190565b6000818152600c6020526040812054614a4b57600b54600160401b811015614a37576001810180600b55811015614a235790826040927f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db90155600b54928152600c6020522055600190565b6000818152601060205260408120549091908015614c045760001990808201818111614bf057600f5490838201918211614bdc57808203614ba8575b505050600f548015614b9457810190614b7382614932565b909182549160031b1b19169055600f55815260106020526040812055600190565b634e487b7160e01b84526031600452602484fd5b614bc6614bb7614a8993614932565b90549060031b1c928392614932565b9055845260106020526040842055388080614b5b565b634e487b7160e01b86526011600452602486fd5b634e487b7160e01b85526011600452602485fd5b505090565b6000818152600e60205260408120549091908015614c045760001990808201818111614bf057600d5490838201918211614bdc57808203614c7e575b505050600d548015614b9457810190614c5d82614969565b909182549160031b1b19169055600d558152600e6020526040812055600190565b614c9c614c8d614a8993614969565b90549060031b1c928392614969565b90558452600e6020526040842055388080614c45565b15614cb957565b60405162461bcd60e51b8152602060048201526002602482015261068760f31b6044820152606490fd5b15614cea57565b60405162461bcd60e51b8152602060048201526002602482015261191960f11b6044820152606490fd5b90815480825260208092019260005281600020916000905b828210614d3a575050505090565b835485529384019360019384019390910190614d2c565b15614d5857565b60405162461bcd60e51b81526020600482015260026024820152610ccd60f21b6044820152606490fd5b6040516323b872dd60e01b60208201526001600160a01b03928316602482015292909116604483015260648083019390935291815261406f91614dc482613dd0565b60018060a01b031690614e23604051614ddc81613e06565b6020938482527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564858301526000808587829751910182855af1614e1d614ec0565b91614eff565b805191821591848315614e95575b505050905015614e3e5750565b6084906040519062461bcd60e51b82526004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152fd5b919381809450010312614ebc57820151908115158203614eb9575080388084614e31565b80fd5b5080fd5b3d15614efa573d906001600160401b038211612ace5760405191614eee601f8201601f191660200184613e3c565b82523d6000602084013e565b606090565b91929015614f615750815115614f13575090565b3b15614f1c5790565b60405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606490fd5b825190915015614f745750805190602001fd5b6040519062461bcd60e51b82528160208060048301528251908160248401526000935b828510614fba575050604492506000838284010152601f80199101168101030190fd5b8481018201518686016044015293810193859350614f97565b90614fdd82613e5d565b614fea6040519182613e3c565b8281528092614ffb601f1991613e5d565b0190602036910137565b805490600160401b821015612ace5781615027916001615049940181556149a0565b815491936001600160a01b0360039290921b82811b19909316911690911b1790565b9055565b8115615057570490565b634e487b7160e01b600052601260045260246000fd5b60ff16604d81116106d357600a0a90565b90816020910312610285575160ff811681036102855790565b60405163313ce56760e01b815290602090829060049082906001600160a01b03165afa908115611a53576000916150cc575090565b613f93915060203d81116150ed575b6150e58183613e3c565b81019061507e565b503d6150db565b92613f93949261511e92855260018060a01b03166020850152608060408501526080840190613ce8565b916060818403910152613eee565b90815480825260208092019260005281600020916000905b828210615152575050505090565b83546001600160a01b031685529384019360019384019390910190615144565b8054801561519f57600019019061518982826149a0565b81549060018060a01b039060031b1b1916905555565b634e487b7160e01b600052603160045260246000fd5b9491936120b66151df9461131860c097611334959b9a9b8a5260e060208b015260e08a0190613ce8565b600260a08401526001600160a01b03909416910152565b9081519060005b82811061520b575050905090565b6001600160a01b038061521e83876148a9565b5116908316146152365761523190613fee565b6151fd565b9250505090565b60018060a01b0381169161526d600091848352600c60205261526460408420541515614cb2565b6130e484615418565b9061528684159283600014615387576130f8601261506d565b9381526015602052604081209384548290915b82821061532c57505080156153245760001981019081116153105760026152c560ff92600188016149a0565b90549060031b1c9501541661530a57613f93936130f8926152f892506000146152fe576152f2601261506d565b906148ee565b91615418565b6152f26132df85615097565b50505090565b634e487b7160e01b82526011600452602482fd5b509250505090565b9091600190615341818518831c82861661402d565b918361534d848b6149a0565b90549060031b1c11600014615366575050915b90615299565b90935081018091111561536057634e487b7160e01b84526011600452602484fd5b6130f86132df86615097565b51906001600160501b038216820361028557565b908160a0910312610285576153bb81615393565b91602082015191604081015191613f93608060608401519301615393565b604d81116106d357600a0a90565b906005820291808305600514901517156106d357565b811561505757600160ff1b81146000198314166106d3570590565b6001600160a01b039081166000818152600e602090815260408083205491949293911561544c5750505050506305f5e10090565b60a09460119384825280838720541693835197888096633fabe5a360e21b825260049788915afa97881561562f578798615606575b5086976000881390816155da575b50156155b257918091859360005286825284600020541684519384809263313ce56760e01b82525afa9283156155a857509060ff929160009261558b575b50501692600884111561550e575060071983019283116154fc575050906154f6613f93926153d9565b906153fd565b634e487b7160e01b6000525260246000fd5b926008811061551f575b5050505090565b90919293506008036008811161557857615538906153d9565b838102939060008212600160ff1b8214166155645781850514901517156154fc57505038808080615518565b5050634e487b7160e01b6000525260246000fd5b50634e487b7160e01b6000525260246000fd5b6155a19250803d106150ed576150e58183613e3c565b38806154cd565b513d6000823e3d90fd5b835162461bcd60e51b81528086018490526002602482015261343360f01b6044820152606490fd5b426201517f198101925082116155f25710153861548f565b8787634e487b7160e01b6000525260246000fd5b90975061562291965060a03d8111611a4c57611a388183613e3c565b5097925050959638615481565b84513d89823e3d90fd5b6000908152601260205260408120600760ff60058301541691015491600882101590816156a457600483149182156156c9575b82156156b8575b8215615691575b5050614a4b5750421161568c57600290565b600390565b9091506156a4575060078114388061567a565b634e487b7160e01b81526021600452602490fd5b8092506156a4576006831491615673565b506005831491508061566c565b8015159081615712575b50156156e857565b60405162461bcd60e51b8152602060048201526002602482015261066760f31b6044820152606490fd5b90506009541015386156e0565b61572890615639565b60088110156115275760030361573a57565b60405162461bcd60e51b8152602060048201526002602482015261333960f01b6044820152606490fd5b61576d90615639565b600881101561152757600381149081156157b4575b501561578a57565b60405162461bcd60e51b8152602060048201526002602482015261034360f41b6044820152606490fd5b600291501438615782565b6004546001600160a01b031633036157d357565b60405162461bcd60e51b8152602060048201526002602482015261343160f01b6044820152606490fd5b919091600083820193841291129080158216911516176106d35756fe7620380c554abbc2f2c8551155ba9b12b3fcd40700f9a718a43678a914753048a933c5f2ff3ab65b8004e747a48707dab64e66636a6ff5247f0431bc16881774eb0cc7968044d5a32d1b2d53427cbc63a081ddf73f5afb1637a4dfbdea2f7db9a26469706673582212205cb023200ab77eb6105b029109f1d79a2294df8d75da4112a2520090bd92d02964736f6c63430008130033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

000000000000000000000000cd854bd769287bee0b9ac77712c32b12b27cb8e4

-----Decoded View---------------
Arg [0] : backend_ (address): 0xcd854Bd769287Bee0b9ac77712C32B12B27Cb8e4

-----Encoded View---------------
1 Constructor Arguments found :
Arg [0] : 000000000000000000000000cd854bd769287bee0b9ac77712c32b12b27cb8e4


Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.