Skip to content
Request an audit

‹ All findings

Users buying too many tickets will DoS them and the protocol if they are the winner due to OOG

MediumWinnables Raffles·Sherlock · Raffle · 16th August 2024CodebaseM-5

Summary

WinnablesTicket stores nft ownership by setting the first minted nft id ownership to the user minting and all the next minted nfts remain as 0. This means it always costs the same to mint, but the ownerOf() function becomes much more expensive, to the point where it may cause OOG errors. In this case, the user is able to buy tickets via WinnablesTicketManager::buyTickets(), the draw is made in WinnablesTicketManager::drawWinner() and the chainlink request is fulfilled with the winner in WinnablesTicketManager::fulfillRandomWords(). However, in WinnablesTicketManager::propagateWinner(), it reverts due to OOG when calling WinnablesTicket::ownerOf().

Root Cause

In WinnablesTicket:97-99, it may run out of gas if enough tickets were bought.

Internal pre-conditions

  1. Admin sets max holdings and max tickets to a number of at least 4000 or simillar.

External pre-conditions

None.

Attack Path

  1. Users by tickets via WinnablesTicketManager::buyTickets()`
  2. The draw is made in WinnablesTicketManager::drawWinner()
  3. The chainlink request is fulfilled with the winner in `WinnablesTicketManager::fulfillRandomWords()]
  4. In WinnablesTicketManager::propagateWinner(), it reverts due to OOG when calling WinnablesTicket::ownerOf().

Impact

DoSed winner and protocol ETH from the raffle.

PoC

The following test costs 9 million gas, more than the block limit on Avalanche.

solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Test, console} from "forge-std/Test.sol";
import {WinnablesPrizeManager, LinkTokenInterface} from "contracts/WinnablesPrizeManager.sol";
import {WinnablesTicket} from "contracts/WinnablesTicket.sol";
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";

contract WinnablesPrizeManagerTest is Test {
    WinnablesPrizeManager public winnablesPrizeManager;
    WinnablesTicket public winnablesTicket;
    LinkTokenInterface public link;
    IRouterClient public router;
    address public owner;


    function setUp() public {
        vm.createSelectFork(vm.envString("ETHEREUM_RPC_URL"));
        link = LinkTokenInterface(0x514910771AF9Ca656af840dff83E8264EcF986CA);
        router = IRouterClient(0x80226fc0Ee2b096224EeAc085Bb9a8cba1146f7D);
        owner = makeAddr("owner");

        vm.prank(owner);
        winnablesPrizeManager = new WinnablesPrizeManager(address(link), address(router));

        vm.prank(owner);
        winnablesTicket = new WinnablesTicket();
    }

    function test_POC_OwnerOfDos() public {
        vm.prank(owner);
        winnablesTicket.setRole(owner, 1, true);

        vm.prank(owner);
        winnablesTicket.mint(owner, 1, 4000);

        winnablesTicket.ownerOf(1, 3999);
    }
}

Mitigation

Set a reasonable cap for max holdings.