All incidents

MuBank Bond Manipulation

Share
Dec 10, 2022 01:21 UTCAttackLoss: 57,659.46 USDC.e, 99,102.2 MU +1 morePending manual check1 exploit txWindow: Atomic
Estimated Impact
57,659.46 USDC.e, 99,102.2 MU +1 more
Label
Attack
Exploit Tx
1
Addresses
1
Attack Window
Atomic
Dec 10, 2022 01:21 UTC → Dec 10, 2022 01:21 UTC

Exploit Transactions

TX 1Avalanche
0xab39a17cdc200c812ecbb05aead6e6f574712170eafbd73736b053b168555680
Dec 10, 2022 01:21 UTCExplorer

Victim Addresses

0x4aa679402c6afce1e0f7eb99ca4f09a30ce228abAvalanche

Loss Breakdown

57,659.46USDC.e
99,102.2MU
9,640.24MUG

Similar Incidents

Root Cause Analysis

MuBank Bond Manipulation

1. Incident Overview TL;DR

On Avalanche block 23435295, attacker EOA 0xd46b44a0e0b90e0136cf456df633cd15a87de77e sent transaction 0xab39a17cdc200c812ecbb05aead6e6f574712170eafbd73736b053b168555680 to helper contract 0xe6c17763ca304d70a3fb334d05b2d29c2bb251e9. The helper flash-swapped MU from the Trader Joe MU/MUG pair, sold that MU into the MU/USDC.e pair to reshape both pools, then called MuBank.mu_bond and MuBank.mu_gold_bond while MuBank was reading the manipulated spot reserves. MuBank transferred 99102201128573810201054 raw MU and 9640237839603403398058 raw MUG for only 10290000000 raw USDC.e, minted 10290000000000000000000 raw MUMO to itself, and the attacker unwound the position back into 57659463903 raw USDC.e profit.

The root cause is a pricing design flaw in MuBank. Its bond quote logic reads live AMM reserves from Trader Joe and prices payouts by averaging getAmountIn and getAmountOut for the same trade size. That arithmetic mean is not manipulation resistant, so a flash-swap adversary can move the reserves and redeem MuBank inventory at an inflated payout within the same transaction.

2. Key Background

MuBank at 0x4aa679402c6afce1e0f7eb99ca4f09a30ce228ab exposes public mu_bond(address stable, uint256 amount) and mu_gold_bond(address stable, uint256 amount) entrypoints. Both functions accept approved stables such as USDC.e, transfer out MU or MUG from protocol inventory, and mint MUMO to MuBank itself. There is no delay, TWAP, or two-step settlement between quote formation and payout.

Two Trader Joe pools matter:

  • MU/USDC.e pair 0xfacb3892f9a8d55eb50fdeee00f2b3fa8a85ded5
  • MU/MUG pair 0x67d9aab77beda392b1ed0276e70598bf2a22945d

Trader Joe pairs support flash-swap callbacks through swap(..., data) and joeCall, so an attacker can borrow pool inventory intra-transaction, distort spot reserves, use the manipulated reserves against an external protocol, and repay before transaction end. That is exactly what happened here.

3. Vulnerability Analysis & Root Cause Summary

This incident is an ATTACK-category ACT opportunity against MuBank’s bond-pricing logic. MuBank does not use an oracle with any manipulation resistance; instead, it synchronously reads Trader Joe spot reserves and derives payout amounts from two opposite-side swap quote functions. In MuBank source, mu_bond and mu_gold_bond call _mu_bond_quote and _get_mug_bond_quote, then immediately transfer MU or MUG and mint MUMO. _mu_bond_quote scales the USDC.e reserve by 1e12, calls router.getAmountIn and router.getAmountOut, and averages them into mu_coin_bond_amount. _get_mug_bond_quote repeats the same pattern on the MU/MUG pool to derive the MUG bond amount. Because both helpers depend on current pair reserves, any attacker who can temporarily move those reserves can force MuBank to overpay inventory. The exploit therefore breaks the invariant that a stable deposit must not buy more MU or MUG from MuBank than it could obtain under manipulation-resistant pricing.

MuBank’s relevant source behavior is:

function mu_bond(address stable, uint256 amount) public nonReentrant {
    (uint256 mu_coin_swap_amount, uint256 mu_coin_amount) = _mu_bond_quote(amount);
    _stable.transferFrom(msg.sender, address(this), _adjusted_amount);
    IERC20(_MuCoin).transfer(msg.sender, mu_coin_amount);
    MuMoneyMinter(_MuMoney).mint(address(this), amount);
}

function mu_gold_bond(address stable, uint256 amount) public nonReentrant {
    (uint256 mu_gold_swap_amount, uint256 mu_gold_bond_amount) = _get_mug_bond_quote(amount);
    _stable.transferFrom(msg.sender, address(this), _adjusted_amount);
    IERC20(_MuGold).transfer(msg.sender, mu_gold_bond_amount);
    MuMoneyMinter(_MuMoney).mint(address(this), amount);
}
function _mu_bond_quote(uint256 amount) internal view returns (uint256 swapAmount, uint256 bondAmount) {
    (uint112 reserve0, uint112 reserve1) = Pair(0xfacB3892F9A8D55Eb50fDeee00F2b3fA8a85DED5).getReserves();
    reserve0 = reserve0 * (10 ** 12);
    uint256 amountIN = router.getAmountIn(amount, reserve1, reserve0);
    uint256 amountOUT = router.getAmountOut(amount, reserve0, reserve1);
    uint256 mu_coin_bond_amount = (((((amountIN + amountOUT) * 10)) / 2) / 10);
    return (amountOUT, mu_coin_bond_amount);
}

4. Detailed Root Cause Analysis

The exploit starts from MuBank’s quote dependency on instantaneous pool state. In the collected trace, the attacker first invoked the MU/MUG pair’s flash-swap path and borrowed 578987936812388669434540 raw MU:

0x67d9...45d::swap(578987936812388669434540, 0, 0xe6c177..., data)
  ERC20::transfer(0xe6c177..., 578987936812388669434540)
  0xe6c177...::joeCall(...)

Inside joeCall, the helper sold the borrowed MU into the MU/USDC.e pair and received 85123535028 raw USDC.e. The trace then shows MuBank reading the manipulated reserves:

JoePair::getReserves() -> 25472863623 USDC.e, 751727888303699108771531 MU
TraderJoeRouter::getAmountIn(3300000000000000000000, 751727888303699108771531, 25472863623000000000000)
TraderJoeRouter::getAmountOut(3300000000000000000000, 25472863623000000000000, 751727888303699108771531)
MuBank::mu_bond(USDC.e, 3300000000000000000000)

Those reserve reads are the breakpoint. MuBank does not observe any historical price, only the attacker-chosen state that exists after the flash-swap sell but before repayment. Under that state, MuBank transferred 99102201128573810201054 raw MU for 3300000000 raw USDC.e and then, through the chained MU/MUG quote logic, transferred 9640237839603403398058 raw MUG for another 6990000000 raw USDC.e. Balance diffs confirm the exact inventory drains and MUMO mint:

MuBank MU delta:   -99102201128573810201054
MuBank MUG delta:  -9640237839603403398058
MuBank USDC.e:     +10290000000
MuBank MUMO:       +10290000000000000000000

After extracting MuBank inventory, the helper used 74833535028 raw USDC.e to buy back 560397963969322482556602 raw MU from the MU/USDC.e pair, transferred 581014394591232029777560 raw MU back into the MU/MUG pair to satisfy the flash-swap invariant, sold the bonded 9640237839603403398058 raw MUG for 180974292926286893962912 raw MU, and finally sold 259460063432951156943008 raw MU into the MU/USDC.e pair. The last swap paid 57659463903 raw USDC.e directly to the attacker EOA, which matches the balance diff.

The flash swap is only an enabler. The actual defect is MuBank’s willingness to transfer protocol inventory against attacker-controlled reserve snapshots and invalid averaging math. If MuBank had used a manipulation-resistant oracle or delayed settlement, the same flash-swap path would not have produced the observed overpayment.

5. Adversary Flow Analysis

The adversary cluster contains:

  • EOA 0xd46b44a0e0b90e0136cf456df633cd15a87de77e, the transaction sender and final profit recipient
  • Helper contract 0xe6c17763ca304d70a3fb334d05b2d29c2bb251e9, deployed in transaction 0x9050550c8bf42653ae1affc755ca0878e972437db0206fe87aa3636ab8450250

The execution flow in transaction 0xab39a17cdc200c812ecbb05aead6e6f574712170eafbd73736b053b168555680 is:

  1. Flash-swap 578987936812388669434540 MU from the MU/MUG Trader Joe pair.
  2. Sell that MU into the MU/USDC.e pair for 85123535028 raw USDC.e, forcing MuBank’s source of price truth into an attacker-selected state.
  3. Call MuBank.mu_bond(USDC.e, 3300e18) and MuBank.mu_gold_bond(USDC.e, 6990e18) while those manipulated reserves remain live.
  4. Receive 99102201128573810201054 MU and 9640237839603403398058 MUG from MuBank, while MuBank mints 10290e18 MUMO to itself.
  5. Spend 74833535028 raw USDC.e to reacquire MU, repay the flash-swap leg in MU, then sell the bonded MUG into the now-reshaped MU/MUG pool.
  6. Sell the remaining 259460063432951156943008 raw MU into the MU/USDC.e pool and deliver 57659463903 raw USDC.e to the attacker EOA.

Every step is permissionless: Trader Joe flash swaps are public, Trader Joe router swaps are public, and MuBank bond functions are public. No privileged role, private key compromise, or attacker-specific artifact is required to realize the opportunity.

6. Impact & Losses

The measurable protocol-side extraction is:

  • 57659463903 raw USDC.e (57659.463903 USDC.e) realized by the attacker EOA
  • 99102201128573810201054 raw MU drained from MuBank inventory
  • 9640237839603403398058 raw MUG drained from MuBank inventory

MuBank also minted 10290000000000000000000 raw MUMO to itself during the bond calls. The economic effect is that MuBank accepted only 10290 USDC.e while transferring out much more valuable MU and MUG inventory under manipulated reserve conditions.

7. References

  1. Exploit transaction: 0xab39a17cdc200c812ecbb05aead6e6f574712170eafbd73736b053b168555680
  2. Helper deployment transaction: 0x9050550c8bf42653ae1affc755ca0878e972437db0206fe87aa3636ab8450250
  3. MuBank contract: 0x4aa679402c6afce1e0f7eb99ca4f09a30ce228ab
  4. MU/USDC.e pair: 0xfacb3892f9a8d55eb50fdeee00f2b3fa8a85ded5
  5. MU/MUG pair: 0x67d9aab77beda392b1ed0276e70598bf2a22945d
  6. Collected trace: /workspace/session/artifacts/collector/seed/43114/0xab39a17cdc200c812ecbb05aead6e6f574712170eafbd73736b053b168555680/trace.cast.log
  7. Balance diff: /workspace/session/artifacts/collector/seed/43114/0xab39a17cdc200c812ecbb05aead6e6f574712170eafbd73736b053b168555680/balance_diff.json
  8. MuBank verified source: https://api.etherscan.io/v2/api?chainid=43114&module=contract&action=getsourcecode&address=0x4aa679402c6afce1e0f7eb99ca4f09a30ce228ab