# dimensional-analysis

Annotates codebases with dimensional analysis comments documenting units, dimensions, and decimal scaling. Use when someone asks to annotate units in a codebase, perform a dimensional analysis, or find vulnerabilities in a DeFi protocol, offchain code, or other blockchain-related codebase with arithmetic. Prevents dimensional mismatches and catches formula bugs early.

- **Kind:** skill
- **Source:** https://github.com/trailofbits/skills
- **Page:** https://forefy.com/skills/e80b859d-b7cf-4f0b-8b47-14d7a4404acc
- **API (JSON + files):** https://forefy.com/api/skills/e80b859d-b7cf-4f0b-8b47-14d7a4404acc

---

## SKILL.md

---
name: dimensional-analysis
description: "Annotates codebases with dimensional analysis comments documenting units, dimensions, and decimal scaling. Use when someone asks to annotate units in a codebase, perform a dimensional analysis, or find vulnerabilities in a DeFi protocol, offchain code, or other blockchain-related codebase with arithmetic. Prevents dimensional mismatches and catches formula bugs early."
allowed-tools: Read Write Grep List Glob Task TodoRead TodoWrite
---

# Dimensional Analysis Skill

This skill orchestrates a dimensional-analysis pipeline for codebases that perform numeric computations with mixed units, precisions, or scaling factors. The main skill context is a workflow controller only: it delegates scanning, vocabulary discovery, annotation, propagation, and validation to specialized subagents, then manages batching, persistence, retries, coverage gates, and final reporting.

## When to Use

- Annotating a codebase with unit/dimension comments (e.g., `D18{tok}`, `D27{UoA/tok}`)
- Performing dimensional analysis on DeFi protocols, financial code, or scientific computations
- Hunting for arithmetic bugs caused by unit mismatches, missing scaling, or precision loss
- Auditing codebases with mixed decimal precisions or fixed-point arithmetic

## When NOT to Use

- Codebases with no numeric arithmetic or unit conversions — there is nothing to annotate
- Pure integer counting logic (loop indices, array lengths) with no physical or financial dimensions
- When you only need a quick spot-check of a single formula — read the code directly instead of running the full pipeline

## Execution Mode

This skill runs in one mode only: `full-auto`.
This is a workflow-based skill that delegates step-specific work to specialized agents via the `Task` tool. You orchestrate the overall process, manage coverage and state persistence, and ensure that every in-scope file is processed through each step of the pipeline.

- Always run the full pipeline in this order: Step 1 -> Step 2 -> Step 3 -> Step 4.
- The main skill context must not perform repository-wide dimensional analysis, annotation, propagation, or bug validation itself when a dedicated subagent exists for that step.
- The main skill context may inspect artifacts, manifests, and subagent outputs only as needed to route work, build prompts, persist state, and determine completion.
- Any mode argument provided by the caller is ignored.
- Report all results at the end in a single summary.

When you start a step, report it:

```text
Starting Step: Step {n}
```

## Scope and Coverage Guarantees

This skill must audit **all in-scope arithmetic files**, including large repositories.

- In-scope files are defined by Step 1 scanner output (`files` array), across **all** priority tiers (CRITICAL, HIGH, MEDIUM, LOW).
- If Step 1 narrows inputs for vocabulary discovery (for example, CRITICAL/HIGH only), that narrowing applies to discovery only. It **never** reduces annotation or validation scope.
- `arithmetic-scanner` persists the in-scope file manifest to `DIMENSIONAL_SCOPE.json` in the project root, and that manifest is the source of truth for Steps 2-4.
- A file is considered fully covered only when all three statuses are present:
  - `step2`: anchor annotation completed (or explicit no-anchor result)
  - `step3`: propagation completed (or explicit no-propagation result)
  - `step4`: validation completed
- `dimension-discoverer` persists the discovered dimensional vocabulary to `DIMENSIONAL_UNITS.md` in the project root for reuse by later steps and future runs.
- When a file ends in a terminal `BLOCKED` state, persist the blocking reason and retry count in `DIMENSIONAL_SCOPE.json` and reflect the same file in `coverage.unprocessed_files`.
- Do not finish while any in-scope file remains unprocessed in any step.

## Delegation Contract

- `arithmetic-scanner` owns repository scanning, arithmetic-file prioritization, and writing `DIMENSIONAL_SCOPE.json`.
- `dimension-discoverer` owns dimensional vocabulary discovery, unit inference, and writing `DIMENSIONAL_UNITS.md`.
- `dimension-annotator` owns annotation format decisions, anchor-point edits, and comment-writing behavior.
- `dimension-propagator` owns propagation logic, inferred annotations, and mismatch reporting during tracing.
- `dimension-validator` owns bug detection, red-flag evaluation, rationalization rejection, and confirmation or refutation of propagated mismatches.
- The main skill context must not substitute its own dimensional reasoning for skipped or unlaunched subagents. If a step requires specialized reasoning, launch the corresponding subagent.
- Use reference files as subagent support material. Pass them to the relevant step in prompts instead of treating them as instructions for the main skill context.

## Workflow

Follow these sections in order. Do not advance until the current step satisfies its completion gate.

### Shared Orchestration Rules

- `DIMENSIONAL_SCOPE.json` and `DIMENSIONAL_UNITS.md` live in the project root.
- The main skill context verifies Step 1 artifacts but does not write either Step 1 artifact itself.
- `DIMENSIONAL_SCOPE.json.in_scope_files` is the source of truth for Steps 2-4. Never derive later scope from discovery-only inputs.
- When a later step reaches terminal `BLOCKED`, persist the matching `step*_reason` and `step*_retry_count` fields on the file entry in `DIMENSIONAL_SCOPE.json`.
- `coverage.unprocessed_files` must be derived from terminal `BLOCKED` entries in `DIMENSIONAL_SCOPE.json` using `{ "path": "...", "blocked_step": "step2|step3|step4", "reason": "...", "retry_count": 1 }`.
- A step may retry a `BLOCKED` file once with a focused prompt. If it is still `BLOCKED`, keep the documented reason and continue. Do not finalize while any file remains `PENDING`.

### Step 1: Vocabulary and Scope Discovery

If cached artifacts cannot be reused, delegate repository scanning to `arithmetic-scanner` and vocabulary discovery to `dimension-discoverer`. Do not do that step-specific analysis directly in the main skill context.

1. Check whether `DIMENSIONAL_UNITS.md` and `DIMENSIONAL_SCOPE.json` already exist in the project root.
2. If both exist, read them and confirm:
   - `DIMENSIONAL_SCOPE.json.project_root` matches the current repo root
   - `DIMENSIONAL_SCOPE.json` contains `in_scope_files`, `discoverer_focus_files`, `recommended_discovery_order`, and per-file `step2`, `step3`, `step4` fields
   - `DIMENSIONAL_UNITS.md` is a usable dimensional vocabulary for this repo
3. If either artifact is stale, malformed, missing required structure, or clearly for another repo, discard reuse and rerun the rest of Step 1.
4. If both artifacts are valid, reuse them directly. If `in_scope_files` is empty, skip Steps 2-4 and produce final output with zero findings.
5. Otherwise use the `Task` tool to spawn the `arithmetic-scanner` agent. Its prompt must include:
   - project root path
   - absolute output path for `DIMENSIONAL_SCOPE.json`
   - instruction to write the Step 1 scope manifest to disk and return the same scope data in its report
6. The scanner owns Step 1 scope persistence. It must:
   - identify dimensional-arithmetic files and prioritize them as usual
   - write `DIMENSIONAL_SCOPE.json` with `project_root`, `in_scope_files`, `discoverer_focus_files`, and `recommended_discovery_order`
   - initialize every in-scope file with `step2: "PENDING"`, `step3: "PENDING"`, and `step4: "PENDING"`
   - still write an empty manifest when no arithmetic files are found
   - still narrow `discoverer_focus_files` to CRITICAL/HIGH when more than 50 arithmetic files are found, while keeping all priorities in `in_scope_files`
7. After the scanner completes, read `DIMENSIONAL_SCOPE.json` from disk and confirm it exists and contains the required Step 1 fields before continuing.
8. Use the `Task` tool to spawn the `dimension-discoverer` agent. Its prompt must include:
   - project root path
   - absolute path to `DIMENSIONAL_SCOPE.json`
   - absolute output path for `DIMENSIONAL_UNITS.md`
   - prioritized `discoverer_focus_files` with each file's path, priority, score, and category
   - `recommended_discovery_order`
9. The discoverer owns Step 1 vocabulary persistence. It must read `DIMENSIONAL_SCOPE.json` as the Step 1 source of truth and write `DIMENSIONAL_UNITS.md` with `Base Units`, `Derived Units`, and `Precision Prefixes` sections. If `in_scope_files` is empty, it must still write the same headings with empty sections.
10. Step 1 is complete only when both artifacts exist on disk, pass the reuse checks above, and correctly represent the zero-file case. If `in_scope_files` is empty after the discoverer writes `DIMENSIONAL_UNITS.md`, skip Steps 2-4 and produce final output with zero findings.

### Step 2: Anchor Annotation

The main skill context must not add annotations itself. Use the `Task` tool to spawn `dimension-annotator` agents for all anchor-point annotation work. For full examples and annotation format details, see `[{baseDir}/references/annotate.md]({baseDir}/references/annotate.md)`.

- Read `DIMENSIONAL_SCOPE.json` and build batches from `in_scope_files`. Every in-scope file, including MEDIUM and LOW priority files, must receive a Step 2 outcome.
- Batch files instead of spawning one agent per file:
  - `<= 10` files: one batch
  - `11-30` files: one batch per category
  - `> 30` files: one batch per category, splitting categories larger than 10 files into sub-batches of about 8 files
- Launch categories in Step 1 recommended discovery order: math libraries, then oracles, then core logic, then peripheral. Batches inside the same category may run in parallel.
- Before launching annotators, set `step2 = "PENDING"` for every in-scope file and persist the updated `DIMENSIONAL_SCOPE.json`.
- Each annotator prompt must include:
  - absolute path to `DIMENSIONAL_UNITS.md`
  - absolute path to `DIMENSIONAL_SCOPE.json`
  - assigned file paths in order
  - each file's category and matched patterns from scanner output
  - summary of previously annotated interfaces or types from earlier batches, when applicable
  - required per-file status output: `ANNOTATED`, `REVIEWED_NO_ANCHOR_CHANGES`, or `BLOCKED` plus a one-line justification
- After each batch, immediately persist each assigned file to exactly one Step 2 status:
  - `ANNOTATED`
  - `REVIEWED_NO_ANCHOR_CHANGES`
  - `BLOCKED`
- If a file is `BLOCKED`, also persist `step2_reason` and `step2_retry_count`. Retry each `BLOCKED` file once with a focused prompt.
- Do not continue to Step 3 while any file remains `PENDING` in on-disk manifest state.

### Step 3: Dimension Propagation

The main skill context must not perform propagation reasoning itself. Use the `Task` tool to spawn `dimension-propagator` agents to extend annotations through arithmetic, function calls, and assignments. For algebra details, see `[{baseDir}/references/dimension-algebra.md]({baseDir}/references/dimension-algebra.md)`.

- Read `DIMENSIONAL_SCOPE.json` and build propagation batches from `in_scope_files`. Every in-scope file must receive a Step 3 outcome.
- Use the same batching rules and category ordering as Step 2.
- Before launching propagators, confirm every file already has a non-pending Step 2 status.
- Then set `step3 = "PENDING"` for every in-scope file and persist the updated manifest.
- Each propagator prompt must include:
  - absolute path to `DIMENSIONAL_UNITS.md`
  - absolute path to `DIMENSIONAL_SCOPE.json`
  - assigned file paths in order
  - each file's category and matched patterns
  - summary of Step 2 anchor annotations for the assigned files and any upstream interfaces they depend on
  - required per-file status output: `PROPAGATED`, `REVIEWED_NO_PROPAGATION_CHANGES`, or `BLOCKED` plus a one-line justification
- After each batch, immediately persist each assigned file to exactly one Step 3 status:
  - `PROPAGATED`
  - `REVIEWED_NO_PROPAGATION_CHANGES`
  - `BLOCKED`
- If a file is `BLOCKED`, also persist `step3_reason` and `step3_retry_count`. Retry each `BLOCKED` file once with a focused prompt.
- After all propagators complete, aggregate:
  - annotations added by confidence level (`CERTAIN`, `INFERRED`, `UNCERTAIN`)
  - mismatches found, with severities for validator deduplication
  - coverage gaps that could not be inferred
- Do not continue to Step 4 while any file remains `PENDING` in on-disk manifest state.

### Step 4: Bug Detection

The main skill context must not perform bug detection itself. Use the `Task` tool to spawn `dimension-validator` agents to detect dimensional bugs in annotated code. For examples, red flags, rationalization checks, and standard vocabulary, see `[{baseDir}/references/bug-patterns.md]({baseDir}/references/bug-patterns.md)`, `[{baseDir}/references/common-dimensions.md]({baseDir}/references/common-dimensions.md)`, and `[{baseDir}/references/dimension-algebra.md]({baseDir}/references/dimension-algebra.md)`. DO NOT DETECT BUGS IN ANY OTHER STEP.

- Validate every file in `DIMENSIONAL_SCOPE.json.in_scope_files`.
- Use this priority order without skipping lower tiers:
  1. files with CRITICAL or HIGH Step 3 mismatches
  2. remaining CRITICAL and HIGH scanner-priority files
  3. remaining MEDIUM and LOW files
- Before launching validators, confirm every file already has a non-pending Step 3 status.
- Then set `step4 = "PENDING"` for every in-scope file and persist the updated manifest.
- Spawn one `dimension-validator` agent per file. For large repos, run them in waves of roughly 10-30 files to keep orchestration stable.
- Each validator prompt must include:
  - absolute path to `DIMENSIONAL_UNITS.md`
  - absolute path to `DIMENSIONAL_SCOPE.json`
  - the single file path to validate
  - a summary of anchor and propagated annotations in the file
  - Step 3 mismatch summaries for the file, including mismatch IDs
  - cross-file function signatures or return dimensions needed for call-boundary checks
  - required per-file status output: `VALIDATED` or `BLOCKED`
- After each wave, immediately persist each file to exactly one Step 4 status:
  - `VALIDATED`
  - `BLOCKED`
- If a file is `BLOCKED`, also persist `step4_reason` and `step4_retry_count`. Retry each `BLOCKED` file once with a focused prompt.
- Deduplicate findings:
  - confirmed Step 3 mismatches keep their original IDs and severities
  - refuted Step 3 mismatches are noted as false positives and excluded from final counts
  - genuinely new findings receive new `DIM-XXX` IDs
- Aggregate confirmed findings, new findings, refuted findings, coverage summary, and final `coverage.unprocessed_files`.
- Step 4 is complete only when `DIMENSIONAL_SCOPE.json.in_scope_files` contains no `step4: "PENDING"` entries.

## Reference Documentation

Pass these references to the relevant subagent when a step needs them:
- `[{baseDir}/references/dimension-algebra.md]({baseDir}/references/dimension-algebra.md)` - Propagator and validator algebra rules
- `[{baseDir}/references/common-dimensions.md]({baseDir}/references/common-dimensions.md)` - Validator vocabulary reference
- `[{baseDir}/references/bug-patterns.md]({baseDir}/references/bug-patterns.md)` - Validator bug-pattern and red-flag reference
- `[{baseDir}/references/annotate.md]({baseDir}/references/annotate.md)` - Annotator format and example reference

## Final Output

At the end of the analysis, provide a structured summary unless some other output format has been specified:

```json
{
  "mode": "full-auto",
  "project_root": "<path>",
  "vocabulary": {
    "base_units": ["..."],
    "derived_units": ["..."],
    "precision_prefixes": ["..."]
  },
  "annotations": {
    "total_added": 0,
    "by_file": {}
  },
  "findings": {
    "critical": 0,
    "high": 0,
    "medium": 0,
    "details": []
  },
  "uncertainties_resolved": 0,
  "coverage": {
    "in_scope_files": 0,
    "anchor_reviewed_files": "0/0",
    "propagation_reviewed_files": "0/0",
    "validation_reviewed_files": "0/0",
    "annotated_functions": "0/0",
    "annotated_variables": "0/0",
    "unprocessed_files": [
      {
        "path": "/path/to/repo/contracts/LegacyMath.sol",
        "blocked_step": "step3",
        "reason": "Parser could not process generated source",
        "retry_count": 1
      }
    ]
  }
}
```

## Completion Checklist

You are NOT done until all of these are true:

### File Coverage Gates
- [ ] `DIMENSIONAL_UNITS.md` exists in the project root
- [ ] `DIMENSIONAL_SCOPE.json` exists in the project root and is the source of truth for downstream coverage
- [ ] Every in-scope arithmetic file discovered in Step 1 appears in `DIMENSIONAL_SCOPE.json.in_scope_files`
- [ ] Every in-scope file has a non-`PENDING` Step 2 status (`ANNOTATED`, `REVIEWED_NO_ANCHOR_CHANGES`, or `BLOCKED`)
- [ ] Every in-scope file has a non-`PENDING` Step 3 status (`PROPAGATED`, `REVIEWED_NO_PROPAGATION_CHANGES`, or `BLOCKED`)
- [ ] Every in-scope file has a non-`PENDING` Step 4 status (`VALIDATED` or `BLOCKED`)
- [ ] No in-scope file remains `PENDING` in any step
- [ ] Any `BLOCKED` file has a documented reason in the final output
- [ ] `coverage.unprocessed_files` exactly matches the final set of terminal `BLOCKED` files after retries, using `path`, `blocked_step`, `reason`, and `retry_count`

### Summary Report
- [ ] Final summary JSON/report provided
- [ ] Final coverage counters match `DIMENSIONAL_SCOPE.json`
- [ ] List of modified files provided when edits occurred
- [ ] Any dimensional mismatches or bugs found are summarized
- [ ] Any remaining blocked or unprocessed files are called out with reasons

**If `DIMENSIONAL_SCOPE.json` and the final report disagree, reconcile the report or continue processing until they match.**
**Do not claim completion from agent intent alone; completion is determined by manifest coverage and final reported statuses.**

## agents

```

```

## agents/openai.yaml

```yaml
interface:
  icon_small: "assets/trail-of-bits-mark.svg"
  icon_large: "assets/trail-of-bits-mark.svg"
  brand_color: "#D83A34"
```

## assets

```

```

## assets/trail-of-bits-mark.svg

```

```

## references

```

```

## references/annotate.md

# Step 2: Annotate the Codebase

After defining dimensions (Step 1), add annotations to all numeric values in the code.

> **Note:** Examples below use Solidity syntax. For other languages, adapt the comment syntax (e.g., `//` in Rust, `#` in Python, `//` or `/** */` in TypeScript) while keeping the same dimensional annotation format.

---

## Comment Placement Patterns

### Variable Declarations

```solidity
// Inline (preferred for brevity)
uint256 public totalAssets;  // D18{UNDERLYING}

// Above (for longer descriptions)
/// D18{UNDERLYING} Total assets under management, excluding pending withdrawals
uint256 public totalAssets;
```

### Function Parameters (NatSpec)

```solidity
// NatSpec (preferred)
/// @param assets D18{UNDERLYING} Amount to deposit
/// @param receiver Address receiving shares
/// @return shares D18{SHARE} Shares minted
function deposit(uint256 assets, address receiver) external returns (uint256 shares);

// Inline (acceptable for simple cases)
function deposit(
    uint256 assets,   // D18{UNDERLYING}
    address receiver
) external returns (uint256 shares);  // D18{SHARE}
```

### Struct Fields

```solidity
struct Position {
    uint256 collateral;    // D18{COLLATERAL} Collateral deposited
    uint256 debt;          // D18{DEBT} Amount borrowed
    uint256 lastUpdate;    // {s} Timestamp of last interest accrual
}
```

### Formula Verification Comments

```solidity
// Show dimensional algebra for non-trivial formulas
//
// shares = assets * totalSupply / totalAssets
// {SHARE} = {UNDERLYING} * {SHARE} / {UNDERLYING}
//         = {SHARE} ✓
//
shares = assets.mulDiv(totalSupply(), totalAssets());
```

---

## Annotating State Variables

For each state variable holding a numeric value:

1. Determine its dimension from the glossary
2. Determine its decimal scaling (D6, D8, D18, D27, etc.)
3. Add inline comment in format: `// D{scale}{dimension} Description`

### Examples

```solidity
// Storage variables
uint256 public totalDeposits;     // D18{UNDERLYING} Total tokens deposited
uint256 public totalShares;       // D18{VAULT} Total vault shares outstanding
uint256 public lastPriceUpdate;   // {s} Timestamp of last oracle update
uint256 public feeRate;           // D18{1} Fee as 18-decimal fraction (1e18 = 100%)
uint256 public accumulatedFees;   // D18{UNDERLYING} Protocol fees collected

// Mappings
mapping(address => uint256) public userShares;      // D18{VAULT} per user
mapping(address => uint256) public userDebt;        // D18{DEBT} per user
mapping(address => uint256) public lastActionTime;  // {s} per user
```

---

## Annotating Function Signatures

For each public/external function:

1. Add NatSpec with dimensions for all numeric parameters
2. Add dimensions for return values
3. Format: `@param name D{scale}{dimension} Description`

### Examples

```solidity
/// @notice Deposit assets and receive shares
/// @param assets D18{UNDERLYING} Amount of tokens to deposit
/// @param minShares D18{VAULT} Minimum shares to receive (slippage protection)
/// @return shares D18{VAULT} Actual shares minted
function deposit(uint256 assets, uint256 minShares) external returns (uint256 shares);

/// @notice Get current price from oracle
/// @return price D8{USD/UNDERLYING} Current asset price
function getPrice() external view returns (uint256 price);

/// @notice Calculate health factor for a position
/// @param collateralValue D18{USD} Total collateral value in USD
/// @param debtValue D18{USD} Total debt value in USD
/// @return healthFactor D18{1} Health factor (>1e18 is healthy)
function calculateHealthFactor(
    uint256 collateralValue,
    uint256 debtValue
) external pure returns (uint256 healthFactor);
```

---

## Full Annotated Examples

### ERC-4626 Vault

```solidity
/**
 * ═══════════════════════════════════════════════════════════
 * DIMENSIONAL GLOSSARY
 * ═══════════════════════════════════════════════════════════
 *
 * Base Dimensions:
 * - {UNDERLYING}  The underlying asset token
 * - {SHARE}       Vault share token
 * - {s}           Time in seconds
 * - {1}           Dimensionless
 *
 * Derived Dimensions:
 * - {SHARE/UNDERLYING}  Exchange rate (shares per asset)
 * - {UNDERLYING/SHARE}  Inverse exchange rate
 */

contract Vault is ERC4626 {
    uint256 public totalAssets;      // D{X}{UNDERLYING} where X = token decimals
    uint256 public totalSupply;      // D18{SHARE}
    uint256 public lastHarvestTime;  // {s} Timestamp of last yield harvest
    uint256 public performanceFee;   // D18{1} Fee rate (1e18 = 100%)

    /// @notice Deposit assets and receive shares
    /// @param assets D{X}{UNDERLYING} Amount to deposit
    /// @return shares D18{SHARE} Shares minted
    function deposit(uint256 assets) external returns (uint256 shares) {
        // {SHARE} = {UNDERLYING} * {SHARE} / {UNDERLYING} = {SHARE} ✓
        shares = assets.mulDiv(totalSupply(), totalAssets());
    }

    /// @notice Withdraw assets by burning shares
    /// @param shares D18{SHARE} Shares to burn
    /// @return assets D{X}{UNDERLYING} Assets returned
    function withdraw(uint256 shares) external returns (uint256 assets) {
        // {UNDERLYING} = {SHARE} * {UNDERLYING} / {SHARE} = {UNDERLYING} ✓
        assets = shares.mulDiv(totalAssets(), totalSupply());
    }

    /// @notice Get current exchange rate
    /// @return rate D18{UNDERLYING/SHARE} Assets per share
    function exchangeRate() external view returns (uint256 rate) {
        // {UNDERLYING/SHARE} = {UNDERLYING} * 1e18 / {SHARE}
        rate = totalAssets().mulDiv(1e18, totalSupply());
    }
}
```

### AMM / DEX

```solidity
/**
 * ═══════════════════════════════════════════════════════════
 * DIMENSIONAL GLOSSARY
 * ═══════════════════════════════════════════════════════════
 *
 * Base Dimensions:
 * - {TOKEN_A}  First token in pair
 * - {TOKEN_B}  Second token in pair
 * - {LP}       Liquidity provider token
 * - {1}        Dimensionless
 *
 * Derived Dimensions:
 * - {TOKEN_A * TOKEN_B}  Constant product invariant
 * - {TOKEN_B / TOKEN_A}  Price of A in terms of B
 */

contract AMM {
    uint256 public reserveA;     // D{X}{TOKEN_A}
    uint256 public reserveB;     // D{Y}{TOKEN_B}
    uint256 public totalSupply;  // D18{LP}
    uint256 public kLast;        // {TOKEN_A * TOKEN_B} Last invariant value
    uint256 public swapFee;      // D18{1} Fee rate (e.g., 3e15 = 0.3%)

    /// @notice Swap token A for token B
    /// @param amountAIn D{X}{TOKEN_A} Amount of token A to swap
    /// @return amountBOut D{Y}{TOKEN_B} Amount of token B received
    function swapAForB(uint256 amountAIn) external returns (uint256 amountBOut) {
        // Calculate output using constant product formula
        // (reserveA + amountAIn) * (reserveB - amountBOut) = k
        //
        // amountBOut = reserveB - k / (reserveA + amountAIn)
        // {TOKEN_B} = {TOKEN_B} - {TOKEN_A * TOKEN_B} / {TOKEN_A}
        // {TOKEN_B} = {TOKEN_B} - {TOKEN_B} = {TOKEN_B} ✓
    }

    /// @notice Get current price of A in terms of B
    /// @return price D18{TOKEN_B / TOKEN_A} Price
    function getPrice() external view returns (uint256 price) {
        // {TOKEN_B / TOKEN_A} = {TOKEN_B} * 1e18 / {TOKEN_A}
        price = reserveB.mulDiv(1e18, reserveA);
    }

    /// @notice Add liquidity
    /// @param amountA D{X}{TOKEN_A} Amount of token A
    /// @param amountB D{Y}{TOKEN_B} Amount of token B
    /// @return lpTokens D18{LP} LP tokens minted
    function addLiquidity(uint256 amountA, uint256 amountB)
        external returns (uint256 lpTokens)
    {
        // LP tokens proportional to liquidity added
        // {LP} = {LP} * {TOKEN_A} / {TOKEN_A} = {LP} ✓
        lpTokens = totalSupply.mulDiv(amountA, reserveA);
    }
}
```

### Lending Protocol

```solidity
/**
 * ═══════════════════════════════════════════════════════════
 * DIMENSIONAL GLOSSARY
 * ═══════════════════════════════════════════════════════════
 *
 * Base Dimensions:
 * - {COLLATERAL}  Collateral token (e.g., WETH)
 * - {DEBT}        Borrowed token (e.g., USDC)
 * - {USD}         Oracle price denomination
 * - {aToken}      Receipt token for deposits
 * - {s}           Time in seconds
 * - {1}           Dimensionless
 *
 * Derived Dimensions:
 * - {USD/COLLATERAL}  Collateral price
 * - {USD/DEBT}        Debt token price
 * - {1/s}             Interest rate per second
 */

contract LendingPool {
    uint256 public totalBorrowed;      // D{X}{DEBT}
    uint256 public totalCollateral;    // D{Y}{COLLATERAL}
    uint256 public liquidationRatio;   // D18{1} (e.g., 1.5e18 = 150%)
    uint256 public borrowRate;         // D27{1/s} Interest rate per second
    uint256 public lastAccrualTime;    // {s} Last interest accrual timestamp

    struct Position {
        uint256 collateral;    // D{Y}{COLLATERAL} Collateral deposited
        uint256 debt;          // D{X}{DEBT} Amount borrowed
        uint256 lastUpdate;    // {s} Timestamp of last update
    }
    mapping(address => Position) public positions;

    /// @notice Calculate health factor for a position
    /// @param collateral D{Y}{COLLATERAL} Collateral amount
    /// @param collateralPrice D8{USD/COLLATERAL} Oracle price
    /// @param debt D{X}{DEBT} Debt amount
    /// @param debtPrice D8{USD/DEBT} Oracle price
    /// @return healthFactor D18{1} Health factor (>1e18 is healthy)
    function getHealthFactor(
        uint256 collateral,
        uint256 collateralPrice,
        uint256 debt,
        uint256 debtPrice
    ) external pure returns (uint256 healthFactor) {
        // collateralValue = collateral * collateralPrice / 1e8
        // D18{USD} = D{Y}{COLLATERAL} * D8{USD/COLLATERAL} / 1e8
        uint256 collateralValue = collateral.mulDiv(collateralPrice, 1e8);

        // debtValue = debt * debtPrice / 1e8
        // D18{USD} = D{X}{DEBT} * D8{USD/DEBT} / 1e8
        uint256 debtValue = debt.mulDiv(debtPrice, 1e8);

        // healthFactor = collateralValue * 1e18 / debtValue
        // D18{1} = D18{USD} * 1e18 / D18{USD}
        healthFactor = collateralValue.mulDiv(1e18, debtValue);
    }

    /// @notice Borrow tokens against collateral
    /// @param amount D{X}{DEBT} Amount to borrow
    function borrow(uint256 amount) external {
        Position storage pos = positions[msg.sender];
        pos.debt += amount;  // {DEBT} + {DEBT} = {DEBT} ✓
        totalBorrowed += amount;  // {DEBT} + {DEBT} = {DEBT} ✓
    }

    /// @notice Deposit collateral
    /// @param amount D{Y}{COLLATERAL} Amount to deposit
    function depositCollateral(uint256 amount) external {
        Position storage pos = positions[msg.sender];
        pos.collateral += amount;  // {COLLATERAL} + {COLLATERAL} = {COLLATERAL} ✓
        totalCollateral += amount;  // {COLLATERAL} + {COLLATERAL} = {COLLATERAL} ✓
    }
}
```

---

## Step 2 Checklist

- [ ] Annotate all numeric state variables with `// D{scale}{dimension}`
- [ ] Annotate all function parameters with NatSpec dimensions
- [ ] Annotate all return values with dimensions
- [ ] Annotate all struct fields with dimensions
- [ ] Add dimensional algebra comments for non-trivial formulas
- [ ] Ensure price dimensions clearly indicate numerator/denominator
- [ ] Ensure different token types have distinct dimensions

## references/bug-patterns.md

# Dimensional Bug Patterns

This document catalogs common dimensional bugs with examples and detection strategies. Examples use Solidity syntax, but these bug patterns occur in any language performing arithmetic with mixed units and scaling factors (Rust, TypeScript, Python, etc.).

## Critical Bugs (P0)

### Pattern 1: Unit Mismatch in Price Feeds

**Description:** Oracle returns price in different precision than expected.

**Example:**
```solidity
// Contract assumes D27 prices
uint256 price; // D27{UoA/tok}

// But Chainlink returns D8!
(, int256 answer,,,) = priceFeed.latestRoundData();
price = uint256(answer); // BUG: D8 assigned to D27 variable

// Correct:
price = uint256(answer) * 1e19; // Scale D8 to D27
```

**Impact:** Price values off by 10^19, causing catastrophic mispricing.

**Detection:** Check oracle `decimals()` vs expected precision.

---

### Pattern 2: Cross-Contract Dimension Assumption Mismatch

**Description:** Caller assumes different dimension than callee returns.

**Example:**
```solidity
// Protocol A's Vault
function getSharePrice() external returns (uint256) {
    return totalAssets * 1e18 / totalShares; // Returns D18{tok/share}
}

// Protocol B consuming it
uint256 sharePrice; // D27{tok/share} - WRONG ASSUMPTION
sharePrice = vaultA.getSharePrice(); // BUG: D18 value in D27 variable

// Correct:
sharePrice = vaultA.getSharePrice() * 1e9;
```

**Impact:** 9 orders of magnitude error in calculations.

**Detection:** Compare interface documentation and actual return dimensions.

---

### Pattern 3: Adding Incompatible Dimensions

**Description:** Adding values with different semantic meanings.

**Example:**
```solidity
// User's position
uint256 tokenBalance;  // {tok}
uint256 shareBalance;  // {share}

// BUG: Can't add tokens and shares!
uint256 totalPosition = tokenBalance + shareBalance;

// Correct: Convert to common dimension
uint256 totalTokens = tokenBalance + convertToAssets(shareBalance);
```

**Impact:** Result is mathematically meaningless.

**Detection:** Verify both operands have identical dimensions.

---

### Pattern 4: Wrong Precision Causing Overflow

**Description:** Multiplication without scaling causes overflow or precision explosion.

**Example:**
```solidity
uint256 amount; // D18{tok}
uint256 price;  // D27{UoA/tok}

// BUG: D18 * D27 = D45, overflows!
uint256 value = amount * price;

// Correct:
uint256 value = Math.mulDiv(amount, price, 1e27); // Result: D18{UoA}
```

**Impact:** Overflow reverts or silent wraparound.

**Detection:** Track precision through multiplication chains.

---

## High Severity Bugs (P1)

### Pattern 5: Missing Scaling Factor

**Description:** Calculation omits necessary precision adjustment.

**Example:**
```solidity
// Calculate shares from deposit
uint256 assets;     // D18{tok}
uint256 supply;     // D18{share}
uint256 totalAssets; // D18{tok}

// BUG: Missing D18 scaling
uint256 shares = assets * supply / totalAssets;
// Actually: D18 * D18 / D18 = D18, but intermediate is D36!

// Correct:
uint256 shares = Math.mulDiv(assets, supply, totalAssets);
```

**Impact:** Precision loss or overflow in intermediate calculation.

**Detection:** Verify all multiplications are properly scaled.

---

### Pattern 6: Wrong Scaling Direction

**Description:** Multiply when should divide, or vice versa.

**Example:**
```solidity
uint256 priceD27; // D27{UoA/tok}

// BUG: Multiplied instead of divided
uint256 priceD18 = priceD27 * 1e9; // Now D36!

// Correct:
uint256 priceD18 = priceD27 / 1e9;
```

**Impact:** Value off by 10^18.

**Detection:** Verify scaling direction matches precision conversion intent.

---

### Pattern 7: Inconsistent Return Path Dimensions

**Description:** Different code paths return values with different dimensions.

**Example:**
```solidity
function getValue(bool useOracle) returns (uint256) {
    if (useOracle) {
        return oracle.getPrice(token); // D8{UoA/tok}
    } else {
        return cachedPrice; // D18{UoA/tok} - DIFFERENT DIMENSION!
    }
}
```

**Impact:** Callers receive inconsistent values.

**Detection:** Trace all return paths and verify dimensions match.

---

### Pattern 8: Implicit Precision Truncation

**Description:** High precision value assigned to lower precision variable.

**Example:**
```solidity
uint256 preciseValue; // D27{UoA}
uint256 result;       // D18{UoA}

// BUG: Truncates 9 decimal places
result = preciseValue / 1e9;

// If preciseValue = 1.5e27 (1.5 in D27)
// result = 1.5e18 (1.5 in D18) - OK
// But if preciseValue = 1e18 (tiny in D27)
// result = 1e9 (still tiny in D18, lost precision)
```

**Impact:** Small values may become zero.

**Detection:** Identify precision reductions and check for rounding issues.

---

## Medium Severity Bugs (P2)

### Pattern 9: Redundant Scaling

**Description:** Unnecessary conversion that wastes gas or introduces rounding.

**Example:**
```solidity
uint256 priceD18; // D18{UoA/tok}

// Redundant: scale up then down
uint256 temp = priceD18 * 1e9;  // D27
uint256 result = temp / 1e9;    // Back to D18

// Could just use priceD18 directly
```

**Impact:** Gas waste, possible rounding errors.

**Detection:** Identify inverse scaling operations.

---

### Pattern 10: Fee Applied to Wrong Dimension

**Description:** Fee percentage applied to value instead of amount, or vice versa.

**Example:**
```solidity
uint256 depositAmount; // {tok}
uint256 feePercent;    // D18{1}
uint256 pricePerToken; // D27{UoA/tok}

// BUG: Fee on USD value, not token amount
uint256 fee = depositAmount * pricePerToken * feePercent / 1e45;

// Correct: Fee on token amount
uint256 fee = depositAmount * feePercent / 1e18; // {tok}
```

**Impact:** Fee calculation incorrect, may over/under charge.

**Detection:** Verify fee is applied to intended base.

---

### Pattern 11: Time Unit Confusion

**Description:** Mixing seconds with other time units.

**Example:**
```solidity
uint256 ratePerYear;  // D18{1} annual rate
uint256 elapsed;      // {s} seconds

// BUG: Applying annual rate to seconds
uint256 accrued = principal * ratePerYear * elapsed / 1e18;

// Correct: Convert to per-second rate
uint256 SECONDS_PER_YEAR = 365.25 days;
uint256 ratePerSecond = ratePerYear / SECONDS_PER_YEAR;
uint256 accrued = principal * ratePerSecond * elapsed / 1e18;
```

**Impact:** Interest/fees off by ~31.5 million.

**Detection:** Verify time unit consistency in rate calculations.

---

### Pattern 12: Division Before Multiplication

**Description:** Dividing first causes precision loss.

**Example:**
```solidity
uint256 a = 100;
uint256 b = 3;
uint256 c = 7;

// BUG: Division truncates
uint256 result = a / b * c; // = 33 * 7 = 231

// Correct:
uint256 result = a * c / b; // = 700 / 3 = 233
```

**Impact:** Silent precision loss.

**Detection:** Reorder to multiply before divide, or use mulDiv.

---

## Common Traps

These traps catch even experienced auditors. Always verify explicitly.

### Trap 1: Assumed Dimensionless Constant

```solidity
// Is this correct?
uint256 result = amount * MULTIPLIER / DIVISOR;

// Question: Are MULTIPLIER and DIVISOR truly dimensionless?
// If MULTIPLIER is actually a price {USD/TOKEN}, this formula is WRONG
// If DIVISOR is actually an amount {TOKEN}, this formula is WRONG

// Always verify what constants represent
```

### Trap 2: Hidden Dimension in Helper Function

```solidity
// Is this correct?
uint256 normalized = normalize(amount);

// Question: What dimension does normalize() return?
// - Does it change {TOKEN_A} to {TOKEN_B}?
// - Does it convert to {USD}?
// - Does it change scale but not dimension?

// MUST trace into the helper function
```

### Trap 3: Chained Operations Hiding Dimension Changes

```solidity
// Is this correct?
uint256 final = step1(step2(step3(input)));

// MUST trace dimensions through each step
// Don't assume the chain is correct because each function "looks right"

// Trace:
//   input           : {A}
//   step3(input)    : {?} - determine from step3's implementation
//   step2(step3...) : {?} - determine from step2's implementation
//   step1(step2...) : {?} - determine from step1's implementation
//   final           : expected {?}
```

### Trap 4: Scale Confused with Dimension

```solidity
// Common mistake:
uint256 price = oracle.getPrice();     // D8{USD/TOKEN}
uint256 value = amount * price / 1e18; // WRONG! Should be 1e8

// Scale is PART of dimensional correctness
// D18{TOKEN} * D8{USD/TOKEN} / 1e18 = D8{USD}  ← wrong scale
// D18{TOKEN} * D8{USD/TOKEN} / 1e8  = D18{USD} ← correct scale
```

### Trap 5: Decimals vs Amounts (Common Real Bug)

```solidity
// Extremely common bug pattern:
uint256 decimals = IERC20(token).decimals();  // Returns 18, dimension is {1}
uint256 amount = vault.convertToAssets(decimals);  // WRONG! Expects {SHARE}

// decimals is a COUNT, not a token amount
// It has dimension {1}, not {TOKEN} or {SHARE}
```

**Real-world example:**
```solidity
// BUGGY CODE (simplified from real audit)
function price(address _asset) external view returns (uint256 latestAnswer) {
    address underlying = IERC4626(_asset).asset();
    (latestAnswer, ) = IOracle(msg.sender).getPrice(underlying);
    uint256 tokenDecimals = IERC20Metadata(underlying).decimals();
    uint256 pricePerFullShare = IERC4626(_asset).convertToAssets(tokenDecimals);
    latestAnswer = latestAnswer * pricePerFullShare / tokenDecimals;
}
```

**Dimensional analysis catches this:**
```
Step 1: What does convertToAssets expect?
  According to ERC-4626: input is {SHARE}, output is {ASSET}

Step 2: What is tokenDecimals?
  It's the NUMBER of decimals (e.g., 18), dimension is {1}
  NOT a token amount!

Step 3: Trace the call
  convertToAssets(tokenDecimals)
  convertToAssets({1})  // WRONG! Expects {SHARE}

  This passes a dimensionless number where a share amount is expected!

BUG: decimals (a count, {1}) was passed to a function expecting assets ({SHARE})
```

### Trap 6: Same Name, Different Tokens

```solidity
// Is this correct?
uint256 total = totalSupplyA + totalSupplyB;

// If A and B are different tokens:
//   {TOKEN_A} + {TOKEN_B} = ??? (INVALID)

// Same-named variables for different tokens can't be added
```

---

## Detection Strategies

### Static Analysis

1. **Parse annotations** - Extract all dimensional comments
2. **Build dimension graph** - Map variable → dimension
3. **Trace arithmetic** - Apply algebra rules
4. **Check assignments** - LHS must match RHS dimension
5. **Check function boundaries** - Args match params, returns match declarations

### Code Patterns to Flag

```solidity
// Flag: Multiplication without mulDiv
a * b                           // Needs dimension check

// Flag: Direct oracle assignment
price = oracle.latestAnswer()   // Check precision match

// Flag: Addition of different variables
total = valueA + valueB         // Verify same dimension

// Flag: Return in conditional
if (x) return a; else return b; // Verify a and b same dimension

// Flag: Scaling literals
value * 1e9                     // Verify direction correct
value / 1e18                    // Verify scaling appropriate
```

### Human Review Triggers

- Cross-contract calls (external assumptions)
- Complex multi-step calculations
- Non-standard token decimals (not 18)
- Custom oracle implementations
- Protocol-specific units

## False Positive Avoidance

### Acceptable Patterns

```solidity
// Intentional dimensionless arithmetic
uint256 doubled = amount * 2;       // {tok} * {1} = {tok}

// Loop bounds
for (uint256 i = 0; i < length; i++)  // {1}

// Explicit documented conversion
// Intentionally converting D27 to D18 with precision loss
uint256 approxPrice = precisePrice / 1e9;

// Test contracts
contract MockOracle { ... }         // Ignore test code
```

### Context Clues

- Check for comments explaining intent
- Check for test file paths
- Check for "mock" or "test" in names
- Check for explicit precision documentation

## references/common-dimensions.md

# Common Dimensions in DeFi

This document catalogs standard dimensional units used across DeFi protocols. While examples use Solidity syntax, the dimensional vocabulary is protocol-agnostic and applies equally to Rust (Anchor, CosmWasm), TypeScript, Python, or any other language implementing DeFi logic.

## Universal Base Units

### Token Amounts: `{tok}`

Represents a quantity of tokens.

```solidity
uint256 public totalSupply;      // {tok}
uint256 public balanceOf;        // {tok}
uint256 amount;                  // {tok}
```

**Typical precision:** D6 (USDC, USDT), D8 (WBTC), D18 (most ERC20)

### Share Amounts: `{share}`

Represents vault/pool shares.

```solidity
uint256 public totalShares;      // {share}
uint256 userShares;              // {share}
```

**Typical precision:** D18

### Time: `{s}`

Timestamps and durations in seconds.

```solidity
uint256 public lastUpdate;       // {s}
uint256 elapsed;                 // {s}
uint256 duration;                // {s}
block.timestamp                  // {s}
```

**Precision:** Integer (no decimals)

### Dimensionless: `{1}`

Pure ratios, percentages, multipliers.

```solidity
uint256 public feeRate;          // D18{1}
uint256 percent;                 // D18{1} or D4{1} for basis points
uint256 multiplier;              // D18{1}
```

**Typical precision:** D18 or D4 (basis points)

### Unit of Account: `{UoA}`

Abstract value unit, typically USD-equivalent.

```solidity
uint256 public totalValue;       // {UoA}
uint256 price;                   // {UoA} (absolute)
```

**Note:** Often implicit in price dimensions like `{UoA/tok}`

## Standard Derived Units

### Exchange Rate: `{tok/share}` or `{share/tok}`

Conversion ratio between tokens and shares.

```solidity
uint256 public exchangeRate;     // D18{tok/share}
uint256 sharePrice;              // D18{tok/share}
```

### Price: `{UoA/tok}`

Value per token in unit of account.

```solidity
uint256 public tokenPrice;       // D8{UoA/tok} (Chainlink)
uint256 oraclePrice;             // D18{UoA/tok} or D27{UoA/tok}
```

### Cross Price: `{tokA/tokB}`

Exchange rate between two tokens.

```solidity
uint256 public swapRate;         // D18{tokA/tokB}
```

### Rate Per Second: `{1/s}`

Time-based rate (fees, interest).

```solidity
uint256 public interestRate;     // D18{1/s}
uint256 feePerSecond;            // D27{1/s}
```

### Value Per Share: `{UoA/share}`

Share value in unit of account.

```solidity
uint256 public nav;              // D18{UoA/share}
uint256 shareValue;              // D27{UoA/share}
```

## Protocol-Specific Units

### Reserve Protocol

| Unit | Description | Example |
|------|-------------|---------|
| `{BU}` | Basket Unit | Target basket composition |
| `{tok/BU}` | Tokens per basket | Weight in basket |
| `{UoA/BU}` | Basket value | Basket price |
| `{BU/share}` | Baskets per share | RToken backing |
| `{RToken}` | RToken amount | Alias for `{share}` |
| `{RSR}` | RSR token amount | Staking token |

```solidity
uint256 public basketsNeeded;    // {BU}
uint256 weight;                  // D27{tok/BU}
uint256 price;                   // D27{UoA/tok}
```

### Lending Protocols (Aave, Compound)

| Unit | Description | Example |
|------|-------------|---------|
| `{debt}` | Debt token amount | Borrowed amount |
| `{collateral}` | Collateral amount | Deposited collateral |
| `{aToken}` | Aave interest-bearing token | Deposit receipt |
| `{cToken}` | Compound interest-bearing token | Deposit receipt |
| `{1}` | Health factor, LTV | Risk metrics |

```solidity
uint256 public totalDebt;        // {debt}
uint256 healthFactor;            // D18{1}
uint256 ltv;                     // D4{1} (basis points)
```

### AMM Protocols (Uniswap, Curve)

| Unit | Description | Example |
|------|-------------|---------|
| `{liq}` | Liquidity units | Pool liquidity |
| `{LP}` | LP token amount | Liquidity provider shares |
| `{sqrtP}` | Square root price | Uniswap V3 |

```solidity
uint256 public liquidity;        // {liq}
uint256 lpBalance;               // {LP}
uint160 sqrtPriceX96;            // Q96{sqrtP}
```

### Staking Protocols

| Unit | Description | Example |
|------|-------------|---------|
| `{staked}` | Staked token amount | Deposited stake |
| `{reward}` | Reward token amount | Earned rewards |
| `{reward/staked}` | Reward rate | Per-token rewards |

```solidity
uint256 public totalStaked;      // {staked}
uint256 rewardPerToken;          // D18{reward/staked}
```

## Standard Precision Levels

| Prefix | Value | Common Usage |
|--------|-------|--------------|
| D4 | 1e4 | Basis points |
| D6 | 1e6 | USDC, USDT decimals |
| D8 | 1e8 | WBTC, Chainlink prices |
| D18 | 1e18 | Standard ERC20, most calculations |
| D27 | 1e27 | High-precision prices (Reserve) |
| Q96 | 2^96 | Uniswap V3 fixed-point |

## Interface Dimensions

### ERC20

```solidity
function totalSupply() external view returns (uint256);        // {tok}
function balanceOf(address) external view returns (uint256);   // {tok}
function decimals() external view returns (uint8);             // precision info
function transfer(address, uint256 amount) external;           // amount: {tok}
function approve(address, uint256 amount) external;            // amount: {tok}
function transferFrom(address, address, uint256 amount);       // amount: {tok}
function allowance(address, address) returns (uint256);        // {tok}
```

### ERC4626

```solidity
function asset() external view returns (address);              // underlying token
function totalAssets() external view returns (uint256);        // {tok}
function convertToShares(uint256 assets) returns (uint256);    // {tok} → {share}
function convertToAssets(uint256 shares) returns (uint256);    // {share} → {tok}
function maxDeposit(address) external view returns (uint256);  // {tok}
function maxMint(address) external view returns (uint256);     // {share}
function maxWithdraw(address) external view returns (uint256); // {tok}
function maxRedeem(address) external view returns (uint256);   // {share}
function previewDeposit(uint256 assets) returns (uint256);     // {tok} → {share}
function previewMint(uint256 shares) returns (uint256);        // {share} → {tok}
function previewWithdraw(uint256 assets) returns (uint256);    // {tok} → {share}
function previewRedeem(uint256 shares) returns (uint256);      // {share} → {tok}
function deposit(uint256 assets, address) returns (uint256);   // {tok} → {share}
function mint(uint256 shares, address) returns (uint256);      // {share} → {tok}
function withdraw(uint256 assets, ...) returns (uint256);      // {tok} → {share}
function redeem(uint256 shares, ...) returns (uint256);        // {share} → {tok}
```

### Chainlink

```solidity
function decimals() external view returns (uint8);             // usually 8
function latestRoundData() external view returns (
    uint80 roundId,
    int256 answer,      // D8{UoA/tok} typically
    uint256 startedAt,  // {s}
    uint256 updatedAt,  // {s}
    uint80 answeredInRound
);
```

## Naming Convention Hints

| Pattern | Likely Dimension |
|---------|-----------------|
| `*Balance`, `*Amount` | `{tok}` |
| `*Shares`, `share*` | `{share}` |
| `*Price`, `price*` | `{UoA/tok}` |
| `*Rate`, `rate*` | `{1}` or `{1/s}` |
| `*Time`, `*Timestamp` | `{s}` |
| `*Duration`, `*Period` | `{s}` |
| `*Fee`, `fee*` | `{1}` |
| `*Ratio`, `ratio*` | `{1}` |
| `*Value`, `value*` | `{UoA}` |
| `*Per*` | derived unit |
| `total*` | aggregate amount |
| `max*`, `min*` | bounds (same as base) |

## references/dimension-algebra.md

# Dimensional Algebra Rules

This document defines the rules for dimensional arithmetic. While examples use Solidity syntax, these algebraic rules are universal and apply to any language performing fixed-point or scaled arithmetic.

## Notation

- `{A}` - A semantic unit (e.g., `{tok}`, `{share}`, `{UoA}`)
- `D18` - A precision prefix indicating 18 decimal places
- `D18{A}` - A value with unit `{A}` and precision D18
- `{A/B}` - A derived unit (A per B)
- `{A*B}` - A compound unit (A times B)
- `{1}` - Dimensionless (pure ratio)

### Formal Grammar

```
annotation     := scale? "{" dimension "}"
scale          := "D" number
dimension      := base_dim | derived_dim | "1"
derived_dim    := dimension "/" dimension | dimension "*" dimension
base_dim       := identifier
```

**Examples:**
- `{tok}` - Token amount (no scale specified)
- `D18{tok}` - Token amount, 18 decimals fixed-point
- `D27{USD/tok}` - Price, 27 decimals fixed-point
- `{1}` - Dimensionless (pure number or ratio)

## Basic Composition Rules

### Multiplication

Dimensions multiply when values are multiplied:

```
{A} * {B} = {A*B}

Examples:
{tok} * {UoA/tok} = {UoA}           # tokens × price = value
{share} * {tok/share} = {tok}       # shares × exchange rate = tokens
{1} * {A} = {A}                     # dimensionless preserves dimension
```

### Division

Dimensions divide when values are divided:

```
{A} / {B} = {A/B}

Examples:
{tok} / {share} = {tok/share}       # exchange rate
{UoA} / {tok} = {UoA/tok}           # price
{A} / {A} = {1}                     # same dimensions cancel
{A} / {1} = {A}                     # dividing by dimensionless preserves
```

### Addition and Subtraction

**CRITICAL: Addition and subtraction require identical dimensions.**

```
{A} + {A} = {A}                     # Valid
{A} - {A} = {A}                     # Valid
{A} + {B} = ERROR                   # Invalid! Dimension mismatch
{A} - {B} = ERROR                   # Invalid! Dimension mismatch

Examples:
{tok} + {tok} = {tok}               # Valid: adding token amounts
{tok} + {share} = ERROR             # Invalid: can't add tokens and shares
{UoA/tok} + {UoA/tok} = {UoA/tok}   # Valid: adding prices
```

## Precision Arithmetic

### Multiplication Precision

Precisions ADD when multiplying:

```
D18 * D18 = D36
D27 * D18 = D45
D18 * D27 = D45

Example:
D18{tok} * D18{share/tok} = D36{share}  # Need to scale down by D18
```

### Division Precision

Precisions SUBTRACT when dividing:

```
D36 / D18 = D18
D27 / D18 = D9
D18 / D18 = D0 (integer)

Example:
D36{share} / D18 = D18{share}           # Scaling down
D27{UoA/tok} / D18{1} = D9{UoA/tok}     # Precision reduced
```

### Scaling Operations

Scaling is multiplication/division by a pure precision constant:

```
D18{A} * D9 = D27{A}                    # Scale up precision
D27{A} / D9 = D18{A}                    # Scale down precision
D36{A} / D18 = D18{A}                   # Common pattern after multiplication
```

## Common Patterns

### Price Calculation

```solidity
// Calculate value from amount and price
// {UoA} = {tok} * D27{UoA/tok} / D27
// D27{UoA} = D18{tok} * D27{UoA/tok} / D18
uint256 value = Math.mulDiv(amount, price, D18);
```

### Share Conversion (ERC4626)

```solidity
// Convert assets to shares
// {share} = {tok} * {share} / {tok}
// D18{share} = D18{tok} * D18{share} / D18{tok}
uint256 shares = Math.mulDiv(assets, totalSupply, totalAssets);

// Convert shares to assets
// {tok} = {share} * {tok} / {share}
uint256 assets = Math.mulDiv(shares, totalAssets, totalSupply);
```

### Fee Application

```solidity
// Apply percentage fee
// {tok} = {tok} * D18{1} / D18
uint256 fee = Math.mulDiv(amount, feeRate, D18);
uint256 netAmount = amount - fee;
```

### Rate Per Second

```solidity
// Calculate accrued amount
// {tok} = {tok} * D18{1/s} * {s} / D18
uint256 accrued = Math.mulDiv(principal, rate * elapsed, D18);
```

### Cross-Rate Calculation

```solidity
// Calculate token A price in terms of token B
// D27{B/A} = D27{UoA/A} * D27 / D27{UoA/B}
uint256 crossRate = Math.mulDiv(priceA, D27, priceB);
```

## Dimensional Simplification

### Cancellation

When the same unit appears in numerator and denominator, it cancels:

```
{tok/share} * {share} = {tok}           # share cancels
{UoA/tok} * {tok/BU} = {UoA/BU}         # tok cancels
{A/B} * {B/C} = {A/C}                   # B cancels
```

### Identity

```
{A} * {1} = {A}
{A} / {1} = {A}
{A} * {B/B} = {A}                       # Multiplying by 1
```

## Multi-Step Calculations

For complex expressions, track dimensions step by step:

```solidity
// Calculate share value in UoA
// Step 1: {tok/share} = {tok} / {share}
// Step 2: {UoA/share} = {tok/share} * {UoA/tok}
//
// In code:
// D18{UoA/share} = D18{tok} * D27{UoA/tok} / D18{share} / D9
uint256 shareValue = Math.mulDiv(
    Math.mulDiv(totalAssets, price, totalShares),
    1,
    1e9  // Scale D27 to D18
);
```

## Error Patterns

### Division Before Multiplication

**Risky for precision loss:**
```solidity
// BAD: May lose precision
uint256 result = a / b * c;

// BETTER: Use mulDiv
uint256 result = Math.mulDiv(a, c, b);
```

### Missing Scaling

**Common bug pattern:**
```solidity
// BUG: D36 result stored in D18 variable
uint256 result = amount * rate;  // D18 * D18 = D36!

// CORRECT:
uint256 result = Math.mulDiv(amount, rate, D18);
```

### Wrong Scaling Direction

```solidity
// BUG: Multiplied when should divide
uint256 price18 = price27 * 1e9;  // Now D36!

// CORRECT:
uint256 price18 = price27 / 1e9;
```

## Special Cases

### Dimensionless Constants

Integer constants like `2`, `100`, `MAX_UINT` are dimensionless `{1}`:

```solidity
uint256 doubled = amount * 2;     // {tok} * {1} = {tok}
uint256 half = amount / 2;        // {tok} / {1} = {tok}
```

### Timestamps

Timestamps and durations have dimension `{s}` (seconds):

```solidity
uint256 elapsed = block.timestamp - lastUpdate;  // {s} - {s} = {s}
uint256 rate = feePerSecond * elapsed;           // {1/s} * {s} = {1}
```

### Basis Points

Basis points are `{1}` with implicit D4 precision:

```solidity
uint256 constant BPS = 10000;     // D4
uint256 fee = amount * feeBps / BPS;  // {tok} * {1} / {1} = {tok}
```

## Validation Checklist

For any arithmetic operation:

1. ✓ Do operand dimensions combine correctly?
2. ✓ Is the result dimension what's expected?
3. ✓ Is precision handled correctly (scaling)?
4. ✓ Is rounding direction appropriate?
5. ✓ Could intermediate values overflow?

