Calculated from recorded token losses using historical USD prices at the incident time.
0x1e813fa05739bf145c1f182cb950da7af046778dBSC0x2ecd8ce228d534d8740617673f31b7541f6a0099BSC0x16f2b73c5dd03e22bf240ed5f4ab3c2ad75b10bbBSCOn BSC, an attacker exploited the LPC token at 0x1e813fa05739bf145c1f182cb950da7af046778d by abusing a broken self-transfer path. In block 19852597, attacker EOA 0xd9936ea91a461aa4b727a7e3661bcd6cd257481c called helper contract 0xcfb7909b7eb27b71fdc482a2883049351a1749d7, flash-borrowed LPC from the public PancakeSwap LPC/USDT pair 0x2ecd8ce228d534d8740617673f31b7541f6a0099, then repeatedly called transfer(address(this), balance) to inflate the helper’s LPC balance. In the next exploit step, transaction 0xbd7ecaf0ce2ea7755d16deb0658fe0570d7e6e45fe3ce5b34f49d1e5f50ddb8b sold the inflated LPC inventory into the LPC/USDT pair and the LPC/WBNB pair 0x16f2b73c5dd03e22bf240ed5f4ab3c2ad75b10bb, paying out 45307199857564059495367 raw USDT units and 800527311924787989 raw WBNB units to the attacker EOA.
The root cause is a balance-accounting bug in LPC’s _transfer function. It snapshots sender and recipient balances before mutation, then writes both balances back independently. When sender == recipient, the second write overwrites the debit with a credit, so a self-transfer mints net balance without increasing totalSupply.
LPC is a fee-on-transfer token with burn, fee-recipient, holder-dividend, parent-reward, and grandparent-reward components. The verified constructor configures these fees through and whitelists only selected addresses. The exploit path matters specifically for non-whitelisted addresses, because that path computes a positive after applying fees and burns.
0xbd7ecaf0ce2ea7755d16deb0658fe0570d7e6e45fe3ce5b34f49d1e5f50ddb8bsetFeeRates(2*1e7, 15*1e6, 2*1e7, 15*1e6, 1*1e7, 21000000*1e18)recipientAmountTwo public PancakeSwap V2 pools provided the monetization path:
0x2ecd8ce228d534d8740617673f31b7541f6a00990x16f2b73c5dd03e22bf240ed5f4ab3c2ad75b10bbThe seed transaction metadata and BSC state confirm that LPC ownership had already been renounced before exploitation, so the attacker did not rely on privileged control. The ACT framing is therefore straightforward: any unprivileged actor able to acquire a small LPC balance in a non-whitelisted address could trigger the same self-transfer bug and monetize it through public liquidity.
The vulnerability is an accounting bug in LPC’s core transfer logic, not a flash-loan flaw or PancakeSwap flaw. LPC first calls _updateBalance(sender) and _updateBalance(recipient) and stores those two pre-mutation balances in local variables. It then computes recipientAmount after fee deductions and, at the end of _transfer, performs two separate storage writes: one debiting the sender and one crediting the recipient. That pattern is only safe when sender and recipient are distinct storage keys. When the same address is used for both roles, the recipient write lands on the same slot and overwrites the debit. The result is that a self-transfer leaves the account with preBalance + recipientAmount instead of preBalance - fees, while totalSupply remains unchanged because no mint function is called.
The vulnerable LPC source shows the issue directly:
function _transfer(address sender, address recipient, uint256 amount) internal {
(bool vs, uint senderBalance) = _updateBalance(sender);
(bool vr, uint recipientBalance) = _updateBalance(recipient);
require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
uint recipientAmount = amount;
if (sender != address(0) && recipient != address(0) && !isWhiteList[sender] && !isWhiteList[recipient]) {
// fee, holder, parent, grandparent, and burn deductions update recipientAmount
}
_balances[sender] = senderBalance.sub(amount);
_balances[recipient] = recipientBalance.add(recipientAmount);
emit Transfer(sender, recipient, recipientAmount);
}
Because the exploit helper was not whitelisted, each self-transfer credited a still-positive recipientAmount. That made repeated self-transfers an on-chain mint primitive.
The LPC constructor confirms the fee configuration used during the exploit:
rlink = IRlinkCore(_rlink);
setFeeTo(address(0xBEc385af40626199D92C402E600f054e157DfA7b));
setFeeRates(2*1e7, 15*1e6, 2*1e7, 15*1e6, 1*1e7, 21000000*1e18);
isWhiteList[msg.sender] = true;
_balances[msg.sender] = _totalSupply;
At block 19852597, the seed trace shows the helper contract borrowing LPC from the LPC/USDT pair and immediately entering a loop of self-transfers. The trace contains repeated calls of the form:
LPC::transfer(0xCfb7909B7EB27b71fDC482A2883049351a1749d7, 1353900800797409815871132)
LPC::transfer(0xCfb7909B7EB27b71fDC482A2883049351a1749d7, 2599489537531026846472577)
LPC::transfer(0xCfb7909B7EB27b71fDC482A2883049351a1749d7, 4991019912059571545227351)
...
LPC::transfer(0xCfb7909B7EB27b71fDC482A2883049351a1749d7, 480062633741411268750082616)
Those successive calls are the exploit itself. Each call reuses the full current LPC balance as the next self-transfer amount, so the helper balance compounds rapidly. Independent RPC queries confirm the semantic effect:
19852596: 9986468431680000000000000019852597: 9986468431680000000000000019852597: 920085110888826773903698624That is the expected signature of the bug: helper balance explodes while totalSupply remains unchanged.
The exploit then repaid the flash-borrowed pair and kept the inflated inventory. The trace shows a repayment transfer back to the LPC/USDT pair:
LPC::transfer(0x2ecD8Ce228D534D8740617673F31b7541f6A0099, 1635145894682862096460000)
After the inflation leg, the attacker triggered a separate cash-out transaction. The receipt for 0xbd7ecaf0ce2ea7755d16deb0658fe0570d7e6e45fe3ce5b34f49d1e5f50ddb8b shows:
USDT Transfer:
from 0x2ecd8ce228d534d8740617673f31b7541f6a0099
to 0xd9936ea91a461aa4b727a7e3661bcd6cd257481c
raw 45307199857564059495367
WBNB Transfer:
from 0x16f2b73c5dd03e22bf240ed5f4ab3c2ad75b10bb
to 0xd9936ea91a461aa4b727a7e3661bcd6cd257481c
raw 800527311924787989
RPC balance queries at blocks 19852617 and 19852618 confirm that the attacker EOA moved from zero USDT and zero WBNB to exactly those post-cash-out balances. Gas spent across the exploit execution was fully public and ordinary, and the inflation and cash-out transactions consumed 21029400000000000 wei in total gas cost.
The end-to-end adversary flow consisted of three public transactions:
0x446e81d2428f7d4bbbd2ef210f5c31ced3dedbfc85a5b7992e2f7857c55012f7
The attacker EOA deployed helper contract 0xcfb7909b7eb27b71fdc482a2883049351a1749d7.
0x0e970ed84424d8ea51f6460ce6105ab68441d4450a80bc8d749fdf01e504ed8c
The attacker EOA called the helper. The helper flash-borrowed LPC from the LPC/USDT pair, executed repeated LPC self-transfers on itself, repaid the pair, and finished with an inflated LPC balance.
0xbd7ecaf0ce2ea7755d16deb0658fe0570d7e6e45fe3ce5b34f49d1e5f50ddb8b
The same helper sold part of the inflated LPC into LPC/USDT and LPC/WBNB through PancakeSwap and delivered the proceeds to the attacker EOA.
Nothing in this flow required privileged victim access, attacker-side secrets, or replay of the original attacker bytecode as a dependency. The decisive condition was simply the availability of a nonzero LPC balance in a non-whitelisted address plus public liquidity deep enough to cash out the inflated token.
The exploit broke LPC’s core balance-conservation invariant and converted the synthetic LPC inflation into real external assets from public liquidity pools. The measurable attacker receipts were:
45307199857564059495367 raw units800527311924787989 raw unitsLiquidity providers in the LPC/USDT and LPC/WBNB pools absorbed those losses because the attacker sold non-conserved LPC inventory into the pools. The token itself also ended up with corrupted balance accounting because helper balances could exceed the unchanged totalSupply.
0x1e813fa05739bf145c1f182cb950da7af046778d0x446e81d2428f7d4bbbd2ef210f5c31ced3dedbfc85a5b7992e2f7857c55012f70x0e970ed84424d8ea51f6460ce6105ab68441d4450a80bc8d749fdf01e504ed8c0xbd7ecaf0ce2ea7755d16deb0658fe0570d7e6e45fe3ce5b34f49d1e5f50ddb8b/workspace/session/artifacts/collector/seed/56/0x0e970ed84424d8ea51f6460ce6105ab68441d4450a80bc8d749fdf01e504ed8c/trace.cast.log/workspace/session/artifacts/collector/seed/56/0x0e970ed84424d8ea51f6460ce6105ab68441d4450a80bc8d749fdf01e504ed8c/metadata.json/workspace/session/artifacts/collector/seed/56/0x0e970ed84424d8ea51f6460ce6105ab68441d4450a80bc8d749fdf01e504ed8c/balance_diff.json