Lesson 52 · Trace Drill · Application

Trace an oracle mispricing

Model A — the other-asset attack — and the first trace where the system fights back. ~15 min.

Builds on: L50 · L20 · L27 Anchor: borrow against inflated collateral New: the other-asset extractable + a defense

Third trace, and it completes the at_risk attack taxonomy: NAV redemption (L50, model B), a pool drain (L51), and now a DeFi-borrow oracle attack (L20 model A). It's the richest of the three — it hinges on an edge the oracle bridger had to derive, its extractable is in a different asset than the one attacked, and for the first time in a trace, the system actively refuses to propagate the attack instead of just modelling and recording it.

The scenario
Focus token FT is listed as collateral in an Aave-style lending market M, which prices it via a Chainlink oracle O. An attacker who can influence O publishes an inflated price for FT, then deposits a little FT and borrows as much USDC as the fake valuation allows — and walks off with the USDC. They never needed to "steal FT."
The counter-intuitive core (from L20)
In a NAV attack (L50) or a pool drain (L51) the attacker takes the focus token itself. Here they take a different asset — the USDC they borrow against mispriced FT collateral. So this cell's extractable is measured in borrowed-asset terms, not FT supply: min(at_stake × LTV, market_other_borrowable_usd) — the borrow the fake price unlocks, capped by the actual cash sitting in M (L20/L21). That OTHER-asset framing is what makes the oracle path the gnarliest of the three.

The stage — an edge the bridger had to derive

At build-time the system assembled exactly the structure model A needs — and one piece is special:

What was builtHow
FT is collateral in M (LENDING_COLLATERAL), with M's LTV + borrowable depthenrichment / lending refresher (L24)
O prices FT (ORACLE_DEP from FT)oracle/NAV discovery (L26)
M consumes O (ORACLE_DEP from M) — model A's eligibilitythe oracle bridger, transitively, in pure Cypher (L27)
O's admin (its owner()), role severityadmin discovery (L25)
No bridger, no cell
L20's model-A eligibility is "a market that both consumes the oracle and holds FT as collateral." A market doesn't declare its oracle dependency on-chain — the oracle bridger (L27) derived M-[:ORACLE_DEP]->O transitively from FT's. That single derived edge is what makes the per-(O, M) attack cell exist. This trace is where L27 finally earns its keep — neither L50 nor L51 needed it.

So at_risk already emits the model-A cell (emitOracleCellsViaToken, L20): target_role=oracle, scoped to market M, at_stake = max(M.avail, M.maxBorrow) for FT, extractable = min(at_stake × LTV, M.other_borrowable), outcome_class = trigger (an oracle attack triggers a mispricing — the L23 invariant that oracle cells are always "trigger"). The standing exposure is quantified before anything happens.

The trace — the bad price is published at block N

1
O publishes the inflated FT price. L1 · L7
the aggregator emits an AnswerUpdated-style log (a new round); block N is ingested and streamed.
2
Filter — O is monitored. L1 · L2
O was added to the monitored set as FT's price oracle (L26), so the update survives the drop.
3
The system reads the new price — and checks it. L26
the NAVLink/price refresher reads O's latestRoundData() and runs validateCrossSource: compare against an independent price (defillama / known_peg).
RPC-economy: a paced refresher read, not a per-block chain hit.
4a
DEFENSE — the wrong price is rejected. L26 · L29
if the inflated price diverges past tolerance from the independent source, the refresher refuses to stamp it (nav_oracle…/cross-source mismatch) and the NavlinkFreshnessVerifier alarms. The bad value never enters the graph; the fake collateral valuation is never trusted.
First trace with an active defense — the system doesn't just model/record, it declines to propagate a value it can disprove.
4b
…but if it slips through (subtle, or no independent price). L20 · L1/L7
a within-tolerance manipulation, or an asset with no cross-source price, passes the gate. The attacker then deposits FT and borrows USDC against it → Borrow events on M; those flow through ingest and update the lending edges.
5
at_risk recompute — the model-A cell was already right. L19 · L20 · L21
the per-(O, M) cell had quantified extractable = min(at_stake × LTV, other_borrowable) in USDC terms; the realized borrow matches the modelled surface. The cell collapses into FT's value pool (max-within, no double-count with any token-admin cell).
Combinator: the OTHER-asset extractable is bounded by two ceilings (LTV-borrow ∧ market cash), then MAX-pooled.
6
Exposure propagates — through the bridger's edge. L16 · L27 · L40
exposure-BFS flows FT's shock along M-[:ORACLE_DEP]->O and onward to any vault that allocates to M (the bridger's path C), each hop weighted (LTV / allocation share).
The contagion travels the very edges L27 derived.
7
Rule fires; alert delivered. L12 · L45 · L15 · L18
an oracle-/admin-risk rule on FT trips (conditionMet) → fire-once → AlertEvent with alert_type reflecting the oracle trigger → dedup by msg-id → notifier → human.
8
Self-checking confirms coverage. L29
verify_at_risk_oracle_coverage / verify_at_risk_oracle_extractable confirm every consuming market that holds FT has its oracle cell — the audit layer that guarantees model A didn't miss a (O, M) pair.
Three reactions, not two
L50/L51 had two clocks (preventive + recording). This trace adds a third: defense. The cross-source check (step 4a) can stop the attack from ever entering the graph — the system disproves the manipulated price against an independent source and refuses it. When that net has nothing to compare against (step 4b), it falls back to quantifying and recording. Preventive · defensive · recording — the full repertoire.

The taxonomy, now fully traced

L50 · NAV (model B)L51 · pool drainL52 · borrow (model A)
attacker takesthe focus token (redeem supply)the focus token (sweep pool)a different asset (borrowed USDC)
extractable boundmin(at_stake, exit_v2) / honest-$0exit liquidity, self-subtract venuemin(at_stake × LTV, other_borrowable)
key upstreamNAV registry binding (L26)HOLDS pool cell (L19 Path 3)oracle bridger ORACLE_DEP (L27)
system reactionpreventive + recordpreventive + recordpreventive + defend + record
You've now traced all three at_risk attack shapes
Redeem-the-supply, drain-the-pool, borrow-the-other-asset — the three ways value leaves a focus token, each with its own cell construction, its own extractable ceiling, and its own upstream edge. Hand yourself any DeFi exploit and you can now classify it into one of these shapes and trace it. That's the working mastery the whole journey was for.

Run it yourself

1. In a model-A oracle attack, what does the attacker actually walk away with?
2. The model-A cell's extractable is min(at_stake × LTV, other_borrowable). Why these two terms?
3. The per-(O, M) attack cell exists only because of a derived edge. Which, and from where?
4. At step 4a the system does something L50 and L51's traces never did. What?
5. When does the cross-source defense (step 4a) fail to stop the attack, falling through to 4b?
6. The model-A cell carries outcome_class = trigger. What's the rule behind that (L23)?
7. At step 6 the FT shock propagates to a vault that allocates to market M. Along which edge, and why does it exist?
8. Across L50–L52, what distinguishes the three at_risk attack shapes?
↳ Ask your teacher
Try: "Trace a depeg cascade so exposure-BFS multi-hop is centre stage." · "What's the cross-source tolerance, and how is it tuned?" · "If O is owner()'d by a Safe, how does L22 expansion change the cell?" · "Quiz me cold: classify a random real-world exploit into a model and trace it." · "How does verify_at_risk_oracle_coverage decide a (O,M) pair is missing?"

What you can now do

Three traces, one instrument
NAV redemption, pool drain, oracle borrow — the at_risk attack taxonomy is now fully traced, and you've seen the system's whole repertoire: quantify ahead (preventive), refuse the bad value (defensive), and record what lands (recording). The trace is no longer a lesson; it's a tool you carry.

Applies: ingest/stream/filter (L1/L2/L7), oracle update read + cross-source defense (L26), AnswerUpdated decode (L3/L44), model-A per-(O,M) cell + other-asset extractable (L20), exit/borrow ceiling (L21), value-pool collapse (L19), oracle bridger ORACLE_DEP eligibility + exposure propagation (L27/L16/L40), verify_at_risk_oracle_coverage (L29), outcome=trigger invariant (L23), admin severity (L25/L13), rule firing + alert delivery (L12/L45/L15/L18); the four syntheses (L46–L49). Verify against source — the code is the truth.