All incidents

bZx/Fulcrum WBTC market manipulation drains ETH liquidity

Share
Feb 15, 2020 01:38 UTCAttackLoss: 1,233.79 ETHManually checked1 exploit txWindow: Atomic

Root Cause Analysis

bZx/Fulcrum WBTC market manipulation drains ETH liquidity

1. Incident Overview TL;DR

On Ethereum mainnet block 9484688, transaction 0xb5c8bd9430b6cc87a0e2fe110ece6bf527fa4f170a4bc8cd032f768fc5219838 executed an atomic exploit path across dYdX/Compound/bZx/Kyber/Uniswap. The adversary used flash liquidity, borrowed 112 WBTC via Compound, then triggered bZx mintWithEther to force a large protocol-driven WETH -> WBTC buy through Kyber into Uniswap V1 liquidity. Immediately after that price impact, the adversary sold the borrowed WBTC into the same Uniswap pool and extracted 6871.412738870224322944 ETH in the unwind leg. The net attacker EOA delta in native ETH was positive (+64.96890957 ETH after gas accounting in root-cause artifacts). The root cause is an execution/oracle design failure: protocol-critical execution depended on same-transaction spot liquidity and tolerated effectively unbounded slippage in the observed route (minConversionRate = 0).

2. Key Background

The incident is centered on bZx/Fulcrum position-token execution and its downstream trade path.

  • Position token used in the incident: 0xb0200b0677dd825bb32b93d055ebb9dc3521db9d.
  • Trade venue where price impact and unwind happened: Uniswap V1 WBTC/ETH pool 0x4d2f5cfba55ae412221182d8475bc85799a5644b.
  • Collateral/borrow setup used Compound contracts (Comptroller, cETH, cWBTC) and borrowed 11200000000 satoshis of WBTC.
  • The bZx token logic wraps execution with setSaneRate/clearSaneRate, but the downstream trade path still called into Kyber/Uniswap with permissive parameters, allowing same-tx price displacement.

Core source context (PositionTokenLogicV2):

modifier fixedSaneRate {
    address currentOracle_ = IBZx(bZxContract).oracleAddresses(bZxOracle);
    IBZxOracle(currentOracle_).setSaneRate(loanTokenAddress, tradeTokenAddress);
    _;
    IBZxOracle(currentOracle_).clearSaneRate(loanTokenAddress, tradeTokenAddress);
}
...
(bool success, bytes memory data) = oracleAddress.call(
    abi.encodeWithSignature(
        "tradeUserAsset(address,address,address,address,uint256,uint256,uint256)",
        sourceTokenAddress,
        destTokenAddress,
        receiver,
        receiver,
        sourceTokenAmount,
        MAX_UINT,
        0 // minConversionRate
    )
);

Snippet origin: PositionTokenLogicV2 source artifact.

3. Vulnerability Analysis & Root Cause Summary

Root cause category: ATTACK.

The vulnerable behavior is an oracle/execution design issue, not a privileged-key event. The protocol’s leverage/margin execution path accepted same-transaction, spot-liquidity-derived pricing in a manipulable venue. The explicit invariant that should have held is: a single unprivileged transaction must not be able to force materially off-market protocol execution and arbitrage that induced price in the same block. The code-level breakpoint is the downstream oracle trade call with minConversionRate = 0 and huge destination limit, which removed an effective execution floor. In this incident, the attacker sequence exploited that exact condition by first forcing bZx to buy WBTC through thin liquidity, then immediately unwinding borrowed WBTC into the now-distorted pool. Because this was done atomically with flash-loan-scale capital, the exploit became deterministic and repeatable on the forked pre-state.

4. Detailed Root Cause Analysis

Step-by-step mechanism validated from code + trace evidence:

  1. The attacker transaction established atomic capital and debt setup (Compound enterMarkets + borrow(11200000000)).
  2. The attacker called mintWithEther on the bZx position token, which delegated into PositionTokenLogicV2 and entered the fixedSaneRate-wrapped path.
  3. During that path, the downstream Kyber call executed with minConversionRate = 0 and a very large destination cap, while sourcing Uniswap-linked liquidity.
  4. That protocol-driven WETH->WBTC buy shifted Uniswap pool reserves in the direction required for profitable attacker unwind.
  5. The attacker then sold all borrowed WBTC (11200000000) into the same pool and received 6871412738870224322944 wei.
  6. Balance diffs show final attacker profit signal and pool ETH depletion.

Critical trace evidence (bZx leg and permissive trade params):

...::mintWithEther{value: 1300000000000000000000}(attacker, 0)
...::setSaneRate(WETH9, WBTC)
...::tradeWithHint(WETH9, 5637623762376237623786, WBTC, ..., 10000000000000000000000000000, 0, ...)
...::clearSaneRate(WETH9, WBTC)

Snippet origin: incident transaction trace.

Critical trace evidence (unwind leg):

WBTC::balanceOf(attacker) -> 11200000000
UniswapV1::tokenToEthSwapInput(11200000000, 1, 4294967295)
emit EthPurchase(..., 11200000000, 6871412738870224322944)

Snippet origin: incident transaction trace.

Direct impact evidence from native balance diff:

{
  "address": "0x148426fdc4c8a51b96b4bed827907b5fa6491ad0",
  "delta_wei": "64968909570000000000"
}
{
  "address": "0x4d2f5cfba55ae412221182d8475bc85799a5644b",
  "delta_wei": "-1233788976493986699158"
}

Snippet origin: incident balance-diff artifact.

5. Adversary Flow Analysis

Adversary strategy summary: single-transaction, multi-stage extraction using flash liquidity, protocol-induced price move, and immediate unwind.

Adversary-related accounts:

  • EOA 0x148426fdc4c8a51b96b4bed827907b5fa6491ad0: transaction sender and final profit recipient.
  • Contract 0x4f4e0f2cb72e718fc0433222768c57e823162152: attacker orchestrator handling protocol interactions and intermediate ETH.
  • Contract 0x0de0dd63d9fb65450339ef27577d4f39d095eb85: flash-loan callback executor in the same strategy path.

Victim-side components observed in the flow:

  • bZx position token 0xb0200b0677dd825bb32b93d055ebb9dc3521db9d (verified source collected).
  • bZx loan-token margin path 0x77f973fcaf871459aa58cd81881ce453759281bc (not verified in collected source set).
  • Uniswap V1 WBTC/ETH pool 0x4d2f5cfba55ae412221182d8475bc85799a5644b (not verified in collected source set).

Lifecycle stages in the incident tx:

  • Atomic financing and setup: flash-loan-backed capital setup, enterMarkets, cETH mint, cWBTC borrow.
  • Protocol-induced price move: mintWithEther path drives large WETH->WBTC route through Kyber/Uniswap.
  • Unwind and realization: attacker sells 112 WBTC into the distorted pool and captures ETH surplus.

6. Impact & Losses

Measured impact (from collector artifacts):

  • Uniswap WBTC/ETH pool ETH reserve delta: -1233.788976493986699158 ETH (-1233788976493986699158 wei).
  • Attacker EOA native balance delta: +64.96890957 ETH net in-transaction signal.
  • Reported total loss overview in root-cause artifact: 1233.788976493986699158 ETH.

Scope and affected parties:

  • Economic loss materialized in AMM liquidity state and protocol-induced toxic execution.
  • Incident realized entirely within one public, unprivileged Ethereum transaction.

7. References

  • Incident transaction: 0xb5c8bd9430b6cc87a0e2fe110ece6bf527fa4f170a4bc8cd032f768fc5219838 (Ethereum mainnet block 9484688).
  • Seed metadata artifact: /workspace/session/artifacts/collector/seed/1/0xb5c8bd9430b6cc87a0e2fe110ece6bf527fa4f170a4bc8cd032f768fc5219838/metadata.json.
  • Seed trace artifact: /workspace/session/artifacts/collector/seed/1/0xb5c8bd9430b6cc87a0e2fe110ece6bf527fa4f170a4bc8cd032f768fc5219838/trace.cast.log.
  • Seed balance-diff artifact: /workspace/session/artifacts/collector/seed/1/0xb5c8bd9430b6cc87a0e2fe110ece6bf527fa4f170a4bc8cd032f768fc5219838/balance_diff.json.
  • Victim-side source artifact: /workspace/session/artifacts/collector/seed/1/0x579ad3c8abc3658341044c1c6d6dc48f9e015026/src/Contract.sol.