Flash-Loan Quote Manipulation
Exploit Transactions
0xeab946cfea49b240284d3baef24a4071313d76c39de2ee9ab00d957896a6c1c4Victim Addresses
0x8262325bf1d8c3be83eb99f5a74b8458ebb96282BSCLoss Breakdown
Similar Incidents
SellToken Reward Oracle Manipulation
38%SmartBank balance-manipulation bug drains USDT via flash-loan roundtrip
38%QiQi Reward Quote Override Drain
37%SlurpyCoin BuyOrSell flaw drains BNB via flash-loan swaps
36%Flash-loan-assisted drain of WKEYDAO-USDT liquidity
35%DMi outfee misrouting collapses pair reserves under flash-loan sells
35%Root Cause Analysis
Flash-Loan Quote Manipulation
1. Incident Overview TL;DR
On BNB Chain block 57432056, transaction 0xeab946cfea49b240284d3baef24a4071313d76c39de2ee9ab00d957896a6c1c4 exploited the YULIAI OTC contract at 0x8262325bf1d8c3be83eb99f5a74b8458ebb96282. The attacker flash-loaned 200000000000000000000000 USDT from Moolah, bought YULIAI on the Pancake V3 YULIAI/USDT pool to move the spot price upward, then sold YULIAI into the victim's public sellToken(uint256) function until the victim's USDT inventory was drained. The root cause is that sellToken priced payouts directly from a same-transaction Pancake QuoterV2 spot quote and immediately transferred victim-held USDT to the seller and fee receiver with no oracle hardening, TWAP, stale-price check, or slippage bound.
2. Key Background
The affected token 0xdf54ee636a308e8eb89a69b6893efa3183c2c1b5 is a plain OpenZeppelin token:
contract YuliAIToken is ERC20, ERC20Burnable, Ownable {
constructor(address initialOwner)
ERC20("YULI AI", "YULIAI")
Ownable(initialOwner)
{
_mint(msg.sender, 8000000000 * 10 ** decimals());
}
}
That code shows the token itself did not contain custom transfer, fee, or pricing logic. The vulnerable component was the OTC-style contract 0x8262325bf1d8c3be83eb99f5a74b8458ebb96282, which exposed public buyToken(uint256) and sellToken(uint256) functions, tracked configurable token and USDT addresses, and paid a fee receiver 0x078f3f917c7355027a8388b7083b2199910c8a9a. The deployment and admin history show this contract was deployed from 0x078f..., then configured to use YULIAI and fee tier 10000. Capital for the exploit was publicly accessible because Moolah's flash-loan function is unrestricted apart from pause, blacklist, and repayment checks:
function flashLoan(address token, uint256 assets, bytes calldata data) external whenNotPaused {
require(!flashLoanTokenBlacklist[token], ErrorsLib.TOKEN_BLACKLISTED);
require(assets != 0, ErrorsLib.ZERO_ASSETS);
IERC20(token).safeTransfer(msg.sender, assets);
IMoolahFlashLoanCallback(msg.sender).onMoolahFlashLoan(assets, data);
IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
}
3. Vulnerability Analysis & Root Cause Summary
The vulnerability class is flash-loan-assisted spot-price manipulation against settlement logic. The victim contract trusted a Pancake QuoterV2 spot quote from the same YULIAI/USDT pool the attacker could move immediately before settlement. The victim then converted that quote into actual USDT transfers from its own balance. Disassembly confirms the contract contains an external call to selector 0xc6a5026a, the QuoterV2 quote function, inside the pricing path used by sellToken. The exploit trace shows that after the pool price was moved, each sellToken call transferred YULIAI from the attacker into the victim and paid USDT out to the attacker plus fee receiver. Because the contract did not anchor settlement to a manipulation-resistant oracle or bounded reference price, same-transaction pool distortion became directly monetizable. This is an ACT exploit because the attacker only needed public contracts, public liquidity, and one adversary-crafted transaction.
4. Detailed Root Cause Analysis
The explicit safety invariant is: the USDT paid by sellToken must reflect a manipulation-resistant price, so an attacker cannot increase victim payout inside the same transaction by first moving the quoted market. The code-level breakpoint is the victim pricing path that calls QuoterV2 selector 0xc6a5026a and then settles ERC20 transfers based on that output.
The selector map confirms the vulnerable public entrypoint:
0x2397e4d7 uint256 payable sellToken(uint256)
0x978bbdb9 view feeRate()
0x2b14ca56 view sellFee()
The disassembly shows the embedded Quoter call:
00000cde: PUSH4 0xc6a5026a
00000cf3: CALL
...
00000f27: PUSH4 0xc6a5026a
00000f3c: CALL
The seed trace shows the exploit sequence directly. First the attacker contract borrows USDT from Moolah and buys YULIAI on Pancake V3:
0x8F73...::flashLoan(USDT, 200000000000000000000000, ...)
...
0x1b81...::exactInputSingle((USDT, YULIAI, 10000, 0xd6b9..., ..., 200000000000000000000000, 0, 0))
...
YuliAIToken::transfer(0xd6b9..., 19127762028424246771866378)
After the price pump, the victim's sellToken queries QuoterV2 and settles at the manipulated rate:
0x8262...::sellToken{value: 250000000000000}(95638810142121233859331)
QuoterV2::quoteExactInputSingle({ tokenIn: YULIAI, tokenOut: USDT, amountIn: 95638810142121233859331, fee: 10000, sqrtPriceLimitX96: 0 })
YuliAIToken::transferFrom(0xd6b9..., 0x8262..., 95638810142121233859331)
BEP20USDT::transfer(0x078F..., 142625763863616412256)
BEP20USDT::transfer(0xd6b9..., 2709889513408711832872)
Those transfers match the configured fee split: 5 percent to the fee receiver and 95 percent to the seller. The attacker repeated this pattern until the victim's inventory was largely exhausted, then sold remaining YULIAI back into Pancake V3, repaid the flash loan, and withdrew profit. The balance diff confirms the net loss was borne by the victim contract rather than by the token or flash-loan provider.
5. Adversary Flow Analysis
The adversary cluster consists of EOA 0x26f8bf8a772b8283bc1ef657d690c19e545ccc0d and transient contract 0xd6b9ee63c1c360d1ea3e4d15170d20638115ffaa. A related transaction 0xb90e722db235b97764912adeabb42384a15b97e9879fd6de70bbcfadbde7bbfe created the helper contract in block 57432055. The exploit transaction then executed the following steps end to end:
- Borrow
200000000000000000000000USDT through the public Moolah flash-loan interface. - Swap the borrowed USDT into
19127762028424246771866378YULIAI on Pancake V3, pushing the pool spot price upward. - Repeatedly call victim
sellToken(uint256)with chunks of YULIAI so the victim consults the now-inflated QuoterV2 output and pays USDT from its own balance. - Swap the remaining YULIAI back to USDT on Pancake V3.
- Repay the
200000000000000000000000USDT flash loan. - Transfer the remaining
78799932076881681340252USDT to the attacker EOA.
The victim remained public and unpaused during the exploit block, and the pre-state already contained enough USDT inventory to make the drain meaningful.
6. Impact & Losses
The measurable asset loss was 99838034704531488579480 USDT from victim contract 0x8262325bf1d8c3be83eb99f5a74b8458ebb96282. In the same transaction, fee receiver 0x078f3f917c7355027a8388b7083b2199910c8a9a gained 4991901735226574428960 USDT and attacker EOA 0x26f8bf8a772b8283bc1ef657d690c19e545ccc0d gained 78799932076881681340252 USDT. The victim received 3347358354974243185076585 YULIAI, but that token inflow was priced from an attacker-manipulated spot quote and did not offset the USDT value lost.
7. References
- Seed exploit tx:
0xeab946cfea49b240284d3baef24a4071313d76c39de2ee9ab00d957896a6c1c4 - Adversary deployment tx:
0xb90e722db235b97764912adeabb42384a15b97e9879fd6de70bbcfadbde7bbfe - Related admin/deactivation tx:
0x230293c5b6b1f030a0988d39ad2edd5d31029b91a1fecc3eb1638cd912fdff8a - Victim contract:
0x8262325bf1d8c3be83eb99f5a74b8458ebb96282 - YULIAI token:
0xdf54ee636a308e8eb89a69b6893efa3183c2c1b5 - USDT token:
0x55d398326f99059ff775485246999027b3197955 - Moolah flash-loan contract:
0x8f73b65b4caaf64fba2af91cc5d4a2a1318e5d8c - Pancake router:
0x1b81d678ffb9c0263b24a97847620c99d213eb14 - Pancake QuoterV2:
0xb048bbc1ee6b733fffcfb9e9cef7375518e25997 - Primary evidence artifacts: seed trace, seed balance diff, victim selector map, victim disassembly, Moolah flash-loan source snippet, and victim admin history under
/workspace/session/artifacts/collector/