# Arbitrary contract call allows attackers to steal ERC20 from users' wallets

Tally Swap.fillZrxQuote makes an arbitrary external call with user-supplied calldata, letting an attacker invoke transferFrom on approved tokens and drain user wallets.

- Payout: Audit contest
- Severity: high
- Program: Tally
- Found by: WatchPug
- Disclosed: 2021-11-19
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2021-10-tally-findings/issues/37
- Library page: https://bugbountylibrary.com/r/c4%3A2021-10-tally%3AH-01
- Tags: area:web3 domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:source-review vuln:access-control vuln:business-logic

**Target:** Solidity Swap contract (fillZrxQuote / swapByQuote)
**Root cause:** fillZrxQuote performs zrxTo.call{value: ethAmount}(zrxData) with attacker-controlled target and calldata, so the target can be any ERC20 and calldata can be transferFrom pulling from users who approved the Swap contract.
**Steps:**
1. Alice approves 1000 WETH to Swap.sol
2. Attacker calls `TallySwap.swapByQuote(address(WETH),0,address(WETH),0,address(0),address(WETH), abi.encodeWithSignature("transferFrom(address,address,uint256)", address(Alice), address(this), 1000 ether))`
3. fillZrxQuote executes `zrxTo.call{value: ethAmount}(zrxData)` with zrxTo=WETH and transferFrom calldata
4. WETH transferFrom moves Alice's 1000 WETH to attacker
**How found:** Code review of fillZrxQuote noticed the low-level call uses caller-supplied zrxTo and zrxData without restriction, and reasoned the target could be an ERC20 whose transferFrom spends existing user approvals.
**Exploitation:** Attacker supplies the approved token itself as zrxTo and encodes transferFrom(victim, attacker, allowance) as zrxData; the internal low-level call executes it, transferring up to the victim's allowance to the attacker. No capital needed beyond gas.
**Insight:** When a contract holds user ERC20 approvals, any unvalidated low-level call with user-controlled target+calldata is equivalent to arbitrary transferFrom; check whether the call target can be the approved token itself.
