# GroupBuy can be drained of all ETH.

GroupBuy.purchase() lacks reentrancy protection and sets success flag after external call, letting attacker re-enter and drain all ETH.

- Payout: Contest reward
- Severity: high
- Program: Tessera
- Credited: Trust
- Disclosed: 2023-04-18
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2022-12-tessera-findings/issues/52
- Library page: https://bugbountylibrary.com/r/c4%3A2022-12-tessera%3AH-09
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:chained vuln:access-control vuln:reentrancy

**Target:** GroupBuy.purchase() via IMarketBuyer.execute external call
**Root cause:** purchase() performs external call to IMarketBuyer.execute before setting poolInfo[_poolId].success = true, and has no reentrancy guard; _verifyUnsuccessfulState only checks the success flag.
**Steps:**
1. Pool holds 1500 ETH; max allowed price = minReservePrices * filledQuantities = 1000 ETH.
2. Attacker calls `purchase(1000 ETH)` with a malicious/controlled `_market` whose `execute()` re-enters `purchase()`.
3. Inner `purchase(500 ETH)` sends 500 ETH and re-enters `execute()` again, which returns the NFT owner address.
4. Inner call sees returned owner, sets success=true, returns.
5. Outer call also sees owner address, sets success=true, returns.
6. Attacker nets 1500 ETH total.
**How found:** Code reading: noticed success flag set only at end of purchase() after external execute() call, and _verifyUnsuccessfulState relies solely on that flag, enabling reentrancy.
**Exploitation:** Attacker controls `_market` (unvalidated) or uses an existing market passing a user address in `_purchaseOrder`; execute() re-enters purchase() repeatedly before success flag is set, each iteration extracting up to the max allowed price, draining the full ETH balance.
**Escalation:** Chainable with the unvalidated `_market` bug to bypass the minReservePrices*filledQuantities cap and steal entire balance instead of just the capped amount.
**Insight:** When a state flag gates a guard check and is written after an external call, reentrancy can re-trigger the guarded path; check ordering of success/termination flags vs external calls in purchase-style flows.
