Skip to main content

Predictions Oracle

Overview

The PredictionsOracle contract serves as the central management contract for the prediction market system. It interacts with the ConditionalTokens and FixedProductMarketMaker contracts to create markets, facilitate trading, and resolve outcomes.

Key Features and Considerations

  1. Market Creation and Management: The Oracle is responsible for creating new markets and managing their lifecycle.
  2. Trading Interface: It provides functions for users to buy and sell positions, interfacing with the FPMM contracts.
  3. Market Resolution: The Oracle handles the process of proposing answers and resolving markets.
  4. Access Control: It uses a system of initializers and proposers to control who can perform certain actions.
  5. Integration with OnchainPoints: The contract is designed to work with an OnchainPoints system, allowing for more complex reward and spending mechanisms.
  6. Upgradability: The contract is designed to be upgradeable, using the UUPS pattern.
  7. Reentrancy Protection: Uses OpenZeppelin's ReentrancyGuard to protect against reentrancy attacks.

Contract Details

  • License: MIT
  • Solidity Version: ^0.8.24
  • Inheritance: Initializable, OwnableUpgradeable, ERC1155HolderUpgradeable, UUPSUpgradeable, ReentrancyGuardUpgradeable
  • Source Code: PredictionsOracle

Data Structures

State Variables

Contract References

ConditionalTokens public conditionalTokens;
IFactory public FPMMFactory;
WPOP public collateralToken;
address payable onchainPointsAddress;
  • conditionalTokens: Reference to the ConditionalTokens contract, which manages the creation, distribution, and resolution of conditional tokens. It creates and tracks ERC1155 tokens representing market positions, handles position splitting and merging, and manages redemption of winning positions. The PredictionsOracle interacts with this contract to prepare conditions for new markets and resolve markets based on reported outcomes.

  • FPMMFactory: Reference to the FPMM Factory contract, responsible for creating new Fixed Product Market Maker instances. The PredictionsOracle uses this factory to deploy new FPMM contracts when creating markets, ensuring each market has its own dedicated FPMM for liquidity provision and trading.

  • collateralToken: Reference to an ERC20 contract, the wrapped token used as collateral in markets. The PredictionsOracle interacts with this contract to handle deposits and withdrawals of collateral when users buy or sell positions, manage fee transfers, and facilitate redemption of winning positions. The PredictionsOracle also manages the conversion between native tokens and wrapped tokens, ensuring seamless interaction between both versions for market operations.

  • onchainPointsAddress: Address of the OnchainPoints contract, which manages locked POP tokens for betting. The PredictionsOracle interacts with this contract to verify user balances of locked points, update point balances after bets or redemptions, and enforce restrictions on betting with locked points vs. unlocked tokens based on the buyWithUnlockedEnabled flag.

Configuration Parameters


uint constant ONE = 10**18;
uint256 public **stopTradingBeforeMarketEnd**;
uint256 public minBuyAmount;
uint256 public maxBuyAmountPerQuestion;
bytes32 parentCollectionId;
bool public sellEnabled;
bool public buyWithUnlockedEnabled;
  • ONE: Represents 1 token in 18 decimal format (1e18).
  • stopTradingBeforeMarketEnd: The duration (in seconds) before a market's end time when trading is halted. For example, if set to 60 seconds and a market has an end time of 600 seconds, trading stops at 540 seconds.
  • minBuyAmount: The minimum amount of POP tokens required to place a bet, ensuring a baseline stake for participation.
  • maxBuyAmountPerQuestion: The maximum amount of POP tokens allowed to be bet on a single market, preventing excessive concentration of stakes.
  • parentCollectionId: The root ConditionalTokens collection ID that encompasses all GPM market collections. Defaults to bytes32(0).
  • sellEnabled: A flag indicating whether users are allowed to sell their conditional token positions. When false, positions can only be held until market resolution.
  • buyWithUnlockedEnabled: A flag determining if bets can be placed using self-custodied POP tokens. When false, bets are restricted to locked points from the OnchainPoints contract.

Access Control Sets

EnumerableSet.AddressSet private initializerSet;
EnumerableSet.AddressSet private proposerSet;
  • initializerSet: A set of addresses authorized to initialize new FPMM markets using the createMarket() function. This restricts market creation to approved entities.
  • proposerSet: A set of addresses permitted to propose answers for markets and resolve them. This ensures that only designated oracles can determine market outcomes. While only designated proposers can propose answers, any user can call resolveMarket() if an answer for the given market has been proposed.

Structs

QuestionData

struct QuestionData {
uint256 beginTimestamp;
uint256 endTimestamp;
uint256 outcomeSlots;
address fpmm;
bytes32 conditionId;
}

This struct stores essential information about each prediction market question:

  • beginTimestamp: The timestamp when the market was created.
  • endTimestamp: The timestamp when the market will end and no more trading will be allowed.
  • outcomeSlots: The number of possible outcomes for this question.
  • fpmm: The address of the FixedProductMarketMaker contract associated with this market.
  • conditionId: The unique identifier of the condition in the ConditionalTokens contract.

AnswerData

struct AnswerData {
uint256[] payouts;
uint256 answerTimestamp;
string answerCid;
}

This struct stores information about the proposed answer for a market:

  • payouts: An array representing the payout for each outcome. For example, [1, 0] would indicate the first outcome is correct.
  • answerTimestamp: The timestamp when the answer was proposed.
  • answerCid: A content identifier (CID) for additional answer details, typically stored on IPFS.

MarketData

struct MarketData {
QuestionData questionData;
AnswerData answerData;
uint256 uniqueBuys;
uint256[] probabilities;
uint256[] buyAmounts;
}

This struct combines question and answer data with additional market information:

  • questionData: The QuestionData struct for this market.
  • answerData: The AnswerData struct for this market.
  • uniqueBuys: The number of unique buy transactions in this market.
  • probabilities: An array of current probabilities for each outcome.
  • buyAmounts: An array of the current total conditional tokens bought for each outcome.

Mappings

mapping(bytes32 => QuestionData) public questions;

Stores QuestionData for each market, indexed by the question ID.

mapping(address => uint256) public userSpendings;

Tracks the total amount spent by each user across all markets.

mapping(address => uint256) public userRedeemed;

Tracks the total amount redeemed by each user across all markets.

mapping(address => EnumerableSet.Bytes32Set) private userOpenPositions;

Stores the set of open positions (represented by question IDs) for each user.

mapping(bytes32 => AnswerData) public answers;

Stores the AnswerData for each market, indexed by the question ID.

mapping(bytes32 => mapping(address => uint256)) public userBuyAmounts;

Tracks the amount spent by each user for each market.

Events

InitializerUpdated

event InitializerUpdated(address initializerAddress, bool allowed)
  • initializerAddress: The address of the initializer being updated.
  • allowed: A boolean indicating whether the address is being added (true) or removed (false) from the initializer set.

Emitted when an address is added to or removed from the initializer set. This occurs when the contract owner updates the list of addresses allowed to create new markets using the updateInitializers() function.

ProposerUpdated

event ProposerUpdated(address proposerAddress, bool allowed)
  • proposerAddress: The address of the proposer being updated.
  • allowed: A boolean indicating whether the address is being added (true) or removed (false) from the proposer set.

Emitted when an address is added to or removed from the proposer set. This occurs when the contract owner updates the list of addresses allowed to propose answers and resolve markets using the updateProposers() function.

AnswerProposed

event AnswerProposed(bytes32 questionId, uint256[] payouts, address proposer, string answerCid)
  • questionId: The unique identifier of the market.
  • payouts: An array representing the payout distribution for each outcome.
  • proposer: The address of the account proposing the answer.
  • answerCid: A content identifier (CID) for additional answer details, typically stored on IPFS.

Emitted when a proposer submits an answer for a market question. This event is triggered in the proposeAnswer() function.

MarketResolved

event MarketResolved(bytes32 questionId, uint256[] payouts, address resolver, string answerCid)
  • questionId: The unique identifier of the resolved market.
  • payouts: An array representing the final payout distribution for each outcome.
  • resolver: The address of the account that resolved the market.
  • answerCid: A content identifier (CID) for additional answer details, typically stored on IPFS.

Emitted when a market is resolved, either through the resolveMarket() function or the proposeAndResolve() function. This event signifies that the final outcome of a market has been determined.

FundingRecovered

event FundingRecovered(bytes32 questionId, uint256 amountRecovered, address recipient)
  • questionId: The unique identifier of the market from which funding was recovered.
  • amountRecovered: The amount of tokens recovered from the market.
  • recipient: The address receiving the recovered funds.

Emitted when an initializer recovers unused funding from a market after the market period has ended. This occurs in the recoverFundingToken() function.

BuyPosition

event BuyPosition(
address indexed wallet,
address indexed fpmmAddress,
bytes32 indexed questionId,
uint256 investmentAmount,
uint256 feeAmount,
uint256 outcomeIndex,
uint256 outcomeTokensBought
)
  • wallet: The address of the user buying the position.
  • fpmmAddress: The address of the Fixed Product Market Maker contract for this market.
  • questionId: The unique identifier of the market question.
  • investmentAmount: The amount of collateral tokens invested in the position.
  • feeAmount: The fee amount charged for the transaction.
  • outcomeIndex: The index of the outcome for which tokens were bought.
  • outcomeTokensBought: The number of outcome tokens received by the wallet.

Emitted when a user buys a position in a market. This event is triggered in various buy functions like buyPosition(), buyPositionWithSignature(), and buyPositionWithLocked().

RedeemPosition

event RedeemPosition(
address indexed wallet,
address indexed fpmmAddress,
bytes32 indexed questionId,
uint256[] indexSets,
uint256 totalPayout
)
  • wallet: The address of the user redeeming the position.
  • fpmmAddress: The address of the Fixed Product Market Maker contract for this market.
  • questionId: The unique identifier of the market question.
  • indexSets: An array of index sets representing the positions being redeemed.
  • totalPayout: The total amount of collateral tokens received from redeeming the position.

Emitted when a user redeems their position after a market has been resolved. This event is triggered in the redeemPosition() function.

SellPosition

event SellPosition(
address indexed wallet,
address indexed fpmmAddress,
bytes32 indexed questionId,
uint256 returnAmount,
uint256 outcomeIndex,
uint256 outcomeTokensSold
)
  • wallet: The address of the user selling the position.
  • fpmmAddress: The address of the Fixed Product Market Maker contract for this market.
  • questionId: The unique identifier of the market question.
  • returnAmount: The amount of collateral tokens received for selling the position.
  • outcomeIndex: The index of the outcome for which tokens were sold.
  • outcomeTokensSold: The number of outcome tokens sold.

Emitted when a user sells their position in a market. This event is triggered in the sellPosition() function.

Function Signatures and Descriptions

initialize

function initialize(address initialOwner) initializer public
  • initialOwner: The address that will be set as the owner of the contract.

Initializes the contract with the initial owner.

updateContracts

function updateContracts(
address conditionalTokensAddress,
address FPMMFactoryAddress,
address collateralTokenAddress,
address _onchainPointsAddress
) external onlyOwner
  • conditionalTokensAddress: The new address of the ConditionalTokens contract.
  • FPMMFactoryAddress: The new address of the FPMMFactory contract.
  • collateralTokenAddress: The new address of the collateral token (WPOP) contract.
  • _onchainPointsAddress: The new address of the OnchainPoints contract.

Updates the addresses of external contracts. This function allows the owner to update the addresses of the ConditionalTokens, FPMMFactory, collateral token (WPOP), and OnchainPoints contracts.

createMarket

function createMarket(
uint256 endTimestamp,
bytes32 questionId,
uint256 outcomeSlots,
uint256 fee,
uint256[] calldata distributionHints,
uint256 initialFunding
) external onlyInitializer payable returns (address)
  • endTimestamp: The timestamp when the market will end.
  • questionId: A unique identifier for the market question.
  • outcomeSlots: The number of possible outcomes for the market.
  • fee: The fee percentage for the market (in basis points).
  • distributionHints: An array of initial probability distribution hints for the market outcomes.
  • initialFunding: The amount of collateral to initially fund the market with.

Creates a new prediction market. This function:

  1. Prepares a condition in the ConditionalTokens contract
  2. Creates a new FixedProductMarketMaker (FPMM) for the market
  3. Adds initial funding to the FPMM
  4. Stores the market data in the questions mapping

Returns the address of the newly created FPMM.

getMarketData

function getMarketData(bytes32 questionId) external view returns (MarketData memory)
  • questionId: The unique identifier of the market to retrieve data for.

Retrieves comprehensive market data for a specific question, including question details, answer details (if proposed), current probabilities, and buy amounts.

buyPosition

function buyPosition(
bytes32 questionId,
uint256 outcomeIndex,
uint256 minOutcomeTokensToBuy,
address conditionTokensReceiver
) external payable
  • questionId: The unique identifier of the market to buy a position in.
  • outcomeIndex: The index of the outcome to buy tokens for.
  • minOutcomeTokensToBuy: The minimum number of outcome tokens to buy (slippage protection).
  • conditionTokensReceiver: The address that will receive the bought position tokens.

Allows users to buy a position in a market. This function:

  1. Wraps the sent POP into the collateral token (WPOP)
  2. Approves the FPMM to spend the collateral token
  3. Calls the FPMM's buyOnBehalf function to buy the position on behalf of the conditionTokensReceiver
  4. Updates user data and emits a BuyPosition event

buyPositionWithSignature

function buyPositionWithSignature(
bytes32 questionId,
uint256 outcomeIndex,
uint256 minOutcomeTokensToBuy,
OnchainPoints.Request calldata request,
bytes calldata signature
) external nonReentrant
  • questionId: The unique identifier of the market to buy a position in.
  • outcomeIndex: The index of the outcome to buy tokens for.
  • minOutcomeTokensToBuy: The minimum number of outcome tokens to buy (slippage protection).
  • request: A struct containing details of the spending request, including the amount to spend.
  • signature: The signature authorizing the spending of locked points.

Allows users to buy a position using locked points from the OnchainPoints contract. This function:

  1. Verifies the signature and spends the locked points.
  2. Converts the spent amount to collateral tokens (WPOP).
  3. Buys the position through the associated FPMM contract.
  4. Updates user data and emits a BuyPosition event.

buyPositionWithLocked

function buyPositionWithLocked(
bytes32 questionId,
uint256 outcomeIndex,
uint256 minOutcomeTokensToBuy,
uint256 amount
) external nonReentrant payable
  • questionId: The unique identifier of the market to buy a position in.
  • outcomeIndex: The index of the outcome to buy tokens for.
  • minOutcomeTokensToBuy: The minimum number of outcome tokens to buy (slippage protection).
  • amount: The amount of locked tokens to spend on the position.

Allows users to buy a position using a combination of locked points from the OnchainPoints contract and unlocked tokens (if enabled). This function:

  1. Checks if the user has sufficient locked points and/or unlocked tokens.
  2. Spends the required amount from locked points and, if necessary and allowed, from unlocked tokens.
  3. Converts the spent amount to collateral tokens (WPOP).
  4. Buys the position through the associated FPMM contract.
  5. Updates user data and emits a BuyPosition event.

This function provides flexibility for users to use their locked points and, optionally, their unlocked tokens to participate in markets. The use of unlocked tokens is controlled by the buyWithUnlockedEnabled flag.

sellPosition

function sellPosition(
bytes32 questionId,
uint256 returnAmount,
uint256 outcomeIndex,
uint256 maxOutcomeTokensToSell
) external
  • questionId: The unique identifier of the market to sell a position in.
  • returnAmount: The amount of collateral tokens to receive in return.
  • outcomeIndex: The index of the outcome to sell tokens for.
  • maxOutcomeTokensToSell: The maximum number of outcome tokens to sell (slippage protection).

Allows users to sell a position in a market. This function interacts with the FPMM to sell outcome tokens and return collateral to the user.

redeemPosition

function redeemPosition(bytes32 questionId, uint256[] memory indexSets) public
  • questionId: The unique identifier of the resolved market to redeem positions for.
  • indexSets: An array of index sets representing the positions to redeem.

Allows users to redeem their position after market resolution. This function interacts with the ConditionalTokens contract to redeem winning positions.

resolveMarket

function resolveMarket(bytes32 questionId) public
  • questionId: The unique identifier of the market to resolve.

Resolves a market based on the proposed answer. This function calls the ConditionalTokens contract to report the final payouts for the market.

proposeAnswer

function proposeAnswer(
bytes32 questionId,
uint256[] calldata payouts,
string calldata answerCid
) public onlyProposer
  • questionId: The unique identifier of the market to propose an answer for.
  • payouts: An array representing the payout distribution for each outcome.
  • answerCid: A content identifier (CID) for additional answer details, typically stored on IPFS.

Allows a proposer to submit an answer for a question. This function stores the proposed answer in the answers mapping.

proposeAndResolve

function proposeAndResolve(
bytes32 questionId,
uint256[] calldata payouts,
string calldata answerCid
) public onlyProposer
  • questionId: The unique identifier of the market to propose an answer for and resolve.
  • payouts: An array representing the payout distribution for each outcome.
  • answerCid: A content identifier (CID) for additional answer details, typically stored on IPFS.

Proposes an answer and resolves the market in one transaction. This function combines the functionality of proposeAnswer() and resolveMarket().

recoverFundingToken

function recoverFundingToken(bytes32 questionId, uint256[] calldata indexSets) external payable onlyInitializer
  • questionId: The unique identifier of the market from which to recover funding.
  • indexSets: An array of index sets representing the positions to redeem.

Allows an initializer to recover unused funding from a market after the market period has ended. This function:

  1. Removes funding from the associated FPMM contract.
  2. Redeems any remaining conditional tokens for the specified index sets.
  3. Converts the redeemed collateral tokens back to the native token (POP).
  4. Transfers the recovered funds to the caller (initializer).

This function can only be called by addresses in the initializer set and is typically used to reclaim liquidity after a market has concluded.

updateMinBuyAmount

function updateMinBuyAmount(uint256 _minBuyAmount) external onlyOwner
  • _minBuyAmount: The new minimum buy amount in wei.

Updates the minimum amount required to buy a position in a market. This function can only be called by the contract owner.

updateBuyWithUnlockedEnabled

function updateBuyWithUnlockedEnabled(bool _buyWithUnlockedEnabled) external onlyOwner
  • _buyWithUnlockedEnabled: A boolean indicating whether buying with unlocked tokens should be enabled.

Toggles the ability to buy positions using unlocked tokens. This function can only be called by the contract owner.

updateStopTradingBeforeMarketEnd

function updateStopTradingBeforeMarketEnd(uint256 _stopTradingBeforeMarketEnd) external onlyOwner
  • _stopTradingBeforeMarketEnd: The new duration (in seconds) before market end when trading should stop.

Updates the duration before a market's end time when trading will be halted. This function can only be called by the contract owner.

updateMaxBuyAmountPerQuestion

function updateMaxBuyAmountPerQuestion(uint256 _maxBuyAmountPerQuestion) external onlyOwner
  • _maxBuyAmountPerQuestion: The new maximum buy amount per question in wei.

Updates the maximum amount a user can spend on a single market question. This function can only be called by the contract owner.

updateSellEnabled

function updateSellEnabled(bool _sellEnabled) external onlyOwner
  • _sellEnabled: A boolean indicating whether selling positions should be enabled.

Toggles the ability to sell positions in markets. This function can only be called by the contract owner.

getRemainingBuyAmount

function getRemainingBuyAmount(bytes32 questionId, address spender) external view returns (uint256)
  • questionId: The unique identifier of the market.
  • spender: The address of the user to check the remaining buy amount for.

Returns the remaining amount a user can spend on a specific market, based on the maxBuyAmountPerQuestion limit.

getUserOpenPositions

function getUserOpenPositions(address user) public view returns (bytes32[] memory)
  • user: The address of the user to get open positions for.

Returns an array of question IDs representing the open positions for a given user.

getUniqueBuys

function getUniqueBuys(bytes32 questionId) external view returns (uint256)
  • questionId: The unique identifier of the market to get unique buys for.

Returns the number of unique buy transactions for a specific market.

updateProposers

function updateProposers(address[] calldata _proposers, bool[] calldata _status) external onlyOwner
  • _proposers: An array of addresses to update as proposers.
  • _status: An array of boolean values indicating whether each address should be added or removed as a proposer.

Updates the list of addresses allowed to propose answers for markets. This function can only be called by the contract owner.

updateInitializers

function updateInitializers(address[] calldata _initializers, bool[] calldata _status) external onlyOwner
  • _initializers: An array of addresses to update as initializers.
  • _status: An array of boolean values indicating whether each address should be added or removed as an initializer.

Updates the list of addresses allowed to create new markets. This function can only be called by the contract owner.

getProposers

function getProposers() external view returns (address[] memory)

Returns an array of all current proposer addresses.

getInitializers

function getInitializers() external view returns (address[] memory)

Returns an array of all current initializer addresses.

emergencyWithdraw

function emergencyWithdraw() external onlyOwner

Allows the contract owner to withdraw all balance from the contract in case of an emergency. This function can only be called by the contract owner.

Modifiers

onlyProposer

modifier onlyProposer();

Restricts function access to addresses in the proposer set. Used to limit who can propose answers for markets.

onlyInitializer

modifier onlyInitializer();

Restricts function access to addresses in the initializer set. Used to limit who can create new markets or recover funding.

nonReentrant

modifier nonReentrant();

Prevents reentrant calls to a function. This modifier is inherited from ReentrancyGuardUpgradeable and is used on several functions to prevent potential reentrancy attacks.

onlyOwner

modifier onlyOwner();

Restricts function access to the contract owner. This modifier is inherited from OwnableUpgradeable and is used on administrative functions to ensure only the owner can perform certain operations.

Usage Example: Creating and Interacting with a Market

// Create a new market
bytes32 questionId = keccak256("Will ETH price be above $2000 on Jan 1, 2025?");
uint256 endTimestamp = block.timestamp + 365 days;
uint256 outcomeSlots = 2; // Yes or No
uint256 fee = 2e15; // 0.2%
uint256[] memory distributionHints = new uint256[](2);
distributionHints[0] = 1;
distributionHints[1] = 1;
uint256 initialFunding = 1000 ether;

address fpmmAddress = predictionsOracle.createMarket{value: initialFunding}(
endTimestamp,
questionId,
outcomeSlots,
fee,
distributionHints,
initialFunding
);

// Buy a position
predictionsOracle.buyPosition{value: 1 ether}(questionId, 0, 0, msg.sender);

// Later: Propose an answer and resolve the market
uint256[] memory payouts = new uint256[](2);
payouts[0] = 1;
payouts[1] = 0;
predictionsOracle.proposeAndResolve(questionId, payouts, "QmAnswerCID");

// Redeem a winning position
uint256[] memory indexSets = new uint256[](1);
indexSets[0] = 1; // Representing the winning outcome
predictionsOracle.redeemPosition(questionId, indexSets);

This example demonstrates the full lifecycle of a prediction market, from creation to resolution and redemption.

The PredictionsOracle contract serves as the central hub of the prediction market system, coordinating between users, markets (FPMMs), and the underlying ConditionalTokens. It abstracts much of the complexity of interacting with these various components, providing a streamlined interface for creating and participating in prediction markets.