All incidents

Matmo MAMO Treasury Drain

Share
Dec 05, 2023 08:05 UTCAttackLoss: 5,683.06 USDTPending manual check1 exploit txWindow: Atomic
Estimated Impact
5,683.06 USDT
Label
Attack
Exploit Tx
1
Addresses
2
Attack Window
Atomic
Dec 05, 2023 08:05 UTC → Dec 05, 2023 08:05 UTC

Exploit Transactions

TX 1BSC
0x189a8dc1e0fea34fd7f5fa78c6e9bdf099a8d575ff5c557fa30d90c6acd0b29f
Dec 05, 2023 08:05 UTCExplorer

Victim Addresses

0x4341bdced3908a45835c67a2dbbde2d2daa6645dBSC
0x5813d7818c9d8f29a9a96b00031ef576e892def4BSC

Loss Breakdown

5,683.06USDT

Similar Incidents

Root Cause Analysis

Matmo MAMO Treasury Drain

1. Incident Overview TL;DR

On BNB Smart Chain block 34083189, transaction 0x189a8dc1e0fea34fd7f5fa78c6e9bdf099a8d575ff5c557fa30d90c6acd0b29f let an unprivileged adversary drain the MAMO/USDT PancakeSwap pool by abusing Matmo's public buy helper. The attacker EOA 0x829Fe73463cEAE6579973b8bcd1e018976040ec4 called its own contract 0xd7a7d90B63da1B4E7eF79cb36935D38aF0D6D0b4, borrowed 19 WBNB from DODO, unwrapped it to BNB, and then called helper 0xa915Bb6D5C117fB95E9ac2edDaE68AAd5EdB5841::BuyToken(address).

That helper was already whitelisted inside the MAMO token and used its privilege to call giveawayOne twice: once for the attacker contract and once for the MAMO/USDT pair 0x5813d7818c9d8F29A9a96B00031ef576E892DEf4. Because MAMO counts treasury credits inside balanceOf(account), the pair's effective MAMO balance jumped even though no funded user transfer moved MAMO into the pair. PancakePair then treated the pair-side treasury credit as fresh token input and paid out 5683062170081466106194 USDT. The attacker swapped that USDT into WBNB, repaid the flashloan, and kept 5680683707976806110 WBNB profit before gas.

2. Key Background

Matmo's MAMO token is not a plain ERC-20 balance ledger. It tracks normal balances in _balances, but it also tracks treasury buckets in _treasury, and balanceOf(account) returns both together. The transfer path can settle from those treasury buckets, so treasury credits are economically spendable.

The critical victim-side code is in the verified MAMO source:

function giveawayOne(
    address _addr,
    uint _amount,
    uint8 times
) external returns (bool) {
    require(_liquidity[_msgSender()] == 1, "Error: Operation failed");
    if (times == 1) {
        _treasury[ANCHOR][_addr].incrFund(_amount);
    } else if (times > 1) {
        _treasury[BANK][_addr].incrFund(_amount);
    }
    emit Transfer(address(0), _addr, _amount);
    _capacity += _amount;
    return true;
}

function balanceOf(address account) public view returns (uint256) {
    return _balances[account] + getTreasury(account);
}

This matters because giveawayOne does not debit any existing holder. It only requires that msg.sender is marked in _liquidity. Historical storage at block 34083188 confirms _liquidity[0xa915Bb6D5C117fB95E9ac2edDaE68AAd5EdB5841] = 1, so the helper had mint privilege before the exploit transaction.

The helper itself was not attacker-owned. Historical storage at the same block shows helper slot 0 was 0x92a9a66763b466956ac8eefda8921c8c03715464, not the attacker EOA, and slot 14 was 10000000000000000 wei, meaning the public path only required a 0.01 BNB minimum payment. The MAMO/USDT pair also held live liquidity before the exploit, with reserves:

reserve0 (MAMO): 30503694129561858470407619
reserve1 (USDT): 23976626623894851541714

3. Vulnerability Analysis & Root Cause Summary

The root cause is an access-control and accounting failure in the Matmo token system, not a PancakeSwap bug. Matmo exposed a privileged mint path through a public helper contract. That helper could be called by any user who paid the configured minimum BNB amount, and once invoked it called MAMO.giveawayOne(...) under the helper's whitelisted identity.

The decisive accounting flaw is that MAMO counts treasury credits immediately inside balanceOf(account). When the helper minted treasury-backed MAMO to the MAMO/USDT pair, PancakePair observed a higher token balance even though no attacker-funded MAMO transfer had entered the pair. That violated the AMM assumption that reserve-affecting token balances only increase when real inventory is transferred in. The code-level breakpoint is therefore the combination of giveawayOne and balanceOf, reached through the public helper BuyToken(address) entry point.

This incident is ACT. The exploit required no privileged key, no private orderflow, and no attacker-owned protocol role. An unprivileged caller only needed public on-chain state, a flashloan, and the helper's public payable entry point.

4. Detailed Root Cause Analysis

4.1 Pre-state and exploit conditions

Immediately before the exploit transaction, the public pre-state already satisfied all exploit conditions:

  • MAMO had helper 0xa915...5841 whitelisted in _liquidity.
  • The helper owner was not the attacker.
  • The helper's minimum payment was only 0.01 BNB, far below the 19 BNB funded by the flashloan.
  • The MAMO/USDT pair still contained real USDT liquidity to withdraw.

Those conditions are sufficient to make the privileged mint path publicly reachable.

4.2 Code-level breakpoint

The invariant that should have held is: the pair-side token balance used by PancakePair pricing must only increase when real transferable token inventory is moved into the pair by a funded actor. MAMO breaks that invariant because pair-side treasury accounting is treated as spendable balance:

function _safeTransfer(
    address account_,
    address recipient,
    uint amount
) internal returns (uint) {
    uint left = amount;
    ...
    for (uint8 i = 0; left > 0 && i < ROUND; i++) {
        left = _treasury[i][account_].settle(left);
    }
    require(left == 0, "Failed: Invalid balance");
    _balances[recipient] += amount;
    return amount;
}

Because treasury value can later settle into transfers, balanceOf(pair) is not merely cosmetic. Once the helper mints treasury-backed MAMO to the pair, PancakePair's reserve accounting sees that treasury credit as immediately usable token input.

4.3 Trace evidence from the seed transaction

The historical trace shows the entire exploit path:

0xd7a7...::DVMFlashLoanCall(...)
  WBNB::withdraw(19000000000000000000)
  0xa915...::BuyToken{value: 19000000000000000000}(PancakePair: [0x5813...DEf4])
    MAMO::giveawayOne(0xd7a7..., 95000000000000000000000000, 2)
    MAMO::giveawayOne(PancakePair: [0x5813...DEf4], 9500000000000000000000000, 1)
  PancakePair::swap(0, 5683062170081466106194, 0xd7a7..., 0x)

The same trace records the pair-side swap accounting:

emit Swap(
  sender: 0xd7a7d90B63da1B4E7eF79cb36935D38aF0D6D0b4,
  amount0In: 9500000000000000000000000,
  amount1In: 0,
  amount0Out: 0,
  amount1Out: 5683062170081466106194,
  to: 0xd7a7d90B63da1B4E7eF79cb36935D38aF0D6D0b4
)

amount0In here is the synthetic MAMO input manufactured by the helper's treasury mint. No attacker-funded MAMO transfer preceded it. That is the precise point where the AMM accounting invariant breaks.

5. Adversary Flow Analysis

The attacker flow was a single deterministic transaction:

  1. 0x829Fe73463cEAE6579973b8bcd1e018976040ec4 called attacker contract 0xd7a7d90B63da1B4E7eF79cb36935D38aF0D6D0b4.
  2. The contract borrowed 19 WBNB from DODO pool 0xD534fAE679f7F02364D177E9D44F1D15963c0Dd7.
  3. It unwrapped the WBNB into BNB and called the public helper BuyToken(address) with the MAMO/USDT pair as the argument.
  4. The helper used its pre-existing MAMO privilege to mint 9.5e25 MAMO treasury units to the attacker contract and 9.5e24 MAMO treasury units to the MAMO/USDT pair.
  5. The attacker contract called the MAMO/USDT pair directly, received 5683062170081466106194 USDT, approved PancakeRouter 0x10ED43C718714eb63d5aA57B78B54704E256024E, and swapped the USDT through the USDT/WBNB pair 0x16b9a82891338f9bA80E2D6970FddA79D1eb0daE.
  6. The backswap returned 24680683707976806110 WBNB, from which the attacker repaid the 19 WBNB flashloan and transferred the remaining 5680683707976806110 WBNB to the originating EOA.

The profit realization is explicit in the trace:

WBNB::transfer(DVM: [0xD534...0Dd7], 19000000000000000000)
WBNB::transfer(0x829Fe73463cEAE6579973b8bcd1e018976040ec4, 5680683707976806110)

This also explains the adversary cluster: the EOA is the gas payer and final profit recipient, while contract 0xd7a7... is the transient execution harness that performed the public helper call, pair swap, and flashloan settlement.

6. Impact & Losses

The MAMO/USDT pair lost 5683062170081466106194 USDT units in the exploit transaction. On BSC USDT uses 18 decimals, so the drained amount is 5683.062170081466106194 USDT.

The attacker EOA received 5680683707976806110 WBNB after settlement and paid 1088997000000000 wei in gas. The exploit path remained permissionless as long as three conditions held simultaneously: the helper stayed whitelisted, the helper's public payable entry point stayed callable, and the MAMO/USDT pair still held counter-asset liquidity.

7. References

  • Incident transaction: 0x189a8dc1e0fea34fd7f5fa78c6e9bdf099a8d575ff5c557fa30d90c6acd0b29f on BNB Smart Chain block 34083189.
  • Verified MAMO token contract: 0x4341bdCEd3908A45835C67A2DbBDe2d2dAA6645D.
  • Public helper contract: 0xa915Bb6D5C117fB95E9ac2edDaE68AAd5EdB5841.
  • Victim Pancake pair: 0x5813d7818c9d8F29A9a96B00031ef576E892DEf4.
  • Flashloan source: DODO DVM 0xD534fAE679f7F02364D177E9D44F1D15963c0Dd7.
  • Independent validation inputs: historical seed trace, seed balance diff, verified MAMO source code, MAMO storage layout, helper runtime code, and historical storage reads at block 34083188 confirming the helper whitelist flag, helper owner, and helper minimum-payment threshold.