This is a lower bound: only assets with reliable historical USD prices are counted, so the actual loss may be higher.
0x660837a1640dd9cc0561ab7ff6c85325edebfa17d8b11a3bb94457ba6dcae18c0xe9d1d2a27458378dd6c6f0b2c390807aed2217caBSCOn BNB Chain block 39107494, an unprivileged adversary cluster exploited Orion Exchange's BNB Chain deployment (AdminUpgradeabilityProxy at 0xe9d1d2a27458378dd6c6f0b2c390807aed2217ca) to drain BNB and multiple ERC20 tokens in a single transaction 0x660837a1640dd9cc0561ab7ff6c85325edebfa17d8b11a3bb94457ba6dcae18c. The attacker used an external orchestrator contract to obtain a USDT flash swap, deposit into Orion, fabricate large internal balances via redeemAtomic (delegating to LibAtomic.doRedeemAtomic), and then withdraw real assets via withdrawTo while remaining superficially margin-positive.
The root cause is that Orion's redeemAtomic flow allows signed RedeemOrders to credit internal balances without binding them to concrete atomicSwaps lock entries or real reserve-backed positions. As long as MarginalFunctionality.calcPosition reports sufficient margin, Exchange.withdrawTo will release proxy-held tokens against these synthetic balances. This breaks the intended invariant that withdrawals must be limited by actual collateral and locked obligations, enabling an anyone-can-take (ACT) opportunity.
Orion Exchange on BNB Chain is deployed behind an AdminUpgradeabilityProxy at 0xe9d1d2a27458378dd6c6f0b2c390807aed2217ca. At block 39107494, its implementation is an Orion exchange contract that maintains a per-user internal ledger and supports:
assetBalances[user][asset]depositAssetTo: credit internal balances when users or contracts deposit BNB/ERC20 tokens.withdrawTo: debit internal balances (subject to margin checks) and transfer actual tokens from the proxy.redeemAtomic: implement atomic redeem flows using LibAtomic and atomicSwaps storage.The relevant implementation code is drawn from the verified Orion project cloned during data collection (e.g., Exchange.sol, ExchangeWithAtomic.sol, LibAtomic.sol). In that project, ExchangeWithAtomic.redeemAtomic delegates to LibAtomic.doRedeemAtomic to adjust internal balances based on a signed RedeemOrder:
// ExchangeWithAtomic.sol (simplified call site)
function redeemAtomic(LibAtomic.RedeemOrder calldata order, bytes calldata secret) external {
LibAtomic.doRedeemAtomic(order, secret, secrets, assetBalances, liabilities);
}
The core logic of LibAtomic.doRedeemAtomic is:
// LibAtomic.doRedeemAtomic (key lines)
function doRedeemAtomic(
LibAtomic.RedeemOrder calldata order,
bytes calldata secret,
mapping(bytes32 => bool) storage secrets,
mapping(address => mapping(address => int192)) storage assetBalances,
mapping(address => MarginalFunctionality.Liability[]) storage liabilities
) public {
require(!secrets[order.secretHash], "E17R");
require(getEthSignedAtomicOrderHash(order).recover(order.signature) == order.sender, "E2");
require(order.expiration / 1000 >= block.timestamp, "E4A");
require(order.secretHash == keccak256(secret), "E17");
secrets[order.secretHash] = true;
LibExchange._updateBalance(order.sender, order.asset, -1 * int(uint(order.amount)), assetBalances, liabilities);
LibExchange._updateBalance(order.receiver, order.asset, int(uint(order.amount)), assetBalances, liabilities);
}
Critically, this function only verifies the redeem order signature, expiration, and matching secret hash. It does not reference any atomicSwaps lock entry or ensure that order.amount is backed by an on-chain lock or by existing reserves; it simply debits and credits internal balances for order.sender and order.receiver.
Withdrawals are handled in Exchange.withdrawTo:
// Exchange.withdrawTo (simplified)
function withdrawTo(address assetAddress, uint112 amount, address to) public nonReentrant {
uint112 safeAmountDecimal = LibUnitConverter.baseUnitToDecimal(assetAddress, amount);
assetBalances[msg.sender][assetAddress] -= int192(uint192(safeAmountDecimal));
emit LibExchange.BalanceChange(msg.sender, assetAddress, -int192(uint192(safeAmountDecimal)));
if (assetBalances[msg.sender][assetAddress] < 0) revert NotEnoughBalance();
if (!checkPosition(msg.sender)) revert IncorrectPosition();
if (assetAddress == address(0)) {
(bool success, ) = to.call{value: amount}("");
if (!success) revert NotEnoughBalance();
} else {
IERC20(assetAddress).safeTransfer(to, amount);
}
emit NewAssetTransaction(msg.sender, to, assetAddress, false, safeAmountDecimal, uint64(block.timestamp));
}
As long as assetBalances[msg.sender][assetAddress] remains non-negative and checkPosition(msg.sender) returns true, the contract will transfer real BNB or tokens held by the proxy to the to address.
The adversary model is a standard, unprivileged on-chain attacker that controls multiple EOAs and contracts, can obtain flash liquidity (e.g., via a USDT flash swap), and interacts only with publicly available Orion functions and on-chain information.
The vulnerability is an internal accounting flaw in Orion's atomic redeem / withdrawal pipeline. LibAtomic.doRedeemAtomic creates internal balances for a receiver purely from a signed RedeemOrder and secret, without tying that balance creation to any actual atomicSwaps lock or reserve-backed obligation. The subsequent withdrawTo function trusts these internal balances (subject only to a portfolio-wide margin check) and releases real assets from the proxy.
The intended invariant is that, for every account, the combination of deposits, locks, and liabilities must ensure that withdrawals cannot exceed the assets genuinely backing that account's position. In the exploited implementation, the invariant is broken because a crafted sequence of RedeemOrders can move large notional amounts into attacker-controlled internal balances without consuming or checking corresponding locks.
An unprivileged adversary can therefore:
redeemAtomic repeatedly to fabricate internal balances across multiple assets while preserving margin.withdrawTo to drain BNB and ERC20 tokens from the proxy to an adversary EOA.This behavior is an ACT opportunity because any unprivileged address or cluster can reproduce the same strategy using public code, traces, and price data under the same pre-state.
The safety invariant can be expressed as:
For every user
uand asseta, the exchange should ensure that withdrawals and internal balance credits are bounded by the combination of deposits and lock-backed obligations, such that the total claim on reserves does not exceed actual reserves.
Formally, for each user u and asset a over time:
internal_balance[u,a] <= deposits[u,a] + lock_claims[u,a] - liabilities[u,a]
and the sum over all users should not exceed the proxy's external holdings for a.
The code-level breakpoint is in LibAtomic.doRedeemAtomic and ExchangeWithAtomic.redeemAtomic:
redeemAtomic invokes LibAtomic.doRedeemAtomic(order, secret, secrets, assetBalances, liabilities).doRedeemAtomic verifies only the signature and secret, then calls _updateBalance to debit order.sender and credit order.receiver for order.asset and order.amount.order and any atomicSwaps[secretHash] entry; no check that a corresponding lock exists, is unused, or matches order.amount and order.asset.As a result, the attacker can construct RedeemOrders where order.sender is an internal/broker account and order.receiver is an attacker-controlled account, with large order.amount values, and apply them repeatedly as long as the global margin check remains satisfied.
On the withdrawal side, Exchange.withdrawTo relies on assetBalances[msg.sender][assetAddress] and checkPosition(msg.sender) to prevent over-withdrawal. However, because the internal balances were already inflated by doRedeemAtomic without consuming real locks, these checks operate on corrupted accounting state and permit withdrawals backed only by fabricated balances.
The adversary controls:
0x51177db1ff3b450007958447946a2eee388288d2 (seed tx sender and gas payer).0xf8bfac82bdd7ac82d3aeec98b9e1e73579509db6 that encodes the attack sequence.0x805d8c96d09f6500cdad501d5c754d2fbf186d7a used within Orion accounting.0xf7a8c237ac04c1c3361851ea78e8f50b04c76152 (receives drained assets).The orchestrator is an unprivileged contract; any EOA could deploy an equivalent contract and call the same public functions on the Orion proxy. All exploited functions are publicly callable:
depositAssetTo on the proxy for depositing USDT.redeemAtomic on the proxy, delegating to LibAtomic.doRedeemAtomic in the implementation.withdrawTo on the proxy for withdrawing BNB and tokens.The seed transaction 0x660837a1640dd9cc0561ab7ff6c85325edebfa17d8b11a3bb94457ba6dcae18c is a standard BNB Chain transaction from an unprivileged EOA to the orchestrator with sufficient gas, demonstrating reachability under the ACT model.
The trace for 0x6608...e18c (captured in trace.cast.log) shows the following high-level sequence:
0x5117...d2 calls orchestrator 0xf8bfac82... with zero BNB value.0x16b9a82891338f9ba80e2d6970fdda79d1eb0dae.depositAssetTo on proxy 0xe9d1d2a2... to credit internal USDT balances for attacker-controlled accounts.redeemAtomic multiple times on the proxy, which delegatecalls into LibAtomic.doRedeemAtomic on implementation 0xc662cea3..., creating large internal balances in BNB and multiple ERC20 tokens for attacker-linked accounts while maintaining a margin-positive portfolio according to calcPosition.withdrawTo repeatedly, causing the proxy to transfer real BNB and ERC20 tokens to profit EOA 0xf7a8... and to the flash swap provider, then repays the USDT flash swap and leaves the net profit with the adversary.The balance_diff.json for the seed transaction confirms the invariant violation at the level of the proxy's reserves. For the proxy 0xe9d1d2a2..., native and ERC20 balance deltas include (negative amounts reflect losses by the proxy):
{
"native_balance_deltas": [
{
"address": "0xe9d1d2a27458378dd6c6f0b2c390807aed2217ca",
"delta_wei": "-79896159740000000000"
}
],
"erc20_balance_deltas": [
{"token": "0x55d398326f99059ff775485246999027b3197955", "delta": "-19844686077960000000000"},
{"token": "0x1d2f0da169ceb9fc7b3144628db156f3f6c60dbe", "delta": "-62444730331000000000000"},
{"token": "0xf8a0bf9cf54bb92f17374d9e9a321e6a111a51bd", "delta": "-1741195444560000000000"},
{"token": "0x7130d2a12b9bcbfae4f2634d864a1ee1ce3ead9c", "delta": "-303464280000000000"},
{"token": "0x2170ed0880ac9a755fd29b2688956bd959f933f8", "delta": "-4808971490000000000"},
{"token": "0x7083609fce4d1d8dc0c979aab8c869ea2c873402", "delta": "-785588869650000000000"},
{"token": "0xe4ca1f75eca6214393fce1c1b316c237664eaa8e", "delta": "-49892192920826"},
{"token": "0xba2ae424d960c26247dd6c32edc70b295c744c43", "delta": "-11261078858282"},
{"token": "0xadbaf88b39d37dc68775ed1541f1bf83a5a45feb", "delta": "-86361313198550000000000"},
{"token": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", "delta": "-11001109311140000000000"},
{"token": "0xbf7c81fff98bbe61b40ed186e4afd6ddd01337fe", "delta": "-237884641240000000000"},
{"token": "0x1ce0c2827e2ef14d5c4f29a091d735a204794041", "delta": "-198361026380000000000"}
]
}
These losses at the proxy are mirrored by gains at the profit EOA 0xf7a8..., confirming that the internal accounting allowed the attacker to withdraw more than any legitimate deposits or locks should have permitted.
The adversary-related cluster of accounts used in this exploit is:
0x51177db1ff3b450007958447946a2eee388288d2 — EOA that submits the seed transaction and pays gas.0xf8bfac82bdd7ac82d3aeec98b9e1e73579509db6 — attacker-controlled orchestrator contract implementing the flash swap, Orion interaction, and withdrawals.0x805d8c96d09f6500cdad501d5c754d2fbf186d7a — broker/internal account within Orion used to route balances.0xf7a8c237ac04c1c3361851ea78e8f50b04c76152 — profit EOA receiving the drained BNB and ERC20 tokens.All of these are unprivileged accounts under the ACT adversary model. The orchestrator is not access-controlled and could be called by any EOA with sufficient gas.
Flash swap and funding
0xf8bfac82... initiates a USDT flash swap from 0x16b9a82891338f9ba80e2d6970fdda79d1eb0dae.balance_diff.json shows 0x16b9... temporarily loses 10,400 USDT (delta -10400000000000000000000 at the lender) and later regains it when the flash swap is repaid.Deposit into Orion
depositAssetTo on proxy 0xe9d1d2a2..., crediting internal USDT balances for attacker-controlled accounts.calcPosition non-negative during the exploit.RedeemAtomic internal balance fabrication
redeemAtomic multiple times with carefully crafted RedeemOrders.LibAtomic.doRedeemAtomic, which:
order.sender and credits order.receiver in assetBalances for various assets (BNB proxy representation and multiple ERC20s).doRedeemAtomic does not consume or verify locks in atomicSwaps, the attacker can move arbitrarily large nominal amounts into their internal balances, as long as the overall margin check remains satisfied.Withdrawals and profit realization
withdrawTo on the proxy for multiple assets, with msg.sender set to an attacker-controlled internal account and to set to the profit EOA 0xf7a8... (or the flash swap lender for the repayment).withdrawTo subtracts safeAmountDecimal from the internal balance, checks NotEnoughBalance and IncorrectPosition, and then transfers tokens from the proxy to the target on success.balance_diff.json show:
0xe9d1d2a2... loses 79.89615974 BNB (delta -79896159740000000000 wei) and large ERC20 balances across USDT, XRP, LINK, BTCB, ETH, DOT, ORN, DOGE, COTI, USDC, EGLD, and AVAX.0xf7a8... gains the corresponding positive deltas for those tokens.0x16b9... receives back 10,400 USDT.0x5117... pays approximately 0.026438544 BNB in gas (native balance delta -26438544000000000 wei).Post-tx state
This flow is reproducible by any unprivileged adversary with knowledge of the vulnerability and access to similar flash liquidity, satisfying the ACT criteria.
From balance_diff.json, the direct on-chain losses from the Orion proxy 0xe9d1d2a2... in transaction 0x6608...e18c are:
79.89615974 BNB (delta -79896159740000000000 wei).0x55d398326f99059ff775485246999027b3197955): 19,844,686.07796 USDT-equivalent units (delta -19844686077960000000000 with 18 decimals).0x1d2f0da169ceb9fc7b3144628db156f3f6c60dbe).0xf8a0bf9cf54bb92f17374d9e9a321e6a111a51bd).0x7130d2a12b9bcbfae4f2634d864a1ee1ce3ead9c).0x2170ed0880ac9a755fd29b2688956bd959f933f8).0x7083609fce4d1d8dc0c979aab8c869ea2c873402).0xe4ca1f75eca6214393fce1c1b316c237664eaa8e).0xba2ae424d960c26247dd6c32edc70b295c744c43).0xadbaf88b39d37dc68775ed1541f1bf83a5a45feb).0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d).0xbf7c81fff98bbe61b40ed186e4afd6ddd01337fe).0x1ce0c2827e2ef14d5c4f29a091d735a204794041).For these ERC20 tokens, the exact integer deltas (in base units) are recorded in balance_diff.json and align with the corresponding positive deltas at the profit EOA 0xf7a8....
Using the historical USD prices in artifacts/root_cause/data_collector/iter_8/prices/historical_prices_block_39107494_binance.json, which provide Binance spot 1m close prices (or 1.0 USD pegs for USDT/USDC) at or immediately preceding the block timestamp, the adversary-cluster portfolio change can be valued in USD.
The portfolio is restricted to assets with non-zero deltas in this transaction (BNB, USDT, XRP, LINK, BTCB, ETH, DOT, ORN, DOGE, COTI, USDC, EGLD, AVAX) and to addresses in the adversary cluster {0x5117..., 0xf8bfac82..., 0x805d8c96..., 0xf7a8...}. Token-unit deltas come from balance_diff.json and decimals from verified sources or explorer metadata (BNB and main BEP20s: 18 decimals; ORN and DOGE: 8 decimals; other BEP20TokenImplementation assets treated as 18 decimals).
Under these assumptions:
Because BNB gas spending is already included in the cluster's native balance deltas, the reported value_delta_in_reference_asset in the analysis is net of fees; fees are reported separately for clarity.
Given the above, this incident clearly satisfies the ACT profit predicate:
Therefore, the root cause is an ACT-style attack: an internal accounting vulnerability in Orion's redeem/withdraw pipeline that any unprivileged adversary can exploit to realize a large, quantified profit.
0x660837a1640dd9cc0561ab7ff6c85325edebfa17d8b11a3bb94457ba6dcae18c (block 39107494), with metadata, trace, and balance diffs in artifacts/root_cause/seed/56/0x6608...e18c/.AdminUpgradeabilityProxy at 0xe9d1d2a27458378dd6c6f0b2c390807aed2217ca on BNB Chain.artifacts/root_cause/data_collector/iter_4/contract/56/0xe2c7ce00e0bbbcd15c691dfcca7434f3b95f0750/source/ (including Exchange.sol, ExchangeWithAtomic.sol, LibAtomic.sol).0xc662cea3d8d6660ca97fb9ff98122da69a199cd8 under artifacts/root_cause/data_collector/iter_5/contract/56/0xc662...cd8/source/.0xf8bfac82bdd7ac82d3aeec98b9e1e73579509db6, profit EOA 0xf7a8c237ac04c1c3361851ea78e8f50b04c76152, broker/internal 0x805d8c96d09f6500cdad501d5c754d2fbf186d7a, flash swap lender 0x16b9a82891338f9ba80e2d6970fdda79d1eb0dae, with txlists in artifacts/root_cause/data_collector/iter_3/address/56/*/txlist.normal.json.artifacts/root_cause/data_collector/iter_8/prices/historical_prices_block_39107494_binance.json.These references are sufficient for an external reader to independently reconstruct the exploit, verify the invariant violation, and re-compute the adversary's profit in USD.