Open code423n4 opened 1 year ago
A more detailed version of #26, although seemingly different. This issue elaborates on the case where the supply has already been filled, while #26 elaborates on the case where there is some supply remaining.
The effect is the same though: unintentionally locking up users' funds in userContributions
, and the user has to wait till the purchase is made before being able to call claim()
to claw back their funds.
HickupHH3 marked the issue as duplicate of #26
HickupHH3 marked the issue as selected for report
Well spotted!
@HickupHH3 I don't think they are the same. This one excludes the case where supply has not been filled yet, intentionally, because when it's not filled, withdrawBalance()
can immediately be used to withdraw, so #26 seems like separate QA issue
@IllIllI000 I don't see where pendingBalances
is incremented in the case where supply has not been filled for the caller.
@HickupHH3 lines 317 and 331 increment it. If the user has a partial fill, they'll have been added to the queue on line 126, and then the check on line 301 will pass since it's either the min, or there's a price lower. The price must be lower, which can only happen if it isn't inserted
HickupHH3 marked the issue as not a duplicate
HickupHH3 marked the issue as primary issue
stevennevins marked the issue as sponsor confirmed
Lines of code
https://github.com/code-423n4/2022-12-tessera/blob/f37a11407da2af844bbfe868e1422e3665a5f8e4/src/modules/GroupBuy.sol#L114-L150 https://github.com/code-423n4/2022-12-tessera/blob/f37a11407da2af844bbfe868e1422e3665a5f8e4/src/modules/GroupBuy.sol#L301-L303
Vulnerability details
If a user contributes funds after there is no more supply left, and they don't provide a price higher than the current minimum bid, they will be unable to withdraw their funds while the NFT remains unbought.
Impact
Ether becomes stuck until and unless the NFT is bought, which may never happen
Proof of Concept
When making a contribution, the user calls the
payable
contribute()
function. If the supply has already been filled (fillAtAnyPriceQuantity
is zero), the bid isn't inserted into the queue, so the new bid is not tracked anywhere. When the function reachesprocessBidsInQueue()
...:https://github.com/code-423n4/2022-12-tessera/blob/f37a11407da2af844bbfe868e1422e3665a5f8e4/src/modules/GroupBuy.sol#L99-L150
...if the price isn't higher than the lowest bid, the while loop is broken out of, with
pendingBalances
having never been updated, and the function does not revert:https://github.com/code-423n4/2022-12-tessera/blob/f37a11407da2af844bbfe868e1422e3665a5f8e4/src/modules/GroupBuy.sol#L291-L313
In order for a user to get funds back, the amount must have been stored in
pendingBalances
, and since this is never done, all funds contributed during thecontribute()
call become property of theGroupBuy
contract, with the user being unable to withdraw...:https://github.com/code-423n4/2022-12-tessera/blob/f37a11407da2af844bbfe868e1422e3665a5f8e4/src/modules/GroupBuy.sol#L274-L284
...until the order has gone through, and they can
claim()
excess funds, but there likely won't be any, due to the separate MEV bug I raised:https://github.com/code-423n4/2022-12-tessera/blob/f37a11407da2af844bbfe868e1422e3665a5f8e4/src/modules/GroupBuy.sol#L228-L244
Tools Used
Code inspection
Recommended Mitigation Steps
revert()
if the price is lower than the min bid, and the queue is already full