YYDS Referral Overpayment
Exploit Transactions
0x04a1f0d1694242515ecb14faa71053901f11a1286cd21c27fe5542f9eeb62356Victim Addresses
0x970a76aea6a0d531096b566340c0de9b027dd39dBSC0xd5ca448b06f8eb5acc6921502e33912fa3d63b12BSCLoss Breakdown
Similar Incidents
BCT Referral Treasury Drain
35%SNKMiner Referral Reward Drain
35%T3913 Pair-Skim Referral Drain
34%TRUST/FCN Flash-Swap Reward Exploit
30%ATK Reward Flashswap Overclaim
30%EGD Finance Reward Oracle Manipulation
30%Root Cause Analysis
YYDS Referral Overpayment
1. Incident Overview TL;DR
On BSC block 21157029, transaction 0x04a1f0d1694242515ecb14faa71053901f11a1286cd21c27fe5542f9eeb62356 used a public Pancake flash-swap and public YYDS protocol entrypoints to overclaim YYDS from consumptionReturnPool at 0x970a76aea6a0d531096b566340c0de9b027dd39d. The attacker first prepared a referral chain and six mature reward entries, then temporarily drained almost all USDT from the YYDS/USDT pair so the pool read an artificially tiny YYDS price. The pool converted a tiny mature claim into outsized YYDS, the attacker swapped that YYDS back through the pair, repaid the flash-swap, and realized 742286272787211571244404 USDT units of profit at EOA 0xed850799cf22b66cb4911539425f8a41423d0933.
The root cause is a pricing bug in consumptionReturnPool: getPriceOfUSDT() reads live pair balances from the manipulable YYDS/USDT pair and performs integer truncation before scaling. The withdrawal functions then use that distorted spot price to compute YYDS payouts.
2. Key Background
consumptionReturnPool tracks three reward streams per participant: consumer, merchant, and referral. New entries are created through addReturn(orderId, consumer, merchant, amount), which is reached from the historical merchantFactory 0x80388904f867b58a9e754ba4ca79afc5d5b77495 during consumer payment flow. Referral rewards are assigned using the public relationship contract at 0xbbfa3bda4ff68941b6a4ba0877e587a93d991eef.
The relevant verified pool code is:
function addReturn(uint256 _orderId,address _consumer,address _merchant,uint256 _amount) public onlyMerchantContract() {
...
addReferralReward(_orderId,_consumer,_merchant,_amount.mul(10).div(100),returnTime);
...
}
function getPriceOfUSDT() public view returns (uint256 price){
uint256 balancePath1 = IERC20(...usdtAddress()).balanceOf(...pair());
uint256 balancePath2 = IERC20(...yydsAddress()).balanceOf(...pair());
uint256 path1Decimals = IERC20(...usdtAddress()).decimals();
uint256 path2Decimals = IERC20(...yydsAddress()).decimals();
price = (balancePath1 / 10**path1Decimals * 10**18) / (balancePath2 / 10**path2Decimals);
}
The relationship contract exposes public bind(address), so an unprivileged actor can assemble its own referral tree before sending consumer payments. Collector evidence reconstructs the pre-exploit graph as 0x1190... -> 0xe70c... -> 0x5867... -> 0x4ed8....
3. Vulnerability Analysis & Root Cause Summary
This is an ACT-style attack, not a privileged compromise. The vulnerable component is the pool’s claim-to-YYDS conversion logic. withdrawReturnAmountByReferral, withdrawReturnAmountByMerchant, and withdrawReturnAmountByConsumer all compute yydsAmount = totalReturnAmount * 1e18 / priceOfUSDT, where priceOfUSDT comes directly from instantaneous pair balances. Because the pair is a public AMM and the withdrawal happens inside the same transaction as the flash-swap, the attacker can choose the exact pair balances observed by the pool.
The flaw is amplified by the arithmetic order in getPriceOfUSDT(): each side is divided by token decimals before the final scaling step. That integer truncation discards precision and makes a low-USDT/high-YYDS transient state even more distortive. The correct safety invariant is that a matured claim of amount A must convert to a YYDS payout derived from a manipulation-resistant price source, not attacker-controlled same-tx pair balances.
4. Detailed Root Cause Analysis
The attacker prepared the exploit in three stages.
First, the attacker EOA 0xed850799cf22b66cb4911539425f8a41423d0933 deployed helper 0x586770e9643be4bda002f78bfd205546a6f492b3 in tx 0x94c5a2dcf2044e721aa509bf0f9dd556f034a9a4684ad9d6b192e27bc7f93ccd. In tx 0x4c56b5e82d4357a539cd1748ca7cb39c2d988d172a9fb6530de040c3fd729190, that helper deployed claim helper 0xe70cdd37667cddf52cabf3edabe377c58fae99e9 and both helper binds were executed. In tx 0x89f0500e06539e7872b67e47292ca10afa405f460173ab7445b6760d8d93bd03, consumer 0x4ed8788aceb0514c7cff2960bbea6cb70131f60f publicly called relationship.bind(0x5867...).
Second, the attacker accrued mature claim entries. The six consumer payment transactions 0xba2eea1846a0a3aff3f20a4e8afa211adea04e31b08e393e55880d11189b8203, 0x2cdbffbef27e8cacb96da9e6aeb009b7d5a903d974bb4d1a2de04f6e9bbf310a, 0x74be75f87fbc990c141febf61996d0c6e766179134159bb0ed20f4720b70a7fa, 0x6acd555f16747195593158516f3817dd1c2cf6ee4e8490529cd43dbbbf9d2ffa, 0x02fededbd4b431ed0da60eaec61d24e74e86c074001d979f60dbcbb5a5c212bb, and 0xda2d5d9fdbefc008d6fc9e20b2fa7546605aaad3b33b85e998434fed864c8e9c fed addReturn(). Pre-exploit state at block 21157028 shows six referral entries for 0xe70c..., six merchant entries for 0x5867..., and six consumer entries for 0x4ed8.... The mature claim amounts were:
claim_helper referral claim: 510000000000000000
merchant_helper referral claim: 510000000000000000
merchant_helper merchant claim: 663000000000000000
consumer consumer claim: 5100000000000000000
Third, tx 0x04a1f0d1694242515ecb14faa71053901f11a1286cd21c27fe5542f9eeb62356 realized the exploit. The trace shows the Pancake pair 0xd5ca448b06f8eb5acc6921502e33912fa3d63b12 transferring 1186469920024032957137499 USDT to helper 0x5867..., leaving only 1200106400000000000 USDT in the pair at the moment the pool executes withdrawReturnAmountByReferral():
BEP20USDT::balanceOf(pair) -> 1200106400000000000
YYDSToken::balanceOf(pair) -> 1656938602269843892480
YYDSToken::transfer(0xe70c..., 844560000000001270218)
YYDSToken::transfer(0x5867..., 844560000000001270218)
YYDSToken::transfer(0x5867..., 1097928000000001651283)
These trace lines show the breakpoint directly: the pool reads manipulated pair balances, computes a tiny price, and transfers outsized YYDS to the attacker helpers. The referral helper’s mature claim was only 510000000000000000, yet the first referral withdrawal paid 844560000000001270218 YYDS. The merchant helper then withdrew its own referral and merchant claims under the same manipulated price regime.
The balance diff confirms end-state realization:
{
"token": "USDT",
"holder": "0xed850799cf22b66cb4911539425f8a41423d0933",
"delta": "742286272787211571244404"
}
That profit is downstream of the core bug. Flash liquidity is only an enabler; the root cause is the pool’s manipulable spot-price read plus pre-scaling truncation.
5. Adversary Flow Analysis
The adversary flow is deterministic and permissionless:
- Deploy helper contracts and bind them into a referral graph using public
relationship.bind. - Use the historical merchant payment path to create six claim entries that will mature by the target block.
- Wait until the daily claim window opens.
- Flash-swap nearly all USDT out of the YYDS/USDT pair.
- Call
withdrawReturnAmountByReferral()through0xe70c...and0x5867..., then callwithdrawReturnAmountByMerchant()through0x5867.... - Receive overpaid YYDS, swap it back into pair-side assets, repay the pair, and forward residual USDT to the attacker EOA.
The helper decompilations are consistent with this flow: both helper contracts contain direct calls to the pool withdrawal functions, and the merchant helper contains the flash-swap orchestration path.
6. Impact & Losses
The economically realized loss is the USDT extracted from the YYDS/USDT pair:
- USDT loss:
742286272787211571244404smallest units (decimal18)
The protocol-side harm originates earlier in the flow: consumptionReturnPool overpays YYDS against a tiny matured claim because it prices payouts from attacker-controlled same-tx pair balances.
7. References
- Exploit tx:
0x04a1f0d1694242515ecb14faa71053901f11a1286cd21c27fe5542f9eeb62356 - Setup txs:
0x94c5a2dcf2044e721aa509bf0f9dd556f034a9a4684ad9d6b192e27bc7f93ccd,0x4c56b5e82d4357a539cd1748ca7cb39c2d988d172a9fb6530de040c3fd729190,0x89f0500e06539e7872b67e47292ca10afa405f460173ab7445b6760d8d93bd03 - Claim accrual txs:
0xba2eea1846a0a3aff3f20a4e8afa211adea04e31b08e393e55880d11189b8203,0x2cdbffbef27e8cacb96da9e6aeb009b7d5a903d974bb4d1a2de04f6e9bbf310a,0x74be75f87fbc990c141febf61996d0c6e766179134159bb0ed20f4720b70a7fa,0x6acd555f16747195593158516f3817dd1c2cf6ee4e8490529cd43dbbbf9d2ffa,0x02fededbd4b431ed0da60eaec61d24e74e86c074001d979f60dbcbb5a5c212bb,0xda2d5d9fdbefc008d6fc9e20b2fa7546605aaad3b33b85e998434fed864c8e9c - Victim pool source: verified
consumptionReturnPoolsource in collector artifacts - Supporting evidence: exploit trace, exploit balance diff, relationship bind summary, claim state summary, and historical merchantFactory summary in the collector artifact set