This is a lower bound: only assets with reliable historical USD prices are counted, so the actual loss may be higher.
0xb38c2d2d6a168d41aa8eb4cead47e01badbdcf57BSCOn BSC, an unprivileged adversary exploited MICToken at 0xb38c2d2d6a168d41aa8eb4cead47e01badbdcf57 by claiming LP-fee rewards that had accrued to historical LPs while only holding the MIC/WBNB LP token temporarily at claim time. The attacker used helper contract 0xafebc0a9e26fea567cc9e6dd7504800c67f4e3fe, public Pancake liquidity, and a permissionless Pancake V3 flash loan to run two exploit cycles and then cash out through an attacker-controlled fake-USDT / real-USDT pair.
The root cause is deterministic: MICToken distributes the global amountLPFee bucket based on the claimant's current MIC/WBNB LP balance, not on historical reward entitlement. That makes previously accrued rewards stealable by any actor who can momentarily acquire LP tokens and trigger the reward path through either a buy into the token or the public swapManual() entrypoint.
MICToken accumulates buy-side LP fees in the state variable amountLPFee. When a reward claim is triggered, the token swaps a proportional amount of MIC into real USDT and transfers the resulting USDT to the chosen claimant. The reward-accounting LP token is the public Pancake pair 0xfee55f16fd5aec503b73146045b1474925a74dec, which is the MIC/WBNB LP token.
Two victim-side entrypoints matter. First, _transfer triggers swapAndSendLPFee(to) on buys because the deployed contract has swapTokensAtAmount = 0, so every buy can enter the reward path immediately. Second, is public and calls without access control. Neither path tracks historical LP ownership, reward debt, or cumulative indexes.
0x362004d8df7c621e8a657c6ea159320eb00dd50863f80a139236f8e458a96bd40x71dd1b81189ead1d7ecd9ebd812d4c639f10d212c8b45b3b1b5344cd2c22dec3swapManual()swapAndSendLPFee(msg.sender)The monetization path relied on attacker-controlled infrastructure rather than any privileged protocol role. The attacker EOA 0x1703062d657c1ca439023f0993d870f4707a37ff controlled fake token 0x9f779d61a7139960577cf7392892296f30d86df7, helper 0xafebc0a9e26fea567cc9e6dd7504800c67f4e3fe, and fake pair 0x0a64851e0a4a110581682acaebc986c3c5a7eeb8, while sourcing real liquidity from Pancake V3 pool 0x92b7807bf19b7dddf89b706143896d05228f3121.
The vulnerability class is an application-level accounting bug in LP reward distribution. MICToken treats current LP balance as if it were equivalent to historical fee entitlement, which is false in the presence of flash liquidity and temporary LP ownership. As a result, the contract allows a transient LP holder to seize fees accrued while other users were providing liquidity.
The critical invariant is straightforward: LP-fee rewards accrued over time must only be claimable according to historical entitlement, not according to a claimant's spot LP balance at the instant of payout. The code-level breakpoint is inside swapAndSendLPFee(address _addr), where the contract reads the claimant's instantaneous LP balance and computes the payout as a direct fraction of the full reward bucket:
uint256 balance = IUniswapV2Pair(uniswapPair).balanceOf(_addr);
uint256 total = IUniswapV2Pair(uniswapPair).totalSupply();
uint256 fee = amountLPFee.mul(balance).div(total);
That design is exploitable because the attacker does not need to hold LP while fees accrue. They only need LP long enough to satisfy the snapshot read above, trigger swapAndSendLPFee, receive swapped USDT, and unwind the LP position. The deployed configuration makes the issue easier to realize because swapTokensAtAmount is zero and swapManual() is publicly callable.
The verified MICToken source shows the full vulnerable mechanism. On buy-side transfers, _transfer checks automatedMarketMakerPairs[from], and if swapping is enabled and the contract token balance meets the threshold, it executes swapAndSendLPFee(to) before the transfer completes. Independently, swapManual() is public and executes the same reward path for msg.sender. In both cases the reward recipient is selected by the immediate call context, not by any accrued accounting record.
The reward function then uses the claimant's current LP balance as the sole entitlement input:
function swapAndSendLPFee(address _addr) private {
uint256 balance = IUniswapV2Pair(uniswapPair).balanceOf(_addr);
if (amountLPFee >= 1 * (10**18) && balance > 0) {
uint256 total = IUniswapV2Pair(uniswapPair).totalSupply();
uint256 fee = amountLPFee.mul(balance).div(total);
...
IERC20(usdt).transfer(_addr, newBalance);
amountLPFee = amountLPFee.sub(fee);
}
}
There is no per-user checkpoint, no reward debt, and no time-weighted accounting. The only question the contract asks is whether the claimant has LP tokens right now. That creates a deterministic exploit condition: if the global fee bucket is positive and an attacker can temporarily acquire MIC/WBNB LP, they can claim a corresponding fraction of the entire bucket regardless of who earned it.
The collected on-chain artifacts match that mechanism. In the seed transaction 0x316c35d483b72700e6f4984650d217304146b3732bb148e32fa7f8017843eb24, the balance diff shows that the MICToken contract lost 179044835356110611976 MIC while the Pancake V3 flash pool gained its fee and attacker-controlled downstream paths accumulated realizable USDT:
{
"token": "0xb38c2d2d6a168d41aa8eb4cead47e01badbdcf57",
"holder": "0xb38c2d2d6a168d41aa8eb4cead47e01badbdcf57",
"delta": "-179044835356110611976"
}
{
"token": "0x55d398326f99059ff775485246999027b3197955",
"holder": "0x92b7807bf19b7dddf89b706143896d05228f3121",
"delta": "170000000000000000"
}
The attacker helper was not a prerequisite for exploitability; it was only the historical execution vehicle. The helper decompilation shows an attacker-bound control point tied to tx.origin == 0x1703062d657c1ca439023f0993d870f4707a37ff, while the exploit itself depends on public victim code and permissionless liquidity, which is why the incident is correctly classified as ACT.
The observed attack sequence contains four adversary-crafted transactions. First, in block 34905162, tx 0x316c35d483b72700e6f4984650d217304146b3732bb148e32fa7f8017843eb24 used helper 0xafeb... plus a Pancake V3 flash loan of real USDT from pool 0x92b7807bf19b7dddf89b706143896d05228f3121. During that cycle, the attacker temporarily acquired LP exposure, triggered repeated LP-fee claims, repaid the flash loan plus fee, and left attacker-controlled pair 0x0a64851e0a4a110581682acaebc986c3c5a7eeb8 holding 1877857022403377322185 real USDT.
Second, tx 0xcce04933cc4afa16cb6a602e78e638500c8e0e837acb76e130e8e7e8171eae08 sent 1e18 fake-USDT back into the helper so the exploit sequence could be repeated. Third, tx 0x362004d8df7c621e8a657c6ea159320eb00dd50863f80a139236f8e458a96bd4 executed the second flash-loan cycle and again moved more real USDT into the attacker-controlled fake pair while preserving the same victim-side reward-accounting flaw.
Finally, in block 34905327, tx 0x71dd1b81189ead1d7ecd9ebd812d4c639f10d212c8b45b3b1b5344cd2c22dec3 swapped 100e18 fake-USDT for real USDT out of the manipulated pair, realizing 735380207909081142051 raw USDT gross at the attacker EOA. Auditor balance checkpoints show the EOA's real USDT balance increased from 28515417251354674792245 at block 34905161 to 29250797459263755934296 at block 34905327, consistent with a net gain after gas of 711131061834724397238.
The direct victim-side depletion is visible in the seed exploit cycle. The MICToken contract lost 179044835356110611976 MIC from its LP-fee reserve and the extracted value was converted through public swap paths into real USDT. Across the full realized sequence, the attacker captured a net increase of 711131061834724397238 raw USDT after subtracting gas valued in USDT terms.
The reported total-loss view for the incident is:
MIC: 179044835356110611976 raw units with 18 decimals.USDT: 2615262675919523289326 raw units with 18 decimals.These amounts reflect asset depletion caused by the faulty reward-entitlement logic rather than a privileged takeover or configuration change.
MICToken verified source at 0xb38c2d2d6a168d41aa8eb4cead47e01badbdcf57, including _transfer, swapManual(), and swapAndSendLPFee(address).0x316c35d483b72700e6f4984650d217304146b3732bb148e32fa7f8017843eb24.0x316c35d483b72700e6f4984650d217304146b3732bb148e32fa7f8017843eb24.34905161, 34905162, 34905178, 34905199, 34905326, and 34905327.0xafebc0a9e26fea567cc9e6dd7504800c67f4e3fe.0x92b7807bf19b7dddf89b706143896d05228f3121.