DKP Exchange Flash-Price Exploit
Exploit Transactions
Victim Addresses
0x89257a52ad585aacb1137fcc8abbd03a963b9683BSC0xbe654fa75bad4fd82d3611391fda6628bb000cc7BSCLoss Breakdown
Similar Incidents
Local Traders Price Takeover
39%FortuneWheel BNBP/Link Price-Manipulation Fee Drain
33%VistaFinance oracle mispricing enables VISTA flash-loan arbitrage drain
33%SlurpyCoin BuyOrSell flaw drains BNB via flash-loan swaps
33%SellToken Short Oracle Manipulation
32%CS Pair Balance Burn Drain
32%Root Cause Analysis
DKP Exchange Flash-Price Exploit
1. Incident Overview TL;DR
On BSC, the adversary cluster used two transactions to extract value from the unverified DKP exchange contract at 0x89257A52Ad585Aacb1137fCc8abbD03a963B9683. In tx 0x0c850f54c1b497c077109b3d2ef13c042bb70f7f697201bcf2a4d0cb95e74271 at block 26284132, the attacker flash-borrowed 259390 USDT from the DKP/USDT Pancake pair 0xBE654FA75bAD4Fd82D3611391fDa6628bB000CC7, temporarily depressed the pair's live USDT balance, and bought DKP from the exchange for only 100 USDT while the exchange was mispricing inventory from those manipulated balances. In tx 0x2d31e45dce58572a99c51357164dc5283ff0c02d609250df1e6f4248bd62ee01 at block 26284146, the attacker sold the acquired DKP back through PancakeRouter and realized 79233.963143957652842113 USDT to the EOA.
The root cause is a deterministic pricing bug, not a privileged action. The exchange's getUsdtPrice() logic prices DKP as DKP.balanceOf(lp) * 1e18 / USDT.balanceOf(lp), so a flash-loan-induced reduction in the pair's live USDT balance makes DKP appear massively cheaper than the honest market price. A constructor-time helper contract bypassed the exchange's contract-caller gate because extcodesize(msg.sender) is zero during construction, allowing the manipulated quote to be consumed permissionlessly. Across the two transactions, the adversary cluster's combined USDT balance increased by 78459.549143957652842113, while the pair lost 78559.549143957652842113 USDT and the exchange lost 17115.243270155346986000 DKP gross inventory.
2. Key Background
- The primary victim-side component is the unverified exchange contract
0x89257A52Ad585Aacb1137fCc8abbD03a963B9683. Validator storage reads at block26284131show that it points at the DKP/USDT Pancake pair in slot1, USDT in slot2, DKP in slot3, uses a1000fee denominator in slot4, fixed shares30/10/5/5in slots5to8, and a minimum trade amount of60e18in slot9. - The DKP token at
0xd06fa1ba7c80f8e113c2dc669a23a9524775cf19is verified source. Its_transferpath applies fees on AMM interactions, which is why the tx2 sale sends only16178.183701114341738517DKP into the pair even though the attacker starts the second transaction with17029.667053804570251070DKP. - The DKP/USDT Pancake pair at
0xBE654FA75bAD4Fd82D3611391fDa6628bB000CC7is a standard UniswapV2-style pair. Its live ERC-20 balances can diverge fromgetReserves()inside a flash-swap callback before the transaction settles, which is exactly the state the exchange incorrectly trusts.
3. Vulnerability Analysis & Root Cause Summary
This incident is an ATTACK against the exchange contract's pricing logic. The exchange does not use a manipulation-resistant oracle, a TWAP, or even the pair's stored reserves; instead it prices DKP from raw token balances currently sitting in the LP address. That choice makes the quote directly controllable inside a single transaction with a flash swap. The tx1 trace shows the exchange reading DkpToken::balanceOf(pair) and BEP20USDT::balanceOf(pair) after the pair has already transferred out 259390 USDT, so the quote is based on 36873.976322434846364457 DKP versus only 215.445236391899433885 USDT. Under honest pre-state balances, 100 USDT would buy about 14.203853193009140470 DKP; under the manipulated state, the exchange computes 17115.243270155346986000 DKP gross for the same 100 USDT input and pays 17029.667053804570251070 DKP net after the fixed connection share. The exchange's anti-bot control is also ineffective, because a freshly created helper contract can call exchange(uint256) from its constructor while its runtime code size is still zero.
The vulnerable components are therefore:
0x89257A52Ad585Aacb1137fCc8abbD03a963B9683::getUsdtPrice()using live LP balances as a price oracle.0x89257A52Ad585Aacb1137fCc8abbD03a963B9683::exchange(uint256)multiplying user input by that manipulable quote and transferring real DKP inventory.
The violated security principles are straightforward: do not price inventory from same-tx manipulable LP balances, do not rely on msg.sender.code.length for authorization, and do not treat a single-pool spot ratio as a safe oracle for inventory sales.
4. Detailed Root Cause Analysis
The pre-state at block 26284131 already contains everything needed for the exploit: the DKP/USDT pair is liquid, the exchange still holds enough DKP to sell, and its minimum trade configuration is active.
Validator storage reads at block 26284131
exchange.slot1 = 0x...be654fa75bad4fd82d3611391fda6628bb000cc7 (LP pair)
exchange.slot2 = 0x...55d398326f99059ff775485246999027b3197955 (USDT)
exchange.slot3 = 0x...d06fa1ba7c80f8e113c2dc669a23a9524775cf19 (DKP)
exchange.slot4 = 1000
exchange.slot5 = 30
exchange.slot6 = 10
exchange.slot7 = 5
exchange.slot8 = 5
exchange.slot9 = 60000000000000000000 (minimum trade = 60e18)
pair USDT = 259605445236391899433885
pair DKP = 36873976322434846364457
exchange DKP = 17901400217105153011279
Tx 0x0c850f54... begins with attacker contract 0xf34ad6cea329f62f4516ffe00317ab09d934fba3 calling PancakePair::swap(259390e18, 0, ...). During the callback, the attacker transfers 100e18 USDT to a fresh helper 0xb24fc2f9ee4467cf64990584fab02274aa247735, deploys it, approves the exchange, and calls exchange(100e18) from the helper's constructor.
Collector trace for tx 0x0c850f54...
PancakePair::swap(259390000000000000000000, 0, 0xf34a..., 0x00)
0xf34a...::pancakeCall(...)
BEP20USDT::transfer(0xb24f..., 100000000000000000000)
new helper @0xb24f...
BEP20USDT::approve(exchange, 100000000000000000000)
exchange::exchange(100000000000000000000)
DkpToken::balanceOf(pair) -> 36873976322434846364457
BEP20USDT::balanceOf(pair) -> 215445236391899433885
DkpToken::transfer(0xb24f..., 17029667053804570251070)
Those two balanceOf calls are the decisive breakpoint. The pair still reports the honest reserves through getReserves(), but the exchange does not use them. Instead it sees only 215.445236391899433885 USDT still sitting in the pair address after the flash borrow, computes a massively inflated DKP-per-USDT ratio, and transfers out real exchange inventory. The economics are explicit:
- Honest pre-state quote:
36873.976322434846364457 / 259605.445236391899433885 = 0.1420385319300914047034DKP per USDT. - Manipulated same-tx quote:
36873.976322434846364457 / 215.445236391899433885 = 171.1524327015534698603DKP per USDT. - For
100USDT, the honest output would be about14.203853193009140470DKP, while the manipulated quote produces17115.243270155346986000DKP gross and17029.667053804570251070DKP net after the fixed share transfer.
The constructor-time helper matters because the exchange rejects normal deployed contract callers. The tx1 trace embeds the helper constructor bytecode and shows the call succeeding before runtime code exists at 0xb24f..., which is consistent with the known extcodesize construction bypass.
After the underpriced purchase, the attacker repays the flash swap with the borrowed USDT plus 674.414 USDT from its own working capital, for a total pair inflow of 260064.414 USDT. That ends tx1 with the attacker contract holding 17029.667053804570251070 DKP and the exchange contract down by 17115.243270155346986000 DKP gross.
Tx 0x2d31e45d... realizes the profit. The attacker approves PancakeRouter and sells the DKP into the same pair.
Collector trace for tx 0x2d31e45d...
0xf34a...::11a73c8e()
DkpToken::balanceOf(0xf34a...) -> 17029667053804570251070
PancakeRouter::swapExactTokensForTokensSupportingFeeOnTransferTokens(
17029667053804570251070,
0,
[DKP, USDT],
0xF38B677fa6E9E51338D0c32FD21afe43406E06Df,
1678270651
)
DkpToken::transferFrom(0xf34a..., pair, 17029667053804570251070)
emit Transfer(... to pair, value: 16178183701114341738517)
PancakePair::swap(79233963143957652842113, 0, attacker EOA, 0x)
The verified DKP token source explains the sale-side fee: only 16178.183701114341738517 DKP reaches the pair, and the rest is distributed according to the token's fee schedule. Even with that fee, the sale drains 79233.963143957652842113 USDT from the pair to attacker EOA 0xf38b677fa6e9e51338d0c32fd21afe43406e06df. RPC balance checks at block 26284146 confirm the final state reported in the JSON artifacts: the EOA holds 79233.963143957652842113 USDT, the attacker contract still holds 158.331305403783296414 USDT, and the pair's USDT balance has fallen to 181045.896092434246591772.
5. Adversary Flow Analysis
The adversary cluster contains:
- EOA
0xf38b677fa6e9e51338d0c32fd21afe43406e06df, the sender of both attacker-crafted transactions and the final USDT profit recipient. - Contract
0xf34ad6cea329f62f4516ffe00317ab09d934fba3, which performs the flash swap in tx1 and the sale in tx2. - Constructor-time helper
0xb24fc2f9ee4467cf64990584fab02274aa247735, deployed during tx1 only to satisfy the exchange's weak caller gate.
The end-to-end flow is:
- Price manipulation and purchase, tx
0x0c850f54.../ block26284132: the attacker flash-borrows259390USDT from the pair, deploys the helper during the callback, pays100USDT into the exchange while the pair balance is depressed, and receives17029.667053804570251070DKP. - Monetization, tx
0x2d31e45d.../ block26284146: after waiting14blocks, the attacker sells the DKP through PancakeRouter back into the same pair and receives79233.963143957652842113USDT to the EOA. - Net outcome: the adversary cluster's combined USDT rises from
932.745305403783296414to79392.294449361436138527, a net gain of78459.549143957652842113USDT before native gas, while the pair loses78559.549143957652842113USDT and the exchange loses gross DKP inventory.
The exploit remains ACT because every step is permissionless: flash swaps are public, the exchange sale path is public, the constructor-time helper is locally deployable by any actor, and the follow-up sale uses public router liquidity.
6. Impact & Losses
The measurable protocol-side loss is concentrated in the DKP/USDT Pancake pair's USDT reserves. From the start of the sequence to the end of tx2, the pair loses 78559.549143957652842113 USDT, represented in smallest units as 78559549143957652842113. The exchange contract also transfers out 17115.243270155346986000 DKP gross inventory on a payment of only 100 USDT, which is the direct manifestation of the pricing bug. The adversary cluster realizes 78459.549143957652842113 USDT profit before gas, while the remaining 100 USDT is the exchange payment path and the difference versus the pair depletion is explained by the attacker's residual contract balance.
7. References
- Seed index covering both exploit transactions and collected artifacts.
- Tx
0x0c850f54c1b497c077109b3d2ef13c042bb70f7f697201bcf2a4d0cb95e74271metadata, balance diff, and full trace. - Tx
0x2d31e45dce58572a99c51357164dc5283ff0c02d609250df1e6f4248bd62ee01metadata, balance diff, and full trace. - Verified DKP token source at
0xd06fa1ba7c80f8e113c2dc669a23a9524775cf19, especially_transfer,_initParam, and_takeFee. - Validator RPC reads at block
26284131confirming exchange storage slots and token balances used in the analysis.