DLMC Token on BNB Chain was exploited on 2026-06-24 at 11:15:10 UTC in transaction 0x151025d3f0a782340a74d30ef33a5fad044b838e74437a803f0652e70c231306. The attacker used a flash-swapped 1,420,000 USDT, registered two fresh affiliate accounts in a one-hop referral chain, and routed two large buy() calls through them. Because buy() mints most new DLMC to the token contract itself while livePrice is recalculated from contract USDT reserves divided by only the externally circulating DLMC supply, the attacker could sharply raise livePrice and then immediately call sell() on the referral-reward tokens minted to the first account. The transaction pulled 1,646,119.118936329868440038 USDT out of the DLMC contract, repaid the 1,423,558.897243107769423559 USDT Pancake flash swap, and retained 222,560.221693222099016479 USDT profit.
Root Cause
Vulnerable Contract
The vulnerable contract is DLMCToken at 0xf2ca2a3572b26ae7c479dc7ae36d922113b1bdf2. Its source is verified on BscScan. The attacked liquidity source is the Pancake V2 pair 0x16b9a82891338f9ba80e2d6970fdda79d1eb0dae (USDT/WBNB), but that pair behaved as expected and only supplied the flash liquidity.
Vulnerable Function
The vulnerable execution path is the interaction between buy(uint256) selector 0xd96a094a, _distributeReferralBonusOnBuy(address,address,uint256), _applyBurnAndDaoSplit(uint256,address), _updatePrice(), and sell(uint256) selector 0xe4849b32. buy() transfers USDT into the contract, mints new DLMC to the contract itself, and then pays referral rewards in freshly minted DLMC using the current livePrice. _updatePrice() sets livePrice = tradingReserve / circulatingSupply, where tradingReserve is the contract’s USDT balance but circulatingSupply excludes the large DLMC balance still sitting on the contract. The attacker used this mismatch to inflate livePrice with borrowed USDT, then sold the reward DLMC back to the same contract at the inflated price.
Vulnerable Code
function buy(uint256 amountQuote) external nonReentrant {
quoteToken.safeTransferFrom(msg.sender, address(this), amountQuote);
totalUSDTReceived += normalizedQuote;
uint256 buyAmount = normalizedQuote - ((normalizedQuote * BUY_PERCENT) / 100);
uint256 tokensToUser = (buyAmount * 10 ** 18) / livePrice;
_mint(address(this), tokensToUser); // <-- VULNERABILITY: borrowed USDT grows reserves while minted DLMC stays on the contract
address referrer = affiliates[msg.sender].referrer;
if (referrer != address(0) && affiliates[referrer].isRegistered) {
_distributeReferralBonusOnBuy(msg.sender, referrer, buyAmount); // attacker-controlled reward path
}
_updatePrice(); // <-- VULNERABILITY: reprices from reserve growth against only external float
}
function _updatePrice() internal {
uint256 usdtReserve = quoteToken.balanceOf(address(this));
uint256 tradingReserve = usdtReserve > daoUsdtBalance ? usdtReserve - daoUsdtBalance : 0;
uint256 total = totalSupply();
uint256 circulatingSupply = total <= PRE_MINED_LP ? 1 : total - PRE_MINED_LP;
uint256 contractLPTBalance = balanceOf(address(this));
if (circulatingSupply > contractLPTBalance) {
circulatingSupply = circulatingSupply - contractLPTBalance;
}
if (circulatingSupply == 0) circulatingSupply = 1;
uint256 newPrice = (reserve18 * 1e18) / circulatingSupply; // <-- VULNERABILITY: reserve includes attacker-funded USDT, denominator excludes most freshly minted DLMC
livePrice = newPrice;
}
function sell(uint256 amountTokens) external nonReentrant {
uint256 sellValueUsdt18 = (amountTokens * livePrice) / 1e18;
uint256 actualPayout = _denormalizeFrom18(sellValueUsdt18, quoteDecimals);
require(quoteToken.balanceOf(address(this)) >= actualPayout, "Insufficient USDT liquidity in contract");
_burn(msg.sender, amountTokens);
quoteToken.safeTransfer(msg.sender, actualPayout); // <-- VULNERABILITY: attacker immediately extracts the attacker-funded reserve at inflated livePrice
_updatePrice();
}
Why It’s Vulnerable
Expected behavior: If a protocol prices redemptions from its own treasury balance, the quoted price must not be manipulable by temporarily depositing borrowed assets while simultaneously excluding the newly issued liabilities from the pricing denominator. Referral or dividend rewards also should not create immediately sellable tokens whose value is derived from attacker-controlled reserve injections in the same transaction.
Actual behavior: buy() adds the full borrowed USDT amount to the contract balance, but most of the corresponding newly minted DLMC never enters circulation because it remains on address(this). _updatePrice() then treats the reserve increase as real backing while subtracting the contract’s own DLMC balance from the denominator. That makes livePrice jump from roughly 0.412638917368540248 USDT before the first buy to roughly 24.975754129523010840 USDT after the second buy, even though the new reserve came entirely from the attacker’s flash-swapped USDT.
The attacker then monetizes that synthetic price through the built-in referral path. The first attacker-controlled account becomes the referrer of the second attacker-controlled account. Each buy() grants the referrer a 5% reward denominated in USDT value but paid out in DLMC at the pre-update livePrice, with the contract itself minting and transferring those tokens. Once the two buys complete, the first account holds 65,908.685295332365480640 DLMC that cost the attacker no permanent capital. Selling those reward tokens at the inflated livePrice drains the contract’s USDT treasury.
This is a protocol-internal accounting flaw, not a flash-loan-provider issue and not a Pancake pair bug. The flash swap only amplifies the design error by temporarily supplying the reserve that livePrice blindly trusts.
Attack Execution
High-Level Flow
- The attacker EOA
0x74c4a756933d0f713facb1dea325ef511646c3b1deployed attack contract0x4adbddea5781caccadd9f73f00e07201b541414e, which in turn deployed helper contracts0xe81bf6e392eca9ad594b5452ea53cf7071760a04and0x8b5a72c4ce0d3a7676ce06b8e42aeb255bba476e. - The helper borrowed
1,420,000 USDTfrom Pancake pair0x16b9a82891338f9ba80e2d6970fdda79d1eb0daeviaswap(..., bytes)and itspancakeCall(...)callback. - During the callback, the first helper registered under pre-existing account
0x62cefe76eecc737d7ee384efdbad8d2c53c1d792, spent420,000 USDTonbuy(), and caused that referrer to receive freshly minted DLMC rewards. - The first helper then funded the second helper with
1,000,000 USDT; the second helper registered under the first helper, spent the full1,000,000 USDTonbuy(), and minted a much larger referral reward to the first helper. - After the two buys pushed
livePriceto about24.975754129523010840 USDTper DLMC, the first helper sold its reward balance of65,908.685295332365480640 DLMCback to the token contract throughsell(). - The helper repaid the Pancake pair
1,423,558.897243107769423559 USDTand sent the remaining222,560.221693222099016479 USDTprofit to attacker receiver0x701bb7b460ae231dbbcfa3d87f0ab5b458429699.
Detailed Call Trace
The top-level attacker contract 0x4adbddea5781caccadd9f73f00e07201b541414e created helper 0xe81bf6e392eca9ad594b5452ea53cf7071760a04 and called its selector 0x16521e5a with three parameters: flash amount 1,420,000 USDT, secondary funding 1,000,000 USDT, and receiver 0x701bb7b460ae231dbbcfa3d87f0ab5b458429699. The helper then called Pancake pair 0x16b9a82891338f9ba80e2d6970fdda79d1eb0dae.swap(uint256,uint256,address,bytes) and received 1,420,000 USDT in the callback.
Inside pancakeCall(address,uint256,uint256,bytes), the first helper executed registerAffiliate(0x62cefe76eecc737d7ee384efdbad8d2c53c1d792) and approved USDT to DLMC. It then called buy(420000000000000000000000), transferring 420,000 USDT into DLMCToken. The verified contract code computed buyAmount = 357,000 USDT after the 15% deduction and minted 865,163.184986627291978275 DLMC to address(this) at the pre-buy livePrice of 0.412638917368540248 USDT. The contract immediately paid three developer reward legs to 0x61e7f1d43567e380ea5b4e7ac81d6ffebf1bbcf5 and a referral reward of 34,606.527399465091679131 DLMC to referrer 0x62cefe76eecc737d7ee384efdbad8d2c53c1d792.
Next, the first helper created second helper 0x8b5a72c4ce0d3a7676ce06b8e42aeb255bba476e, transferred 1,000,000 USDT to it, and invoked unresolved selector 0xa2608d86, which the trace shows simply performs a nested register/buy sequence. That second helper registered under the first helper and called buy(1000000000000000000000000). This second purchase transferred another 1,000,000 USDT into DLMC, minted 1,839,009.849156875914417340 DLMC to the contract, and paid a referral reward of 73,560.393966275036576695 DLMC to the first helper. With the reserve now bloated to 1,646,119.118936329868440038 USDT while most newly minted DLMC still sat inside the contract, _updatePrice() repriced livePrice to 24.975754129523010840 USDT.
The first helper then queried livePrice() and called sell(65908685295332365480640), i.e. 65,908.685295332365480640 DLMC. That balance exactly matches the helper’s referral reward from the second buy plus the helper’s one-share DAO allocation from the same reward round. sell() burned those tokens and transferred 1,646,119.118936329868440038 USDT from DLMC back to the helper. The helper finally repaid 1,423,558.897243107769423559 USDT to the Pancake pair and transferred 222,560.221693222099016479 USDT to 0x701bb7b460ae231dbbcfa3d87f0ab5b458429699.
Financial Impact
The DLMC contract paid 1,646,119.118936329868440038 USDT to the attacker-controlled helper during the final sell() call. Of that amount, 1,420,000 USDT was the borrowed flash principal and 3,558.897243107769423559 USDT was the Pancake flash-swap fee, so the transaction’s retained profit was 222,560.221693222099016479 USDT.
The irreversible loss was borne by the DLMC token treasury. Before the attack transaction, the contract held 226,119.118936329868440044 USDT; after both buys and before the final sell, its USDT balance had grown to 1,646,119.118936329868440038 USDT, entirely from attacker-controlled deposits. The final sell() withdrew all of that balance, meaning the protocol not only lost its pre-existing treasury but also let the attacker recover almost all flash-injected liquidity plus the protocol’s original USDT backing. The Pancake pair itself ended the transaction with a positive 3,558.897243107769423559 USDT gain from fees.
Evidence
- Transaction:
0x151025d3f0a782340a74d30ef33a5fad044b838e74437a803f0652e70c231306on BNB Chain, block106091607, statussuccess. - Attacker:
0x74c4a756933d0f713facb1dea325ef511646c3b1; profit receiver0x701bb7b460ae231dbbcfa3d87f0ab5b458429699. - Vulnerable contract:
DLMCTokenat0xf2ca2a3572b26ae7c479dc7ae36d922113b1bdf2. - Impacted component: DLMC’s internal USDT treasury and its self-priced
buy()/sell()market. - Key on-chain fact: receipt log
0x93transfers1,646,119.118936329868440038 USDTfrom DLMC to helper0xe81bf6e392eca9ad594b5452ea53cf7071760a04. - Key on-chain fact: receipt log
0x97transfers1,423,558.897243107769423559 USDTfrom the helper back to Pancake pair0x16b9a82891338f9ba80e2d6970fdda79d1eb0dae, leaving222,560.221693222099016479 USDTsent to the attacker receiver in log0x98.
Remediation
DLMC should not derive redeemability from quoteToken.balanceOf(address(this)) while excluding most outstanding DLMC from the pricing denominator. The protocol needs an explicit solvency invariant tying mintable rewards, circulating supply, and redeemable reserves together, and should block same-transaction reserve-funded repricing entirely. Referral, dividend, and DAO rewards should vest or be claimable only from pre-funded reward buckets rather than being minted on demand against attacker-supplied reserves. Finally, any self-priced treasury market should use capped oracle/TWAP style pricing or delayed epoch accounting, not immediate reserve snapshots that a flash loan can control inside one transaction.