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

scope: |
  Detect contract execution paths in which an external call transfers
  control before storage is finalised, enabling re-entry. Covers five
  sub-patterns documented under T9.005: single-function (canonical),
  cross-function (per-fn lock insufficient), cross-protocol (re-enters
  peer protocol reading stale state), hook-based (ERC-777 / ERC-721 /
  ERC-1155 implicit calls), and read-only (third protocol consumes a
  view-function read against mid-update state). Excludes: T9.002
  (flash-loan capital acquisition — frequently chained, separate
  primitive); accounting bugs that aren't reentrancy.

data_sources: [contract_source, tx_call_trace, contract_inventory]

detection_logic:
  description: |
    Four orthogonal paths. PATH A (AST): external call before state
    write in same function lacking guard. PATH B (shared-state graph):
    function pair sharing storage slot with mismatched lock domains.
    PATH C (runtime): outer-frame-still-open re-entry with inner state
    write. PATH D (read-only): STATICCALL into a target whose outermost
    frame in the same tx hasn't returned, from a known consumer.
  pseudocode: |
    EXTERNAL_CALL_OPS = {call, callcode, delegatecall, staticcall, send,
                         transfer, erc777_send, safe_transfer_from, ...}

    # PATH A — static AST (per function)
    for each fn F:
      ops ← walk_ast(F)            # ordered: state-writes + external-calls
      for each external_call C with index(C) < max_state_write_index(F):
        kind ← hook_implicit if C ∈ hook_calls(hook_token_registry) else explicit
        if not has_lock(F, modifiers = static_analysis_lock_modifiers):
          emit(PATH_A, fn=F, kind=kind, severity=high)

    # PATH B — static shared-state graph (per contract)
    for each pair (A, B) of functions sharing ≥1 storage slot:
      if shared_slot_pattern(A, B) implies stale-read risk
         and lock_domain(A) ≠ lock_domain(B):
        emit(PATH_B, fn_pair=(A, B), shared_slots=A.slots ∩ B.slots,
             severity=high)

    # PATH C — runtime call-trace (per transaction)
    for each tx T:
      for each address X appearing in ≥2 frames in T:
        if any inner frame F has state_write
           and outer frame F_outer not yet returned at F:
          emit(PATH_C, contract=X, tx=T.hash, frame_count, inner_writes,
               severity=critical) if inner_writes ≥ inner_write_threshold

    # PATH D — read-only reentrancy (per transaction)
    for each tx T:
      for each STATICCALL S into target X:
        outermost ← outermost_frame_for_address(T, X)
        if outermost.not_returned_at(S)
           and S.from ∈ protocol_dependencies.consumers_of(X):
          emit(PATH_D, target=X, consumer=S.from, tx=T.hash,
               severity=critical)

parameters:
  hook_token_registry: { type: list, default: [] }
  protocol_dependencies: { type: object, default: {} }
  inner_write_threshold: { type: integer, default: 2 }
  static_analysis_lock_modifiers:
    type: list
    default: [nonReentrant, noReentry, lock]

output_alert: [oak_technique, detection_path, severity, chain, evidence]

test_fixtures:
  positive:
    - 2016-06-the-dao
    - 2020-04-lendf-me
    - 2020-11-akropolis
    - 2020-11-origin-dollar
    - 2023-03-paraspace
    - 2023-04-sentiment
    - 2023-07-curve-vyper
    - 2023-11-kyberswap
    - 2024-09-penpie
  negative:
    - "OZ ReentrancyGuard correctly applied across function set sharing state"
    - "Multicall aggregator — outer frame returns between inner calls"

false_positive_modes:
  - multicall pattern (outer frame returns cleanly between inner calls)
  - legitimate ERC-777-hooked tokens (flag the consumer, not the token)
  - inline assembly / Yul raw call ops invisible to AST analysis
  - compiler-level reentrancy (Curve July 2023 Vyper) bypasses AST — PATH C required

mitigations: [OAK-M02, OAK-M03, OAK-M05, OAK-M10, OAK-M16, OAK-M32, OAK-M33, OAK-M34, OAK-M35, OAK-M38, OAK-M39]

reference_implementations:
  - { target: slither-plugin, chain: evm, url: "" }
  - { target: forta-bot, chain: evm, url: "" }
  - { target: certora-spec, chain: evm, url: "" }
  - { target: oz-defender-sentinel, chain: evm, url: "" }
