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
-
Condition Creation: An oracle creates a condition by specifying a question and the number of outcome slots.
-
Position Minting: Users can lock collateral tokens to mint position tokens representing specific outcomes.
-
Trading: These position tokens can be traded freely, representing the market's belief in various outcomes.
-
Condition Resolution: The oracle reports the actual outcome when the event occurs.
-
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.
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:
- Preparing conditions (prepareCondition)
- Splitting and merging positions (splitPosition, mergePositions)
- Redeeming positions (redeemPositions)
- Reporting outcomes (reportPayouts)
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.
- Parameters are the same as PositionSplit event.
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.
- Parameters are the same as splitPosition.
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
-
Oracle Trust: The contract relies on trusted oracles to report outcomes. Ensure that oracle addresses are secure and controlled by trusted entities.
-
Outcome Slot Limit: The contract limits the number of outcome slots to 256. Ensure that complex prediction markets do not exceed this limit.
-
Reentrancy: While the contract uses OpenZeppelin's
ReentrancyGuard
, always be cautious when interacting with external contracts, especially when transferring tokens. -
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.
-
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.