hats-finance / SeeR-PM-0x899bc13919880db76edf4ccd72bdfa5dfa666fb7

1 stars 0 forks source link

Value can be maximized by frontrunning market resolution #87

Open hats-bug-reporter[bot] opened 1 month ago

hats-bug-reporter[bot] commented 1 month ago

Github username: -- Twitter username: -- Submission hash (on-chain): 0x9bd66c162b0de1235270393429f06c3715d7fa8084f92d3ae4ba5ab922a40111 Severity: medium

Description: Description\

The answer which determines which one of the outcomes is correct is provided by the RealityETH, which is called by the oracle when calling RealityProxy ’s resolveMarket function. Although, the answer isn’t submitted on-chain at the same transaction that the market resolution is called by the oracle. Which leads to the answer being read and a malicious user buying the tokens of the correct option in order to profit since he knows the outcome. The issue leads to the seer prediction market being predictable, and not meritocratic.

P.S. the exploit can be done by either front-running the call of the oracle to market resolution, or by back-running the arbitrator’s call to submitAnswerByArbitrator.

function submitAnswerByArbitrator(bytes32 question_id, bytes32 answer, address answerer) 
    onlyArbitrator(question_id)
    statePendingArbitration(question_id)
public {

    require(answerer != NULL_ADDRESS, "answerer must be provided");
    emit LogFinalize(question_id, answer);

    questions[question_id].is_pending_arbitration = false;
    _addAnswerToHistory(question_id, answer, answerer, 0, false);
    _updateCurrentAnswerByArbitrator(question_id, answer);

}
...

function _updateCurrentAnswerByArbitrator(bytes32 question_id, bytes32 answer)
internal {
    questions[question_id].best_answer = answer;
    questions[question_id].finalize_ts = uint32(block.timestamp);
}
...

function resolveCategoricalMarket(
    bytes32 questionId,
    bytes32[] memory questionsIds,
    uint256 numOutcomes
) internal {
    uint256 answer = uint256(realitio.resultForOnceSettled(questionsIds[0]));
    uint256[] memory payouts = new uint256[](numOutcomes + 1);

    if (answer == uint256(INVALID_RESULT) || answer >= numOutcomes) {
        // the last outcome is INVALID_RESULT.
        payouts[numOutcomes] = 1;
    } else {
        payouts[answer] = 1;
    }

    conditionalTokens.reportPayouts(questionId, payouts);
}
...
function resultForOnceSettled(bytes32 question_id)
external view returns(bytes32) {
    bytes32 result = resultFor(question_id);
    if (result == UNRESOLVED_ANSWER) {
        // Try the replacement
        bytes32 replacement_id = reopened_questions[question_id];
        require(replacement_id != bytes32(0x0), "Question was settled too soon and has not been reopened");
        // We only try one layer down rather than recursing to keep the gas costs predictable
        result = resultFor(replacement_id);
        require(result != UNRESOLVED_ANSWER, "Question replacement was settled too soon and has not been reopened");
    }
    return result;
}
...
function resultFor(bytes32 question_id) 
    stateFinalized(question_id)
public view returns (bytes32) {
    return questions[question_id].best_answer;
}
  1. Revised Code

The issue can be resolved by halting the transferring of conditional tokens of the market, that the arbitrator can call submitAnswerByArbitrator to, which means that the question_id is is_pending_arbitration . That could be done by having a check in the _transfer of the Wrapped1155 .

xyzseer commented 1 month ago

Which leads to the answer being read and a malicious user buying the tokens of the correct option in order to profit since he knows the outcome

Liquidity providers are responsible for removing the liquidity once the outcome is known.

PlamenTSV commented 1 month ago

This is not a problem in the Seer system, it is front-running an answer coming from the arbitrator. In case of a wider known event you could easily just stockpile on the correct answer via other external means other than front-running the mempool.

clesaege commented 1 month ago

Answers are known (with a pretty good %) even before the arbitrator calls reality. Liquidity providers should remove liquidity before the answer is known (and in the future, we'll have mix AMM-auction systems).