# LML / APower Reward-Claim Price Manipulation
On March 31, 2026 at 20:39:02 UTC, the attacker used flash-loaned capital on BNB Chain to manipulate the LML/USDT market, then batch-triggered reward claims for pre-seeded accounts through APower and immediately sold the resulting LML back into the distorted pool. The primary issue is a price-manipulable reward-claim flow: the public APower claim entrypoint calls `updatePrice()`, `updateUser(account)`, and `claimReward(account)` in the same transaction, after the attacker has already moved the LML spot price. The trace confirms 11 reward-claim attempts, 56.591788627072709517 LML paid out to claimant wallets, and a subsequent dump of 52.448700122473787159 LML for 310,718,697.249411714088249714 gross USDT before the financing legs were unwound. The user-supplied hypothesis is therefore substantially confirmed: the exploit is not a pure flash-loan bug, but a reward-accounting design that can be re-priced and settled inside the same manipulated market window.
## Root Cause
### Vulnerable Contract
The economically vulnerable backend is the stake proxy at `0xae406f357541f45f01bec21f9f28c43757f202e4`, which delegates to unverified implementation `0xbe97138647a993d9d1aabb25f10b3611c0adce19`. The publicly reachable trigger path is `APower` at `0xb7b7631b97d93344b2a29e926e42578006794b3b`, whose verified source is available locally as `0xb7…/APower_excerpt.sol`. The trace shows every reward claim going through `APower -> StakeProxy.claimReward(address)` at depths 37-38, so APower is the user-controlled entrypoint and the stake proxy is the reward-accounting sink.
Source type:
– `APower`: verified excerpt
– `StakeProxy`: proxy with unverified implementation
– `AttackerHelper`: recovered only, medium confidence, used only to explain call orchestration
### Vulnerable Function
The concrete exploit entrypoint is APower `receive()` together with `_claimReward(address)`:
– `receive()` has no selector; any plain call to APower with `msg.value < userMin` enters `_claimReward(account)`.
– `_claimReward(address)` then executes `updatePrice() -> updateUser(account) -> claimReward(account)` against the stake proxy.
Trace evidence:
– `idx 311`, `515`, `773`, `1037`, `1298+`, `1450+`, `1599+`, `1744+`, `1875+`, `2040+`, `2213+`: attacker-controlled claimant addresses call APower with empty calldata.
– `idx 318/319`, `522/523`, …, `2220/2221`: APower calls `StakeProxy.updatePrice()`.
– `idx 320/321`, `524/525`, …, `2222/2223`: APower calls `StakeProxy.updateUser(address)`.
– `idx 339/340`, `543/544`, …, `2241/2242`: APower calls `StakeProxy.claimReward(address)`.
### Vulnerable Code
“`
function _transfer(address from, address to, uint256 amount) internal virtual { amount; if (to == address(this) && tx.origin == from) { _claimReward(from); // <– VULNERABILITY: direct self-transfer can trigger claim settlement } else revert(“Can’t Transfer”); } receive() external payable { address account = msg.sender; ISTAKE _STAKE = ISTAKE(stakeAdd); (IMAIN.ConfigSingle memory config, IMAIN.TokenAdd memory _ta, ) = IMAIN(mainAdd).getConfig(); (ISTAKE.ConfigSingle memory _cs, ISTAKE.TokenAdd memory tokenAdd, ) = _STAKE.getConfig(); require(!IMAIN(mainAdd).isBlackList(msg.sender), “Power: User Invalid”); if (msg.value < _cs.userMin) { if (msg.value > 0) payable(account).transfer(msg.value); _claimReward(account); // <– VULNERABILITY: zero-value/plain calls enter claim path } else { uint usdt = _ta.USDT.balanceOf(address(this)); uint lp = _ta.LP.balanceOf(address(this)); if (lp > 0) { _ta.LP.transfer(_ta.lpRecieve, _ta.LP.balanceOf(address(this))); } AiWeb3Tools.swapETHForToken(_ta.ROUTER, _ta.USDT, msg.value, 1000, address(this)); usdt = _ta.USDT.balanceOf(address(this)) – usdt; _ta.USDT.transfer(tokenAdd.market, (usdt * config.fundRate) / 10000); { uint beforeBalance = _ta.TOKEN.balanceOf(address(this)); AiWeb3Tools.swapForToken(_ta.ROUTER, _ta.USDT, _ta.TOKEN, (usdt * (10000 – config.fundRate)) / 20000, 1000, address(this)); beforeBalance = _ta.TOKEN.balanceOf(address(this)) – beforeBalance; AiWeb3Tools.addLiquidityUSDT(_ta.ROUTER, _ta.USDT, (usdt * (10000 – config.fundRate)) / 20000, _ta.TOKEN, beforeBalance, address(this)); lp = _ta.LP.balanceOf(address(this)); _ta.LP.transfer(_ta.lpRecieve, _ta.LP.balanceOf(address(this))); } _STAKE.deposit(account, msg.value, lp, usdt); _mint(account, lp); } } function _claimReward(address account) private { ISTAKE _STAKE = ISTAKE(stakeAdd); _STAKE.updatePrice(); // <– VULNERABILITY: refreshes claim state from live manipulated market conditions _STAKE.updateUser(account); // <– VULNERABILITY: re-prices the user immediately after the manipulated update uint balance = _STAKE.users(account).balance; uint rate = IMAIN(mainAdd).getDynamicRate(); balance = balance * (10000 + rate) / 10000; if (balance > 0) { (, IMAIN.TokenAdd memory _ta, ) = IMAIN(mainAdd).getConfig(); IMAIN(address(_ta.PROOF)).sendMining(balance); _ta.TOKEN.approve(address(_STAKE), balance); _STAKE.claimReward(account); // <– VULNERABILITY: claim is settled in the same manipulated window if (_ta.TOKEN.balanceOf(address(this)) > 0) { _ta.TOKEN.transfer(address(_ta.PROOF), _ta.TOKEN.balanceOf(address(this))); } } }
“`
The LML token also participates in the issue because its transfer logic continuously pushes price updates into the stake system:
“`
if (!_inSwapAndLiquify && _cs.startBlock > 0 && !(to == _ta.recieve || to == address(_ta.PROOF) || to == address(_ta.POWER) || to == address(this) || to == _dead) && from == _ta.swapPair) { _inSwapAndLiquify = true; try _ta.STAKE.updatePrice() {} catch {} try _ta.STAKE.updatePool() {} catch {} _inSwapAndLiquify = false; } else if (!_inSwapAndLiquify && _cs.startBlock > 0 && !(from == _ta.recieve || from == address(_ta.PROOF) || from == address(_ta.POWER) || from == address(this)) && to == _ta.swapPair) { _inSwapAndLiquify = true; try _ta.STAKE.updatePrice() {} catch {} try _ta.STAKE.updatePool() {} catch {} _inSwapAndLiquify = false; }
“`
This excerpt comes from `0x737d…/0x737d….sol` lines 744-753 and shows that LML buy/sell activity itself can refresh stake-side price state.
### Why It’s Vulnerable
Expected behavior:
– Reward claims should not be settled from a price that the claimant can manipulate in the same transaction.
– The claim trigger should not allow a plain zero-value call to force `updatePrice()`, `updateUser(account)`, and `claimReward(account)` back-to-back while the attacker still controls the market state.
– If a market price is needed for reward settlement, it should come from manipulation-resistant data or a delayed checkpoint that cannot be changed and consumed atomically.
Actual behavior:
– APower exposes `_claimReward(account)` through both self-transfer and zero-value/plain `receive()`.
– `_claimReward(account)` immediately calls `updatePrice()`, then `updateUser(account)`, then `claimReward(account)`.
– The attacker manipulates the LML/USDT pool first, then calls APower from attacker-controlled claimant wallets, so the stake system settles rewards against the manipulated price window.
– The attacker then routes the freshly claimed LML back to the helper and sells it into the same distorted pool before unwinding financing.
Why this matters:
– The exploitable invariant is not “can rewards be claimed?” but “can the claim-trigger and the price refresh happen in the same attacker-controlled transaction?” Here the answer is yes.
– The trace proves the attacker did not need a separate delayed settlement phase. Price movement, price refresh, account update, reward claim, reward aggregation, and dump all happen inside one transaction.
– The user’s TWAP/snapshot hypothesis remains plausible for the unverified stake implementation, but the trace is already sufficient to conclude that reward settlement consumed a manipulable same-tx price update. That is the on-chain-proven root cause.
## Attack Execution
### High-Level Flow
1. The attacker calls `AttackerHelper.Transfer(…)` from EOA `0x982e…`.
2. The helper draws nested Moolah flash loans and additional temporary capital from Venus and an Aave-style pool.
3. The helper enters a chain of Pancake V2/V3 and Uniswap-style flash-swap callbacks, then calls `LML.swapAndTrans()`.
4. The attack path distorts the LML/USDT pair, including a pair transfer of `21481.670943088126592818` LML to `0xdead`, which is consistent with the user’s “receiver = address(0)” manipulation hint.
5. While the manipulated market state is still live, 11 attacker-controlled claimant wallets send plain calls into APower, which triggers `_claimReward(account)` for each wallet.
6. APower calls `StakeProxy.updatePrice()`, `StakeProxy.updateUser(account)`, and `StakeProxy.claimReward(account)` for those wallets in the same transaction.
7. Claimed LML is transferred out to the claimant wallets, then most of it is sent back into the attacker helper.
8. The helper approves and sells the claimed LML through Pancake Router into the LML/USDT pair, realizes gross USDT proceeds at the distorted price, and then unwinds Venus, Aave, and Moolah financing legs.
### Detailed Call Trace
“`
EOA (0x982e1dc1…) → AttackerHelper (0x03811ea7…).Transfer() [0xd1398bee] [PHASE 1: NESTED FLASH LOANS] ListaMoolah.flashLoan(USDT, 8,211,014.02) [0xe0232b42] → USDT.transfer(AttackerHelper, 8,211,014.02 USDT) → AttackerHelper.onMoolahFlashLoan() [0x13a1a562] ListaMoolah.flashLoan(WBNB, 382,768.85) [0xe0232b42] → WBNB.transfer(AttackerHelper, 382,768.85 WBNB) → AttackerHelper.onMoolahFlashLoan() [0x13a1a562] [PHASE 2: AMPLIFY CAPITAL VIA LENDING] Venus vWBNB.mint(314,768.85 WBNB) [0xa0712d68] Venus vUSDT.borrow(91,670,118.22 USDT) [0xc5ebeaec] Aave Pool.supply(USDT, …) [0x617ba037] Aave Pool.borrow(USDT, …) [0xa415bcad] [PHASE 3: FLASH-SWAP CASCADE (10 Pancake V3 + 3 V2 pairs)] PancakeV4.unlock() → unlockCallback() [0x48c89491] PancakeV3 (0x81c729…).flash() → uniswapV3FlashCallback() PancakeV3 (0xe1acb4…).flash() → uniswapV3FlashCallback() … 8 more nested V3 flash callbacks … PancakeV3 (0x9c4ee8…).flash() → pancakeV3FlashCallback() Pair (0xcaaf3c…).swap(USDT) → pancakeCall() [0x022c0d9f] Pair (0x999687…).swap(USDT) → pancakeCall() Pair (0xb720ea…).swap(USDT) → pancakeCall() [PHASE 4: PRICE MANIPULATION] LML.swapAndTrans() [0xafe9d95c] PancakeRouter.swapExact…(USDT → LML) [0x5c11d795] PancakePairUSDTLML.swap() → pair reserves distorted LML._transfer() → 21,481.67 LML burned to 0xdead PancakeRouter.swapExact…(USDT → LML) [0x5c11d795] PancakePairUSDTLML.swap() → second swap deepens distortion [PHASE 5: REWARD CLAIM — 11 CLAIMANT WALLETS] ← EXPLOIT [x11] AttackerHelper → Claimant.transfer(addr,addr) [0xba45b0b8] → Claimant calls APower with empty calldata APower.receive() → _claimReward(claimant): StakeProxy.updatePrice() [0x673a7e28] ← reads manipulated price StakeProxy.updateUser(claimant) [0xed03b336] ← re-prices at manipulated rate StakeProxy.users(claimant) [0xa87430ba] LML.swapBack() [0x6ac5eeee] ← transfer hook fires StakeProxy.claimReward(claimant) [0xd279c191] ← settles at manipulated price → LML paid from APower to claimant → Claimant sends LML back to AttackerHelper Claimant wallets: 0xd7cf95d0… (idx 310) 0xe50e39cd… (idx 1443) 0xe8c28290… (idx 514) 0xfd89c599… (idx 1582) 0x07504be7… (idx 772) 0x052d79ff… (idx 1728) 0x7922d9ec… (idx 1036) 0x6bf576c9… (idx 1874) 0x053e493f… (idx 1300) 0x5ba6e85f… (idx 2039) 0x8ee66ca2… (idx 2212) → Total claimed: 56.59 LML | Total returned to helper: 60.15 LML [PHASE 6: DUMP CLAIMED LML] PancakeRouter.swapExact…(52.45 LML → USDT) [0x5c11d795] LML._transfer() → StakeProxy.updatePrice() (transfer hook) PancakePairUSDTLML.swap() → 310,718,697.25 USDT to helper [PHASE 7: REPAY FLASH-SWAPS] USDT.transfer → repay V2 pair (0xb720ea…), (0x999687…), (0xcaaf3c…) … repay all 10 Pancake V3 flash loans … [PHASE 8: UNWIND LENDING + REPAY FLASH LOANS] Aave Pool.repay(USDT) [0x573ade81] Aave Pool.withdraw(USDT) [0x69328dec] Venus vUSDT.repayBorrow(91,670,118.22 USDT) [0x0e752702] Venus vWBNB.redeem() [0xdb006a75] WBNB.transferFrom → ListaMoolah (repay 382,768.85 WBNB) USDT.transferFrom → ListaMoolah (repay 8,211,014.02 USDT)
“`
## Financial Impact
Observable reward-claim leg:
– The trace contains 11 `claimReward(address)` attempts through APower and the stake proxy.
– Receipt-derived transfer data shows 11 positive LML reward payouts from APower to claimant wallets, totaling `56.591788627072709517` LML (on-chain verified; earlier draft figure of `51.452744625963916443` was an undercount).
– Those claimant wallets then send `60.147591883570861171` LML back to the attacker helper during the same transaction (on-chain verified; earlier draft figure of `48.944359774500526415` was an undercount).
Observable dump leg:
– The helper transfers `52.448700122473787159` LML into `PancakePairUSDTLML`.
– The pair returns `310,718,697.249411714088249714` gross USDT to the helper in the same manipulated window.
– That implies a realized sale rate of roughly `5,924,240.191345973910021591986` USDT per LML, which is plainly inconsistent with organic market behavior and is strong on-chain evidence of successful price distortion.
Net profit caveat:
– `funds_flow.json` shows the helper’s net USDT delta as approximately zero by the end of the transaction because the same transaction also repays flash loans and unwinds debt positions.
– This transaction therefore proves the over-claim and the inflated dump price, but it does not by itself isolate final attacker profit after all financing legs. The helper retains only dust-level residual balances on the tx-local net-change view.
– The safest financial statement is: the attacker over-claimed at least `56.591788627072709517` LML during the manipulated claim window and realized `310,718,697.249411714088249714` gross USDT on the dump leg before repayment/unwind.
## Assessment
Primary classification: `price_manipulation`
Secondary classification: `flash_loan_abuse`
Assessment details:
– The user hint about attacker-controlled claimant wallets is directly confirmed by the 11 APower receive-path calls followed by `claimReward(address)` for those same wallets.
– The claim flow is public and same-tx: a plain APower call with `msg.value < userMin` is enough to enter `_claimReward(account)`.
– The exact internal stake implementation remains unverified, so statements about whether it uses a pure spot price, a lagged snapshot, or a derived checkpoint are an inference. What is proven on-chain is narrower and sufficient: the claim path refreshes price and settles reward inside the manipulated market window, which is enough to explain the exploit.
## Remediation
1. Remove any same-tx path that lets a user trigger `updatePrice()` and `claimReward()` atomically after manipulating the underlying market.
2. Replace manipulable AMM-derived pricing in reward settlement with manipulation-resistant oracle data or delayed checkpoints that cannot be changed and consumed in the same transaction.
3. Separate “price update” and “reward claim” across time, or at minimum enforce a cooldown between them.
4. Eliminate public zero-value and self-transfer claim triggers in APower; require explicit authenticated claim functions with stronger state gating.
5. Review all LML transfer hooks that call `Stake.updatePrice()`/ `updatePool()` on every pair interaction, because they make reward state reactive to attacker-controlled spot moves.
## Evidence
– Transaction hash: `0x805d273a63d905d7827d43f6dc051eafdcd0cb69a07c7eb74358c6a5c6255b47`
– Block: `89867310`
– Timestamp: `2026-03-31T20:39:02Z`
– Status: success ( `0x1`)
– Gas used: `23,405,858`
– Log count: `539`
**Selector verification**:
– `0xd1398bee` = `Transfer(address,address,address,uint256)`— AttackerHelper entrypoint
– `0xe0232b42` = `flashLoan(address,uint256,bytes)`— Moolah flash loans (4 calls)
– `0x13a1a562` = `onMoolahFlashLoan(uint256,bytes)`— flash loan callbacks (2 calls)
– `0x490e6cbc` = `flash(address,uint256,uint256,bytes)`— Pancake V3 flash loans (10 calls)
– `0xa1d48336` = `pancakeV3FlashCallback(uint256,uint256,bytes)`— V3 callbacks (8 calls)
– `0xe9cbafb0` = `uniswapV3FlashCallback(uint256,uint256,bytes)`— Uniswap V3 callbacks (2 calls)
– `0x022c0d9f` = `swap(uint256,uint256,address,bytes)`— PancakeSwap V2 swaps (7 calls)
– `0x84800812` = `pancakeCall(address,uint256,uint256,bytes)`— V2 flash-swap callbacks (3 calls)
– `0xafe9d95c` = `swapAndTrans()`— LML price manipulation trigger (1 call)
– `0x5c11d795` = `swapExactTokensForTokensSupportingFeeOnTransferTokens(…)`— PancakeRouter swaps (3 calls)
– `0xba45b0b8` = `transfer(address,address)`— claimant wallet APower triggers (11 calls)
– `0x673a7e28` = `updatePrice()`— StakeProxy price refresh (24 calls)
– `0xed03b336` = `updateUser(address)`— StakeProxy user update (22 calls)
– `0xd279c191` = `claimReward(address)`— StakeProxy reward claim (22 calls: 11 via APower + 11 delegatecall)
– `0x6ac5eeee` = `swapBack()`— LML internal swap hook (11 calls)
– `0xa87430ba` = `users(address)`— StakeProxy user query (618 calls)
– `0xc3f909d4` = `getConfig()`— config lookups (488 calls)
**Validated call counts and amounts**:
– `11` claimant wallet triggers into APower (idx 310, 514, 772, 1036, 1300, 1443, 1582, 1728, 1874, 2039, 2212)
– `11` `claimReward(address)` calls to StakeProxy (idx 339, 543, 801, 1065, 1326, 1472, 1611, 1757, 1903, 2068, 2241)
– `56.591788627072709517` LML total claimed by 11 wallets (receipt-verified)
– `60.147591883570861171` LML sent back from claimant wallets to AttackerHelper (receipt-verified)
– `52.448700122473780993` LML dumped into PancakePairUSDTLML (receipt log 0x1f8)
– `310,718,697.249411702156066895` USDT received from pair (receipt log 0x201)
– `21,481.670943088127387455` LML burned to 0xdead during manipulation (receipt log 0x55)
– `8,211,014.021716461258690486` USDT flash-loaned from Moolah (first loan)
– `382,768.850110561062762505` WBNB flash-loaned from Moolah (second loan)
**Code-path evidence**:
– APower `_claimReward(account)` calls `updatePrice()`, `updateUser(account)`, `claimReward(account)` in sequence — confirmed in `0xb7b7…/APower_excerpt.sol` lines 72-88.
– APower `receive()` enters `_claimReward()` when `msg.value < userMin`— confirmed in source line 46-48.
– LML `_transfer()` calls `STAKE.updatePrice()` and `STAKE.updatePool()` on pair interactions — confirmed in `0x737d…/0x737d….sol` lines 744-753.
– StakeProxy delegates all calls to unverified implementation `0xbe97138647a993d9d1aabb25f10b3611c0adce19`— confirmed by `DELEGATECALL` at trace depths 38 for every `updatePrice`/ `updateUser`/ `claimReward`.
## Related URLs
– Transaction: https://bscscan.com/tx/0x805d273a63d905d7827d43f6dc051eafdcd0cb69a07c7eb74358c6a5c6255b47
– Attacker EOA: https://bscscan.com/address/0x982e1dc183313ae16002a88aff460c0e6a20e1b6
– AttackerHelper: https://bscscan.com/address/0x03811ea796a084a78356cdedc27de8676457dfa3
– APower: https://bscscan.com/address/0xb7b7631b97d93344b2a29e926e42578006794b3b
– StakeProxy: https://bscscan.com/address/0xae406f357541f45f01bec21f9f28c43757f202e4
– StakeProxy implementation: https://bscscan.com/address/0xbe97138647a993d9d1aabb25f10b3611c0adce19
– LML Token: https://bscscan.com/address/0x737d5b7a41749ab426f41c36a242959e4d0b9e46
– PancakePairUSDTLML: https://bscscan.com/address/0x4be51ecae1860e5a5fd6db56bc4486ab1e4afad7
– ListaMoolah (flash loan provider): https://bscscan.com/address/0x8f73b65b4caaf64fba2af91cc5d4a2a1318e5d8c
– Venus vWBNB: https://bscscan.com/address/0x6bca74586218db34cdb402295796b79663d816e9
– Venus vUSDT: https://bscscan.com/address/0xfd5840cd36d94d7229439859c0112a4185bc0255
– Aave Pool: https://bscscan.com/address/0x6807dc923806fe8fd134338eabca509979a7e0cb
