UMA – oracle for the prediction market Polymarket

Before diving into UMA, it is worth noting that the oracle in Polymarket is the second important component of the service. We discussed the first part in the previous article. “Tokenization of the prediction market: Gnosis Conditional Token Framework”.

UMA is a decentralized oracle that specializes in recording any data on the blockchain, except for those that cannot be verified.

This oracle is called optimistic because it is believed that if the data is not challenged, then it is completely correct. It has its own arbitration system to resolve disputes.

Oracle provides data for projects such as cross-chain bridges, insurance protocols, prediction markets and derivatives.

The type of data can vary from cryptocurrency prices to sporting or political events. However, all data relates to the real world and can be verified at a specific point in time.

Here are a couple of examples of such data:

  1. Elon Musk will tweet about cryptocurrencies before August 31st.

  2. Trump will say the word “tampon” in his interview.

  3. There will be a monkeypox epidemic in 2024.

  4. The Solana token will be over $140 on August 23rd.

  5. Satoshi's pseudonym will be revealed in 2024.

How does the oracle work on fingers?

Let's look at a simplified process for an oracle to obtain data that can be trusted. By data we mean the examples described above. In the article we will call this data with which the oracle works by different terms: statement, statement, event, prediction, and so on. All of these terms describe data for different subject areas at different points in their life cycle.

According to the diagram, the process of supplying data to the oracle is as follows:

  1. Statement. The statement is added to the oracle along with the reward. It is assumed that whoever can challenge the claim will be able to claim the reward.

  2. Challenge period. Anyone can challenge the claim during this period. Or don't challenge it. If time runs out, the statement will be considered ready for final commit. This will mean that it is correct and can be trusted.

  3. Dispute. And yet, any participant in the protocol can challenge the statement in order to receive an award or fulfill their “civic duty.” This is a joke of course. In practice this happens quite rarely. According to game theory, the majority of participants behave fairly in the protocol ecosystem.

  4. Voting. If a dispute has been started, it is resolved by the holders of the UMA token. It is a protocol token that allows you to vote to resolve disputes and get rewarded for doing so.

  5. Settle. The last stage is the process of settling or the actual recording of data, after which we can assume that the proposed statement is guaranteed to be true.

Let's look at this process with an example.

Vasily, thank God not Terkin, knows that Spain won the Euro 2024 football tournament. The fact is quite well known and Vasily decides to teach the UMA oracle this. To do this, it transmits information about the event to the oracle. This is the stage statement.

Passes challenge period. Nobody disputed this because it makes no sense.

Then the last stage settle finally recorded in the oracle that Spain became the champion. Now the oracle can always respond to this event and produce a winner.

Vasily is inspired by the success and plans to transfer the entire Euro 2024 football results table.

But Vasily in this tournament was rooting for the French team, which took third place. And he decides to cheat by moving her to second place.

Disputer notices this and opens a dispute. I must say that both Vasily and disputer they put it on the line not only your reputation, but also some amount tokens.

The voting process went through, Vasily was unable to make a forgery, since the oracle’s arbitration system worked normally and gave the result that the event was incorrect.

Then at the last stage settledthe oracle gave part of Vasily’s tokens to the wrangler as a reward. Vasily lost his tokens, disputer multiplied.

This is roughly how we can describe the work of the UMA oracle in approving new data from the real world.

How does it work technically?

The UMA oracle allows smart contracts to quickly request and receive information. However, until the information becomes confirmed, a certain point in time will pass and the entire process of data verification will take place.

The following system components take part in the oracle operation:

  • Asserter – a participant who will publish some statement from the real world in the system. For example, “The yuan exchange rate today is equal to one dollar.”

  • Disputer – a participant who may doubt the statement that is published asserter.

  • DVM – software module for resolving disputes between asserter And disputer. However, the use of this module is optional and can be replaced by our own dispute resolution system.

  • voter – a participant who will decide with his own voice who is right, asserter or disputer.

Let's look at the diagram from the official documentation, which shows the interaction of all the system components described above.

According to the diagram, the UMA operating process is as follows:

  1. Asserter publishes some statement and transfers some amount of collateral in a token that is allowed by the protocol.

  2. Throughout challenge period disputer can open a dispute if he does not agree with the statement asserter. To initiate a dispute disputer also transfers the collateral to the protocol. If during the permitted period statement is not disputed, then it is considered true.

  3. After that in DVM the process of independent voting by all holders of the UMA token begins. This process includes the stages commit And revealwhen at first no one knows the votes of other participants, and then each participant reveals the result of his vote.

  4. After the voting process is completed, if you were right asserterthen he receives half of the deposit disputerthe second half remains in the protocol. If he wins disputerthen on the contrary, he takes half of the deposit asserter.

In order to voter was able to take part in the voting, it is necessary that his UMA token be staked on the protocol. This is a significant difference between DVM v1.0 and v2.0. The motivation for staking is the opportunity to receive rewards. In general, this is a standard story that is used by many protocols and UMA does not offer anything new here.

Voter takes part in voting at two stages: transferring a vote to a closed vote and revealing one’s vote. The so-called “commit/reveal” scheme, where at the first stage the result of hashing the vote is transferred to the smart contract, and then its disclosure. This is necessary to ensure that no one sees the voting results of the participants. It is believed that if participants do not know the voting result of others, this guarantees a truly independent vote.

There is also one very interesting mechanism called slashing. According to this mechanism, staked balances are redistributed from participants who do not take part in voting, or who do it incorrectly, to participants who vote correctly.

Here it is worth identifying the “right” and “wrong” participants. An incorrect participant is a participant who cast a vote and ends up in the minority group.

For example, the system offers the event “Zimbabwe will win exactly 10 gold medals at the Olympic Games.” An unlikely event, however, the stars aligned and Zimbabwe won exactly 10 gold medals. Everything seemed simple, we vote that the event is correct, but most of the participants, for some reason, apparently got to the source with an error, voted that the event did not occur, and thought that Zimbabwe did not win medals at all. In this case, even though we did everything honestly, we will be called an “incorrect” vote and subject to a process slashing.

It is worth noting once again that the wrong participant is the one who cast his vote along with the minority. However, the example is unlikely because the participants do not know the votes of others until the moment of disclosure; everyone votes in secret. Therefore, none of the voters will want to be in a minority group, which means they will vote correctly.

Thus, economic motivation to behave incorrectly in the system or sabotage the voting process is eliminated. In the documentation this is called a penalty for not participating in voting.

OOV3

OOV3 is the code name of the smart contract Optimistic Oracle V3. It will be necessary to integrate with this smart contract if there is a need to connect the UMA protocol.

During deployment, the smart contract constructor takes three parameters:

  • finder. A smart contract that stores the addresses of all current smart contracts of the protocol. Allows you to effectively use it in any third-party smart contract.

  • defaultCurrency. The token address in which the protocol will accept collateral.

  • defaultLiveness. The time during which it is possible to start a dispute regarding the statement.

constructor(
    FinderInterface _finder,
    IERC20 _defaultCurrency,
    uint64 _defaultLiveness
) {
    finder = _finder;
    // Устанавливаем _defaultCurrency и _defaultLiveness на смарт-контракт
    setAdminProperties(_defaultCurrency, _defaultLiveness, 0.5e18);
}

The main logic of the smart contract is simple and reflects the operation of the protocol in three steps:

  1. Assert truth. Offers an event/statement from the world to commit the statement

  2. Dispute assertion. Allows you to challenge the event

  3. Settle assertion. Records an event as true, accurate, or correct

There are two public functions to assert an event: assertTruthWithDefaults() And assertTruth(). In fact, the first calls the second with default parameters, so let’s look at the second function in a simplified form.

function assertTruth(
    bytes memory claim, // Набор байт кодированного утверждения
    address asserter, // Адрес инициатора утверждения, нужен для получения своего залога обратно
    address callbackRecipient, // Адрес дял обратного вызова
    address escalationManager, // Альтернативный менеджер разрешения спора
    uint64 liveness, // Время, в течении которого можно оспорить утверждение
    IERC20 currency, // Адрес токена в котором будет взиматься залог
    uint256 bond, // Размер залога, не должен быть меньше минимально разрешенного протоколом
    bytes32 identifier, // Идентификатор для DVM
    bytes32 domainId // Опциональный параметр, который позволяет объединять утверждения в группы
) public nonReentrant returns (bytes32 assertionId) {
    uint64 time = uint64(getCurrentTime());
    // Создается идентификатор для утверждения. По сути это хеш его параметров
    assertionId = _getId(claim, bond, time, liveness, currency, callbackRecipient, escalationManager, identifier);

    // Проверки возможности добавления утверждения
    require(asserter != address(0), "Asserter cant be 0");
    require(assertions[assertionId].asserter == address(0), "Assertion already exists");
    require(_validateAndCacheIdentifier(identifier), "Unsupported identifier");
    require(_validateAndCacheCurrency(address(currency)), "Unsupported currency");
    require(bond >= getMinimumBond(address(currency)), "Bond amount too low");

    // Сохраняет утверждение на смарт-контракте
    assertions[assertionId] = Assertion({
        // все параметры утверждения
        ...
    });

    // Эта часть определяет каким образом будет разрешаться спор. По дефолту,
    // если escalationManager указан, как address(0), то спор будет решаться через DVM протокола, иначе через escalationManager, который должен реализовывать специальный интерфейс
    {
        EscalationManagerInterface.AssertionPolicy memory assertionPolicy = _getAssertionPolicy(assertionId);
        // Система разрешающая споры должна быть активна (DVM или escalationManager)
        require(!assertionPolicy.blockAssertion, "Assertion not allowed");
        EscalationManagerSettings storage emSettings = assertions[assertionId].escalationManagerSettings;
        // Переприсвоение необходимо, чтобы использовались настройки напрямую из escalationManager
        (emSettings.arbitrateViaEscalationManager, emSettings.discardOracle,
        emSettings.validateDisputers) = (
            assertionPolicy.arbitrateViaEscalationManager,
            assertionPolicy.discardOracle,
            assertionPolicy.validateDisputers
        );
    }

    // Забирает залог у вызывающего и переводит на смарт-контракт
    currency.safeTransferFrom(msg.sender, address(this), bond);
    ...
}

After the approval is created, the countdown begins during which it is possible to open a dispute. To do this, anyone who disagrees needs to call the function disputeAssertion(). This is a small function, let's take a closer look at it.

function disputeAssertion(bytes32 assertionId, address disputer) external nonReentrant {
    // Проверка возможности создавать спор
    require(disputer != address(0), "Disputer can't be 0");
    Assertion storage assertion = assertions[assertionId];
    require(assertion.asserter != address(0), "Assertion does not exist");
    require(assertion.disputer == address(0), "Assertion already disputed");
    // Проверка того, что время открытия спора не истекло
    require(assertion.expirationTime > getCurrentTime(), "Assertion is expired");
    // Проверка, что есть система, которая будет разрешать спор (DVM или escalation manager)
    require(_isDisputeAllowed(assertionId), "Dispute not allowed");

    // Устанавливает адрес спорщика для утверждения
    assertion.disputer = disputer;

    // Взимает залог у спорщика
    assertion.currency.safeTransferFrom(msg.sender, address(this), assertion.bond);

    // Сообщает DVM о том, что пора запускать механизм голосования для разрешения спора
    _oracleRequestPrice(assertionId, assertion.identifier, assertion.assertionTime);

    // Делает обратный вызов для адреса callbackRecipient, который был установлен вместе с утверждением
    _callbackOnAssertionDispute(assertionId);
    ...
    emit AssertionDisputed(assertionId, msg.sender, disputer);
}

Once the time for dispute has passed, the statement can be finally recorded for public use. To do this you need to call the function settleAssertion(). Let's look at it in more detail.

function settleAssertion(bytes32 assertionId) public nonReentrant {
  Assertion storage assertion = assertions[assertionId];

  // Проверки возможности вызывать settleAssertion()
  require(assertion.asserter != address(0), "Assertion does not exist");
  require(!assertion.settled, "Assertion already settled");

  // Устанавливает флаг, что утверждение settled()
  assertion.settled = true;

  // Если спор не был начат
  if (assertion.disputer == address(0)) {
      // Дожидаемся, чтобы прошло время, когда еще можно начать спор
      require(assertion.expirationTime <= getCurrentTime(), "Assertion not expired");

      assertion.settlementResolution = true;
      // Возвращаем залог обратно аккаунту создавшему утверждение
      assertion.currency.safeTransfer(assertion.asserter, assertion.bond);

      // Делаем обратный вызов на адресе callbackRecipient, который создается вместе с утверждением
      _callbackOnAssertionResolve(assertionId, true);

      emit AssertionSettled(assertionId, assertion.asserter, false, true, msg.sender);
  } else {
      // Если спор все-таки был, то запрашиваем его результат
      int256 resolvedPrice = _oracleGetPrice(assertionId, assertion.identifier, assertion.assertionTime);

      // Записываем результат спора
      if (assertion.escalationManagerSettings.discardOracle) assertion.settlementResolution = false;
      else assertion.settlementResolution = resolvedPrice == numericalTrue;

      // Решает, кто будет получать залог обратно
      address bondRecipient = resolvedPrice == numericalTrue ? assertion.asserter : assertion.disputer;

      // Рассчитываем комиссию оракулу
      uint256 oracleFee = (burnedBondPercentage * assertion.bond) / 1e18;
      // Рассчитываем размер суммы получателю залога
      uint256 bondRecipientAmount = assertion.bond * 2 - oracleFee;

      // Отправляем комиссию
      assertion.currency.safeTransfer(address(_getStore()), oracleFee);
      // Отправляем залог получателю
      assertion.currency.safeTransfer(bondRecipient, bondRecipientAmount);

      // Делаем обратный вызов для адреса callbackRecipient, который был установлен вместе с утверждением
      if (!assertion.escalationManagerSettings.discardOracle)
          _callbackOnAssertionResolve(assertionId, assertion.settlementResolution);

      emit AssertionSettled(assertionId, bondRecipient, true, assertion.settlementResolution, msg.sender);
  }
}

DVM

It's time to become more familiar with the dispute resolution system DVM. In the repository, this part of the code is in a separate folder called data-verification-mechanism.

The main contract from which it is convenient to start disassembling the system is called Voting.sol. Below we will analyze its second version: smart contract VotingV2.sol.

This is already a larger system than OOV3, but we will try to cover the most important points. At the center of the scheme is the smart contract itself VotingV2.sol and the interfaces from which it inherits are listed: OracleInterface, OracleAncillaryInterface, OracleGovernanceInterface, VotingV2Interface.

In order for UMA token holders to vote in disputes, they need to stake these tokens. A separate smart contract is responsible for this Staker.solfrom which VotingV2.sol inherited. It is marked in green on the diagram.

Smart contracts that are used inside the main one are marked either yellow or orange. VotingV2.sol. All of them somehow connect additional logic. For some of them you need to pass their addresses to the constructor VotingV2.sol when deployed, which of course implies their separate, premature deployment. Let's list and say what each of them is responsible for:

  1. VoteTiming.sol. Defines time intervals for voting by UMA token holders. It would be more correct to say the order of commit/reveal operations. We remember that voting takes place in two stages. First, voters send a hash of their vote, and then they reveal that hash.

  2. Finder.sol. Responsible for storing addresses of other smart contracts that are used throughout the protocol and beyond.

  3. FixedSlashSlashingLibrary.sol. Responsible for the redistribution of the UMA token between honest and dishonest participants in favor of the former.

  4. VotingToken.sol.. This is the UMA protocol token. His address is sent to VotingV2.sol along with the deployment. This is what users must stake to gain access to voting.

  5. ResultComputationV2. A very important library that deals with vote counting.

  6. Previous voting contract. Address of the previous version of the smart contract Voting.sol. It is used only to receive rewards from the old version or information stored on it.

The purple color in the diagram lists the groups of smart contract functions that the smart contract implements Voting.sol:

  • Voting functions

  • Voting getter functions

  • Price request and access functions

  • Owner admin functions

  • Staking functions

  • Migrating support functions

  • Private and internal functions

I think there is no point in describing here what functions are done in each group. The names speak for themselves.

Knowing all this, you can rummage through the smart contract yourself VotingV2.sol.

Slashing

We should also talk about the mechanism slashing. We have already identified it as the main mechanism for encouraging UMA token holders to participate in dispute resolution.

To understand how this mechanism works, you need to find its beginning in the code. It is convenient to consider it from the internal function _updateTrackers() in the smart contract VotingV2.sol. Let's look at her.

// voter - адрес голосующего участника
function _updateTrackers(address voter) internal override {
    // Подготавливает список запросов на голосование, дополняет список тех по которым голосование прошло, чтобы можно было посмотреть участвовал ли в них voter
    processResolvablePriceRequests();

    // Делает slashing для конкретного адреса voter
    _updateAccountSlashingTrackers(voter, UINT64_MAX);

    // Вызывает функцию, которая была переопределена. Физически она находится в смарт-контракте Staker.sol
    super._updateTrackers(voter);
}

This function is used in many places in the code. But if you look closely, most places are user action: stake(), unstake(), withdrawRewards(). There are a couple more places: as they say, find them yourself.

It turns out that any action of the voter will do for him slashing. And he will no longer be able to avoid it. That is, it will not be possible to stake tokens, accumulate rewards for staking without participating in voting, and then withdraw the reward. At the time of withdrawal, the user himself, through his own transaction, will start slashing for himself and receive a fine. The only way to avoid a fine is to vote and do it honestly.

In addition, the protocol makes it possible for anyone and for anyone to launch slashing at any time. That is, potentially, even just sitting out until better times, when the protocol abolishes slashing through DAO, will not work. Someone will definitely start slashing for such a user. To do this, just call the public function updateTrackers(). But under the hood it will call the internal function of the same name.

function updateTrackers(address voter) external {
    _updateTrackers(voter);
}

If you don’t want to run the user through the entire voting history, you can use a function that will allow you to determine the maximum number of events in depth for slashing. The function is responsible for this updateTrackersRange().

It remains to summarize when the user gets into slashing. Let's look at the diagram.

The only thing we haven't covered is how the slashing code is written. I'm leaving this as homework. Hint: watch the function _updateAccountSlashingTrackers().

Important! We mentioned the library earlier FixedSlashSlashingLibrary.sol. Upon closer examination, one can understand that it is only responsible for the amount of token that will be withdrawn from the user if he receives a fine for non-participation in voting or for incorrect voting.

Build with UMA

UMA offers several example projects that show use cases for the protocol:

  • Prediction market. Implements a prediction market where predictions are verified using Optimistic Oracle V3.

  • Insurance contract. Implements an insurance contract that allows you to obtain one policy with the opportunity to receive payment in the event of an insured event.

  • A generic data assertion framework. Allows you to make statements on arbitrary data, confirming them through Optimistic Oracle V3.

Let's look at the most interesting example “Prediction market”. I chose this particular example because I know one popular service among prediction markets Polymarketwhich uses the UMA protocol.

How does the prediction market work? The service is filled with predictions with the ability to vote for one of its outcomes. For example: “Tomorrow Bitcoin will cost $100,000.” The user has the right to agree or disagree with this statement and vote for a positive or negative outcome.

You can look at it as a betting service. We bet on a certain outcome of an event. When the event happens, the bet will either win and we will make money, or we will all lose. Usually a prediction has two or three outcomes, but there are more. For each outcome, an interchangeable token is created. In order to place a bet, we need to buy this token. The resulting token can be exchanged on other sites for a more well-known token if it is listed there. Often the opportunity for exchange is implemented directly in the prediction service itself.

Let's start right off the bat. Let's look at constructor:

constructor(
    address _finder,
    address _currency,
    address _optimisticOracleV3
) {
    // Устанавливаем адреса:
    // finder - для обращения к нужным контрактам UMA
    // currency - токен в котором будет залог
    // optimisticOracleV3 - адрес оракула
    finder = FinderInterface(_finder);
    // Проверяем, разрешает ли UMA использовать этот токен в качестве залога
    require(_getCollateralWhitelist().isOnWhitelist(_currency), "Unsupported currency");
    currency = IERC20(_currency);
    oo = OptimisticOracleV3Interface(_optimisticOracleV3);
    defaultIdentifier = oo.defaultIdentifier();
}

The main task of the prediction market is to create various binary statements where users can leave their opinion whether they “agree” or “disagree” with the statement. Such things in a smart contract are called possible outcomes of a statement or prediction. To store statements there is a special mapping called markets.

struct Market {
    bool resolved; // True, если утверждение проверено и может быть settled
    bytes32 assertedOutcomeId; // Хеш всевозможных исходов (outcome1, outcome2 or unresolvable).
    ExpandedIERC20 outcome1Token;
    ExpandedIERC20 outcome2Token;
    uint256 reward; // Вознаграждение за утверждение истинного результата
    uint256 requiredBond; // Ожидаемый залог для добавления утверждения в UMA оракул
    bytes outcome1; // Короткое название первого исхода
    bytes outcome2; // Короткое название второго исхода
    bytes description; // Описание предсказания
}

mapping(bytes32 => Market) public markets;

Knowing which data structures describe the prediction market, you can look at the function initializeMarket()which creates markets on a smart contract.

function initializeMarket(
    string memory outcome1, // Название первого исхода
    string memory outcome2, // Название второго исхода
    string memory description, // Описание рынка
    uint256 reward, // Вознаграждение для asserter
    uint256 requiredBond // Залог для оркула UMA
) public returns (bytes32 marketId) {
    ...

    // Создание идентификатора для рынка предсказаний
    marketId = keccak256(abi.encode(block.number, description));

    ...

    // Создание записи о рынке
    markets[marketId] = Market({
        resolved: false,
        assertedOutcomeId: bytes32(0),
        outcome1Token: outcome1Token,
        outcome2Token: outcome2Token,
        reward: reward,
        requiredBond: requiredBond,
        outcome1: bytes(outcome1),
        outcome2: bytes(outcome2),
        description: bytes(description)
    });

    // Пополнение контракта на сумму вознаграждения
    if (reward > 0) currency.safeTransferFrom(msg.sender, address(this), reward);

    emit MarketInitialized(
        marketId,
        outcome1,
        outcome2,
        description,
        address(outcome1Token),
        address(outcome2Token),
        reward,
        requiredBond
    );
}

The next step, after the prediction market has been created and users have already placed their bets for or against and the predicted event has occurred, it is necessary to add the expected outcome to the UMA protocol in order to validate the result. Our smart contract for this implements the function assertMarket().

// assertedOutcome - это один из исходов предсказания, которое вызывающий функцию считает верным
function assertMarket(bytes32 marketId, string memory assertedOutcome) public returns (bytes32 assertionId) {
    Market storage market = markets[marketId];
    require(market.outcome1Token != ExpandedIERC20(address(0)), "Market does not exist");

    // Хеш исхода будет являться идентификатором
    bytes32 assertedOutcomeId = keccak256(bytes(assertedOutcome));
    require(market.assertedOutcomeId == bytes32(0), "Assertion active or resolved");
    // Полученный хеш обязательно должен равняться хешу одного из исходов маркета
    require(
        assertedOutcomeId == keccak256(market.outcome1) ||
            assertedOutcomeId == keccak256(market.outcome2) ||
            assertedOutcomeId == keccak256(unresolvable),
        "Invalid asserted outcome"
    );

    market.assertedOutcomeId = assertedOutcomeId;
    // Получаем размер залога, который требует оракул UMA
    uint256 minimumBond = oo.getMinimumBond(address(currency));
    uint256 bond = market.requiredBond > minimumBond ? market.requiredBond : minimumBond;
    // Генерируем утверждение, которое будет отправлено в UMA для проверки
    bytes memory claim = _composeClaim(assertedOutcome, market.description);

    // Переводим залог с вызывающего транзакцию на смарт-контракт
    currency.safeTransferFrom(msg.sender, address(this), bond);
    currency.safeApprove(address(oo), bond);
    // Отправляем утверждение в оракул UMA
    assertionId = _assertTruthWithDefaults(claim, bond);

    // Сохраняем идентификатор утверждения и идентификатор соответствующего маркета
    assertedMarkets[assertionId] = AssertedMarket({ asserter: msg.sender, marketId: marketId });

    emit MarketAsserted(marketId, assertedOutcome, assertionId);
}

A reasonable question may arise here: how are we now on a smart contract? PredictionMarket.sol We will know when the oracle calculates the event and is ready to tell us that our statement is true. Time to remember about the callbacks that the oracle can do. Therefore, it is enough for us to implement one of these callbacks, called assertionResolvedCallback(). When the statement is verified by the oracle, it will issue a callback.

function assertionResolvedCallback(bytes32 assertionId, bool assertedTruthfully) public {
    // Проверяем, что вызывающий является оракулом
    require(msg.sender == address(oo), "Not authorized");
    Market storage market = markets[assertedMarkets[assertionId].marketId];

    if (assertedTruthfully) {
        // Если исход подтвержден, то считать рынок рассчитанным
        market.resolved = true;
        // Если кому-то за этот рынок предназначалось вознаграждение, то отправить его
        if (market.reward > 0) currency.safeTransfer(assertedMarkets[assertionId].asserter, market.reward);
        emit MarketResolved(assertedMarkets[assertionId].marketId);
    } else market.assertedOutcomeId = bytes32(0); // В противном случае ничего не делать, необходимо начинать процесс утверждения заново

    // Удаляем информацию о том, что рынок утверждается оракулом
    delete assertedMarkets[assertionId];
}

The entire order of calls that we described above can be presented in the diagram for consolidation.

This is the main logic of smart contract interaction PredictionMarket.sol with the oracle UMA ends. We have not considered and deliberately omitted the story that concerns the operation of the prediction market itself. Often a fungible token is created for each market outcome. When users choose an outcome, they essentially buy these tokens under the hood and, moreover, can exchange it (this functionality is outside the scope of the example). The beginnings of this logic are also in our example contract. The following functions are responsible for this: createOutcomeTokens(), redeemOutcomeTokens(), settleOutcomeTokens().

Participation as a voter

Previously, in order to gain the right to take part in the vote, it was enough to keep UMA tokens in your wallet. The protocol used a snapshot mechanism that recorded user balances, and this was the “entrance ticket.” In the new version, to access voting it is already necessary to stake UMA tokens on the smart contract of the protocol.

At the time of writing, users are offered an APR of 30.1%. But this does not mean that you can stake and passively receive rewards. We remember that the system has a slashing mechanism that can redistribute staker balances.

According to the documentation, each voting period is 48 hours:

Staking rewards accumulate continuously. You can pick them up at any time.

However, there is a nuance when you need to unstake tokens back. You will have to first make an application, after which you need to wait a period of time, the tokens will not bring rewards, and only after that, you can pick up the staked tokens in a separate transaction.

Important! At the time of writing, the penalty for missing a vote is 0.05% of the staked balance.

Governance

The UMA protocol token, in addition to being needed for voting when approving real-world events, is also used to manage the protocol.

The voting process is divided into two stages: commit and reveal votes and lasts 48 hours. This is already familiar to us.

The protocol came up with its own standard of proposals in the image and likeness of EIP in Ethereum. The proposals are called UMIPs (UMA Improvement Proposals).

It is worth noting that the protocol uses a progressive approach to voting. What does this mean? This means that voting is not immediately carried out on-chain, but starts from a special service snapshot.org. As a result, the voting looks like this:

  1. Post to Discourse. The first step is to place an offer in “https://discourse.uma.xyz/“. The proposal describes the key idea. At this stage, the community has the opportunity to discuss the proposal.

  2. Snapshot Vote. When the proposal is ready, the author creates a snapshot vote lasting five days.

  3. On-chain Vote. If the voting in the snapshot has a positive result, then the voting is carried out on-chain.

At the time of writing protocol in snapshot has only 71 participants.

Conclusion

The optimistic UMA oracle is guaranteed to stand apart from other oracles. In the classical model, oracles try to eliminate the human factor as much as possible, introducing decentralization in data aggregation and determining the leader who will deliver this data. For this, entire blockchains, additional consensuses, several independent data sources, and so on are used. There is nothing like this in UMA, this protocol takes a completely different route, relying entirely on its community to determine the validity of the data.

This view opens up completely new use cases that are perfect for forecasting markets, insurance, and many other areas where speed of data delivery is not such a priority and they can afford to wait until the full cycle of data verification within the UMA protocol has passed. However, this allows the oracle to supply completely unlimited types of data, essentially with semi-automatic confirmation of its integrity and truthfulness.

Traditionally, my personal opinion is this. This is an interesting protocol that has thought well about the motivation system and deterrence of its participants. He perfectly understands his niche, where he can be useful, and tailors his service to this. The protocol definitely deserves attention because the solution is quite simple and elegant technically, but at the same time completely different from the classical oracle model. Practical success depends on the activity of the community, which needs to stake, vote, and resolve controversial statements. But this is of course a different story.

Thank you for reading the article to the end! I hope you found it useful. I will be glad to discuss your questions and opinions in the comments.

Our own thoughts on the world of blockchain in telegram channel.

Links

  1. Official documentation

  2. Official repository

  3. UMA token staking is live — Here is how you participate

  4. UMA Building with the Optimistic Oracle

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *