All incidents

Helper Callback Drain

Share
Dec 11, 2022 00:19 UTCAttackLoss: 1,342.17 USDTPending manual check1 exploit txWindow: Atomic
Estimated Impact
1,342.17 USDT
Label
Attack
Exploit Tx
1
Addresses
1
Attack Window
Atomic
Dec 11, 2022 00:19 UTC → Dec 11, 2022 00:19 UTC

Exploit Transactions

TX 1Ethereum
0x313d23bdd9277717e3088f32c976479c09d4b8a94d5d94deb835d157fd0850ce
Dec 11, 2022 00:19 UTCExplorer

Victim Addresses

0x28d949fdfb5d9ea6b604fa6fee3d6548ea779f17Ethereum

Loss Breakdown

1,342.17USDT

Similar Incidents

Root Cause Analysis

Helper Callback Drain

1. Incident Overview TL;DR

Transaction 0x313d23bdd9277717e3088f32c976479c09d4b8a94d5d94deb835d157fd0850ce drained 1,342,169,600 raw USDT units from helper contract 0x28d949fdfb5d9ea6b604fa6fee3d6548ea779f17 and delivered 1,342,168,246 raw USDC units to attacker EOA 0xb61e7f192a9ad5d11e2452f53d0ddf91b58239dc. The root cause was a permissionless flash-loan callback path: any caller could invoke DSP pool 0x3058ef90929cb8180174d74c507176cca6835d73 with the helper as assetTo, causing the helper to swap flash-loaned USDT into USDC, forward that USDC to an attacker-controlled recipient path, and cover DSP repayment from the helper's own USDT inventory.

2. Key Background

The victim was an unverified helper contract deployed in block 13956790 by 0x37c6ca7be321e1ce86f2f8062171c47748951bfb. Its creation bytecode hardcodes USDT 0xdac17f958d2ee523a2206206994597c13d831ec7, USDC 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48, DODO pool 0xc9f93163c99695c6526b799ebca2207fdf7d61ad, and DSP pool 0x3058ef90929cb8180174d74c507176cca6835d73.

The verified DSP source shows that flashLoan transfers assets to assetTo and then calls IDODOCallee(assetTo).DSPFlashLoanCall(...) when data.length > 0. That makes the helper callback reachable by any external caller that targets the helper as assetTo.

function flashLoan(
    uint256 baseAmount,
    uint256 quoteAmount,
    address assetTo,
    bytes calldata data
) external preventReentrant {
    _transferBaseOut(assetTo, baseAmount);
    _transferQuoteOut(assetTo, quoteAmount);
    if (data.length > 0)
        IDODOCallee(assetTo).DSPFlashLoanCall(msg.sender, baseAmount, quoteAmount, data);
    ...
}

The verified DODO source shows sellBaseToken pays quote token to msg.sender before pulling base token in. In this incident, the helper used that path to convert flash-loaned USDT into attacker-forwarded USDC.

function sellBaseToken(
    uint256 amount,
    uint256 minReceiveQuote,
    bytes calldata data
) external ... returns (uint256) {
    (uint256 receiveQuote, ... ) = _querySellBaseToken(amount);
    require(receiveQuote >= minReceiveQuote, "SELL_BASE_RECEIVE_NOT_ENOUGH");
    _quoteTokenTransferOut(msg.sender, receiveQuote);
    ...
    _baseTokenTransferIn(msg.sender, amount);
}

3. Vulnerability Analysis & Root Cause Summary

The vulnerability is an attacker-triggerable funds-misuse path in the helper's DSPFlashLoanCall logic. The helper trusted msg.sender == DSP as its effective authorization boundary, but DSP itself exposes flashLoan publicly. That made the callback reachable by any unprivileged caller willing to ask DSP to target the helper as assetTo. Inside the callback, the helper swapped the flash-loaned USDT for USDC through DODO, forwarded the USDC to an attacker-controlled recipient, and then measured the DSP pool balance to repay any shortfall from the helper's own USDT balance. The violated invariant is that helper-owned USDT should not be spendable for arbitrary third-party benefit without explicit authorization of both the spend path and the beneficiary. The concrete breakpoint is the callback sequence that transfers out received USDC and later transfers helper-held USDT back to DSP to keep the flash loan whole.

4. Detailed Root Cause Analysis

The seed trace shows repeated calls from the attacker wrapper 0x0757d02596ef9840048def00eeb8e0f3862cc7ca into helper 0x28d949... via DSPFlashLoanCall, proving the callback was reachable through public DSP flash-loan routing rather than helper ownership.

0x28d949...::DSPFlashLoanCall(
  0x0757d02596eF9840048deF00eeB8E0F3862cC7Ca,
  0,
  16777120,
  0x0000000000000000000000000757d02596ef9840048def00eeb8e0f3862cc7ca...
)
0x0757d02596eF9840048deF00eeB8E0F3862cC7Ca::7b4323de(...)

The helper creation bytecode embeds the relevant protocol addresses and the logic pattern described in the analysis: a DSP-only callback gate, a DODO sale using USDT as base and USDC as quote, an outward USDC transfer, a callback into the attacker-controlled contract, and a final USDT transfer back to DSP when needed. That is sufficient to attribute the vulnerable behavior even though the helper is unverified.

Receipt-scoped USDT transfer accounting for the seed transaction is decisive. It records net deltas of -1342169600 for helper 0x28d949..., +1342169600 for DODO pool 0xc9f931..., and 0 for DSP 0x3058ef.... DSP was not the loss bearer; the helper inventory financed repayment.

Exact transaction delta evidence independently shows the attacker EOA ended the transaction with +1342168246 raw USDC units, while native balance deltas show -351621050000000000 wei of gas paid. Public market pricing artifacts value that gas at 445209837 raw USDC units, leaving positive profit after gas. This matches the ACT framing: an unprivileged adversary could realize value directly from the misconfigured helper.

The exploit preconditions are public and concrete: the helper must hold USDT, DSP flash loans must remain callable, and the DODO pool must still quote enough USDC for the helper's repeated sales. No private key compromise, admin privilege, or private orderflow was required.

5. Adversary Flow Analysis

The attacker lifecycle is fully traceable on-chain.

First, EOA 0xb61e7f192a9ad5d11e2452f53d0ddf91b58239dc deployed wrapper 0x0757d02596ef9840048def00eeb8e0f3862cc7ca in transaction 0xfe551432654fc16ad10263cb1495ef2dc2e08ed8be525f743f3c05c40f5e96f0 at block 16157740. The deployment metadata ties the wrapper directly to that EOA.

Second, in transaction 0x313d23bdd9277717e3088f32c976479c09d4b8a94d5d94deb835d157fd0850ce, the wrapper repeatedly called DSP flashLoan with helper 0x28d949... as assetTo. Each iteration borrowed 16,777,120 raw USDT units, invoked the helper callback, sold the borrowed USDT into the DODO pool, forwarded the USDC proceeds through the attacker-controlled path, and left DSP flat after repayment.

Third, the attacker realized profit in USDC. Exact balance diffs show attacker EOA 0xb61e7f... moved from 0 to 1342168246 raw USDC units during the exploit transaction. The same transaction paid gas from the EOA, so the profit accounting stays inside one public transaction.

6. Impact & Losses

The directly affected victim was helper 0x28d949fdfb5d9ea6b604fa6fee3d6548ea779f17. It lost 1,342,169,600 raw USDT units, with decimal=6, so the loss was 1,342.169600 USDT. The attacker received 1,342,168,246 raw USDC units and remained profitable after gas using public historical pricing evidence. DSP itself was net flat in USDT, which rules out a DSP insolvency explanation and confirms the helper inventory absorbed the loss.

7. References

  1. Seed transaction trace: 0x313d23bdd9277717e3088f32c976479c09d4b8a94d5d94deb835d157fd0850ce
  2. Helper creation metadata and bytecode for 0x28d949fdfb5d9ea6b604fa6fee3d6548ea779f17
  3. Strategy creation metadata for 0x0757d02596ef9840048def00eeb8e0f3862cc7ca
  4. Verified DSP source for 0x3058ef90929cb8180174d74c507176cca6835d73
  5. Verified DODO source for 0xc9f93163c99695c6526b799ebca2207fdf7d61ad
  6. Exact USDT receipt accounting for the seed transaction
  7. Exact USDC/native delta evidence for the attacker EOA
  8. Public market pricing evidence for block 16157843