All incidents

CCV Treasury Rebalancer Attack

Share
Dec 28, 2023 05:08 UTCAttackLoss: 6,489.05 CCV, 896.25 USDTPending manual check1 exploit txWindow: Atomic
Estimated Impact
6,489.05 CCV, 896.25 USDT
Label
Attack
Exploit Tx
1
Addresses
2
Attack Window
Atomic
Dec 28, 2023 05:08 UTC → Dec 28, 2023 05:08 UTC

Exploit Transactions

TX 1BSC
0x6ba4152db9da45f5751f2c083bf77d4b3385373d5660c51fe2e4382718afd9b4
Dec 28, 2023 05:08 UTCExplorer

Victim Addresses

0x37177ccc66ef919894cef37596bbebd76e7a40b2BSC
0xe38d7ff85bb801d35382eef15eb8263f2c751ecdBSC

Loss Breakdown

6,489.05CCV
896.25USDT

Similar Incidents

Root Cause Analysis

CCV Treasury Rebalancer Attack

1. Incident Overview TL;DR

On BNB Chain block 34739874, transaction 0x6ba4152db9da45f5751f2c083bf77d4b3385373d5660c51fe2e4382718afd9b4 used a public DODO flash loan to manipulate the CCV/USDT Pancake pair around two treasury rebalance proxies: sell proxy 0x37177ccc66ef919894cef37596bbebd76e7a40b2 and buy proxy 0xe38d7ff85bb801d35382eef15eb8263f2c751ecd. The attacker forced the treasury to sell CCV into a depressed price, bought CCV with flash-loaned USDT, forced the treasury to buy CCV back at the manipulated higher price, then exited into USDT and repaid the flash loan.

The root cause is a pair of public treasury rebalance entry points that spend treasury inventory on PancakeRouter without caller authorization and with amountOutMin=0. That makes the treasury a deterministic counterparty to flash-loan-driven price manipulation instead of a protected operator-only rebalancer.

2. Key Background

The victim system is implemented as two proxy-held treasury accounts. The sell-side proxy holds CCV and swaps CCV to USDT for the buy-side proxy; the buy-side proxy holds USDT and swaps USDT back to CCV for the sell-side proxy. Both routes use PancakeRouter 0x10ED43C718714eb63d5aA57B78B54704E256024E against the CCV/USDT pair 0x4da070f3c4295389ddff6d4398650001e412cb39.

The proxy shells are explorer-verified ERC1967Proxy contracts, but the exploit block did not execute the explorer-reported current implementation metadata. Historical EIP-1967 storage at block 34739873 resolves the actual logic contracts to sell logic 0x18F6e45B017187E19E62BA0118621c9A2200Ce0C and buy logic 0x238217598aBb32A3a031f6a9cccC86F5946A07e3. Those logic contracts are unverified, so the correct evidence is historical storage, runtime selector extraction, and traced delegated execution.

Historical implementation resolution:

sell_proxy 0x37177ccc66ef919894cef37596bbebd76e7a40b2
0x00000000000000000000000018f6e45b017187e19e62ba0118621c9a2200ce0c
buy_proxy 0xe38d7ff85bb801d35382eef15eb8263f2c751ecd
0x000000000000000000000000238217598abb32a3a031f6a9cccc86f5946a07e3

Runtime selector extraction from those historical logic contracts shows the treasury-management surface:

0x18F6...: ccv(), usdt(), pancakeRouter(), balance(address), hasRole(bytes32,address), 0x369baafe(uint256)
0x2382...: ccv(), usdt(), pancakeRouter(), balance(address), hasRole(bytes32,address), 0xb7da6a49(uint256)

The attacker helper 0xb2f22296661ccc5530ebdbabb8264b82e977504d held neither DEFAULT_ADMIN_ROLE nor MANAGE_ROLE on either proxy. The exploit is therefore ACT: an arbitrary unprivileged caller can trigger the same treasury swaps.

3. Vulnerability Analysis & Root Cause Summary

This is an ATTACK root cause, not a benign MEV pattern. The safety invariant is straightforward: only authorized treasury operators should be able to move proxy-held assets, and any treasury AMM swap should reject materially manipulated execution with a meaningful minimum output. The implementation violated both halves of that invariant.

At block 34739873, sell proxy 0x37177... delegated selector 0x369baafe(uint256) into logic 0x18F6..., and buy proxy 0xe38d... delegated selector 0xb7da6a49(uint256) into logic 0x2382.... Historical traces show that both paths approve treasury tokens to PancakeRouter and call swapExactTokensForTokensSupportingFeeOnTransferTokens with amountOutMin hard-coded to zero. Separate historical calls from unrelated address 0x1111111111111111111111111111111111111111 succeed on both selectors and show no preceding role-gate. That means any caller can force the treasury to trade against the live AMM spot price.

Once that surface exists, a flash-loan adversary can deterministically choose the order: treasury sell first to move price down, attacker buy second to accumulate cheap CCV, treasury buyback third to overpay into the manipulated market, attacker exit last to crystallize profit. The flash loan is only the capital amplifier. The actual bug is public, zero-slippage treasury trading logic.

4. Detailed Root Cause Analysis

Immediately before the exploit transaction, the CCV/USDT pair held 104740664590590730857512 raw USDT and 112978784535369593584814 raw CCV. The sell proxy held 9057418516433752518165 raw CCV, and the buy proxy held 896254526292098922216 raw USDT. Those balances are sufficient for the treasury to become the victim leg of a self-contained manipulation cycle.

The first decisive evidence is the unrelated-caller historical sell trace. It shows an arbitrary caller reaching the sell proxy, delegating into the historical sell logic, approving proxy-held CCV, and executing a PancakeRouter swap with zero slippage protection:

ERC1967Proxy::fallback(0x369baafe...)
  0x18F6e45B017187E19E62BA0118621c9A2200Ce0C::369baafe(...) [delegatecall]
    CCV::approve(PancakeRouter, 35157884829819346944)
    PancakeRouter::swapExactTokensForTokensSupportingFeeOnTransferTokens(
      35157884829819346944,
      0,
      [CCV, USDT],
      0xE38d7ff85bB801D35382eeF15eB8263F2c751ecd,
      1703740467
    )

The unrelated-caller historical buy trace proves the symmetric bug on the USDT side:

ERC1967Proxy::fallback(0xb7da6a49...)
  0x238217598aBb32A3a031f6a9cccC86F5946A07e3::b7da6a49(...) [delegatecall]
    BEP20USDT::approve(PancakeRouter, 896254526292098922216)
    PancakeRouter::swapExactTokensForTokensSupportingFeeOnTransferTokens(
      896254526292098922216,
      0,
      [USDT, CCV],
      0x37177ccC66ef919894CeF37596BBebd76E7A40B2,
      1703740467
    )

The exploit transaction used those exact public paths with larger balances. The seed trace shows the attacker helper receiving 100000000000000000000000 raw USDT from DODO DPP 0x6098a5638d8d7e9ed2f952d35b2b67c34ec6b476, then calling the sell proxy to dump 9000418516433752817664 raw CCV, then buying 59977618482634557809307 raw CCV with the flash-loaned USDT, then calling the buy proxy to spend 8606802835639752029023 raw USDT on only 2511367361827836275070 raw CCV for the treasury.

That order matters. The forced treasury sell worsens the CCV price before the attacker buys. The attacker-controlled buy then pulls CCV out of the pair while using flash-loaned USDT. The forced treasury buyback spends treasury USDT at the manipulated price because the buy-side logic also accepts any output. After that, the attacker holds inventory acquired before the treasury’s overpaying leg and can sell back into the re-priced pair.

The balance diff closes the proof. The attacker EOA 0x835b45d38cbdccf99e609436ff38e31ac05bc502 gained 3171894324298232046025 raw USDT. The sell proxy lost 6489051154605916542594 raw CCV. The buy proxy lost its full 896254526292098922216 raw USDT balance. Gas cost was 1306725000000000 wei BNB, which the collected valuation artifact prices at 424164109164511869 raw USDT, leaving the transaction strongly net positive at 3171470160189067534156 raw USDT after fees.

5. Adversary Flow Analysis

The adversary flow is fully contained in one attacker-crafted transaction:

  1. EOA 0x835b45d38cbdccf99e609436ff38e31ac05bc502 calls helper contract 0xb2f22296661ccc5530ebdbabb8264b82e977504d.
  2. The helper borrows 100000000000000000000000 raw USDT from the public DODO DPP pool.
  3. The helper calls sell proxy 0x37177... with selector 0x369baafe, causing treasury CCV to be swapped into USDT for buy proxy 0xe38d....
  4. The helper swaps the flash-loaned USDT into CCV on PancakeRouter while price is depressed by the treasury sell.
  5. The helper calls buy proxy 0xe38d... with selector 0xb7da6a49, causing treasury USDT to be spent buying CCV back for sell proxy 0x37177... at the now-manipulated price.
  6. The helper sells its CCV inventory back into USDT, repays the flash loan, and transfers the residual USDT to the originating EOA.

Seed exploit trace excerpt:

sellProxy::369baafe(...)
  0x18F6...::369baafe(...) [delegatecall]
  pancakeRouter::swapExactTokensForTokensSupportingFeeOnTransferTokens(..., 0, [CCV, USDT], buyProxy, ...)

attacker swapExactTokensForTokensSupportingFeeOnTransferTokens(
  100000000000000000000000,
  0,
  [USDT, CCV],
  attacker,
  ...
)

buyProxy::b7da6a49(...)
  0x2382...::b7da6a49(...) [delegatecall]
  pancakeRouter::swapExactTokensForTokensSupportingFeeOnTransferTokens(..., 0, [USDT, CCV], sellProxy, ...)

The two treasury swaps are not reactions to a victim transaction. They are active building blocks invoked by the attacker. That is why the incident is ACT: the profitable sequence is attacker-authored end to end.

6. Impact & Losses

The measurable treasury losses are:

  • 6489051154605916542594 raw CCV (decimal = 18) from sell proxy 0x37177...
  • 896254526292098922216 raw USDT (decimal = 18) from buy proxy 0xe38d...

The attacker realized 3171894324298232046025 raw USDT before gas and 3171470160189067534156 raw USDT after pricing the BNB gas spend through PancakeRouter at the exploit block. The treasury’s economic harm is larger than a simple role misuse because both proxies are transformed into manipulable inventory endpoints: anyone able to source transient liquidity can re-run the same pattern whenever the proxies remain funded and the public selectors remain exposed.

7. References

  1. Seed transaction metadata: 0x6ba4152db9da45f5751f2c083bf77d4b3385373d5660c51fe2e4382718afd9b4
  2. Seed execution trace showing flash loan, delegated treasury swaps, exit, and repayment
  3. Seed balance diff showing attacker profit and treasury depletion
  4. Historical EIP-1967 implementation resolution for both proxies
  5. Historical selector extraction for sell logic 0x18F6... and buy logic 0x2382...
  6. Historical public sell call trace from unrelated caller 0x1111111111111111111111111111111111111111
  7. Historical public buy call trace from unrelated caller 0x1111111111111111111111111111111111111111
  8. Explorer source status for the proxy shells and historical logic contracts