Document
OAK Pseudocode Language Specification (PLS) v0.7.0
This document defines the formal grammar, type system, and runtime contract for the
pseudocode language used in OAK detection specs (detection_logic.pseudocode).
It is the normative reference for all 134 specs — every pseudocode block across
the spec corpus MUST be parseable against this grammar.
1. Design principles
Readable first. An investigator with no compiler background must understand the logic.
Compilable. The grammar is unambiguous and mechanically translatable to Python, Rust, TypeScript.
Data-source abstract. The language references data sources by logical name; binding to a specific blockchain RPC, indexer API, or off-chain feed is the implementation layer's responsibility.
PATH-compositional. Every spec emits alerts via
emit(PATH_X, ...). PATHs are independent detection paths within a spec; cross-spec correlation is a higher-order concern.Deterministic. Given the same data-source state at the same block height, the same spec MUST produce the same alerts.
2. Lexical grammar
2.1 Tokens
IDENTIFIER := [a-zA-Z_][a-zA-Z0-9_]*
INTEGER := [0-9]+
FLOAT := [0-9]+ '.' [0-9]+
DURATION := INTEGER ('s' | 'm' | 'h' | 'd')
STRING := '"' [^"]* '"' | "'" [^']* "'"
COMMENT := '#' .* (until end-of-line)
BLOCK_COMMENT := '/*' .* '*/'
PATH_LABEL := 'PATH_' [A-E]
2.2 Operators
| Category | Operators |
|---|---|
| Assignment | ← (U+2190) |
| Arithmetic | + − (U+2212) × (U+00D7) / % |
| Comparison | = ≠ (U+2260) < > ≤ (U+2264) ≥ (U+2265) |
| Logical | and or not |
| Set membership | ∈ (U+2208) ∉ (U+2209) |
| Set operations | ∩ (U+2229) ∪ (U+222A) − ∅ (U+2205) |
| Range | .. (inclusive..inclusive) |
| Pipe (comprehension) | ` |
| Lambda key | → (U+2192) or by= |
| Optional access | ?. |
2.3 Keywords (reserved)
for each in where if elif else and or not
on event matching with as continue break
return emit None True False null
2.4 String interpolation
"text {expression} more text {expression}"
Expressions inside {...} are evaluated and coerced to string. Occurrences inside
severity and guidance fields of emit() are the primary use.
3. Type system
3.1 Primitive types
| Type | Description | Literal examples |
|---|---|---|
integer |
256-bit signed integer | 0, 42, −1 |
number |
IEEE 754 float64 | 0.01, 3.14, 1e10 |
boolean |
True / False |
True, False |
string |
UTF-8 string | "hello", 'world' |
duration |
Time interval in seconds | 30d, 48h, 365d |
address |
20-byte (EVM) or 32-byte (Solana/Aptos/Sui) | 0x... |
selector |
4-byte function selector | 0x8129fc1c |
hash |
32-byte keccak256/sha256 | 0x... |
block_number |
Chain block height | 18500000 |
null |
Absent value | null, ∅ |
3.2 Collection types
| Type | Description | Construction |
|---|---|---|
list<T> |
Ordered, indexable | [a, b, c] or comprehension |
set<T> |
Unordered, unique | {a, b, c} or comprehension |
map<K,V> |
Key-value | {key: value, ...} or comprehension |
tuple |
Fixed-arity heterogeneous | (a, b, c) |
3.3 Domain types (opaque)
These types are produced by data-source accessors and consumed by built-in functions. Implementations map them to concrete schemas.
| Type | Description | Produced by |
|---|---|---|
contract |
Deployed contract handle | query_louper(), get_contract_deployment_timestamp() |
tx_trace |
Transaction call trace | get_call_trace() |
event |
Contract event log entry | data source iteration |
pool |
DEX pool handle | creates_new_pool(), pool iteration |
vault |
ERC-4626 vault handle | vault iteration |
bridge |
Bridge contract handle | bridge iteration |
oracle |
Oracle feed handle | oracle iteration |
validator |
Bridge/AVS validator descriptor | validator set iteration |
attestation |
Device attestation result | execute_vendor_crypto_challenge() |
bundle |
MEV relay bundle | data source bundle_data |
curve |
Mathematical function (x→y) | compute_brute_force_completion_curve() |
3.4 Generics in built-in signatures
list<T>.map(fn: T → U): list<U>
list<T>.filter(fn: T → boolean): list<T>
list<T>.group_by(fn: T → K): map<K, list<T>>
4. Control flow
4.1 for each — primary iteration
for each <variable> in <iterable>:
<body>
for each <variable> in <iterable> where <condition>:
<body>
for each (<v1>, <v2>) in <iterable_of_tuples>:
<body>
The where clause is syntactic sugar for an inline filter — it is evaluated before
the first body statement.
4.2 if / elif / else
if <condition>:
<body>
elif <condition>:
<body>
else:
<body>
4.3 Ternary expression (inline conditional)
<value_if_true> if <condition> else <value_if_false>
# Multi-branch
value1 if condition1 else
value2 if condition2 else
value3
4.4 on event — reactive trigger
on event <variable> ∈ <event_source> matching <selector_set>:
<body>
Semantics: the body executes once per matched event. This is the primary
continuous-monitoring construct. Inside the body, E.block, E.tx, E.from,
E.signature, E.args are available.
4.5 with chain_fork(<block>) — sandboxed simulation
with chain_fork(<block>):
<body>
Semantics: the body executes against a temporary fork of chain state at the given block. State mutations inside the body are discarded. Used by PATHs that need to simulate "what if" scenarios (e.g., setFee(MAX), process invalid message).
4.6 with chain_fork(): — default to current block
with chain_fork():
<body>
Forks at the current detection block.
4.7 as(<role>) — identity switch inside fork
with chain_fork():
as(owner): call setFee(MAX)
Switches tx.origin and msg.sender to the specified role address for the
duration of the call.
5. Expressions
5.1 Comprehension forms
# List comprehension
[<expr> for <var> in <iterable>]
[<expr> for <var> in <iterable> if <condition>]
[<expr> for <var> in <iterable> where <condition>]
# Set comprehension
{<expr> for <var> in <iterable>}
{<expr> for <var> in <iterable> where <condition>}
# Map comprehension
{<key_expr>: <value_expr> for <var> in <iterable>}
5.2 Set operations
A ∩ B # intersection
A ∪ B # union
A − B # difference
A ∈ B # membership test
A ∉ B # non-membership
∅ # empty set
len(A) # cardinality
5.3 Aggregation expressions
sum(<expr> for <var> in <iterable>)
sum(<expr> for <var> in <iterable> where <condition>)
len(<iterable>)
max(<iterable>)
min(<iterable>)
mean(<iterable>)
abs(<number>)
round(<number>)
5.4 Lambda-like key arguments
sorted(<iterable>, by=<expr>)
sorted(<iterable>, by=<expr>, descending)
group_by(<iterable>, key=<expr>)
max(<iterable>, key=<expr>)
min(<iterable>, key=<expr>)
5.5 Range
0..N # inclusive range: 0, 1, ..., N
0..min_blocks # variable endpoint
5.6 Slice notation
list[start:end] # Python-style slice
list[−window:] # last `window` elements (negative index from end)
list[:n] # first n elements
5.7 Optional chaining
obj?.field # null if obj is null, otherwise obj.field
obj?.method(args) # null if obj is null
5.8 Generator expressions (lazy)
# In aggregation calls:
sum(v.stake for v in B.validator_set)
any(condition for x in collection)
all(condition for x in collection)
6. Function definitions
6.1 Scalar helper
func_name(param1, param2, ..., paramN) := <expression>
6.2 Conditional helper (multi-branch)
func_name(p) :=
value1 if condition1 else
value2 if condition2 else
value3
6.3 Block helper (multi-statement)
func_name(p) :=
stmt1
stmt2
...
stmtN
return <expression>
6.4 Visibility and scope
- Helpers are file-private (scoped to the spec).
- No cross-spec function imports.
- Parameters are pass-by-value.
- Recursion is not permitted.
7. emit() specification
7.1 Signature
emit(PATH_X, <keyword_args>...)
7.2 Required keyword arguments
| Argument | Type | Description |
|---|---|---|
PATH_X |
keyword | The detection path that fired (PATH_A through PATH_E) |
7.3 Standard keyword arguments (all PATHs SHOULD include)
| Argument | Type | Description |
|---|---|---|
severity |
"critical" |
"high" |
guidance |
string (interpolated) |
Human-readable operator guidance |
chain |
string |
Chain where the alert fired |
7.4 Spec-specific keyword arguments
Defined per spec in output_alert. Every argument listed in the spec's output_alert
field MUST appear in at least one emit() call.
7.5 Semantics
Each
emit()produces exactly one alert.PATHs are independent: PATH_A and PATH_B firing on the same transaction produce two alerts with distinct
detection_pathvalues.severityis per-emit, not per-spec. The same PATH may emit at different severities depending on parameter thresholds.guidancestrings use f-string-style interpolation. Variables in{...}are evaluated in the current scope.
8. Data source interface
8.1 Contract
The pseudocode references data sources by their logical name as declared in the
spec's data_sources field. The implementation layer resolves each logical name
to a concrete data provider.
8.2 Standard data source catalog
| Logical name | Provides | Example implementations |
|---|---|---|
tx_call_trace |
Per-transaction EVM call traces (Parity trace_* or debug_traceTransaction) | Erigon, Geth debug, QuickNode |
contract_bytecode |
Deployed contract bytecode by address | Etherscan, Sourcify, RPC eth_getCode |
contract_source |
Verified Solidity/Vyper source | Etherscan, Sourcify |
contract_storage |
Per-slot storage reads (eth_getStorageAt) | Any RPC |
contract_events |
Contract event logs (eth_getLogs) | Any RPC, The Graph |
contract_deployment |
Contract deployment timestamps and deployer addresses | Etherscan, Blockscout |
dex_trades |
DEX swap events with price/volume | Dune, The Graph, DefiLlama |
reference_price_feed |
CEX/DEX mid-market reference prices | Chainlink, Pyth, Binance API |
oracle_feed |
Oracle-reported values (Chainlink, Tellor, UMA) | Oracle contract events |
mempool_transaction |
Pending transactions before inclusion | Flashbots, bloXroute, local node txpool |
bundle_data |
MEV relay bundle contents | Flashbots, bloXroute, Eden, ultrasound |
mev_relay_feed |
Bundle metadata (builder, relay, block) | mevboost.pics, eigenphi |
governance_events |
Governance proposal/approval/execution events | Tally, Boardroom, contract events |
bridge_message_logs |
Cross-chain message events (sent/received) | LayerZero Scan, Wormhole Explorer |
audit_report_feed |
Published audit reports with facet/bytecode lists | Solidity Finance, Trail of Bits, OpenZeppelin |
breach_disclosure_feed |
Third-party breach disclosures | HaveIBeenPwned, vendor security bulletins |
token_prices |
Per-token USD/ETH price series | DefiLlama, CoinGecko, Dune |
avs_event_feed |
EigenLayer AVS events (yield, slashing, operator changes) | EigenLayer subgraph, contract events |
withdrawal_queue_state |
LRT withdrawal queue snapshots | Contract storage reads |
validator_set_attestation |
Bridge/AVS validator set composition and stake | Contract storage, staking explorer |
staking_rewards |
Validator staking reward history | Staking indexers, reward events |
relayer_authorization_events |
Cross-chain relayer set changes | Bridge contract events |
liquidation_events |
Lending protocol liquidation events | The Graph, Dune, contract events |
lending_protocol_state |
Lending pool state (borrow limits, collateral ratios) | Contract storage reads |
domain_registration |
New domain registration feed | CertSpotter, WhoisXML, ZoneFiles |
certificate_transparency_log |
Certificate transparency log entries | CertSpotter, crt.sh |
token_deployment_events |
Token contract deployment events | Contract deployment monitoring |
authority_graph |
On-chain authority/ownership relationship graph | mg-detectors-rs, custom indexers |
simulation_environment |
Forked-chain simulation capability | Tenderly, Anvil, Ganache |
funder_graph |
Cross-address funding relationship graph | Chainalysis, TRM, Elliptic, mg-detectors-rs |
8.3 Data source access pattern
Data sources are accessed through iteration (the for each ... in DATASOURCE pattern)
or through point queries (eth_call(), getStorageAt(), simulate_call()).
Implementations MUST provide:
- Iteration: a cursor/stream interface that yields typed records
- Point query: a request/response interface with block-height parameter
- Simulation: a sandboxed EVM fork with state override capability
9. PATH composition semantics
9.1 PATH independence
PATH_A through PATH_E within a spec are independent detection paths — they share helper functions and parameters but do not share mutable state. An alert from PATH_A does not suppress or amplify PATH_B.
9.2 Cross-PATH correlation (PATH E convention)
By convention, PATH_E performs cross-PATH correlation: it consumes the aggregated outputs of PATH_A through PATH_D over a time window. PATH_E alerts indicate multi-signal convergence, not a single-signal detection.
The pseudocode naming convention for this pattern:
PATH_A_ALERTS # the accumulated alerts from PATH_A over a window
PATH_B_ALERTS # same for PATH_B
...
This is not a language feature — it is a naming convention. Implementations must provide a temporal windowed store of prior emits to serve as input to PATH_E.
9.3 Explicit cross-spec composition
Not defined in PLS v0.1.0. Cross-spec correlation (e.g., "this T9.001 alert and this T9.002 alert share a funder cluster") is a higher-order concern addressed by the correlation engine, not by the pseudocode language.
10. EBNF grammar (normative)
This grammar defines the parseable subset. It is the reference for transpiler implementations and spec validators.
(* Top-level *)
spec := { helper_def } { path_def } ;
helper_def := IDENTIFIER '(' [ param_list ] ')' ':=' helper_body ;
helper_body := expression | conditional_block | statement_block ;
param_list := IDENTIFIER { ',' IDENTIFIER } ;
(* PATH blocks *)
path_def := comment? for_each_stmt | comment? on_event_stmt
| comment? with_stmt | comment? if_stmt_standalone ;
(* Statements *)
statement := assignment | for_each_stmt | if_stmt | emit_stmt
| on_event_stmt | with_stmt | return_stmt
| continue_stmt | break_stmt | expression_stmt ;
statement_block:= statement { statement } ;
assignment := IDENTIFIER '←' expression ;
emit_stmt := 'emit' '(' 'PATH_' [A-E] { ',' keyword_arg } ')' ;
keyword_arg := IDENTIFIER '=' expression ;
expression_stmt:= expression ;
return_stmt := 'return' expression ;
continue_stmt := 'continue' ;
break_stmt := 'break' ;
(* Control flow *)
for_each_stmt := 'for' 'each' binding 'in' expression [ 'where' expression ] ':'
statement_block ;
binding := IDENTIFIER | '(' IDENTIFIER { ',' IDENTIFIER } ')' ;
if_stmt := 'if' expression ':' statement_block
{ 'elif' expression ':' statement_block }
[ 'else' ':' statement_block ] ;
if_stmt_standalone := if_stmt ;
on_event_stmt := 'on' 'event' IDENTIFIER '∈' expression
[ 'matching' expression ] ':' statement_block ;
with_stmt := 'with' 'chain_fork' '(' [ expression ] ')' ':' statement_block ;
(* Expressions *)
expression := conditional_expr ;
conditional_expr := logical_or { 'if' logical_or 'else' logical_or } ;
logical_or := logical_and { 'or' logical_and } ;
logical_and := not_expr { 'and' not_expr } ;
not_expr := [ 'not' ] comparison ;
comparison := set_expr { comp_op set_expr } ;
comp_op := '=' | '≠' | '<' | '>' | '≤' | '≥' | '∈' | '∉' ;
set_expr := additive { set_op additive } ;
set_op := '∩' | '∪' | '−' ;
additive := multiplicative { add_op multiplicative } ;
add_op := '+' | '−' ;
multiplicative := unary { mul_op unary } ;
mul_op := '×' | '/' | '%' ;
unary := [ '−' ] primary ;
primary := literal | IDENTIFIER | function_call
| comprehension | '(' expression ')'
| IDENTIFIER '?' '.' IDENTIFIER
| IDENTIFIER '[' expression ']'
| IDENTIFIER '[' expression ':' expression ']'
| primary '[' '−'? expression ':' ']' ;
function_call := IDENTIFIER '(' [ arg_list ] ')' ;
arg_list := expression { ',' expression } | keyword_splat ;
keyword_splat := IDENTIFIER '=' expression { ',' IDENTIFIER '=' expression } ;
comprehension := list_comp | set_comp | map_comp ;
list_comp := '[' expression 'for' binding 'in' expression [ 'where' expression ] ']' ;
set_comp := '{' expression 'for' binding 'in' expression [ 'where' expression ] '}' ;
map_comp := '{' expression ':' expression 'for' binding 'in' expression '}' ;
(* Literals *)
literal := INTEGER | FLOAT | STRING | DURATION
| 'True' | 'False' | 'None' | 'null' | '∅'
| range | list_literal | set_literal | map_literal ;
range := expression '..' expression ;
list_literal := '[' [ expression { ',' expression } ] ']' ;
set_literal := '{' [ expression { ',' expression } ] '}' ;
map_literal := '{' [ expression ':' expression { ',' expression ':' expression } ] '}' ;
11. Built-in function catalog
Built-in functions are organized by domain. Each entry includes the signature, return type, and the data source(s) it depends on.
11.1 Blockchain state — point queries
eth_call(address, selector, block?) → any
Data sources: contract_storage
Performs an eth_call (read-only) against the contract at the given block.
Returns the ABI-decoded return value.
getStorageAt(contract, slot, block) → value
Data sources: contract_storage
Reads raw storage slot value at a given block.
is_contract(address) → boolean
Data sources: contract_bytecode
True if the address has deployed bytecode.
get_contract_deployment_timestamp(address) → timestamp
Data sources: contract_deployment
Returns the block timestamp of the transaction that deployed the contract.
get_contract_deployment_block(address) → block_number
Data sources: contract_deployment
11.2 Blockchain state — trace analysis
get_call_trace(tx_hash) → tx_trace
Data sources: tx_call_trace
Returns the full internal call trace for a transaction.
find_outermost_frame(trace, target_contract) → frame | null
Data sources: tx_call_trace
Returns the outermost (earliest) call frame for the target contract,
or null if the contract is not in the trace.
find_consumer_value_transfer(trace, staticcall_frame) → frame | null
Data sources: tx_call_trace
Returns the call frame that consumes the staticcall result for a
value-transfer decision (liquidation, borrow, redeem, etc.).
has_cross_contract_callback(tx) → boolean
Data sources: tx_call_trace
True if the transaction's trace shows a callback pattern where contract A
calls B which calls back into A before A's first frame returns.
classify_economic_action(frame) → string | null
Data sources: tx_call_trace, contract_bytecode
Classifies a frame's purpose: "liquidation", "borrow", "redeem", "mint", etc.
Returns null if not an economic action.
11.3 Blockchain state — event queries
query_governance_events(chain, proposal_id) → proposal | null
Data sources: governance_events
Returns the governance proposal data (params, proposer, vote counts) or null.
find_role_grant_event(contract, role, grantee_address) → event | null
Data sources: contract_events
Returns the event that granted a specific role, or null.
find_relayer_addition_event(executor, relayer_address) → event | null
Data sources: relayer_authorization_events
Returns the event where a relayer was added to an executor.
find_corresponding_governance_proposal(change_event) → proposal | null
Data sources: governance_events
Cross-references a relayer/permission change against governance proposals.
11.4 Blockchain state — iterative access
get_avs_events(avs, lookback_blocks) → list<event>
Data sources: avs_event_feed
Returns AVS-related events (yield_distribution, slashing, operator_set_change,
strategy_addition, strategy_removal, fee_change) in the window.
get_recent_avs_events(avs, lookback_blocks) → list<event>
Data sources: avs_event_feed
Alias for get_avs_events with event-type filtering.
get_transactions_between(start_block, end_block, address) → list<tx>
Data sources: tx_call_trace, mempool_transaction
Returns all transactions touching the address between two blocks.
get_trades_in_blocks(pool, from_block, max_blocks) → list<trade>
Data sources: dex_trades
Returns all trades for a pool within a block range.
get_view_reads_in_block(block, target_contract) → list<read>
Data sources: tx_call_trace
Returns all view-function reads of the target contract in a given block.
get_deployer_history(deployer, platform) → object
Data sources: token_deployment_events, contract_deployment
Returns {prior_launches: integer, ...} for a deployer on a platform.
11.5 DEX and price
reference_price(asset, block, venues) → number
Data sources: reference_price_feed
Returns the reference (CEX mid-market or multi-venue TWAP) price of an asset.
get_pool_price(pool, block, tx_index) → number
Data sources: dex_trades, contract_storage
Returns the spot price at a specific tx position within a block.
compute_output_at_price(amount_in, price) → number
Pure function: computes swap output given input amount and price.
compute_price_move(tx, pool) → number
Data sources: dex_trades
Computes the price impact of a specific transaction on a pool.
get_twap(pool, from_block, to_block) → number
Data sources: dex_trades
Time-weighted average price over a block range.
get_cumulative_volume(pool, from_block, to_block) → number
Data sources: dex_trades
get_first_trade_block(pool, reference_block) → integer
Data sources: dex_trades
Returns the block of the first trade in the pool on/before the reference block.
simulate_swap(pool, token_in, token_out, amount_in, price_limit) → number
Data sources: simulation_environment
Returns output amount for a simulated swap at the given price limit.
simulate_swap_at_current_price(pool, token_in, token_out, amount_in) → number
Data sources: simulation_environment, dex_trades
compute_slippage(amount_out_min, amount_in, reserves) → number
Pure function: computes the effective slippage parameter from raw values.
compute_slippage_band(swap) → object
Pure function: computes the full slippage band (min_output, expected_output, band_width)
from a swap's parameters.
compute_recent_volatility(pool, lookback_blocks) → number
Data sources: dex_trades
Standard deviation of log returns over the lookback window.
11.6 Simulation
simulate_call(contract, function_signature, args) → call_result
Data sources: simulation_environment
Executes a call against a forked chain state. Returns {succeeded: boolean, return_data: any}.
simulate_staticcall(target, input_calldata, block) → value
Data sources: simulation_environment
Executes a staticcall at a given block height. Returns the return value.
simulate_transfer(amount, from, to) → transfer_result
Data sources: simulation_environment
Simulates a token transfer and returns {fee: number, success: boolean}.
random_invalid_message(bridge) → message
Data sources: contract_bytecode
Generates a structurally-valid but unverifiable message for smoke testing.
chain_fork(block) → context_manager
Data sources: simulation_environment
Creates a sandboxed fork at the given block. Use with `with chain_fork(block):`.
11.7 Static analysis (bytecode/source)
find_external_calls_before_guard_write(bytecode) → list<call_site>
Data sources: contract_bytecode
Returns all CALL/DELEGATECALL/STATICCALL sites reachable before the
SSTORE that writes the initializer guard flag.
classify_target(call_site_address, implementation_address) → string
Data sources: contract_bytecode
Returns "user_supplied", "known_protocol", "zero_address", or "delegatecall_target".
has_external_call_surface(facet) → boolean
Data sources: contract_bytecode
True if the facet bytecode contains CALL, DELEGATECALL, or STATICCALL opcodes.
has_token_transfer_logic(facet) → boolean
Data sources: contract_bytecode
True if the facet bytecode contains transfer/transferFrom/approve selector pushes.
has_delegatecall_forwarding(facet) → boolean
Data sources: contract_bytecode
has_selfdestruct_capability(facet) → boolean
Data sources: contract_bytecode
reentrancy_surface_score(contract) → integer (0-5)
Data sources: contract_bytecode
Scores a contract's reentrancy exposure including staticcall paths.
resolve_modifier_chain(selector) → gate | None
Data sources: contract_bytecode, contract_source
Traces the modifier chain guarding a function. Returns None if ungated.
resolve_authority_addresses(gate) → list<address>
Data sources: contract_bytecode, authority_graph
Resolves the set of addresses that control a given access gate.
11.8 Address and entity attribution
address_cluster_distance(address_a, address_b) → number (0.0–1.0)
Data sources: funder_graph
Returns the clustering distance between two addresses (0=identical entity, 1=unrelated).
shared_funding(address_set, hops) → boolean
Data sources: funder_graph
True if all addresses in the set share a common funder within `hops` hops.
count_same_deployer_interactions(sniper, deployer) → integer
Data sources: dex_trades, token_deployment_events
count_distinct_legal_entities(validators) → integer
Data sources: validator_set_attestation
Resolves each validator address to a legal entity; returns distinct entity count.
resolve_entity(address) → entity | null
Data sources: validator_set_attestation, funder_graph
Maps an address to a known legal entity or cluster label.
count_stake_controlled_by_entity(entity, validator_set) → number
Data sources: validator_set_attestation
11.9 Amount extraction and estimation
estimate_max_extractable_value(swap, slippage_tolerance) → number
Pure function: amount_in × slippage × price.
estimate_gas_cost(pending_swap) → number
Pure function: estimates the gas cost of a transaction in USD.
estimate_slippage_band_extraction(front_run, victim_swap, back_run) → number
Pure function: computes the attacker's profit from a slippage-band sandwich.
estimate_value_at_risk(stale_value, settled_value, action, frame) → number
Data sources: lending_protocol_state
Estimates the USD value at risk from a stale read consumed for `action`.
compute_loss_from_stale_read(read, stale_value, settled_value) → number
Data sources: lending_protocol_state
11.10 Classification and detection
is_token_buy(tx, token) → boolean
Data sources: dex_trades, tx_call_trace
is_token_sell(tx, token) → boolean
Data sources: dex_trades, tx_call_trace
creates_new_pool(tx) → boolean
Data sources: tx_call_trace, contract_events
True if the transaction deploys a new DEX liquidity pool.
is_consumed_for_value_transfer(protocol, staticcall_result) → boolean
Data sources: tx_call_trace, contract_bytecode
True if the staticcall result influences a value-transfer decision in the protocol.
classify_bundle(front_tx, add_liq_tx, back_tx) → string
Data sources: bundle_data, mempool_transaction
Returns "atomic_bundle", "mempool_snipe", or "coincidence".
detect_sandwich_around_addLiquidity(trades, creation_tx) → object | null
Data sources: dex_trades
Returns {sniper, profit, bundle_type} or null if no sandwich detected.
detect_queue_front_running(queue) → boolean
Data sources: withdrawal_queue_state, avs_event_feed
True if addresses deposit immediately before the withdrawal queue closes.
detect_queue_anomalies(queue, lookback_blocks) → list<anomaly>
Data sources: withdrawal_queue_state
correlate_with_avs_events(actions) → number (0.0–1.0)
Data sources: avs_event_feed
Returns the correlation between a set of actions and AVS event timing.
check_mempool_front_run(swap) → boolean
Data sources: mempool_transaction, bundle_data
True if a front-run transaction was observed in the mempool before the swap.
estimate_mempool_sandwich_probability(swap_request) → number
Data sources: mempool_transaction
Returns estimated probability (0-1) that a pending swap will be sandwiched.
recommended_slippage(swap_request) → number
Data sources: dex_trades
Computes the recommended slippage tolerance based on pool depth and volatility.
correlate_swap_paths(swaps) → number (0.0–1.0)
Data sources: tx_call_trace
Returns the route correlation between multiple swaps (1.0=same multi-hop route).
find_common_attacker(swaps) → address | null
Data sources: tx_call_trace, bundle_data
find_sandwich_pairs(bundle) → list<(front, victim, back)>
Data sources: bundle_data, tx_call_trace
canonical_form(args) → hash
Pure function: returns a canonical structural hash of call args, ignoring
addresses/specific values — used for template matching.
11.11 Bridge and cross-chain
query_louper(diamond_address) → set<facet>
Data sources: contract_bytecode, contract_storage
Enumerates all facets and their function selectors via IDiamondLoupe.
load_audit_report_facets(audit_report) → set<facet>
Data sources: audit_report_feed
Returns the set of facets listed in the audit report.
load_audit_report_selectors(audit_report) → map<selector, facet_info>
Data sources: audit_report_feed
find_supplemental_audit(diamond, facet_address) → audit_report | null
Data sources: audit_report_feed
compute_bridge_tvl(bridge) → number
Data sources: contract_storage, token_prices
compute_annualized_yield(rewards, stake) → number
Pure function: annualizes a validator's reward stream.
get_authorized_relayers(executor_address) → list<address>
Data sources: contract_storage, relayer_authorization_events
get_governance_timelock(chain, governance_contract) → duration
Data sources: governance_events, contract_storage
compute_state_diff(contract, pre=frame, post=frame) → map<string, change>
Pure function: diffs the contract state before and after two call frames.
is_atomic_deploy_and_init(tx) → boolean
Data sources: contract_deployment, tx_call_trace
True if the transaction both deploys and initializes a proxy in one atomic call.
decode_diamond_cut_facets(calldata) → list<facet>
Data sources: contract_events
Decodes a DiamondCut event's facet addition data.
decode_diamond_cut_action(calldata) → string
Data sources: contract_events
Returns "Add", "Replace", or "Remove".
decode_swap_output(receipt) → number
Data sources: tx_call_trace, dex_trades
get_dex_spot_price(pool, quote_asset) → number
Data sources: dex_trades, contract_storage
11.12 Statistical
stdev(series) → number
Population standard deviation.
pearson_r(series_a, series_b) → number (−1.0 to 1.0)
Pearson correlation coefficient.
gini(values) → number (0.0 to 1.0)
Gini coefficient: 2×Σ(i×sorted[i])/(n×Σsorted) − (n+1)/n.
mean(values) → number
Arithmetic mean.
11.13 Cryptographic
keccak256(bytes...) → hash
Keccak-256 hash of the concatenated inputs.
11.14 Off-chain (T11-specific)
These functions reference data sources that do not yet exist as structured feeds. They are formally specified so that implementors can build the feeds.
detect_seed_solicitation_patterns(binary, patterns) → boolean
Data sources: contract_bytecode (companion app binary analysis)
Scans a binary for UI strings matching seed-phrase solicitation patterns.
verify_published_checksum(binary, vendor, version) → boolean
Data sources: breach_disclosure_feed (vendor-published checksums)
execute_vendor_crypto_challenge(device) → attestation
Data sources: contract_bytecode (vendor attestation protocol)
Returns {passed: boolean, se_attestation_ok, firmware_hash, ...}.
detect_seal_reapplication(packaging) → boolean
Data sources: breach_disclosure_feed (vendor packaging specifications)
analyze_domain_content(domain) → content_signals
Data sources: domain_registration, certificate_transparency_log
Returns {serves_seed_solicitation_ui, impersonates_vendor_branding,
has_firmware_update_template, has_qr_code_flow}.
match_vendor_branding(claimed_sender) → vendor | null
Data sources: breach_disclosure_feed (vendor brand asset registry)
cluster_by_time_and_channel(campaigns, domains, solicitations, window) → list<cluster>
Pure function: clusters alerts by time proximity and channel overlap.
estimate_overlap(cluster) → number
Pure function: estimates victim-set intersection across a correlated cluster.
estimate_crypto_user_fraction(demographics, service_type) → number
Data sources: breach_disclosure_feed
compute_brute_force_completion_curve(hash_scheme, iterations, entropy, hashrate) → curve
Pure function: expected cumulative password-cracking completion over time.
cumulative_drain_curve(drain_events, from_date) → curve
Data sources: tx_call_trace
Observed cumulative drained value plotted against time since breach.
find_first_cloud_backup_drain(wallet) → event | null
Data sources: breach_disclosure_feed, tx_call_trace
interview_exclusion(victim, attack_vectors) → set<attack_vector>
Data sources: breach_disclosure_feed (victim interview data)
identify_common_service(victim_cohort) → service | null
Data sources: breach_disclosure_feed
score_physical_access_risk(travel, border_crossings, accommodation, storage) → number
Pure function: computes a physical-access risk score (0.0–1.0).
classify_device_vulnerability(device) → classification
Data sources: breach_disclosure_feed (hardware vulnerability database)
Returns {class_name, exposure_multiplier}.
estimate_zxcvbn_entropy(passphrase) → number
Pure function: estimates BIP39 passphrase entropy using zxcvbn algorithm.
rank_by_cloud_exposure(wallets) → ranking
Pure function: ranks wallets by cloud-exposure score.
cloud_exposure_score(wallet) → integer
Pure function: estimated number of users exposed to cloud-backup risk.
12. Parameter binding
Every spec declares parameters with type and default:
parameters:
deviation_threshold: { type: number, default: 0.02 }
dispute_window_blocks: { type: integer, default: 1800 }
Inside pseudocode, parameters are accessed as bare identifiers (e.g., deviation_threshold).
The implementation layer MUST:
- Accept parameter overrides per chain (e.g.,
deviation_threshold_polygon: 0.05). - Validate parameter type against the declared
typefield. - Apply the default when no override is provided.
13. Conformance levels
| Level | Requirement |
|---|---|
| L1 — Parseable | Pseudocode parses against the EBNF grammar without errors. |
| L2 — Type-checked | All variable bindings, function calls, and emit() arguments are consistent with declared types. Parameters match their declared types. |
| L3 — Bound | Every data-source reference is bound to a concrete provider. Every built-in function has an implementation. |
| L4 — Calibrated | All parameters have chain-specific calibrations validated against known-positive incidents. |
| L5 — Tested | The spec's positive fixtures pass against a test harness; negative fixtures do not produce false positives. |
Current state of the OAK spec corpus: L1 (parseable) for most specs; no automated L2-L5 validation exists.
14. Versioning
PLS versions track the spec corpus evolution. Breaking changes (grammar changes that would make existing pseudocode unparseable) require a major version bump.
| PLS version | OAK version | Changes |
|---|---|---|
| 0.1.0 | v0.2.0 | Initial formal grammar, type system, built-in catalog extracted from 134 specs |
Appendix A: Operator precedence (lowest to highest)
if/else(ternary conditional)orandnot=≠<>≤≥∈∉∩∪−+−×/%−(unary negation).?.[]()(postfix)
15. LLM Translation Guide
15.1 Philosophy
OAK does not mandate a single transpiler or target language. Instead, the language specification is designed so that any capable LLM can translate pseudocode into production code in the implementor's language of choice (Python, TypeScript, Rust, Go, SQL).
The three artefacts an LLM needs for accurate translation:
- This PLS document — grammar, types, control flow semantics
- The DSR (Data Source Schema Registry) — concrete schemas for every
data_source - The target spec's YAML — pseudocode + parameters + output_alert
15.2 Translation prompt template
You are an expert detection engineer translating an OAK detection spec into
production code. You have three documents for context:
1. OAK Pseudocode Language Specification (PLS v0.1.0) — grammar, types, built-in functions
2. OAK Data Source Schema Registry (DSR v0.1.0) — schemas for all data sources
3. The target spec YAML — pseudocode, parameters, output_alert
Your task: translate the pseudocode from the spec into {TARGET_LANGUAGE}.
RULES:
1. Preserve all PATHs as separate functions/classes. Each PATH = one detector.
2. Preserve helper functions as private/static methods.
3. Every built-in function call MUST be implemented. Use the DSR schemas to
understand the data shape. If a built-in function has no existing
implementation, generate a stub with the correct signature and a // TODO
comment referencing the DSR schema.
4. Parameters become constructor arguments or config fields with the spec's
default values.
5. emit(PATH_X, ...) calls become structured alert objects matching the
spec's output_alert field.
6. Data-source iteration (for each ... in DATA_SOURCE) binds to a provider
interface. Generate the interface. The implementor wires the concrete
provider.
7. "Pure function" built-ins are implemented directly (no external deps).
8. Statistical built-ins (stdev, pearson_r, gini) may use standard libraries.
9. with chain_fork() blocks become try/finally with sandbox setup/teardown.
10. String interpolation in guidance uses the target language's native
formatting (f-strings, template literals, format!).
11. Add type annotations appropriate for {TARGET_LANGUAGE}.
12. Generate unit test stubs for each positive fixture slug.
Output: a single {TARGET_LANGUAGE} file with all PATHs, helpers, data-source
interfaces, and test stubs. Include import statements.
15.3 Construct mapping table
For each pseudocode construct, the LLM should produce the target-language equivalent. This table provides the canonical mapping.
| Pseudocode | Python | TypeScript | Rust |
|---|---|---|---|
x ← expr |
x = expr |
const x = expr |
let x = expr; |
for each x in coll: |
for x in coll: |
for (const x of coll) { |
for x in &coll { |
for each x in coll where cond: |
for x in coll: if not cond: continue |
for (const x of coll) { if (!cond) continue; |
`for x in coll.iter().filter( |
if cond: ... elif: ... else: |
if cond: ... elif: ... else: |
if (cond) { ... } else if { ... } else { |
if cond { ... } else if { ... } else { |
value1 if cond else value2 |
value1 if cond else value2 |
cond ? value1 : value2 |
if cond { value1 } else { value2 } |
A ∩ B |
A & B (set) / A.intersection(B) |
new Set([...A].filter(x => B.has(x))) |
a.intersection(&b) (HashSet) |
A ∪ B |
`A | B` (set) | new Set([...A, ...B]) |
A − B |
A - B (set) |
new Set([...A].filter(x => !B.has(x))) |
a.difference(&b) (HashSet) |
x ∈ coll |
x in coll |
coll.has(x) / coll.includes(x) |
coll.contains(&x) |
x ∉ coll |
x not in coll |
!coll.has(x) |
!coll.contains(&x) |
∅ |
set() / [] |
new Set() / [] |
HashSet::new() / vec![] |
[expr for v in coll] |
[expr for v in coll] |
coll.map(v => expr) |
`coll.iter().map( |
[expr for v in coll if cond] |
[expr for v in coll if cond] |
coll.filter(v => cond).map(v => expr) |
`coll.iter().filter( |
{expr for v in coll} |
{expr for v in coll} |
new Set(coll.map(v => expr)) |
`coll.iter().map( |
len(coll) |
len(coll) |
coll.length / coll.size |
coll.len() |
sum(expr for v in coll) |
sum(expr for v in coll) |
coll.reduce((s, v) => s + expr, 0) |
`coll.iter().map( |
any(cond for v in coll) |
any(cond for v in coll) |
coll.some(v => cond) |
`coll.iter().any( |
all(cond for v in coll) |
all(cond for v in coll) |
coll.every(v => cond) |
`coll.iter().all( |
max(coll) / min(coll) |
max(coll) / min(coll) |
Math.max(...coll) / Math.min(...coll) |
coll.iter().max() / coll.iter().min() |
abs(x) |
abs(x) |
Math.abs(x) |
x.abs() |
obj?.field |
obj.field if obj else None |
obj?.field |
obj.as_ref()?.field |
sorted(coll, by=expr) |
sorted(coll, key=lambda v: expr) |
coll.sort((a,b) => expr_a - expr_b) |
`coll.sort_by( |
N..M |
range(N, M+1) |
Array.from({length: M-N+1}, (_,i) => N+i) |
N..=M |
coll[−n:] |
coll[-n:] |
coll.slice(-n) |
&coll[coll.len()-n..] |
15.4 Built-in function implementation patterns
Built-in functions fall into three implementation classes:
Class P — Pure functions (implement directly):
# Pseudocode: stdev(series) → number
def stdev(series: list[float]) -> float:
n = len(series)
mean = sum(series) / n
return (sum((x - mean) ** 2 for x in series) / n) ** 0.5
# Pseudocode: gini(values) → number
def gini(values: list[float]) -> float:
sv = sorted(values)
n = len(sv)
return (2 * sum((i + 1) * v for i, v in enumerate(sv)) / (n * sum(sv))) - (n + 1) / n
Class D — Data-source dependent (generate interface + provider binding):
# Pseudocode: get_call_trace(tx_hash) → tx_trace
# DSR schema: tx_call_trace (see DSR.md §2.2)
# Interface:
class TxTraceProvider(Protocol):
def get_call_trace(self, tx_hash: str) -> CallTrace: ...
# Concrete binding (Erigon):
class ErigonTraceProvider(TxTraceProvider):
def __init__(self, rpc_url: str):
self.rpc = Web3(HTTPProvider(rpc_url))
def get_call_trace(self, tx_hash: str) -> CallTrace:
raw = self.rpc.provider.make_request("trace_transaction", [tx_hash])
return CallTrace.from_rpc(raw)
Class S — Simulation (forked environment, heavyweight):
# Pseudocode: with chain_fork(block):
# Implementation: Tenderly VirtualNet or Anvil fork
class ChainFork:
def __init__(self, block_number: int, rpc_url: str):
self.fork_id = tenderly.create_fork(block_number)
def __enter__(self): return self
def __exit__(self, *args): tenderly.delete_fork(self.fork_id)
def simulate_call(self, contract, fn, args):
return tenderly.simulate(self.fork_id, contract, fn, args)
15.5 Data source provider interface pattern
Every data source referenced in a spec must be translated to a provider interface. The LLM generates the interface; the implementor wires the concrete provider.
# Generated from data_sources: [dex_trades, tx_call_trace, mempool_transaction]
from abc import ABC, abstractmethod
class DexTradesProvider(ABC):
"""DSR §2.4 — DEX swap events."""
@abstractmethod
def get_trades_in_blocks(self, pool: str, from_block: int, max_blocks: int) -> list[Trade]: ...
@abstractmethod
def get_twap(self, pool: str, from_block: int, to_block: int) -> float: ...
@abstractmethod
def get_cumulative_volume(self, pool: str, from_block: int, to_block: int) -> float: ...
class TxTraceProvider(ABC):
"""DSR §2.2 — Transaction call traces."""
@abstractmethod
def get_call_trace(self, tx_hash: str) -> CallTrace: ...
@abstractmethod
def find_outermost_frame(self, trace: CallTrace, target: str) -> Frame | None: ...
class MempoolProvider(ABC):
"""DSR §2.17 — Pending transaction pool."""
@abstractmethod
def get_pending_swaps(self, pool: str) -> list[PendingSwap]: ...
@abstractmethod
def check_front_run(self, swap: ExecutedSwap) -> bool: ...
15.6 emit() translation pattern
Every emit(PATH_X, ...) call becomes a structured alert object. The target
language representation depends on the runtime environment:
# emit(PATH_A, oracle=O, deviation=dev(O,b), severity="high")
# becomes:
@dataclass
class OracleDeviationAlert:
oak_technique: str = "OAK-T9.001"
detection_path: str = "PATH_A"
severity: str # "critical" | "high" | "medium" | "low"
chain: str
oracle_address: str
asset: str
deviation: float
reference_sources: list[str]
actor: str | None
tx: str | None
evidence: dict
def emit(self):
"""Publish to alerting pipeline."""
logger.info(f"OAK {self.oak_technique} {self.detection_path}: "
f"{self.severity} — {self.asset} deviation {self.deviation:.2%}")
# Kafka, Webhook, SIEM, Slack, etc.
15.7 Guidance string interpolation
The guidance field in emit() uses f-string-style interpolation with
variables from the current scope. The LLM translates this to the target
language's native string formatting:
# Pseudocode:
# guidance="Oracle {O} deviation {dev(O,b):.2%} exceeds threshold."
# Python:
guidance = f"Oracle {oracle} deviation {dev(oracle, block):.2%} exceeds threshold."
# TypeScript:
const guidance = `Oracle ${oracle} deviation ${(dev(oracle, block) * 100).toFixed(2)}% exceeds threshold.`;
# Rust:
let guidance = format!("Oracle {} deviation {:.2}% exceeds threshold.", oracle, dev(oracle, block) * 100.0);
15.8 PATH independence translation
Each PATH becomes an independent function or class. They share helper functions and parameter config but do not share mutable state:
class T9001_OraclePriceManipulation:
"""OAK-T9.001: Oracle Price Manipulation detection."""
def __init__(self, params: OracleParams, providers: OracleProviders):
self.params = params
self.providers = providers
def path_a_deviation(self, oracle: Oracle, block: int) -> list[OracleDeviationAlert]:
"""PATH A — oracle vs reference deviation, sustained."""
...
def path_b_cross_action(self, tx: Transaction) -> list[CrossActionAlert]:
"""PATH B — same-tx input-move + price-consume by one cluster."""
...
def path_c_vault_share(self, vault: Vault, block: int) -> list[VaultShareAlert]:
"""PATH C — vault-share invariant (donation-driven pricePerShare)."""
...
def path_d_dispute_window(self, event: OracleEvent) -> list[DisputeWindowAlert]:
"""PATH D — pull-oracle dispute-window respect."""
...
15.9 Complete worked example: T9.001 PATH A
Input pseudocode (from spec):
dev(O, b) := |O.price(b) − reference_price(O.asset, b, reference_venues)|
/ reference_price(O.asset, b, reference_venues)
# PATH A — oracle vs reference deviation, sustained
for each oracle O consumed by protocol P at block b:
if all(dev(O, b−i) > deviation_threshold for i in 0..min_blocks-1):
emit(PATH_A, oracle=O, deviation=dev(O,b), severity=high)
LLM output (Python):
from dataclasses import dataclass
from typing import Protocol, Iterable
# --- Parameters (from spec YAML) ---
@dataclass
class T9001Params:
deviation_threshold: float = 0.02
min_blocks: int = 1
reference_venues: list[str] = field(default_factory=lambda: ["cex_mid", "multi_venue_twap_30m"])
# --- Provider interfaces (from DSR schemas) ---
class OracleFeedProvider(Protocol):
"""DSR §2.19 — Oracle-reported values."""
def get_price(self, oracle_address: str, block: int) -> float: ...
def get_oracles_for_protocol(self, protocol_address: str) -> list[str]: ...
class ReferencePriceProvider(Protocol):
"""DSR §2.14 — Reference (CEX mid-market / multi-venue TWAP) prices."""
def reference_price(self, asset: str, block: int, venues: list[str]) -> float: ...
class OracleProtocolIndex(Protocol):
"""Maps protocol addresses to the oracles they consume."""
def get_consumed_oracles(self, protocol_address: str) -> list[tuple[str, str]]: ...
# returns [(oracle_address, asset), ...]
# --- Alert dataclass (from spec output_alert) ---
@dataclass
class OracleDeviationAlert:
oak_technique: str = "OAK-T9.001"
detection_path: str = "PATH_A"
severity: str = "high"
chain: str = ""
oracle_address: str = ""
asset: str = ""
deviation: float = 0.0
reference_sources: list[str] = field(default_factory=list)
actor: str | None = None
tx: str | None = None
evidence: dict = field(default_factory=dict)
# --- Helper (pure function) ---
def dev(
oracle_address: str,
block: int,
asset: str,
ref_provider: ReferencePriceProvider,
oracle_provider: OracleFeedProvider,
venues: list[str],
) -> float:
oracle_price = oracle_provider.get_price(oracle_address, block)
ref_price = ref_provider.reference_price(asset, block, venues)
if ref_price == 0:
return float("inf")
return abs(oracle_price - ref_price) / ref_price
# --- PATH A detector ---
def path_a_oracle_deviation(
protocol_address: str,
current_block: int,
params: T9001Params,
oracle_provider: OracleFeedProvider,
ref_provider: ReferencePriceProvider,
protocol_index: OracleProtocolIndex,
) -> list[OracleDeviationAlert]:
"""PATH A — oracle vs reference deviation, sustained."""
alerts: list[OracleDeviationAlert] = []
oracles = protocol_index.get_consumed_oracles(protocol_address)
for oracle_address, asset in oracles:
# Check sustained deviation across min_blocks
sustained = all(
dev(oracle_address, current_block - i, asset,
ref_provider, oracle_provider, params.reference_venues)
> params.deviation_threshold
for i in range(params.min_blocks)
)
if sustained:
current_deviation = dev(
oracle_address, current_block, asset,
ref_provider, oracle_provider, params.reference_venues,
)
alerts.append(OracleDeviationAlert(
severity="high",
oracle_address=oracle_address,
asset=asset,
deviation=current_deviation,
reference_sources=params.reference_venues,
))
return alerts
LLM output (TypeScript):
// --- Parameters ---
interface T9001Params {
deviationThreshold: number; // default: 0.02
minBlocks: number; // default: 1
referenceVenues: string[]; // default: ["cex_mid", "multi_venue_twap_30m"]
}
// --- Provider interfaces ---
interface OracleFeedProvider {
getPrice(oracleAddress: string, block: number): Promise<number>;
getOraclesForProtocol(protocolAddress: string): Promise<string[]>;
}
interface ReferencePriceProvider {
referencePrice(asset: string, block: number, venues: string[]): Promise<number>;
}
// --- Alert type ---
interface OracleDeviationAlert {
oakTechnique: "OAK-T9.001";
detectionPath: "PATH_A";
severity: "critical" | "high" | "medium" | "low";
chain: string;
oracleAddress: string;
asset: string;
deviation: number;
referenceSources: string[];
}
// --- Helper ---
async function dev(
oracleAddress: string, block: number, asset: string,
oracleP: OracleFeedProvider, refP: ReferencePriceProvider,
venues: string[],
): Promise<number> {
const oraclePrice = await oracleP.getPrice(oracleAddress, block);
const refPrice = await refP.referencePrice(asset, block, venues);
if (refPrice === 0) return Infinity;
return Math.abs(oraclePrice - refPrice) / refPrice;
}
// --- PATH A detector ---
async function pathAOracleDeviation(
protocolAddress: string, currentBlock: number,
params: T9001Params,
oracleP: OracleFeedProvider, refP: ReferencePriceProvider,
protocolIndex: { getConsumedOracles(addr: string): Promise<[string, string][]> },
): Promise<OracleDeviationAlert[]> {
const alerts: OracleDeviationAlert[] = [];
const oracles = await protocolIndex.getConsumedOracles(protocolAddress);
for (const [oracleAddress, asset] of oracles) {
const deviations = await Promise.all(
Array.from({ length: params.minBlocks }, (_, i) =>
dev(oracleAddress, currentBlock - i, asset, oracleP, refP, params.referenceVenues))
);
const sustained = deviations.every(d => d > params.deviationThreshold);
if (sustained) {
alerts.push({
oakTechnique: "OAK-T9.001",
detectionPath: "PATH_A",
severity: "high",
chain: "",
oracleAddress,
asset,
deviation: deviations[0],
referenceSources: params.referenceVenues,
});
}
}
return alerts;
}
15.10 Translation quality checklist
Before accepting an LLM-generated translation, verify:
- All PATHs present. PATH_A through PATH_E each have a corresponding function/method.
- Helper functions translated. Every
func_name(...) := ...appears as a private function. - Parameters externalized. All spec parameters are configurable, not hardcoded.
- Data sources abstracted. Every data source is behind an interface (not a concrete import).
- emit() → alert objects. Every emit call produces a typed alert, not a print statement.
- Guidance strings format-correct. String interpolation uses the target language's native formatting.
- Set operations correct.
∩/∪/−/∈/∉map to the correct target-language equivalents. - Null safety.
∅andnullchecks are present;?.optional chaining is preserved. - Type annotations present. All function signatures have type annotations.
- Test stubs generated. Each positive fixture slug has a corresponding test function skeleton.
- No pseudocode left untranslated. Search for
←,≠,∈,for each— none should remain.
15.11 Common LLM failure modes and fixes
| Failure | Symptom | Fix in prompt |
|---|---|---|
Treating emit() as print() |
Alerts go to stdout instead of structured pipeline | Add rule: "emit() produces a structured alert object, not console output" |
| Hardcoding data-source access | requests.get("https://api.etherscan.io/...") |
Add rule: "Every data source must be behind an interface/protocol. The concrete provider is wired separately." |
Skipping where clause filter |
for x in coll: ... without filter |
Add rule: "for each x in coll where cond: is for x in coll: if not cond: continue" |
Translating ∅ to None |
∅ becomes None instead of empty collection |
Add mapping: ∅ in set context = empty set/array, not null |
| Inlining parameters | Magic numbers in code | Add rule: "Parameters become constructor args or config fields with spec defaults" |
Ignoring ?. (optional chaining) |
obj.field instead of obj?.field |
Add mapping: ?. must be preserved for null safety |
| Guidance interpolation: format specifiers | {divergence:.2%} becomes {divergence} |
Add rule: "Preserve format specifiers using the target language's equivalent" |
Appendix B: Unicode operator reference
| Glyph | Unicode | Name | Keybinding suggestion |
|---|---|---|---|
← |
U+2190 | LEFTWARDS ARROW | <- (digraph) |
≠ |
U+2260 | NOT EQUAL TO | != (digraph) |
≤ |
U+2264 | LESS-THAN OR EQUAL TO | <= (digraph) |
≥ |
U+2265 | GREATER-THAN OR EQUAL TO | >= (digraph) |
∈ |
U+2208 | ELEMENT OF | in (digraph) |
∉ |
U+2209 | NOT AN ELEMENT OF | !in (digraph) |
∩ |
U+2229 | INTERSECTION | & (digraph) |
∪ |
U+222A | UNION | ` |
∅ |
U+2205 | EMPTY SET | {} (digraph) |
− |
U+2212 | MINUS SIGN | - (digraph) |
× |
U+00D7 | MULTIPLICATION SIGN | * (digraph) |
→ |
U+2192 | RIGHTWARDS ARROW | -> (digraph) |