# Users can avoid paying fees for ETH swaps

Malicious zrxBuyTokenAddress contract fakes ERC20 buy in swapByQuote so ETH swap output is returned as 'refunded ETH' with no swap fee charged.

- Payout: Audit contest
- Severity: medium
- Program: Tally
- Found by: pants
- Disclosed: 2021-11-19
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2021-10-tally-findings/issues/68
- Library page: https://bugbountylibrary.com/r/c4%3A2021-10-tally%3AM-03
- Tags: area:web3 domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:solidity technique:source-review vuln:business-logic vuln:fee-bypass

**Target:** Swap.swapByQuote() 0x-based swap contract
**Root cause:** Fee logic distinguishes ETH vs ERC20 swaps by checking boughtERC20Amount from zrxBuyTokenAddress.balanceOf, which an attacker-controlled token address can spoof; refunded ETH is returned fee-free.
**Steps:**
1. Deploy malicious contract M to use as zrxBuyTokenAddress.
2. Call `Swap.swapByQuote()` with `zrxBuyTokenAddress=M`, `minimumAmountReceived=0`, args for an ETH-output swap.
3. M.balanceOf returns 0 on first call and 1 on second call inside fillZrxQuote.
4. boughtERC20Amount becomes 1, so boughtETHAmount is treated as refunded ETH and returned without swap fee.
**How found:** Manual code review of the fee accounting in swapByQuote, noticing that boughtERC20Amount is derived from an attacker-supplied token address's balanceOf and gates whether ETH is treated as refunded.
**Exploitation:** Attacker performs a normal ETH swap but supplies a fake token address whose balanceOf lies, causing the contract to classify the ETH output as a refund and skip the fee, saving the swap fee on every ETH swap.
**Insight:** When fee/refund logic branches on a token balance, check whether the token address is caller-supplied; a fake balanceOf can flip the branch and reclassify value as a fee-exempt refund.
