# SwapperCallbackValidation doesn't do anything, opens up users to having contracts drained

SwapperCallbackValidation only checks caller is any registered Swapper, so anyone can deploy a Swapper and invoke callback-only functions to drain contracts.

- Payout: Contest reward
- Severity: medium
- Program: Splits
- Credited: 0x00ffDa
- Disclosed: 2023-04-06
- Write-up quality: 7/10
- Original: https://github.com/sherlock-audit/2023-04-splits-judging/issues/9
- Library page: https://bugbountylibrary.com/r/sherlock%3A2023-04-splits%3AM-1
- Tags: domain:web3 impact:funds-loss kind:contest platform:sherlock sev:medium tech:evm tech:solidity technique:source-review vuln:access-control vuln:business-logic

**Target:** SwapperCallbackValidation library / SwapperImpl (WalletImpl.execCalls)
**Root cause:** verifyCallback only checks factory_.isSwapper(swapper_) — i.e. that the caller is *a* Swapper, not the specific expected one — and SwapperImpl inherits WalletImpl.execCalls allowing arbitrary calls, so any user can deploy a Swapper and pass arbitrary calldata.
**Steps:**
1. Deploy a new Swapper via SwapperFactory so isSwapper(swapper) returns true.
2. Call the victim contract's callback-only function (e.g. `swapperFlashCallback()`) directly from the new Swapper, or route arbitrary calldata through `execCalls()` on the Swapper.
3. Victim's `verifyCallback(SwapperImpl(msg.sender))` returns true, bypassing the guard.
4. Callback logic sends all ETH/ERC20 held by the victim contract to msg.sender.
**How found:** Code reading: noticed verifyCallback only checks factory.isSwapper(swapper_) rather than matching a specific expected swapper, then traced SwapperImpl inheriting WalletImpl.execCalls to confirm arbitrary calldata can be sent from any attacker-deployed Swapper.
**Exploitation:** Attacker deploys a Swapper, then invokes the victim's callback-only entrypoint (e.g. swapperFlashCallback) directly from that Swapper; the victim's verifyCallback passes because the caller is a registered Swapper, and the victim forwards its entire balance to msg.sender.
**Insight:** When a 'callback validation' helper only checks that the caller is *some* instance of a factory-registered type, it is trivially bypassed by deploying your own instance; look for checks that compare against a stored/expected address rather than a membership predicate.
