# Re-entrancy bug allows inflating balance

Fake token pair returns inflated reserves in MarginRouter.crossSwapExactTokensForTokens, then re-entrancy during _swap credits the same output twice, enabling theft of all tokens.

- Payout: Audit contest
- Severity: high
- Program: Marginswap
- Disclosed: 2021-05-03
- Write-up quality: 8/10
- Original: https://github.com/code-423n4/2021-04-marginswap-findings/issues/19
- Library page: https://bugbountylibrary.com/r/c4%3A2021-04-marginswap%3AH-01
- Tags: area:web3 domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:chained vuln:accounting-error vuln:reentrancy

**Target:** MarginRouter.crossSwapExactTokensForTokens / _swapExactT4T
**Root cause:** Output amounts are estimated via UniswapStyleLib.getAmountsOut against attacker-controlled pair reserves and credited via registerTrade before the actual swap; _swap's balance check can be satisfied by a re-entrant inner swap, so the same output is credited twice.
**Steps:**
1. Call `crossSwapExactTokensForTokens(0.0001 WETH, 0, [ATTACKER_CONTRACT], [WETH, WBTC])` with attacker contract posing as a pair.
2. Attacker contract returns fake reserves from getAmountsOut yielding 1 WBTC for the tiny input; amount credited via registerTrade.
3. Router calls `_swapExactT4T([0.0001 WETH, 1 WBTC], 0, [ATTACKER_CONTRACT], [WETH, WBTC])`; inside `_swap` startingBalance is stored and `pair.swap(0.0001 WETH, 1 WBTC, FUND, new bytes(0))` is invoked.
4. From ATTACKER_CONTRACT re-enter `MarginRouter.crossSwapExactTokensForTokens(30 WETH, 0, WETH_WBTC_PAIR, [WETH, WBTC])` with the real pair.
5. Inner swap deposits real funds to FUND; outer `_swap` resumes and `endingBalance >= startingBalance + amounts[amounts.length - 1]` passes.
6. One real trade results in double credit; repeat to drain all tokens.
**How found:** Observation that getAmountsOut estimation runs against caller-supplied pairs and that registerTrade credits before the actual swap; the balance check in _swap only compares balances, so a re-entrant inner swap can satisfy it.
**Exploitation:** Attacker deploys a contract impersonating a token pair that lies about reserves, triggers crossSwapExactTokensForTokens, and re-enters with the genuine WETH/WBTC pair during the pair.swap callback so the balance check is satisfied by the inner swap's deposit while the outer call still credits the inflated amount. Repeated to accumulate credit and steal all tokens.
**Escalation:** Repeat the double-credit cycle many times to drain all tokens from the protocol.
**Insight:** When a router credits amounts from a caller-supplied pair's getAmountsOut before executing the swap, treat the pair as untrusted: the post-swap balance check can be satisfied by a re-entrant legitimate swap, so credit and settlement must use the same actual trade result.

## Proof of concept (copied exactly from the original report)

**Fake pair crossSwap call with attacker contract**

```
crossSwapExactTokensForTokens(0.0001 WETH, 0, [ATTACKER_CONTRACT], [WETH, WBTC])`. When the amounts are computed by the `amounts = UniswapStyleLib.getAmountsOut(amountIn - fees, pairs, tokens);` call, the attacker contract returns fake reserves that yield 1 WBTC for the tiny input
```

**Re-entrancy point in pair.swap**

```
pair.swap(0.0001 WETH, 1 WBTC, FUND, new bytes(0)); // can re-enter here
```

**Re-enter with real WETH/WBTC pair**

```
MarginRouter.crossSwapExactTokensForTokens(30 WETH, 0, WETH_WBTC_PAIR, [WETH, WBTC])` function with the actual WETH <> WBTC pair contract
```

