Matmo MAMO Treasury Drain
Exploit Transactions
0x189a8dc1e0fea34fd7f5fa78c6e9bdf099a8d575ff5c557fa30d90c6acd0b29fVictim Addresses
0x4341bdced3908a45835c67a2dbbde2d2daa6645dBSC0x5813d7818c9d8f29a9a96b00031ef576e892def4BSCLoss Breakdown
Similar Incidents
GGGTOKEN Treasury Drain via receive()
38%Eterna Buyback Treasury Drain
38%Public Treasury Spend on BRAND Helper
38%BCT Referral Treasury Drain
37%Elephant Treasury Sweep Sandwich
37%CCV Treasury Rebalancer Attack
36%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...5841whitelisted in_liquidity. - The helper owner was not the attacker.
- The helper's minimum payment was only
0.01 BNB, far below the19 BNBfunded 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:
0x829Fe73463cEAE6579973b8bcd1e018976040ec4called attacker contract0xd7a7d90B63da1B4E7eF79cb36935D38aF0D6D0b4.- The contract borrowed
19 WBNBfrom DODO pool0xD534fAE679f7F02364D177E9D44F1D15963c0Dd7. - It unwrapped the WBNB into BNB and called the public helper
BuyToken(address)with the MAMO/USDT pair as the argument. - The helper used its pre-existing MAMO privilege to mint
9.5e25MAMO treasury units to the attacker contract and9.5e24MAMO treasury units to the MAMO/USDT pair. - The attacker contract called the MAMO/USDT pair directly, received
5683062170081466106194USDT, approved PancakeRouter0x10ED43C718714eb63d5aA57B78B54704E256024E, and swapped the USDT through the USDT/WBNB pair0x16b9a82891338f9bA80E2D6970FddA79D1eb0daE. - The backswap returned
24680683707976806110WBNB, from which the attacker repaid the19 WBNBflashloan and transferred the remaining5680683707976806110WBNB 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:
0x189a8dc1e0fea34fd7f5fa78c6e9bdf099a8d575ff5c557fa30d90c6acd0b29fon BNB Smart Chain block34083189. - 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
34083188confirming the helper whitelist flag, helper owner, and helper minimum-payment threshold.