Skip to content

Commit

Permalink
Merge pull request #56 from Polymarket/fix/reset-on-too-early
Browse files Browse the repository at this point in the history
Fix: reset question if too early price is received from the DVM
  • Loading branch information
JonathanAmenechi authored Oct 25, 2022
2 parents d0ffe4a + b0dfa83 commit 3ad05fe
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 93 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,20 @@ When a new market is deployed, it is `initialized`, meaning:
2) The market is [`prepared`](https://github.com/Polymarket/conditional-tokens-contracts/blob/a927b5a52cf9ace712bf1b5fe1d92bf76399e692/contracts/ConditionalTokens.sol#L65) on the CTF contract
3) A resolution data request is sent out to the Optimistic Oracle

UMA Proposers will then respond to the request and fetch resolution data offchain. If the resolution data is not disputed, the data will be available to the Adapter after a defined liveness period(currently about 2 hours). If the proposal is disputed, UMA's [DVM](https://docs.umaproject.org/getting-started/oracle#umas-data-verification-mechanism) is the fallback and will return data after a 48 - 72 hour period.
UMA Proposers will then respond to the request and fetch resolution data offchain. If the resolution data is not disputed, the data will be available to the Adapter after a defined liveness period(currently about 2 hours).

If the proposal is disputed, UMA's [DVM](https://docs.umaproject.org/getting-started/oracle#umas-data-verification-mechanism) is the fallback and will return data after a 48 - 72 hour period.

After resolution data is available, anyone can call `resolve` which resolves the market using the resolution data.


## Deployments

See [current deployments](./deploys.md)
| Network | Address |
| ---------------- | --------------------------------------------------------------------------------- |
| Polygon | [0xB97455fcF78eb37375e8be6f26df895341CA073d](https://polygonscan.com/address/0xB97455fcF78eb37375e8be6f26df895341CA073d)|
| Mumbai | [0xB97455fcF78eb37375e8be6f26df895341CA073d](https://mumbai.polygonscan.com/address/0xB97455fcF78eb37375e8be6f26df895341CA073d)|


## Development

Expand Down
7 changes: 0 additions & 7 deletions deploys.md

This file was deleted.

1 change: 0 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
openzeppelin/=lib/openzeppelin-contracts/contracts/
openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/
forge-std/=lib/forge-std/src/
dev/=src/dev/
solmate/=lib/solmate/src/
68 changes: 36 additions & 32 deletions src/UmaCtfAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -97,20 +97,21 @@ contract UmaCtfAdapter is IUmaCtfAdapter, Auth, BulletinBoard, IOptimisticReques

/// @notice Checks whether a questionID is ready to be resolved
/// @param questionID - The unique questionID
function readyToResolve(bytes32 questionID) public view returns (bool) {
return _readyToResolve(questions[questionID]);
function ready(bytes32 questionID) public view returns (bool) {
return _ready(questions[questionID]);
}

/// @notice Resolves a question
/// Pulls price information from the OO and resolves the underlying CTF market.
/// Is only available after price information is available on the OO
/// Reverts if price is not available on the OO
/// Resets the question if the price returned by the OO is the Ignore price
/// @param questionID - The unique questionID of the question
function resolve(bytes32 questionID) external {
QuestionData storage questionData = questions[questionID];

if (questionData.paused) revert Paused();
if (questionData.resolved) revert Resolved();
if (!_readyToResolve(questionData)) revert NotReadyToResolve();
if (!_ready(questionData)) revert NotReadyToResolve();

// Resolve the underlying market
return _resolve(questionID, questionData);
Expand Down Expand Up @@ -139,8 +140,10 @@ contract UmaCtfAdapter is IUmaCtfAdapter, Auth, BulletinBoard, IOptimisticReques
bytes32 questionID = keccak256(ancillaryData);
QuestionData storage questionData = questions[questionID];

// Upon dispute, immediately reset the question, sending out a new price request
// paying for the price request from the Adapter's balance
if (questionData.reset) return;

// If the question has not been reset previously, reset the question
// Ensures that there are at most 2 OO Requests at a time for a question
_reset(address(this), questionID, questionData);
}

Expand Down Expand Up @@ -174,9 +177,7 @@ contract UmaCtfAdapter is IUmaCtfAdapter, Auth, BulletinBoard, IOptimisticReques
if (!_isInitialized(questionData)) revert NotInitialized();
if (_isFlagged(questionData)) revert Flagged();

questionData.adminResolutionTimestamp = block.timestamp + emergencySafetyPeriod;

// Flagging a question pauses it by default
questionData.emergencyResolutionTimestamp = block.timestamp + emergencySafetyPeriod;
questionData.paused = true;

emit QuestionFlagged(questionID);
Expand All @@ -203,7 +204,7 @@ contract UmaCtfAdapter is IUmaCtfAdapter, Auth, BulletinBoard, IOptimisticReques
if (payouts.length != 2) revert InvalidPayouts();
if (!_isInitialized(questionData)) revert NotInitialized();
if (!_isFlagged(questionData)) revert NotFlagged();
if (block.timestamp <= questionData.adminResolutionTimestamp) revert SafetyPeriodNotPassed();
if (block.timestamp < questionData.emergencyResolutionTimestamp) revert SafetyPeriodNotPassed();

questionData.resolved = true;
ctf.reportPayouts(questionID, payouts);
Expand Down Expand Up @@ -235,7 +236,7 @@ contract UmaCtfAdapter is IUmaCtfAdapter, Auth, BulletinBoard, IOptimisticReques
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////////*/

function _readyToResolve(QuestionData storage questionData) internal view returns (bool) {
function _ready(QuestionData storage questionData) internal view returns (bool) {
if (!_isInitialized(questionData)) return false;
// Check that the OO has an available price
return _hasPrice(questionData);
Expand All @@ -260,7 +261,7 @@ contract UmaCtfAdapter is IUmaCtfAdapter, Auth, BulletinBoard, IOptimisticReques
resolved: false,
paused: false,
reset: false,
adminResolutionTimestamp: 0
emergencyResolutionTimestamp: 0
});
}

Expand Down Expand Up @@ -317,25 +318,21 @@ contract UmaCtfAdapter is IUmaCtfAdapter, Auth, BulletinBoard, IOptimisticReques
/// @param questionID - The unique questionID
function _reset(address requestor, bytes32 questionID, QuestionData storage questionData) internal {
uint256 requestTimestamp = block.timestamp;
// Update the question parameters in storage
questionData.requestTimestamp = requestTimestamp;
questionData.reset = true;

// If the question has not been reset previously, reset the question
// Ensures that there are at most 2 OO Requests at a time for a question
if (!questionData.reset) {
// Update the question parameters in storage
questionData.requestTimestamp = requestTimestamp;
questionData.reset = true;

// Send out a new price request with the new request timestamp
_requestPrice(
requestor,
requestTimestamp,
questionData.ancillaryData,
questionData.rewardToken,
questionData.reward,
questionData.proposalBond
);
emit QuestionReset(questionID);
}
// Send out a new price request with the new timestamp
_requestPrice(
requestor,
requestTimestamp,
questionData.ancillaryData,
questionData.rewardToken,
questionData.reward,
questionData.proposalBond
);

emit QuestionReset(questionID);
}

/// @notice Resolves the underlying CTF market
Expand All @@ -347,6 +344,9 @@ contract UmaCtfAdapter is IUmaCtfAdapter, Auth, BulletinBoard, IOptimisticReques
yesOrNoIdentifier, questionData.requestTimestamp, questionData.ancillaryData
);

// If the OO returns the ignore price, reset the question
if (price == _ignorePrice()) return _reset(address(this), questionID, questionData);

// Construct the payout array for the question
uint256[] memory payouts = _constructPayouts(price);

Expand All @@ -366,7 +366,7 @@ contract UmaCtfAdapter is IUmaCtfAdapter, Auth, BulletinBoard, IOptimisticReques
}

function _isFlagged(QuestionData storage questionData) internal view returns (bool) {
return questionData.adminResolutionTimestamp > 0;
return questionData.emergencyResolutionTimestamp > 0;
}

function _isInitialized(QuestionData storage questionData) internal view returns (bool) {
Expand All @@ -379,7 +379,7 @@ contract UmaCtfAdapter is IUmaCtfAdapter, Auth, BulletinBoard, IOptimisticReques
// Payouts: [YES, NO]
uint256[] memory payouts = new uint256[](2);
// Valid prices are 0, 0.5 and 1
if (price != 0 && price != 0.5 ether && price != 1 ether) revert InvalidResolutionData();
if (price != 0 && price != 0.5 ether && price != 1 ether) revert InvalidOOPrice();

if (price == 0) {
// NO: Report [Yes, No] as [0, 1]
Expand All @@ -396,4 +396,8 @@ contract UmaCtfAdapter is IUmaCtfAdapter, Auth, BulletinBoard, IOptimisticReques
}
return payouts;
}

function _ignorePrice() internal pure returns (int256) {
return type(int256).min;
}
}
48 changes: 24 additions & 24 deletions src/interfaces/IOptimisticOracleV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,32 @@ pragma solidity 0.8.15;

import "openzeppelin-contracts/token/ERC20/IERC20.sol";

/// @title Optimistic Oracle V2
interface IOptimisticOracleV2 {
struct RequestSettings {
bool eventBased; // True if the request is set to be event-based.
bool refundOnDispute; // True if the requester should be refunded their reward on dispute.
bool callbackOnPriceProposed; // True if callbackOnPriceProposed callback is required.
bool callbackOnPriceDisputed; // True if callbackOnPriceDisputed callback is required.
bool callbackOnPriceSettled; // True if callbackOnPriceSettled callback is required.
uint256 bond; // Bond that the proposer and disputer must pay on top of the final fee.
uint256 customLiveness; // Custom liveness value set by the requester.
}
struct RequestSettings {
bool eventBased; // True if the request is set to be event-based.
bool refundOnDispute; // True if the requester should be refunded their reward on dispute.
bool callbackOnPriceProposed; // True if callbackOnPriceProposed callback is required.
bool callbackOnPriceDisputed; // True if callbackOnPriceDisputed callback is required.
bool callbackOnPriceSettled; // True if callbackOnPriceSettled callback is required.
uint256 bond; // Bond that the proposer and disputer must pay on top of the final fee.
uint256 customLiveness; // Custom liveness value set by the requester.
}

// Struct representing a price request.
struct Request {
address proposer; // Address of the proposer.
address disputer; // Address of the disputer.
IERC20 currency; // ERC20 token used to pay rewards and fees.
bool settled; // True if the request is settled.
RequestSettings requestSettings; // Custom settings associated with a request.
int256 proposedPrice; // Price that the proposer submitted.
int256 resolvedPrice; // Price resolved once the request is settled.
uint256 expirationTime; // Time at which the request auto-settles without a dispute.
uint256 reward; // Amount of the currency to pay to the proposer on settlement.
uint256 finalFee; // Final fee to pay to the Store upon request to the DVM.
}
// Struct representing a price request.
struct Request {
address proposer; // Address of the proposer.
address disputer; // Address of the disputer.
IERC20 currency; // ERC20 token used to pay rewards and fees.
bool settled; // True if the request is settled.
RequestSettings requestSettings; // Custom settings associated with a request.
int256 proposedPrice; // Price that the proposer submitted.
int256 resolvedPrice; // Price resolved once the request is settled.
uint256 expirationTime; // Time at which the request auto-settles without a dispute.
uint256 reward; // Amount of the currency to pay to the proposer on settlement.
uint256 finalFee; // Final fee to pay to the Store upon request to the DVM.
}

/// @title Optimistic Oracle V2 Interface
interface IOptimisticOracleV2 {
/// @notice Requests a new price.
/// @param identifier price identifier being requested.
/// @param timestamp timestamp of the price being requested.
Expand Down
8 changes: 4 additions & 4 deletions src/interfaces/IUmaCtfAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ struct QuestionData {
uint256 reward;
/// @notice Additional bond required by Optimistic oracle proposers/disputers
uint256 proposalBond;
/// @notice Admin Resolution timestamp, set when a market is flagged for admin resolution
uint256 adminResolutionTimestamp;
/// @notice Emergency resolution timestamp, set when a market is flagged for emergency resolution
uint256 emergencyResolutionTimestamp;
/// @notice Flag marking whether a question is resolved
bool resolved;
/// @notice Flag marking whether a question is paused
Expand Down Expand Up @@ -38,7 +38,7 @@ interface IUmaCtfAdapterEE {
error PriceNotAvailable();
error InvalidAncillaryData();
error NotOptimisticOracle();
error InvalidResolutionData();
error InvalidOOPrice();
error InvalidPayouts();

/// @notice Emitted when a questionID is initialized
Expand Down Expand Up @@ -86,5 +86,5 @@ interface IUmaCtfAdapter is IUmaCtfAdapterEE {

function getQuestion(bytes32) external returns (QuestionData memory);

function readyToResolve(bytes32) external view returns (bool);
function ready(bytes32) external view returns (bool);
}
Loading

0 comments on commit 3ad05fe

Please sign in to comment.