All incidents

DMi outfee misrouting collapses pair reserves under flash-loan sells

Share
Dec 08, 2025 15:55 UTCAttackLoss: 138.68 WBNBManually checked1 exploit txWindow: Atomic
Estimated Impact
138.68 WBNB
Label
Attack
Exploit Tx
1
Addresses
2
Attack Window
Atomic
Dec 08, 2025 15:55 UTC → Dec 08, 2025 15:55 UTC

Exploit Transactions

TX 1BSC
0x2330a8cf71d301a0f5c2f617f6e0a4c3ec5005ac1953fbccbb72f905113b9b6a
Dec 08, 2025 15:55 UTCExplorer

Victim Addresses

0xfbe049df1364fe120a0e7f0ddf586dbb38c79219BSC
0x86be038dfbf6506ca186af1001b7db201ed0586eBSC

Loss Breakdown

138.68WBNB

Similar Incidents

Root Cause Analysis

DMi outfee misrouting collapses pair reserves under flash-loan sells

1. Incident Overview TL;DR

TX 0x2330a8cf71d301a0f5c2f617f6e0a4c3ec5005ac1953fbccbb72f905113b9b6a borrowed 102,693 WBNB from Moolah, pushed it through the DMi/WBNB Pancake pair, and repaid the loan while leaving 138.68 WBNB sitting in the attacker cluster for EOA 0x47f7b5d64644e88c9f773a379674c768dfe78e05, paying only ~0.0497 BNB in native gas. The exploit succeeds because every sell into the pair routes the DMi tokens to slot outfee_add (which defaults to the zero address), so the pair never receives the sold DMi and its constant-product reserve quickly collapses.

2. Key Background

DMi is an ERC1967-upgradeable token with numerous reward hooks and a Pancake swap interface; its ERC20 implementation lives in src/token/DMi/DMi.sol and inherits storage from mod/m_storage.sol. The token trades against WBNB on the verified Pancake pair 0x86be038dfbf6506ca186af1001b7db201ed0586e, which runs the standard Pancake pair contract (artifacts/root_cause/data_collector/iter_1/contract_source/0x86be038dfbf6506ca186af1001b7db201ed0586e/src/Contract.sol:1). Normal trading expects the pair to receive the tokens sent by sellers so the reserve ratios track traders' balances; any deviation from that assumption breaks slippage control.

3. Vulnerability Analysis & Root Cause Summary

DMi.transfer checks _isPancakePair(to) and, when a Pancake pair is the recipient, reroutes the transfer to outfee_add before calling _transfer and invoking _swap_deal. At the same time, outfee_add is declared in m_storage.sol but never written to, so every Pancake pair sell deducts DMi from the seller and credits the zero address instead of the pair. This means the DMi/WBNB pair consistently records WBNB inflows without the matching DMi inflows, so its constant-product invariant degenerates and flash swaps can drain the WBNB reserve while the DMi reserve sits near zero.

4. Detailed Root Cause Analysis

The hooked transfer path sits at the center of the issue:

    function transfer(address to, uint256 amount) public override returns (bool) {
        address owner = _msgSender();
        _transfer_before(owner, to, amount);
        if (_isPancakePair(to)) {
            _transfer(owner, outfee_add, amount);
        } else {
            _transfer(owner, to, amount);
        }
        _swap_deal(owner, to, amount);
        return true;
    }

Here _isPancakePair(to) matches the DMi/WBNB pair, and the sell path intentionally reroutes the funds to outfee_add rather than the pair itself; the _swap_deal hook runs after the rerouted transfer. The storage module declares the slot but never updates it:

    IPancakeRouter public router;
    bool is_this_swap;
    address outfee_add;

A repository-wide scan shows no assignment to outfee_add, so it stays the default zero address forever. As a result, every user sale burns DMi to address(0), leaving the pair with WBNB but no corresponding DMi balance. The ensuing trace for the exploit transaction demonstrates the invariant collapse: after burning the DMi inflows the pair still reports ~1.539×10²² WBNB and only 502 DMi, and it emits a Sync before sending that WBNB to the attack contract.

Trace excerpt (cast run -vvvvv for tx 0x2330a8cf... lines 57700-57725):

    ├─ [893] PancakePair::getReserves() [staticcall]
    │   └─ ← [Return] 15393575117076498217329 [1.539e22], 502, 1765209308 [1.765e9]
    ├─ emit Sync(reserve0: 1, reserve1: 135698123795054990706267220 [1.356e26])
    ├─ emit Swap(sender: 0xdD065A63792B5ed26d412BFe74591074dFdc28e1, amount0In: 0, amount1In: 135698123795054990706266718 [1.356e26], amount0Out: 15393575117076498217328 [1.539e22], amount1Out: 0, to: 0xdD065A63792B5ed26d412BFe74591074dFdc28e1)

The getReserves/Sync snapshot proves the reserve pairing is WBNB-heavy and holds almost no DMi, while the subsequent swap drains 1.539e22 WBNB to the attacker contract. Because each sell burns DMi rather than updating the pair's reserve, the constant-product invariant can never defend the pool when the attack contract orchestrates a flash swap followed by the Pancake callback loop in the trace.

5. Adversary Flow Analysis

  1. Funding – The adversary EOA 0x47f7b5d64644e88c9f773a379674c768dfe78e05 received multiple BNB transfers (e.g., txs 0x319a7..., 0xf76a4..., and 0xe2d60... recorded in artifacts/root_cause/data_collector/iter_1/normal_txlist/0x47f7b5d64644e88c9f773a379674c768dfe78e05.json). These transfers supplied the router fees and deployment gas.
  2. Orchestrator deployment – In tx 0x21ae02bbc7fb5d8681b82c0b76b83444937bcfccb167ee69b2849ddf74b30bd2, the EOA deployed the helper contract 0xdd065a63792b5ed26d412bfe74591074dfdc28e1, which later handles the flash loan callback and all Pancake interactions.
  3. Flash loan + swap – In tx 0x2330a8cf71d301a0f5c2f617f6e0a4c3ec5005ac1953fbccbb72f905113b9b6a, the helper contract borrowed 102,693 WBNB from Moolah, initiates repeated Pancake swaps that sell DMi but burn the tokens instead of crediting the pair, and repays the flash loan while keeping 138.68 WBNB. The final WBNB transfer in the trace moves the profit from the helper contract back to 0x47f7b5d64644e88c9f773a379674c768dfe78e05.

6. Impact & Losses

Pancake pair 0x86be038dfbf6506ca186af1001b7db201ed0586e lost almost all of its DMi reserve while holding the original WBNB balance, so any follow-up swap would experience catastrophic slippage. The attacker netted 138.68 WBNB (≈138,680 WBNB in raw units) as shown in the trace, with the pair left at only 502 DMi. The same trace records the final profit handoff:

    ├─ [25162] WBNB::transfer(0x47f7B5D64644E88C9f773A379674c768DFe78E05, 138680856910599083039 [1.386e20])
    │   ├─ emit Transfer(from: 0xdD065A63792B5ed26d412BFe74591074dFdc28e1, to: 0x47f7B5D64644E88C9f773A379674c768DFe78E05, value: 138680856910599083039 [1.386e20])

This is the recorded profit after the flash swap and loan repayment. The victim cluster (DMi token and its Pancake pair) therefore lost DMi reserve consistency and paid out 138.68 WBNB to the adversary.

7. References

  • Trace log for tx 0x2330a8cf71d301a0f5c2f617f6e0a4c3ec5005ac1953fbccbb72f905113b9b6a (artifacts/root_cause/seed/56/0x2330a8cf71d301a0f5c2f617f6e0a4c3ec5005ac1953fbccbb72f905113b9b6a/trace.cast.log)
  • DMi implementation (artifacts/root_cause/seed/56/0xd88c707e32651ba7e9a08446ab0fcfb962934cf8/src/token/DMi/DMi.sol)
  • Standard Pancake pair contract (artifacts/root_cause/data_collector/iter_1/contract_source/0x86be038dfbf6506ca186af1001b7db201ed0586e/src/Contract.sol)
  • Normal TX list for 0x47f7b5d64644e88c9f773a379674c768dfe78e05 (artifacts/root_cause/data_collector/iter_1/normal_txlist/0x47f7b5d64644e88c9f773a379674c768dfe78e05.json)