# The buy function's mechanism enables users to acquire flash loans at a cheaper fee rate.

Caviar PrivatePool.buy transfers NFTs before collecting payment, letting buyers use the NFT in the ERC721 callback and sell it back, effectively a cheaper flash loan.

- Payout: Contest reward
- Severity: medium
- Program: Caviar
- Credited: KrisApostolov
- Disclosed: 2023-05-18
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2023-04-caviar-findings/issues/885
- Library page: https://bugbountylibrary.com/r/c4%3A2023-04-caviar%3AM-01
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:evm tech:solidity vuln:business-logic vuln:fee-bypass vuln:flash-loan

**Target:** PrivatePool.buy / ERC721 receive callback
**Root cause:** buy() transfers NFTs to the buyer before pulling base tokens, so the buyer controls execution (onERC721Received) while holding the NFT but before paying.
**Steps:**
1. Initialize PrivatePool with ERC20 baseToken, NFT, feeRate, changeFee.
2. Call `privatePool.buyQuote(5e18)` to get netInputAmount.
3. Approve baseToken and call `privatePool.buy(tokenIds, tokenWeights, proofs)`.
4. Inside `onERC721Received`, use the received NFT (e.g. claim airdrop), then approve and call `privatePool.sell(...)` to recover base tokens.
5. Net cost = buy fee + sell fee instead of the flash loan fee, profitable when flash loan fee > combined buy/sell fees.
**How found:** Noticed buy() transfers NFTs before verifying/receiving payment, mirroring Uniswap flash swaps; reasoned that ERC20 pools need no upfront payment and callback allows use-then-pay.
**Exploitation:** Buyer receives NFT before payment, uses it within the callback (airdrop claim, sell back), and only then pays the buy cost, netting a flash-loan-equivalent at lower fee.
**Insight:** When a marketplace/AMM transfers assets before collecting payment, treat the transfer callback as a flash-swap primitive and compare its round-trip fee against the protocol's explicit flash loan fee.
