Automated Prediction Market
This article delves into two versions of Automated Prediction Market smart contracts, showcasing different approaches to improving prediction markets using reactive network.
Introduction
Prediction markets provide a way to forecast future events by aggregating collective knowledge. With blockchain advancements, these markets can now operate in a decentralized manner, ensuring transparency and eliminating intermediaries.
This article explores two distinct versions of Automated Prediction Market smart contracts, each offering unique insights and functionalities. The first version, by Prakhar Srivastava, uses `AutomatedPredictionMarket.sol` and `AutomatedPredictionReactive.sol` to integrate reactive components with traditional prediction market structures. The second version, by etchedheadplate, employs `PredictionMarket.sol`, `PriceOracle.sol`, and `ReactivePayout.sol` to reduce costs through event-based market closures and oracle interactions.
Smart Contracts Version One
Understanding the Components
The `AutomatedPredictionMarket.sol` contract manages market creation, participant entries, and outcome finalization. It holds bets and resolves outcomes.
The `AutomatedPredictionReactive.sol` contract reacts to events and external data, processing callbacks, and updating`AutomatedPredictionMarket.sol`.
Workflow
Market Creation: The process begins with creating a new prediction market via `AutomatedPredictionMarket.sol`. Participants can then stake their tokens on a specific outcome.
Event Subscription: `AutomatedPredictionReactive.sol` subscribes to relevant external events that might affect the outcome of the prediction market. This could include real-world data feeds, oracle responses, or other blockchain events.
Event Handling: When the subscribed event occurs, `AutomatedPredictionReactive.sol` processes the event data and triggers a callback to `AutomatedPredictionMarket.sol`, updating the market’s state based on the received data.
Outcome Finalization: After processing the event data, `AutomatedPredictionMarket.sol` finalizes the outcome of the prediction market. The contract determines the winning outcome and distributes rewards to participants who predicted correctly.
`AutomatedPredictionMarket`
This contract manages the creation of prediction markets, bet placement, and payout distribution.
Market Structure and State Variables
The `Market` struct represents an individual prediction market. It includes an `eventDescription`, possible `outcomes`, a mapping of `bets` for each participant, the total betting pool (`totalPool`), and flags for resolution (`isResolved` and `resolvedOutcome`). The `markets` mapping stores all the created markets, and `marketCount` keeps track of the total number of markets.
struct Market {
string eventDescription;
string[] outcomes;
mapping(address => uint256) bets;
uint256 totalPool;
bool isResolved;
string resolvedOutcome;
}
mapping(uint256 => Market) public markets;
uint256 public marketCount;
Market Creation
The `createMarket` function allows the creation of a new market. It takes the event description and possible outcomes as input, storing them in the `Market` struct and incrementing the `marketCount`.
function createMarket(string memory _eventDescription, string[] memory _outcomes) public {
Market storage market = markets[marketCount];
market.eventDescription = _eventDescription;
market.outcomes = _outcomes;
marketCount++;
}
Bet Placement
The `placeBet` function enables participants to place bets on a specific market and outcome. It requires the market not to be resolved yet, updates the participant's bet, and increases the market's total pool.
function placeBet(uint256 _marketId, uint256 _outcomeIndex) public payable {
Market storage market = markets[_marketId];
require(!market.isResolved, "Market already resolved");
market.bets[msg.sender] += msg.value;
market.totalPool += msg.value;
}
Payout Distribution
The `distributePayout` function handles the distribution of winnings after the market is resolved. It checks if the market is resolved and then processes the payouts according to the resolved outcome.
function distributePayout(uint256 _marketId) public {
Market storage market = markets[_marketId];
require(market.isResolved, "Market not resolved yet");
// Payout logic based on the resolvedOutcome
}
`AutomatedPredictionReactive`
This contract is responsible for automating the event resolution process using oracles and Reactive Smart Contracts (RSCs).
Event Subscription and Oracle Interaction
The `subscribeToEvent` function allows the contract to subscribe to external events, such as real-world outcomes, using RSCs. The `onEventReceived` function processes the event data once it's received, triggering the resolution of the corresponding market.
function subscribeToEvent(uint256 _marketId, bytes memory _eventPayload) public {
// Logic to subscribe to an external event using RSCs
}
function onEventReceived(bytes memory _eventData) public {
// Logic to handle the received event and update the market outcome
}
Market Resolution
The `resolveMarket` function marks a market as resolved and sets the `resolvedOutcome`. It then triggers the payout distribution process.
function resolveMarket(uint256 _marketId, string memory _outcome) internal {
Market storage market = markets[_marketId];
market.isResolved = true;
market.resolvedOutcome = _outcome;
// Trigger payout distribution
distributePayout(_marketId);
}
Smart Contracts Version Two
Traditional prediction markets involve high costs due to frequent oracle interactions. This version uses Reactive Smart Contracts to reduce costs and improve flexibility.
Understanding the Components
The `PredictionMarket.sol` contract handles deposits, stores predictions, and distributes payouts. The `PriceOracle.sol` contract emits events with price updates. The `ReactivePayout.sol` listens for price updates and triggers payouts based on the correct prediction.
Workflow
Market Initialization: The process begins with deploying the `AutomatedPredictionMarket.sol` contract. This contract sets up a new prediction market by defining initial parameters such as event details, bet types (e.g., binary outcomes), and the timing for placing bets and resolving the market.
User Participation: Participants engage with the `AutomatedPredictionMarket.sol` contract to place bets on the outcome of the event. Each user’s prediction, along with their stake, is recorded on the blockchain, ensuring transparency and immutability.
Price Oracle Integration: The `PriceOracle.sol` contract is responsible for continuously monitoring the event's outcome through off-chain data sources. External oracles provide this data, which is periodically updated in the `PriceOracle.sol` contract. This integration ensures that the market reflects the most current and accurate information about the event.
Outcome Determination: When the event concludes, the final outcome is pushed to the `AutomatedPredictionReactive.sol` contract by `PriceOracle.sol`. The `AutomatedPredictionReactive.sol` contract evaluates all placed bets against the outcome provided by the oracle. It determines the winning predictions and identifies the winners based on this outcome.
Payout Distribution: After the outcome is determined, the `ReactivePayout.sol` contract calculates and distributes rewards to the winning participants. Each winner receives their payout directly to their wallet, based on their initial bet and the total pool of funds. All transactions are recorded on-chain to ensure transparency and auditability.
Market Closure: Once payouts are distributed, the `AutomatedPredictionMarket.sol` contract marks the market as closed. This step prevents any further bets and archives the market data for future reference. The market closure process also triggers a callback that finalizes all outstanding transactions, ensuring that all participants receive their rightful payouts.
`PredictionMarket`
This contract manages the prediction market, allowing users to place bets on the future price direction (UP, DOWN) and handles payout distributions based on the outcome.
Market Structure and State Variable
The contract maintains separate arrays to store deposits for each prediction type: `upDeposits`, `downDeposits`, and `allDeposits`. Each deposit is stored as a `Deposit` struct, which includes the sender’s address, the amount of Ether deposited, and their prediction.
struct Deposit {
address sender;
uint256 amount;
string prediction;
}
Deposit[] public upDeposits;
Deposit[] public downDeposits;
Deposit[] public allDeposits;
Depositing Ether
Participants can deposit Ether along with their prediction (either “UP” or “DOWN”) using the `depositEther` function. The function checks that the deposited amount is exactly 0.001 Ether and that the prediction is valid. It then adds the deposit to the appropriate array based on the prediction and logs the deposit using the `DepositReceived` event.
function depositEther(string memory prediction) public payable {
require(msg.value == 0.001 ether, "Deposit must be equal to 0.001 SepETH");
require(compareStrings(prediction, UP) || compareStrings(prediction, DOWN), "Allowed predictions: UP, DOWN");
if (compareStrings(prediction, UP)) {
upDeposits.push(Deposit(msg.sender, msg.value, prediction));
} else {
downDeposits.push(Deposit(msg.sender, msg.value, prediction));
}
allDeposits.push(Deposit(msg.sender, msg.value, prediction));
emit DepositReceived(msg.sender, msg.value, prediction);
}
Payout Distribution
The `payoutPrediction` function is responsible for distributing the winnings to participants who made the correct prediction. It is restricted to be callable only by authorized addresses (either the deploying address or the Reactive Network Sepolia address). The function prevents reentrancy by using a lock and processes payouts based on the winning prediction. If there are no winners, all deposits are refunded.
function payoutPrediction(address /* RVM ID */, string memory winningPrediction) public onlyAuthorized() {
require(compareStrings(winningPrediction, UP) || compareStrings(winningPrediction, DOWN) || compareStrings(winningPrediction, DRAW), "Allowed predictions: UP, DOWN, DRAW");
require(!locked, "Reentrancy detected");
locked = true;
Deposit[] storage winningDeposits;
if (compareStrings(winningPrediction, UP)) {
winningDeposits = upDeposits;
} else if (compareStrings(winningPrediction, DOWN)) {
winningDeposits = downDeposits;
} else if (compareStrings(winningPrediction, DRAW)) {
winningDeposits = allDeposits;
}
uint256 winnerCount = winningDeposits.length;
if (winnerCount > 0) {
uint256 payoutAmount = address(this).balance / winnerCount;
for (uint256 i = 0; i < winnerCount; i++) {
payable(winningDeposits[i].sender).transfer(payoutAmount);
emit PayoutSuccessful(winningDeposits[i].sender, payoutAmount);
}
} else {
for (uint256 i = 0; i < allDeposits.length; i++) {
payable(allDeposits[i].sender).transfer(allDeposits[i].amount);
emit RefundProcessed(allDeposits[i].sender, allDeposits[i].amount);
}
}
delete upDeposits;
delete downDeposits;
delete allDeposits;
locked = false;
}
`PriceOracle`
This contract acts as a simple oracle that emits price update events. These events include the timestamp and the price, which are later used by the `ReactivePayout` contract. The `emitEvent` function triggers the `PriceUpdate` event with the provided timestamp and price data. This event is central to the reactive mechanisms used by the other contracts.
function emitEvent(uint256 _timestamp, uint256 _price) external {
emit PriceUpdate(_timestamp, _price);
}
`ReactivePayout`
This contract automates the payout process based on the price data received from the `PriceOracle`. It integrates with the Reactive Network to subscribe to price events and react accordingly. Upon deployment, the contract subscribes to the `PriceOracle` events. It uses the `react` function to handle the incoming events, determining the winner prediction based on the price change and triggering the payout process.
function react(
uint256 chain_id,
address _contract,
uint256 topic_0,
uint256 topic_1,
uint256 topic_2,
uint256 topic_3,
bytes calldata data,
uint256 /* block_number */,
uint256 /* op_code */
) external vmOnly {
emit Event(chain_id, _contract, topic_0, topic_1, topic_2, topic_3, data, ++counter);
receivedTimestamp = topic_1;
receivedPrice = topic_2;
if (receivedTimestamp > storedTimestamp) {
if (receivedPrice > storedPrice) {
winnerPrediction = "UP";
storedPrice = receivedPrice;
} else if (receivedPrice < storedPrice) {
winnerPrediction = "DOWN";
storedPrice = receivedPrice;
} else {
winnerPrediction = "DRAW";
storedPrice = receivedPrice;
}
bytes memory payload = abi.encodeWithSignature("payoutPrediction(address,string)", address(0), winnerPrediction);
emit Callback(chain_id, _callback, GAS_LIMIT, payload);
}
}
Conclusion
The exploration of these two Automated Prediction Market implementations reveals the diverse approaches to optimizing prediction markets with smart contracts. Prakhar Srivastava demonstrated how integrating reactive components with traditional market structures can improve automation and event handling. On the other hand, etchedheadplate presented a solution that utilizes external oracles and reactive mechanisms to manage predictions and payouts.
By combining insights from both versions, we gain a holistic understanding of how reactive smart contracts can be used to create automated prediction markets.
Join our Hackathon and bounty program today to start building with Reactive Smart Contracts and bring your innovative ideas to life on the Reactive Network!
About Reactive Network
The Reactive Network, pioneered by PARSIQ, ushers in a new wave of blockchain innovation through its Reactive Smart Contracts (RSCs). These advanced contracts can autonomously execute based on specific on-chain events, eliminating the need for off-chain computation and heralding a seamless cross-chain ecosystem vital for Web3’s growth.
Central to this breakthrough is the Inversion of Control (IoC) framework, which redefines smart contracts and decentralized applications (DApps) by imbuing them with unparalleled autonomy, efficiency, and interactivity. By marrying RSCs with IoC, Reactive Network is setting the stage for a transformative blockchain era, characterized by enhanced interoperability and the robust, user-friendly foundation Web3 demands.