# Groupbuy: _verifyUnsuccessfulState and _verifySuccessfulState both can return true when block.timestamp == pool.terminationPeriod

GroupBuy's success/failure state checks both pass at block.timestamp == terminationPeriod, letting a bidder claim refunds twice and drain the pool.

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

**Target:** GroupBuy.sol state verification functions (claim/contribute flow)
**Root cause:** Boundary mismatch: _verifyUnsuccessfulState uses `block.timestamp > terminationPeriod` while _verifySuccessfulState uses `block.timestamp < terminationPeriod`, so at exact equality both pass without reverting.
**Steps:**
1. Attacker holds lowest bid: quantity 10, price 1 ETH at timestamp == pool.terminationPeriod.
2. Call `claim()` to recover 10 ETH contribution.
3. Call `contribute(quantity=10, price=2 ETH)`; higher than prior lowest bid, so pendingBalances=10 ETH and userContributions=20 ETH.
4. Call `claim()` again to withdraw 20 ETH userContributions plus 10 ETH pendingBalances, netting 10 ETH profit.
5. Repeat pattern to drain contract.
**How found:** Code review of the two state-verification functions revealed asymmetric comparison operators (> vs <) around terminationPeriod, exposing a boundary where both succeed.
**Exploitation:** At the exact terminationPeriod timestamp, both state verifiers pass, so claim() succeeds while the pool is still considered active. Attacker claims refund, re-contributes at a higher price to inflate pendingBalances and userContributions, then claims again to withdraw both, extracting the difference.
**Escalation:** Repeatable pattern drains the entire contract balance.
**Insight:** When two mutually-exclusive state predicates guard the same lifecycle, diff their boundary operators; equality at a timestamp boundary is reachable ~1/12 of blocks on 12s mainnet, so treat == as a real exploit window, not a theoretical edge.
