Skip to main content

Conditional Token Framework

The Conditional Token Framework is a powerful and flexible system for creating and trading in prediction markets and complex financial products. Developed by Gnosis, it provides a modular way to define and trade on various types of events and outcomes. By incorporating the Conditional Token Framework, GPM enables the creation of complex prediction markets, including multi-outcome scenarios and combinatorial markets, while ensuring efficient settlement and token management. GPM uses a slightly modified version of the Conditional Token Framework to allow for integration with our PredictionOracle contract.

Key Concepts

  • Conditions: Represent questions about future events. For example, "Will the price of ETH be above $2000 on January 1, 2025?"

  • Outcomes: Possible answers to a condition. In the above example, the outcomes could be "Yes" and "No".

  • Outcome Slots: Numbered positions corresponding to each outcome. For a binary outcome, slot 0 might represent "No" and slot 1 "Yes".

  • Outcome Collections: Sets of outcomes across one or more conditions.

  • Positions: Tokens that represent a claim on a particular outcome or combination of outcomes.

  • Collateral Token: The base asset (usually an ERC20 token) used to back positions.

How It Works

  1. Condition Creation: An oracle creates a condition by specifying a question and the number of outcome slots.

  2. Position Minting: Users can lock collateral tokens to mint position tokens representing specific outcomes.

  3. Trading: These position tokens can be traded freely, representing the market's belief in various outcomes.

  4. Condition Resolution: The oracle reports the actual outcome when the event occurs.

  5. Redemption: Holders of winning position tokens can redeem them for the original collateral.

Advanced Features

  • Combining Conditions: Multiple conditions can be combined to create complex prediction markets.

  • Atomic Transactions: The framework allows for splitting, merging, and trading positions in single transactions.

  • Scalar Markets: By using many outcome slots, the framework can approximate continuous-value predictions.

info

For more detailed information, check out the Gnosis short primer article here.

ConditionalTokens Contract

Source Code: ConditionalTokens

The ConditionalTokens contract is an implementation of the Conditional Token Framework. It provides the core functionality described in the framework:

By using this contract, developers can create applications that leverage the full power of the Conditional Token Framework, from simple prediction markets to complex financial derivatives.

Data Structures

Mappings

mapping(bytes32 => uint[]) public payoutNumerators;

Stores condition resolution results as arrays of payout numerators for each outcome.

mapping(bytes32 => uint) public payoutDenominator;

Used in conjunction with payoutNumerators to calculate the actual payout fractions.

Example:

bytes32 conditionId = 0x1234...;
uint[] memory payouts = new uint[](3);
payouts[0] = 0; // 0% payout for outcome 0
payouts[1] = 7000; // 70% payout for outcome 1
payouts[2] = 3000; // 30% payout for outcome 2
payoutNumerators[conditionId] = payouts;

payoutDenominator[conditionId] = 10000; // Denominator for 100% (allows for 0.01% precision)

// To calculate the payout for outcome 1:
// payoutNumerators[conditionId][1] / payoutDenominator[conditionId] = 7000 / 10000 = 0.7 or 70%

In this example, the payoutNumerators and payoutDenominator work together to represent the payout fractions for each outcome of a condition. The denominator is typically set to a value like 10000 to allow for precise fractional payouts.

Events

ConditionPreparation

event ConditionPreparation(
bytes32 indexed conditionId,
address indexed oracle,
bytes32 indexed questionId,
uint outcomeSlotCount
);

Emitted when a new condition is prepared. This event is emitted in the prepareCondition function.

  • conditionId: Unique identifier for the condition (indexed).
  • oracle: Address of the oracle responsible for resolving the condition (indexed).
  • questionId: Identifier for the question associated with the condition (indexed).
  • outcomeSlotCount: Number of possible outcomes for the condition.

ConditionResolution

event ConditionResolution(
bytes32 indexed conditionId,
address indexed oracle,
bytes32 indexed questionId,
uint outcomeSlotCount,
uint[] payoutNumerators
);

Emitted when a condition is resolved. This event is emitted in the reportPayouts function.

  • conditionId: Unique identifier for the resolved condition (indexed).
  • oracle: Address of the oracle that resolved the condition (indexed).
  • questionId: Identifier for the question associated with the condition (indexed).
  • outcomeSlotCount: Number of possible outcomes for the condition.
  • payoutNumerators: Array of payout numerators for each outcome.

PositionSplit

event PositionSplit(
address indexed stakeholder,
IERC20 collateralToken,
bytes32 indexed parentCollectionId,
bytes32 indexed conditionId,
uint[] partition,
uint amount
);

Emitted when a position is split into multiple sub-positions. This event is emitted in the splitPosition function.

  • stakeholder: Address of the user splitting the position (indexed).
  • collateralToken: Address of the ERC20 token used as collateral.
  • parentCollectionId: ID of the parent collection (indexed).
  • conditionId: ID of the condition being split on (indexed).
  • partition: Array representing how the position is being split.
  • amount: Amount of tokens being split.

PositionsMerge

event PositionsMerge(
address indexed stakeholder,
IERC20 collateralToken,
bytes32 indexed parentCollectionId,
bytes32 indexed conditionId,
uint[] partition,
uint amount
);

Emitted when positions are merged. This event is emitted in the mergePositions function.

PayoutRedemption

event PayoutRedemption(
address indexed redeemer,
IERC20 indexed collateralToken,
bytes32 indexed parentCollectionId,
bytes32 conditionId,
uint[] indexSets,
uint payout
);

Emitted when a payout is redeemed. This event is emitted in the redeemPositions function.

  • redeemer: Address of the user redeeming the payout (indexed).
  • collateralToken: Address of the ERC20 token used as collateral (indexed).
  • parentCollectionId: ID of the parent collection (indexed).
  • conditionId: ID of the condition being redeemed.
  • indexSets: Array of index sets being redeemed.
  • payout: Amount of collateral tokens paid out.

Functions

prepareCondition

function prepareCondition(
address oracle,
bytes32 questionId,
uint outcomeSlotCount
) external;

Prepares a condition by initializing a payout vector associated with the condition.

  • oracle: The account assigned to report the result for the prepared condition.
  • questionId: An identifier for the question to be answered by the oracle.
  • outcomeSlotCount: The number of outcome slots which should be used for this condition. Must not exceed 256.

Emits a ConditionPreparation event.

reportPayouts

function reportPayouts(
bytes32 questionId,
uint[] calldata payouts
) external;

Called by the oracle to report the result of a condition.

  • questionId: The question ID the oracle is answering for.
  • payouts: The oracle's answer as an array of payout numerators.

Emits a ConditionResolution event.

splitPosition

function splitPosition(
IERC20 collateralToken,
bytes32 parentCollectionId,
bytes32 conditionId,
uint[] calldata partition,
uint amount
) external;

Splits a position. Positions are represented as ERC1155 tokens.

  • collateralToken: The address of the positions' backing collateral token.
  • parentCollectionId: The ID of the outcome collections common to the position being split and the split target positions.
  • conditionId: The ID of the condition to split on.
  • partition: An array of disjoint index sets representing a nontrivial partition of the outcome slots of the given condition.
  • amount: The amount of collateral or stake to split.

Emits a PositionSplit event.

mergePositions

function mergePositions(
IERC20 collateralToken,
bytes32 parentCollectionId,
bytes32 conditionId,
uint[] calldata partition,
uint amount
) external;

Merges separate positions into a combined position.

Emits a PositionsMerge event.

redeemPositions

function redeemPositions(
IERC20 collateralToken,
bytes32 parentCollectionId,
bytes32 conditionId,
uint[] calldata indexSets
) external;

Redeems positions after the outcome is determined.

  • collateralToken: The address of the positions' backing collateral token.
  • parentCollectionId: The ID of the outcome collections common to the position being redeemed.
  • conditionId: The ID of the condition to redeem positions for.
  • indexSets: An array of index sets to redeem positions for.

Emits a PayoutRedemption event.

getOutcomeSlotCount

function getOutcomeSlotCount(bytes32 conditionId) external view returns (uint);

Gets the outcome slot count of a condition.

  • conditionId: ID of the condition.
  • Returns: Number of outcome slots associated with a condition, or zero if condition has not been prepared yet.

getConditionId

function getConditionId(
address oracle,
bytes32 questionId,
uint outcomeSlotCount
) external pure returns (bytes32);

Constructs a condition ID from its parameters.

  • oracle: The account assigned to report the result for the prepared condition.
  • questionId: An identifier for the question to be answered by the oracle.
  • outcomeSlotCount: The number of outcome slots which should be used for this condition.
  • Returns: The computed condition ID.

getCollectionId

function getCollectionId(
bytes32 parentCollectionId,
bytes32 conditionId,
uint indexSet
) external view returns (bytes32);

Constructs an outcome collection ID from a parent collection and an outcome collection.

  • parentCollectionId: Collection ID of the parent outcome collection, or bytes32(0) if there's no parent.
  • conditionId: Condition ID of the outcome collection to combine with the parent outcome collection.
  • indexSet: Index set of the outcome collection to combine with the parent outcome collection.
  • Returns: The computed collection ID.

getPositionId

function getPositionId(
IERC20 collateralToken,
bytes32 collectionId
) external pure returns (uint);

Constructs a position ID from a collateral token and an outcome collection.

  • collateralToken: Collateral token which backs the position.
  • collectionId: ID of the outcome collection associated with this position.
  • Returns: The computed position ID.

Usage Examples

Preparing a Condition

bytes32 questionId = keccak256(abi.encodePacked("Will ETH price be above $2000 on Jan 1, 2025?"));
address oracle = 0x1234567890123456789012345678901234567890;
uint outcomeSlotCount = 2; // Yes or No

conditionalTokens.prepareCondition(oracle, questionId, outcomeSlotCount);

Reporting Outcome

bytes32 questionId = keccak256(abi.encodePacked("Will ETH price be above $2000 on Jan 1, 2025?"));
uint[] memory payouts = new uint[](2);
payouts[0] = 1; // Yes
payouts[1] = 0; // No

conditionalTokens.reportPayouts(questionId, payouts);

Splitting a Position

IERC20 collateralToken = IERC20(0x1234567890123456789012345678901234567890);
bytes32 parentCollectionId = bytes32(0);
bytes32 conditionId = 0x1234567890123456789012345678901234567890123456789012345678901234;
uint[] memory partition = new uint[](2);
partition[0] = 1; // Represents "Yes"
partition[1] = 2; // Represents "No"
uint amount = 100 ether;

conditionalTokens.splitPosition(collateralToken, parentCollectionId, conditionId, partition, amount);

Security Considerations

  1. Oracle Trust: The contract relies on trusted oracles to report outcomes. Ensure that oracle addresses are secure and controlled by trusted entities.

  2. Outcome Slot Limit: The contract limits the number of outcome slots to 256. Ensure that complex prediction markets do not exceed this limit.

  3. Reentrancy: While the contract uses OpenZeppelin's ReentrancyGuard, always be cautious when interacting with external contracts, especially when transferring tokens.

  4. Integer Overflow/Underflow: The contract uses Solidity 0.8.x, which includes built-in overflow checks. However, be cautious when performing calculations, especially with user-supplied input.

  5. Gas Limitations: Some functions, like splitPosition and mergePositions, can be gas-intensive, especially with a large number of outcomes. Be aware of potential block gas limits.