41672 sc insight permanent loss risk of user funds due to inflexible function design in claim
Was this helpful?
Was this helpful?
Submitted on Mar 17th 2025 at 13:56:22 UTC by @perseverance for
Report ID: #41672
Report Type: Smart Contract
Report severity: Insight
Target: https://github.com/immunefi-team/audit-comp-yeet/blob/main/src/Yeet.sol
Impacts:
Permanent freezing of funds
After a game round completed, the winner of a game round in Yeet can claim the reward by calling claim()
function.
https://github.com/immunefi-team/audit-comp-yeet/blob/main/src/Yeet.sol#L335-L345
Or in Yeetback.sol (https://github.com/immunefi-team/audit-comp-yeet/blob/main/src/Yeetback.sol#L121-L134) there is similar claim function to claim the lottery reward.
The current implementation of the claim()
function works for most situations. But there is some edge case when the msg.sender cannot receive native BERA . For example, if the winner of the game or lottery draw is a smart contract without receive()
or fallback() payable
functions, then the call claim()
will always reverted.
Although this scenario is just an edge case, that is not likely to happen. But if this happened, then nothing can be done for the user to claim the winning BERA.
This vulnerability is classified as Critical severity because:
Impact category: Permanent freezing of funds
It can lead to permanent loss of user funds
There is no recovery mechanism
The impact is direct financial loss
Although this scenario is an edge case, not likely to happen in most situations. But if it happens, then there is no recovery mechanism. Since this is related to many users in the system, so the risk still exists. It is affecting the main functionality of the Yeet game and the risk of affected fund can be big. For example, right now the pot to winner can be 10_000 USD.
So to prevent this bug:
So you can improve the documentation to highlight this for users. But this does not eliminate the risk. So unfortunately if it happens, there is no way to help as the fund is stuck forever.
But also you can design the claim() function to accept an address of receiver and allow the winner to claim the BERA sent to that receiver.
The second way is much better as it allows some flexibility for the winner to claim the reward and eliminated the risk of stuck fund forever.
Now when we design the system, I recommend to follow the second way.
Step 1: Let's consider a scenario where a smart contract participates in the Yeet game:
Step 2: This contract wins the game, and winnings[contractAddress]
is set to a positive value.
Step 3: When trying to claim:
The claim() fails because:
The contract has no receive()
or fallback() payable
function so the BERA (ETH) transfer fails