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

scope: |
  Detect ERC-20-style tokens with authority-mutable transfer fees or
  caps. The defender concern is the post-launch authority surface, not
  the present fee value. Excludes: T1.005 (immutable but predicate-
  gated fees), T1.004 (binary pause / blacklist), governance-timelock-
  gated fees (suppressed via `timelock_allowlist`).

data_sources: [contract_bytecode, contract_events, authority_graph, simulation_environment]

detection_logic:
  description: |
    Three orthogonal paths. PATH A (static): writers to fee storage
    slot whose access gate is not a known timelock. PATH B (simulation):
    forked-state synthetic setFee(MAX) from owner produces observable
    fee jump. PATH C (continuous): FeeChanged-class events fired from
    a non-timelock caller.
  pseudocode: |
    # PATH A — static bytecode + storage layout
    fee_slots ← slots SLOAD-ed inside transfer arithmetic
    writers ← selectors that SSTORE to fee_slots
    for each w in writers:
      gate ← resolve_modifier_chain(w)
      if gate is None: emit(PATH_A, w, gate=ungated, severity=critical)
      elif gate.kind in {Ownable, AccessControl}:
        authorities ← resolve_authority_addresses(gate)
        if any(a ∉ timelock_allowlist for a in authorities):
          emit(PATH_A, w, gate=gate.kind, authorities, severity=high)

    # PATH B — simulation matrix (when forking is available)
    if simulation_available and len(fee_slots) > 0:
      baseline ← simulate_transfer(amount=sample_amount, from=POOL, to=EOA).fee
      with chain_fork():
        as(owner): call setFee(MAX)
        post ← simulate_transfer(amount=sample_amount, from=POOL, to=EOA).fee
        if post > baseline × fee_bump_ratio:
          emit(PATH_B, baseline, post, severity=critical)

    # PATH C — continuous event monitoring
    on event E ∈ contract_events matching fee_event_signatures:
      if E.tx.from ∉ timelock_allowlist:
        emit(PATH_C, caller=E.tx.from, prev_fee=E.prev, new_fee=E.new,
             tx=E.tx_hash, severity=high)

parameters:
  timelock_allowlist: { type: list, default: [] }
  fee_bump_ratio: { type: number, default: 5 }
  sample_amount: { type: number, default: 1e18 }
  fee_event_signatures:
    type: list
    default:
      - FeeChanged(uint256)
      - TaxUpdated(uint256)
      - FeesUpdated(uint256,uint256)
      - BuyTaxChanged(uint256)
      - SellTaxChanged(uint256)
      - MaxTxAmountChanged(uint256)

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

test_fixtures:
  positive:
    - 2021-11-squid
    - 2023-11-safemoon-charges
    - 2021-2026-influencer-amplified-non-memecoin-rug-cohort
  negative:
    - "USDC mainnet — no fee_slots resolved, must not flag"
    - "Governance-timelocked fee protocol — suppressed via timelock_allowlist"

false_positive_modes:
  - governance-controlled timelock (use canonical timelock_allowlist)
  - emergency-pause guardian (binary pause, not fee mutation)
  - rebase tokens — modify global supply scalar, not per-transfer multiplier

mitigations: [OAK-M01, OAK-M02, OAK-M03, OAK-M04, OAK-M05, OAK-M16, OAK-M17, OAK-M23, OAK-M25, OAK-M32]

reference_implementations:
  - { target: slither-plugin, chain: evm, url: "" }
  - { target: forta-bot, chain: evm, url: "" }
  - { target: tenderly-virtualnet, chain: evm, url: "" }
  - { target: dune, chain: evm, url: "" }
