harb/onchain/script/backtesting/attacks/SCHEMA.md

199 lines
6.2 KiB
Markdown
Raw Permalink Normal View History

# 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.