# PrivatePool.flashLoan() takes fee from the wrong address

PrivatePool.flashLoan pulls the flash fee from msg.sender instead of the receiver, causing unexpected fund loss for callers with prior pool approvals.

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

**Target:** PrivatePool.flashLoan() ERC3156 flashloan lender
**Root cause:** Fee is transferred via ERC20(baseToken).transferFrom(msg.sender,...) rather than from the receiver, and for ETH pools the fee is required upfront in msg.value instead of after the callback.
**Steps:**
1. Call `flashLoan(receiver, token, tokenId, data)` where msg.sender != receiver and msg.sender has previously approved the pool to spend baseToken
2. Observe `ERC20(baseToken).transferFrom(msg.sender, address(this), fee)` deducts the fee from the caller, not the receiver
3. For ETH pools, note `msg.value < fee` reverts unless caller pre-sends the fee before earning profits
**How found:** Code review of flashLoan against the EIP-3156 lender spec, noticing the fee transferFrom source address and the ETH upfront-fee check diverge from the spec's 'take amount+fee from the receiver after callback' requirement.
**Insight:** When auditing flashloan/lender implementations, diff the fee/repayment source address against the EIP-3156 spec: fee must be pulled from the receiver after the callback, not from msg.sender, otherwise any caller with a standing approval to the pool silently pays.
