An equality check between address(this).balance
and totalFees
is performed before transferring the totalFee
amount to the caller.
A fixed fee is charged to each player for entering the raffle game, and the fees are accumulated in the contract. 80% of the total fee is stored as the prize pool to be awarded to the winner of each game, while the remaining 20% is stored as a protocol fee within the contract. The protocol fee is accumulated until the withdraw()
function is invoked. Upon calling the selectWinner()
function, the prize pool
is transferred to the raffle winner, and the remaining 20% is added to totalFees
.
The logic within the withdraw()
function assumes that totalFees
will always match the contract's balance. This logic is flawed if the contract's balance changes, as illustrated in the following scenarios:
Assuming there were initially four players in the raffle, and one of them is refunded, the actual number of players would be three, yet the length of the players' array would remain at four. Refunded player's index will be set to the zero address. However, the player will not be removed from the list of players, resulting in the length of the players' array remaining one more than the actual number of players.
According to above calculations, totalFees
gets effected due to miscalculation of the players array.
address(this).balance == uint256(totalFees)
The calculations above demonstrate that totalFees
is affected due to miscalculations stemming from the players' array discrepancy.
Additionally, there are two other methods to transfer balance to a contract, like in our case, if the contract lacks a receive()
or fallback()
function: selfdestruct()
and sending balance before the contract's creation.
Utilizing either of the aforementioned options will result in the fees being trapped within the contract indefinitely. Proof of concept for the exploit:
Manual code review
Foundry
The primary purpose of the equality check is to verify the presence of active players. Consider implementing an additional mechanism to determine whether the raffle is OPEN/ONGOING/CLOSED, possibly using an enum.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.