# Attack File Schema (v1) Attack files use [JSON Lines](https://jsonlines.org/) format (`.jsonl`): one JSON object per line, each representing a single operation executed sequentially against a forked chain. ## Schema version Schema version is tracked in this document and referenced by a comment line at the top of each attack file: ```jsonl // schema-version: 1 ``` JSONL parsers that do not tolerate comment lines should skip lines starting with `//`. ## Consumers Two independent consumers execute attack files: | Consumer | Location | Context | |----------|----------|---------| | **AttackRunner** | `onchain/script/backtesting/AttackRunner.s.sol` | Foundry script (`forge script`), runs against a live Anvil fork | | **FitnessEvaluator** | `onchain/test/FitnessEvaluator.t.sol` | Foundry test (`forge test`), runs in-process against a fork | Both consumers read the same `.jsonl` files from this directory and interpret operations identically, with one exception noted under `burn_lp` below. ## Operations ### `buy` Swap WETH → KRK via SwapRouter. | Field | Type | Description | |-------|------|-------------| | `op` | `"buy"` | Operation identifier | | `amount` | string (wei) | WETH amount to swap | | `token` | string | Ignored (WETH assumed). Present for readability. | ```jsonl {"op":"buy","amount":"100000000000000000000","token":"WETH"} ``` ### `sell` Swap KRK → WETH via SwapRouter. | Field | Type | Description | |-------|------|-------------| | `op` | `"sell"` | Operation identifier | | `amount` | string (wei) or `"all"` | KRK amount to swap; `"all"` sells entire balance | | `token` | string | Ignored. Present for readability. | ```jsonl {"op":"sell","amount":"all","token":"KRK"} ``` ### `recenter` Call `LiquidityManager.recenter()` via the authorized recenter account. No additional fields. Emits a snapshot in AttackRunner. ```jsonl {"op":"recenter"} ``` ### `buy_recenter_loop` Batch N × (buy → recenter) cycles in a single op. Avoids per-step forge overhead for high-cycle attacks. | Field | Type | Description | |-------|------|-------------| | `op` | `"buy_recenter_loop"` | Operation identifier | | `count` | uint | Number of buy→recenter cycles | | `amount` | string (wei) | WETH amount per buy | ```jsonl {"op":"buy_recenter_loop","count":80,"amount":"100000000000000000000"} ``` ### `stake` Call `Stake.snatch()` to create a staking position. | Field | Type | Description | |-------|------|-------------| | `op` | `"stake"` | Operation identifier | | `amount` | string (wei) | KRK amount to stake | | `taxRateIndex` | uint | Raw tax-rate value passed to `Stake.snatch()` | ```jsonl {"op":"stake","amount":"1000000000000000000000","taxRateIndex":0} ``` ### `unstake` Call `Stake.exitPosition()`. | Field | Type | Description | |-------|------|-------------| | `op` | `"unstake"` | Operation identifier | | `positionId` | uint | 1-based index into staking positions created by prior `stake` ops in this run | ```jsonl {"op":"unstake","positionId":1} ``` ### `mint_lp` Add a Uniswap V3 LP position via the NonfungiblePositionManager (NPM). The minted NPM tokenId is stored internally (in insertion order) and can be referenced by subsequent `burn_lp` ops. | Field | Type | Description | |-------|------|-------------| | `op` | `"mint_lp"` | Operation identifier | | `tickLower` | int | Lower tick boundary | | `tickUpper` | int | Upper tick boundary | | `amount0` | string (wei) | token0 amount | | `amount1` | string (wei) | token1 amount | ```jsonl {"op":"mint_lp","tickLower":-100,"tickUpper":100,"amount0":"1000000","amount1":"1000000"} ``` ### `burn_lp` Remove a Uniswap V3 LP position (decreaseLiquidity + collect). **tokenId convention:** The position to burn is identified by a **1-based index** into the list of NPM tokenIds created by prior `mint_lp` ops in this attack run (index 1 = first `mint_lp`, index 2 = second, etc.). This indirection exists because raw NPM tokenIds vary depending on the fork block tip and would make attack files non-portable across fork blocks (see issue #614). > **Field-name divergence (consumer-specific):** > > | Consumer | JSON field | Semantics | > |----------|-----------|-----------| > | **AttackRunner** | `.positionIndex` | 1-based index into `_mintedLpTokenIds` | > | **FitnessEvaluator** | `.tokenId` | 1-based index into `_mintedNpmTokenIds` | > > Both fields carry the same semantic value (1-based mint_lp index), but the > JSON key differs. Attack files targeting AttackRunner must use `positionIndex`; > files targeting FitnessEvaluator must use `tokenId`. > > **Migration note:** A future unification should standardise on `positionIndex` > (the more descriptive name) and update FitnessEvaluator to match. Until then, > include both fields in attack files that need to work with both consumers: > ```jsonl > {"op":"burn_lp","positionIndex":1,"tokenId":1} > ``` ### `mine` Advance the block number (Anvil/VM only). | Field | Type | Description | |-------|------|-------------| | `op` | `"mine"` | Operation identifier | | `blocks` | uint | Number of blocks to advance | ```jsonl {"op":"mine","blocks":10} ``` ## Snapshot schema (AttackRunner output) AttackRunner emits a JSON-line snapshot to stdout after each `recenter` op: | Field | Description | |-------|-------------| | `seq` | Sequence counter | | `tick` | Current pool tick | | `lm_eth_free` | Free ETH in LiquidityManager | | `lm_weth_free` | Free WETH in LiquidityManager | | `lm_eth_total` | Total ETH in LiquidityManager | | `positions` | Object with `floor`, `anchor`, `discovery` sub-objects | | `vwap_x96` | VWAP in X96 format | | `vwap_tick` | VWAP as tick | | `outstanding_supply` | Outstanding KRK supply | | `total_supply` | Total KRK supply | | `optimizer_output` | Current optimizer parameters | | `adversary_eth` | Adversary ETH balance | | `adversary_krk` | Adversary KRK balance | ## Regenerating attack files When regenerating attack files (e.g. via the red-team agent): 1. Always include the `// schema-version: 1` header as the first line. 2. For `burn_lp` ops, use 1-based `mint_lp` indexes — never raw NPM tokenIds. 3. If the file must work with both AttackRunner and FitnessEvaluator, include both `positionIndex` and `tokenId` fields with the same value. 4. Test against both consumers after regeneration to catch field-name mismatches.