Reproduced Exploit

BankX Router — `swapXSDForETH` Reentrancy (BSC focus)

swapXSDForETH pulls amountInMax XSD into the pool (not the quoted amount), swaps WETH out, unwraps, and safeTransferETHs to msg.sender before burnpoolXSD(amountInMax/10).

Feb 2025BNB Chainreentrancy2 min read

Loss

~$43K across BSC + ETH + Optimism

Chain

BNB Chain

Category

reentrancy

Date

Feb 2025

EVM Playground

Source-level debugger — step opcodes and Solidity in sync

evm-hack-analyzer

The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.

Loading fork state…

Source & credit. Exploit reproduction, trace data, and analysis adapted from DeFiHackLabs by SunWeb3Sec — an open registry of reproduced on-chain exploits. Standalone Foundry PoC and full write-up: 2025-02-BankX_exp in the evm-hack-registry mirror. Upstream DeFiHackLabs PoC: src/test/…/BankX_exp.sol.


Vulnerability classes: vuln/reentrancy/single-function · vuln/logic/incorrect-state-transition

Reproduction: Foundry PoC in this project folder — ONLINE BSC archive fork. Verbose run: output.txt. Router source: sources/Router.sol.


Key info#

Loss~$43K across BSC + ETH + Optimism
Vulnerable contractBankX Router0xaadae9117df8b5d584378a41a105cc4862a16e99 (BSC)
XSD/WETH pool0x8a4e0e2a778df8ce4ea5d5108fffe690cc9ae07a
XSD0x39400e67820c88a9d67f4f9c1fbf86f3d688e9f6
Attacker EOA (BSC)0x867aa2a2667060096d0f108ddfa3367caca9fd34
Attack contract (BSC)0xab50cfdab8484e15fee82852c08eec135ac00e4c
Attack txsBSC · ETH · OP
Chain / block / dateBSC / 46,433,152 (fork 46,433,151) / Feb 8, 2025
Bug classETH refund before XSD burn + full amountInMax transfer

TL;DR#

swapXSDForETH pulls amountInMax XSD into the pool (not the quoted amount), swaps WETH out, unwraps, and safeTransferETHs to msg.sender before burnpoolXSD(amountInMax/10).

A contract attacker reenters on receive() while pid_controller still has pricecheck=true (setPriceCheck runs only after the outer body), performing additional swaps against a desynced pool and burning large XSD from the pool balance.

PoC: priceCheck() → wait block_delay (2) → reentrant swapXSDForETH7 BNB out (5 primary + 2 reentered) with reenterCount=2.


Vulnerable code#

SOLIDITY
function swapXSDForETH(uint amountOut, uint amountInMax, uint deadline) external ... {
    (uint reserveA, uint reserveB, ) = IXSDWETHpool(XSDWETH_pool_address).getReserves();
    uint amounts = BankXLibrary.quote(amountOut, reserveB, reserveA);
    require(amounts <= amountInMax, 'BankXRouter: EXCESSIVE_INPUT_AMOUNT');
    TransferHelper.safeTransferFrom(xsd_address, msg.sender, XSDWETH_pool_address, amountInMax);
    IXSDWETHpool(XSDWETH_pool_address).swap(0, amountOut, address(this));
    IWBNB(WETH).withdraw(amountOut);
    TransferHelper.safeTransferETH(msg.sender, amountOut); // ← reentrancy
    if (... ) {
        XSD.burnpoolXSD(amountInMax/10); // ← after external call; uses full amountInMax
    }
}

PoC notes#

BASH
BSC_RPC_URL=https://bsc-mainnet.public.blastapi.io forge test --match-contract BankX_exp -vv
  • Requires PID priceCheck() then vm.roll(+block_delay).
  • ONLINE only (no anvil_state).
  • Demonstrates reentrancy multi-swap; live attack optimized amounts across 3 chains.

References#


Sources & further analysis#

Reproductions & code

Alerts & third-party analyses

These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.