UMA Protocol
Search…
In depth tutorial: Event-based prediction market
In this section, we'll talk about the Event Based Prediction market contract, which you can find in the developer's quick-start repo. This tutorial will show how event-based OO data requests can be used in a binary prediction market.
You will find out how this smart contract works and how to test it and deploy it. Refer to the Optimistic Oracle V2 contract for additional information on the event-based price requests.

The Event-Based Prediction Market

This smart contract lets you set up prediction markets that are based on an event-based price request.
One of these price requests could be, "When X happens, will the price of Y equal Z?".
In addition, an event-based price request cannot expire early and automatically returns the requester's reward in the event of a dispute. By participating in the contract, users are issued long and short position tokens, which, when held, provide exposure to the prediction market. This contract's logic is a streamlined version of the LongShortPair with an event-based pricing request.

Development environment and tests

Install dependencies

To install dependencies, you will need to install the long-term support version of nodejs, currently nodejs v16, and yarn. You can then install dependencies by running yarn with no arguments:
yarn

Compiling your contracts

We will need to run the following command to compile the contracts and make the typescript interfaces so that they are easy to work with:
yarn hardhat compile

Contract design

Contract creation and initialization

The contract is created by setting up the prediction market with a series of parameters. With the parameters, you can choose what kind of collateral you want to use with _collateralToken. With _pairName, we can choose a name for the pair of long and short tokens. _customAncillaryData is where we define the price request question. The addresses of the _finder and _timerAddress set the rest of the contracts addresses we interact with. For reference, here is the full list of UMA contract deployments.
constructor(
string memory _pairName,
ExpandedERC20 _collateralToken,
bytes memory _customAncillaryData,
FinderInterface _finder,
address _timerAddress
) ...
Once the contract has been deployed, the owner can call initializeMarket() after approving the proposerReward amount to be paid to the wallet that resolves the price request. To keep things simple, proposerReward is set to 10e18.
Also observe how priceIdentifier is set to "YES_OR_NO_QUERY". This function sets up the prediction market by getting the proposer reward and calling _requestOraclePrice. This last function starts the price request in Optimistic Oracle V2 and sets up a number of options that are explained below.
function _requestOraclePrice() internal {
OptimisticOracleV2Interface optimisticOracle = getOptimisticOracle();
collateralToken.safeApprove(address(optimisticOracle), proposerReward);
optimisticOracle.requestPrice(
priceIdentifier,
expirationTimestamp,
customAncillaryData,
collateralToken,
proposerReward
);
// Set the Optimistic oracle liveness for the price request.
optimisticOracle.setCustomLiveness(
priceIdentifier,
expirationTimestamp,
customAncillaryData,
optimisticOracleLivenessTime
);
// Set the Optimistic oracle proposer bond for the price request.
optimisticOracle.setBond(
priceIdentifier,
expirationTimestamp,
customAncillaryData,
optimisticOracleProposerBond
);
// Make the request an event-based request.
optimisticOracle.setEventBased(priceIdentifier, expirationTimestamp, customAncillaryData);
// Enable the priceDisputed and priceSettled callback
optimisticOracle.setCallbacks(priceIdentifier, expirationTimestamp, customAncillaryData, false, true, true);
priceRequested = true;
}
_requestOraclePrice is in charge of initializing the price request in the Optimistic Oracle V2 by performing the following actions:
  1. 1.
    Create the price request with the above-mentioned parameters.
  2. 2.
    Define the custom liveness period that a proposed oracle response must sit through before being accepted as truth.
  3. 3.
    Specify the bond that the proposer and disputer must post in order to resolve a request.
  4. 4.
    Set the type of request to Event Based.
  5. 5.
    Turn on the priceSettled and priceDisputed callbacks so that our contract can use OO callbacks to respond to these kinds of events

Long and Short tokens creation and redemption

Any user can now call the create function with the tokensToCreate parameter to mint the same number of short and long tokens. Having both tokens in the same proportion means being in a neutral position, as is the case when calling create.
Holding only long tokens (by transferring short tokens to another wallet or adding liquidity to an AMM pair), gives exposition to the long position and vice versa.
function create(uint256 tokensToCreate) public requestInitialized {
collateralToken.safeTransferFrom(msg.sender, address(this), tokensToCreate);
require(longToken.mint(msg.sender, tokensToCreate));
require(shortToken.mint(msg.sender, tokensToCreate));
emit TokensCreated(msg.sender, tokensToCreate, tokensToCreate);
}
At any time a token holder with both tokens in the same proportion can exchange them for collateral with redeem.
function redeem(uint256 tokensToRedeem) public {
require(longToken.burnFrom(msg.sender, tokensToRedeem));
require(shortToken.burnFrom(msg.sender, tokensToRedeem));
collateralToken.safeTransfer(msg.sender, tokensToRedeem);
emit TokensRedeemed(msg.sender, tokensToRedeem, tokensToRedeem);
}
Any long-short token holder can settle tokens for collateral with settle if the oracle has processed the price request. The returned collateral amount is a function of longTokensToRedeem, shortTokensToRedeem, and settlementPrice.
function settle(uint256 longTokensToRedeem, uint256 shortTokensToRedeem)
public
returns (uint256 collateralReturned)
{
require(receivedSettlementPrice, "price not yet resolved");
require(longToken.burnFrom(msg.sender, longTokensToRedeem));
require(shortToken.burnFrom(msg.sender, shortTokensToRedeem));
// settlementPrice is a number between 0 and 1e18. 0 means all collateral goes to short tokens and 1e18 means
// all collateral goes to the long token. Total collateral returned is the sum of payouts.
uint256 longCollateralRedeemed = (longTokensToRedeem * settlementPrice) / (1e18);
uint256 shortCollateralRedeemed = (shortTokensToRedeem * (1e18 - settlementPrice)) / (1e18);
collateralReturned = longCollateralRedeemed + shortCollateralRedeemed;
collateralToken.safeTransfer(msg.sender, collateralReturned);
emit PositionSettled(msg.sender, collateralReturned, longTokensToRedeem, shortTokensToRedeem);
}

Price request lifecycle callbacks: priceSettled and priceDisputed

When the price request we set up above is settled in the Optimistic Oracle V2, the priceSettled function of this contract is invoked.
This function calculates and stores settlementPrice as 0, 0.5, or 1. This number is used in the settle function to calculate the collateral to pay in exchange of the long and short tokens.
function priceSettled(
bytes32 identifier,
uint256 timestamp,
bytes memory ancillaryData,
int256 price
) external {
OptimisticOracleV2Interface optimisticOracle = getOptimisticOracle();
require(msg.sender == address(optimisticOracle), "not authorized");
require(timestamp == expirationTimestamp, "different timestamps");
require(identifier == priceIdentifier, "same identifier");
require(keccak256(ancillaryData) == keccak256(customAncillaryData), "same ancillary data");
// Calculate the value of settlementPrice using either 0, 0.5e18, or 1e18 as the expiryPrice.
if (price >= 1e18) {
settlementPrice = 1e18;
} else if (price == 5e17) {
settlementPrice = 5e17;
} else {
settlementPrice = 0;
}
receivedSettlementPrice = true;
}
In the same way, this contract's priceDisputed function is called when a price request is disputed. This function re-starts the same price request with the bond amount that was given back to the requester, which in our case is the EventBasedPredictionMarket.
function priceDisputed(
bytes32 identifier,
uint256 timestamp,
bytes memory ancillaryData,
uint256 refund
) external {
OptimisticOracleV2Interface optimisticOracle = getOptimisticOracle();
require(msg.sender == address(optimisticOracle), "not authorized");
expirationTimestamp = getCurrentTime();
require(timestamp <= expirationTimestamp, "different timestamps");
require(identifier == priceIdentifier, "same identifier");
require(keccak256(ancillaryData) == keccak256(customAncillaryData), "same ancillary data");
require(refund == proposerReward, "same proposerReward amount");
_requestOraclePrice();
}

Tests

To execute the EventBasedPredictionMarket tests, run:
yarn test test/EventBasedPredictionMarket.ts

Deployment

Before deploying the contract check/edit the default arguments defined in the deployment script.
To deploy EventBasedPredictionMarket in Görli network, run:
yarn hardhat deploy --network goerli --tags EventBasedPredictionMarket
Copy link
Outline
The Event-Based Prediction Market
Development environment and tests
Contract design