This commit is contained in:
8
audit-context-building/.skillshare-meta.json
Normal file
8
audit-context-building/.skillshare-meta.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"source": "github.com/trailofbits/skills/tree/main/plugins/audit-context-building/skills/audit-context-building",
|
||||
"type": "github-subdir",
|
||||
"installed_at": "2026-01-30T02:23:12.950780406Z",
|
||||
"repo_url": "https://github.com/trailofbits/skills.git",
|
||||
"subdir": "plugins/audit-context-building/skills/audit-context-building",
|
||||
"version": "650f6e3"
|
||||
}
|
||||
297
audit-context-building/SKILL.md
Normal file
297
audit-context-building/SKILL.md
Normal file
@@ -0,0 +1,297 @@
|
||||
---
|
||||
name: audit-context-building
|
||||
description: Enables ultra-granular, line-by-line code analysis to build deep architectural context before vulnerability or bug finding.
|
||||
---
|
||||
|
||||
# Deep Context Builder Skill (Ultra-Granular Pure Context Mode)
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
This skill governs **how Claude thinks** during the context-building phase of an audit.
|
||||
|
||||
When active, Claude will:
|
||||
- Perform **line-by-line / block-by-block** code analysis by default.
|
||||
- Apply **First Principles**, **5 Whys**, and **5 Hows** at micro scale.
|
||||
- Continuously link insights → functions → modules → entire system.
|
||||
- Maintain a stable, explicit mental model that evolves with new evidence.
|
||||
- Identify invariants, assumptions, flows, and reasoning hazards.
|
||||
|
||||
This skill defines a structured analysis format (see Example: Function Micro-Analysis below) and runs **before** the vulnerability-hunting phase.
|
||||
|
||||
---
|
||||
|
||||
## 2. When to Use This Skill
|
||||
|
||||
Use when:
|
||||
- Deep comprehension is needed before bug or vulnerability discovery.
|
||||
- You want bottom-up understanding instead of high-level guessing.
|
||||
- Reducing hallucinations, contradictions, and context loss is critical.
|
||||
- Preparing for security auditing, architecture review, or threat modeling.
|
||||
|
||||
Do **not** use for:
|
||||
- Vulnerability findings
|
||||
- Fix recommendations
|
||||
- Exploit reasoning
|
||||
- Severity/impact rating
|
||||
|
||||
---
|
||||
|
||||
## 3. How This Skill Behaves
|
||||
|
||||
When active, Claude will:
|
||||
- Default to **ultra-granular analysis** of each block and line.
|
||||
- Apply micro-level First Principles, 5 Whys, and 5 Hows.
|
||||
- Build and refine a persistent global mental model.
|
||||
- Update earlier assumptions when contradicted ("Earlier I thought X; now Y.").
|
||||
- Periodically anchor summaries to maintain stable context.
|
||||
- Avoid speculation; express uncertainty explicitly when needed.
|
||||
|
||||
Goal: **deep, accurate understanding**, not conclusions.
|
||||
|
||||
---
|
||||
|
||||
## Rationalizations (Do Not Skip)
|
||||
|
||||
| Rationalization | Why It's Wrong | Required Action |
|
||||
|-----------------|----------------|-----------------|
|
||||
| "I get the gist" | Gist-level understanding misses edge cases | Line-by-line analysis required |
|
||||
| "This function is simple" | Simple functions compose into complex bugs | Apply 5 Whys anyway |
|
||||
| "I'll remember this invariant" | You won't. Context degrades. | Write it down explicitly |
|
||||
| "External call is probably fine" | External = adversarial until proven otherwise | Jump into code or model as hostile |
|
||||
| "I can skip this helper" | Helpers contain assumptions that propagate | Trace the full call chain |
|
||||
| "This is taking too long" | Rushed context = hallucinated vulnerabilities later | Slow is fast |
|
||||
|
||||
---
|
||||
|
||||
## 4. Phase 1 — Initial Orientation (Bottom-Up Scan)
|
||||
|
||||
Before deep analysis, Claude performs a minimal mapping:
|
||||
|
||||
1. Identify major modules/files/contracts.
|
||||
2. Note obvious public/external entrypoints.
|
||||
3. Identify likely actors (users, owners, relayers, oracles, other contracts).
|
||||
4. Identify important storage variables, dicts, state structs, or cells.
|
||||
5. Build a preliminary structure without assuming behavior.
|
||||
|
||||
This establishes anchors for detailed analysis.
|
||||
|
||||
---
|
||||
|
||||
## 5. Phase 2 — Ultra-Granular Function Analysis (Default Mode)
|
||||
|
||||
Every non-trivial function receives full micro analysis.
|
||||
|
||||
### 5.1 Per-Function Microstructure Checklist
|
||||
|
||||
For each function:
|
||||
|
||||
1. **Purpose**
|
||||
- Why the function exists and its role in the system.
|
||||
|
||||
2. **Inputs & Assumptions**
|
||||
- Parameters and implicit inputs (state, sender, env).
|
||||
- Preconditions and constraints.
|
||||
|
||||
3. **Outputs & Effects**
|
||||
- Return values.
|
||||
- State/storage writes.
|
||||
- Events/messages.
|
||||
- External interactions.
|
||||
|
||||
4. **Block-by-Block / Line-by-Line Analysis**
|
||||
For each logical block:
|
||||
- What it does.
|
||||
- Why it appears here (ordering logic).
|
||||
- What assumptions it relies on.
|
||||
- What invariants it establishes or maintains.
|
||||
- What later logic depends on it.
|
||||
|
||||
Apply per-block:
|
||||
- **First Principles**
|
||||
- **5 Whys**
|
||||
- **5 Hows**
|
||||
|
||||
---
|
||||
|
||||
### 5.2 Cross-Function & External Flow Analysis
|
||||
*(Full Integration of Jump-Into-External-Code Rule)*
|
||||
|
||||
When encountering calls, **continue the same micro-first analysis across boundaries.**
|
||||
|
||||
#### Internal Calls
|
||||
- Jump into the callee immediately.
|
||||
- Perform block-by-block analysis of relevant code.
|
||||
- Track flow of data, assumptions, and invariants:
|
||||
caller → callee → return → caller.
|
||||
- Note if callee logic behaves differently in this specific call context.
|
||||
|
||||
#### External Calls — Two Cases
|
||||
|
||||
**Case A — External Call to a Contract Whose Code Exists in the Codebase**
|
||||
Treat as an internal call:
|
||||
- Jump into the target contract/function.
|
||||
- Continue block-by-block micro-analysis.
|
||||
- Propagate invariants and assumptions seamlessly.
|
||||
- Consider edge cases based on the *actual* code, not a black-box guess.
|
||||
|
||||
**Case B — External Call Without Available Code (True External / Black Box)**
|
||||
Analyze as adversarial:
|
||||
- Describe payload/value/gas or parameters sent.
|
||||
- Identify assumptions about the target.
|
||||
- Consider all outcomes:
|
||||
- revert
|
||||
- incorrect/strange return values
|
||||
- unexpected state changes
|
||||
- misbehavior
|
||||
- reentrancy (if applicable)
|
||||
|
||||
#### Continuity Rule
|
||||
Treat the entire call chain as **one continuous execution flow**.
|
||||
Never reset context.
|
||||
All invariants, assumptions, and data dependencies must propagate across calls.
|
||||
|
||||
---
|
||||
|
||||
### 5.3 Complete Analysis Example
|
||||
|
||||
See [FUNCTION_MICRO_ANALYSIS_EXAMPLE.md](resources/FUNCTION_MICRO_ANALYSIS_EXAMPLE.md) for a complete walkthrough demonstrating:
|
||||
- Full micro-analysis of a DEX swap function
|
||||
- Application of First Principles, 5 Whys, and 5 Hows
|
||||
- Block-by-block analysis with invariants and assumptions
|
||||
- Cross-function dependency mapping
|
||||
- Risk analysis for external interactions
|
||||
|
||||
This example demonstrates the level of depth and structure required for all analyzed functions.
|
||||
|
||||
---
|
||||
|
||||
### 5.4 Output Requirements
|
||||
|
||||
When performing ultra-granular analysis, Claude MUST structure output following the format defined in [OUTPUT_REQUIREMENTS.md](resources/OUTPUT_REQUIREMENTS.md).
|
||||
|
||||
Key requirements:
|
||||
- **Purpose** (2-3 sentences minimum)
|
||||
- **Inputs & Assumptions** (all parameters, preconditions, trust assumptions)
|
||||
- **Outputs & Effects** (returns, state writes, external calls, events, postconditions)
|
||||
- **Block-by-Block Analysis** (What, Why here, Assumptions, First Principles/5 Whys/5 Hows)
|
||||
- **Cross-Function Dependencies** (internal calls, external calls with risk analysis, shared state)
|
||||
|
||||
Quality thresholds:
|
||||
- Minimum 3 invariants per function
|
||||
- Minimum 5 assumptions documented
|
||||
- Minimum 3 risk considerations for external interactions
|
||||
- At least 1 First Principles application
|
||||
- At least 3 combined 5 Whys/5 Hows applications
|
||||
|
||||
---
|
||||
|
||||
### 5.5 Completeness Checklist
|
||||
|
||||
Before concluding micro-analysis of a function, verify against the [COMPLETENESS_CHECKLIST.md](resources/COMPLETENESS_CHECKLIST.md):
|
||||
|
||||
- **Structural Completeness**: All required sections present (Purpose, Inputs, Outputs, Block-by-Block, Dependencies)
|
||||
- **Content Depth**: Minimum thresholds met (invariants, assumptions, risk analysis, First Principles)
|
||||
- **Continuity & Integration**: Cross-references, propagated assumptions, invariant couplings
|
||||
- **Anti-Hallucination**: Line number citations, no vague statements, evidence-based claims
|
||||
|
||||
Analysis is complete when all checklist items are satisfied and no unresolved "unclear" items remain.
|
||||
|
||||
---
|
||||
|
||||
## 6. Phase 3 — Global System Understanding
|
||||
|
||||
After sufficient micro-analysis:
|
||||
|
||||
1. **State & Invariant Reconstruction**
|
||||
- Map reads/writes of each state variable.
|
||||
- Derive multi-function and multi-module invariants.
|
||||
|
||||
2. **Workflow Reconstruction**
|
||||
- Identify end-to-end flows (deposit, withdraw, lifecycle, upgrades).
|
||||
- Track how state transforms across these flows.
|
||||
- Record assumptions that persist across steps.
|
||||
|
||||
3. **Trust Boundary Mapping**
|
||||
- Actor → entrypoint → behavior.
|
||||
- Identify untrusted input paths.
|
||||
- Privilege changes and implicit role expectations.
|
||||
|
||||
4. **Complexity & Fragility Clustering**
|
||||
- Functions with many assumptions.
|
||||
- High branching logic.
|
||||
- Multi-step dependencies.
|
||||
- Coupled state changes across modules.
|
||||
|
||||
These clusters help guide the vulnerability-hunting phase.
|
||||
|
||||
---
|
||||
|
||||
## 7. Stability & Consistency Rules
|
||||
*(Anti-Hallucination, Anti-Contradiction)*
|
||||
|
||||
Claude must:
|
||||
|
||||
- **Never reshape evidence to fit earlier assumptions.**
|
||||
When contradicted:
|
||||
- Update the model.
|
||||
- State the correction explicitly.
|
||||
|
||||
- **Periodically anchor key facts**
|
||||
Summarize core:
|
||||
- invariants
|
||||
- state relationships
|
||||
- actor roles
|
||||
- workflows
|
||||
|
||||
- **Avoid vague guesses**
|
||||
Use:
|
||||
- "Unclear; need to inspect X."
|
||||
instead of:
|
||||
- "It probably…"
|
||||
|
||||
- **Cross-reference constantly**
|
||||
Connect new insights to previous state, flows, and invariants to maintain global coherence.
|
||||
|
||||
---
|
||||
|
||||
## 8. Subagent Usage
|
||||
|
||||
Claude may spawn subagents for:
|
||||
- Dense or complex functions.
|
||||
- Long data-flow or control-flow chains.
|
||||
- Cryptographic / mathematical logic.
|
||||
- Complex state machines.
|
||||
- Multi-module workflow reconstruction.
|
||||
|
||||
Subagents must:
|
||||
- Follow the same micro-first rules.
|
||||
- Return summaries that Claude integrates into its global model.
|
||||
|
||||
---
|
||||
|
||||
## 9. Relationship to Other Phases
|
||||
|
||||
This skill runs **before**:
|
||||
- Vulnerability discovery
|
||||
- Classification / triage
|
||||
- Report writing
|
||||
- Impact modeling
|
||||
- Exploit reasoning
|
||||
|
||||
It exists solely to build:
|
||||
- Deep understanding
|
||||
- Stable context
|
||||
- System-level clarity
|
||||
|
||||
---
|
||||
|
||||
## 10. Non-Goals
|
||||
|
||||
While active, Claude should NOT:
|
||||
- Identify vulnerabilities
|
||||
- Propose fixes
|
||||
- Generate proofs-of-concept
|
||||
- Model exploits
|
||||
- Assign severity or impact
|
||||
|
||||
This is **pure context building** only.
|
||||
47
audit-context-building/resources/COMPLETENESS_CHECKLIST.md
Normal file
47
audit-context-building/resources/COMPLETENESS_CHECKLIST.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Completeness Checklist
|
||||
|
||||
Before concluding micro-analysis of a function, verify:
|
||||
|
||||
---
|
||||
|
||||
## Structural Completeness
|
||||
- [ ] Purpose section: 2+ sentences explaining function role
|
||||
- [ ] Inputs & Assumptions section: All parameters + implicit inputs documented
|
||||
- [ ] Outputs & Effects section: All returns, state writes, external calls, events
|
||||
- [ ] Block-by-Block Analysis: Every logical block analyzed (no gaps)
|
||||
- [ ] Cross-Function Dependencies: All calls and shared state documented
|
||||
|
||||
---
|
||||
|
||||
## Content Depth
|
||||
- [ ] Identified at least 3 invariants (what must always hold)
|
||||
- [ ] Documented at least 5 assumptions (what is assumed true)
|
||||
- [ ] Applied First Principles at least once
|
||||
- [ ] Applied 5 Whys or 5 Hows at least 3 times total
|
||||
- [ ] Risk analysis for all external interactions (reentrancy, malicious contracts, etc.)
|
||||
|
||||
---
|
||||
|
||||
## Continuity & Integration
|
||||
- [ ] Cross-reference with related functions (if internal calls exist, analyze callees)
|
||||
- [ ] Propagated assumptions from callers (if this function is called by others)
|
||||
- [ ] Identified invariant couplings (how this function's invariants relate to global system)
|
||||
- [ ] Tracked data flow across function boundaries (if applicable)
|
||||
|
||||
---
|
||||
|
||||
## Anti-Hallucination Verification
|
||||
- [ ] All claims reference specific line numbers (L45, L98-102, etc.)
|
||||
- [ ] No vague statements ("probably", "might", "seems to") - replaced with "unclear; need to check X"
|
||||
- [ ] Contradictions resolved (if earlier analysis conflicts with current findings, explicitly updated)
|
||||
- [ ] Evidence-based: Every invariant/assumption tied to actual code
|
||||
|
||||
---
|
||||
|
||||
## Completeness Signal
|
||||
|
||||
Analysis is complete when:
|
||||
1. All checklist items above are satisfied
|
||||
2. No remaining "TODO: analyze X" or "unclear Y" items
|
||||
3. Full call chain analyzed (for internal calls, jumped into and analyzed)
|
||||
4. All identified risks have mitigation analysis or acknowledged as unresolved
|
||||
@@ -0,0 +1,355 @@
|
||||
# Function Micro-Analysis Example
|
||||
|
||||
This example demonstrates a complete micro-analysis following the Per-Function Microstructure Checklist.
|
||||
|
||||
---
|
||||
|
||||
## Target: `swap(address tokenIn, address tokenOut, uint256 amountIn, uint256 minAmountOut, uint256 deadline)` in Router.sol
|
||||
|
||||
**Purpose:**
|
||||
Enables users to swap one token for another through a liquidity pool. Core trading operation in a DEX that:
|
||||
- Calculates output amount using constant product formula (x * y = k)
|
||||
- Deducts 0.3% protocol fee from input amount
|
||||
- Enforces user-specified slippage protection
|
||||
- Updates pool reserves to maintain AMM invariant
|
||||
- Prevents stale transactions via deadline check
|
||||
|
||||
This is a critical financial primitive affecting pool solvency, user fund safety, and protocol fee collection.
|
||||
|
||||
---
|
||||
|
||||
**Inputs & Assumptions:**
|
||||
|
||||
*Parameters:*
|
||||
- `tokenIn` (address): Source token to swap from. Assumed untrusted (could be malicious ERC20).
|
||||
- `tokenOut` (address): Destination token to receive. Assumed untrusted.
|
||||
- `amountIn` (uint256): Amount of tokenIn to swap. User-specified, untrusted input.
|
||||
- `minAmountOut` (uint256): Minimum acceptable output. User-specified slippage tolerance.
|
||||
- `deadline` (uint256): Unix timestamp. Transaction must execute before this or revert.
|
||||
|
||||
*Implicit Inputs:*
|
||||
- `msg.sender`: Transaction initiator. Assumed to have approved Router to spend amountIn of tokenIn.
|
||||
- `pairs[tokenIn][tokenOut]`: Storage mapping to pool address. Assumed populated during pool creation.
|
||||
- `reserves[pair]`: Pool's current token reserves. Assumed synchronized with actual pool balances.
|
||||
- `block.timestamp`: Current block time. Assumed honest (no validator manipulation considered here).
|
||||
|
||||
*Preconditions:*
|
||||
- Pool exists for tokenIn/tokenOut pair (pairs[tokenIn][tokenOut] != address(0))
|
||||
- msg.sender has approved Router for at least amountIn of tokenIn
|
||||
- msg.sender balance of tokenIn >= amountIn
|
||||
- Pool has sufficient liquidity to output at least minAmountOut
|
||||
- block.timestamp <= deadline
|
||||
|
||||
*Trust Assumptions:*
|
||||
- Pool contract correctly maintains reserves
|
||||
- ERC20 tokens follow standard behavior (return true on success, revert on failure)
|
||||
- No reentrancy from tokenIn/tokenOut during transfers (or handled by nonReentrant modifier)
|
||||
|
||||
---
|
||||
|
||||
**Outputs & Effects:**
|
||||
|
||||
*Returns:*
|
||||
- Implicit: amountOut (not returned, but emitted in event)
|
||||
|
||||
*State Writes:*
|
||||
- `reserves[pair].reserve0` and `reserves[pair].reserve1`: Updated to reflect post-swap balances
|
||||
- Pool token balances: Physical token transfers change actual balances
|
||||
|
||||
*External Interactions:*
|
||||
- `IERC20(tokenIn).transferFrom(msg.sender, pair, amountIn)`: Pulls tokenIn from user to pool
|
||||
- `IERC20(tokenOut).transfer(msg.sender, amountOut)`: Sends tokenOut from pool to user
|
||||
|
||||
*Events Emitted:*
|
||||
- `Swap(msg.sender, tokenIn, tokenOut, amountIn, amountOut, block.timestamp)`
|
||||
|
||||
*Postconditions:*
|
||||
- `amountOut >= minAmountOut` (slippage protection enforced)
|
||||
- Pool reserves updated: `reserve0 * reserve1 >= k_before` (constant product maintained with fee)
|
||||
- User received exactly amountOut of tokenOut
|
||||
- Pool received exactly amountIn of tokenIn
|
||||
- Fee collected: `amountIn * 0.003` remains in pool as liquidity
|
||||
|
||||
---
|
||||
|
||||
**Block-by-Block Analysis:**
|
||||
|
||||
```solidity
|
||||
// L90: Deadline validation (modifier: ensure(deadline))
|
||||
modifier ensure(uint256 deadline) {
|
||||
require(block.timestamp <= deadline, "Expired");
|
||||
_;
|
||||
}
|
||||
```
|
||||
- **What:** Checks transaction hasn't expired based on user-provided deadline
|
||||
- **Why here:** First line of defense; fail fast before any state reads or computation
|
||||
- **Assumption:** `block.timestamp` is sufficiently honest (no 900-second manipulation considered)
|
||||
- **Depends on:** User setting reasonable deadline (e.g., block.timestamp + 300 seconds)
|
||||
- **First Principles:** Time-sensitive operations need expiration to prevent stale execution at unexpected prices
|
||||
- **5 Whys:**
|
||||
- Why check deadline? → Prevent stale transactions
|
||||
- Why are stale transactions bad? → Price may have moved significantly
|
||||
- Why not just use slippage protection? → Slippage doesn't prevent execution hours later
|
||||
- Why does timing matter? → Market conditions change, user intent expires
|
||||
- Why user-provided vs fixed? → User decides their time tolerance based on urgency
|
||||
|
||||
---
|
||||
|
||||
```solidity
|
||||
// L92-94: Input validation
|
||||
require(amountIn > 0, "Invalid input amount");
|
||||
require(minAmountOut > 0, "Invalid minimum output");
|
||||
require(tokenIn != tokenOut, "Identical tokens");
|
||||
```
|
||||
- **What:** Validates basic input sanity (non-zero amounts, different tokens)
|
||||
- **Why here:** Second line of defense; cheap checks before expensive operations
|
||||
- **Assumption:** Zero amounts indicate user error, not intentional probe
|
||||
- **Invariant established:** `amountIn > 0 && minAmountOut > 0 && tokenIn != tokenOut`
|
||||
- **First Principles:** Fail fast on invalid input before consuming gas on computation/storage
|
||||
- **5 Hows:**
|
||||
- How to ensure valid swap? → Check inputs meet minimum requirements
|
||||
- How to check minimum requirements? → Test amounts > 0 and tokens differ
|
||||
- How to handle violations? → Revert with descriptive error
|
||||
- How to order checks? → Cheapest first (inequality checks before storage reads)
|
||||
- How to communicate failure? → Require statements with clear messages
|
||||
|
||||
---
|
||||
|
||||
```solidity
|
||||
// L98-99: Pool resolution
|
||||
address pair = pairs[tokenIn][tokenOut];
|
||||
require(pair != address(0), "Pool does not exist");
|
||||
```
|
||||
- **What:** Looks up liquidity pool address for token pair, validates existence
|
||||
- **Why here:** Must identify pool before reading reserves or executing transfers
|
||||
- **Assumption:** `pairs` mapping is correctly populated during pool creation; no race conditions
|
||||
- **Depends on:** Factory having called createPair(tokenIn, tokenOut) previously
|
||||
- **Invariant established:** `pair != 0x0` (valid pool address exists)
|
||||
- **Risk:** If pairs mapping is corrupted or pool address is incorrect, funds could be sent to wrong address
|
||||
|
||||
---
|
||||
|
||||
```solidity
|
||||
// L102-103: Reserve reads
|
||||
(uint112 reserveIn, uint112 reserveOut) = getReserves(pair, tokenIn, tokenOut);
|
||||
require(reserveIn > 0 && reserveOut > 0, "Insufficient liquidity");
|
||||
```
|
||||
- **What:** Reads current pool reserves for tokenIn and tokenOut, validates pool has liquidity
|
||||
- **Why here:** Need current reserves to calculate output amount; must confirm pool is operational
|
||||
- **Assumption:** `reserves[pair]` storage is synchronized with actual pool token balances
|
||||
- **Invariant established:** `reserveIn > 0 && reserveOut > 0` (pool is liquid)
|
||||
- **Depends on:** Sync mechanism keeping reserves accurate (called after transfers/swaps)
|
||||
- **5 Whys:**
|
||||
- Why read reserves? → Need current pool state for price calculation
|
||||
- Why must reserves be > 0? → Division by zero in formula if empty
|
||||
- Why check liquidity here? → Cheaper to fail now than after transferFrom
|
||||
- Why not just try the swap? → Better UX with specific error message
|
||||
- Why trust reserves storage? → Alternative is querying balances (expensive)
|
||||
|
||||
---
|
||||
|
||||
```solidity
|
||||
// L108-109: Fee application
|
||||
uint256 amountInWithFee = amountIn * 997;
|
||||
uint256 numerator = amountInWithFee * reserveOut;
|
||||
```
|
||||
- **What:** Applies 0.3% protocol fee by multiplying amountIn by 997 (instead of deducting 3)
|
||||
- **Why here:** Fee must be applied before price calculation to affect output amount
|
||||
- **Assumption:** 997/1000 = 0.997 = (1 - 0.003) represents 0.3% fee deduction
|
||||
- **Invariant maintained:** `amountInWithFee = amountIn * 0.997` (3/1000 fee taken)
|
||||
- **First Principles:** Fees modify effective input, reducing output proportionally
|
||||
- **5 Whys:**
|
||||
- Why multiply by 997? → Gas optimization: avoids separate subtraction step
|
||||
- Why not amountIn * 0.997? → Solidity doesn't support floating point
|
||||
- Why 0.3% fee? → Protocol parameter (Uniswap V2 standard, commonly copied)
|
||||
- Why apply before calculation? → Fee reduces input amount, must affect price
|
||||
- Why not apply after? → Would incorrectly calculate output at full amountIn
|
||||
|
||||
---
|
||||
|
||||
```solidity
|
||||
// L110-111: Output calculation (constant product formula)
|
||||
uint256 denominator = (reserveIn * 1000) + amountInWithFee;
|
||||
uint256 amountOut = numerator / denominator;
|
||||
```
|
||||
- **What:** Calculates output amount using AMM constant product formula: `Δy = (x * Δx_fee) / (y + Δx_fee)`
|
||||
- **Why here:** After fee application; core pricing logic of the AMM
|
||||
- **Assumption:** `k = reserveIn * reserveOut` is the invariant to maintain (with fee adding to k)
|
||||
- **Invariant formula:** `(reserveIn + amountIn) * (reserveOut - amountOut) >= reserveIn * reserveOut`
|
||||
- **First Principles:** Constant product AMM maintains `x * y = k` (with fee slightly increasing k)
|
||||
- **5 Whys:**
|
||||
- Why this formula? → Constant product market maker (x * y = k)
|
||||
- Why not linear pricing? → Would drain pool at constant price (exploitable)
|
||||
- Why multiply reserveIn by 1000? → Match denominator scale with numerator (997 * 1000)
|
||||
- Why divide? → Solving for Δy in: (x + Δx_fee) * (y - Δy) = k
|
||||
- Why this maintains k? → New product = (reserveIn + amountIn*0.997) * (reserveOut - amountOut) ≈ k * 1.003
|
||||
- **Mathematical verification:**
|
||||
- Given: `k = reserveIn * reserveOut`
|
||||
- New reserves: `reserveIn' = reserveIn + amountIn`, `reserveOut' = reserveOut - amountOut`
|
||||
- With fee: `amountInWithFee = amountIn * 0.997`
|
||||
- Solving `(reserveIn + amountIn) * (reserveOut - amountOut) = k`:
|
||||
- `reserveOut - amountOut = k / (reserveIn + amountIn)`
|
||||
- `amountOut = reserveOut - k / (reserveIn + amountIn)`
|
||||
- Substituting and simplifying yields the formula above
|
||||
|
||||
---
|
||||
|
||||
```solidity
|
||||
// L115: Slippage protection enforcement
|
||||
require(amountOut >= minAmountOut, "Slippage exceeded");
|
||||
```
|
||||
- **What:** Validates calculated output meets user's minimum acceptable amount
|
||||
- **Why here:** After calculation, before any state changes or transfers (fail fast if insufficient)
|
||||
- **Assumption:** User calculated minAmountOut correctly based on acceptable slippage tolerance
|
||||
- **Invariant enforced:** `amountOut >= minAmountOut` (user-defined slippage limit)
|
||||
- **First Principles:** User must explicitly consent to price via slippage tolerance; prevents sandwich attacks
|
||||
- **5 Whys:**
|
||||
- Why check minAmountOut? → Protect user from excessive slippage
|
||||
- Why is slippage protection critical? → Prevents sandwich attacks and MEV extraction
|
||||
- Why user-specified? → Different users have different risk tolerances
|
||||
- Why fail here vs warn? → Financial safety: user should not receive less than intended
|
||||
- Why before transfers? → Cheaper to revert now than after expensive external calls
|
||||
- **Attack scenario prevented:**
|
||||
- Attacker front-runs with large buy → price increases
|
||||
- Victim's swap would execute at worse price
|
||||
- This check causes victim's transaction to revert instead
|
||||
- Attacker cannot profit from sandwich
|
||||
|
||||
---
|
||||
|
||||
```solidity
|
||||
// L118: Input token transfer (pull pattern)
|
||||
IERC20(tokenIn).transferFrom(msg.sender, pair, amountIn);
|
||||
```
|
||||
- **What:** Pulls tokenIn from user to liquidity pool
|
||||
- **Why here:** After all validations pass; begins state-changing operations (point of no return)
|
||||
- **Assumption:** User has approved Router for at least amountIn; tokenIn is standard ERC20
|
||||
- **Depends on:** Prior approval: `tokenIn.approve(router, amountIn)` called by user
|
||||
- **Risk considerations:**
|
||||
- If tokenIn is malicious: could revert (DoS), consume excessive gas, or attempt reentrancy
|
||||
- If tokenIn has transfer fee: actual amount received < amountIn (breaks invariant)
|
||||
- If tokenIn is pausable: could revert if paused
|
||||
- Reentrancy: If tokenIn has callback, attacker could call Router again (mitigated by nonReentrant modifier)
|
||||
- **First Principles:** Pull pattern (transferFrom) is safer than users sending first (push) - Router controls timing
|
||||
- **5 Hows:**
|
||||
- How to get tokenIn? → Pull from user via transferFrom
|
||||
- How to ensure Router can pull? → User must have approved Router
|
||||
- How to specify destination? → Send directly to pair (gas optimization: no router intermediate storage)
|
||||
- How to handle failures? → transferFrom reverts on failure (ERC20 standard)
|
||||
- How to prevent reentrancy? → nonReentrant modifier (assumed present)
|
||||
|
||||
---
|
||||
|
||||
```solidity
|
||||
// L122: Output token transfer (push pattern)
|
||||
IERC20(tokenOut).transfer(msg.sender, amountOut);
|
||||
```
|
||||
- **What:** Sends calculated amountOut of tokenOut from pool to user
|
||||
- **Why here:** After input transfer succeeds; completes the swap atomically
|
||||
- **Assumption:** Pool has at least amountOut of tokenOut; tokenOut is standard ERC20
|
||||
- **Invariant maintained:** User receives exact amountOut (no more, no less)
|
||||
- **Risk considerations:**
|
||||
- If tokenOut is malicious: could revert (DoS), but user selected this token pair
|
||||
- If tokenOut has transfer hook: could attempt reentrancy (mitigated by nonReentrant)
|
||||
- If transfer fails: entire transaction reverts (atomic swap)
|
||||
- **CEI pattern:** Not strictly followed (Check-Effects-Interactions) - both transfers are interactions
|
||||
- Typically Effects (reserve update) should precede Interactions (transfers)
|
||||
- Here, transfers happen before reserve update (see next block)
|
||||
- Justification: nonReentrant modifier prevents exploitation
|
||||
- **5 Whys:**
|
||||
- Why transfer to msg.sender? → User initiated swap, they receive output
|
||||
- Why not to an arbitrary recipient? → Simplicity; extensions can add recipient parameter
|
||||
- Why this amount exactly? → amountOut calculated from constant product formula
|
||||
- Why after input transfer? → Ensures atomicity: both succeed or both fail
|
||||
- Why trust pool has balance? → Pool's job to maintain reserves; if insufficient, transfer reverts
|
||||
|
||||
---
|
||||
|
||||
```solidity
|
||||
// L125-126: Reserve synchronization
|
||||
reserves[pair].reserve0 = uint112(reserveIn + amountIn);
|
||||
reserves[pair].reserve1 = uint112(reserveOut - amountOut);
|
||||
```
|
||||
- **What:** Updates stored reserves to reflect post-swap balances
|
||||
- **Why here:** After transfers complete; brings storage in sync with actual balances
|
||||
- **Assumption:** No other operations have modified pool balances since reserves were read
|
||||
- **Invariant maintained:** `reserve0 * reserve1 >= k_before * 1.003` (constant product + fee)
|
||||
- **Casting risk:** `uint112` casting could truncate if reserves exceed 2^112 - 1 (≈ 5.2e33)
|
||||
- For most tokens with 18 decimals: limit is ~5.2e15 tokens
|
||||
- Overflow protection: require reserves fit in uint112, else revert
|
||||
- **5 Whys:**
|
||||
- Why update reserves? → Storage must match actual balances for next swap
|
||||
- Why after transfers? → Need to know final state before recording
|
||||
- Why not query balances? → Gas optimization: storage update cheaper than CALL + BALANCE
|
||||
- Why uint112? → Pack two reserves in one storage slot (256 bits = 2 * 112 + 32 for timestamp)
|
||||
- Why this formula? → reserveIn increased by amountIn, reserveOut decreased by amountOut
|
||||
- **Invariant verification:**
|
||||
- Before: `k_before = reserveIn * reserveOut`
|
||||
- After: `k_after = (reserveIn + amountIn) * (reserveOut - amountOut)`
|
||||
- With 0.3% fee: `k_after ≈ k_before * 1.003` (fee adds permanent liquidity)
|
||||
|
||||
---
|
||||
|
||||
```solidity
|
||||
// L130: Event emission
|
||||
emit Swap(msg.sender, tokenIn, tokenOut, amountIn, amountOut, block.timestamp);
|
||||
```
|
||||
- **What:** Emits event logging swap details for off-chain indexing
|
||||
- **Why here:** After all state changes finalized; last operation before return
|
||||
- **Assumption:** Event watchers (subgraphs, dex aggregators) rely on this for tracking trades
|
||||
- **Data included:**
|
||||
- `msg.sender`: Who initiated swap (for user trade history)
|
||||
- `tokenIn/tokenOut`: Which pair was traded
|
||||
- `amountIn/amountOut`: Exact amounts for price tracking
|
||||
- `block.timestamp`: When trade occurred (for TWAP calculations, analytics)
|
||||
- **First Principles:** Events are write-only log for off-chain systems; don't affect on-chain state
|
||||
- **5 Hows:**
|
||||
- How to notify off-chain? → Emit event (logs are cheaper than storage)
|
||||
- How to structure event? → Include all relevant swap parameters
|
||||
- How do indexers use this? → Build trade history, calculate volume, track prices
|
||||
- How to ensure consistency? → Emit after state finalized (can't be front-run)
|
||||
- How to query later? → Blockchain logs filtered by event signature + contract address
|
||||
|
||||
---
|
||||
|
||||
**Cross-Function Dependencies:**
|
||||
|
||||
*Internal Calls:*
|
||||
- `getReserves(pair, tokenIn, tokenOut)`: Helper to read and order reserves based on token addresses
|
||||
- Depends on: `reserves[pair]` storage being synchronized
|
||||
- Returns: (reserveIn, reserveOut) in correct order for tokenIn/tokenOut
|
||||
|
||||
*External Calls (Outbound):*
|
||||
- `IERC20(tokenIn).transferFrom(msg.sender, pair, amountIn)`: ERC20 standard call
|
||||
- Assumes: tokenIn implements ERC20, user has approved Router
|
||||
- Reentrancy risk: If tokenIn is malicious, could callback
|
||||
- Failure: Reverts entire transaction
|
||||
- `IERC20(tokenOut).transfer(msg.sender, amountOut)`: ERC20 standard call
|
||||
- Assumes: Pool has sufficient tokenOut balance
|
||||
- Reentrancy risk: If tokenOut has hooks
|
||||
- Failure: Reverts entire transaction
|
||||
|
||||
*Called By:*
|
||||
- Users directly (external call)
|
||||
- Aggregators/routers (external call)
|
||||
- Multi-hop swap functions (internal call from same contract)
|
||||
|
||||
*Shares State With:*
|
||||
- `addLiquidity()`: Modifies same reserves[pair], must maintain k invariant
|
||||
- `removeLiquidity()`: Modifies same reserves[pair]
|
||||
- `sync()`: Emergency function to force reserves sync with balances
|
||||
- `skim()`: Removes excess tokens beyond reserves
|
||||
|
||||
*Invariant Coupling:*
|
||||
- **Global invariant:** `sum(all reserves[pair].reserve0 for all pairs) <= sum(all token balances in pools)`
|
||||
- **Per-pool invariant:** `reserves[pair].reserve0 * reserves[pair].reserve1 >= k_initial * (1.003^n)` where n = number of swaps
|
||||
- Each swap increases k by 0.3% due to fee
|
||||
- **Reentrancy protection:** `nonReentrant` modifier ensures no cross-function reentrancy
|
||||
- swap() cannot be re-entered while executing
|
||||
- addLiquidity/removeLiquidity also cannot execute during swap
|
||||
|
||||
*Assumptions Propagated to Callers:*
|
||||
- Caller must have approved Router to spend amountIn of tokenIn
|
||||
- Caller must set reasonable deadline (e.g., block.timestamp + 300 seconds)
|
||||
- Caller must calculate minAmountOut based on acceptable slippage (e.g., expectedOutput * 0.99 for 1%)
|
||||
- Caller assumes pair exists (or will handle "Pool does not exist" revert)
|
||||
71
audit-context-building/resources/OUTPUT_REQUIREMENTS.md
Normal file
71
audit-context-building/resources/OUTPUT_REQUIREMENTS.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Output Requirements
|
||||
|
||||
When performing ultra-granular analysis, Claude MUST structure output following the Per-Function Microstructure Checklist format demonstrated in [FUNCTION_MICRO_ANALYSIS_EXAMPLE.md](FUNCTION_MICRO_ANALYSIS_EXAMPLE.md).
|
||||
|
||||
---
|
||||
|
||||
## Required Structure
|
||||
|
||||
For EACH analyzed function, output MUST include:
|
||||
|
||||
**1. Purpose** (mandatory)
|
||||
- Clear statement of function's role in the system
|
||||
- Impact on system state, security, or economics
|
||||
- Minimum 2-3 sentences
|
||||
|
||||
**2. Inputs & Assumptions** (mandatory)
|
||||
- All parameters (explicit and implicit)
|
||||
- All preconditions
|
||||
- All trust assumptions
|
||||
- Each input must identify: type, source, trust level
|
||||
- Minimum 3 assumptions documented
|
||||
|
||||
**3. Outputs & Effects** (mandatory)
|
||||
- Return values (or "void" if none)
|
||||
- All state writes
|
||||
- All external interactions
|
||||
- All events emitted
|
||||
- All postconditions
|
||||
- Minimum 3 effects documented
|
||||
|
||||
**4. Block-by-Block Analysis** (mandatory)
|
||||
For EACH logical code block, document:
|
||||
- **What:** What the block does (1 sentence)
|
||||
- **Why here:** Why this ordering/placement (1 sentence)
|
||||
- **Assumptions:** What must be true (1+ items)
|
||||
- **Depends on:** What prior state/logic this relies on
|
||||
- **First Principles / 5 Whys / 5 Hows:** Apply at least ONE per block
|
||||
|
||||
Minimum standards:
|
||||
- Analyze at minimum: ALL conditional branches, ALL external calls, ALL state modifications
|
||||
- For complex blocks (>5 lines): Apply First Principles AND 5 Whys or 5 Hows
|
||||
- For simple blocks (<5 lines): Minimum What + Why here + 1 Assumption
|
||||
|
||||
**5. Cross-Function Dependencies** (mandatory)
|
||||
- Internal calls made (list all)
|
||||
- External calls made (list all with risk analysis)
|
||||
- Functions that call this function
|
||||
- Shared state with other functions
|
||||
- Invariant couplings (how this function's invariants interact with others)
|
||||
- Minimum 3 dependency relationships documented
|
||||
|
||||
---
|
||||
|
||||
## Quality Thresholds
|
||||
|
||||
A complete micro-analysis MUST identify:
|
||||
- Minimum 3 invariants (per function)
|
||||
- Minimum 5 assumptions (across all sections)
|
||||
- Minimum 3 risk considerations (especially for external interactions)
|
||||
- At least 1 application of First Principles
|
||||
- At least 3 applications of 5 Whys or 5 Hows (combined)
|
||||
|
||||
---
|
||||
|
||||
## Format Consistency
|
||||
|
||||
- Use markdown headers: `**Section Name:**` for major sections
|
||||
- Use bullet points (`-`) for lists
|
||||
- Use code blocks (` ```solidity `) for code snippets
|
||||
- Reference line numbers: `L45`, `lines 98-102`
|
||||
- Separate blocks with `---` horizontal rules for readability
|
||||
Reference in New Issue
Block a user