oak_techniques: [OAK-T9.002]
spec_id: oak-detection-T9.002
version: 0.1.0
maturity: stable
maintainer: "@iZonex"
license: Apache-2.0

scope: |
  Detect transactions that combine an outsized flash-loan position with
  a same-tx interaction against a flash-loan-sensitive surface (governance
  vote, oracle consumption, vault donation, lending solvency check).
  T9.002 is rarely standalone — it is the enabling primitive for a
  T9.001 / T9.003 / T9.004 logic flaw. Detection is therefore both
  per-tx (chain detection) and pre-deployment (surface inventory).
  Excludes: legitimate flash-loan integrations (atomic-arbitrage,
  collateral swaps, position migration) where the consuming protocol
  is flash-loan-resistant by design (allowlisted via
  `flashloan_resistant_protocols`).

data_sources: [tx_call_trace, contract_inventory, contract_bytecode,
               contract_events, governance_events]

detection_logic:
  description: |
    Three orthogonal paths. PATH A (per-tx pattern): flash-loan provider
    callback in the call trace, with same-tx interaction against a
    sensitive function set on a non-allowlisted protocol. PATH B (block
    anomaly): flash-loan provider outflow spike correlated with sensitive-
    function call at a target protocol in the same block — coarse upstream
    signal. PATH C (pre-deployment inventory): static scan of recently-
    deployed contracts for code paths that snapshot balances or voting
    power without flash-loan resistance.
  pseudocode: |
    FLASHLOAN_PROVIDERS = {                           # ABI-signature → provider
      "flashLoan(address,address[],uint256[],uint256[],address,bytes,uint16)": aave_v2_v3,
      "flash(address,uint256,uint256,bytes)":                                  uniswap_v3,
      "flashLoan(address,address[],uint256[],bytes)":                          balancer_v2,
      "flashLoan(address,uint256)":                                            maker_dss_flash,
      "operate(...)":                                                          dydx_solomargin,
    }
    SENSITIVE_SELECTORS = {                           # consuming-side surface
      governance: {castVote, propose, queue, execute, emergencyCommit},
      oracle:     {update, poke, sync, consult, latestAnswer},
      vault:      {donateToReserves, donate, transferToReserves, mint, burn},
      lending:    {borrow, liquidate, withdraw, redeem},
    }

    sensitive(f) := f.selector ∈ flatten(SENSITIVE_SELECTORS)
                     and f.contract ∉ flashloan_resistant_protocols

    # PATH A — per-tx flash-loan pattern + sensitive interaction
    for each tx T:
      provs ← [f for f in T.frames if selector(f) ∈ keys(FLASHLOAN_PROVIDERS)]
      if provs == ∅: continue
      borrow_usd ← Σ usd_value(f.amounts, f.tokens) for f in provs
      hits       ← [f for f in T.frames if sensitive(f)]
      if borrow_usd < min_borrow_usd or hits == ∅: continue
      chained ← classify_chain(hits)                  # → T9.001 | T9.003 | T9.004 | mixed
      emit(PATH_A, tx=T.hash, providers=provs, borrow_usd, sensitive_calls=hits,
           chained_with=chained,
           severity = critical if chained ∈ {T9.001, T9.003} else high)

    # PATH B — block-level outflow anomaly + sensitive call (coarse upstream)
    for each block b:
      outflow ← Σ amount_out(p, b) for p in known_flashloan_pools
      sensitives ← [c for c in calls_in(b) if sensitive(c)]
      if outflow ≥ block_outflow_threshold and sensitives ≠ ∅:
        emit(PATH_B, block=b, outflow_usd=outflow,
             targets={c.contract for c in sensitives}, severity=medium)

    # PATH C — pre-deployment inventory (static)
    for each newly_deployed_contract C:
      reads  ← spot_balance_reads(C) ∪ vote_power_snapshot_reads(C)
      writes ← state_writes_in_same_fn(reads)
      if reads ≠ ∅ and writes ≠ ∅ and not flashloan_resistant_pattern(C):
        emit(PATH_C, contract=C, sensitive_reads=reads, severity=medium)

parameters:
  min_borrow_usd:                  { type: number,  default: 1000000 }   # $1M
  flashloan_resistant_protocols:   { type: list,    default: [] }        # canonical-design-allowlist
  known_flashloan_pools:           { type: list,    default: [] }        # per-chain Aave/Balancer/etc.
  block_outflow_threshold:         { type: number,  default: 100000000 } # $100M block-level outflow

output_alert: [oak_technique, detection_path, severity, chain, tx,
               providers, borrow_usd, sensitive_calls, chained_with, evidence]

test_fixtures:
  positive:
    - 2022-04-beanstalk                         # T9.002 → T9.003 (governance)
    - 2023-03-euler-finance                     # T9.002 → T9.004 (donateToReserves)
    - 2021-05-pancake-bunny                     # T9.002 → T9.001 (LP-reserve oracle)
    - 2021-10-cream-finance                     # T9.002 → T9.001 (book-value oracle)
    - 2022-04-saddle-finance                    # T9.002 → T9.004 (stableswap virtual-price)
  negative:
    - "Atomic arbitrage flash-loan with no sensitive-function consumption (DEX A → DEX B → repay)"
    - "Collateral-swap / position-migration flash-loan against an allowlisted flashloan_resistant_protocols target"

false_positive_modes:
  - legitimate atomic arbitrage routing (no sensitive selectors hit) — PATH A's sensitive-set filter handles this
  - legitimate flash-loan-funded leveraged-position open against a flash-loan-resistant lender — allowlist via flashloan_resistant_protocols
  - block-level outflow from large protocol-owned-liquidity rebalance (not paired with sensitive call at any target) — PATH B's sensitive-set filter handles this
  - false positives on legitimate `mint` / `burn` selectors at vault-share contracts under user-deposit / user-withdraw flow — chain-classifier should require donation-shape

mitigations: [OAK-M09, OAK-M10, OAK-M11, OAK-M16, OAK-M17, OAK-M32, OAK-M33, OAK-M34, OAK-M35, OAK-M38, OAK-M39]

reference_implementations:
  - { target: forta-bot,             chain: evm,    url: "" }
  - { target: blocksec-phalcon,      chain: evm,    url: "" }
  - { target: oz-defender-sentinel,  chain: evm,    url: "" }
  - { target: dune,                  chain: evm,    url: "" }
  - { target: tenderly-virtualnet,   chain: evm,    url: "" }
