# DoinGud: acceptOffer replay on amount=0 path drains escrow

DoinGud marketplace acceptOffer with amount=0 paid out escrow without clearing the offer, letting a flash-loan-funded self-offer be accepted twice to drain ~35,380 USDC.

- Payout: Real-world hack
- Severity: critical
- Program: DoinGud
- Disclosed: 2026-09-21
- Write-up quality: 8/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2026-09/DoinGud_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20260921-doingud
- Tags: domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:critical tech:evm tech:solidity technique:reverse-engineering technique:source-review vuln:business-logic vuln:reentrancy

**Target:** EIP-2535 Diamond marketplace acceptOffer/makeOffer (Polygon)
**Root cause:** acceptOffer(offerer, tokenId, price, amount) transfers `price` from escrow when amount==0 but never deletes/zeroes the offer record, and lacks an offerer!=msg.sender check, so byte-identical calls replay.
**Steps:**
1. Flash-swap-borrow 35,486.935717 USDC from UniV2 USDC/miMATIC pair `0x160532...` via `swap(PRICE,0,this,data)`.
2. In uniswapV2Call, approve marketplace and call makeOffer selector `0xf6dbe82d` with (address(this), tokenId=1, price=PRICE, six zero words) to fund offer id 1.
3. Call acceptOffer selector `0x5c924960` with (address(this), 1, PRICE, amount=0) — pays PRICE out, offer not cleared.
4. Call the identical acceptOffer again — pays PRICE a second time from pre-existing escrow.
5. Repay flash swap `borrowed*1000/997+1` = 35,593.716868 USDC; net ~35,380.15 USDC.
**How found:** Facet unverified on Polygonscan; researcher reconstructed selectors and arg layout from the attack trace, then empirically confirmed on fork that amount==0 succeeds and replays while amount>0 reverts, isolating the missing cleanup on the zero-amount path.
**Exploitation:** Attacker EOA used a helper contract to flash-borrow the offer price, self-create and self-accept an offer with amount=0 twice, extracting the second payout from other users' escrow balance, then repaid the flash loan with zero principal at risk.
**Insight:** When a marketplace facet is unverified, decode selectors/arg layout from the attack trace and probe boundary values (amount=0) on a fork to find guard-skipped branches that bypass state cleanup while still paying out.
