Market Resolution
Polymarket uses UMA for market resolution. UMA and CTF are connected via the contract UmaCtfAdapter. The adapter acts as the CTF's oracle and it requests for data / retrieves results from UMA's Optimistic Oracle.
Terminologies
Optimistic Oracle: An optimistic oracle is the protocol developed by UMA that allows anyone to ask questions and get answers. By optimistic it means answerers are assumed to be honest and answers are by default accepted (hence the oracle is "optimistic"). The reality is of course different, so it is possible to dispute an answer.
Yes or No Query: A UMA price identifier intended for plaintext binary questions. The answers must be one of YES (p2), NO (p1), UNKNOWN (p3) or TOO_EARLY_RESPONSE (p4).
Ancillary Data: Human-readable text that describes the question and the rules of resolution. Polymarket appends the function caller to the ancillary data and refers to it as the initializer.
Bond: A bond is required to be posted by proposers and disputers to prevent them from acting maliciously. The higher the stake, the higher the bond should be.
Reward: A reward is given to the result proposer. Similar to bonds, the higher the stake, the higher the reward should be. For Polymarket it ranges from 5 to 750 USDC.
Liveness: It is the amount of time that must have elapsed before a price proposal expires. When it expires, the price is considered final.
Price: Price is not the price of an asset in the traditional sense. It refers to the answer of a question. When Polymarket initializes a market, it makes a "price request" to UMA's Optimistic Oracle.
Initializing a question
The function initialize is called when Polymarket initializes a market. It does the following:
Saves the question on-chain
function _saveQuestion(
address creator,
bytes32 questionID,
bytes memory ancillaryData,
uint256 requestTimestamp,
address rewardToken,
uint256 reward,
uint256 proposalBond,
uint256 liveness
) internal {
questions[questionID] = QuestionData({
requestTimestamp: requestTimestamp,
reward: reward,
proposalBond: proposalBond,
liveness: liveness,
manualResolutionTimestamp: 0,
resolved: false,
paused: false,
reset: false,
refund: false,
rewardToken: rewardToken,
creator: creator,
ancillaryData: ancillaryData
});
}Prepares CTF condition with the generated question ID. The question ID is a keccak256 hash of the modified ancillary data.
Makes a price request to UMA.
Makes the price request event based (It forbids TOO_EARLY_RESPONSE and makes the price request automatically refund reward on dispute).
Makes Optimistic Oracle sends a
priceDisputedcallback on price dispute.
Sets bond and liveness
Technically the question can be instantly resolved, there isn't a timestamp that we must live pass to propose a price. If it is too early the answer will be disputed.
Price Proposal
A market's result is proposed to UMA's Optimistic Oracle by a proposer by calling the function proposePrice. The price request's state must be Requested (The price request must exist and must not have a proposed price already).
The function sets the proposer and the proposedPrice and it cannot be set again (Ok I lied, I will talk about dispute).
The expirationTime is set to the current timestamp + the price request's custom liveness. What that means is even after the price has been proposed, the price request isn't considered final and retrieving the price request status will not return Expired until the timestamp is past this expiration time. Polymarket's UMA CTF adapter can only report payouts to the CTF contract after expiration time.
When the proposer submits a price request, it has to pay for a bond. This is needed in order to prevent malicious proposers. If the proposer is malicious, a disputer can dispute the price request and confiscates the bond if the disputer turns out to be right.
Price Dispute
A price proposal can be disputed as long as it is not expired. It is done by calling disputePrice.
The disputer has to pay the same amount of bond and fee as the price proposer to enter into a dispute as the loser has to pay the winner. Without a bond on each side, the side that is not paying can act maliciously for free.
Half of the bond goes to the store contract and not to the future winner of the dispute. The reason given by UMA is
The part of the bond that doesn't go to the disputer in a successful dispute goes to the UMA
Storecontract. The reason the disputer does not get the full amount is to prevent a malicious proposer from making a bad proposal and then front-running an honest disputer to dispute themselves if they get caught.
When there is a dispute, the current price request is discarded and the Optimistic Oracle makes a new price request with the same data.
The original reward sent by Polymarket's UMA CTF adapter is refunded to the contract. The code inside the if statement is executed because refundOnDispute is set to true by UMA CTF adapter by calling setEventBased during its initial price request.
Finally, it makes a callback to Polymarket's UMA CTF adapter to notify the contract of the dispute. callbackOnPriceDisputed is set to true by UMA CTF adapter also by calling setCallbacks during the initial price request.
UMA CTF adapter is expected to implement the function priceDisputed that observes the OptimisticRequester interface.
The function refunds the question creator the original reward if the question is already resolved. This is normally not the case. It should only happen if the contract's admin has manually resolved the question.
The refund flag is set to true only if questionData's reset flag is true. This is also not the case for the first dispute. The refund flag is only set to true on reset, which happens as the last step of the function. The reset flag is only going to be set to true if there is a second dispute.
_reset creates a brand new price request and sets the reset flag to true, so the next dispute will see refund also being set to true. When the refund flag is on, the reward refunded by the Optimistic Oracle is held by the adapter contract and a natural/manual market resolution refunds the reward to the question's original creator.
The new price request has the same question ID.
If you go back to the reset/refund logic above, priceDisputed returns early if reset is already set to true. This means the adapter is not going to make a price request for the third time. In that case contract admin has to step in to resolve the question (The function used to be called emergencyResolve, it seems to have been renamed to resolveManually).
Resolution
To resolve a question, anyone can call the function resolve. Remember the adapter acts as a bridge between UMA's Optimistic Oracle and the CTF. It retrieves the result from UMA if it's available and constructs a CTF compatible payout array to settle score.
To check if the question is ready to be resolved, the adapter calls UMA Optimistic Oracle's hasPrice.
Price is available if the price request's state is Settled, Resolved or Expired.
It is settled if the price request has been officially settled by calling
settleorsettleAndGetPrice.It is resolved if there is a dispute and the dispute result is determined.
It is expired if there is a price proposal and the it is past the expiration time with no dispute, before any settlement is done.
If price is available, the adapter calls settleAndGetPrice to settle score and also determines who receives the payout.
If the price request expires uncontested, the oracle sends the proposer back the original bond, final fee and also the reward.
If the price request dispute is resolved, the winner (original proposer or the disputer) gets back a) their bond, b) the unburned portion of the loser's bond, c) their final fee and d) the reward.
It also sets the price request's resolvedPrice, which is used by the adapter to construct a payout array. The payout array is always of length 2 and the only valid values are [0,1], [1,0] and [1,1]. They corresponding to No winning, Yes winning and a draw.
There is a code comment on [1,1] being invalid for neg risk markets. We can talk about it more in the chapter on neg risk, but the general idea is neg risk markets have multiple connected questions and one of them must resolve to YES and the rest must resolve to NO.
This payout array is sent to the CTF contract for score settling. See the page Conditional Tokens Framework on how reportPayouts and redeemPositions work.
Last updated
