James Bachini

Prediction Markets Solidity

Prediction Markets

Prediction markets are decentralized platforms where participants can bet on the outcome of future events. For example, people could bet on the outcome of a presidential election or a sports game. In this tutorial, we will walk through a smart contract built on Ethereum, which allows users to create and participate in prediction markets.

The code consists of two contracts:

  • PredictionMarket.sol Handles the core functionality of placing bets and settling the market.
  • PredictionMarketFactory.sol Allows the permissionless creation of new markets.

We’ll explore how this works in detail and what each contract does. Before diving into the code, it’s assumed that you have some basic knowledge of Soldity and Ethereum.

Full code for this is open source at: https://github.com/jamesbachini/Prediction-Markets


PredictionMarket.sol

This contract represents a single prediction market where users can bet on the outcome of an event.

  • Oracle The person who creates the market and has the authority to settle it.
  • Bets Users can place bets on whether the outcome will be True or False.
  • Settlement After the event is concluded, the oracle determines the result (True, False, or Invalid), and participants can withdraw their winnings.
Enum: Outcome
enum Outcome { Undecided, TrueOutcome, FalseOutcome, InvalidOutcome }

The Outcome enum defines the possible states of the market:

  • Undecided: The outcome has not been decided yet.
  • TrueOutcome: The event resulted in “True”.
  • FalseOutcome: The event resulted in “False”.
  • InvalidOutcome: The market is invalid (e.g., if the event did not occur as expected).
Variables:
string public description;
address public oracle;
Outcome public marketOutcome;
bool public isSettled;

These are the main state variables:

  • description: A short description of the event (e.g., “Will it rain tomorrow?”).
  • oracle: The address of the market creator, who also acts as the oracle.
  • marketOutcome: The current outcome of the market.
  • isSettled: A boolean flag indicating if the market has been settled.
Mappings:
mapping(address => uint256) public trueBets;
mapping(address => uint256) public falseBets;
uint256 public totalTrueBets;
uint256 public totalFalseBets;

These mappings and variables keep track of the bets:

  • trueBets and falseBets: Maps each user to the amount they bet on the True or False outcome.
  • totalTrueBets and totalFalseBets: Keeps track of the total amount bet on each outcome.
Constructor:
constructor(string memory _description, address _oracle) {
description = _description;
oracle = _oracle;
marketOutcome = Outcome.Undecided;
isSettled = false;
}

The constructor initializes the market with the description and the oracle (market creator). By default, the outcome is Undecided and the market is not yet settled.

Placing a Bet on the True Outcome:

    function betTrue() external payable {
    require(!isSettled, "Market already settled");
    require(msg.value > 0, "Must send ETH to bet");

    trueBets[msg.sender] += msg.value;
    totalTrueBets += msg.value;

    emit BetPlaced(msg.sender, msg.value, true);
    }

    This function allows a user to place a bet on the True outcome:

    • It checks if the market is not settled and if the user has sent some ETH.
    • The user’s bet is added to the trueBets mapping, and the total True bets are updated.
    • An event BetPlaced is emitted.

    Placing a Bet on the False Outcome:

      function betFalse() external payable {
      require(!isSettled, "Market already settled");
      require(msg.value > 0, "Must send ETH to bet");

      falseBets[msg.sender] += msg.value;
      totalFalseBets += msg.value;

      emit BetPlaced(msg.sender, msg.value, false);
      }

      Similar to the betTrue function, this allows a user to bet on the False outcome.

      Settling the Market:

        function settleMarket(Outcome _outcome) external {
        require(msg.sender == oracle, "Only oracle can settle market");
        require(!isSettled, "Market already settled");
        require(
        _outcome == Outcome.TrueOutcome ||
        _outcome == Outcome.FalseOutcome ||
        _outcome == Outcome.InvalidOutcome,
        "Invalid outcome"
        );

        marketOutcome = _outcome;
        isSettled = true;

        emit MarketSettled(_outcome);
        }

        The settleMarket function allows the oracle to settle the market. It checks:

        • That the caller is the oracle.
        • The market is not already settled.
        • The outcome is valid (True, False, or Invalid).

        Once settled, it updates the marketOutcome and sets isSettled to true. The MarketSettled event is emitted.

        Withdrawing Winnings:

          function withdrawWinnings() external {
          require(isSettled, "Market not yet settled");
          uint256 payout = 0;

          if (marketOutcome == Outcome.TrueOutcome && trueBets[msg.sender] > 0) {
          payout = (address(this).balance * trueBets[msg.sender]) / totalTrueBets;
          trueBets[msg.sender] = 0;
          } else if (marketOutcome == Outcome.FalseOutcome && falseBets[msg.sender] > 0) {
          payout = (address(this).balance * falseBets[msg.sender]) / totalFalseBets;
          falseBets[msg.sender] = 0;
          } else if (marketOutcome == Outcome.InvalidOutcome) {
          uint256 totalBet = trueBets[msg.sender] + falseBets[msg.sender];
          payout = totalBet;
          trueBets[msg.sender] = 0;
          falseBets[msg.sender] = 0;
          } else {
          revert("No winnings to withdraw");
          }

          require(payout > 0, "No payout available");
          payable(msg.sender).transfer(payout);

          emit WinningsWithdrawn(msg.sender, payout);
          }

          After the market is settled, users can withdraw their winnings. The function calculates the user’s share based on the total bets:

          • If the user bet on the winning outcome, they get a proportional payout.
          • If the outcome was invalid, they get their original bet back.
          • The WinningsWithdrawn event is emitted after a successful withdrawal.

          PredictionMarketFactory.sol

          The second contract PredictionMarketFactory.sol allows users to create new prediction markets. Every time a user creates a new market a new contract is deployed.

          Variables:

          codeaddress[] public markets;

          This array stores the addresses of all created prediction markets.

          Functions:

          Creating a New Market

            createMarket(string memory _description) external {
            PredictionMarket market = new PredictionMarket(_description, msg.sender);
            markets.push(address(market));
            emit MarketCreated(msg.sender, address(market), _description);
            }

            This function allows anyone to create a new market. The new market is initialized with a description and sets the caller (msg.sender) as the oracle.

            Retrieving All Markets

              function getMarkets() external view returns (address[] memory) {
              return markets;
              }

              This function returns the list of all created prediction markets.

              Full code for this tutorial is available at: https://github.com/jamesbachini/Prediction-Markets


              Get The Blockchain Sector Newsletter, binge the YouTube channel and connect with me on Twitter

              The Blockchain Sector newsletter goes out a few times a month when there is breaking news or interesting developments to discuss. All the content I produce is free, if you’d like to help please share this content on social media.

              Thank you.

              James Bachini

              Disclaimer: Not a financial advisor, not financial advice. The content I create is to document my journey and for educational and entertainment purposes only. It is not under any circumstances investment advice. I am not an investment or trading professional and am learning myself while still making plenty of mistakes along the way. Any code published is experimental and not production ready to be used for financial transactions. Do your own research and do not play with funds you do not want to lose.