Calculated from recorded token losses using historical USD prices at the incident time.
0x4ed59e3013215c272536775a966f4365112997a6eec534d38325be014f2e15ee0x6524a5fd3fec179db3b3c1d21f700da7abe6b0deBSCOn BSC block 33527745, exploit tx 0x4ed59e3013215c272536775a966f4365112997a6eec534d38325be014f2e15ee drained the USDT side of LinkdaoDex pair 0x6524a5fd3fec179db3b3c1d21f700da7abe6b0de. The attacker-controlled helper contract 0x721a66c7767103e7dcacf8440e8dd074edff40a8, which had been deployed earlier in tx 0x6402eecdd3bf15cd2b954b422dd9fe737fb06bc60549c24cd38a30229f6b3bd6, received 29663356140000000000000 USDT from the pair, returned only 986000000000000000 USDT during the callback, and then received another 986000000000000 USDT fee rebate after reserves had already been updated.
The root cause is a math bug in LinkdaoDexPair.swap. The contract scales the adjusted balances by totalFeePercentage = 10000, but it compares the result to reserve0 * reserve1 * 1e6 instead of reserve0 * reserve1 * 10000^2. That lowers the effective invariant threshold by 100x and lets any unprivileged caller with a callback-capable contract withdraw almost the entire USDT reserve while repaying only dust. The helper ended the transaction with 29662357126000000000000 raw USDT units, and after valuing gas through the pre-state PancakeSwap USDT/WBNB pool, the net profit remains 29661985011081741594147 raw USDT units.
LinkdaoDex is a Uniswap-V2-style AMM. The victim pair 0x6524a5fd3fec179db3b3c1d21f700da7abe6b0de is the USDT/LKD market, where token0 = USDT (0x55d398326f99059ff775485246999027b3197955) and token1 = LKD (0xaf027427dc6d31a3e7e162a710a5fe27e63e275f). The pair is not directly explorer-verified, but BscScan’s verified source bundle for factory 0x717d0a192f4c5f9bc8a2d45b01d696dab2bd1b7a includes the deployed LinkdaoDexPair.sol implementation.
At the pre-state immediately after block 33527744, the pair held reserves of 29963000000000000000000 USDT and 48277499838300344044804 LKD. The governing factory exposed feePercentage = 25, buyBackLkdPercentage = 3000, treasuryPercentage = 1000, and totalFeePercentage = 10000. LinkdaoDex also supports flash-swap style callbacks through LinkdaoDexCall(address,uint256,uint256,bytes), so any public caller can ask the pair to transfer output tokens first and settle later in the callback.
The adversary model here is fully permissionless. The exploit sender was a normal EOA, no protocol role was needed, and the only attacker-side requirement was a self-controlled helper contract that implements the callback interface and can return a small amount of the borrowed token.
This is an ATTACK case caused by a broken constant-product invariant in LinkdaoDexPair.swap, not by privileged access. The pair computes balance0Adjusted and balance1Adjusted using the live factory denominator totalFeePercentage = 10000, so both adjusted-balance terms are scaled by 10000. A correct Uniswap-V2-style invariant must therefore compare those adjusted terms against reserve0 * reserve1 * 10000^2.
Instead, the deployed implementation hard-codes 1e6, which is only correct for a 1000-based denominator. The bug makes the right-hand side 100x too small, so the pair accepts swaps that repay only about 1% of the value required by the intended invariant. The exploit transaction is a direct realization of that gap: the helper withdraws almost the full USDT reserve, returns only 1 USDT during the callback, still passes the buggy inequality, and keeps the rest. Because the callback path is public and the exploit only needs on-chain state plus a generic helper contract, the opportunity is ACT.
The verified factory bundle shows both the fee configuration and the vulnerable pair logic:
uint256 private TOTAL_FEE_PERCENTAGE = 10000; // 100%
uint256 feePercentage = ILinkdaoDexFactory(factory).getFeePercentage();
uint256 totalFeePercentage = ILinkdaoDexFactory(factory).getTotalFeePercentage();
uint256 balance0Adjusted = balance0 * totalFeePercentage - amount0In * feePercentage;
uint256 balance1Adjusted = balance1 * totalFeePercentage - amount1In * feePercentage;
require(
balance0Adjusted * balance1Adjusted >=
uint256(_reserve0) * _reserve1 * 1e6,
"LinkdaoDex: K"
);
That code is dimensionally inconsistent. Since each adjusted-balance term is multiplied by 10000, the invariant should compare against reserve0 * reserve1 * 10000 * 10000, not reserve0 * reserve1 * 1000000.
Using the actual exploit-state values from the trace and receipt:
reserve0 = 29963000000000000000000
reserve1 = 48277499838300344044804
amount0Out = 29663356140000000000000
amount0In = 986000000000000000
balance0 = 300629860000000000000
balance1 = 48277499838300344044804
balance0Adjusted = 3006273950000000000000000
balance1Adjusted = 482774998383003440448040000
buggyLhs = 1451353901350115365779318980558000000000000000000000
buggyRhs = 1446538727654993208614462252000000000000000000000000
correctRhs = 144653872765499320861446225200000000000000000000000000
The exploit passes because buggyLhs >= buggyRhs, but it would fail immediately under the correct invariant because buggyLhs < correctRhs. The ratio is roughly 1:99, which matches the observed near-total drain.
The transaction trace and receipt logs show the flow clearly:
0x6524...b0de::swap(29663356140000000000000, 0, 0x721a...40a8, 0x313233)
BEP20USDT::transfer(0x721a...40a8, 29663356140000000000000)
0x721a...40a8::LinkdaoDexCall(...)
Transfer pair -> helper : 29663356140000000000000 USDT
Transfer helper-> pair : 986000000000000000 USDT
Sync reserve0,reserve1 : 300629860000000000000 / 48277499838300344044804
Transfer pair -> helper : 986000000000000 USDT
The last transfer is important. The pair calls _update before paying the rebate to msg.sender, so reserve storage is left higher than the pair’s real USDT balance by 986000000000000 raw units immediately after the exploit. That accounting drift is a secondary effect of the same vulnerable swap path.
The exploit is a single-chain, two-transaction attacker sequence:
0x6402eecdd3bf15cd2b954b422dd9fe737fb06bc60549c24cd38a30229f6b3bd6 at block 33527727, EOA 0xdf6b0200b4e1bc4a310f33df95a9087cc2c79038 deployed helper 0x721a66c7767103e7dcacf8440e8dd074edff40a8.0 of that helper contains the same EOA address at block 33527744, confirming control.0x4ed59e3013215c272536775a966f4365112997a6eec534d38325be014f2e15ee, the EOA called the helper, which then called pair.swap(...) with itself as the callback recipient.29663356140000000000000 USDT.LinkdaoDexCall, the helper returned only 986000000000000000 USDT, just enough to satisfy the buggy 1e6-scaled inequality._update, the pair paid a 986000000000000 USDT rebate back to msg.sender, which further improved attacker profit and left reserves stale.29662357126000000000000 more raw USDT units, while the pair retained only 300628874000000000000.Nothing in this sequence depends on the original attacker EOA or original helper bytecode. Any searcher could deploy a fresh callback contract, target the same public pair state, and realize the same profit condition.
The direct loss is the USDT drained from the LinkdaoDex USDT/LKD pool:
USDT296623571260000000000001829662.357126 USDTThe pair’s real USDT balance moved from 29962986000000000000000 before the exploit to 300628874000000000000 after it, leaving roughly 1% of the original USDT side in place. The exploit therefore removed about 99% of the pair’s USDT liquidity in one transaction.
Using the transaction receipt gas usage (147214) and effective gas price (10000000000 wei), gas cost was 1472140000000000 wei. Valuing that gas via the pre-state PancakeSwap USDT/WBNB pair 0x16b9a82891338f9ba80e2d6970fdda79d1eb0dae gives 372114918258405853 raw USDT units of cost, so net profit remains 29661985011081741594147 raw USDT units of USDT-equivalent value.
0x4ed59e3013215c272536775a966f4365112997a6eec534d38325be014f2e15ee0x6402eecdd3bf15cd2b954b422dd9fe737fb06bc60549c24cd38a30229f6b3bd60x6524a5fd3fec179db3b3c1d21f700da7abe6b0de0x717d0a192f4c5f9bc8a2d45b01d696dab2bd1b7a0x721a66c7767103e7dcacf8440e8dd074edff40a8/workspace/session/artifacts/collector/seed/56/0x4ed59e3013215c272536775a966f4365112997a6eec534d38325be014f2e15ee/trace.cast.log/workspace/session/artifacts/collector/seed/56/0x4ed59e3013215c272536775a966f4365112997a6eec534d38325be014f2e15ee/balance_diff.json/workspace/session/artifacts/collector/seed/56/0x4ed59e3013215c272536775a966f4365112997a6eec534d38325be014f2e15ee/metadata.jsonhttps://bscscan.com/address/0x717d0a192f4c5f9bc8a2d45b01d696dab2bd1b7a#codehttps://bscscan.com/address/0x721a66c7767103e7dcacf8440e8dd074edff40a80x6524a5fd3fec179db3b3c1d21f700da7abe6b0de: returns Contract source code not verified