hats-finance / SeeR-PM-0x899bc13919880db76edf4ccd72bdfa5dfa666fb7

1 stars 0 forks source link

Issue resolver does not check if the issue is finalized. #24

Open hats-bug-reporter[bot] opened 4 hours ago

hats-bug-reporter[bot] commented 4 hours ago

Github username: -- Twitter username: -- Submission hash (on-chain): 0xbc97de7e82f2857a81317d45d211b61e0fab29b8612cdcd81c32af6a12945a6d Severity: high

Description: Description\

Issue resolver does not check if the issue is finalized.

Attack Scenario\

In RealityProxy, the contract is used to resolve the answer.

In ResolveMultiScalarMarket, the code does not convert the answer to payout

   function resolveMultiScalarMarket(
        bytes32 questionId,
        bytes32[] memory questionsIds,
        uint256 numOutcomes
    ) internal {
        uint256[] memory payouts = new uint256[](numOutcomes + 1);
        bool allZeroesOrInvalid = true;

        /*
         * We set maxPayout to a sufficiently large number for most possible outcomes that also avoids overflows in the following places:
         * https://github.com/gnosis/conditional-tokens-contracts/blob/master/contracts/ConditionalTokens.sol#L89
         * https://github.com/gnosis/conditional-tokens-contracts/blob/master/contracts/ConditionalTokens.sol#L242
         */
        uint256 maxPayout = 2 ** (256 / 2) - 1;

        for (uint256 i = 0; i < numOutcomes; i++) {
            payouts[i] = uint256(realitio.resultForOnceSettled(questionsIds[i]));

            if (payouts[i] == uint256(INVALID_RESULT)) {
                payouts[i] = 0;
            } else if (payouts[i] > maxPayout) {
                payouts[i] = maxPayout;
            }

            allZeroesOrInvalid = allZeroesOrInvalid && payouts[i] == 0;
        }

        if (allZeroesOrInvalid) {
            // invalid result.
            payouts[numOutcomes] = 1;
        }

        conditionalTokens.reportPayouts(questionId, payouts);
    }

the code query realitio.resultForOnceSettled directly,

https://gnosisscan.io/address/0xE78996A233895bE74a66F451f1019cA9734205cc#code#L608

   /// @notice Return the final answer to the specified question, or revert if there isn't one
    /// @param question_id The ID of the question
    /// @return The answer formatted as a bytes32
    function resultFor(bytes32 question_id) 
        stateFinalized(question_id)
    public view returns (bytes32) {
        return questions[question_id].best_answer;
    }

However, the question maybe pending for arbitration and not finalized yet.

https://gnosisscan.io/address/0xE78996A233895bE74a66F451f1019cA9734205cc#code#L131

  struct Question {
        bytes32 content_hash;
        address arbitrator;
        uint32 opening_ts;
        uint32 timeout;
        uint32 finalize_ts;
        bool is_pending_arbitration;
        uint256 bounty;
        bytes32 best_answer;
        bytes32 history_hash;
        uint256 bond;
        uint256 min_bond;
    }

the code does not check if the question is finalized or if the arbitration and dispute is completed.

https://gnosisscan.io/address/0xE78996A233895bE74a66F451f1019cA9734205cc#code#L589

 /// @notice Report whether the answer to the specified question is finalized
    /// @param question_id The ID of the question
    /// @return Return true if finalized
    function isFinalized(bytes32 question_id) 
    view public returns (bool) {
        uint32 finalize_ts = questions[question_id].finalize_ts;
        return ( !questions[question_id].is_pending_arbitration && (finalize_ts > UNANSWERED) && (finalize_ts <= uint32(block.timestamp)) );
    }

Attachments

  1. Proof of Concept (PoC) File

  2. user A a create a question.

  3. user B create a prediction market.

  4. user C answer incorrect answer.

  5. user C resolve the prediction market using incorrect answer.

  6. user B create a arbitration and dispute and wants to modify the incorrect answer.

  7. the incorrect answer is changed and overwritten to correct answer.

  8. but user C already use the incorrect answer to resolve the prediction

because the code does not check if the question is finalized.

  1. Revised Code File (Optional)
xyzseer commented 25 minutes ago

isFinalized is called inside the stateFinalized modifier on resultFor

the call tree is as follow:

  1. resultForOnceSettled calls resultFor
  2. resultFor has the modifier stateFinalized
  3. stateFinalized calls isFinalized and reverts if the question is not finalized yet