Ape Curtis Testnet

Contract Diff Checker

Contract Name:
PredictionMarket

Contract Source Code:

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.20;

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```solidity
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 *
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Storage of the initializable contract.
     *
     * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
     * when using with upgradeable contracts.
     *
     * @custom:storage-location erc7201:openzeppelin.storage.Initializable
     */
    struct InitializableStorage {
        /**
         * @dev Indicates that the contract has been initialized.
         */
        uint64 _initialized;
        /**
         * @dev Indicates that the contract is in the process of being initialized.
         */
        bool _initializing;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;

    /**
     * @dev The contract is already initialized.
     */
    error InvalidInitialization();

    /**
     * @dev The contract is not initializing.
     */
    error NotInitializing();

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint64 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
     * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
     * production.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        // Cache values to avoid duplicated sloads
        bool isTopLevelCall = !$._initializing;
        uint64 initialized = $._initialized;

        // Allowed calls:
        // - initialSetup: the contract is not in the initializing state and no previous version was
        //                 initialized
        // - construction: the contract is initialized at version 1 (no reininitialization) and the
        //                 current contract is just being deployed
        bool initialSetup = initialized == 0 && isTopLevelCall;
        bool construction = initialized == 1 && address(this).code.length == 0;

        if (!initialSetup && !construction) {
            revert InvalidInitialization();
        }
        $._initialized = 1;
        if (isTopLevelCall) {
            $._initializing = true;
        }
        _;
        if (isTopLevelCall) {
            $._initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint64 version) {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing || $._initialized >= version) {
            revert InvalidInitialization();
        }
        $._initialized = version;
        $._initializing = true;
        _;
        $._initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        _checkInitializing();
        _;
    }

    /**
     * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
     */
    function _checkInitializing() internal view virtual {
        if (!_isInitializing()) {
            revert NotInitializing();
        }
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing) {
            revert InvalidInitialization();
        }
        if ($._initialized != type(uint64).max) {
            $._initialized = type(uint64).max;
            emit Initialized(type(uint64).max);
        }
    }

    /**
     * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
    function _getInitializedVersion() internal view returns (uint64) {
        return _getInitializableStorage()._initialized;
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
    function _isInitializing() internal view returns (bool) {
        return _getInitializableStorage()._initializing;
    }

    /**
     * @dev Returns a pointer to the storage namespace.
     */
    // solhint-disable-next-line var-name-mixedcase
    function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
        assembly {
            $.slot := INITIALIZABLE_STORAGE
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol)

pragma solidity ^0.8.20;

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

/**
 * @title IERC1363
 * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
 *
 * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
 * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
 */
interface IERC1363 is IERC20, IERC165 {
    /*
     * Note: the ERC-165 identifier for this interface is 0xb0202a11.
     * 0xb0202a11 ===
     *   bytes4(keccak256('transferAndCall(address,uint256)')) ^
     *   bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
     */

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @param data Additional data with no specified format, sent in call to `spender`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)

pragma solidity ^0.8.20;

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

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../token/ERC20/IERC20.sol";

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/IBeacon.sol)

pragma solidity ^0.8.20;

/**
 * @dev This is the interface that {BeaconProxy} expects of its beacon.
 */
interface IBeacon {
    /**
     * @dev Must return an address that can be used as a delegate call target.
     *
     * {UpgradeableBeacon} will check that this address is a contract.
     */
    function implementation() external view returns (address);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
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 value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

    /**
     * @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` 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 value) external returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
import {Address} from "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC-20 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 {
    /**
     * @dev An operation with an ERC-20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @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.encodeCall(token.transfer, (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.encodeCall(token.transferFrom, (from, to, 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.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @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.
     *
     * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
     * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
     * set here.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

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

    /**
     * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            safeTransfer(token, to, value);
        } else if (!token.transferAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
     * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferFromAndCallRelaxed(
        IERC1363 token,
        address from,
        address to,
        uint256 value,
        bytes memory data
    ) internal {
        if (to.code.length == 0) {
            safeTransferFrom(token, from, to, value);
        } else if (!token.transferFromAndCall(from, to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
     * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
     * once without retrying, and relies on the returned value to be true.
     *
     * Reverts if the returned value is other than `true`.
     */
    function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            forceApprove(token, to, value);
        } else if (!token.approveAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @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 {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            // bubble errors
            if iszero(success) {
                let ptr := mload(0x40)
                returndatacopy(ptr, 0, returndatasize())
                revert(ptr, returndatasize())
            }
            returnSize := returndatasize()
            returnValue := mload(0)
        }

        if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @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 silently catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0)
        }
        return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Address.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

    /**
     * @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.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        if (address(this).balance < amount) {
            revert Errors.InsufficientBalance(address(this).balance, amount);
        }

        (bool success, ) = recipient.call{value: amount}("");
        if (!success) {
            revert Errors.FailedCall();
        }
    }

    /**
     * @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 or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {Errors.FailedCall} error.
     *
     * 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.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    /**
     * @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`.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert Errors.InsufficientBalance(address(this).balance, value);
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

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

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

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
     * was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case
     * of an unsuccessful call.
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata
    ) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            // only check if target is a contract if the call was successful and the return data is empty
            // otherwise we already know that it was a contract
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
     * revert reason or with a default {Errors.FailedCall} error.
     */
    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            return returndata;
        }
    }

    /**
     * @dev Reverts with returndata if present. Otherwise reverts with {Errors.FailedCall}.
     */
    function _revert(bytes memory returndata) 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
            assembly ("memory-safe") {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert Errors.FailedCall();
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of common custom errors used in multiple contracts
 *
 * IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
 * It is recommended to avoid relying on the error API for critical functionality.
 *
 * _Available since v5.1._
 */
library Errors {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error InsufficientBalance(uint256 balance, uint256 needed);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedCall();

    /**
     * @dev The deployment failed.
     */
    error FailedDeployment();

    /**
     * @dev A necessary precompile is missing.
     */
    error MissingPrecompile(address);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

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

// SPDX-License-Identifier: MIT
pragma solidity =0.8.28;

import "@openzeppelin/contracts/proxy/beacon/IBeacon.sol";
import "../libraries/MarketStruct.sol";

/**
 * @title IMarketFactory
 * @dev Interface for the MarketFactory contract.
 */
interface IMarketFactory is IBeacon {
    /**
     * @notice Emitted when the treasury address is updated.
     * @param tresuary The new treasury address.
     */
    event Tresuary(address indexed tresuary);

    /**
     * @notice Emitted when the base price is updated.
     * @param basePrice The new base price.
     */
    event BasePrice(uint256 indexed basePrice);

    /**
     * @notice Emitted when the event duration is updated.
     * @param duration The new event duration.
     */
    event EventDuration(uint256 indexed duration);

    /**
     * @notice Emitted when the "no buy" fee percentage is updated.
     * @param noBuyFeePercentage The new "no buy" fee percentage.
     */
    event NoBuyFeePercentage(uint256 indexed noBuyFeePercentage);

    /**
     * @notice Emitted when the "yes claim" fee percentage is updated.
     * @param yesClaimFeePercentage The new "yes claim" fee percentage.
     */
    event YesClaimFeePercentage(uint256 indexed yesClaimFeePercentage);

    /**
     * @notice Emitted when a new market is created.
     * @param market The address of the created market.
     * @param signer The signer who authorized the market creation.
     * @param token The token address associated with the market.
     * @param creator The creator of the market.
     * @param basePrice The base price for the market.
     * @param noBuyFeePercentage The "no buy" fee percentage.
     * @param yesClaimFeePercentage The "yes claim" fee percentage.
     * @param initialEvent The initial event type of the market.
     * @param eventDuration The duration of the event.
     */
    event MarketCreated(
        address indexed market,
        address indexed signer,
        address indexed token,
        address creator,
        uint256 basePrice,
        uint256 noBuyFeePercentage,
        uint256 yesClaimFeePercentage,
        PredictionMarketEvent initialEvent,
        uint256 eventDuration
    );

    /**
     * @dev Error thrown when the market creation signature is expired.
     * @param deadline The deadline that has passed.
     */
    error CreateMarketSignatureExpired(uint256 deadline);

    /**
     * @dev Error thrown when the signer for market creation is invalid.
     * @param signer The invalid signer address.
     * @param from The address attempting to create the market.
     */
    error CreateMarketInvalidSigner(address signer, address from);

    /**
     * @dev Error thrown when a fee percentage exceeds the maximum allowed value.
     * @param feePercentage The fee percentage that exceeded the limit.
     */
    error GtThenMaxFeePercentage(uint256 feePercentage);

    /**
     * @dev Error thrown when a market already exists for a specific token.
     * @param token The token address for which the market exists.
     */
    error MarketAlreadyExist(address token);

    /**
     * @notice Creates a new prediction market.
     * @param params_ The parameters required to create the market.
     * @return The address of the newly created market.
     */
    function createMarket(
        PredictionMarketCreateParams calldata params_
    ) external returns (address);

    /**
     * @notice Updates the treasury address.
     * @param tresuary_ The new treasury address.
     */
    function setTresuary(address tresuary_) external;

    /**
     * @notice Updates the base price for market creation.
     * @param basePrice_ The new base price.
     */
    function setBasePrice(uint256 basePrice_) external;

    /**
     * @notice Updates the "no buy" fee percentage.
     * @param noBuyFeePercentage_ The new "no buy" fee percentage.
     */
    function setNoBuyFeePercentage(uint256 noBuyFeePercentage_) external;

    /**
     * @notice Updates the "yes claim" fee percentage.
     * @param yesClaimFeePercentage_ The new "yes claim" fee percentage.
     */
    function setYesClaimFeePercentage(uint256 yesClaimFeePercentage_) external;

    /**
     * @notice Updates the event duration for markets.
     * @param eventDuration_ The new event duration.
     */
    function setEventDuration(uint256 eventDuration_) external;

    /**
     * @notice Pauses or unpauses the contract.
     * @param pause_ True to pause, false to unpause.
     */
    function setPause(bool pause_) external;

    /**
     * @notice Checks if the specified address has the `ORACLE_ROLE`.
     * @param oracle_ The address to check.
     * @return True if the address has the `ORACLE_ROLE`, otherwise false.
     */
    function isOracle(address oracle_) external view returns (bool);

    /**
     * @notice Checks if the contract is paused.
     * @return True if the contract is paused, otherwise false.
     */
    function paused() external view returns (bool);

    /**
     * @notice Returns the deposit token address.
     * @return The deposit token address.
     */
    function depositToken() external view returns (address);

    /**
     * @notice Returns the implementation address.
     * @return The implementation address.
     */
    function implementation() external view returns (address);

    /**
     * @notice Returns the treasury address.
     * @return The treasury address.
     */
    function tresuary() external view returns (address);

    /**
     * @notice Returns the base price for market creation.
     * @return The base price.
     */
    function basePrice() external view returns (uint256);

    /**
     * @notice Returns the event duration for markets.
     * @return The event duration.
     */
    function eventDuration() external view returns (uint256);

    /**
     * @notice Returns the "no buy" fee percentage.
     * @return The "no buy" fee percentage.
     */
    function noBuyFeePercentage() external view returns (uint256);

    /**
     * @notice Returns the "yes claim" fee percentage.
     * @return The "yes claim" fee percentage.
     */
    function yesClaimFeePercentage() external view returns (uint256);

    /**
     * @notice Returns the length of the markets array.
     * @return The number of markets.
     */
    function marketsLength() external view returns (uint256);

    /**
     * @notice Returns the market address associated with a specific index.
     * @return The market address.
     */
    function markets(uint256 index_) external view returns (address);

    /**
     * @notice Returns a list of market addresses within the specified range.
     * @param offset_ The starting index of the markets array.
     * @param limit_ The maximum number of markets to return.
     * @return A list of market addresses.
     */
    function getMarkets(
        uint256 offset_,
        uint256 limit_
    ) external view returns (address[] memory);

    /**
     * @notice Returns the market address associated with a specific token.
     * @param token The token address.
     * @return The market address.
     */
    function getMarketByToken(address token) external view returns (address);
}

// SPDX-License-Identifier: MIT
pragma solidity =0.8.28;

import "../libraries/MarketStruct.sol";

/**
 * @title IPredictionMarket
 * @dev Interface for a prediction market contract allowing users to participate in events and buy shares.
 */
interface IPredictionMarket {
    /**
     * @dev Enum representing the status of an event.
     */
    enum EventStatus {
        COMPLETED_IN_PAST, // Event has already completed in the past
        AVAILABLE, // Event is currently available for participation
        WAITING_RESULT, // Event is waiting for a result to be provided
        RESULT_YES, // Event result is "YES"
        RESULT_NO // Event result is "NO"
    }

    /**
     * @dev Struct representing a user's balances for a specific event.
     */
    struct UserBalance {
        uint256 noDepoosit; // Total "no" deposits by the user
        uint256 yesDeposit; // Total "yes" deposits by the user
        uint256 noShares; // Total "no" shares owned by the user
        uint256 yesShares; // Total "yes" shares owned by the user
        uint256 claimed; // Total amount claimed by the user
    }

    /**
     * @dev Struct containing data for a specific event.
     */
    struct EventData {
        EventStatus status; // Current status of the event
        uint256 endTime; // Timestamp when the event ends
        uint256 startWaitingResultBlock; // Block number when waiting for result starts
        uint256 yesSharesTotal; // Total "yes" shares in the event
        uint256 noSharesTotal; // Total "no" shares in the event
        uint256 yesDepositTotal; // Total "yes" deposits in the event
        uint256 noDepositTotal; // Total "no" deposits in the event
        uint256 feeAccumulated; // Total fees accumulated for the event
        mapping(address => UserBalance) balances; // Mapping of user balances
    }

    /**
     * @dev Emitted when an event status is updated.
     * @param caller Address of the function caller
     * @param marketEvent The event being updated
     * @param status The new status of the event
     */
    event UpdateEventStatus(
        address indexed caller,
        PredictionMarketEvent indexed marketEvent,
        EventStatus indexed status
    );

    /**
     * @dev Emitted when a new active event is set.
     * @param caller Address of the function caller
     * @param marketEvent The new active event
     * @param endTime The end time of the new active event
     */
    event NextActiveEvent(
        address indexed caller,
        PredictionMarketEvent indexed marketEvent,
        uint256 endTime
    );

    /**
     * @dev Emitted when "yes" shares are purchased.
     * @param caller Address of the buyer
     * @param marketEvent The event for which shares are purchased
     * @param depositAmount Amount deposited for the shares
     * @param sharesAmountOut Number of shares purchased
     * @param fee Fee applied to the purchase
     */
    event BuyYesShares(
        address indexed caller,
        PredictionMarketEvent indexed marketEvent,
        uint256 depositAmount,
        uint256 sharesAmountOut,
        uint256 fee
    );

    /**
     * @dev Emitted when "no" shares are purchased.
     * @param caller Address of the buyer
     * @param marketEvent The event for which shares are purchased
     * @param depositAmount Amount deposited for the shares
     * @param sharesAmountOut Number of shares purchased
     * @param sharesFee Fee applied to the purchase
     */
    event BuyNoShares(
        address indexed caller,
        PredictionMarketEvent indexed marketEvent,
        uint256 depositAmount,
        uint256 sharesAmountOut,
        uint256 sharesFee
    );

    /**
     * @dev Emitted when a user claims their rewards.
     * @param caller Address of the claimant
     * @param marketEvent The event for which rewards are claimed
     * @param amount Amount claimed
     * @param fee Fee deducted from the claim
     */
    event Claim(
        address indexed caller,
        PredictionMarketEvent indexed marketEvent,
        uint256 amount,
        uint256 fee
    );

    /**
     * @dev Emitted when fees are withdrawn.
     * @param caller Address of the fee withdrawer (treasury)
     * @param marketEvent The event for which fees are withdrawn
     * @param fee Amount of fees withdrawn
     */
    event WithdrawFee(
        address indexed caller,
        PredictionMarketEvent indexed marketEvent,
        uint256 fee
    );

    /**
     * @dev Error thrown when the market is paused.
     */
    error EnforcedPause();

    /**
     * @dev Error thrown when an invalid active event is provided.
     * @param marketEvent The invalid event
     */
    error InvalidActiveEvent(PredictionMarketEvent marketEvent);

    /**
     * @dev Error thrown when an event has an invalid status.
     */
    error InvalidEventStatus();

    /**
     * @dev Error thrown when an invalid block is skipped.
     */
    error InvalidBlockSkip();

    /**
     * @dev Error thrown when an unauthorized oracle attempts an action.
     * @param caller Address of the unauthorized oracle
     */
    error UnauthorizedOracle(address caller);

    /**
     * @dev Error thrown when an event is not available for participation.
     */
    error EventNotAvailable();

    /**
     * @dev Error thrown when the market is waiting for a result.
     */
    error WaitingResult();

    /**
     * @dev Error thrown when attempting to claim a zero amount.
     */
    error ZeroAmountToClaim();

    /**
     * @dev Error thrown when access is denied to a caller.
     */
    error AccessDenied();

    /**
     * @notice Returns the factory address that deployed the market.
     */
    function factory() external view returns (address);

    /**
     * @notice Returns the address of the ERC20 token used for deposits.
     */
    function token() external view returns (address);

    /**
     * @notice Returns the base price for buying shares.
     */
    function basePrice() external view returns (uint256);

    /**
     * @notice Returns the fee percentage for buying "no" shares.
     */
    function noBuyFeePercentage() external view returns (uint256);

    /**
     * @notice Returns the fee percentage for claiming "yes" outcomes.
     */
    function yesClaimFeePercentage() external view returns (uint256);

    /**
     * @notice Returns the duration (in seconds) for which an event remains active.
     */
    function eventDuration() external view returns (uint256);

    /**
     * @notice Initializes the market with the provided parameters.
     * @param params_ Initial configuration parameters for the market
     */
    function initialize(
        PredictionMarketInitialParams calldata params_
    ) external;

    /**
     * @notice Starts the waiting result state for a specific event.
     * @param event_ The event to transition to waiting result state
     */
    function startWaitingResultState(PredictionMarketEvent event_) external;

    /**
     * @notice Processes the result of an event and updates its status accordingly.
     * @param event_ The event to process
     * @param result_ The result of the event (true for "yes", false for "no")
     */
    function proccessEventResult(
        PredictionMarketEvent event_,
        bool result_
    ) external;

    /**
     * @notice Purchases "yes" shares for a specific event.
     * @param event_ The event for which shares are being purchased
     * @param sharesAmount_ The number of shares to purchase
     * @return depositAmount The amount of tokens deposited for the purchase
     */
    function buyYesShares(
        PredictionMarketEvent event_,
        uint256 sharesAmount_
    ) external returns (uint256 depositAmount);

    /**
     * @notice Purchases "no" shares for a specific event.
     * @param event_ The event for which shares are being purchased
     * @param sharesAmount_ The number of shares to purchase
     * @return depositAmount The total amount of tokens deposited, including fees
     * @return fee The fee applied to the purchase
     */
    function buyNoShares(
        PredictionMarketEvent event_,
        uint256 sharesAmount_
    ) external returns (uint256 depositAmount, uint256 fee);

    /**
     * @notice Withdraws accumulated fees for a specific event.
     * @param event_ The event for which fees are being withdrawn
     */
    function withdrawFee(PredictionMarketEvent event_) external;

    /**
     * @notice Claims rewards for a specific event based on the user's shares.
     * @param event_ The event for which rewards are being claimed
     */
    function claim(PredictionMarketEvent event_) external;

    /**
     * @notice Retrieves the amount available to claim for a user for a specific event.
     * @param user_ The address of the user
     * @param event_ The event for which the claimable amount is being calculated
     * @return amountOut The total amount available to claim for the user
     * @return fee The fee applicable on the claim (if any)
     */
    function getAvailableToClaim(
        address user_,
        PredictionMarketEvent event_
    ) external view returns (uint256 amountOut, uint256 fee);

    /**
     * @notice Retrieves the balance details of a user for a specific event.
     * @param user_ The address of the user
     * @param event_ The event for which balances are being queried
     * @return The user's balance details as a `UserBalance` struct
     */
    function getUserBalances(
        address user_,
        PredictionMarketEvent event_
    ) external view returns (UserBalance memory);

    /**
     * @notice Returns the currently active event in the market.
     * @return The active event as a `PredictionMarketEvent`
     */
    function getActiveEvent() external view returns (PredictionMarketEvent);

    /**
     * @notice Calculates the deposit amount required to buy "no" shares, including fees.
     * @param sharesAmount_ The number of "no" shares to purchase
     * @return depositAmountInWithFee The total deposit amount including fees
     * @return depositAmountIn The deposit amount excluding fees
     */
    function calculateNoSharesDepositAmountIn(
        uint256 sharesAmount_
    )
        external
        view
        returns (uint256 depositAmountInWithFee, uint256 depositAmountIn);

    /**
     * @notice Calculates the deposit amount required to buy "yes" shares.
     * @param sharesAmount_ The number of "yes" shares to purchase
     * @return The required deposit amount
     */
    function calculateYesDepositAmountIn(
        uint256 sharesAmount_
    ) external view returns (uint256);

    /**
     * @notice Checks if the current active event is in the waiting result state.
     * @return True if the active event is waiting for a result, otherwise false
     */
    function isWaitingResult() external view returns (bool);
}

// SPDX-License-Identifier: MIT
pragma solidity =0.8.28;

uint256 constant PRECISION = 1e18;

// SPDX-License-Identifier: MIT
pragma solidity =0.8.28;

enum PredictionMarketEvent {
    REACH_POOL,
    REACH_500_000_CAP,
    REACH_1_000_000_CAP,
    REACH_2_000_000_CAP,
    REACH_5_000_000_CAP
}

struct PredictionMarketInitialParams {
    address token;
    uint256 basePrice;
    uint256 noBuyFeePercentage;
    uint256 yesClaimFeePercentage;
    uint256 eventDuration;
    PredictionMarketEvent initialEvent;
}

struct PredictionMarketCreateParams {
    address token;
    PredictionMarketEvent initialEvent;
    uint256 deadline;
    bytes signature;
}

// SPDX-License-Identifier: MIT
pragma solidity =0.8.28;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import "./interfaces/IPredictionMarket.sol";
import "./interfaces/IMarketFactory.sol";
import "./libraries/Constants.sol";

/**
 * @title PredictionMarket
 * @dev Implementation of a prediction market that allows users to buy shares
 * and participate in events based on their outcomes. The contract supports multiple
 * events, each with its own status, and enforces permissioned access for oracles.
 * The market ensures safe token handling using OpenZeppelin's SafeERC20.
 */
contract PredictionMarket is IPredictionMarket, Initializable {
    using SafeERC20 for IERC20;

    /** @notice Address of the market factory contract that deployed this market. */
    address public override factory;

    /** @notice Address of the ERC20 token used for deposits in the market. */
    address public override token;

    /** @notice Base price for buying shares in the market. */
    uint256 public override basePrice;

    /** @notice Fee percentage for buying "no" shares in the market. */
    uint256 public override noBuyFeePercentage;

    /** @notice Fee percentage applied when claiming "yes" outcomes. */
    uint256 public override yesClaimFeePercentage;

    /** @notice Duration (in seconds) for which an event remains active. */
    uint256 public override eventDuration;

    /** @notice Mapping of events to their data, including status, balances, and deposits. */
    mapping(PredictionMarketEvent => EventData) public events;

    /** @notice Currently active event in the market. */
    PredictionMarketEvent internal _activeEvent;

    /**
     * @dev Modifier to ensure the market is not paused.
     */
    modifier whenNotPaused() {
        if (IMarketFactory(factory).paused()) {
            revert EnforcedPause();
        }
        _;
    }

    /**
     * @dev Modifier to restrict access to authorized oracles only.
     */
    modifier onlyOracle() {
        if (!IMarketFactory(factory).isOracle(msg.sender)) {
            revert UnauthorizedOracle(msg.sender);
        }
        _;
    }
    /**
     * @dev Modifier to restrict access to authorized oracles only.
     */
    modifier onlyTresuary() {
        if (!(IMarketFactory(factory).tresuary() == msg.sender)) {
            revert AccessDenied();
        }
        _;
    }

    /**
     * @dev Disables initializers to prevent misuse during deployment.
     */
    constructor() {
        _disableInitializers();
    }

    /**
     * @notice Initializes the market with the provided parameters.
     * @param params_ Struct containing the initial configuration of the market.
     */
    function initialize(
        PredictionMarketInitialParams calldata params_
    ) external initializer {
        factory = msg.sender;
        token = params_.token;
        basePrice = params_.basePrice;
        noBuyFeePercentage = params_.noBuyFeePercentage;
        yesClaimFeePercentage = params_.yesClaimFeePercentage;
        eventDuration = params_.eventDuration;

        for (
            uint8 i = uint8(params_.initialEvent);
            i <= uint8(PredictionMarketEvent.REACH_5_000_000_CAP);
            i++
        ) {
            _updateEventStatus(PredictionMarketEvent(i), EventStatus.AVAILABLE);
        }
        _updateActiveEvent(params_.initialEvent);
    }

    /**
     * @notice Starts the waiting result state for a specific event.
     * @param event_ The event to transition to waiting result state.
     *
     * Requirements:
     * - The caller must be an authorized oracle.
     * - The event must be the currently active event.
     * - The event's status must be `AVAILABLE`.
     */
    function startWaitingResultState(
        PredictionMarketEvent event_
    ) external onlyOracle {
        if (_activeEvent != event_) {
            revert InvalidActiveEvent(event_);
        }
        if (events[event_].status != EventStatus.AVAILABLE) {
            revert InvalidEventStatus();
        }
        events[event_].startWaitingResultBlock = block.number;

        _updateEventStatus(event_, EventStatus.WAITING_RESULT);
    }

    /**
     * @notice Processes the result of an event and updates its status accordingly.
     * @param event_ The event to process.
     * @param result_ The result of the event (`true` for "yes", `false` for "no").
     *
     * Requirements:
     * - The caller must be an authorized oracle.
     * - The event must be the currently active event.
     * - The event's status must be `WAITING_RESULT`.
     * - The result must be processed after the waiting block.
     */
    function proccessEventResult(
        PredictionMarketEvent event_,
        bool result_
    ) external onlyOracle {
        if (_activeEvent != event_) {
            revert InvalidActiveEvent(event_);
        }

        if (events[event_].status != EventStatus.WAITING_RESULT) {
            revert InvalidEventStatus();
        }

        if (events[event_].startWaitingResultBlock >= block.number) {
            revert InvalidBlockSkip();
        }
        EventStatus newStatus = result_
            ? EventStatus.RESULT_YES
            : EventStatus.RESULT_NO;

        if (newStatus == EventStatus.RESULT_YES) {
            _updateEventStatus(event_, newStatus);
            if (event_ != PredictionMarketEvent.REACH_5_000_000_CAP) {
                _updateActiveEvent(PredictionMarketEvent(uint8(event_) + 1));
            }
        } else {
            for (
                uint8 i = uint8(event_);
                i <= (uint8(PredictionMarketEvent.REACH_5_000_000_CAP));
                i++
            ) {
                _updateEventStatus(PredictionMarketEvent(i), newStatus);
            }
        }
    }

    /**
     * @notice Purchases "yes" shares for a specific event.
     * @param event_ The event for which shares are being purchased.
     * @param sharesAmount_ The number of shares to purchase.
     * @return depositAmount The amount of tokens deposited for the purchase.
     *
     * Emits a {BuyYesShares} event.
     */
    function buyYesShares(
        PredictionMarketEvent event_,
        uint256 sharesAmount_
    ) external whenNotPaused returns (uint256 depositAmount) {
        _checkAvailableEvent(event_);

        depositAmount = calculateYesDepositAmountIn(sharesAmount_);

        _depositToken().safeTransferFrom(
            msg.sender,
            address(this),
            depositAmount
        );

        events[event_].balances[msg.sender].yesShares += sharesAmount_;
        events[event_].balances[msg.sender].yesDeposit += depositAmount;
        events[event_].yesDepositTotal += depositAmount;
        events[event_].yesSharesTotal += sharesAmount_;

        emit BuyYesShares(msg.sender, event_, depositAmount, sharesAmount_, 0);
    }

    /**
     * @notice Purchases "no" shares for a specific event.
     * @param event_ The event for which shares are being purchased.
     * @param sharesAmount_ The number of shares to purchase.
     * @return depositAmount The total amount of tokens deposited, including fees.
     *
     * Requirements:
     * - The event must be available.
     * - The market must not be paused.
     *
     * Emits a {BuyNoShares} event.
     */
    function buyNoShares(
        PredictionMarketEvent event_,
        uint256 sharesAmount_
    ) external whenNotPaused returns (uint256 depositAmount, uint256 fee) {
        _checkAvailableEvent(event_);

        uint256 depositAmountWithoutFee;
        (
            depositAmount,
            depositAmountWithoutFee
        ) = calculateNoSharesDepositAmountIn(sharesAmount_);

        _depositToken().safeTransferFrom(
            msg.sender,
            address(this),
            depositAmount
        );

        fee = depositAmount - depositAmountWithoutFee;

        events[event_].balances[msg.sender].noShares += sharesAmount_;
        events[event_]
            .balances[msg.sender]
            .noDepoosit += depositAmountWithoutFee;
        events[event_].noDepositTotal += depositAmountWithoutFee;
        events[event_].noSharesTotal += sharesAmount_;
        events[event_].feeAccumulated += fee;

        emit BuyNoShares(
            msg.sender,
            event_,
            depositAmountWithoutFee,
            sharesAmount_,
            fee
        );
    }

    /**
     * @notice Withdraws accumulated fees for a specific event.
     * @param event_ The event for which fees are being withdrawn.
     *
     * Requirements:
     * - Caller must be the treasury.
     * - The event must have a "RESULT_NO" or "RESULT_YES" status.
     * - The accumulated fee must be greater than 0.
     *
     * Emits a {WithdrawFee} event.
     */
    function withdrawFee(PredictionMarketEvent event_) external onlyTresuary {
        EventStatus eventStatus = events[event_].status;
        if (
            eventStatus != EventStatus.RESULT_NO &&
            eventStatus != EventStatus.RESULT_YES
        ) {
            revert InvalidEventStatus();
        }

        uint256 fee = events[event_].feeAccumulated;
        if (fee == 0) {
            revert ZeroAmountToClaim();
        }

        events[event_].feeAccumulated = 0;

        _depositToken().safeTransfer(msg.sender, fee);

        emit WithdrawFee(msg.sender, event_, fee);
    }

    /**
     * @notice Claims rewards for a specific event based on the user's shares.
     * @param event_ The event for which rewards are being claimed.
     *
     * Requirements:
     * - The event must have a "RESULT_NO" or "RESULT_YES" status.
     * - The user must have an unclaimed balance.
     *
     * Emits a {Claim} event.
     */
    function claim(PredictionMarketEvent event_) external whenNotPaused {
        EventStatus eventStatus = events[event_].status;
        if (
            eventStatus != EventStatus.RESULT_NO &&
            eventStatus != EventStatus.RESULT_YES
        ) {
            revert InvalidEventStatus();
        }

        (uint256 amount, uint256 fee) = getAvailableToClaim(msg.sender, event_);
        if (amount == 0) {
            revert ZeroAmountToClaim();
        }
        events[event_].balances[msg.sender].claimed += amount;

        if (fee > 0) {
            events[event_].feeAccumulated += fee;
        }

        _depositToken().safeTransfer(msg.sender, amount);

        emit Claim(msg.sender, event_, amount, fee);
    }

    /**
     * @notice Retrieves the amount available to claim for a user for a specific event.
     * @param user_ The address of the user.
     * @param event_ The event for which the claimable amount is being calculated.
     * @return amountOut The total amount available to claim for the user.
     * @return fee The fee applicable on the claim (if any).
     *
     * Requirements:
     * - The user must not have already claimed for the event.
     * - The event must have a "RESULT_NO" or "RESULT_YES" status.
     *
     * Logic:
     * - If the event result is "NO", calculates the user's proportionate share from total deposits.
     * - If the event result is "YES", deducts a claim fee from the user's share of total deposits.
     */
    function getAvailableToClaim(
        address user_,
        PredictionMarketEvent event_
    ) public view returns (uint256 amountOut, uint256 fee) {
        EventStatus eventStatus = events[event_].status;
        if (events[event_].balances[msg.sender].claimed > 0) {
            return (0, 0);
        }

        uint256 totalDeposits = events[event_].yesDepositTotal +
            events[event_].noDepositTotal;
        if (totalDeposits == 0) {
            return (0, 0);
        }
        if (eventStatus == EventStatus.RESULT_NO) {
            amountOut =
                (events[event_].balances[user_].noShares * totalDeposits) /
                events[event_].noSharesTotal;
        } else if (eventStatus == EventStatus.RESULT_YES) {
            amountOut =
                (events[event_].balances[user_].yesShares * totalDeposits) /
                events[event_].yesSharesTotal;

            fee = (amountOut * yesClaimFeePercentage) / PRECISION;
            amountOut -= fee;
        }
    }

    /**
     * @notice Retrieves the balance details of a user for a specific event.
     * @param user_ The address of the user.
     * @param event_ The event for which balances are being queried.
     * @return The user's balance details as a `UserBalance` struct.
     */
    function getUserBalances(
        address user_,
        PredictionMarketEvent event_
    ) external view returns (UserBalance memory) {
        return events[event_].balances[user_];
    }

    /**
     * @notice Returns the currently active event in the market.
     * @return The active event as a `PredictionMarketEvent`.
     */
    function getActiveEvent() public view returns (PredictionMarketEvent) {
        return (_activeEvent);
    }

    /**
     * @notice Calculates the deposit amount required to buy "no" shares, including fees.
     * @param sharesAmount_ The number of "no" shares to purchase.
     * @return depositAmountInWithFee The total deposit amount including fees.
     * @return depositAmountIn The deposit amount excluding fees.
     */
    function calculateNoSharesDepositAmountIn(
        uint256 sharesAmount_
    )
        public
        view
        returns (uint256 depositAmountInWithFee, uint256 depositAmountIn)
    {
        uint256 priceWithFee = basePrice +
            (basePrice * noBuyFeePercentage) /
            1e18;
        depositAmountInWithFee = (sharesAmount_ * priceWithFee) / 1e18;
        depositAmountIn = (sharesAmount_ * basePrice) / 1e18;
    }

    /**
     * @notice Calculates the deposit amount required to buy "yes" shares.
     * @param sharesAmount_ The number of "yes" shares to purchase.
     * @return The required deposit amount.
     */
    function calculateYesDepositAmountIn(
        uint256 sharesAmount_
    ) public view returns (uint256) {
        return (sharesAmount_ * basePrice) / 1e18;
    }

    /**
     * @notice Checks if the current active event is in the waiting result state.
     * @return True if the active event is waiting for a result, otherwise false.
     */
    function isWaitingResult() public view returns (bool) {
        PredictionMarketEvent activeEventCache = _activeEvent;
        EventStatus eventStatus = events[activeEventCache].status;

        if (eventStatus == EventStatus.WAITING_RESULT) {
            return true;
        }

        return
            eventStatus == EventStatus.AVAILABLE &&
            block.timestamp >= events[activeEventCache].endTime;
    }

    /**
     * @notice Updates the currently active event and sets its end time.
     * @param marketEvent_ The event to set as active.
     *
     * Emits a {NextActiveEvent} event.
     */
    function _updateActiveEvent(PredictionMarketEvent marketEvent_) internal {
        _activeEvent = marketEvent_;
        uint256 endTime = block.timestamp + eventDuration;
        events[marketEvent_].endTime = endTime;
        emit NextActiveEvent(msg.sender, marketEvent_, endTime);
    }

    /**
     * @notice Updates the status of a specific event.
     * @param marketEvent_ The event to update.
     * @param status_ The new status to set for the event.
     *
     * Emits an {UpdateEventStatus} event.
     */
    function _updateEventStatus(
        PredictionMarketEvent marketEvent_,
        EventStatus status_
    ) internal {
        events[marketEvent_].status = status_;
        emit UpdateEventStatus(msg.sender, marketEvent_, status_);
    }

    /**
     * @notice Validates that an event is available for participation.
     * @param event_ The event to validate.
     *
     * Requirements:
     * - The market must not be waiting for a result.
     * - The event's status must be `AVAILABLE`.
     */
    function _checkAvailableEvent(PredictionMarketEvent event_) internal view {
        if (isWaitingResult()) {
            revert WaitingResult();
        }

        if (events[event_].status != EventStatus.AVAILABLE) {
            revert EventNotAvailable();
        }
    }

    /**
     * @notice Retrieves the deposit token used by the market.
     * @return The deposit token as an `IERC20` interface.
     */
    function _depositToken() internal view returns (IERC20) {
        return IERC20(IMarketFactory(factory).depositToken());
    }
}

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

Context size (optional):