# saleReceiver and feeReceiver can steal refunds after sale has ended

LPDA sale allows buying 0 NFTs after sale end, re-triggering payout so saleReceiver/feeReceiver drains buyer overpayments held for refunds.

- Payout: Contest reward
- Severity: high
- Program: Escher
- Credited: immeas
- Disclosed: 2023-02-22
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2022-12-escher-findings/issues/441
- Library page: https://bugbountylibrary.com/r/c4%3A2022-12-escher%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:** LPDA.sol Dutch auction minter contract
**Root cause:** buy() only checks newId <= finalId, so buying 0 NFTs when currentId already equals finalId passes and re-executes the end-of-sale payout block, transferring totalSale again from contract balance.
**Steps:**
1. Deploy LPDA sale; buyers purchase most NFTs at higher price, leaving overpayments in contract.
2. Warp to lowest price; buy final NFT triggering payout to saleReceiver and feeReceiver.
3. Compute totalSale = finalPrice * amountSold; delta = totalSale - address(sale).balance.
4. Call `sale.buy{value: delta}(0)` — newId == finalId passes require, payout block re-runs, delta split 5% fee / 95% to saleReceiver.
5. Repeat until contract balance drained; buyers' `refund()` reverts OutOfFund.
**How found:** Reading buy() flow: noted the end-of-sale payout is gated only by newId == finalId, and the guard is newId <= finalId, so a zero-amount buy at finalId re-enters the payout branch.
**Exploitation:** saleReceiver or feeReceiver (privileged payout recipients) call buy(0) with value equal to totalSale minus current contract balance, re-triggering the finalId payout branch and siphoning buyer overpayments; iterable until contract empty.
**Insight:** When a state-transition branch (sale end) is keyed off an equality check on a counter that a zero-amount call can re-satisfy, test buy(0)/deposit(0) paths to see if terminal payouts re-fire against residual balances.
