THORChain ETH Router msg.value Spoof / Fake-Deposit Swap+Refund Drain (2021-07-15)
- Name: THORChain ETH Router
msg.valuespoof / fake-deposit swap+refund drain - Date: 2021-07-15
- Protocol: THORChain / Bifrost / ETH Router
- Chain: Ethereum + THORChain
- Category: bridge deposit-signal mismatch / wrapper-contract call-context confusion / fake inbound accounting / refund-assisted drain
- Loss: about
$5Mdirectly from the first exploit loop, with public writeups listing roughly2,500 ETH,57,975.33 SUSHI,8.7365 YFI,171,912.96 DODO,514.519 ALCX,1,167,216.739 KYL, and13.30 AAVEdrained from the system; later insolvency was worsened further by the separate premature-return-to-trading incident - Summary: THORChain’s first July 2021 ETH-router exploit worked because Bifrost’s Ethereum scanner accepted the outer transaction’s
msg.valueas deposit truth when a smart contract sat in front of the router. The attacker wrapped THORChain’s router with contract0x4a33862042d004d3fc45e284e1aafa05b48e3c9c, sent large outer-call ETH values into that wrapper, then had the wrapper callRouter.deposit()with0ETH and attacker-chosen swap memos. Bifrost interpreted the transaction as if THORChain had received the outer ETH amount even though the router-side deposit path had not actually taken custody of matching ETH. THORChain’s own postmortem later said the relevantmsg.valueoverride existed to support router-upgrade / vault-transfer handling, but should never have remained authoritative for ordinary deposit actions once wrappers were allowed in front of the router. That let the attacker manufacture fake ETH inbounds, route them through THORChain’s normal swap flow into real ERC-20 inventory, and finally force a refund leg to pull out ETH as well. - Trigger / entry point:
- THORChain’s Ethereum-side Router had recently been made wrap-friendly so aggregator or ecosystem contracts could sit in front of it.
- Bifrost’s ETH scanner still had legacy override logic meant for special vault-transfer handling.
- That scanner logic read the outer transaction’s
msg.valuerather than strictly trusting the router-emittedDepositevent for deposit actions. - The attacker used a front contract to separate
top-level ETH sent into the wrapperfromETH actually forwarded into Router.deposit().
- Root cause:
- The bridge-authenticity failure was semantic, not cryptographic: Bifrost treated
outer transaction carried ETHas if it meantrouter deposit locked ETH for THORChain. - That shortcut only held when users called the router directly. Once wrapper contracts were allowed, the outer transaction value and the router’s actual received value were no longer guaranteed to match.
- THORChain’s own postmortem says the vulnerable override path was necessary to support router-upgrade / vault-transfer-style handling, but should not have been used for deposit events at all. The healthy boundary for deposits was
read only the router's own Deposit event after the router actually received custody, notinfer deposit amount from the top-level transaction shell. - In effect, THORChain’s source-side proof of inbound ETH drifted from
what the router deposit event and custody change provedtowhat the surrounding transaction looked like from outside.
- The bridge-authenticity failure was semantic, not cryptographic: Bifrost treated
- Exploit path:
- The attacker funded EOA
0x3a196410a0f5facd08fd7880a4b8551cd085c031from Tornado Cash address0x4b713980d60b4994e0aa298a66805ec0d35ebc5aand used it to drive attack contract0x4a33862042d004d3fc45e284e1aafa05b48e3c9c. - The attack contract sat in front of THORChain’s Ethereum Router
0xc145990e84155416144c532e31f89b840ca8c2ceand targeted Asgard vault0xf56cba49337a624e94042e325ad6bc864436e370. - The attacker sent large top-level ETH values like
100,200,400,500, and2,000 ETHinto the wrapper contract in repeated transactions. - The wrapper contract then called the router’s deposit surface with
0ETH while supplying attacker-chosen THORChain swap memos such asSWAP:ETH.DODO-...,SWAP:ETH.YFI-...,SWAP:ETH.SUSHI-..., andSWAP:ETH.ALCX-.... - Bifrost interpreted those transactions using the outer
msg.value/ transaction value rather than the router’s real deposit event semantics, so it reported fake ETH inbounds to THORChain. - THORChain’s normal swap machinery then honored those fabricated ETH deposits and released real ERC-20 inventory from the protocol side.
- After looping fake inbounds through multiple assets and pushing pool prices around, the attacker also used a deliberately bad-memo refund path to siphon ETH back out of the system.
- The exploit therefore monetized the same source-side lie twice: first as swap power against ERC-20 pools, then as refund authority for ETH.
- The attacker funded EOA
- What the attacker needed to be true:
- A contract had to be allowed to wrap the router rather than forcing direct user calls.
- Bifrost had to keep reading top-level transaction value for deposit interpretation instead of pinning deposit truth to the router’s own emitted event and actual custody change.
- THORChain had to accept Bifrost’s reported ETH inbound as normal swap input.
- ETH-side vaults and pools had to still hold economically significant ERC-20 and ETH inventory worth stealing.
- Refund logic had to spend real assets once the fake inbound had crossed the authenticity boundary.
- Technical anatomy worth remembering:
- This is a strong
deposit signal ≠ locked valuebridge case even though THORChain is often discussed as a cross-chain DEX rather than a bridge. Bifrost still had to decide whether Ethereum-side value had really arrived before THORChain could safely act on it. - The exploit was not
router accepted a bad memoin isolation. The crucial earlier failure was that the fake ETH inbound was admitted as authentic deposit truth at all. - The wrapper contract mattered because it broke an implicit assumption in the scanner:
tx.value on the outer call equals ETH actually delivered into the router deposit path. - The sharper healthy-side lesson is not merely
don't trust wrappers; it isdeposit truth must come from the router's own custody-proving artifact, not from an ambient transaction field that only coincides with custody under direct-call assumptions. - This incident pairs cleanly with THORChain’s second ETH-router exploit one week later. The first exploit sat in front of the router and abused transaction-value interpretation; the second sat behind the router and abused deposit-looking event interpretation.
- This is a strong
Three deposit-truth promotions this incident collapsed
-
Some ETH-looking transaction shell existed
- A top-level Ethereum transaction really did carry ETH into the attack wrapper sitting in front of THORChain’s router.
- That only proved
ETH moved somewhere in the outer call graph, not that the canonical router deposit lane had received redeemable ETH for THORChain.
-
That shell still counted as canonical router-side deposit truth
- Bifrost kept promoting the outer transaction’s
msg.valueinto inbound ETH truth even after wrapper contracts were allowed to separatetop-level ETH attached to the wrapper callfromETH actually forwarded into Router.deposit(). - The dangerous jump was not merely that a wrapper existed; it was that ambient call-shell context still outranked the router’s own custody-authored
Depositsemantics.
- Bifrost kept promoting the outer transaction’s
-
That accepted deposit truth still deserved immediate swap and refund authority
- Once the fake inbound crossed Bifrost’s authenticity boundary, THORChain let it spend against real ERC-20 pool inventory and later against ETH-side refund inventory too.
- The same source-side lie therefore inherited both cross-asset swap power and refund-release authority before any route-local brake narrowed loss.
- Representative transactions and contracts:
- Attacker EOA:
0x3a196410a0f5facd08fd7880a4b8551cd085c031 - Attack contract:
0x4a33862042d004d3fc45e284e1aafa05b48e3c9c - Tornado Cash funding address cited in public writeups:
0x4b713980d60b4994e0aa298a66805ec0d35ebc5a - THORChain Ethereum Router:
0xc145990e84155416144c532e31f89b840ca8c2ce - Asgard vault targeted in public writeups:
0xf56cba49337a624e94042e325ad6bc864436e370 - Representative exploit transactions into the attack wrapper contract:
0xe71529643934723a0da66825e51d3c0fcacda791be724f633ad7987ab58a5c13— outer call value100 ETH, memo payload ending inSWAP:ETH.DODO-...0x3c0377b41d3b0d709cf5fe15767422b91faab724a12816c4650833e551406e36— outer call value200 ETH, memo payload ending inSWAP:ETH.DODO-...0x03c90ba802f5cc14fb21912e8ecd3501c09d60f7335293a1d4b2b9f4500d29c5— outer call value2,000 ETH, memo payload ending inSWAP:ETH.YFI-...0x91395c49f8100ba856e1d630b9aba5e42a150049ecfd3e63cf3ea94b29694e12— outer call value2,000 ETH, memo payload ending inSWAP:ETH.SUSHI-...0xb8eea9e6ed2bd81f758b59746435e4e331675d8595a1327c843d94774d0b25c3— outer call value2,000 ETH, memo payload ending inSWAP:ETH.ALCX-...
- Attacker EOA:
- Response / mitigation:
- THORChain halted Ethereum operations and later published that the fix was to make the override apply only to vault-transfer handling instead of ordinary deposits.
- The postmortem says deposit actions were changed so Bifrost would read only the router’s
Depositevent, not the outer transaction value — effectively re-pinning inbound ETH truth to the router-authored custody artifact instead of the wrapper-authored transaction shell. - The wider recovery plan also added audits, a formal bug bounty, red-team work, solvency checks, node-triggerable timeouts, and outbound throttling after the July incidents exposed how much damage fake inbounds could do before operators could react.
Pattern tags
- deposit-signal-locked-value-mismatch
- bridge-verification-failure
- Wrapper-context ambiguity / outer transaction shell treated as deposit truth instead of router-authored custody proof
- Counterfeit inbound admitted as immediate swap power plus refund-release authority
Why the exploit worked
- THORChain split ETH deposit semantics across two unequal objects but still let Bifrost collapse them into one meaning. The outer transaction really did carry ETH into the attacker’s wrapper, but the router-side deposit path did not receive matching ETH, and Bifrost still treated the outer shell as if it proved router custody.
- The vulnerable override was only safe for the narrower vault-transfer / router-upgrade handling it had been written for. Once wrapper contracts were allowed in front of ordinary deposits, that same override became a second speaker for deposit truth beside the router’s own
Depositevent. - The decisive failure was therefore not
smart contract wrapper badin the abstract, butBifrost trusted an ambient transaction field whose meaning depended on direct-call assumptions that wrappers had already broken. - THORChain also lacked a meaningful blast-radius break between
one fake ETH inbound acceptedandlive ERC-20 / ETH inventory released. The same counterfeit source-side fact immediately became swap authority across pool inventory and later refund authority against ETH-side vault balances. - The exploit monetized one authenticity failure twice. First, fake ETH bought real ERC-20s through normal swap flow; second, the same forged inbound premise was steered into the refund path to pull real ETH back out.
Detection and audit pivots
- Enumerate every field Bifrost, relayers, or bridge scanners treat as deposit truth for ETH-like assets. If any path still derives credited amount from outer
tx.value, wrapper context, or call-shell metadata instead of the router’s canonical custody artifact, treat it as a live bridge-authentication bug. - Review wrapper compatibility as a source-side authenticity problem, not just an integration feature. The key question is whether a wrapper can change the meaning of
ETH moved in this transactionwithout changing what the bridge listener thinks that fact proves. - Separate ordinary deposit semantics from exceptional router-upgrade, vault-return, or admin-transfer overrides. Any branch that needs looser
msg.valuehandling for migration or vault operations should be unable to author ordinary deposit truth on the same relay path. - Compare source-side inbound admission with downstream spend authority. If one accepted ETH inbound can immediately command cross-asset swaps and refund releases, ask what route-local cap, throttle, or quarantine would still narrow loss after one counterfeit deposit fact lands.
- Trace the exact deposit object across the whole path: outer transaction, wrapper contract, router call, router-emitted event, Bifrost parsing, THORChain inbound accounting, swap execution, and refund execution. The audit question is where the system first stopped requiring
router actually received spendable ETHand started acceptingtransaction looked like ETH moved nearbyinstead.
Failure-prevention checklist
- Make the canonical deposit lane exclusive for ordinary user inbounds. If router-upgrade, vault-transfer, or migration handling still needs a broader
msg.valueinterpretation, that logic must live on a path that cannot emit or relay the same deposit truth ordinary swaps and refunds consume. - Compute credited ETH from the router’s own custody change, not from outer transaction shape. Wrapper compatibility is safe only if Bifrost still anchors amount and provenance to what the router actually received under the canonical deposit path.
- Treat contracts in front of the router as part of the verifier boundary. If a wrapper can change
top-level tx carried ETHwithout changingrouter received ETH, relayers must treat the wrapper shell as unauthenticated context rather than as deposit evidence. - Keep exceptional
msg.valueoverrides path-local and non-transitive. A special case introduced for router upgrades or vault handling should never remain a second speaker for ordinary deposit truth after new composability is introduced. - Re-bind refunds to the original custody object.
Bad memo -> refundshould not spend real ETH or ERC-20 inventory unless the system can still point to the canonical inbound artifact that proved the router actually received redeemable value. - Add route-local loss limits beneath deposit truth: per-asset outbound throttles, bounded refund budgets, and fast chain halts should narrow loss even if one counterfeit ETH inbound is admitted.
- Reconcile accepted ETH inbounds against router-observed custody quickly enough to detect
Bifrost credited more ETH than the router receivedbefore fake deposits can be looped through multiple pools and refund rails.
Audit prompts
- Which exact Ethereum-side functions or Bifrost branches are still allowed to author THORChain inbound ETH truth, and do they all prove the same router-side custody change?
- At the moment Bifrost credits an ETH deposit, what measured post-condition proves the Router actually received that amount under the ordinary deposit lane rather than merely appearing in the outer call shell?
- Can a wrapper contract still create
tx.value > router-received ETHwhile leaving the relayer-visible deposit object unchanged? - Which legacy router-upgrade, vault-transfer, or migration exceptions still share parsing or value-derivation logic with ordinary deposits, and how would operators know that overlap still exists?
- If an inbound is later marked refundable because of a bad memo, what exact object proves the original deposit was real before any refund path spends live vault inventory?
- What route-local cap, throttle, pause, or quarantine limits damage if one counterfeit ETH inbound still lands and starts driving swaps or refunds before operators react?
- How quickly can THORChain prove that accepted Ethereum inbounds, router-side ETH custody, and outstanding refund / outbound liabilities still match after wrapper-heavy traffic?
Lessons that should already be standard
- Outer transaction value is not deposit truth.
ETH moved somewhere in the top-level callis a different security object fromthe canonical router deposit path actually received redeemable ETH. - Composability changes verifier boundaries. The moment wrappers were allowed in front of the router, any logic still reading ambient transaction shell data became bridge-authentication code.
- Exceptional migration or upgrade semantics must stay exceptional. A
msg.valueoverride that is safe for router-maintenance flows becomes counterfeit-deposit surface the moment ordinary user deposits can reach equivalent relay meaning. - Refund logic is part of bridge loss socialization. Once a fake inbound is admitted,
bad memo -> refundbecomes a second cash-out rail unless refunds re-bind to the original custody proof. - Honest relayers can still mint against fiction if the protocol feeds them an under-constrained source-side artifact. The bug here lived in what Bifrost was allowed to believe, not in downstream swap execution.
- Bounded release belongs under deposit truth, not just under signatures and proofs. One fake ETH inbound should never have had immediate line-of-sight to deep ERC-20 and ETH inventory.
Why this incident matters
- It preserves a very clean example of smart-contract composability expanding the bridge trust boundary. Allowing wrapper contracts in front of a deposit path is safe only if the observer keeps authenticating the real custody-changing event instead of the outer transaction shell.
- It shows that
transaction carried ETHis not the same security object asprotocol-owned router received spendable ETH. - It is also a useful sibling to the better-known
2021-07-22THORChain exploit: the two incidents failed at different joints, but both reduced bridge truth to a source-side signal that Bifrost trusted too loosely. - It also preserves the separate bounded-release lesson: one counterfeit inbound was still allowed to command immediate swap power against live ERC-20 pools and later refund authority against ETH-side vault inventory before operators could isolate the route. For that blast-radius question, pair this incident with bridge-bounded-release-design.
Internal linkages
- More specific recurring pattern: deposit-signal-locked-value-mismatch.
- Adjacent bridge-wide framing: bridge-verification-failure.
- Positive-design companion: bridge-deposit-truth-binding — the sharper framing for why wrapped or proxied router flows still needed one canonical router-authored custody object before Bifrost treated any ETH inbound as real.
- Positive-design companion: bridge-canonical-path-exclusivity-and-stale-authoring-surface-deauthorization — the sharper framing for why wrapper-friendly ETH router support should not have left the ambient
msg.valueoverride able to speak with authority equivalent to the canonical deposit lane. - Positive-design companion: bridge-bounded-release-design — the cleaner framing for why one fake inbound was still allowed to reach so much immediately spendable swap and refund authority.
- Closely related THORChain follow-on incident: thorchain-eth-router-fake-deposit-refund-drain-2021-07-22.
Control surface
-
The leverage sat in Bifrost’s Ethereum block-scanner logic, the distinction between outer transaction value and router-side custody, the wrapper contract allowed to sit in front of the router, and THORChain’s willingness to treat Bifrost’s reported inbound ETH as immediate swap or refund authority.
-
The clean design boundary THORChain later articulated is useful to preserve: deposit actions should have consumed only the router’s own
Depositevent semantics, while the broadermsg.valueoverride belonged only to the narrower vault-transfer / upgrade handling path that actually needed it. -
The loss size also depended on containment choices: once Bifrost accepted the counterfeit inbound, the same fake bridge fact could immediately reach economically meaningful pool inventory and refund-release paths before a narrower route-local brake or lower release budget could matter.
-
THORChain did not primarily fail because swaps or refunds are inherently unsafe. It failed because the system accepted a fake inbound premise and then let normal swap and refund machinery honor that fiction.
-
Sources:
- https://blog.thorchain.org/post-mortem-eth-router-exploits-1-2-and-premature-return-to-trading-incident/
- https://rekt.news/thorchain-rekt
- https://eth.blockscout.com/address/0x4a33862042d004d3fc45e284e1aafa05b48e3c9c/transactions
- https://eth.blockscout.com/api/v2/transactions/0xe71529643934723a0da66825e51d3c0fcacda791be724f633ad7987ab58a5c13
-
Last reviewed: 2026-08-02 UTC