Closed codehawks-bot closed 1 year ago
rescuing tokens is not a valid finding.
The README does describe that rescuing funds is one of the features of Sparkn. The only limitation is that the rescued token should be one of the whitelisted ones.
This issue (and its duplicate) reveal that rescue does not always work well: if the rescued token is the same ERC20 that's used for distribution, it will be lost.
The README does describe that rescuing funds is one of the features of Sparkn. The only limitation is that the rescued token should be one of the whitelisted ones.
This issue (and its duplicate) reveal that rescue does not always work well: if the rescued token is the same ERC20 that's used for distribution, it will be lost.
I agreed with @aslanbekaibimov.
Rescuing stuck tokens is one of the primary features of Sparkn (see the protocol's README). This issue indicates that the rescuing feature was misdesigned.
If the stuck token's address is identical to the prize token's, the stuck amount will be distributed to contest winners unexpectedly since the stuck amount will be counted as prizes for the winners.
Subsequently, a rescue requestor will lose their token permanently. This also means that the rescuing feature could not function as promised (design flaw).
For this reason, I escalate this issue.
Note: Please consider the statement from the protocol's README:
There is a way to rescue the token stuck in the proxy contract after the deployment and distribution of prizes only when the token is whitelisted.".
This means that a rescue requestor must be able to recover their token if the token is whitelisted (no matter that the token is the same ERC-20 token as the prize token).
checking with sponsor
Checked with sponsor. The docs were a bit misleading. The protocol is not supposed to be able to work with multiple tokens. I will mark this as informational and the docs will be updated.
Design flaw in the _distribute() can unexpectedly drain the rescue requestor's stuck token
Severity
High Risk
Relevant GitHub Links
https://github.com/Cyfrin/2023-08-sparkn/blob/0f139b2dc53905700dd29a01451b330f829653e9/src/Distributor.sol#L139
https://github.com/Cyfrin/2023-08-sparkn/blob/0f139b2dc53905700dd29a01451b330f829653e9/src/Distributor.sol#L146-L147
https://github.com/Cyfrin/2023-08-sparkn/blob/0f139b2dc53905700dd29a01451b330f829653e9/src/Distributor.sol#L154
https://github.com/Cyfrin/2023-08-sparkn/blob/0f139b2dc53905700dd29a01451b330f829653e9/src/Distributor.sol#L164
Summary
The
Distributor::_distribute()
does not account for the stuck amount mistakenly transferred to theProxy
contract. If the stuck token's address is identical to the prize token's address, the stuck amount will be distributed to contest winners.Subsequently, a rescue requestor will lose their token permanently.
Vulnerability Details
The
Sparkn
protocol was designed as an escrow for distributing prizes (tokens) to contest winners. One of the protocol features is recovering stuck tokens (being sent by mistake) from a contest'sProxy
contract after the contest expires.In other words, all prizes must be distributed to all winners before an owner (protocol admin) can execute the stuck tokens' recovery process. However, if the stuck token's address is identical to the prize token's address, the stuck amount will not be accounted for by the
_distribute()
.The
_distribute()
will count the total balance locked in theProxy
as prizes for winners. Therefore, the stuck amount will also be distributed to the contest winners. The remaining amount will then become the protocol's commission fee and be transferred to the protocol's fee collector.Consequently, a rescue requestor could not retrieve their token as expected.
The total token balance is viewed as winners' prizes
: https://github.com/Cyfrin/2023-08-sparkn/blob/0f139b2dc53905700dd29a01451b330f829653e9/src/Distributor.sol#L139All balance (minus the commission fee) is distributed to winners
: https://github.com/Cyfrin/2023-08-sparkn/blob/0f139b2dc53905700dd29a01451b330f829653e9/src/Distributor.sol#L146-L147Transferring the commission fee
: https://github.com/Cyfrin/2023-08-sparkn/blob/0f139b2dc53905700dd29a01451b330f829653e9/src/Distributor.sol#L154The commission fee is transferred to the protocol's fee collector
: https://github.com/Cyfrin/2023-08-sparkn/blob/0f139b2dc53905700dd29a01451b330f829653e9/src/Distributor.sol#L164Impact
Suppose the stuck token's address is identical to the prize token's address. In that case, the stuck amount will be distributed to contest winners unexpectedly since the stuck amount will be counted as prizes for the winners.
Subsequently, a rescue requestor will lose their token permanently.
Tools Used
Manual Review
Recommendations
To fix the vulnerability, I recommend improving the following functions:
distribute()
,_distribute()
, and_commissionTransfer()
, as shown below.uint256 maxAmountToTransfer
param is introduced to thedistribute()
, which must also be passed to the_distribute()
._distribute()
must count themaxAmountToTransfer
param as the maximum prize amount to distribute to winners instead._commissionTransfer()
must transfer only a commission fee calculated from themaxAmountToTransfer
param instead of transferring all remaining amount.This way, an organizer or owner (protocol admin) can define the
maxAmountToTransfer
param when executing thedistribute()
. The stuck amount will not be mistakenly transferred from theProxy
and can be retrieved to a rescue requestor as expected.