# Users that send funds at a price lower than the current low bid have the funds locked

Contributing to a filled Tessera GroupBuy pool at a price below the lowest bid skips queue insertion and pendingBalances, locking the ether until an NFT purchase that may never occur.

- Payout: Contest reward
- Severity: medium
- Program: Tessera
- Credited: IllIllI
- Disclosed: 2023-04-18
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2022-12-tessera-findings/issues/31
- Library page: https://bugbountylibrary.com/r/c4%3A2022-12-tessera%3AM-03
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** GroupBuy.sol contribute/processBidsInQueue/withdrawBalance
**Root cause:** When remaining supply is zero, fillAtAnyPriceQuantity is 0 so the bid is never inserted into the priority queue; processBidsInQueue then breaks immediately because _price < lowestBid.price, leaving pendingBalances uncredited while msg.value was already added to userContributions/totalContributions.
**Steps:**
1. Fill the pool supply so remainingSupply == 0.
2. Call `contribute(_poolId, _quantity, _price)` with msg.value == _quantity*_price and _price below the current min bid price.
3. Observe fillAtAnyPriceQuantity == 0, so no queue insert; processBidsInQueue breaks at `if (_price < lowestBid.price) break;` without updating pendingBalances.
4. Call `withdrawBalance()` — reverts with InsufficientBalance since pendingBalances[msg.sender] == 0.
5. Funds remain in contract until claim() after a successful purchase or termination.
**How found:** Code reading of contribute(): noticed the conditional queue insert gated on fillAtAnyPriceQuantity > 0 and the early break in processBidsInQueue when _price < lowestBid.price, then traced that pendingBalances is only credited inside the loop, so the contributed ether is never refundable.
**Exploitation:** Any user can accidentally (or an attacker deliberately) lock their own ether by contributing at a below-min price after supply is filled; funds are unrecoverable via withdrawBalance and only claimable after a successful NFT purchase or termination period.
**Insight:** In queue-based contribution contracts, check every path where a bid is rejected or not inserted: verify the contributed value is either queued or credited to a refundable balance, otherwise the early-break path silently strands funds.
