All incidents

SellToken Short Oracle Manipulation

Share
May 13, 2023 07:02 UTCAttackLoss: 35.26 BNBPending manual check4 exploit txWindow: 9m 58s
Estimated Impact
35.26 BNB
Label
Attack
Exploit Tx
4
Addresses
2
Attack Window
9m 58s
May 13, 2023 07:02 UTC → May 13, 2023 07:12 UTC

Exploit Transactions

TX 1BSC
0x7d04e953dad4c880ad72b655a9f56bc5638bf4908213ee9e74360e56fa8d7c6a
May 13, 2023 07:12 UTCExplorer
TX 2BSC
0xd877c0c2852ee688706a3899e7233a32c57ecef7616afe97b74aaff1b58964bd
May 13, 2023 07:04 UTCExplorer
TX 3BSC
0xca4e97a3221026c4fd271a342e7ff21ab4006e15da7b3801e8c44d66780fe8d6
May 13, 2023 07:03 UTCExplorer
TX 4BSC
0x893453b1c9db19d59548bf05c9213e1d2a9ff8195c4f0bc5f17bba9b3b18beee
May 13, 2023 07:02 UTCExplorer

Victim Addresses

0x57Db19127617B77c8abd9420b5a35502b59870D6BSC
0x8D190C70937493046a464440d28f126A4E42eF7fBSC

Loss Breakdown

35.26BNB

Similar Incidents

Root Cause Analysis

SellToken Short Oracle Manipulation

1. Incident Overview TL;DR

SellToken on BNB Smart Chain exposed an ACT-style short-engine exploit because it let any user list arbitrary tokens through its Minerals child and then priced both short entry and short settlement from live PancakeSwap spot quotes. In seed transaction 0x7d04e953dad4c880ad72b655a9f56bc5638bf4908213ee9e74360e56fa8d7c6a at block 28168035, the attacker EOA 0x1581262Fd72776bA5DA2337C4C4E1B92C6e36ae6 used orchestrator contract 0x19Ed7Cd5F1d2bD02713131344d6890454D7C599F to borrow WBNB from public DODO pools, manipulate the 0xa645995e9801F2ca6e2361eDF4c2A138362BADe4/WBNB Pancake market, open a short against the inflated quote, reverse the market, and settle against the depressed quote.

The root cause is the combination of two permissionless design choices in protocol contract 0x57Db19127617B77c8abd9420b5a35502b59870D6 and its Minerals child 0x8D190C70937493046a464440d28f126A4E42eF7f: anyone can seed and register an arbitrary token market, and the protocol trusts attacker-controllable IRouter.getAmountsOut spot prices in both setTokenPrice / ShortStart and withdraw. The seed balance diff shows the attacker EOA's native balance increased from 239.449728247930520710 BNB to 274.705384655194683681 BNB, a net gain of 35.255656407264162971 BNB after gas.

2. Key Background

SellToken is a short engine that deploys and uses a Minerals child contract to hold token inventory and route Pancake trades. The verified protocol source shows:

  • Minerals.setPool(..., isPool = 1) accepts arbitrary token listings as long as the paired asset is WBNB or USDT, the caller transfers tokens in, and the caller pays the listing fee.
  • SellToken.setTokenPrice stores a one-token snapshot for the caller and token, but the snapshot is just the current Pancake getAmountsOut quote and expires after 30 seconds.
  • SellToken.ShortStart requires that the current quote stay within 5% of the stored snapshot, but once that timer passes it again reads Pancake spot pricing to set short state and then instructs Minerals to buy more of the same token.
  • SellToken.withdraw recomputes payout from another spot read and has Minerals sell protocol inventory into the same market.

Independent RPC reads at block 28168034, the state immediately before the seed exploit transaction, confirm the protocol conditions claimed in root_cause.json:

SellToken.mkt() = 0x8D190C70937493046a464440d28f126A4E42eF7f
Minerals.getPair(0xa645995e9801F2ca6e2361eDF4c2A138362BADe4) = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c
SellToken.tokenPrice(0x19Ed7Cd5F1d2bD02713131344d6890454D7C599F, 0xa645995e9801F2ca6e2361eDF4c2A138362BADe4) = 154467646217615
SellToken.tokenPriceTime(0x19Ed7Cd5F1d2bD02713131344d6890454D7C599F, 0xa645995e9801F2ca6e2361eDF4c2A138362BADe4) = 1683961924
Minerals.balanceOf(0xa645995e9801F2ca6e2361eDF4c2A138362BADe4) = 15917150134720644674378854

Those values show that the attacker-controlled market was already registered, the attacker contract already held a live price snapshot, the 30-second gate had already matured before block timestamp 1683961936, and the protocol had live inventory available to monetize.

3. Vulnerability Analysis & Root Cause Summary

This is an ATTACK root cause, not a benign MEV opportunity. The protocol violates a basic safety invariant for short systems: collateral limits and settlement must not depend on same-transaction, attacker-manipulable spot quotes. Instead, SellToken takes both the opening quote and the settlement quote directly from PancakeSwap through IRouter.getAmountsOut, and Minerals allows arbitrary tokens to be listed without any liquidity-quality, oracle-source, or economic-sanity control.

The verified source makes the vulnerable control flow explicit. Minerals.setPool(..., isPool = 1) seeds inventory and binds a token to WBNB or USDT without access control. SellToken.setTokenPrice stores a live quote, ShortStart checks only a narrow 5% change bound and then snapshots a fresh spot quote into short state, and withdraw computes payout from yet another fresh spot quote before calling mkt.sell. Because the attacker controls the listed token market, those price reads are attacker inputs rather than independent measurements.

The code-level breakpoint is in ShortStart at uint newTokenValue = getTokenPrice(coin,bnbOrUsdt,bnb*98/100); and in withdraw at uint getBNB = getMyShort(...) followed by uint getTokens = getTokenPrice(...) before mkt.sell(...). Once those values are derived from manipulated reserves, the protocol converts its inventory into attacker profit.

4. Detailed Root Cause Analysis

4.1 Permissionless market creation

The verified Minerals source lets any user seed protocol inventory for a new token and bind that token to WBNB or USDT:

function setPool(address token,address token1,uint coin,uint _day,uint isPool) payable public {
    require(token1 == _WBNB || token1 == _USDT);
    if (isPool == 0) {
        ...
    } else {
        require(msg.value >= fee);
        bool isok = IERC20(token).transferFrom(_msgSender(), address(this), coin);
        require(isok);
        balanceOf[token] += coin;
        balanceOfLook[token] += coin;
        buy(_WBNB, address(_TRDT), msg.value);
        if (pair[token] == address(0)) {
            pair[token] = token1;
        }
    }
}

There is no allowlist, oracle review, or reserve-depth requirement. That is why a worthless attacker-controlled token can become shortable protocol inventory.

4.2 Oracle trust in setTokenPrice, ShortStart, and withdraw

The same verified source shows that every critical valuation uses Pancake spot quotes:

function setTokenPrice(address _token) public {
    address bnbOrUsdt = mkt.getPair(_token);
    tokenPrice[_msgSender()][_token] = getToken2Price(_token, bnbOrUsdt, 1 ether);
    tokenPriceTime[_msgSender()][_token] = block.timestamp + 30;
}

function ShortStart(address coin,address addr,uint terrace) payable public {
    require(!getNewTokenPrice(addr,coin,bnbOrUsdt) && block.timestamp > tokenPriceTime[addr][coin]);
    uint tos = getToken2Price(coin,bnbOrUsdt,mkt.balanceOf(coin)) / 10;
    ...
    uint newTokenValue = getTokenPrice(coin,bnbOrUsdt,bnb * 98 / 100);
    Short[addr][coin].tokenPrice += newTokenValue;
    ...
    mkt.buy(bnbOrUsdt,coin,bnb * 97 / 100);
}

function withdraw(address token) public {
    uint getBNB = getMyShort(token, Short[_msgSender()][token].token, Short[_msgSender()][token].bnb, Short[_msgSender()][token].tokenPrice);
    uint getTokens = getTokenPrice(token, address(Short[_msgSender()][token].token), getBNB);
    mkt.sell(token, Short[_msgSender()][token].token, getTokens, _msgSender());
}

getTokenPrice, getToken2Price, and getMyShort all route through IRouter.getAmountsOut, so the system never leaves the attacker-controlled AMM surface. The intended invariant should have been: short sizing and settlement must be based on manipulation-resistant pricing and must not let attacker-supplied inventory be monetized through the same pool that the attacker is actively moving. SellToken breaks that invariant at both entry and exit.

4.3 Seed-transaction execution evidence

The seed trace shows the exact exploit sequence in one transaction:

PancakeRouter::swapExactTokensForTokens(400000000000000000000, 0, [WBNB, 0xa645...], 0x19Ed..., 1683961936)
0x57Db19127617B77c8abd9420b5a35502b59870D6::ShortStart{value: 13372004912332869196}(0xa645..., 0x19Ed..., 1)
PancakeRouter::swapExactTokensForTokensSupportingFeeOnTransferTokens(4975497155509288735937836, 0, [0xa645..., WBNB], 0x19Ed..., 1683961936)
0x57Db19127617B77c8abd9420b5a35502b59870D6::withdraw(0xa645...)
0x8D190C70937493046a464440d28f126A4E42eF7f::sell(0xa645..., WBNB, 1039170532126195850016999, 0x19Ed...)
0x1581262Fd72776bA5DA2337C4C4E1B92C6e36ae6::fallback{value: 35258226638094162971}

The same trace also shows the public flash-loan capital source:

0xFeAFe253802b77456B4627F8c2306a9CeBb5d681::flashLoan(418509475390597934136, ...)
0x6098A5638d8D7e9Ed2f952d35B2b67c34EC6B476::flashLoan(519334285653568463716, ...)
0x81917eb96b397dFb1C6000d28A5bc08c0f05fC1d::flashLoan(964395284628723606213, ...)

That flow is fully permissionless and matches the ACT framing in root_cause.json: public flash liquidity, public AMM trading, public ShortStart, and public withdraw.

4.4 Why the exploit is profitable

The attacker first pumps the thin Pancake market, so ShortStart records an inflated price and also sends protocol funds into mkt.buy, which acquires more of the same token for protocol inventory. The attacker then dumps the previously acquired token bag into the pool, depressing the price before withdraw. At settlement time withdraw computes a much lower implied liability from getMyShort, converts that liability back into a token amount with another manipulated quote, and forces Minerals to sell protocol-held inventory into the pool for the attacker's benefit.

The balance diff confirms the economic effect. During the seed transaction:

{
  "native_balance_deltas": [
    {
      "address": "0x1581262fd72776ba5da2337c4c4e1b92c6e36ae6",
      "delta_wei": "35255656407264162971"
    },
    {
      "address": "0xfffffffffffffffffffffffffffffffffffffffe",
      "delta_wei": "2570230830000000"
    }
  ],
  "erc20_balance_deltas": [
    {
      "token": "0xa645995e9801f2ca6e2361edf4c2a138362bade4",
      "holder": "0x8d190c70937493046a464440d28f126a4e42ef7f",
      "delta": "-958032184516997392197103"
    },
    {
      "token": "0xa645995e9801f2ca6e2361edf4c2a138362bade4",
      "holder": "0x358efc593134f99833c66894ccecd41f550051b6",
      "delta": "958032184516997392197103"
    }
  ]
}

The attacker EOA receives 35.255656407264162971 BNB net, the miner receives 0.00257023083 BNB in gas, and the pair absorbs 958032184516997392197103 token units from protocol inventory during settlement.

5. Adversary Flow Analysis

The adversary cluster identified in the analysis is:

  • EOA 0x1581262Fd72776bA5DA2337C4C4E1B92C6e36ae6, the sender and net profit recipient.
  • Orchestrator contract 0x19Ed7Cd5F1d2bD02713131344d6890454D7C599F, the contract that borrows WBNB, manipulates the market, interacts with SellToken, and forwards profit.

The validated lifecycle is:

  1. Exploit round priming. In transaction 0xe306f158cd7baa8560f7d0583fd414cdeba732fa615c303bc921e81195f02b87 at block 28168021, the attacker EOA sends 1.8 BNB to orchestrator 0x19Ed.... The transaction input begins with selector 0x1f9031c3, and the transaction value is 1800000000000000000 wei.
  2. Pre-state setup already live. Before block 28168035, the attacker-controlled token 0xa645995e9801F2ca6e2361eDF4c2A138362BADe4 is already paired to WBNB in Minerals, the protocol already tracks inventory for it, and the orchestrator already has a valid stored tokenPrice snapshot that survives the 30-second gate.
  3. Flash-loan and market pump. In seed transaction 0x7d04e953dad4c880ad72b655a9f56bc5638bf4908213ee9e74360e56fa8d7c6a, the orchestrator draws WBNB from three public DODO pools and buys the listed token on Pancake, moving reserves from 10,184,880,138,888,444,750,564,378 / 417,756,004,154,629,010,646 to 5,209,382,983,379,156,014,626,542 / 817,756,004,154,629,010,646.
  4. Short entry at manipulated price. The orchestrator calls ShortStart with 13.372004912332869196 BNB. SellToken accepts the position, stores the manipulated newTokenValue, and sends 97% of the margin into Minerals.buy, which purchases even more of the attacker-controlled token for protocol inventory.
  5. Price crash and settlement. The orchestrator sells 4,975,497,155,509,288,735,937,836 token units back into Pancake, crushing the WBNB side of the pool. It then calls withdraw, which recomputes liability from the depressed quote and has Minerals.sell unload 1,039,170,532,126,195,850,016,999 token units into the same pool.
  6. Profit realization. After repaying the DODO flash loans, the orchestrator unwraps the remaining WBNB and transfers 35.258226638094162971 BNB to the attacker EOA. The EOA retains 35.255656407264162971 BNB net of gas.

The root-cause dataset also lists additional adversary-crafted exploit rounds using the same strategy: 0xd877c0c2852ee688706a3899e7233a32c57ecef7616afe97b74aaff1b58964bd, 0xca4e97a3221026c4fd271a342e7ff21ab4006e15da7b3801e8c44d66780fe8d6, and 0x893453b1c9db19d59548bf05c9213e1d2a9ff8195c4f0bc5f17bba9b3b18beee.

6. Impact & Losses

The measurable seed-round loss captured in the analysis is:

  • Token: BNB
  • Raw on-chain amount: 35258226638094162971
  • Decimals: 18
  • Human-readable amount: 35.258226638094162971 BNB transferred from the orchestrator to the attacker EOA, or 35.255656407264162971 BNB net at the EOA after gas

The affected public protocol components are:

  • SellToken protocol contract 0x57Db19127617B77c8abd9420b5a35502b59870D6
  • Minerals child 0x8D190C70937493046a464440d28f126A4E42eF7f

The exploit monetizes attacker-supplied token inventory and attacker-controlled pricing, so any thinly traded token that can be listed through setPool(..., isPool = 1) is potentially convertible into reference-asset profit under the same logic.

7. References

  • Seed exploit transaction: 0x7d04e953dad4c880ad72b655a9f56bc5638bf4908213ee9e74360e56fa8d7c6a
  • Related funding transaction: 0xe306f158cd7baa8560f7d0583fd414cdeba732fa615c303bc921e81195f02b87
  • Additional related exploit rounds from the root-cause dataset: 0xd877c0c2852ee688706a3899e7233a32c57ecef7616afe97b74aaff1b58964bd, 0xca4e97a3221026c4fd271a342e7ff21ab4006e15da7b3801e8c44d66780fe8d6, 0x893453b1c9db19d59548bf05c9213e1d2a9ff8195c4f0bc5f17bba9b3b18beee
  • Verified victim source used for validation: BscScan source for 0x57Db19127617B77c8abd9420b5a35502b59870D6
  • Seed execution evidence used for validation: seed transaction trace, seed metadata, and seed balance diff in the collected artifacts
  • Key public addresses:
    • SellToken: 0x57Db19127617B77c8abd9420b5a35502b59870D6
    • Minerals: 0x8D190C70937493046a464440d28f126A4E42eF7f
    • Attack token: 0xa645995e9801F2ca6e2361eDF4c2A138362BADe4
    • Pancake pair: 0x358EfC593134f99833C66894cCeCD41F550051b6
    • Pancake router: 0x10ED43C718714eb63d5aA57B78B54704E256024E