xDAO / Unicorn Finance DAO Public LP Offer Treasury Drain
Exploit Transactions
0x933d19d7d822e84e34ca47ac733226367fbee0d9c0c89d88d431c4f99629d77aVictim Addresses
0xca49ecf7e7bb9bbc9d1d295384663f6ba5c0e366BSC0x2101e0f648a2b5517fd2c5d9618582e9de7a651aBSC0xf887a2dac0dd432997c970bce597a94ead4a8c25BSCLoss Breakdown
Similar Incidents
GGGTOKEN Treasury Drain via receive()
35%NeverFallToken LP Drain
32%CS Pair Balance Burn Drain
31%Cellframe Migration Drain
31%Thena Strategy Public Unstake Drain
31%SellToken Arbitrary-Pair LP Drain
31%Root Cause Analysis
xDAO / Unicorn Finance DAO Public LP Offer Treasury Drain
1. Incident Overview TL;DR
On BSC block 24705059, transaction 0x933d19d7d822e84e34ca47ac733226367fbee0d9c0c89d88d431c4f99629d77a exploited xDAO's public LP sale for Unicorn Finance DAO (0x2101e0f648a2b5517fd2c5d9618582e9de7a651a). The sender 0xc578d755cd56255d3ff6e92e1b6371ba945e3984 deployed helper contract 0x1bBed50a248CFd2Ce498E2219C92F86aD657e7AC, swapped 0.4 BNB for USDC on PancakeSwap, bought DAO LP from xDAO Shop at a fixed rate of 1 USDC per LP, burned that LP against the DAO treasury, repeated the same round trip a second time, and finished with 90070.588320368098073575 USDC in the attacker EOA.
The root cause is a deterministic accounting mismatch between issuance and redemption. Shop.buyPublicOffer minted LP at a stale fixed price and never read DAO treasury balances or LP supply, while LP.burn and Dao.burnLp redeemed LP against the live treasury. At the vulnerable pre-state, the DAO had only 6.8 LP outstanding but already held 90000.668202254645779194 USDC and 0.032328555433316689 BNB, so buying temporary LP at 1 USDC each and immediately burning it returned a vastly larger pro-rata share of treasury assets.
2. Key Background
xDAO uses a Shop contract at 0xca49ecf7e7bb9bbc9d1d295384663f6ba5c0e366 to sell LP for a DAO through publicOffers[dao]. Each public offer stores three values: isActive, currency, and rate. For Unicorn Finance DAO, the offer was active at block 24705058, used USDC (0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d) as the payment token, and priced LP at 1e18, which corresponds to 1 USDC per 1 LP.
The DAO points to LP token 0xf887a2dac0dd432997c970bce597a94ead4a8c25. That LP token is a standard ERC-20 share token with a public burn path. When a holder burns LP, the LP contract computes a share ratio from the current totalSupply() and forwards that ratio into Dao.burnLp, which transfers the same share of the DAO's live native-token balance and requested ERC-20 balances.
Independent state queries at block 24705058, immediately before the exploit transaction, confirm the vulnerable configuration:
Shop.publicOffers(DAO) = (true, USDC, 1000000000000000000)
Dao.lp() = 0xf887A2DaC0DD432997C970BCE597A94EaD4A8c25
LP.totalSupply() = 6800000000000000000
USDC.balanceOf(DAO) = 90000668202254645779194
BNB balance of DAO = 32328555433316689
Those values mean the DAO treasury already held more than 90000 USDC while only 6.8 LP existed, so treasury NAV per LP was orders of magnitude above the Shop sale price.
3. Vulnerability Analysis & Root Cause Summary
This incident is an ACT-style contract attack caused by inconsistent share pricing across issuance and redemption. The Shop contract sold new LP at a fixed rate, but the LP and DAO contracts redeemed LP against the full live treasury. Because issuance ignored both LP.totalSupply() and DAO asset balances, the public offer remained underpriced even after the treasury had accumulated substantial assets. Any unprivileged account that could acquire the payment token was able to execute the exploit path.
The vulnerable code path is explicit in the verified contracts:
// xDAO Shop: fixed-price minting
IERC20(publicOffer.currency).safeTransferFrom(
msg.sender,
_dao,
(_lpAmount * publicOffer.rate) / 1e18
);
address lp = IDao(_dao).lp();
bool b = ILP(lp).mint(msg.sender, _lpAmount);
// Unicorn Finance LP: live-supply redemption share
uint256 _share = (1e18 * _amount) / totalSupply();
_burn(msg.sender, _amount);
bool b = IDao(dao).burnLp(
msg.sender,
_share,
_tokens,
_adapters,
_pools
);
// Unicorn Finance DAO: redeem against live treasury balances
payable(_recipient).sendValue((address(this).balance * _share) / 1e18);
_tokenShares[i] =
(IERC20(_tokens[i]).balanceOf(address(this)) * _share) / 1e18;
IERC20(_tokens[i]).safeTransfer(_recipient, _tokenShares[i]);
The broken invariant is: a permissionless LP buy-then-burn round trip must not be profitable. That requires the issuance path to price LP at or above redeemable treasury NAV per LP. xDAO violated that invariant by combining fixed-price issuance with NAV-based redemption.
4. Detailed Root Cause Analysis
Let:
S= LP supply before a new purchase.A= DAO treasury value already held in the payment asset.r= fixed Shop sale rate.X= LP amount purchased.
Shop.buyPublicOffer charges:
C = X * r / 1e18
and mints X LP without considering S or A.
If the buyer immediately burns that LP, LP.burn computes:
share = X / (S + X)
and Dao.burnLp returns:
(A + C) * X / (S + X)
of each treasury asset. Profit is positive whenever A > r * S, because the buyer pays the stale fixed price but redeems against live treasury NAV. In this incident:
S = 6.8e18r = 1e18A = 90000.668202254645779194 USDCbefore counting BNBr * S = 6.8 USDC
The inequality A > r * S was therefore massively true before the exploit started.
The trace shows the exploit realizing that mismatch twice in the same transaction:
0x10ED43C718714eb63d5aA57B78B54704E256024E::swapExactETHForTokensSupportingFeeOnTransferTokens(...)
0xCA49EcF7e7bb9bBc9D1d295384663F6BA5c0e366::buyPublicOffer(DAO, 111622391174831097929)
0x2101e0F648A2b5517FD2C5D9618582E9De7a651A::burnLp(helper, 942578426828411838, [USDC], [], [])
0xCA49EcF7e7bb9bBc9D1d295384663F6BA5c0e366::buyPublicOffer(DAO, 1000000000000000000000)
0x2101e0F648A2b5517FD2C5D9618582E9De7a651A::burnLp(helper, 993245927691696464, [USDC], [], [])
During the first round:
- The PancakeSwap route converted
0.4 BNBinto111.622391174831097929 USDC. Shop.buyPublicOfferaccepted exactly that amount and minted111.622391174831097929 LP.- LP total supply moved from
6.8e18to118.422391174831097929 LP. LP.burncomputed share0.942578426828411838.Dao.burnLpreturned84937.901105459450533735 USDCand0.03047219892197075 BNB.
The first USDC transfer out of the DAO is visible in the trace:
Transfer(
from: 0x2101e0F648A2b5517FD2C5D9618582E9De7a651A,
to: 0x1bBed50a248CFd2Ce498E2219C92F86aD657e7AC,
value: 84937901105459450533735
)
After that burn, LP supply returned to 6.8e18, but the DAO still held 5174.389487970026343388 USDC, so the same invariant violation remained. The attacker then repeated the cycle with 1000 LP, extracting another 6132.687214908647539840 USDC and 0.001843818545238318 BNB. Because supply reset after each burn, the opportunity stayed open until the treasury had been depleted.
5. Adversary Flow Analysis
The adversary flow was a single permissionless transaction executed from a fresh helper contract. No privileged signatures, governance powers, or private orderflow were required.
Stage 1: acquire the payment asset
The helper constructor called PancakeSwap router 0x10ed43c718714eb63d5aa57b78b54704e256024e and swapped 0.4 BNB for USDC. The trace shows the router call and the resulting helper USDC balance of 111.622391174831097929.
Stage 2: first underpriced LP round trip
The helper approved Shop, called Shop.buyPublicOffer(DAO, 111622391174831097929), received the same amount of LP, then immediately called LP.burn with USDC as the requested token list. That burn routed through Dao.burnLp and transferred a dominant share of the DAO's USDC and BNB back to the helper.
Stage 3: second round on the now-smaller treasury
Because LP supply had reset while the DAO still retained thousands of USDC, the helper called Shop.buyPublicOffer(DAO, 1000e18), minted 1000 LP, and burned that second batch. The second burn removed most of the remaining liquid treasury.
Stage 4: payout to the attacker EOA
At the end of the transaction, the helper transferred 90070.588320368098073575 USDC to EOA 0xc578d755cd56255d3ff6e92e1b6371ba945e3984. The helper retained the redeemed BNB. The final balance diff records:
- DAO USDC delta:
-89958.965929193266975646 - DAO BNB delta:
-0.032316017467209068 - Attacker EOA USDC delta:
+90070.588320368098073575
6. Impact & Losses
The measurable victim-side losses were:
89958.965929193266975646 USDC0.032316017467209068 BNB
The victim was Unicorn Finance DAO's treasury. LP total supply returned to its original 6.8 LP after each burn, so the exploit did not rely on permanently inflating LP supply. That property made the drain repeatable until treasury NAV per LP fell toward the stale public-offer price.
The incident therefore depleted almost the entire liquid treasury in one transaction while preserving the same nominal LP supply structure that existed before the attack.
7. References
- Seed exploit transaction:
0x933d19d7d822e84e34ca47ac733226367fbee0d9c0c89d88d431c4f99629d77a - xDAO Shop:
0xca49ecf7e7bb9bbc9d1d295384663f6ba5c0e366 - Unicorn Finance DAO:
0x2101e0f648a2b5517fd2c5d9618582e9de7a651a - Unicorn Finance LP:
0xf887a2dac0dd432997c970bce597a94ead4a8c25 - USDC on BSC:
0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d - PancakeSwap router:
0x10ed43c718714eb63d5aa57b78b54704e256024e - Collector trace:
/workspace/session/artifacts/collector/seed/56/0x933d19d7d822e84e34ca47ac733226367fbee0d9c0c89d88d431c4f99629d77a/trace.cast.log - Collector balance diff:
/workspace/session/artifacts/collector/seed/56/0x933d19d7d822e84e34ca47ac733226367fbee0d9c0c89d88d431c4f99629d77a/balance_diff.json - Collector metadata:
/workspace/session/artifacts/collector/seed/56/0x933d19d7d822e84e34ca47ac733226367fbee0d9c0c89d88d431c4f99629d77a/metadata.json - Verified Shop source:
https://bscscan.com/address/0xca49ecf7e7bb9bbc9d1d295384663f6ba5c0e366#code - Verified DAO source:
https://bscscan.com/address/0x2101e0f648a2b5517fd2c5d9618582e9de7a651a#code - Verified LP source:
https://bscscan.com/address/0xf887a2dac0dd432997c970bce597a94ead4a8c25#code