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());
}
}