THORChain ETH Router msg.value Spoof / Fake-Deposit Swap+Refund Drain (2021-07-15)

  • Name: THORChain ETH Router msg.value spoof / 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 $5M directly from the first exploit loop, with public writeups listing roughly 2,500 ETH, 57,975.33 SUSHI, 8.7365 YFI, 171,912.96 DODO, 514.519 ALCX, 1,167,216.739 KYL, and 13.30 AAVE drained 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.value as deposit truth when a smart contract sat in front of the router. The attacker wrapped THORChain’s router with contract 0x4a33862042d004d3fc45e284e1aafa05b48e3c9c, sent large outer-call ETH values into that wrapper, then had the wrapper call Router.deposit() with 0 ETH 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 relevant msg.value override 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.value rather than strictly trusting the router-emitted Deposit event for deposit actions.
    • The attacker used a front contract to separate top-level ETH sent into the wrapper from ETH actually forwarded into Router.deposit().
  • Root cause:
    • The bridge-authenticity failure was semantic, not cryptographic: Bifrost treated outer transaction carried ETH as if it meant router 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, not infer 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 proved to what the surrounding transaction looked like from outside.
  • Exploit path:
    1. The attacker funded EOA 0x3a196410a0f5facd08fd7880a4b8551cd085c031 from Tornado Cash address 0x4b713980d60b4994e0aa298a66805ec0d35ebc5a and used it to drive attack contract 0x4a33862042d004d3fc45e284e1aafa05b48e3c9c.
    2. The attack contract sat in front of THORChain’s Ethereum Router 0xc145990e84155416144c532e31f89b840ca8c2ce and targeted Asgard vault 0xf56cba49337a624e94042e325ad6bc864436e370.
    3. The attacker sent large top-level ETH values like 100, 200, 400, 500, and 2,000 ETH into the wrapper contract in repeated transactions.
    4. The wrapper contract then called the router’s deposit surface with 0 ETH while supplying attacker-chosen THORChain swap memos such as SWAP:ETH.DODO-..., SWAP:ETH.YFI-..., SWAP:ETH.SUSHI-..., and SWAP:ETH.ALCX-....
    5. 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.
    6. THORChain’s normal swap machinery then honored those fabricated ETH deposits and released real ERC-20 inventory from the protocol side.
    7. 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.
    8. The exploit therefore monetized the same source-side lie twice: first as swap power against ERC-20 pools, then as refund authority for ETH.
  • 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 value bridge 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 memo in 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 is deposit 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.

Three deposit-truth promotions this incident collapsed

  1. 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.
  2. That shell still counted as canonical router-side deposit truth

    • Bifrost kept promoting the outer transaction’s msg.value into inbound ETH truth even after wrapper contracts were allowed to separate top-level ETH attached to the wrapper call from ETH 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 Deposit semantics.
  3. 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 value 100 ETH, memo payload ending in SWAP:ETH.DODO-...
      • 0x3c0377b41d3b0d709cf5fe15767422b91faab724a12816c4650833e551406e36 — outer call value 200 ETH, memo payload ending in SWAP:ETH.DODO-...
      • 0x03c90ba802f5cc14fb21912e8ecd3501c09d60f7335293a1d4b2b9f4500d29c5 — outer call value 2,000 ETH, memo payload ending in SWAP:ETH.YFI-...
      • 0x91395c49f8100ba856e1d630b9aba5e42a150049ecfd3e63cf3ea94b29694e12 — outer call value 2,000 ETH, memo payload ending in SWAP:ETH.SUSHI-...
      • 0xb8eea9e6ed2bd81f758b59746435e4e331675d8595a1327c843d94774d0b25c3 — outer call value 2,000 ETH, memo payload ending in SWAP:ETH.ALCX-...
  • 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 Deposit event, 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

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 Deposit event.
  • The decisive failure was therefore not smart contract wrapper bad in the abstract, but Bifrost 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 accepted and live 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 transaction without 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.value handling 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 ETH and started accepting transaction looked like ETH moved nearby instead.

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.value interpretation, 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 ETH without changing router received ETH, relayers must treat the wrapper shell as unauthenticated context rather than as deposit evidence.
  • Keep exceptional msg.value overrides 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 -> refund should 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 received before 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 ETH while 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 call is a different security object from the 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.value override 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 -> refund becomes 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 ETH is not the same security object as protocol-owned router received spendable ETH.
  • It is also a useful sibling to the better-known 2021-07-22 THORChain 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

Control surface